Files
boxmaker/crates/proto/tests/strict.rs
T
kyle e8568edf7e Add the admin messages, approval ids as numbers, and two turn events
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
2026-09-19 00:28:46 -07:00

151 lines
5.3 KiB
Rust

//! Every JSON object in every fixture must reject an unknown key. Do not edit.
//!
//! The other test files check unknown fields in a few hand-picked places. This one checks all of
//! them: it walks each fixture, adds one unknown key to one object at a time, at every depth, and
//! requires that the result no longer decodes.
use proto::{AuditRecord, Envelope, Grant, LogRecord};
use serde::de::DeserializeOwned;
use serde_json::Value;
/// Every copy of `value` that has exactly one extra key in exactly one object.
fn with_one_unknown_key(value: &Value) -> Vec<Value> {
let mut out = Vec::new();
match value {
Value::Object(map) => {
let mut extended = map.clone();
extended.insert("zz_unknown".to_string(), Value::Bool(true));
out.push(Value::Object(extended));
for (key, child) in map {
for changed in with_one_unknown_key(child) {
let mut copy = map.clone();
copy.insert(key.clone(), changed);
out.push(Value::Object(copy));
}
}
}
Value::Array(items) => {
for (i, child) in items.iter().enumerate() {
for changed in with_one_unknown_key(child) {
let mut copy = items.clone();
copy[i] = changed;
out.push(Value::Array(copy));
}
}
}
_ => {}
}
out
}
/// Returns how many variations were tried, so callers can check the walk reached nested objects.
fn check<T: DeserializeOwned>(what: &str, text: &str) -> usize {
let value: Value = serde_json::from_str(text).unwrap_or_else(|e| panic!("{what}: {e}"));
assert!(
serde_json::from_value::<T>(value.clone()).is_ok(),
"{what}: fixture must decode"
);
let variations = with_one_unknown_key(&value);
for changed in &variations {
assert!(
serde_json::from_value::<T>(changed.clone()).is_err(),
"{what}: accepted an unknown key: {changed}"
);
}
variations.len()
}
fn fixture(path: &str) -> String {
let full = format!("{}/tests/fixtures/{path}", env!("CARGO_MANIFEST_DIR"));
std::fs::read_to_string(&full).unwrap_or_else(|e| panic!("{full}: {e}"))
}
#[test]
fn envelopes_reject_unknown_keys_at_every_depth() {
for name in [
"tool_request.json",
"tool_response_pending.json",
"tool_response_result.json",
"tool_response_failed.json",
"tool_response_denied.json",
"error.json",
"turn.json",
"turn_event_tool_result.json",
"turn_event_content.json",
"turn_event_retrying.json",
"turn_done.json",
"error_session_full.json",
"approvals.json",
"approval_list.json",
"approval_list_empty.json",
"approve.json",
"approve_result_allowed.json",
"approve_result_denied.json",
"refuse.json",
"refuse_no_reason.json",
"ok.json",
"check_grants.json",
"grants_report.json",
"grants_report_ok.json",
"error_forbidden.json",
"error_no_such_approval.json",
"turn_event_approval_pending.json",
"turn_event_tool_denied.json",
] {
// Envelope, msg and body: three objects. Some bodies hold more: turn_done a usage
// object, approval_list one item, approve_result an outcome, grants_report two problems.
let want = match name {
"turn_done.json" | "approval_list.json" => 4,
"approve_result_allowed.json" | "approve_result_denied.json" => 4,
"grants_report.json" => 5,
_ => 3,
};
assert_eq!(
check::<Envelope>(name, &fixture(&format!("wire/{name}"))),
want,
"{name}"
);
}
}
#[test]
fn audit_records_reject_unknown_keys_at_every_depth() {
for (i, line) in fixture("records/audit.jsonl").lines().enumerate() {
// The record and its event: two objects. Decision and approval events also hold an
// outcome object.
let want = if line.contains("\"outcome\"") { 3 } else { 2 };
assert_eq!(
check::<AuditRecord>(&format!("audit.jsonl:{}", i + 1), line),
want
);
}
}
#[test]
fn log_records_reject_unknown_keys_at_every_depth() {
let mut tried = 0;
for (i, line) in fixture("records/session.jsonl").lines().enumerate() {
tried += check::<LogRecord>(&format!("session.jsonl:{}", i + 1), line);
}
// Seven records, plus the one tool call inside the first assistant record.
assert_eq!(tried, 8);
}
#[test]
fn usage_log_records_reject_unknown_keys_at_every_depth() {
let mut tried = 0;
for (i, line) in fixture("records/session_usage.jsonl").lines().enumerate() {
tried += check::<LogRecord>(&format!("session_usage.jsonl:{}", i + 1), line);
}
// Seven records, plus the one tool call inside the first assistant record.
assert_eq!(tried, 8);
}
#[test]
fn grants_reject_unknown_keys_at_every_depth() {
let grant: Grant = toml::from_str(&fixture("grant/full.toml")).unwrap();
let text = serde_json::to_string(&grant).unwrap();
// The grant and its constraints: two objects.
assert_eq!(check::<Grant>("full.toml as JSON", &text), 2);
}