test: add BAST drift detection for the StreamType enum
Parse docs/architecture/tty-bast.md at test time, extract the BAST
JSON block, and assert the StreamType enum values match wire.rs's
STREAM_STDIN/STREAM_STDOUT/STREAM_STDERR/STREAM_CTRL_IN/
STREAM_CTRL_OUT constants (the index of each name in the enum's
values array is the integer on the wire). Catches the common drift
case — a new stream_type added to wire.rs but not the BAST, or vice
versa — without requiring alktype as a dep, just serde_json which
alktty already has.
This is the mitigation listed in the project setup plan's "Risk: BAST
schema drift" section. The test lives in src/wire.rs mod tests alongside
the STREAM_* constants it asserts against, so a developer editing the
constants sees the test fail locally before the BAST doc and the wire
format drift apart.
Verified the test catches drift by temporarily renaming CtrlOut ->
CtrlOutX in the BAST: the test fails with a clear diagnostic
("StreamType[4] is 'CtrlOutX' in BAST but 'CtrlOut' in wire.rs"),
then restored.
Verification:
- cargo test --all-features -> 123 tests pass (104 lib + 19
integration; +1 from the new drift test)
- cargo clippy --all-targets --all-features -- -D warnings -> clean
- cargo clippy --target wasm32-unknown-unknown -- -D warnings -> clean
- cargo fmt --check -> clean
This commit is contained in:
1 parent
da0d395ab3
commit
18c4924ad7
1 file changed
+66
+66
@@ -432,4 +432,70 @@ mod tests {
|
||||
let err = reader.read_chunk().await.unwrap_err();
|
||||
assert!(matches!(err, RawError::ConnectionClosed));
|
||||
}
|
||||
|
||||
/// Drift detection: the `StreamType` enum in the BAST document
|
||||
/// (`docs/architecture/tty-bast.md`) MUST list the stream types in
|
||||
/// the same order as the `STREAM_*` constants in this module, so
|
||||
/// the BAST's index→name table matches the on-wire `uint8` values.
|
||||
/// Catches the common drift case (a new stream_type added to
|
||||
/// `wire.rs` but not the BAST, or vice versa) without requiring
|
||||
/// alktype as a dep — just `serde_json`, which alktty already has.
|
||||
/// See the project setup plan's "Risk: BAST schema drift"
|
||||
/// mitigation.
|
||||
#[test]
|
||||
fn bast_stream_type_enum_matches_wire_constants() {
|
||||
let bast_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("docs/architecture/tty-bast.md");
|
||||
let markdown = std::fs::read_to_string(&bast_path)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", bast_path.display()));
|
||||
|
||||
let json_start = markdown
|
||||
.find("```json\n")
|
||||
.expect("BAST markdown has a ```json block")
|
||||
+ "```json\n".len();
|
||||
let json_end = markdown[json_start..]
|
||||
.find("\n```")
|
||||
.expect("BAST json block is terminated")
|
||||
+ json_start;
|
||||
let bast: serde_json::Value = serde_json::from_str(&markdown[json_start..json_end])
|
||||
.expect("BAST json block parses");
|
||||
|
||||
let stream_type = bast
|
||||
.get("$defs")
|
||||
.and_then(|d| d.get("StreamType"))
|
||||
.expect("BAST has $defs.StreamType");
|
||||
assert_eq!(
|
||||
stream_type["kind"].as_str(),
|
||||
Some("enum"),
|
||||
"StreamType is a BAST enum"
|
||||
);
|
||||
let values = stream_type["values"]
|
||||
.as_array()
|
||||
.expect("StreamType.values is an array")
|
||||
.iter()
|
||||
.map(|v| v.as_str().expect("enum value is a string"))
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
let expected = [
|
||||
("Stdin", STREAM_STDIN),
|
||||
("Stdout", STREAM_STDOUT),
|
||||
("Stderr", STREAM_STDERR),
|
||||
("CtrlIn", STREAM_CTRL_IN),
|
||||
("CtrlOut", STREAM_CTRL_OUT),
|
||||
];
|
||||
assert_eq!(
|
||||
values.len(),
|
||||
expected.len(),
|
||||
"StreamType enum has {} values, wire.rs has {} — drift",
|
||||
values.len(),
|
||||
expected.len()
|
||||
);
|
||||
for (i, (name, value)) in expected.iter().enumerate() {
|
||||
assert_eq!(
|
||||
values[i], *name,
|
||||
"StreamType[{i}] is {:?} in BAST but {:?} in wire.rs (value {value})",
|
||||
values[i], name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user