Skip to main content

sofie_testing/
app.rs

1use std::sync::Mutex;
2use std::time::Duration;
3
4use sofie_core::{SqlValue, TestClock};
5use sofie_db::worker::Worker;
6use sofie_db::{Db, Id};
7use sofie_http::{Area, RoutePath};
8
9use crate::caller::{TestCaller, TestPrincipal};
10use crate::fixture::AggregateFixture;
11use crate::request::{TestRequest, TestSession};
12
13pub struct TestApp {
14    pub(crate) db: Db,
15    pub(crate) clock: TestClock,
16    pub(crate) worker: Worker,
17    pub(crate) postgres: sofie_postgres::Postgres,
18    pub(crate) server: crate::TestServer,
19    pub(crate) seed: Mutex<sofie_values::Seed>,
20    #[cfg(feature = "flags")]
21    pub(crate) flags: Option<sofie_flags::Flags>,
22    #[cfg(feature = "mail")]
23    pub(crate) outbox: sofie_mail::testing::Outbox,
24    /// Declared last, so it is dropped after everything that uses it.
25    pub(crate) _database: crate::TestDatabase,
26}
27
28impl TestApp {
29    #[must_use]
30    pub fn db(&self) -> &Db {
31        &self.db
32    }
33
34    #[must_use]
35    pub fn clock(&self) -> &TestClock {
36        &self.clock
37    }
38
39    #[cfg(feature = "flags")]
40    #[must_use]
41    pub fn flags(&self) -> &sofie_flags::Flags {
42        self.flags
43            .as_ref()
44            .expect("the app was started with flags: `start_with_flags` or a wiring taking them")
45    }
46
47    /// Fails the test when a job fails or panics; `drain_failing` is for failures a test expects.
48    pub async fn drain(&self) -> usize {
49        crate::messages::drain(self).await
50    }
51
52    /// Stops at each moment a schedule, retry or delayed job comes due on the way, draining there.
53    pub async fn advance(&self, by: Duration) -> usize {
54        crate::messages::advance(self, by).await
55    }
56
57    pub async fn query(&self, sql: &str, params: &[SqlValue]) -> Vec<Vec<SqlValue>> {
58        sofie_db::query_values(&self.postgres, sql, params)
59            .await
60            .unwrap_or_else(|error| panic!("running `{sql}`: {error}"))
61    }
62
63    /// `http://` and the address the app is served at, as a browser's `Origin` names it.
64    #[must_use]
65    pub fn origin(&self) -> &str {
66        self.server.origin()
67    }
68
69    pub fn get(&self, path: &str) -> TestRequest {
70        self.server.get(path)
71    }
72
73    pub fn post(&self, path: &str) -> TestRequest {
74        self.server.post(path)
75    }
76
77    pub fn request(&self, route: &RoutePath) -> TestRequest {
78        self.server.request(route.method().clone(), route.path())
79    }
80
81    pub fn as_<A: Area>(&self, principal: &TestPrincipal<A>) -> TestCaller<'_> {
82        TestCaller::new(self, principal)
83    }
84
85    /// Whom a session the app issued signs in to area `A`, if anyone.
86    #[must_use]
87    pub fn principal_of<A: Area>(&self, session: &TestSession) -> Option<TestPrincipal<A>> {
88        let claims = self.server.open(session, crate::messages::now(self))?;
89        (claims.area() == A::NAME).then(|| TestPrincipal::new(Id::from_uuid(claims.principal())))
90    }
91
92    pub fn fixture<A: sofie_db::Create>(&self) -> AggregateFixture<'_, A> {
93        AggregateFixture::new(self)
94    }
95}