mod apps;
mod err;

use std::sync::Arc;

use parking_lot::Mutex;

use qsu::rt::RunCtx;

use err::Error;

const SVCNAME: &str = "svctest";

/// Returning an `Err(AppErr)` from `init()` should return it back to the
/// application's initial call to kick off the runtime.
#[test]
fn error_from_sync_init() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));

  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MySyncService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_init()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_sync(svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only init() failed
  assert!(errs.init.is_some());
  assert!(errs.run.is_none());
  assert!(errs.shutdown.is_none());

  let Error::Hello(s) = errs.init.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Sync::init()");

  // Failing init should cause run() not to be called, but shutdown() should
  // sitll be called.
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(!visited.run);
  assert!(visited.shutdown);
}

/// Returning an `Err(AppErr)` from `run()` should return it back to the
/// application's initial call to kick off the runtime.
#[test]
fn error_from_sync_run() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MySyncService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_run()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_sync(svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only run() failed
  assert!(errs.init.is_none());
  assert!(errs.run.is_some());
  assert!(errs.shutdown.is_none());

  let Error::Hello(s) = errs.run.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Sync::run()");

  // Failing run should not hinder shutdown() from being called.
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(visited.run);
  assert!(visited.shutdown);
}

/// Returning an `Err(AppErr)` from `run()` should return it back to the
/// application's initial call to kick off the runtime.
#[test]
fn error_from_sync_shutdown() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MySyncService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_shutdown()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_sync(svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only shutdown() failed
  assert!(errs.init.is_none());
  assert!(errs.run.is_none());
  assert!(errs.shutdown.is_some());

  let Error::Hello(s) = errs.shutdown.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Sync::shutdown()");

  // All callbacks should have been visited
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(visited.run);
  assert!(visited.shutdown);
}


/// Returning an `Err(AppErr)` from `init()` should return it back to the
/// application's initial call to kick off the runtime.
#[cfg(feature = "tokio")]
#[test]
fn error_from_tokio_init() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MyTokioService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_init()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_tokio(None, svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only init() failed
  assert!(errs.init.is_some());
  assert!(errs.run.is_none());
  assert!(errs.shutdown.is_none());

  let Error::Hello(s) = errs.init.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Tokio::init()");

  // Failing init should cause run() not to be called, but shutdown() should
  // sitll be called.
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(!visited.run);
  assert!(visited.shutdown);
}

/// Returning an `Err(AppErr)` from `run()` should return it back to the
/// application's initial call to kick off the runtime.
#[cfg(feature = "tokio")]
#[test]
fn error_from_tokio_run() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MyTokioService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_run()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_tokio(None, svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only run() failed
  assert!(errs.init.is_none());
  assert!(errs.run.is_some());
  assert!(errs.shutdown.is_none());

  let Error::Hello(s) = errs.run.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Tokio::run()");

  // Failing run should not hinder shutdown() from being called.
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(visited.run);
  assert!(visited.shutdown);
}


/// Returning an `Err(AppErr)` from `run()` should return it back to the
/// application's initial call to kick off the runtime.
#[cfg(feature = "tokio")]
#[test]
fn error_from_tokio_shutdown() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MyTokioService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_shutdown()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_tokio(None, svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only shutdown() failed
  assert!(errs.init.is_none());
  assert!(errs.run.is_none());
  assert!(errs.shutdown.is_some());

  let Error::Hello(s) = errs.shutdown.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Tokio::shutdown()");

  // All callbacks should have been visited
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(visited.run);
  assert!(visited.shutdown);
}


/// Returning an `Err(AppErr)` from `init()` should return it back to the
/// application's initial call to kick off the runtime.
#[cfg(feature = "rocket")]
#[test]
fn error_from_rocket_init() {
  let runctx = RunCtx::new(SVCNAME).test_mode();

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MyRocketService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_init()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_rocket(svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only init() failed
  assert!(errs.init.is_some());
  assert!(errs.run.is_none());
  assert!(errs.shutdown.is_none());

  let Error::Hello(s) = errs.init.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Rocket::init()");

  // Failing init should cause run() not to be called, but shutdown() should
  // sitll be called.
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(!visited.run);
  assert!(visited.shutdown);
}

/// Returning an `Err(AppErr)` from `run()` should return it back to the
/// application's initial call to kick off the runtime.
#[cfg(feature = "rocket")]
#[test]
fn error_from_rocket_run() {
  let runctx = RunCtx::new(SVCNAME).log_init(false);

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MyRocketService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_run()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_rocket(svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only run() failed
  assert!(errs.init.is_none());
  assert!(errs.run.is_some());
  assert!(errs.shutdown.is_none());

  let Error::Hello(s) = errs.run.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Rocket::run()");

  // Failing run should not hinder shutdown() from being called.
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(visited.run);
  assert!(visited.shutdown);
}

/// Returning an `Err(AppErr)` from `run()` should return it back to the
/// application's initial call to kick off the runtime.
#[cfg(feature = "rocket")]
#[test]
fn error_from_rocket_shutdown() {
  let runctx = RunCtx::new(SVCNAME).log_init(false);

  // Prepare a server application context which keeps track of which callbacks
  // have been called
  let visited = Arc::new(Mutex::new(apps::Visited::default()));
  let svcevt_handler = Box::new(move |_msg| {});
  let rt_handler = Box::new(
    apps::MyRocketService {
      visited: Arc::clone(&visited),
      ..Default::default()
    }
    .fail_shutdown()
  );

  // Call RunCtx::run(), expecting an server application callback error.
  let Err(qsu::CbErr::SrvApp(errs)) =
    runctx.run_rocket(svcevt_handler, rt_handler)
  else {
    panic!("Not expected Err(qsu::Error::SrvApp(_))");
  };

  // Verify that only shutdown() failed
  assert!(errs.init.is_none());
  assert!(errs.run.is_none());
  assert!(errs.shutdown.is_some());

  let Error::Hello(s) = errs.shutdown.unwrap() else {
    panic!("Not expected Error::Hello");
  };
  assert_eq!(s, "From Rocket::shutdown()");

  // All callbacks should have been visited
  let visited = Arc::into_inner(visited)
    .expect("Unable to into_inner Arc")
    .into_inner();
  assert!(visited.init);
  assert!(visited.run);
  assert!(visited.shutdown);
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
