Files
buzz/desktop/src-tauri/src/managed_agents/agent_snapshot_tests.rs
Wes 4794a5cecf ci: relax file-size ceilings by surface (#6485)
## Summary

- raise Desktop Rust's differential file-size ceiling from 1,000 to
1,500 lines
- raise Desktop frontend and Mobile ceilings from 1,000 to 1,200 lines
- keep Web at 1,000 lines and preserve the existing no-growth ratchet
above each ceiling

## Why

The flat 1,000-line limit is forcing mechanical trimming in Desktop and
Mobile even for small cohesive changes. Surface-specific ceilings
relieve that pressure without granting Web or every authored component a
blanket 2,000-line budget.

## Testing

- `just file-size-check`
- `cd desktop && pnpm exec biome check scripts/check-file-sizes.mjs`
- `node --check mobile/scripts/check-file-sizes.mjs`
- `git diff HEAD^ --check`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-09-01 19:14:15 +00:00

672 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Unit tests for `managed_agents/agent_snapshot.rs`.
//!
//! Kept in a sibling file so `agent_snapshot.rs` stays under the
//! 1500-line gate; `#[path]`-included from there.
use super::*;
use crate::managed_agents::types::{BackendKind, ManagedAgentRecord, RespondTo};
use std::collections::BTreeMap;
/// Build a minimal `ManagedAgentRecord` for testing. Only the fields
/// relevant to snapshot export are filled; the rest use defaults.
fn minimal_record() -> ManagedAgentRecord {
ManagedAgentRecord {
description: None,
pubkey: "deadbeef".to_string(),
name: "Test Agent".to_string(),
display_name: Some("Test Agent Display".to_string()),
persona_id: Some("SENTINEL_PERSONA_ID".to_string()), // MUST NOT appear in snapshot
team_id: Some("SENTINEL_TEAM_ID".to_string()), // MUST NOT appear in snapshot
private_key_nsec: "nsec1secret".to_string(), // MUST NOT appear in snapshot
auth_tag: Some("auth-tag-secret".to_string()), // MUST NOT appear in snapshot
relay_url: "wss://relay.example.com".to_string(), // MUST NOT appear in snapshot
avatar_url: Some("https://example.com/avatar.png".to_string()),
acp_command: "/usr/local/bin/acp".to_string(), // MUST NOT appear in snapshot
agent_command: "goose".to_string(), // MUST NOT appear in snapshot
agent_command_override: Some("goose-override".to_string()), // MUST NOT appear
agent_args: vec!["--arg".to_string()], // MUST NOT appear in snapshot
mcp_command: "mcp-server".to_string(), // MUST NOT appear in snapshot
turn_timeout_seconds: 120, // deprecated, MUST NOT appear
idle_timeout_seconds: Some(30),
max_turn_duration_seconds: Some(600),
parallelism: 2,
system_prompt: Some("You are a test agent.".to_string()),
model: Some("claude-opus-4".to_string()),
provider: Some("anthropic".to_string()),
persona_source_version: Some("v1.0".to_string()), // MUST NOT appear
env_vars: {
let mut m = BTreeMap::new();
m.insert("API_KEY".to_string(), "secret123".to_string()); // MUST NOT appear
m
},
start_on_app_launch: true,
auto_restart_on_config_change: true,
runtime_pid: Some(12345), // MUST NOT appear
backend: BackendKind::Provider {
// MUST NOT appear — carries a provider secret
id: "SENTINEL_BACKEND_ID".to_string(),
config: serde_json::json!({"api_key": "SENTINEL_BACKEND_SECRET"}),
},
backend_agent_id: Some("SENTINEL_BACKEND_AGENT_ID".to_string()), // MUST NOT appear
provider_policy_pending: false,
provider_binary_path: Some("/usr/bin/SENTINEL_PROVIDER_BINARY".to_string()), // MUST NOT appear
persona_team_dir: Some(std::path::PathBuf::from("SENTINEL_TEAM_DIR")), // MUST NOT appear
persona_name_in_team: Some("SENTINEL_NAME_IN_TEAM".to_string()), // MUST NOT appear
created_at: "2024-01-01T00:00:00Z".to_string(),
updated_at: "2024-01-02T00:00:00Z".to_string(),
last_started_at: Some("2024-01-03T00:00:00Z".to_string()), // MUST NOT appear
last_stopped_at: None,
last_exit_code: Some(0), // MUST NOT appear
last_error: Some("SENTINEL_LAST_ERROR".to_string()), // MUST NOT appear
last_error_code: Some(42), // MUST NOT appear
respond_to: RespondTo::default(),
respond_to_allowlist: vec!["pubkey1hex".to_string()],
slug: Some("test-agent".to_string()),
runtime: Some("goose".to_string()),
name_pool: vec!["Alice".to_string(), "Bob".to_string()],
is_builtin: false,
is_active: true,
shared: false,
source_team: Some("team-id-123".to_string()), // MUST NOT appear
source_team_persona_slug: Some("lep".to_string()), // MUST NOT appear
definition_respond_to: Some("allowlist".to_string()),
catalog_source: None,
team_catalog_source: None,
definition_respond_to_allowlist: vec!["abc123def".to_string()],
definition_parallelism: Some(4),
relay_mesh: None,
effort_level: None,
}
}
// ── Round-trip tests ──────────────────────────────────────────────────────
#[test]
fn json_round_trip_config_only() {
let record = minimal_record();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], None);
let bytes = encode_snapshot_json(&snapshot).unwrap();
let parsed = decode_snapshot_json(&bytes).unwrap();
assert_eq!(parsed, snapshot);
}
#[test]
fn json_round_trip_with_memory() {
let record = minimal_record();
let entries = vec![
AgentSnapshotMemoryEntry {
slug: "core".to_string(),
body: "I am a test agent.".to_string(),
},
AgentSnapshotMemoryEntry {
slug: "mem/research".to_string(),
body: "Some research notes.".to_string(),
},
];
let snapshot = build_snapshot(&record, MemoryLevel::Everything, entries, None);
let bytes = encode_snapshot_json(&snapshot).unwrap();
let parsed = decode_snapshot_json(&bytes).unwrap();
assert_eq!(parsed, snapshot);
}
#[test]
fn png_round_trip_no_memory() {
let record = minimal_record();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], None);
let png_bytes = encode_snapshot_png(&snapshot, None).unwrap();
let parsed = decode_snapshot_png(&png_bytes).unwrap();
assert_eq!(parsed.definition.name, snapshot.definition.name);
assert_eq!(parsed.profile.display_name, snapshot.profile.display_name);
assert_eq!(parsed.memory.level, MemoryLevel::None);
}
#[test]
fn png_round_trip_with_avatar_png() {
// Build a minimal PNG avatar.
let avatar = make_png_with_text("dummy", "value").unwrap();
let record = minimal_record();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], Some(&avatar));
// Avatar should be inlined as a data URL.
assert!(snapshot
.profile
.avatar_data_url
.as_deref()
.unwrap_or("")
.starts_with("data:image/png;base64,"));
let png_bytes = encode_snapshot_png(&snapshot, Some(&avatar)).unwrap();
let parsed = decode_snapshot_png(&png_bytes).unwrap();
assert_eq!(parsed.definition.name, snapshot.definition.name);
}
/// Plain-card byte compatibility: `encode_snapshot_png` was refactored
/// through the shared `encode_chunk_payload_png` when locked cards were
/// added. Plain cards must emit byte-identical PNGs to the pre-envelope
/// encoder. This vector reimplements the legacy encoder body verbatim and
/// asserts equality on all three composition paths: placeholder (no avatar),
/// PNG-avatar (where tEXt chunk injection ordering matters), and
/// JPEG-avatar transcode.
#[test]
fn plain_encoder_bytes_identical_to_pre_envelope_encoder() {
// Verbatim pre-refactor `encode_snapshot_png` body (post memory guard).
fn legacy_encode(
snapshot: &AgentSnapshot,
avatar_bytes: Option<&[u8]>,
) -> Result<Vec<u8>, String> {
let json_bytes = encode_snapshot_json(snapshot)?;
let chunk_text = STANDARD.encode(&json_bytes);
let png_bytes = match avatar_bytes.filter(|bytes| !bytes.is_empty()) {
Some(bytes) => {
let encoded_avatar = if bytes.starts_with(b"\x89PNG") {
inject_text_chunk(bytes, PNG_CHUNK_KEYWORD, &chunk_text).or_else(|_| {
transcode_avatar_to_png_with_text(bytes, PNG_CHUNK_KEYWORD, &chunk_text)
})
} else {
transcode_avatar_to_png_with_text(bytes, PNG_CHUNK_KEYWORD, &chunk_text)
};
match encoded_avatar {
Ok(png_bytes) => png_bytes,
Err(_) => make_png_with_text(PNG_CHUNK_KEYWORD, &chunk_text)?,
}
}
None => make_png_with_text(PNG_CHUNK_KEYWORD, &chunk_text)?,
};
Ok(png_bytes)
}
let record = minimal_record();
// Placeholder path (no avatar).
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], None);
assert_eq!(
encode_snapshot_png(&snapshot, None).unwrap(),
legacy_encode(&snapshot, None).unwrap(),
"placeholder-path plain PNG bytes must match the pre-envelope encoder"
);
// PNG-avatar path: chunk injected into the avatar image body.
let avatar = make_png_with_text("dummy", "value").unwrap();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], Some(&avatar));
assert_eq!(
encode_snapshot_png(&snapshot, Some(&avatar)).unwrap(),
legacy_encode(&snapshot, Some(&avatar)).unwrap(),
"avatar-path plain PNG bytes must match the pre-envelope encoder"
);
// JPEG-avatar path: transcode-to-PNG composition.
let jpeg_avatar = image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(
4,
4,
image::Rgb([0x10, 0x20, 0x30]),
));
let mut jpeg_bytes = Vec::new();
jpeg_avatar
.write_to(&mut Cursor::new(&mut jpeg_bytes), image::ImageFormat::Jpeg)
.unwrap();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], Some(&jpeg_bytes));
assert_eq!(
encode_snapshot_png(&snapshot, Some(&jpeg_bytes)).unwrap(),
legacy_encode(&snapshot, Some(&jpeg_bytes)).unwrap(),
"transcode-path plain PNG bytes must match the pre-envelope encoder"
);
}
#[test]
fn png_snapshot_transcodes_jpeg_avatar_into_image_body() {
let avatar = image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(
3,
2,
image::Rgb([0x12, 0x34, 0x56]),
));
let mut jpeg_bytes = Vec::new();
avatar
.write_to(&mut Cursor::new(&mut jpeg_bytes), image::ImageFormat::Jpeg)
.unwrap();
let snapshot = build_snapshot(
&minimal_record(),
MemoryLevel::None,
vec![],
Some(&jpeg_bytes),
);
let png_bytes = encode_snapshot_png(&snapshot, Some(&jpeg_bytes)).unwrap();
let decoder = Decoder::new(Cursor::new(png_bytes));
let reader = decoder.read_info().unwrap();
assert_eq!((reader.info().width, reader.info().height), (3, 2));
}
#[test]
fn png_snapshot_downscales_oversize_avatar_under_cap() {
// A large avatar (mirrors Gurney's 2764×4096 image that encoded to ~26 MB)
// must be downscaled for the PNG body so the snapshot stays under the
// 10 MiB cap — while the manifest keeps the untouched source reference.
// An already-PNG oversize avatar exercises the `png_within_body_cap` guard
// that routes it through the downscaling transcode path.
let avatar = image::DynamicImage::ImageRgb8(image::RgbImage::from_fn(2764, 4096, |x, y| {
image::Rgb([(x % 256) as u8, (y % 256) as u8, ((x + y) % 256) as u8])
}));
let mut source_bytes = Vec::new();
avatar
.write_to(&mut Cursor::new(&mut source_bytes), image::ImageFormat::Png)
.unwrap();
let snapshot = build_snapshot(
&minimal_record(),
MemoryLevel::None,
vec![],
Some(&source_bytes),
);
let png_bytes = encode_snapshot_png(&snapshot, Some(&source_bytes)).unwrap();
assert!(
png_bytes.len()
<= super::MAX_PNG_BODY_EDGE as usize * super::MAX_PNG_BODY_EDGE as usize * 4,
"downscaled snapshot ({} bytes) must be far under the 10 MiB cap",
png_bytes.len()
);
let reader = Decoder::new(Cursor::new(png_bytes)).read_info().unwrap();
let (width, height) = (reader.info().width, reader.info().height);
assert!(
width <= 512 && height <= 512,
"body dimensions {width}×{height} must fit the 512px cap"
);
// Aspect ratio preserved: the longest edge (height) is clamped to the cap.
assert_eq!(height, 512, "longest edge should hit the 512px cap");
// The manifest keeps the untouched full-resolution source reference — only
// the PNG body is downscaled. The oversize source bytes exceed the inline
// cap, so the manifest falls back to the record's `avatar_url`.
let manifest =
decode_snapshot_png(&encode_snapshot_png(&snapshot, Some(&source_bytes)).unwrap()).unwrap();
assert_eq!(
manifest.profile.avatar_url.as_deref(),
Some("https://example.com/avatar.png"),
"manifest must preserve the untouched source avatar reference"
);
assert!(
manifest.profile.avatar_data_url.is_none(),
"oversize source bytes must not be inlined into the manifest"
);
}
#[test]
fn png_round_trip_with_core_memory() {
let record = minimal_record();
let entries = vec![AgentSnapshotMemoryEntry {
slug: "core".to_string(),
body: "remember this".to_string(),
}];
let snapshot = build_snapshot(&record, MemoryLevel::Core, entries, None);
let png_bytes = encode_snapshot_png(&snapshot, None).unwrap();
let parsed = decode_snapshot_png(&png_bytes).unwrap();
assert_eq!(parsed.memory, snapshot.memory);
}
#[test]
fn png_round_trip_with_everything_memory() {
let record = minimal_record();
let entries = vec![
AgentSnapshotMemoryEntry {
slug: "core".to_string(),
body: "remember this".to_string(),
},
AgentSnapshotMemoryEntry {
slug: "mem/notes".to_string(),
body: "private notes".to_string(),
},
];
let snapshot = build_snapshot(&record, MemoryLevel::Everything, entries, None);
let png_bytes = encode_snapshot_png(&snapshot, None).unwrap();
let parsed = decode_snapshot_png(&png_bytes).unwrap();
assert_eq!(parsed.memory, snapshot.memory);
}
#[test]
fn png_export_with_no_memory_succeeds() {
let record = minimal_record();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], None);
assert!(encode_snapshot_png(&snapshot, None).is_ok());
}
#[test]
fn png_export_rejects_none_level_with_nonempty_entries() {
// Inconsistent state: level == None but entries is non-empty.
// The encoder must reject this to prevent a memory-leak bypass.
let record = minimal_record();
let entries = vec![AgentSnapshotMemoryEntry {
slug: "core".to_string(),
body: "leaked memory".to_string(),
}];
// Build with entries, then override level to None in the struct.
let mut snapshot = build_snapshot(&record, MemoryLevel::Core, entries, None);
snapshot.memory.level = MemoryLevel::None; // force inconsistency
let result = encode_snapshot_png(&snapshot, None);
assert!(
result.is_err(),
"PNG encoder must reject level=None with non-empty entries"
);
assert!(
result
.unwrap_err()
.contains("memory.level 'none' and non-empty memory entries"),
"Error must explain the malformed memory state"
);
}
// ── Secret exclusion tests ────────────────────────────────────────────────
//
// These tests assert that every field in the exclusion list is absent from
// the serialized snapshot. We serialize to JSON and assert the key is NOT
// present.
fn snapshot_json_string(record: &ManagedAgentRecord) -> String {
let snapshot = build_snapshot(record, MemoryLevel::None, vec![], None);
let bytes = encode_snapshot_json(&snapshot).unwrap();
String::from_utf8(bytes).unwrap()
}
#[test]
fn secret_exclusion_private_key_nsec_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("nsec1secret"),
"nsec must not appear in snapshot"
);
assert!(
!json.contains("privateKeyNsec") && !json.contains("private_key_nsec"),
"privateKeyNsec field must not appear in snapshot"
);
}
#[test]
fn secret_exclusion_auth_tag_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("auth-tag-secret"),
"auth_tag value must not appear in snapshot"
);
assert!(
!json.contains("authTag") && !json.contains("auth_tag"),
"authTag field must not appear in snapshot"
);
}
#[test]
fn secret_exclusion_env_vars_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("API_KEY") && !json.contains("secret123"),
"env_vars content must not appear in snapshot"
);
assert!(
!json.contains("envVars") && !json.contains("env_vars"),
"envVars field must not appear in snapshot"
);
}
#[test]
fn secret_exclusion_relay_url_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("wss://relay.example.com"),
"relay_url value must not appear in snapshot"
);
assert!(
!json.contains("relayUrl") && !json.contains("relay_url"),
"relayUrl field must not appear in snapshot"
);
}
#[test]
fn snapshot_omits_removed_mcp_toolsets_config() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("mcpToolsets") && !json.contains("mcp_toolsets"),
"removed MCP toolsets config must not re-enter snapshots"
);
}
#[test]
fn secret_exclusion_machine_commands_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
// acp_command / agent_command / agent_command_override / agent_args / mcp_command
assert!(
!json.contains("/usr/local/bin/acp"),
"acp_command path must not appear"
);
assert!(
!json.contains("acpCommand") && !json.contains("acp_command"),
"acpCommand field must not appear"
);
assert!(
!json.contains("agentCommand") && !json.contains("agent_command"),
"agentCommand field must not appear"
);
assert!(
!json.contains("mcpCommand") && !json.contains("mcp_command"),
"mcpCommand field must not appear"
);
}
#[test]
fn secret_exclusion_runtime_state_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("runtimePid") && !json.contains("runtime_pid"),
"runtimePid must not appear"
);
assert!(
!json.contains("backendAgentId") && !json.contains("backend_agent_id"),
"backendAgentId must not appear"
);
assert!(
!json.contains("SENTINEL_BACKEND_AGENT_ID"),
"backendAgentId value must not appear"
);
assert!(
!json.contains("providerBinaryPath") && !json.contains("provider_binary_path"),
"providerBinaryPath must not appear"
);
assert!(
!json.contains("SENTINEL_PROVIDER_BINARY"),
"providerBinaryPath value must not appear"
);
assert!(
!json.contains("lastStartedAt") && !json.contains("last_started_at"),
"lastStartedAt must not appear"
);
assert!(
!json.contains("lastExitCode") && !json.contains("last_exit_code"),
"lastExitCode must not appear"
);
// backend blob — neither the type tag nor provider secret must leak.
assert!(
!json.contains("\"backend\"") && !json.contains("backend"),
"backend field must not appear"
);
assert!(
!json.contains("SENTINEL_BACKEND_ID") && !json.contains("SENTINEL_BACKEND_SECRET"),
"backend config values must not appear"
);
// last_error / last_error_code
assert!(
!json.contains("lastError") && !json.contains("last_error"),
"lastError must not appear"
);
assert!(
!json.contains("SENTINEL_LAST_ERROR"),
"lastError value must not appear"
);
assert!(
!json.contains("lastErrorCode") && !json.contains("last_error_code"),
"lastErrorCode must not appear"
);
}
#[test]
fn secret_exclusion_lineage_ids_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
assert!(
!json.contains("team-id-123"),
"source_team value must not appear"
);
assert!(
!json.contains("sourceTeam") && !json.contains("source_team"),
"sourceTeam field must not appear"
);
assert!(
!json.contains("sourceTeamPersonaSlug"),
"sourceTeamPersonaSlug must not appear"
);
assert!(
!json.contains("personaSourceVersion") && !json.contains("persona_source_version"),
"personaSourceVersion must not appear"
);
// personaId
assert!(
!json.contains("personaId") && !json.contains("persona_id"),
"personaId field must not appear"
);
assert!(
!json.contains("SENTINEL_PERSONA_ID"),
"personaId value must not appear"
);
// teamId
assert!(
!json.contains("teamId") && !json.contains("team_id"),
"teamId field must not appear"
);
assert!(
!json.contains("SENTINEL_TEAM_ID"),
"teamId value must not appear"
);
// personaTeamDir
assert!(
!json.contains("personaTeamDir") && !json.contains("persona_team_dir"),
"personaTeamDir field must not appear"
);
assert!(
!json.contains("SENTINEL_TEAM_DIR"),
"personaTeamDir value must not appear"
);
// personaNameInTeam
assert!(
!json.contains("personaNameInTeam") && !json.contains("persona_name_in_team"),
"personaNameInTeam field must not appear"
);
assert!(
!json.contains("SENTINEL_NAME_IN_TEAM"),
"personaNameInTeam value must not appear"
);
}
// ── Definition field presence tests ──────────────────────────────────────
#[test]
fn definition_fields_present_in_snapshot() {
let record = minimal_record();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], None);
assert_eq!(snapshot.definition.name, "Test Agent Display");
assert!(!snapshot.definition.source_is_builtin);
assert_eq!(
snapshot.definition.system_prompt.as_deref(),
Some("You are a test agent.")
);
assert_eq!(snapshot.definition.runtime.as_deref(), Some("goose"));
assert_eq!(snapshot.definition.model.as_deref(), Some("claude-opus-4"));
assert_eq!(snapshot.definition.provider.as_deref(), Some("anthropic"));
assert_eq!(snapshot.definition.name_pool, vec!["Alice", "Bob"]);
// definition_respond_to maps to respond_to in the snapshot definition
assert_eq!(snapshot.definition.respond_to.as_deref(), Some("allowlist"));
// definition_respond_to_allowlist should be included
assert!(!snapshot.definition.respond_to_allowlist.is_empty());
}
#[test]
fn profile_fields_present_in_snapshot() {
let mut record = minimal_record();
record.description = Some(" A careful test agent. ".to_string());
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], None);
assert_eq!(snapshot.profile.display_name, "Test Agent Display");
assert_eq!(
snapshot.profile.about.as_deref(),
Some("A careful test agent.")
);
// No bytes → should fall back to avatar_url
assert_eq!(
snapshot.profile.avatar_url.as_deref(),
Some("https://example.com/avatar.png")
);
assert!(snapshot.profile.avatar_data_url.is_none());
}
#[test]
fn snapshot_rejects_unsafe_or_overlong_description() {
let mut snapshot = build_snapshot(&minimal_record(), MemoryLevel::None, vec![], None);
snapshot.profile.about = Some("unsafe\u{200b}description".to_string());
assert!(validate_snapshot(&snapshot).is_err());
snapshot.profile.about = Some("a".repeat(281));
assert!(validate_snapshot(&snapshot).is_err());
}
#[test]
fn avatar_inlined_when_under_size_limit() {
let record = minimal_record();
let small_png = make_png_with_text("k", "v").unwrap();
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], Some(&small_png));
assert!(snapshot.profile.avatar_data_url.is_some());
assert!(snapshot.profile.avatar_url.is_none());
}
#[test]
fn avatar_url_fallback_when_over_size_limit() {
let mut record = minimal_record();
record.avatar_url = Some("https://example.com/big.png".to_string());
// Synthesize oversized avatar bytes (> 2 MB) — just a large zeroed vec.
let big_bytes = vec![0u8; MAX_AVATAR_INLINE_BYTES + 1];
let snapshot = build_snapshot(&record, MemoryLevel::None, vec![], Some(&big_bytes));
assert!(snapshot.profile.avatar_data_url.is_none());
assert_eq!(
snapshot.profile.avatar_url.as_deref(),
Some("https://example.com/big.png")
);
}
// ── Format/version validation ─────────────────────────────────────────────
#[test]
fn invalid_format_discriminator_is_rejected() {
let mut snapshot = build_snapshot(&minimal_record(), MemoryLevel::None, vec![], None);
snapshot.format = "not-a-buzz-snapshot".to_string();
let bytes = serde_json::to_vec(&snapshot).unwrap();
let result = decode_snapshot_json(&bytes);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unsupported snapshot format"));
}
#[test]
fn unsupported_version_is_rejected() {
let mut snapshot = build_snapshot(&minimal_record(), MemoryLevel::None, vec![], None);
snapshot.version = 99;
let bytes = serde_json::to_vec(&snapshot).unwrap();
let result = decode_snapshot_json(&bytes);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unsupported snapshot version"));
}