Files
codex/codex-rs/attachment-store/src/lib_tests.rs
T
Krish Chainani cb66dbecf2 Replace unavailable image references in model requests (#57585)
## Why

Image files can become unavailable between turns. Model requests should reflect their current availability while preserving the original references in saved history.

## What changed

- Add opt-in availability checks through `AttachmentStore::is_resolvable`, retaining references by default and on backend or access errors.
- Check distinct file IDs concurrently for each prepared request and replace confirmed-missing images with `[image unavailable]` in messages and tool outputs.
- Apply the same handling to compaction requests, with background image checks running outside the session lock without delaying foreground turn completion.

## Testing

Add unit tests and multi-turn scenarios covering placeholders, preserved history and annotations, concurrent deduplicated checks, backend errors, and nonblocking background compaction.

GitOrigin-RevId: ec98c7014c6e6b5063d87b943d2cd66a9ce7c1e3
2026-10-27 22:59:22 +00:00

85 lines
2.6 KiB
Rust

use pretty_assertions::assert_eq;
use super::*;
/// Debug output redacts attachment bytes in requests and results.
#[test]
fn attachment_debug_output_redacts_bytes() {
let request = UploadRequest {
thread_id: "thread-1".to_string(),
ephemeral: false,
file_name: Some("image.png".to_string()),
tags: HashMap::new(),
data: b"secret".to_vec(),
};
let result = UploadResult::Inline {
bytes: b"secret".to_vec(),
};
assert_eq!(
(format!("{request:?}"), format!("{result:?}")),
(
r#"UploadRequest { thread_id: "thread-1", ephemeral: false, file_name: Some("image.png"), tags: {}, data: "<redacted>" }"#.to_string(),
r#"Inline { bytes: "<redacted>" }"#.to_string(),
)
);
}
/// Debug output redacts credential-bearing file URLs in resolved metadata.
#[test]
fn attachment_metadata_debug_output_redacts_file_url() {
let metadata = AttachmentMetadata {
file_url: Some("https://attachments.test/file?signed=secret".to_string()),
..AttachmentMetadata::default()
};
let debug = format!("{metadata:?}");
assert!(!debug.contains("signed=secret"));
assert!(debug.contains("<redacted>"));
}
/// The inline store preserves PNG and JPEG attachment bytes.
#[tokio::test]
async fn inline_store_preserves_image_bytes() {
let cases: [(&str, &[u8]); 2] = [
("image.png", b"\x89PNG\r\n\x1a\n"),
("image.jpg", b"\xff\xd8\xff\xe0JFIF\x00\xff\xd9"),
];
for (file_name, data) in cases {
let attachment = InlineAttachmentStore
.upload(UploadRequest {
thread_id: "thread-1".to_string(),
ephemeral: false,
file_name: Some(file_name.to_string()),
tags: HashMap::new(),
data: data.to_vec(),
})
.await
.expect("inline attachment");
let UploadResult::Inline { bytes } = attachment else {
panic!("inline store returned a file reference");
};
assert_eq!(bytes, data);
}
}
/// The inline store preserves opaque file references even though it cannot resolve them.
#[tokio::test]
async fn inline_store_cannot_resolve_file_references() {
assert!(InlineAttachmentStore.is_resolvable("file_123").await);
let error = InlineAttachmentStore
.resolve(ResolveRequest {
file_id: "file_123",
tags: HashMap::new(),
download_url_ttl: None,
})
.await
.expect_err("inline store cannot resolve file references");
assert_eq!(error.kind(), AttachmentStoreErrorKind::NotFound);
}