use super::*;
use proptest::prelude::*;
use serde_json::json;
fn bash_call(id: Option<&str>) -> Block {
Block::ToolCall {
id: id.map(str::to_string),
name: "Bash".into(),
args: json!({ "command": "cargo test", "timeout": 120 }),
}
}
#[test]
fn falsify_ir_anthropic_roundtrip_001() {
let m = Message {
role: Role::Assistant,
blocks: vec![
Block::Text("Running the tests.".into()),
bash_call(Some("toolu_01ABC")),
],
};
assert_eq!(from_anthropic(&to_anthropic(&m)).unwrap(), m);
}
#[test]
fn falsify_ir_anthropic_roundtrip_001b() {
let m = Message {
role: Role::User,
blocks: vec![Block::ToolResult {
tool_call_id: Some("toolu_01ABC".into()),
name: String::new(), response: json!({ "stdout": "ok", "code": 0 }),
}],
};
assert_eq!(from_anthropic(&to_anthropic(&m)).unwrap(), m);
}
#[test]
fn falsify_ir_gemini_roundtrip_002() {
let call = Message {
role: Role::Assistant,
blocks: vec![bash_call(None)], };
assert!(call.is_gemini_native());
assert_eq!(from_gemini(&to_gemini(&call)).unwrap(), call);
let result = Message {
role: Role::User,
blocks: vec![Block::ToolResult {
tool_call_id: None,
name: "Bash".into(),
response: json!({ "stdout": "ok" }),
}],
};
assert_eq!(from_gemini(&to_gemini(&result)).unwrap(), result);
}
#[test]
fn falsify_ir_cross_harness_003() {
let m = Message {
role: Role::Assistant,
blocks: vec![
Block::Text("Let me run that.".into()),
bash_call(Some("toolu_99")), ],
};
let via_anthropic = from_anthropic(&to_anthropic(&m)).unwrap().semantic();
let via_gemini = from_gemini(&to_gemini(&m)).unwrap().semantic();
assert_eq!(
via_anthropic, via_gemini,
"cross-harness content diverged: anthropic={via_anthropic:?} gemini={via_gemini:?}"
);
assert_eq!(via_anthropic, m.semantic());
}
#[test]
fn falsify_ir_tool_schema_004() {
let t = ToolSchema {
name: "Edit".into(),
description: "Replace a string in a file".into(),
parameters: json!({
"type": "object",
"required": ["file_path", "old", "new"],
"properties": {
"file_path": { "type": "string" },
"old": { "type": "string" },
"new": { "type": "string" },
"count": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}),
};
assert_eq!(tool_from_anthropic(&tool_to_anthropic(&t)).unwrap(), t);
assert_eq!(tool_from_gemini(&tool_to_gemini(&t)).unwrap(), t);
assert_eq!(
tool_to_anthropic(&t)["input_schema"],
tool_to_gemini(&t)["parameters"]
);
}
#[test]
fn binding_liveness_l5_bound_functions_exist() {
let _oblig_ir_1: fn(&serde_json::Value) -> Result<Message, String> = from_anthropic;
let _oblig_ir_2: fn(&serde_json::Value) -> Result<Message, String> = from_gemini;
let _oblig_ir_3: fn(&Message) -> Message = Message::semantic;
let _oblig_ir_4: fn(&ToolSchema) -> serde_json::Value = tool_to_anthropic;
}
prop_compose! {
fn arb_json_scalar()(choice in 0..4usize, s in "[a-z]{1,6}", n in -100i64..100)
-> serde_json::Value {
match choice {
0 => json!(s),
1 => json!(n),
2 => json!(n % 2 == 0),
_ => serde_json::Value::Null,
}
}
}
prop_compose! {
fn arb_args()(k1 in "[a-z]{1,5}", v1 in arb_json_scalar(), k2 in "[a-z]{1,5}", v2 in arb_json_scalar())
-> serde_json::Value {
json!({ k1: v1, k2: v2 })
}
}
fn arb_role() -> impl Strategy<Value = Role> {
prop_oneof![Just(Role::User), Just(Role::Assistant)]
}
fn arb_shared_block() -> impl Strategy<Value = Block> {
prop_oneof![
"[a-zA-Z0-9 .]{0,20}".prop_map(Block::Text),
("[a-zA-Z_]{1,8}", arb_args(), any::<bool>()).prop_map(|(name, args, has_id)| {
Block::ToolCall {
id: has_id.then(|| "toolu_x".to_string()),
name,
args,
}
}),
]
}
proptest! {
#[test]
fn prop_anthropic_roundtrip_exact(
role in arb_role(),
blocks in prop::collection::vec(arb_shared_block(), 0..5),
) {
let m = Message { role, blocks };
prop_assert_eq!(from_anthropic(&to_anthropic(&m)).unwrap(), m);
}
#[test]
fn prop_gemini_roundtrip_exact(
role in arb_role(),
blocks in prop::collection::vec(arb_shared_block(), 0..5),
) {
let m = Message { role, blocks }.semantic();
prop_assert!(m.is_gemini_native());
prop_assert_eq!(from_gemini(&to_gemini(&m)).unwrap(), m);
}
#[test]
fn prop_cross_harness_semantic_equivalence(
role in arb_role(),
blocks in prop::collection::vec(arb_shared_block(), 0..5),
) {
let m = Message { role, blocks };
let via_anthropic = from_anthropic(&to_anthropic(&m)).unwrap().semantic();
let via_gemini = from_gemini(&to_gemini(&m)).unwrap().semantic();
prop_assert_eq!(&via_anthropic, &via_gemini);
prop_assert_eq!(via_anthropic, m.semantic());
}
#[test]
fn prop_tool_schema_lossless(
name in "[a-zA-Z_]{1,8}",
desc in "[a-zA-Z ]{0,20}",
params in arb_args(),
) {
let t = ToolSchema { name, description: desc, parameters: params };
prop_assert_eq!(tool_from_anthropic(&tool_to_anthropic(&t)).unwrap(), t.clone());
prop_assert_eq!(tool_from_gemini(&tool_to_gemini(&t)).unwrap(), t.clone());
prop_assert_eq!(tool_to_anthropic(&t)["input_schema"].clone(), tool_to_gemini(&t)["parameters"].clone());
}
}