use super::*;
use crate::budget::Tokens;
use crate::error::AxCode;
use crate::event::Payload;
use crate::locator::{B3Hash, Locator};
use serde_json::Map;
fn one_image() -> ImageRef {
ImageRef {
locator: Locator::parse(&format!("cas:b3-{}", "ab".repeat(32))).unwrap(),
media_type: ImageType::Png,
width: 640,
height: 480,
}
}
#[test]
fn a_picture_crosses_the_ledger_as_a_locator_and_four_integers() {
let blocks = vec![
ContentBlock::Image(one_image()),
ContentBlock::ToolResult {
tool_use_id: "tu_1".to_owned(),
content: "the screenshot".to_owned(),
is_error: false,
attachments: vec![one_image()],
},
];
let payload = message_payload(&blocks).unwrap();
assert_eq!(content_from_message(&payload).unwrap(), blocks);
let json = serde_json::to_value(&blocks[0]).unwrap();
assert_eq!(json["kind"], "image");
assert_eq!(json["media_type"], "png");
assert_eq!(json["width"], 640);
assert_eq!(json["height"], 480);
assert_eq!(ImageType::Jpeg.mime(), "image/jpeg");
}
#[test]
fn a_tool_result_written_before_attachments_existed_still_replays() {
let old = serde_json::json!({
"kind": "tool_result",
"tool_use_id": "tu_1",
"content": "ok",
"is_error": false,
});
let block: ContentBlock = serde_json::from_value(old).unwrap();
assert_eq!(
block,
ContentBlock::ToolResult {
tool_use_id: "tu_1".to_owned(),
content: "ok".to_owned(),
is_error: false,
attachments: Vec::new(),
}
);
}
#[test]
fn policy_default_is_not_confidential() {
assert!(!BuildingPolicy::default().confidential);
assert!(BuildingPolicy::new(true).confidential);
}
#[test]
fn model_return_roundtrips_with_sorted_payload_keys() {
let ret = ModelReturn::bare(Payload::empty(), vec![]);
let json = serde_json::to_string(&ret).unwrap();
let back: ModelReturn = serde_json::from_str(&json).unwrap();
assert_eq!(back, ret);
}
#[test]
fn thinking_survives_the_payload_round_trip_verbatim() {
let signature = format!("{}{}", "WaUjzkyp", "Q2mUEVM36O2Txu");
let issued = vec![
ContentBlock::Thinking {
thinking: "The question has two parts.".to_owned(),
signature,
},
ContentBlock::RedactedThinking {
data: "EroBCkYIARgCKkBmx".to_owned(),
},
ContentBlock::Text {
text: "Based on my analysis...".to_owned(),
},
];
let payload = message_payload(&issued).unwrap();
assert_eq!(content_from_message(&payload).unwrap(), issued);
}
#[test]
fn a_content_array_that_cannot_be_read_is_an_error_not_an_empty_window() {
let mut map = Map::new();
map.insert(
"content".to_owned(),
serde_json::json!([{ "kind": "from_a_later_build" }]),
);
let payload = Payload::new(map).unwrap();
let err = content_from_message(&payload).unwrap_err();
assert_eq!(*err.code(), AxCode::WireMismatch);
assert!(content_from_message(&Payload::empty()).unwrap().is_empty());
}
#[test]
fn effort_is_ordered_from_none_to_max() {
let ladder = [
Effort::None,
Effort::Low,
Effort::Medium,
Effort::High,
Effort::XHigh,
Effort::Max,
];
assert!(ladder.windows(2).all(|pair| pair[0] < pair[1]));
assert_eq!(
serde_json::to_string(&Effort::XHigh).unwrap(),
"\"xhigh\"",
"the city spells an effort level the way the provider spells it"
);
}
#[test]
fn request_carries_four_segment_hashes() {
let req = ModelRequest {
policy: BuildingPolicy::default(),
segments: [B3Hash::digest(b"a"); 4],
chat: ChatRequest::empty("m", Ceiling::new(64).unwrap()),
};
let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["segments"].as_array().unwrap().len(), 4);
}
#[test]
fn an_unversioned_usage_row_reads_in_the_one_meaning() {
let read = |row: serde_json::Value| {
serde_json::from_value::<ModelUsage>(row)
.unwrap()
.input_tokens
.get()
};
let anthropic_row = serde_json::json!({
"input_tokens": 10, "output_tokens": 1,
"cache_read_tokens": 90, "cache_write_tokens": 5,
});
let uncached_row = serde_json::json!({
"input_tokens": 10, "output_tokens": 1,
"cache_read_tokens": 0, "cache_write_tokens": 0,
});
let versioned_row = serde_json::json!({
"input_tokens": 10, "output_tokens": 1,
"cache_read_tokens": 90, "cache_write_tokens": 5, "v": 1,
});
let all_cached_row = serde_json::json!({
"input_tokens": 10, "output_tokens": 1,
"cache_read_tokens": 6, "cache_write_tokens": 4,
});
assert_eq!(
[
read(anthropic_row),
read(uncached_row),
read(versioned_row),
read(all_cached_row)
],
[105, 10, 10, 10]
);
}
#[test]
fn a_cache_count_absent_from_a_row_reads_as_unreported_and_a_zero_stays_reported() {
let read = |row: serde_json::Value| {
let usage = serde_json::from_value::<ModelUsage>(row).unwrap();
(usage.cache_read_tokens, usage.cache_write_tokens)
};
let silent = serde_json::json!({ "input_tokens": 10, "output_tokens": 1, "v": 1 });
let null = serde_json::json!({
"input_tokens": 10, "output_tokens": 1, "v": 1,
"cache_read_tokens": null, "cache_write_tokens": null,
});
let zero = serde_json::json!({
"input_tokens": 10, "output_tokens": 1,
"cache_read_tokens": 0, "cache_write_tokens": 0,
});
assert_eq!(
[read(silent), read(null), read(zero)],
[
(CacheCount::Unreported, CacheCount::Unreported),
(CacheCount::Unreported, CacheCount::Unreported),
(
CacheCount::Reported(Tokens::new(0)),
CacheCount::Reported(Tokens::new(0))
),
]
);
let unreported = ModelUsage {
input_tokens: Tokens::new(10),
output_tokens: Tokens::new(1),
cache_read_tokens: CacheCount::Unreported,
cache_write_tokens: CacheCount::Reported(Tokens::new(2)),
dialect: None,
};
let row = serde_json::to_value(unreported).unwrap();
assert_eq!(
(
row.get("cache_read_tokens"),
serde_json::from_value::<ModelUsage>(row.clone()).unwrap()
),
(None, unreported)
);
}
#[test]
fn a_tail_breakpoint_is_carried_by_the_last_message_alone() {
let said = |text: &str| ChatMessage {
role: Role::User,
content: vec![ContentBlock::Text {
text: text.to_owned(),
}],
};
let messages = vec![said("a"), said("b"), said("c")];
let ceiling = Ceiling::new(1024).unwrap();
let tail = ChatRequest {
messages: std::borrow::Cow::Borrowed(&messages),
breakpoint: MessageBreakpoint::Tail,
..ChatRequest::empty("m", ceiling)
};
let unmarked = ChatRequest {
messages: std::borrow::Cow::Borrowed(&messages),
..ChatRequest::empty("m", ceiling)
};
let carried = |request: &ChatRequest<'_>| -> Vec<bool> {
(0..=messages.len())
.map(|at| request.carries_breakpoint(at))
.collect()
};
assert_eq!(
(carried(&tail), carried(&unmarked)),
(
vec![false, false, true, false],
vec![false, false, false, false]
)
);
}
#[cfg(feature = "conformance")]
#[test]
fn the_model_suite_passes_an_answering_adapter_and_bites_one_that_cannot_see() {
struct Scripted {
sees_pictures: bool,
}
impl Model for Scripted {
fn call(&mut self, req: &ModelRequest) -> Result<ModelReturn, AxError> {
let pictured = req.chat.messages.iter().any(|message| {
message
.content
.iter()
.any(|block| matches!(block, ContentBlock::Image(_)))
});
assert!(self.sees_pictures || !pictured, "no arm for a picture");
Ok(ModelReturn::bare(Payload::empty(), Vec::new()))
}
}
let benign = ModelRequest {
policy: BuildingPolicy::default(),
segments: [B3Hash::digest(b"a"); 4],
chat: ChatRequest::empty("m", Ceiling::new(64).unwrap()),
};
conformance::assert_model_conformance(
&mut Scripted {
sees_pictures: true,
},
&benign,
);
let blind = std::panic::catch_unwind(|| {
conformance::assert_model_conformance(
&mut Scripted {
sees_pictures: false,
},
&benign,
);
});
assert!(blind.is_err(), "the suite must reach the picture round");
}