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}