use std::{ env::temp_dir, fs::remove_dir_all, panic, path::PathBuf, process::{Child, Command}, time::Duration, }; use anyhow::{anyhow, Context, Result}; use nom_nom_gc::{config::{BinaryCacheConfig, Config, DBConfig}, models::{AppState, BinaryCache, BinaryCacheType, Project, User, UserUuid}}; use tokio::time::sleep; use uuid::Uuid; struct TestDB { path: PathBuf, pid: Child, db_name: String, port: u16, } async fn setup_db() -> Result { let mut dbdir = temp_dir(); let dir_uuid = Uuid::new_v4(); let db_name = "nom-nom-integration-test".to_string(); let port: u16 = 12345; dbdir.push(dir_uuid.to_string()); let dbdir_str = dbdir.to_str().unwrap(); Command::new("initdb") .args(["--encoding", "UTF8", &dbdir.to_str().unwrap()]) .env("LC_ALL", "C") .env("LC_CTYPE", "C") .env("LANG", "C") .env("LC_*", "C") .spawn()? .wait()?; let db_proc_handle = Command::new("postgres") .args([ "-D", dbdir_str, "-c", &format!("unix_socket_directories={}", dbdir_str), "-c", "listen_addresses=", "-c", &format!("port={}", &port.to_string()), ]) .spawn()?; sleep(Duration::from_secs(1)).await; Command::new("createdb") .args(["-h", dbdir_str, "-p", &port.to_string(), &db_name]) .spawn()? .wait()?; Ok(TestDB { path: dbdir, pid: db_proc_handle, db_name, port, }) } fn teardown_db(mut db: TestDB) -> Result<()> { println!("Stopping postgres"); db.pid.kill()?; remove_dir_all(db.path)?; Ok(()) } /** This function is the actual test main function. We can't inline it in main, we need to make sure to teardown the DB setup even if it fails. */ async fn run_test_db(db: &TestDB) -> Result<()> { let mut dbpath = db.path.to_str().unwrap().to_string(); dbpath.push('/'); let conf = Config { url: "http://localhost:9000".to_string(), db_config: DBConfig { port: db.port, host: dbpath, db_name: db.db_name.clone(), }, binary_caches: [BinaryCacheConfig { name: "dummy".to_string(), bucket: "dummy".to_string(), endpoint_url: "dummy".to_string(), region: "dummy".to_string(), access_key: "dummy".to_string(), secret_key_path: PathBuf::from("/dummy"), }].to_vec() }; let state = AppState::new(conf).await; state.run_migrations().await.context("migrations failed")?; let test_user = User { uuid: UserUuid(Uuid::new_v4()), name: "test-user".to_owned(), }; state .save_user(&test_user) .await .context("should save user")?; let reg_uuid = state .generate_registration_uuid(&test_user.uuid) .await .context("should generate registration uuid for test user")?; let usr2 = state.retrieve_registration_user(®_uuid).await?; let usr2 = usr2.ok_or_else(|| anyhow!("state.retrieve_registration_user(®_uuid) returns Nothing"))?; if test_user != usr2 { return Err(anyhow!("test_user != usr2: {:?} != {:?}", test_user, usr2)); } let binary_cache = BinaryCache { name: "test-cache".to_string(), access_key: "access key".to_string(), secret_key: "secret key".to_string(), region: "reg-01".to_string(), endpoint_url: "localhost://".to_string(), bucket: "nix-cache".to_string(), setup: BinaryCacheType::Interactive }; state .create_binary_cache(&binary_cache) .await .context("binary cache creation failed")?; let project = Project { name: "super-duper-project".to_string(), latest_closure_generation: 0, }; let _ = state .create_project(&binary_cache, &project) .await .context("project creation failed")?; let token = state .create_project_token(&project) .await .context("project token creation failed")?; let project2 = state .get_project(&token) .await .context("cannot retrieve project")?; if project != project2 { return Err(anyhow!( "project != project2: {:?} != {:?}", project, project2 )); } assert_eq!(project, project2); Ok(()) } #[tokio::test] async fn test_db() { let mdb = setup_db().await; let db = mdb.expect("setup db"); let res = run_test_db(&db).await; teardown_db(db).expect("Failed to teardown DB."); res.unwrap_or_else(|e| panic!("{}", e)); }