db: end-to-end tests for change_column int -> bool (B2)
The (Int, Bool) entry of the ADR-0017 §3 matrix was already covered at the per-cell unit-test level in `type_change.rs`, but the end-to-end change_column path through `db.rs` had no test exercising it. This closes that gap with the two cases called out in the handoff: - `change_column_type_int_to_bool_with_zero_one_succeeds`: Rows 0/1/0 succeed, no [client-side] note. The matrix returns the same Value::Integer for 0 and 1, so is_non_identity reports false for every cell and ClientSideNote.transformed stays at 0 — the `transformed > 0 || auto_filled > 0` filter therefore drops the note. - `change_column_type_int_to_bool_refuses_other_values`: Row with 2 → Incompatible. Verified under both Default and ForceConversion modes (per ADR-0017 §5: incompatible is not lossy, --force-conversion must not advertise). No production code change; tests only. 534 -> 536 passing, clippy clean with nursery lints enabled.
This commit is contained in:
@@ -5282,6 +5282,107 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn change_column_type_int_to_bool_with_zero_one_succeeds() {
|
||||||
|
// ADR-0017 §3 matrix: (Int, Bool) is per-cell-classified.
|
||||||
|
// Values 0/1 are Clean (storage class doesn't change); the
|
||||||
|
// transformer returns Value::Integer(0)/(1) unchanged, so
|
||||||
|
// is_non_identity is false for every cell. No
|
||||||
|
// [client-side] note is expected.
|
||||||
|
let db = db();
|
||||||
|
make_id_table(&db, "T").await;
|
||||||
|
db.add_column("T".to_string(), "Flag".to_string(), Type::Int, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
for v in ["0", "1", "0"] {
|
||||||
|
db.insert(
|
||||||
|
"T".to_string(),
|
||||||
|
Some(vec!["Flag".to_string()]),
|
||||||
|
vec![Value::Number(v.to_string())],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let result = db
|
||||||
|
.change_column_type(
|
||||||
|
"T".to_string(),
|
||||||
|
"Flag".to_string(),
|
||||||
|
Type::Bool,
|
||||||
|
ChangeColumnMode::Default,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("int -> bool with all 0/1 values should succeed");
|
||||||
|
let flag = result
|
||||||
|
.description
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.name == "Flag")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(flag.user_type, Some(Type::Bool));
|
||||||
|
assert!(
|
||||||
|
result.client_side.is_none(),
|
||||||
|
"int -> bool with values that map identity should not fire a client-side note: {:?}",
|
||||||
|
result.client_side
|
||||||
|
);
|
||||||
|
// Data preserved.
|
||||||
|
let data = db.query_data("T".to_string(), None).await.unwrap();
|
||||||
|
assert_eq!(data.rows.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn change_column_type_int_to_bool_refuses_other_values() {
|
||||||
|
// ADR-0017 §3 matrix: (Int, Bool) classifies any value
|
||||||
|
// other than 0/1 as Incompatible. A single offending row
|
||||||
|
// triggers a refusal, and --force-conversion does not
|
||||||
|
// help (incompatible is not lossy).
|
||||||
|
let db = db();
|
||||||
|
make_id_table(&db, "T").await;
|
||||||
|
db.add_column("T".to_string(), "Flag".to_string(), Type::Int, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
for v in ["0", "1", "2"] {
|
||||||
|
db.insert(
|
||||||
|
"T".to_string(),
|
||||||
|
Some(vec!["Flag".to_string()]),
|
||||||
|
vec![Value::Number(v.to_string())],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
for mode in [ChangeColumnMode::Default, ChangeColumnMode::ForceConversion] {
|
||||||
|
let err = db
|
||||||
|
.change_column_type(
|
||||||
|
"T".to_string(),
|
||||||
|
"Flag".to_string(),
|
||||||
|
Type::Bool,
|
||||||
|
mode,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
match err {
|
||||||
|
DbError::Unsupported(message) => {
|
||||||
|
assert!(
|
||||||
|
message.contains("cannot be converted"),
|
||||||
|
"expected incompatible header for {mode:?}: {message}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
message.contains("not 0 or 1"),
|
||||||
|
"expected per-cell reason naming the offending value for {mode:?}: {message}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!message.contains("--force-conversion"),
|
||||||
|
"incompatible refusal must NOT advertise --force-conversion for {mode:?}: {message}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
other => panic!("unexpected ({mode:?}): {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn change_column_type_dont_convert_skips_client_side() {
|
async fn change_column_type_dont_convert_skips_client_side() {
|
||||||
// text -> int: under the per-cell matrix, "1"/"2"/"3"
|
// text -> int: under the per-cell matrix, "1"/"2"/"3"
|
||||||
|
|||||||
Reference in New Issue
Block a user