#![cfg(feature = "test-utils")]
use anyhow::Result;
use camino::Utf8PathBuf;
use std::fs;
use tempfile::TempDir;
use xchecker::packet::{
ContentSelector, DEFAULT_PACKET_MAX_BYTES, DEFAULT_PACKET_MAX_LINES, PacketBuilder,
};
use xchecker::test_support;
use xchecker::types::Priority;
#[test]
fn test_deterministic_ordering_sorted_paths() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
fs::write(base_path.join("zebra.md"), "# Zebra")?;
fs::write(base_path.join("alpha.md"), "# Alpha")?;
fs::write(base_path.join("beta.md"), "# Beta")?;
let selector = ContentSelector::new()?;
let files = selector.select_files(&base_path)?;
let paths: Vec<String> = files.iter().map(|f| f.path.to_string()).collect();
assert_eq!(paths.len(), 3);
assert!(paths[0].contains("zebra.md"));
assert!(paths[1].contains("beta.md"));
assert!(paths[2].contains("alpha.md"));
Ok(())
}
#[test]
fn test_priority_based_selection() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
fs::write(base_path.join("config.toml"), "# Low priority")?;
fs::write(base_path.join("README.md"), "# Medium priority")?;
fs::write(base_path.join("SPEC-001.md"), "# High priority")?;
fs::write(base_path.join("design.core.yaml"), "# Upstream priority")?;
let selector = ContentSelector::new()?;
let files = selector.select_files(&base_path)?;
assert_eq!(files.len(), 4);
assert_eq!(files[0].priority, Priority::Upstream);
assert!(files[0].path.to_string().contains("design.core.yaml"));
assert_eq!(files[1].priority, Priority::High);
assert!(files[1].path.to_string().contains("SPEC-001.md"));
assert_eq!(files[2].priority, Priority::Medium);
assert!(files[2].path.to_string().contains("README.md"));
assert_eq!(files[3].priority, Priority::Low);
assert!(files[3].path.to_string().contains("config.toml"));
Ok(())
}
#[test]
fn test_lifo_ordering_within_priority() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
fs::write(base_path.join("SPEC-001.md"), "# Spec 1")?;
fs::write(base_path.join("SPEC-002.md"), "# Spec 2")?;
fs::write(base_path.join("SPEC-003.md"), "# Spec 3")?;
let selector = ContentSelector::new()?;
let files = selector.select_files(&base_path)?;
assert_eq!(files.len(), 3);
assert_eq!(files[0].priority, Priority::High);
assert_eq!(files[1].priority, Priority::High);
assert_eq!(files[2].priority, Priority::High);
assert!(files[0].path.to_string().contains("SPEC-003.md"));
assert!(files[1].path.to_string().contains("SPEC-002.md"));
assert!(files[2].path.to_string().contains("SPEC-001.md"));
Ok(())
}
#[test]
fn test_byte_and_line_counting() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let content = "Line 1\nLine 2\nLine 3\n";
fs::write(base_path.join("test.md"), content)?;
let mut builder = PacketBuilder::new()?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert!(packet.budget_used.bytes_used > 0);
assert!(packet.budget_used.lines_used > 0);
assert!(packet.budget_used.bytes_used >= content.len());
assert!(packet.budget_used.lines_used >= 3);
Ok(())
}
#[test]
fn test_limit_enforcement_overflow() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let large_content = "data: value\n".repeat(1000);
fs::write(base_path.join("large.core.yaml"), &large_content)?;
let mut builder = PacketBuilder::with_limits(100, 5)?;
let result = builder.build_packet(&base_path, "test", &context_dir, None);
assert!(result.is_err());
let err = result.unwrap_err();
let err_string = format!("{:?}", err);
assert!(
err_string.contains("PacketOverflow")
|| err_string.contains("packet")
|| err_string.to_lowercase().contains("overflow")
|| err_string.contains("budget")
|| err_string.contains("limit")
);
Ok(())
}
#[test]
fn test_upstream_non_evictable() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("small.core.yaml"), "key: value")?;
fs::write(base_path.join("large.md"), "# Large\n".repeat(200))?;
let mut builder = PacketBuilder::with_limits(300, 30)?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert!(
packet
.evidence
.files
.iter()
.any(|f| f.path.contains("small.core.yaml"))
);
assert!(
!packet
.evidence
.files
.iter()
.any(|f| f.path.contains("large.md"))
);
Ok(())
}
#[test]
fn test_packet_preview_always_written() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("test.md"), "# Test")?;
let mut builder = PacketBuilder::new()?;
let _packet = builder.build_packet(&base_path, "requirements", &context_dir, None)?;
let context_file = context_dir.join("requirements-packet.txt");
assert!(context_file.exists());
let preview_content = fs::read_to_string(&context_file)?;
assert!(preview_content.contains("test.md"));
Ok(())
}
#[test]
fn test_packet_preview_written_on_overflow() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let upstream_content = "key: value\n".repeat(5); fs::write(base_path.join("data.core.yaml"), &upstream_content)?;
let upstream2 = "other: data\n".repeat(5); fs::write(base_path.join("more.core.yaml"), &upstream2)?;
let mut builder = PacketBuilder::with_limits(100, 50)?;
let _result = builder.build_packet(&base_path, "test", &context_dir, None);
let context_file = context_dir.join("test-packet.txt");
assert!(context_file.exists());
Ok(())
}
#[test]
fn test_packet_evidence_completeness() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("test.md"), "# Test")?;
fs::write(base_path.join("config.yaml"), "key: value")?;
let mut builder = PacketBuilder::new()?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert_eq!(packet.evidence.max_bytes, DEFAULT_PACKET_MAX_BYTES);
assert_eq!(packet.evidence.max_lines, DEFAULT_PACKET_MAX_LINES);
assert_eq!(packet.evidence.files.len(), 2);
for file_evidence in &packet.evidence.files {
assert!(!file_evidence.path.is_empty());
assert!(!file_evidence.blake3_pre_redaction.is_empty());
assert_eq!(file_evidence.blake3_pre_redaction.len(), 64); }
Ok(())
}
#[test]
fn test_priority_assignment_comprehensive() -> Result<()> {
let selector = ContentSelector::new()?;
assert_eq!(
selector.get_priority(Utf8PathBuf::from("test.core.yaml").as_path()),
Priority::Upstream
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("docs/design.core.yaml").as_path()),
Priority::Upstream
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("SPEC-001.md").as_path()),
Priority::High
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("docs/ADR-002.md").as_path()),
Priority::High
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("REPORT-final.md").as_path()),
Priority::High
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("README.md").as_path()),
Priority::Medium
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("docs/SCHEMA.yaml").as_path()),
Priority::Medium
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("config.toml").as_path()),
Priority::Low
);
assert_eq!(
selector.get_priority(Utf8PathBuf::from("src/main.rs").as_path()),
Priority::Low
);
Ok(())
}
#[test]
fn test_deterministic_ordering_multiple_runs() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
fs::write(base_path.join("file1.md"), "# File 1")?;
fs::write(base_path.join("file2.md"), "# File 2")?;
fs::write(base_path.join("file3.md"), "# File 3")?;
let selector = ContentSelector::new()?;
let files1 = selector.select_files(&base_path)?;
let files2 = selector.select_files(&base_path)?;
let files3 = selector.select_files(&base_path)?;
assert_eq!(files1.len(), files2.len());
assert_eq!(files2.len(), files3.len());
for i in 0..files1.len() {
assert_eq!(files1[i].path, files2[i].path);
assert_eq!(files2[i].path, files3[i].path);
assert_eq!(files1[i].priority, files2[i].priority);
}
Ok(())
}
#[test]
fn test_budget_tracking_multiple_files() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("file1.md"), "12345")?; fs::write(base_path.join("file2.md"), "67890")?; fs::write(base_path.join("file3.md"), "abcde")?;
let mut builder = PacketBuilder::new()?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert_eq!(packet.evidence.files.len(), 3);
assert!(packet.budget_used.bytes_used >= 15); assert!(packet.budget_used.bytes_used < DEFAULT_PACKET_MAX_BYTES);
assert!(!packet.budget_used.is_exceeded());
Ok(())
}
#[test]
fn test_byte_limit_enforcement() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let content = "x".repeat(200);
fs::write(base_path.join("large.core.yaml"), &content)?;
let mut builder = PacketBuilder::with_limits(100, 1000)?;
let result = builder.build_packet(&base_path, "test", &context_dir, None);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_line_limit_enforcement() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let content = "line\n".repeat(100);
fs::write(base_path.join("many_lines.core.yaml"), &content)?;
let mut builder = PacketBuilder::with_limits(100000, 10)?;
let result = builder.build_packet(&base_path, "test", &context_dir, None);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_mixed_priority_budget_constraints() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("upstream.core.yaml"), "key: value")?; fs::write(base_path.join("SPEC.md"), "# Spec")?; fs::write(base_path.join("README.md"), "# Readme")?; fs::write(base_path.join("large.md"), "x".repeat(500))?;
let mut builder = PacketBuilder::with_limits(200, 50)?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert!(
packet
.evidence
.files
.iter()
.any(|f| f.path.contains("upstream.core.yaml"))
);
assert!(
packet
.evidence
.files
.iter()
.any(|f| f.path.contains("SPEC.md"))
);
assert!(
!packet
.evidence
.files
.iter()
.any(|f| f.path.contains("large.md"))
);
Ok(())
}
#[test]
fn test_file_path_sorting_same_priority() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
fs::write(base_path.join("zebra.md"), "z")?;
fs::write(base_path.join("alpha.md"), "a")?;
fs::write(base_path.join("middle.md"), "m")?;
let selector = ContentSelector::new()?;
let files = selector.select_files(&base_path)?;
assert_eq!(files.len(), 3);
let paths: Vec<String> = files
.iter()
.map(|f| f.path.file_name().unwrap().to_string())
.collect();
assert_eq!(paths[0], "zebra.md");
assert_eq!(paths[1], "middle.md");
assert_eq!(paths[2], "alpha.md");
Ok(())
}
#[test]
fn test_blake3_hash_in_evidence() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("test.md"), "test content")?;
let mut builder = PacketBuilder::new()?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert_eq!(packet.evidence.files.len(), 1);
let hash = &packet.evidence.files[0].blake3_pre_redaction;
assert_eq!(hash.len(), 64);
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
Ok(())
}
#[test]
fn test_packet_content_format() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("test.md"), "# Test Content")?;
let mut builder = PacketBuilder::new()?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
assert!(packet.content.contains("=== "));
assert!(packet.content.contains("test.md"));
assert!(packet.content.contains("==="));
Ok(())
}
#[test]
fn test_packet_manifest_on_overflow() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let content1 = "data: value\n".repeat(5);
fs::write(base_path.join("first.core.yaml"), &content1)?;
let content2 = "more: stuff\n".repeat(5);
fs::write(base_path.join("second.core.yaml"), &content2)?;
let mut builder = PacketBuilder::with_limits(100, 5)?;
let _result = builder.build_packet(&base_path, "test", &context_dir, None);
let manifest_file = context_dir.join("test-packet.manifest.json");
assert!(manifest_file.exists());
let manifest_content = fs::read_to_string(&manifest_file)?;
assert!(manifest_content.contains("overflow"));
assert!(manifest_content.contains("budget"));
assert!(manifest_content.contains("max_bytes"));
assert!(manifest_content.contains("max_lines"));
assert!(manifest_content.contains("used_bytes"));
assert!(manifest_content.contains("used_lines"));
assert!(manifest_content.contains("files"));
assert!(
manifest_content.contains("first.core.yaml")
|| manifest_content.contains("second.core.yaml")
);
assert!(!manifest_content.contains("data: value"));
Ok(())
}
#[test]
fn test_manifest_no_content_leak() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let content1 = "unique: val\n".repeat(3);
let file_path = base_path.join("config.core.yaml");
fs::write(&file_path, &content1)?;
let content2 = "other: data\n".repeat(3);
fs::write(base_path.join("more.core.yaml"), &content2)?;
assert!(file_path.exists(), "Test file must exist");
let mut builder = PacketBuilder::with_limits(100, 3)?;
let result = builder.build_packet(&base_path, "test", &context_dir, None);
assert!(result.is_err(), "Packet should have overflowed");
let manifest_file = context_dir.join("test-packet.manifest.json");
assert!(manifest_file.exists(), "Manifest file should exist");
let manifest_content = fs::read_to_string(&manifest_file)?;
assert!(!manifest_content.contains("unique: val"));
assert!(
manifest_content.contains("config.core.yaml")
|| manifest_content.contains("more.core.yaml")
);
assert!(manifest_content.contains("blake3_pre_redaction"));
assert!(manifest_content.contains("priority"));
Ok(())
}
#[test]
fn test_debug_packet_writing() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("test.md"), "# Test Content")?;
let mut builder = PacketBuilder::new()?;
let packet = builder.build_packet(&base_path, "test", &context_dir, None)?;
builder.write_debug_packet(&packet.content, "test", &context_dir)?;
let debug_file = context_dir.join("test-packet-debug.txt");
assert!(debug_file.exists());
let debug_content = fs::read_to_string(&debug_file)?;
assert!(debug_content.contains("Test Content"));
assert!(debug_content.contains("test.md"));
Ok(())
}
#[test]
fn test_debug_packet_not_written_on_secret() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
let token = test_support::github_pat();
fs::write(base_path.join("secret.md"), format!("token: {}", token))?;
let mut builder = PacketBuilder::new()?;
let result = builder.build_packet(&base_path, "test", &context_dir, None);
assert!(result.is_err());
let debug_file = context_dir.join("test-packet-debug.txt");
assert!(!debug_file.exists());
Ok(())
}
#[test]
fn test_manifest_includes_priorities() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("upstream.core.yaml"), "x".repeat(50))?;
fs::write(base_path.join("another.core.yaml"), "y".repeat(50))?;
let mut builder = PacketBuilder::with_limits(100, 5)?;
let _result = builder.build_packet(&base_path, "test", &context_dir, None);
let manifest_file = context_dir.join("test-packet.manifest.json");
let manifest_content = fs::read_to_string(&manifest_file)?;
assert!(manifest_content.contains("Upstream") || manifest_content.contains("priority"));
assert!(
manifest_content.contains("upstream.core.yaml")
|| manifest_content.contains("another.core.yaml")
);
Ok(())
}
#[test]
fn test_manifest_includes_blake3_hashes() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
let context_dir = base_path.join("context");
fs::write(base_path.join("first.core.yaml"), "x".repeat(50))?;
fs::write(base_path.join("second.core.yaml"), "y".repeat(50))?;
let mut builder = PacketBuilder::with_limits(100, 5)?;
let _result = builder.build_packet(&base_path, "test", &context_dir, None);
let manifest_file = context_dir.join("test-packet.manifest.json");
let manifest_content = fs::read_to_string(&manifest_file)?;
assert!(manifest_content.contains("blake3_pre_redaction"));
assert!(manifest_content.contains("\"blake3_pre_redaction\":"));
Ok(())
}
#[test]
fn test_comprehensive_deterministic_ordering() -> Result<()> {
let temp_dir = TempDir::new()?;
let base_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
fs::write(base_path.join("z-file.core.yaml"), "upstream")?;
fs::write(base_path.join("a-file.core.yaml"), "upstream")?;
fs::write(base_path.join("SPEC-Z.md"), "high")?;
fs::write(base_path.join("SPEC-A.md"), "high")?;
fs::write(base_path.join("README-Z.md"), "medium")?;
fs::write(base_path.join("README-A.md"), "medium")?;
fs::write(base_path.join("z-misc.md"), "low")?;
fs::write(base_path.join("a-misc.md"), "low")?;
let selector = ContentSelector::new()?;
let files = selector.select_files(&base_path)?;
let mut last_priority_value = 0u8; for file in &files {
let current_priority_value = match file.priority {
Priority::Upstream => 0,
Priority::High => 1,
Priority::Medium => 2,
Priority::Low => 3,
};
assert!(
current_priority_value >= last_priority_value,
"Priority ordering violated: {:?} came after priority value {}",
file.priority,
last_priority_value
);
last_priority_value = current_priority_value;
}
let upstream_files: Vec<_> = files
.iter()
.filter(|f| f.priority == Priority::Upstream)
.collect();
if upstream_files.len() > 1 {
for i in 0..upstream_files.len() - 1 {
assert!(upstream_files[i].path >= upstream_files[i + 1].path);
}
}
Ok(())
}