Skip to main content

sofie_testing/
fake.rs

1use std::sync::{Arc, Mutex, MutexGuard};
2
3use sofie_core::{Error, Result, RowReader, SqlValue, unpoisoned};
4use sofie_db::{BoxFuture, Dialect, Driver, MessageSql, Migrations, RowSink, Step};
5
6/// A test build chooses no gateway adapter, so a test that reaches a gateway provides its fake here.
7#[cfg(feature = "contracts")]
8pub fn fake<G: ?Sized + Send + Sync + 'static>(app: &crate::TestApp, fake: Arc<G>) {
9    sofie_db::provide::<G>(app.db(), fake);
10}
11
12#[derive(Clone, Default)]
13pub struct FakeDriver {
14    record: Arc<Mutex<Record>>,
15    rows: Arc<Vec<Vec<SqlValue>>>,
16    is_down: bool,
17}
18
19#[derive(Default)]
20struct Record {
21    queries: Vec<(String, Vec<SqlValue>)>,
22    transactions: Vec<Vec<Step>>,
23}
24
25impl FakeDriver {
26    #[must_use]
27    pub fn returning(rows: Vec<Vec<SqlValue>>) -> Self {
28        Self {
29            rows: Arc::new(rows),
30            ..Self::default()
31        }
32    }
33
34    #[must_use]
35    pub fn down() -> Self {
36        Self {
37            is_down: true,
38            ..Self::default()
39        }
40    }
41
42    #[must_use]
43    pub fn queries(&self) -> Vec<(String, Vec<SqlValue>)> {
44        self.record().queries.clone()
45    }
46
47    #[must_use]
48    pub fn transaction_count(&self) -> usize {
49        self.record().transactions.len()
50    }
51
52    #[must_use]
53    pub fn take_transactions(&self) -> Vec<Vec<Step>> {
54        std::mem::take(&mut self.record().transactions)
55    }
56
57    fn record(&self) -> MutexGuard<'_, Record> {
58        unpoisoned::lock(&self.record)
59    }
60
61    fn outcome(&self) -> Result<()> {
62        if self.is_down {
63            return Err(Error::internal_message("connection refused"));
64        }
65        Ok(())
66    }
67}
68
69impl Driver for FakeDriver {
70    fn dialect(&self) -> &dyn Dialect {
71        &FakeDialect
72    }
73
74    fn query<'a>(
75        &'a self,
76        sql: &'a str,
77        params: &'a [SqlValue],
78        rows: &'a mut dyn RowSink,
79    ) -> BoxFuture<'a, Result<()>> {
80        self.record()
81            .queries
82            .push((sql.to_owned(), params.to_vec()));
83        let answer = self.outcome().and_then(|()| {
84            self.rows
85                .iter()
86                .try_for_each(|values| rows.row(&mut RowReader::new(values)))
87        });
88        Box::pin(std::future::ready(answer))
89    }
90
91    fn transaction(&self, steps: Vec<Step>) -> BoxFuture<'_, Result<()>> {
92        self.record().transactions.push(steps);
93        Box::pin(std::future::ready(self.outcome()))
94    }
95}
96
97struct FakeDialect;
98
99const NO_MESSAGES: MessageSql = MessageSql {
100    insert: "",
101    fan_out: "",
102    fan_out_events: "",
103    claim: "",
104    renew: "",
105    complete: "",
106    retry: "",
107    bury: "",
108    to_job: "",
109    add_schedule: "",
110    fire_schedule: "",
111    notify: None,
112};
113
114impl Dialect for FakeDialect {
115    fn push_placeholder(&self, sql: &mut String, index: usize) {
116        sql.push('$');
117        sql.push_str(&index.to_string());
118    }
119
120    fn migrations_setup(&self) -> &'static str {
121        ""
122    }
123
124    fn migrations_lock(&self) -> &'static str {
125        ""
126    }
127
128    fn enter_schema(&self, _schema: &str) -> String {
129        String::new()
130    }
131
132    fn framework_migrations(&self) -> Migrations {
133        Migrations::new("sofie", &[])
134    }
135
136    fn messages(&self) -> &'static MessageSql {
137        &NO_MESSAGES
138    }
139}