#![cfg(feature = "bedrock")]
use adk_core::{Content, Part};
use adk_model::part_conversion::{ConversionReport, PartDisposition};
fn every_part_shape() -> Vec<(&'static str, Part)> {
vec![
("text", Part::Text { text: "hello".to_string() }),
("inline image (supported)", Part::inline_data("image/png", vec![1, 2, 3])),
("inline pdf (supported document)", Part::inline_data("application/pdf", vec![4, 5])),
("inline audio (unsupported)", Part::inline_data("audio/wav", vec![6, 7])),
("inline video (unsupported)", Part::inline_data("video/mp4", vec![8, 9])),
("inline arbitrary binary", Part::inline_data("application/octet-stream", vec![0])),
("file image reference", Part::file_data("image/png", "https://example.test/a.png")),
("file pdf reference", Part::file_data("application/pdf", "https://example.test/a.pdf")),
("file audio reference", Part::file_data("audio/wav", "https://example.test/a.wav")),
]
}
fn bedrock_outcomes(part: Part) -> ConversionReport {
let content = Content { role: "user".to_string(), parts: vec![part] };
adk_model::bedrock::convert::report_for_contents(std::slice::from_ref(&content))
}
#[test]
fn no_part_reaches_bedrock_unaccounted_for() {
let mut unaccounted = Vec::new();
for (label, part) in every_part_shape() {
let report = bedrock_outcomes(part);
if report.outcomes().is_empty() {
unaccounted.push(label);
}
}
assert!(
unaccounted.is_empty(),
"these parts produced no recorded outcome, so their fate is invisible: {unaccounted:?}"
);
}
#[test]
fn unsupported_media_is_recorded_as_omitted_with_a_reason() {
for (label, mime) in
[("audio", "audio/wav"), ("video", "video/mp4"), ("binary", "application/octet-stream")]
{
let report = bedrock_outcomes(Part::inline_data(mime, vec![1, 2, 3]));
let omitted: Vec<_> = report.omitted_parts().collect();
assert_eq!(omitted.len(), 1, "{label} ({mime}) must be recorded exactly once: {report:?}");
assert!(!omitted[0].detail.is_empty(), "{label} must carry a reason a caller can act on");
assert_eq!(omitted[0].mime_type.as_deref(), Some(mime));
}
}
#[test]
fn a_textualized_file_reference_is_recorded_as_downgraded_not_converted() {
let report = bedrock_outcomes(Part::file_data("image/png", "https://example.test/a.png"));
let downgraded: Vec<_> = report.downgraded_parts().collect();
assert_eq!(downgraded.len(), 1, "the reference must be recorded as a downgrade: {report:?}");
assert_eq!(downgraded[0].disposition, PartDisposition::Downgraded { to: "text" });
assert!(!report.has_omissions(), "it still reaches the model, so it is not an omission");
assert!(report.has_losses(), "but it is a loss of fidelity");
}
#[test]
fn supported_media_records_no_loss() {
for (label, mime) in [("png", "image/png"), ("pdf", "application/pdf")] {
let report = bedrock_outcomes(Part::inline_data(mime, vec![1, 2, 3]));
assert!(
!report.has_losses(),
"{label} is natively supported, so nothing is lost: {report:?}"
);
}
}
#[test]
fn omissions_can_be_turned_into_a_pre_dispatch_error() {
let report = bedrock_outcomes(Part::inline_data("audio/wav", vec![1, 2, 3]));
let error = report.into_error().expect("an omission must be convertible to an error");
let message = error.to_string();
assert!(message.contains("audio/wav"), "{message}");
assert!(message.contains("bedrock"), "{message}");
}