Skip to main content

sofie_testing/
fixture.rs

1use std::future::IntoFuture;
2use std::pin::Pin;
3use std::sync::MutexGuard;
4
5use serde::Serialize;
6use serde_json::Value as Json;
7use sofie_core::unpoisoned;
8use sofie_db::{Aggregate, Create, Id, Input};
9use sofie_values::{Fixture, InputFixture, Seed};
10
11use crate::TestApp;
12
13pub trait FixtureOwner {
14    fn fixture_owner(app: &TestApp) -> Self;
15}
16
17impl FixtureOwner for () {
18    fn fixture_owner(_app: &TestApp) -> Self {}
19}
20
21impl<T> FixtureOwner for Id<T> {
22    fn fixture_owner(app: &TestApp) -> Self {
23        Id::from_uuid(fixture_value(app))
24    }
25}
26
27#[must_use = "a fixture is made only when awaited"]
28pub struct AggregateFixture<'app, A: Create> {
29    app: &'app TestApp,
30    owner: Option<A::Owner>,
31    fields: Vec<(String, Json)>,
32}
33
34impl<'app, A: Create> AggregateFixture<'app, A> {
35    pub(crate) fn new(app: &'app TestApp) -> Self {
36        Self {
37            app,
38            owner: None,
39            fields: Vec::new(),
40        }
41    }
42
43    pub fn owned_by(self, owner: A::Owner) -> Self {
44        Self {
45            owner: Some(owner),
46            ..self
47        }
48    }
49
50    pub fn with(mut self, field: &str, value: impl Serialize) -> Self {
51        let value = serde_json::to_value(value).expect("a field's value serializes to JSON");
52        self.fields.push((field.to_owned(), value));
53        self
54    }
55}
56
57impl<'app, A> IntoFuture for AggregateFixture<'app, A>
58where
59    A: Aggregate + Create<Owner: FixtureOwner + Send, Input: InputFixture>,
60{
61    type Output = A;
62    type IntoFuture = Pin<Box<dyn Future<Output = A> + Send + 'app>>;
63
64    fn into_future(self) -> Self::IntoFuture {
65        let mut input = fixture_input::<A::Input>(self.app);
66        for (field, value) in self.fields {
67            input[field] = value;
68        }
69        let owner = self
70            .owner
71            .unwrap_or_else(|| A::Owner::fixture_owner(self.app));
72        let app = self.app;
73        Box::pin(async move {
74            let input = A::Input::from_json(input).unwrap_or_else(|error| {
75                panic!("a fixture {} input is refused: {error:?}", A::NAME)
76            });
77            let mut record = A::create(input, owner).unwrap_or_else(|error| {
78                panic!("the factory refuses a fixture {}: {error:?}", A::NAME)
79            });
80            app.db()
81                .commit(&mut record)
82                .await
83                .unwrap_or_else(|error| panic!("committing a fixture {}: {error:?}", A::NAME));
84            record
85        })
86    }
87}
88
89pub fn fixture_input<I: InputFixture>(app: &TestApp) -> Json {
90    I::fixture_json(&mut seed(app))
91}
92
93pub fn fixture_value<T: Fixture>(app: &TestApp) -> T {
94    T::fixture(&mut seed(app))
95}
96
97fn seed(app: &TestApp) -> MutexGuard<'_, Seed> {
98    unpoisoned::lock(&app.seed)
99}