use std::env; use anyhow::Context; use base64::URL_SAFE_NO_PAD; use futures::channel::oneshot::channel; use minor_skulk::{build, db::Db}; use password_hash::rand_core::OsRng; use rand::RngCore; use rocket::{ fairing::AdHoc, tokio::{process::Command, spawn}, }; use time::{Duration, OffsetDateTime}; #[macro_use] extern crate rocket; extern crate anyhow; async fn run_pytest( markers: &'static str, port_offset: u16, invite_admin: Option<&'static str>, ) -> anyhow::Result<()> { dotenv::dotenv().ok(); // at this point this is only a test runner to be used by the nix build. env::set_var("ROCKET_LOG_LEVEL", "off"); // allow multiple runs of the server, with different port offsets. let port = 8000 + port_offset; let mail_port = 2525 + port_offset; let (tx, rx) = channel(); let rocket = rocket::build(); let figment = rocket .figment() .clone() .merge((rocket::Config::PORT, port)) .merge(("location", format!("http://localhost:{}", port))) .merge(("mail_port", mail_port)); let figment = if let Some(admin) = invite_admin { figment.merge(("invite_only", true)).merge(("invite_admin_address", admin)) } else { figment.merge(("invite_only", false)) }; let rocket = build(rocket.configure(figment)).await?.attach(AdHoc::on_liftoff( "notify startup", move |rocket| { Box::pin(async move { // add an invite code as-if generated during startup and move it to an // env var, emulating the user looking at the logs and copying the code. // invite_only needs this to function. if invite_admin.is_some() { let db = rocket.state::().unwrap(); let tx = db.begin().await.unwrap(); let mut code = [0; 32]; OsRng.fill_bytes(&mut code); let code = base64::encode_config(code, URL_SAFE_NO_PAD); tx.add_invite_code(&code, OffsetDateTime::now_utc() + Duration::minutes(5)) .await .unwrap(); tx.commit().await.unwrap(); env::set_var("INVITE_CODE", code); } tx.send(()).unwrap(); }) }, )); spawn(async move { rocket.launch().await }); let test = spawn(async move { rx.await.expect("test trigger failed"); let mut child = Command::new("pytest") .arg("-vvv") .arg("-m") .arg(markers) .env("API_PORT", port.to_string()) .env("MAIL_PORT", mail_port.to_string()) .kill_on_drop(true) .spawn() .expect("failed to spawn"); child.wait().await }); assert!(test.await.context("starting pytest")?.context("running pytest")?.success()); Ok(()) } #[async_test] async fn open() -> anyhow::Result<()> { run_pytest("not invite", 100, None).await } #[async_test] async fn invite_only() -> anyhow::Result<()> { run_pytest("invite", 200, Some("test.account.invite@test-auth")).await }