use std::sync::Arc;
use crate::tests::helpers::make_analyzer;
use crate::tools::common::no_cache_meta;
use aptu_coder_core::analyze;
use aptu_coder_core::cache::CacheTier;
#[tokio::test]
async fn test_no_cache_meta_on_analyze_directory_result() {
use aptu_coder_core::types::AnalyzeDirectoryParams;
use tempfile::TempDir;
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("main.rs"), "fn main() {}").unwrap();
let analyzer = make_analyzer();
let params: AnalyzeDirectoryParams = serde_json::from_value(serde_json::json!({
"path": dir.path().to_str().unwrap(),
}))
.unwrap();
let ct = tokio_util::sync::CancellationToken::new();
let (arc_output, _cache_hit) = analyzer.handle_overview_mode(¶ms, ct).await.unwrap();
let meta = no_cache_meta();
assert_eq!(
meta.0.get("cache_hint").and_then(|v| v.as_str()),
Some("no-cache"),
);
drop(arc_output);
}
#[tokio::test]
async fn test_analyze_directory_cache_hit_metrics() {
use aptu_coder_core::types::AnalyzeDirectoryParams;
use tempfile::TempDir;
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("lib.rs"), "fn foo() {}").unwrap();
let analyzer = make_analyzer();
let params: AnalyzeDirectoryParams = serde_json::from_value(serde_json::json!({
"path": dir.path().to_str().unwrap(),
}))
.unwrap();
let ct1 = tokio_util::sync::CancellationToken::new();
let (_out1, hit1) = analyzer.handle_overview_mode(¶ms, ct1).await.unwrap();
let ct2 = tokio_util::sync::CancellationToken::new();
let (_out2, hit2) = analyzer.handle_overview_mode(¶ms, ct2).await.unwrap();
assert_eq!(hit1, CacheTier::Miss, "first call must be a cache miss");
assert_eq!(hit2, CacheTier::L1Memory, "second call must be a cache hit");
}
#[test]
fn test_analyze_module_cache_hit_metrics() {
use std::io::Write as _;
use tempfile::NamedTempFile;
let cwd = std::env::current_dir().unwrap();
let mut f = NamedTempFile::with_suffix_in(".rs", &cwd).unwrap();
write!(f, "use std::io;\nfn bar() {{}}\n").unwrap();
f.flush().unwrap();
let result = analyze::analyze_module_file(f.path().to_str().unwrap());
let module_info = result.expect("analyze_module_file must succeed");
assert_eq!(
module_info.functions.len(),
1,
"expected exactly one function"
);
assert_eq!(module_info.functions[0].name, "bar");
assert_eq!(module_info.imports.len(), 1, "expected exactly one import");
assert!(
module_info.imports[0].module.contains("std"),
"import module must contain 'std', got: {}",
module_info.imports[0].module
);
}
#[test]
#[serial_test::serial]
fn test_file_cache_capacity_default() {
unsafe { std::env::remove_var("APTU_CODER_FILE_CACHE_CAPACITY") };
let analyzer = make_analyzer();
assert_eq!(analyzer.cache.file_capacity(), 100);
}
#[test]
#[serial_test::serial]
fn test_file_cache_capacity_from_env() {
unsafe { std::env::set_var("APTU_CODER_FILE_CACHE_CAPACITY", "42") };
let analyzer = make_analyzer();
unsafe { std::env::remove_var("APTU_CODER_FILE_CACHE_CAPACITY") };
assert_eq!(analyzer.cache.file_capacity(), 42);
}
#[test]
fn test_analyze_symbol_cache_fields_use_cache_tier_enum() {
assert_eq!(
CacheTier::Miss.as_str(),
"miss",
"CacheTier::Miss.as_str() must stay \"miss\" -- analyze_symbol metrics depend on it"
);
assert!(
!matches!(CacheTier::Miss, CacheTier::L1Memory | CacheTier::L2Disk),
"CacheTier::Miss must not be a hit variant (cache_hit=false for a miss)"
);
}
#[test]
fn test_metric_chars_threshold_breach_fires() {
let output_chars: usize = 35_000;
let event = crate::metrics::MetricEvent {
ts: 0,
tool: "exec_command",
duration_ms: 1,
output_chars,
param_path_depth: 0,
max_depth: None,
result: "ok",
error_type: None,
error_subtype: None,
session_id: None,
seq: None,
cache_hit: None,
cache_write_failure: None,
cache_tier: None,
exit_code: None,
timed_out: false,
output_truncated: None,
chars_threshold_breach: output_chars > 30_000,
file_ext: None,
filter_applied: None,
language: None,
git_ref_used: false,
summary_mode: false,
is_paginated: false,
fields_projected: false,
match_mode: None,
follow_depth: None,
import_lookup: false,
def_use: false,
impl_only: false,
stdin_provided: false,
timeout_configured_ms: None,
drain_timeout_ms: None,
working_dir_used: false,
l1_eviction_count: None,
l2_entry_count: None,
l2_size_bytes: None,
stdout_bytes_raw: None,
stderr_bytes_raw: None,
};
assert!(
event.chars_threshold_breach,
"chars_threshold_breach should be true for output_chars=35000"
);
}
#[test]
fn test_metric_chars_threshold_breach_no_fire() {
let output_chars: usize = 5_000;
let event = crate::metrics::MetricEvent {
ts: 0,
tool: "exec_command",
duration_ms: 1,
output_chars,
param_path_depth: 0,
max_depth: None,
result: "ok",
error_type: None,
error_subtype: None,
session_id: None,
seq: None,
cache_hit: None,
cache_write_failure: None,
cache_tier: None,
exit_code: None,
timed_out: false,
output_truncated: None,
chars_threshold_breach: output_chars > 30_000,
file_ext: None,
filter_applied: None,
language: None,
git_ref_used: false,
summary_mode: false,
is_paginated: false,
fields_projected: false,
match_mode: None,
follow_depth: None,
import_lookup: false,
def_use: false,
impl_only: false,
stdin_provided: false,
timeout_configured_ms: None,
drain_timeout_ms: None,
working_dir_used: false,
l1_eviction_count: None,
l2_entry_count: None,
l2_size_bytes: None,
stdout_bytes_raw: None,
stderr_bytes_raw: None,
};
assert!(
!event.chars_threshold_breach,
"chars_threshold_breach should be false for output_chars=5000"
);
}