feat(seed): SeedResult outcome, capped preview, advisory, count cap (ADR-0048 P1.3c)
A dedicated SeedResult replaces the borrowed insert outcome (X5): - CommandOutcome::Seed + DslSeedSucceeded event + handle_dsl_seed_success render: the echo, "N row(s) seeded into T", a capped preview table (D18, first 20 rows; full count always reported), and a Hint-styled advisory naming enum-ish / un-derivable-CHECK columns filled with generic text (D12/D13, Phase-1 wording). - SeedResult carries requested vs produced, so a junction cap is now reported to the user, not only logged. - Count cap (D6): a seed over 10000 rows is refused with a friendly error. - Catalog keys ok.rows_seeded / seed.capped / seed.advisory_generic. 4 new tests (advisory flag, IN-check not flagged, preview cap, excess count). 2346 pass / 0 fail / 0 skip, clippy clean.
This commit is contained in:
+69
-8
@@ -87,7 +87,7 @@ fn seed_populates_a_table_and_persists_rows() {
|
||||
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);
|
||||
assert_eq!(result.produced, 7);
|
||||
|
||||
let csv = read_csv(&project, "People").expect("People CSV exists after seed");
|
||||
assert_eq!(
|
||||
@@ -108,7 +108,7 @@ fn seed_count_defaults_to_twenty() {
|
||||
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");
|
||||
assert_eq!(result.produced, 20, "omitted count defaults to 20");
|
||||
let csv = read_csv(&project, "People").expect("People CSV exists");
|
||||
assert_eq!(data_row_count(&csv), 20);
|
||||
}
|
||||
@@ -217,7 +217,7 @@ fn seed_fills_foreign_keys_from_existing_parents() {
|
||||
let res = rt
|
||||
.block_on(db.seed("Orders".into(), Some(10), Some(2), Some("seed Orders 10".into())))
|
||||
.expect("seed Orders");
|
||||
assert_eq!(res.rows_affected, 10, "every child row must insert (valid FK)");
|
||||
assert_eq!(res.produced, 10, "every child row must insert (valid FK)");
|
||||
|
||||
let csv = read_csv(&project, "Orders").expect("Orders CSV");
|
||||
let valid: std::collections::HashSet<String> = (1..=5).map(|i| i.to_string()).collect();
|
||||
@@ -294,7 +294,7 @@ fn seed_omits_a_nullable_blob_column() {
|
||||
let res = rt
|
||||
.block_on(db.seed("Files".into(), Some(3), Some(1), Some("seed Files 3".into())))
|
||||
.expect("seed succeeds despite the nullable blob");
|
||||
assert_eq!(res.rows_affected, 3);
|
||||
assert_eq!(res.produced, 3);
|
||||
let csv = read_csv(&project, "Files").expect("Files CSV");
|
||||
assert_eq!(data_row_count(&csv), 3);
|
||||
}
|
||||
@@ -328,7 +328,7 @@ fn seed_keeps_unique_columns_distinct() {
|
||||
let res = rt
|
||||
.block_on(db.seed("Tags".into(), Some(8), Some(3), Some("seed Tags 8".into())))
|
||||
.expect("seed");
|
||||
assert_eq!(res.rows_affected, 8);
|
||||
assert_eq!(res.produced, 8);
|
||||
|
||||
let csv = read_csv(&project, "Tags").expect("Tags CSV");
|
||||
let labels = nth_column_values(&csv, 1);
|
||||
@@ -357,7 +357,7 @@ fn seed_sequences_identifier_int_columns() {
|
||||
let res = rt
|
||||
.block_on(db.seed("Items".into(), Some(5), Some(1), Some("seed Items 5".into())))
|
||||
.expect("seed");
|
||||
assert_eq!(res.rows_affected, 5);
|
||||
assert_eq!(res.produced, 5);
|
||||
|
||||
let csv = read_csv(&project, "Items").expect("Items CSV");
|
||||
let codes: Vec<i64> = nth_column_values(&csv, 1)
|
||||
@@ -431,7 +431,8 @@ fn seed_junction_produces_distinct_combinations_and_caps() {
|
||||
.seed("J".into(), Some(10), Some(7), Some("seed J 10".into()))
|
||||
.await
|
||||
.expect("seed J");
|
||||
assert_eq!(res.rows_affected, 4, "junction caps at available combos");
|
||||
assert_eq!(res.produced, 4, "junction caps at available combos");
|
||||
assert_eq!(res.requested, 10, "the requested count is reported for the cap note");
|
||||
});
|
||||
|
||||
let csv = read_csv(&project, "J").expect("J CSV");
|
||||
@@ -463,7 +464,7 @@ fn seed_draws_enum_values_from_an_in_check() {
|
||||
let res = rt
|
||||
.block_on(db.seed("Tickets".into(), Some(12), Some(2), Some("seed Tickets 12".into())))
|
||||
.expect("seed");
|
||||
assert_eq!(res.rows_affected, 12, "all rows insert — values satisfy the CHECK");
|
||||
assert_eq!(res.produced, 12, "all rows insert — values satisfy the CHECK");
|
||||
|
||||
let csv = read_csv(&project, "Tickets").expect("Tickets CSV");
|
||||
for v in nth_column_values(&csv, 1) {
|
||||
@@ -472,4 +473,64 @@ fn seed_draws_enum_values_from_an_in_check() {
|
||||
"status `{v}` was not drawn from the IN check:\n{csv}"
|
||||
);
|
||||
}
|
||||
// The IN-check column is derived, not generic, so it is NOT flagged.
|
||||
assert!(
|
||||
res.advisory_columns.is_empty(),
|
||||
"an IN-check column should not be flagged: {:?}",
|
||||
res.advisory_columns
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_advises_on_enum_ish_columns() {
|
||||
let (_project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
// `status` has no CHECK and no name heuristic → generic text, so it
|
||||
// is flagged for the advisory (D12/D13).
|
||||
rt.block_on(db.create_table(
|
||||
"Tasks".to_string(),
|
||||
vec![
|
||||
ColumnSpec::new("id", Type::Serial),
|
||||
ColumnSpec::new("status", Type::Text),
|
||||
],
|
||||
vec!["id".to_string()],
|
||||
None,
|
||||
))
|
||||
.expect("create Tasks");
|
||||
|
||||
let res = rt
|
||||
.block_on(db.seed("Tasks".into(), Some(3), Some(1), Some("seed Tasks 3".into())))
|
||||
.expect("seed");
|
||||
assert!(
|
||||
res.advisory_columns.contains(&"status".to_string()),
|
||||
"enum-ish `status` should be flagged: {:?}",
|
||||
res.advisory_columns
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_refuses_an_excessive_count() {
|
||||
let (_project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_people(&db, &rt);
|
||||
let err = rt
|
||||
.block_on(db.seed("People".into(), Some(1_000_000), Some(1), Some("seed People 1000000".into())))
|
||||
.expect_err("an excessive count must be refused");
|
||||
assert!(
|
||||
err.to_string().to_lowercase().contains("maximum"),
|
||||
"error should mention the maximum: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_preview_is_capped_but_count_is_full() {
|
||||
let (_project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_people(&db, &rt);
|
||||
|
||||
let res = rt
|
||||
.block_on(db.seed("People".into(), Some(25), Some(1), Some("seed People 25".into())))
|
||||
.expect("seed");
|
||||
assert_eq!(res.produced, 25, "the full count is produced");
|
||||
assert_eq!(res.data.rows.len(), 20, "the preview is capped at 20 rows");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user