Skip to main content

sofie_testing/
start.rs

1use std::sync::Mutex;
2
3use sofie_core::{Result, TestClock, Timestamp};
4use sofie_db::worker::Worker;
5use sofie_db::{Db, Migrations};
6use sofie_http::App;
7use sofie_postgres::Postgres;
8use sofie_values::Seed;
9
10use crate::wiring::{AppWiring, Flags, WorkerWiring};
11use crate::{TestApp, TestDatabase, TestServer};
12
13/// Fixtures are the same in every run unless this names another seed.
14const SEED_VARIABLE: &str = "SOFIE_TEST_SEED";
15const DEFAULT_SEED: u64 = 0x5EED;
16#[cfg(feature = "flags")]
17const FLAGS_ENVIRONMENT: &str = "test";
18
19/// A test whose app needs tables migrates them in `app`, as the binary does only with `SOFIE_MIGRATE=on`.
20pub async fn start<Built>(
21    app: impl FnOnce(Db) -> Built,
22    worker: impl FnOnce(Db) -> Worker,
23) -> TestApp
24where
25    Built: Future<Output = Result<App>>,
26{
27    wired(&[], app, worker).await
28}
29
30#[cfg(feature = "flags")]
31pub async fn start_with_flags<Built>(
32    app: impl FnOnce(Db, Flags) -> Built,
33    worker: impl FnOnce(Db, &Flags) -> Worker,
34) -> TestApp
35where
36    Built: Future<Output = Result<App>>,
37{
38    wired(&[], app, worker).await
39}
40
41pub(crate) async fn wired<A, W, Application, Jobs>(
42    migrations: &[Migrations],
43    app: Application,
44    worker: Jobs,
45) -> TestApp
46where
47    Application: AppWiring<A>,
48    Jobs: WorkerWiring<W>,
49{
50    sofie::fakes_from_tests();
51    let database = TestDatabase::create().await;
52    let clock = TestClock::starting_at(Timestamp::now());
53    let db = Db::new(connect(database.url())).with_clock(clock.clock());
54    sofie_db::keep_raised(&db);
55    db.schema()
56        .migrate(migrations)
57        .await
58        .expect("migrating the test database");
59    #[cfg(feature = "mail")]
60    let outbox = sofie_mail::testing::fake(&db);
61    let flags = start_flags(&db, Application::FLAGS).await;
62    let built: Result<App> = app.build(db.clone(), flags.as_ref()).await;
63    let server = TestServer::start(built.expect("building the app")).await;
64    let postgres = connect(database.url());
65    // A worker a test builds from `app.db()` signs links like the app's own.
66    let db = db.with_signing_keys(server.signing_keys());
67    let worker = worker.build(db.clone(), flags.as_ref());
68    #[cfg(feature = "mail")]
69    let worker = sofie_mail::worker(worker);
70    sofie_db::worker::publish_heard(&worker);
71    TestApp {
72        worker,
73        #[cfg(feature = "mail")]
74        outbox,
75        db,
76        clock,
77        postgres,
78        server,
79        seed: Mutex::new(Seed::new(seed())),
80        #[cfg(feature = "flags")]
81        flags,
82        _database: database,
83    }
84}
85
86fn seed() -> u64 {
87    std::env::var(SEED_VARIABLE).map_or(DEFAULT_SEED, |seed| {
88        seed.parse()
89            .unwrap_or_else(|_| panic!("{SEED_VARIABLE} must be a number, got `{seed}`"))
90    })
91}
92
93async fn start_flags(db: &Db, wanted: bool) -> Option<Flags> {
94    #[cfg(feature = "flags")]
95    if wanted {
96        return Some(
97            Flags::start(db, FLAGS_ENVIRONMENT)
98                .await
99                .expect("starting the feature flags"),
100        );
101    }
102    #[cfg(not(feature = "flags"))]
103    let _ = (db, wanted);
104    None
105}
106
107fn connect(url: &str) -> Postgres {
108    Postgres::connect(url).expect("connecting to the test database")
109}