use super::*;
fn line(record: Record) -> serde_json::Value {
let line = Line {
seq: 7,
ts: "2026-08-15T10:00:00.000Z".to_string(),
record,
};
serde_json::to_value(&line).expect("a record must serialise")
}
#[test]
fn test9657_every_line_is_flat_and_typed() {
let value = line(Record::Stdout {
text: "hello".to_string(),
});
assert_eq!(value["type"], "stdout");
assert_eq!(value["seq"], 7);
assert_eq!(value["ts"], "2026-08-15T10:00:00.000Z");
assert_eq!(value["text"], "hello");
assert!(
value.get("record").is_none(),
"the record must be flattened into the line, got {value}"
);
}
#[test]
fn test9658_unknown_fields_are_omitted() {
let value = line(Record::Progress {
phase: None,
done: 3,
total: None,
label: None,
});
assert_eq!(value["type"], "progress");
assert_eq!(value["done"], 3);
assert!(value.get("total").is_none(), "got {value}");
assert!(value.get("phase").is_none(), "got {value}");
assert!(value.get("label").is_none(), "got {value}");
}
#[test]
fn test9659_every_record_round_trips() {
let records = vec![
Record::CommandStarted {
argv: vec!["wsb".to_string(), "test".to_string()],
cwd: "/w".to_string(),
version: "1.0.0".to_string(),
pid: 42,
},
Record::CommandFinished {
outcome: Outcome::Failed,
exit_code: 2,
duration_ms: 10,
},
Record::Stdout {
text: "out".to_string(),
},
Record::Stderr {
text: "err".to_string(),
},
Record::Message {
level: Level::Warn,
text: "careful".to_string(),
code: Some("stale-pin".to_string()),
},
Record::Failure {
message: "no".to_string(),
code: None,
},
Record::PhaseStarted {
phase: "build".to_string(),
label: Some("Build".to_string()),
total: Some(3),
},
Record::PhaseFinished {
phase: "build".to_string(),
outcome: Outcome::Ok,
duration_ms: 5,
},
Record::Progress {
phase: Some("build".to_string()),
done: 1,
total: Some(3),
label: None,
},
Record::SubprocessStarted {
pid: 4242,
id: 1,
argv: vec!["cargo".to_string(), "test".to_string()],
cwd: Some("/w/wsb".to_string()),
},
Record::SubprocessOutput {
id: 1,
stream: Stream::Stderr,
text: "compiling".to_string(),
},
Record::SubprocessFinished {
id: 1,
exit_code: None,
signal: Some(9),
duration_ms: 7,
},
Record::TestGroupPlanned {
group: "rust".to_string(),
label: "RUST".to_string(),
suites: vec![
crate::output::PlannedSuite {
suite: "wsb".to_string(),
steps: vec!["build".to_string(), "test".to_string()],
skip: None,
},
crate::output::PlannedSuite {
suite: "capdag-objc".to_string(),
steps: Vec::new(),
skip: Some("macOS-only on Linux".to_string()),
},
],
},
Record::TestStarted {
suite: "wsb".to_string(),
group: "rust".to_string(),
step: "test".to_string(),
log: Some(".wsb/disposable/logs/test/wsb.test.log".to_string()),
},
Record::TestFinished {
suite: "wsb".to_string(),
group: "rust".to_string(),
step: "test".to_string(),
outcome: Outcome::Ok,
duration_ms: 900,
log: Some("/w/.wsb/logs/wsb.test.log".to_string()),
message: None,
},
Record::TestSummary {
passed: 3,
failed: 1,
skipped: 2,
duration_ms: 1000,
},
Record::Artifact {
kind: "log".to_string(),
path: "/w/log".to_string(),
label: None,
},
Record::PeerState {
peer: "rsh9g0dadbhcbt22".to_string(),
name: "laptop".to_string(),
fingerprint: "SHA256:abc".to_string(),
address: "192.168.1.4:7645".to_string(),
state: "connected".to_string(),
detail: None,
},
Record::WorkspacePeerState {
peer: "rsh9g0dadbhcbt22".to_string(),
workspace: "b37d4fd6e7c0b2a7".to_string(),
presence: "shared".to_string(),
desired: "enabled".to_string(),
repositories: 4,
blocked: 1,
conflicts: 2,
},
Record::SyncState {
peer: "rsh9g0dadbhcbt22".to_string(),
workspace: "b37d4fd6e7c0b2a7".to_string(),
repository: String::new(),
desired: "enabled".to_string(),
observed: "blocked".to_string(),
blocked: Some("branch_mismatch".to_string()),
reason: Some("Branch mismatch — here main, on the peer topic".to_string()),
pending: 0,
conflicts: 0,
branch: Some("main".to_string()),
head: Some("a".repeat(40)),
},
Record::SyncProgress {
peer: "rsh9g0dadbhcbt22".to_string(),
workspace: "b37d4fd6e7c0b2a7".to_string(),
repository: "child".to_string(),
files: 3,
bytes: 40_960,
},
Record::SyncConflict {
peer: "rsh9g0dadbhcbt22".to_string(),
workspace: "b37d4fd6e7c0b2a7".to_string(),
repository: "child".to_string(),
path: "notes.md".to_string(),
here: "modified".to_string(),
there: "deleted".to_string(),
},
Record::SyncDiff {
peer: "rsh9g0dadbhcbt22".to_string(),
workspace: "b37d4fd6e7c0b2a7".to_string(),
repository: String::new(),
path: "src/main.rs".to_string(),
direction: "send".to_string(),
size: Some(4096),
},
];
for record in records {
let encoded = serde_json::to_string(&Line {
seq: 1,
ts: "2026-08-15T10:00:00.000Z".to_string(),
record: record.clone(),
})
.expect("a record must serialise");
let decoded: Line = serde_json::from_str(&encoded)
.unwrap_or_else(|e| panic!("a record must parse back: {e} in {encoded}"));
assert_eq!(decoded.record, record);
assert!(
!encoded.contains('\n'),
"a line must be one line, got {encoded}"
);
}
}
#[test]
fn test10253_a_planned_group_carries_its_whole_row_set() {
let value = line(Record::TestGroupPlanned {
group: "cartridges".to_string(),
label: "CARTRIDGES".to_string(),
suites: vec![
crate::output::PlannedSuite {
suite: "txtcartridge".to_string(),
steps: vec!["build".to_string(), "test".to_string()],
skip: None,
},
crate::output::PlannedSuite {
suite: "mlxcartridge".to_string(),
steps: Vec::new(),
skip: Some("macOS-only on Linux".to_string()),
},
],
});
assert_eq!(value["type"], "test_group_planned");
assert_eq!(value["group"], "cartridges");
assert_eq!(value["label"], "CARTRIDGES");
let suites = value["suites"].as_array().expect("suites is an array");
assert_eq!(suites.len(), 2, "both rows, including the one that will not run");
assert_eq!(suites[0]["suite"], "txtcartridge");
assert_eq!(suites[0]["steps"][0], "build");
assert_eq!(suites[0]["steps"][1], "test");
assert!(
suites[0].get("skip").is_none(),
"a suite that runs carries no skip reason at all, not a null one: {}",
suites[0]
);
assert_eq!(suites[1]["suite"], "mlxcartridge");
assert_eq!(suites[1]["skip"], "macOS-only on Linux");
assert_eq!(
suites[1]["steps"].as_array().expect("steps is an array").len(),
0,
"a skipped suite claims no steps — it has none and never will"
);
}