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}