Skip to main content

storekit/
fixtures.rs

1use crate::paths::App;
2use crate::settings;
3use rusqlite::Connection;
4
5const SEEDED_KEY: &str = "fixture_seeded";
6
7pub trait Fixture {
8    fn name(&self) -> &str;
9    fn seed(&self, connection: &Connection) -> rusqlite::Result<()>;
10}
11
12pub fn requested(app: &App) -> Option<String> {
13    std::env::var(app.env_key("FIXTURES"))
14        .ok()
15        .map(|name| name.trim().to_owned())
16        .filter(|name| !name.is_empty())
17}
18
19pub fn seed_if_requested(
20    connection: &Connection,
21    app: &App,
22    fixtures: &[&dyn Fixture],
23) -> rusqlite::Result<Option<String>> {
24    seed_named(connection, requested(app), fixtures)
25}
26
27fn seed_named(
28    connection: &Connection,
29    requested: Option<String>,
30    fixtures: &[&dyn Fixture],
31) -> rusqlite::Result<Option<String>> {
32    let Some(name) = requested else {
33        return Ok(None);
34    };
35    if settings::get(connection, SEEDED_KEY)?.as_deref() == Some(&name) {
36        return Ok(Some(name));
37    }
38    let fixture = fixtures
39        .iter()
40        .find(|fixture| fixture.name() == name)
41        .ok_or_else(|| rusqlite::Error::InvalidParameterName(name.clone()))?;
42    fixture.seed(connection)?;
43    settings::set(connection, SEEDED_KEY, &name)?;
44    Ok(Some(name))
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::settings::SETTINGS_TABLE_SQL;
51
52    struct Demo;
53
54    impl Fixture for Demo {
55        fn name(&self) -> &str {
56            "demo"
57        }
58
59        fn seed(&self, connection: &Connection) -> rusqlite::Result<()> {
60            connection.execute("INSERT INTO fixture_rows DEFAULT VALUES", [])?;
61            Ok(())
62        }
63    }
64
65    fn connection() -> Connection {
66        let connection = Connection::open_in_memory().unwrap();
67        connection
68            .execute_batch(&format!(
69                "{SETTINGS_TABLE_SQL} CREATE TABLE fixture_rows(id INTEGER PRIMARY KEY);"
70            ))
71            .unwrap();
72        connection
73    }
74
75    #[test]
76    fn the_fixture_door_seeds_once_and_only_when_asked() {
77        let connection = connection();
78        let fixtures: &[&dyn Fixture] = &[&Demo];
79        assert_eq!(seed_named(&connection, None, fixtures).unwrap(), None);
80        assert_eq!(count(&connection), 0);
81        assert_eq!(
82            seed_named(&connection, Some("demo".to_owned()), fixtures).unwrap(),
83            Some("demo".to_owned())
84        );
85        assert_eq!(count(&connection), 1);
86        seed_named(&connection, Some("demo".to_owned()), fixtures).unwrap();
87        assert_eq!(count(&connection), 1, "the named fixture is seeded once");
88    }
89
90    fn count(connection: &Connection) -> i64 {
91        connection
92            .query_row("SELECT COUNT(*) FROM fixture_rows", [], |row| row.get(0))
93            .unwrap()
94    }
95}