//! Tier-3 integration tests for the `seed` command (ADR-0048, the //! Phase-1 walking skeleton). Covers the parse path (grammar → AST), //! the worker round-trip (rows generated + persisted to CSV), //! reproducibility via a fixed `--seed`, and the single `history.log` //! line for the whole command (ADR-0048 D15 / U3). use rdbms_playground::db::Database; use rdbms_playground::dsl::{ColumnSpec, Command, Type, parse_command}; use rdbms_playground::persistence::Persistence; use rdbms_playground::project; fn rt() -> tokio::runtime::Runtime { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("tokio rt") } fn open_project_db() -> (project::Project, Database, tempfile::TempDir) { let dir = tempfile::tempdir().expect("create tempdir"); let project = project::open_or_create(None, Some(dir.path())).expect("open or create project"); let persistence = Persistence::new(project.path().to_path_buf()); let db = Database::open_with_persistence(project.db_path(), persistence) .expect("open db with persistence"); (project, db, dir) } fn read_csv(project: &project::Project, table: &str) -> Option { std::fs::read_to_string(project.path().join("data").join(format!("{table}.csv"))).ok() } /// `People(id serial pk, name text, email text)` — `id` is autogen /// (excluded from generation, so no PK collisions), `name`/`email` /// are generated. fn create_people(db: &Database, rt: &tokio::runtime::Runtime) { rt.block_on(db.create_table( "People".to_string(), vec![ ColumnSpec::new("id", Type::Serial), ColumnSpec::new("name", Type::Text), ColumnSpec::new("email", Type::Text), ], vec!["id".to_string()], None, )) .expect("create People"); } /// Data rows in a CSV = non-empty lines minus the header. fn data_row_count(csv: &str) -> usize { csv.lines() .filter(|l| !l.trim().is_empty()) .count() .saturating_sub(1) } #[test] fn seed_parses_with_and_without_count() { match parse_command("seed People 5").expect("`seed People 5` parses") { Command::Seed { table, count, rng_seed, } => { assert_eq!(table, "People"); assert_eq!(count, Some(5)); assert_eq!(rng_seed, None); } other => panic!("expected Command::Seed, got {other:?}"), } match parse_command("seed People").expect("`seed People` parses") { Command::Seed { table, count, .. } => { assert_eq!(table, "People"); assert_eq!(count, None, "omitted count is None (executor defaults to 20)"); } other => panic!("expected Command::Seed, got {other:?}"), } } #[test] fn seed_populates_a_table_and_persists_rows() { let (project, db, _dir) = open_project_db(); let rt = rt(); create_people(&db, &rt); let result = rt .block_on(db.seed("People".into(), Some(7), Some(42), Some("seed People 7".into()))) .expect("seed succeeds"); assert_eq!(result.rows_affected, 7); let csv = read_csv(&project, "People").expect("People CSV exists after seed"); assert_eq!( data_row_count(&csv), 7, "CSV should hold 7 generated rows:\n{csv}" ); // The generated `email` column produces address-shaped values. assert!(csv.contains('@'), "seeded emails should appear in the CSV:\n{csv}"); } #[test] fn seed_count_defaults_to_twenty() { let (project, db, _dir) = open_project_db(); let rt = rt(); create_people(&db, &rt); let result = rt .block_on(db.seed("People".into(), None, Some(1), Some("seed People".into()))) .expect("seed succeeds"); assert_eq!(result.rows_affected, 20, "omitted count defaults to 20"); let csv = read_csv(&project, "People").expect("People CSV exists"); assert_eq!(data_row_count(&csv), 20); } #[test] fn seed_is_reproducible_with_a_fixed_seed() { let (p1, db1, _d1) = open_project_db(); let (p2, db2, _d2) = open_project_db(); let rt = rt(); create_people(&db1, &rt); create_people(&db2, &rt); rt.block_on(db1.seed("People".into(), Some(4), Some(123), Some("seed People 4".into()))) .expect("seed run 1"); rt.block_on(db2.seed("People".into(), Some(4), Some(123), Some("seed People 4".into()))) .expect("seed run 2"); let csv1 = read_csv(&p1, "People").expect("csv 1"); let csv2 = read_csv(&p2, "People").expect("csv 2"); assert_eq!(csv1, csv2, "the same --seed must reproduce identical data"); } #[test] fn seed_writes_exactly_one_history_line() { let (project, db, _dir) = open_project_db(); let rt = rt(); create_people(&db, &rt); rt.block_on(db.seed("People".into(), Some(5), Some(1), Some("seed People 5".into()))) .expect("seed succeeds"); let history = std::fs::read_to_string(project.path().join("history.log")) .expect("history.log exists"); let seed_lines = history.lines().filter(|l| l.contains("seed People 5")).count(); assert_eq!( seed_lines, 1, "a seed of 5 rows must write exactly one history line:\n{history}" ); }