use chrono::{Duration, Utc}; use cot::db::Model; use meguri::{ domain::*, models::UserRow, service, store::{Store, WorkspaceRecord}, }; use uuid::Uuid; pub struct Fixture { pub db: Store, pub people: [User; 3], pub team: WorkspaceRecord, pub schedule: Uuid, pub start: chrono::DateTime, } impl Fixture { pub async fn new() -> Self { let url = std::env::var("MEGURI_TEST_DATABASE_URL") .expect("Set MEGURI_TEST_DATABASE_URL to a disposable PostgreSQL database"); let db = Store::connect(&url).await.unwrap(); let mut people = vec![]; for name in ["Alice", "Bob", "Carol"] { let user = User { language: None, id: Uuid::new_v4(), username: format!("test-{}", Uuid::new_v4().simple()), display_name: name.into(), country: "CY".into(), timezone: None, }; UserRow { language: None, demo_deleted_at: None, id: user.id.to_string(), username: user.username.clone(), display_name: user.display_name.clone(), country: user.country.clone(), timezone: None, password_hash: "test-only-unusable-password".into(), created_at: Utc::now(), } .insert(&db.db) .await .unwrap(); people.push(user); } let people: [User; 3] = people.try_into().unwrap(); let mut team = service::create_workspace(&db, people[0].id, "Test team".into()) .await .unwrap(); for person in &people[1..] { team = service::set_member( &db, people[0].id, team.id, team.revision, person.username.clone(), Permission::member(), ) .await .unwrap(); } let schedule = Uuid::new_v4(); let day = Utc::now().date_naive() + Duration::days(2); let mut doc = team.document.clone(); doc.timezone = "UTC".into(); doc.schedules.push(Schedule { handover: default_handover(), id: schedule, name: "General".into(), kind: ScheduleKind::Rotation { participants: people.iter().map(|p| p.id).collect(), anchor: day, shift_days: 7, }, }); team = service::update_document(&db, people[0].id, team.id, team.revision, doc) .await .unwrap(); let start = meguri::engine::handover(&team.document, day).unwrap(); Self { db, people, team, schedule, start, } } pub fn leg(&self) -> ReplacementLeg { ReplacementLeg { schedule_id: self.schedule, start: self.start, end: self.start + Duration::days(1), from_user: self.people[0].id, to_user: self.people[1].id, } } pub async fn propose(&mut self, legs: Vec, force: bool) { self.team = service::request_replacement( &self.db, self.people[0].id, self.team.id, service::NewRequest { remaining: false, revision: self.team.revision, reason: "Test replacement".into(), legs, force, }, ) .await .unwrap(); } pub fn decision(&self, action: &str) -> service::RequestDecision { service::RequestDecision { revision: self.team.revision, action: action.into(), reason: "Test decision".into(), } } pub async fn prioritize_publication(&self) { // The worker takes a bounded batch. Repeated runs leave other test tenants // in this disposable database, so explicitly put this test's job first. self.db .db .raw_with( "UPDATE meguri__sync_jobs SET next_attempt='1970-01-01T00:00:00Z' WHERE workspace_id=$1", &[&self.team.id.to_string()], ) .await .unwrap(); } }