use std::fmt::Write as _;
use clap::ValueEnum;
use crate::context::types::ContextBundle;
use crate::db;
#[derive(Debug, Clone)]
pub struct SearchMetadata {
pub query: String,
pub mode: String,
pub hits: usize,
pub total_estimate: usize,
}
#[derive(Clone, Debug, PartialEq, ValueEnum)]
pub enum OutputFormat {
Json,
Agent,
}
pub fn format_hits_agent(hits: &[db::SearchHit], meta: &SearchMetadata) -> String {
let mut output = String::new();
let escaped_query = meta.query.replace('"', "\\\"");
let _ = write!(
output,
"SEARCH query=\"{}\" | hits={} | total_estimate={} | mode={}",
escaped_query, meta.hits, meta.total_estimate, meta.mode
);
for hit in hits.iter() {
output.push('\n');
let _ = write!(output, "{}:{}", hit.file_relpath, hit.start_line);
let kind_segment = match &hit.symbol_name {
Some(name) if !name.is_empty() => {
format!("{} {}", hit.kind, name)
}
_ => hit.kind.clone(),
};
let _ = write!(output, " | {}", kind_segment);
let _ = write!(output, " | {:.2}", hit.score);
let preview_text = match &hit.preview {
Some(text) if !text.is_empty() => sanitize_newlines(text),
_ => "-".to_string(),
};
let _ = write!(output, " | {}", preview_text);
}
output
}
pub fn format_hits_json_search(
hits: &[db::SearchHit],
meta: &SearchMetadata,
) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&serde_json::json!({
"hits": hits,
"total_matches": meta.total_estimate,
"query": meta.query,
"mode": meta.mode,
}))
}
pub fn format_hits_json_vector(
hits: &[serde_json::Value],
total: usize,
query: &str,
mode: &str,
k: usize,
threshold: Option<f32>,
) -> Result<String, serde_json::Error> {
let output = serde_json::json!({
"hits": hits,
"total": total,
"query": query,
"mode": mode,
"k": k,
"threshold": threshold,
});
serde_json::to_string_pretty(&output)
}
pub fn format_context_agent(bundle: &ContextBundle, chunk_id: i64, budget: usize) -> String {
let mut output = String::new();
let total_tokens: usize = bundle.items.iter().map(|i| i.tokens).sum();
let truncated_flag = if bundle.truncated { "yes" } else { "no" };
let _ = writeln!(
output,
"CONTEXT chunk_id={} | tokens={}/{} | items={} | truncated={}",
chunk_id,
total_tokens,
budget,
bundle.items.len(),
truncated_flag
);
for item in &bundle.items {
let safe_reason = item.reason.replace('|', " ");
let safe_relpath = item.relpath.replace('|', " ");
let location = format!("{}:{}-{}", safe_relpath, item.range.start, item.range.end);
if item.role == "primary" {
let safe_content = item.content.replace('\0', "");
let preview_lines: Vec<&str> = safe_content.lines().take(3).collect();
let preview_joined = preview_lines.join(" ");
let preview_sanitized = sanitize_newlines(&preview_joined);
let preview_capped: String = preview_sanitized.chars().take(200).collect();
let _ = writeln!(
output,
"{} | {} | {} | {} | {}",
item.role, location, item.tokens, safe_reason, preview_capped
);
} else {
let _ = writeln!(
output,
"{} | {} | {} | {}",
item.role, location, item.tokens, safe_reason
);
}
}
if output.ends_with('\n') {
output.pop();
}
output
}
#[allow(clippy::collapsible_str_replace)]
pub fn sanitize_newlines(text: &str) -> String {
text.replace("\r\n", " ")
.replace('\n', " ")
.replace('\r', " ")
}
pub const KNOWN_ERROR_TYPES: &[&str] = &[
"database",
"embedding_provider",
"config_error",
"not_found",
"validation",
"timeout",
"unknown",
];
pub fn format_agent_error(error_type: &str, message: &str, suggestion: &str) -> String {
if !KNOWN_ERROR_TYPES.contains(&error_type) {
let message_preview: String = message.chars().take(50).collect();
tracing::warn!(
error_type = error_type,
message_preview = %message_preview,
"Unknown error type used in format_agent_error"
);
}
let sanitized_message = sanitize_newlines(&message.replace('|', "-"));
let sanitized_suggestion = sanitize_newlines(&suggestion.replace('|', "-"));
format!(
"ERROR | type={} | message={} | suggestion={}",
error_type, sanitized_message, sanitized_suggestion
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::SearchHit;
fn make_hit(
file: &str,
line: i32,
kind: &str,
symbol: Option<&str>,
score: f64,
preview: Option<&str>,
) -> SearchHit {
SearchHit {
chunk_id: 1,
score,
file_relpath: file.to_string(),
symbol_name: symbol.map(|s| s.to_string()),
kind: kind.to_string(),
start_line: line,
end_line: line + 10,
base_score: None,
kind_mult: None,
exact_mult: None,
preview: preview.map(|s| s.to_string()),
}
}
fn make_test_metadata() -> SearchMetadata {
SearchMetadata {
query: "test".to_string(),
mode: "fts".to_string(),
hits: 5,
total_estimate: 10,
}
}
fn agent_hit_lines(output: &str) -> Vec<&str> {
let lines: Vec<&str> = output.lines().collect();
if lines.len() > 1 {
lines[1..].to_vec()
} else {
vec![]
}
}
#[test]
fn test_agent_format_basic() {
let hits = vec![make_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point for the application"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines.len(), 1);
assert_eq!(
lines[0],
"src/app.rs:42 | func main | 0.92 | Entry point for the application"
);
}
#[test]
fn test_agent_format_missing_symbol() {
let hits = vec![make_hit(
"docs/api.md",
8,
"heading_2",
None,
0.73,
Some("Authentication API reference"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"docs/api.md:8 | heading_2 | 0.73 | Authentication API reference"
);
}
#[test]
fn test_agent_format_empty_symbol() {
let hits = vec![make_hit(
"src/lib.rs",
1,
"module",
Some(""),
0.50,
Some("Module declarations"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"src/lib.rs:1 | module | 0.50 | Module declarations"
);
}
#[test]
fn test_agent_format_missing_preview() {
let hits = vec![make_hit("src/lib.rs", 1, "func", Some("init"), 0.85, None)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines[0], "src/lib.rs:1 | func init | 0.85 | -");
}
#[test]
fn test_agent_format_empty_preview() {
let hits = vec![make_hit(
"src/lib.rs",
1,
"func",
Some("init"),
0.85,
Some(""),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines[0], "src/lib.rs:1 | func init | 0.85 | -");
}
#[test]
fn test_agent_format_newline_sanitization() {
let hits = vec![make_hit(
"src/main.rs",
10,
"func",
Some("run"),
0.60,
Some("Line one\nLine two\r\nLine three\rLine four"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"src/main.rs:10 | func run | 0.60 | Line one Line two Line three Line four"
);
}
#[test]
fn test_agent_format_empty_hits() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "test".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_agent(&hits, &meta);
assert!(output.starts_with("SEARCH query="));
assert_eq!(agent_hit_lines(&output).len(), 0);
}
#[test]
fn test_agent_format_multiple_hits() {
let hits = vec![
make_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point"),
),
make_hit(
"docs/api.md",
8,
"heading_2",
None,
0.73,
Some("API reference"),
),
];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "src/app.rs:42 | func main | 0.92 | Entry point");
assert_eq!(lines[1], "docs/api.md:8 | heading_2 | 0.73 | API reference");
}
#[test]
fn test_agent_format_score_precision() {
let meta = make_test_metadata();
let hits = vec![make_hit("a.rs", 1, "func", Some("f"), 1.0, Some("text"))];
let output = format_hits_agent(&hits, &meta);
assert!(output.contains("| 1.00 |"));
let hits = vec![make_hit("a.rs", 1, "func", Some("f"), 0.1, Some("text"))];
let output = format_hits_agent(&hits, &meta);
assert!(output.contains("| 0.10 |"));
let hits = vec![make_hit(
"a.rs",
1,
"func",
Some("f"),
0.123456,
Some("text"),
)];
let output = format_hits_agent(&hits, &meta);
assert!(output.contains("| 0.12 |"));
}
#[test]
fn test_json_search_format() {
let hits = vec![make_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point"),
)];
let meta = make_test_metadata();
let output = format_hits_json_search(&hits, &meta).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed["hits"].is_array());
assert_eq!(parsed["hits"].as_array().unwrap().len(), 1);
assert_eq!(parsed["hits"][0]["file_relpath"], "src/app.rs");
}
#[test]
fn test_json_vector_format() {
let hits = vec![serde_json::json!({
"chunk_id": 1,
"score": 0.92,
"file_relpath": "test/file.rs",
"file_path": "test/file.rs",
})];
let output =
format_hits_json_vector(&hits, 1, "test query", "vector", 10, Some(0.5)).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert_eq!(parsed["total"], 1);
assert_eq!(parsed["query"], "test query");
assert_eq!(parsed["mode"], "vector");
assert_eq!(parsed["k"], 10);
assert_eq!(parsed["threshold"], 0.5);
assert!(parsed["hits"].is_array());
let hits_arr = parsed["hits"].as_array().unwrap();
assert!(hits_arr[0]["file_relpath"].is_string());
assert_eq!(hits_arr[0]["file_relpath"], hits_arr[0]["file_path"]);
}
#[test]
fn test_json_vector_format_no_threshold() {
let hits: Vec<serde_json::Value> = vec![];
let output = format_hits_json_vector(&hits, 0, "query", "vector", 5, None).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed["threshold"].is_null());
}
#[test]
fn test_sanitize_newlines() {
assert_eq!(sanitize_newlines("hello\nworld"), "hello world");
assert_eq!(sanitize_newlines("hello\r\nworld"), "hello world");
assert_eq!(sanitize_newlines("hello\rworld"), "hello world");
assert_eq!(sanitize_newlines("a\nb\r\nc\rd"), "a b c d");
assert_eq!(sanitize_newlines("no newlines"), "no newlines");
assert_eq!(sanitize_newlines(""), "");
}
fn make_test_hit(
file: &str,
line: i32,
kind: &str,
symbol: Option<&str>,
score: f64,
preview: Option<&str>,
) -> SearchHit {
SearchHit {
chunk_id: 1,
score,
file_relpath: file.to_string(),
symbol_name: symbol.map(|s| s.to_string()),
kind: kind.to_string(),
start_line: line,
end_line: line + 10,
base_score: None,
kind_mult: None,
exact_mult: None,
preview: preview.map(|s| s.to_string()),
}
}
#[test]
fn test_format_hits_agent_normal_all_fields() {
let hits = vec![make_test_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point for the application"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"src/app.rs:42 | func main | 0.92 | Entry point for the application"
);
}
#[test]
fn test_format_hits_agent_without_symbol_name() {
let hits = vec![make_test_hit(
"docs/api.md",
8,
"heading_2",
None,
0.73,
Some("Authentication API reference"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"docs/api.md:8 | heading_2 | 0.73 | Authentication API reference"
);
for line in &lines {
assert!(!line.contains("null"));
}
}
#[test]
fn test_format_hits_agent_without_preview() {
let hits = vec![make_test_hit(
"src/lib.rs",
1,
"func",
Some("init"),
0.85,
None,
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines[0], "src/lib.rs:1 | func init | 0.85 | -");
}
#[test]
fn test_format_hits_agent_empty_results() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "test".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_agent(&hits, &meta);
assert!(output.starts_with("SEARCH query="));
assert_eq!(agent_hit_lines(&output).len(), 0);
}
#[test]
fn test_format_hits_agent_multiple_results() {
let hits = vec![
make_test_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point"),
),
make_test_hit(
"docs/api.md",
8,
"heading_2",
None,
0.73,
Some("API reference"),
),
make_test_hit(
"tests/test_app.rs",
100,
"func",
Some("test_main"),
0.55,
Some("Test case"),
),
];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "src/app.rs:42 | func main | 0.92 | Entry point");
assert_eq!(lines[1], "docs/api.md:8 | heading_2 | 0.73 | API reference");
assert_eq!(
lines[2],
"tests/test_app.rs:100 | func test_main | 0.55 | Test case"
);
}
#[test]
fn test_format_hits_agent_score_precision_point_nine() {
let hits = vec![make_test_hit(
"a.rs",
1,
"func",
Some("f"),
0.9,
Some("text"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines[0], "a.rs:1 | func f | 0.90 | text");
}
#[test]
fn test_format_hits_agent_score_precision_zero() {
let hits = vec![make_test_hit(
"a.rs",
1,
"func",
Some("f"),
0.0,
Some("text"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines[0], "a.rs:1 | func f | 0.00 | text");
}
#[test]
fn test_format_hits_agent_score_precision_high() {
let hits = vec![make_test_hit(
"a.rs",
1,
"func",
Some("f"),
17.5,
Some("text"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(lines[0], "a.rs:1 | func f | 17.50 | text");
}
#[test]
fn test_format_hits_agent_unicode_file_path() {
let hits = vec![make_test_hit(
"src/\u{00e9}dit.rs",
42,
"func",
Some("main"),
0.92,
Some("Funci\u{00f3}n principal"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert!(lines[0].contains("src/\u{00e9}dit.rs"));
assert_eq!(
lines[0],
"src/\u{00e9}dit.rs:42 | func main | 0.92 | Funci\u{00f3}n principal"
);
}
#[test]
fn test_format_hits_agent_unicode_preview() {
let hits = vec![make_test_hit(
"src/main.rs",
1,
"func",
Some("greet"),
0.80,
Some("\u{3053}\u{3093}\u{306b}\u{3061}\u{306f}\u{4e16}\u{754c}"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert!(lines[0].contains("\u{3053}\u{3093}\u{306b}\u{3061}\u{306f}\u{4e16}\u{754c}"));
assert_eq!(
lines[0],
"src/main.rs:1 | func greet | 0.80 | \u{3053}\u{3093}\u{306b}\u{3061}\u{306f}\u{4e16}\u{754c}"
);
}
#[test]
fn test_format_hits_agent_long_file_path() {
let long_path = format!("src/{}/file.rs", "a".repeat(190));
assert!(long_path.len() >= 200);
let hits = vec![make_test_hit(
&long_path,
1,
"func",
Some("f"),
0.50,
Some("code"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert!(lines[0].contains(&long_path));
assert_eq!(lines[0], format!("{}:1 | func f | 0.50 | code", long_path));
}
#[test]
fn test_format_hits_agent_empty_symbol_name_treated_as_none() {
let meta = make_test_metadata();
let hits_empty = vec![make_test_hit(
"src/lib.rs",
1,
"module",
Some(""),
0.50,
Some("Module declarations"),
)];
let hits_none = vec![make_test_hit(
"src/lib.rs",
1,
"module",
None,
0.50,
Some("Module declarations"),
)];
let output_empty = format_hits_agent(&hits_empty, &meta);
let output_none = format_hits_agent(&hits_none, &meta);
let lines_empty = agent_hit_lines(&output_empty);
let lines_none = agent_hit_lines(&output_none);
assert_eq!(lines_empty[0], lines_none[0]);
assert_eq!(
lines_empty[0],
"src/lib.rs:1 | module | 0.50 | Module declarations"
);
}
#[test]
fn test_format_hits_agent_preview_with_newlines() {
let hits = vec![make_test_hit(
"src/main.rs",
10,
"func",
Some("run"),
0.60,
Some("Line one\nLine two"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"src/main.rs:10 | func run | 0.60 | Line one Line two"
);
assert!(!lines[0].contains('\n'));
}
#[test]
fn test_format_hits_agent_preview_with_multiple_newline_types() {
let hits = vec![make_test_hit(
"src/main.rs",
10,
"func",
Some("run"),
0.60,
Some("Line one\nLine two\r\nLine three\rLine four"),
)];
let meta = make_test_metadata();
let output = format_hits_agent(&hits, &meta);
let lines = agent_hit_lines(&output);
assert_eq!(
lines[0],
"src/main.rs:10 | func run | 0.60 | Line one Line two Line three Line four"
);
assert!(!lines[0].contains('\n'));
assert!(!lines[0].contains('\r'));
}
#[test]
fn test_json_search_hits_array_structure() {
let hits = vec![
make_test_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point"),
),
make_test_hit("src/lib.rs", 10, "module", None, 0.75, None),
];
let meta = make_test_metadata();
let output = format_hits_json_search(&hits, &meta).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed.is_object());
assert!(parsed["hits"].is_array());
let hits_arr = parsed["hits"].as_array().unwrap();
assert_eq!(hits_arr.len(), 2);
let hit0 = &hits_arr[0];
assert_eq!(hit0["file_relpath"], "src/app.rs");
assert_eq!(hit0["start_line"], 42);
assert_eq!(hit0["kind"], "func");
assert_eq!(hit0["symbol_name"], "main");
assert_eq!(hit0["score"], 0.92);
assert_eq!(hit0["chunk_id"], 1);
assert_eq!(hit0["preview"], "Entry point");
let hit1 = &hits_arr[1];
assert_eq!(hit1["file_relpath"], "src/lib.rs");
assert!(hit1["symbol_name"].is_null());
assert!(hit1.get("preview").is_none() || hit1["preview"].is_null());
}
#[test]
fn test_json_vector_search_metadata_structure() {
let hits = vec![
serde_json::json!({
"chunk_id": 101,
"score": 0.95,
"file_relpath": "src/auth.rs",
"file_path": "src/auth.rs",
"symbol_name": "authenticate",
"kind": "func",
"start_line": 15,
"end_line": 45,
}),
serde_json::json!({
"chunk_id": 202,
"score": 0.82,
"file_relpath": "src/session.rs",
"file_path": "src/session.rs",
"symbol_name": "create_session",
"kind": "func",
"start_line": 5,
"end_line": 20,
}),
];
let output =
format_hits_json_vector(&hits, 2, "auth logic", "vector", 10, Some(0.75)).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed["hits"].is_array());
assert_eq!(parsed["total"], 2);
assert_eq!(parsed["query"], "auth logic");
assert_eq!(parsed["mode"], "vector");
assert_eq!(parsed["k"], 10);
assert_eq!(parsed["threshold"], 0.75);
let hits_arr = parsed["hits"].as_array().unwrap();
assert_eq!(hits_arr.len(), 2);
assert_eq!(hits_arr[0]["file_path"], "src/auth.rs");
assert_eq!(hits_arr[0]["score"], 0.95);
assert_eq!(hits_arr[1]["file_path"], "src/session.rs");
assert_eq!(hits_arr[0]["file_relpath"], hits_arr[0]["file_path"]);
assert_eq!(hits_arr[1]["file_relpath"], hits_arr[1]["file_path"]);
}
#[test]
fn test_json_search_empty_hits_array() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "empty".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_json_search(&hits, &meta).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed["hits"].is_array());
assert_eq!(parsed["hits"].as_array().unwrap().len(), 0);
}
#[test]
fn test_json_vector_search_empty_hits_array() {
let hits: Vec<serde_json::Value> = vec![];
let output =
format_hits_json_vector(&hits, 0, "empty query", "vector", 5, Some(0.5)).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert!(parsed["hits"].is_array());
assert_eq!(parsed["hits"].as_array().unwrap().len(), 0);
assert_eq!(parsed["total"], 0);
assert_eq!(parsed["query"], "empty query");
assert_eq!(parsed["mode"], "vector");
assert_eq!(parsed["k"], 5);
assert_eq!(parsed["threshold"], 0.5);
}
use crate::context::types::{ContextBundle, ContextItem, LineRange};
fn make_context_item(
relpath: &str,
start: i32,
end: i32,
role: &str,
reason: &str,
content: &str,
tokens: usize,
) -> ContextItem {
ContextItem {
relpath: relpath.to_string(),
range: LineRange::new(start, end),
role: role.to_string(),
reason: reason.to_string(),
content: content.to_string(),
tokens,
}
}
#[test]
fn test_context_agent_empty_bundle() {
let bundle = ContextBundle::new();
let output = format_context_agent(&bundle, 42, 6000);
assert_eq!(
output,
"CONTEXT chunk_id=42 | tokens=0/6000 | items=0 | truncated=no"
);
}
#[test]
fn test_context_agent_single_primary() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/auth.rs",
10,
30,
"primary",
"Target chunk",
"fn authenticate(user: &str) -> bool {\n // Check credentials\n true\n}",
150,
));
let output = format_context_agent(&bundle, 12345, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
assert_eq!(
lines[0],
"CONTEXT chunk_id=12345 | tokens=150/6000 | items=1 | truncated=no"
);
assert_eq!(
lines[1],
"primary | src/auth.rs:10-30 | 150 | Target chunk | fn authenticate(user: &str) -> bool { // Check credentials true"
);
}
#[test]
fn test_context_agent_non_primary_no_preview() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/caller.rs",
5,
15,
"caller",
"Calls authenticate()",
"fn login() {\n authenticate(\"admin\");\n}",
80,
));
let output = format_context_agent(&bundle, 99, 4000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
assert_eq!(
lines[0],
"CONTEXT chunk_id=99 | tokens=80/4000 | items=1 | truncated=no"
);
assert_eq!(
lines[1],
"caller | src/caller.rs:5-15 | 80 | Calls authenticate()"
);
}
#[test]
fn test_context_agent_multi_item() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/auth.rs",
10,
30,
"primary",
"Target chunk",
"fn authenticate() {}",
150,
));
bundle.add_item(make_context_item(
"src/caller.rs",
5,
15,
"caller",
"Calls authenticate()",
"fn login() { authenticate(); }",
80,
));
bundle.add_item(make_context_item(
"src/callee.rs",
20,
40,
"callee",
"Called by authenticate()",
"fn verify_token() {}",
90,
));
bundle.add_item(make_context_item(
"tests/auth_test.rs",
1,
25,
"test",
"Tests authenticate()",
"#[test]\nfn test_auth() { assert!(authenticate()); }",
100,
));
let output = format_context_agent(&bundle, 555, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 5); assert_eq!(
lines[0],
"CONTEXT chunk_id=555 | tokens=420/6000 | items=4 | truncated=no"
);
assert!(lines[1].starts_with("primary | src/auth.rs:10-30 | 150 | Target chunk | "));
assert_eq!(
lines[2],
"caller | src/caller.rs:5-15 | 80 | Calls authenticate()"
);
assert_eq!(
lines[3],
"callee | src/callee.rs:20-40 | 90 | Called by authenticate()"
);
assert_eq!(
lines[4],
"test | tests/auth_test.rs:1-25 | 100 | Tests authenticate()"
);
}
#[test]
fn test_context_agent_truncated_bundle() {
let mut bundle = ContextBundle::new();
bundle.truncated = true;
bundle.add_item(make_context_item(
"src/big.rs",
1,
500,
"primary",
"Large chunk",
"fn big() {}",
5500,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
assert!(lines[0].contains("truncated=yes"));
assert_eq!(
lines[0],
"CONTEXT chunk_id=1 | tokens=5500/6000 | items=1 | truncated=yes"
);
}
#[test]
fn test_preview_length_cap() {
let long_line = "x".repeat(250);
let content = format!("{}\nsecond line\nthird line", long_line);
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/long.rs",
1,
10,
"primary",
"Long content",
&content,
200,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let primary_line = lines[1];
let segments: Vec<&str> = primary_line.splitn(5, " | ").collect();
assert_eq!(segments.len(), 5);
let preview = segments[4];
assert!(
preview.chars().count() <= 200,
"Preview should be capped at 200 chars, got {}",
preview.chars().count()
);
}
#[test]
fn test_preview_first_three_lines() {
let content = "line1\nline2\nline3\nline4\nline5";
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/multi.rs",
1,
5,
"primary",
"Multi-line",
content,
50,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let primary_line = lines[1];
let segments: Vec<&str> = primary_line.splitn(5, " | ").collect();
let preview = segments[4];
assert!(preview.contains("line1"));
assert!(preview.contains("line2"));
assert!(preview.contains("line3"));
assert!(!preview.contains("line4"));
assert!(!preview.contains("line5"));
assert_eq!(preview, "line1 line2 line3");
}
#[test]
fn test_context_agent_empty_content() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/empty.rs",
1,
1,
"primary",
"Empty content",
"",
0,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let primary_line = lines[1];
assert!(primary_line.ends_with("| Empty content | "));
}
#[test]
fn test_context_agent_multiple_primaries() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/a.rs",
1,
10,
"primary",
"First primary",
"fn first() {}",
100,
));
bundle.add_item(make_context_item(
"src/b.rs",
20,
30,
"primary",
"Second primary",
"fn second() {}",
120,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 3);
let segments_a: Vec<&str> = lines[1].splitn(5, " | ").collect();
let segments_b: Vec<&str> = lines[2].splitn(5, " | ").collect();
assert_eq!(segments_a.len(), 5, "First primary should have preview");
assert_eq!(segments_b.len(), 5, "Second primary should have preview");
assert!(segments_a[4].contains("fn first()"));
assert!(segments_b[4].contains("fn second()"));
}
#[test]
fn test_context_agent_unicode_content() {
let unicode_content = "\u{4e16}\u{754c}".repeat(150); let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/unicode.rs",
1,
10,
"primary",
"Unicode content",
&unicode_content,
500,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let primary_line = lines[1];
let segments: Vec<&str> = primary_line.splitn(5, " | ").collect();
let preview = segments[4];
assert_eq!(
preview.chars().count(),
200,
"Unicode preview should be capped at exactly 200 chars"
);
assert!(std::str::from_utf8(preview.as_bytes()).is_ok());
}
#[test]
fn test_context_agent_zero_tokens() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/zero.rs",
1,
1,
"caller",
"Zero tokens",
"",
0,
));
let output = format_context_agent(&bundle, 1, 6000);
assert!(output.contains("tokens=0/6000"));
assert!(output.contains("| 0 |"));
}
#[test]
fn test_context_agent_single_line_content() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/one.rs",
1,
1,
"primary",
"One liner",
"fn one_liner() {}",
10,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let segments: Vec<&str> = lines[1].splitn(5, " | ").collect();
assert_eq!(segments[4], "fn one_liner() {}");
}
#[test]
fn test_context_agent_all_roles() {
let mut bundle = ContextBundle::new();
let roles = [
"primary",
"caller",
"callee",
"test",
"doc",
"config",
"hook",
"jsx_parent",
"jsx_child",
];
for (i, role) in roles.iter().enumerate() {
bundle.add_item(make_context_item(
&format!("src/{}.rs", role),
(i as i32) + 1,
(i as i32) + 10,
role,
&format!("Role: {}", role),
&format!("fn {}() {{}}", role),
50,
));
}
let output = format_context_agent(&bundle, 42, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 10);
assert!(lines[0].contains("items=9"));
for role in &roles {
assert!(
output.contains(&format!("{} | src/{}.rs:", role, role)),
"Missing role: {}",
role
);
}
let primary_segments: Vec<&str> = lines[1].splitn(5, " | ").collect();
assert_eq!(primary_segments.len(), 5, "Primary should have preview");
for line in &lines[2..] {
let segments: Vec<&str> = line.splitn(5, " | ").collect();
assert_eq!(
segments.len(),
4,
"Non-primary should have no preview: {}",
line
);
}
}
#[test]
fn test_context_agent_header_format() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/main.rs",
1,
50,
"primary",
"Target",
"fn main() {}",
200,
));
bundle.add_item(make_context_item(
"src/lib.rs",
1,
10,
"caller",
"Calls main",
"use main;",
50,
));
let output = format_context_agent(&bundle, 99999, 8000);
let header = output.lines().next().unwrap();
assert_eq!(
header,
"CONTEXT chunk_id=99999 | tokens=250/8000 | items=2 | truncated=no"
);
let segments: Vec<&str> = header.split(" | ").collect();
assert_eq!(segments.len(), 4);
assert!(segments[0].starts_with("CONTEXT chunk_id="));
assert!(segments[1].starts_with("tokens="));
assert!(segments[2].starts_with("items="));
assert!(segments[3].starts_with("truncated="));
}
#[test]
fn test_context_agent_large_budget() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/big.rs",
1,
10000,
"primary",
"Huge file",
"fn big() {}",
999999,
));
let output = format_context_agent(&bundle, 1000000, 1000000);
let header = output.lines().next().unwrap();
assert_eq!(
header,
"CONTEXT chunk_id=1000000 | tokens=999999/1000000 | items=1 | truncated=no"
);
}
#[test]
fn test_context_agent_unicode_path() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/\u{65e5}\u{672c}\u{8a9e}.rs",
1,
10,
"primary",
"Japanese path",
"fn greet() {}",
30,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
assert!(lines[1].contains("src/\u{65e5}\u{672c}\u{8a9e}.rs:1-10"));
}
#[test]
fn test_context_agent_empty_reason() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/noreason.rs",
1,
5,
"caller",
"",
"fn call() {}",
20,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines[1], "caller | src/noreason.rs:1-5 | 20 | ");
}
#[test]
fn test_preview_newline_sanitization() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/newlines.rs",
1,
5,
"primary",
"Newline test",
"line_unix\nline_windows\r\nline_mac\rline_end",
40,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let segments: Vec<&str> = lines[1].splitn(5, " | ").collect();
let preview = segments[4];
assert!(!preview.contains('\n'));
assert!(!preview.contains('\r'));
assert_eq!(preview, "line_unix line_windows line_mac line_end");
}
#[test]
fn test_preview_exact_200_char_boundary() {
let exact_200 = "a".repeat(200);
let content = format!("{}\nextra line", exact_200);
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/boundary.rs",
1,
5,
"primary",
"Boundary test",
&content,
100,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
let segments: Vec<&str> = lines[1].splitn(5, " | ").collect();
let preview = segments[4];
assert_eq!(
preview.chars().count(),
200,
"Preview should be exactly 200 chars at the boundary, got {}",
preview.chars().count()
);
}
#[test]
fn test_preview_only_for_primary() {
let mut bundle = ContextBundle::new();
let non_primary_roles = [
"caller",
"callee",
"test",
"doc",
"config",
"hook",
"jsx_parent",
"jsx_child",
];
for role in &non_primary_roles {
bundle.add_item(make_context_item(
&format!("src/{}.rs", role),
1,
10,
role,
&format!("{} item", role),
"fn content_that_should_not_appear() {}",
50,
));
}
let output = format_context_agent(&bundle, 1, 6000);
assert!(
!output.contains("content_that_should_not_appear"),
"Non-primary items should not include content preview"
);
for line in output.lines().skip(1) {
let segments: Vec<&str> = line.splitn(5, " | ").collect();
assert_eq!(
segments.len(),
4,
"Non-primary item should have 4 segments: {}",
line
);
}
}
#[test]
fn test_context_agent_no_primary() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/caller.rs",
1,
10,
"caller",
"Caller item",
"fn call() {}",
50,
));
bundle.add_item(make_context_item(
"tests/test.rs",
1,
5,
"test",
"Test item",
"#[test] fn t() {}",
30,
));
let output = format_context_agent(&bundle, 1, 6000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 3); assert!(lines[0].contains("items=2"));
assert!(!output.contains("fn call()"));
assert!(!output.contains("#[test] fn t()"));
}
#[test]
fn test_token_efficiency_benchmark() {
let primary_content = (0..50)
.map(|i| format!(" let var_{} = compute_value({}, &config);", i, i))
.collect::<Vec<_>>()
.join("\n");
let primary_content = format!(
"pub fn authenticate(user: &str, password: &str, config: &AuthConfig) -> Result<Session, AuthError> {{\n{}\n Ok(Session::new(user))\n}}",
primary_content
);
let caller1_content = (0..30)
.map(|i| format!(" let step_{} = process_request({}, &ctx);", i, i))
.collect::<Vec<_>>()
.join("\n");
let caller1_content = format!(
"pub fn login_handler(req: &Request) -> Response {{\n{}\n Response::ok()\n}}",
caller1_content
);
let caller2_content = (0..30)
.map(|i| format!(" let check_{} = validate_input({}, &rules);", i, i))
.collect::<Vec<_>>()
.join("\n");
let caller2_content = format!(
"pub fn api_middleware(req: &Request, next: &Handler) -> Response {{\n{}\n next.call(req)\n}}",
caller2_content
);
let test_content = (0..25)
.map(|i| {
format!(
" assert_eq!(authenticate(\"user_{}\", \"pass_{}\", &config), expected_{});",
i, i, i
)
})
.collect::<Vec<_>>()
.join("\n");
let test_content = format!(
"#[test]\nfn test_authenticate() {{\n let config = AuthConfig::default();\n{}\n}}",
test_content
);
let doc_content = (0..20)
.map(|i| format!("/// Parameter {}: Description of parameter {} and its usage in the authentication flow.", i, i))
.collect::<Vec<_>>()
.join("\n");
let counter = crate::context::TokenCounter::new();
let primary_tokens = counter.count(&primary_content).unwrap();
let caller1_tokens = counter.count(&caller1_content).unwrap();
let caller2_tokens = counter.count(&caller2_content).unwrap();
let test_tokens = counter.count(&test_content).unwrap();
let doc_tokens = counter.count(&doc_content).unwrap();
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/auth.rs",
10,
60,
"primary",
"Target function: authenticate()",
&primary_content,
primary_tokens,
));
bundle.add_item(make_context_item(
"src/handlers/login.rs",
25,
55,
"caller",
"Calls authenticate() in login flow",
&caller1_content,
caller1_tokens,
));
bundle.add_item(make_context_item(
"src/middleware/api.rs",
100,
130,
"caller",
"Calls authenticate() in API middleware",
&caller2_content,
caller2_tokens,
));
bundle.add_item(make_context_item(
"tests/auth_test.rs",
1,
25,
"test",
"Unit tests for authenticate()",
&test_content,
test_tokens,
));
bundle.add_item(make_context_item(
"docs/auth.rs",
1,
20,
"doc",
"Documentation for authentication module",
&doc_content,
doc_tokens,
));
let budget = 6000;
let chunk_id = 12345;
let agent_output = format_context_agent(&bundle, chunk_id, budget);
let json_output = serde_json::to_string_pretty(&bundle).unwrap();
let agent_tokens = counter.count(&agent_output).unwrap();
let json_tokens = counter.count(&json_output).unwrap();
let ratio = agent_tokens as f64 / json_tokens as f64;
assert!(
agent_tokens <= (json_tokens as f64 * 0.60) as usize,
"Agent format ({} tokens) should be <= 60% of JSON format ({} tokens). Ratio: {:.2}%",
agent_tokens,
json_tokens,
ratio * 100.0
);
}
#[test]
fn test_vector_search_json_has_both_file_fields() {
let hit_json = serde_json::json!({
"chunk_id": 123,
"score": 0.95,
"start_line": 10,
"end_line": 20,
"symbol_name": "test_function",
"kind": "function",
"file_relpath": "src/test.rs",
"file_path": "src/test.rs",
});
assert!(hit_json["file_relpath"].is_string());
assert!(hit_json["file_path"].is_string());
assert_eq!(hit_json["file_relpath"], hit_json["file_path"]);
}
#[test]
fn test_vector_search_json_file_relpath_matches_source() {
let test_path = "src/specific/path.rs";
let hit_json = serde_json::json!({
"chunk_id": 456,
"score": 0.88,
"start_line": 5,
"end_line": 15,
"symbol_name": "another_function",
"kind": "function",
"file_relpath": test_path,
"file_path": test_path,
});
assert_eq!(hit_json["file_relpath"].as_str().unwrap(), test_path);
assert_eq!(hit_json["file_path"].as_str().unwrap(), test_path);
}
#[test]
fn test_vector_search_json_file_relpath_not_null() {
let hit_json = serde_json::json!({
"chunk_id": 789,
"score": 0.75,
"start_line": 1,
"end_line": 10,
"symbol_name": "empty_path_test",
"kind": "function",
"file_relpath": "",
"file_path": "",
});
assert!(hit_json["file_relpath"].is_string());
assert!(hit_json["file_path"].is_string());
assert_eq!(hit_json["file_relpath"], "");
assert_eq!(hit_json["file_path"], "");
}
#[test]
fn test_agent_header_with_results() {
let hits = vec![make_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point"),
)];
let meta = SearchMetadata {
query: "authentication".to_string(),
mode: "fts".to_string(),
hits: 1,
total_estimate: 25,
};
let output = format_hits_agent(&hits, &meta);
let header = output.lines().next().unwrap();
assert_eq!(
header,
"SEARCH query=\"authentication\" | hits=1 | total_estimate=25 | mode=fts"
);
assert_eq!(agent_hit_lines(&output).len(), 1);
}
#[test]
fn test_agent_header_without_results() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "nonexistent".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_agent(&hits, &meta);
let header = output.lines().next().unwrap();
assert_eq!(
header,
"SEARCH query=\"nonexistent\" | hits=0 | total_estimate=0 | mode=fts"
);
assert_eq!(agent_hit_lines(&output).len(), 0);
}
#[test]
fn test_agent_header_query_with_double_quotes() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "user \"name\" field".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_agent(&hits, &meta);
let header = output.lines().next().unwrap();
assert_eq!(
header,
"SEARCH query=\"user \\\"name\\\" field\" | hits=0 | total_estimate=0 | mode=fts"
);
}
#[test]
fn test_agent_header_query_with_special_characters() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "key=value | pipe | another=test spaces".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_agent(&hits, &meta);
let header = output.lines().next().unwrap();
assert_eq!(
header,
"SEARCH query=\"key=value | pipe | another=test spaces\" | hits=0 | total_estimate=0 | mode=fts"
);
}
#[test]
fn test_agent_header_mode_fts_vs_vector() {
let hits: Vec<SearchHit> = vec![];
let meta_fts = SearchMetadata {
query: "test".to_string(),
mode: "fts".to_string(),
hits: 0,
total_estimate: 0,
};
let output_fts = format_hits_agent(&hits, &meta_fts);
assert!(output_fts.contains("| mode=fts"));
let meta_vector = SearchMetadata {
query: "test".to_string(),
mode: "vector".to_string(),
hits: 0,
total_estimate: 0,
};
let output_vector = format_hits_agent(&hits, &meta_vector);
assert!(output_vector.contains("| mode=vector"));
}
#[test]
fn test_json_envelope_includes_all_metadata_fields() {
let hits = vec![make_hit(
"src/app.rs",
42,
"func",
Some("main"),
0.92,
Some("Entry point"),
)];
let meta = SearchMetadata {
query: "authentication".to_string(),
mode: "fts".to_string(),
hits: 1,
total_estimate: 50,
};
let output = format_hits_json_search(&hits, &meta).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert_eq!(parsed["total_matches"], 50);
assert_eq!(parsed["query"], "authentication");
assert_eq!(parsed["mode"], "fts");
assert!(parsed["hits"].is_array());
assert_eq!(parsed["hits"].as_array().unwrap().len(), 1);
}
#[test]
fn test_agent_header_long_query() {
let long_query = "x".repeat(1500);
let meta = SearchMetadata {
query: long_query.clone(),
mode: "fts".to_string(),
hits: 5,
total_estimate: 10,
};
let result = format_hits_agent(&[], &meta);
assert!(result.contains(&long_query));
assert!(result.contains("hits=5"));
assert!(result.contains("total_estimate=10"));
}
#[test]
fn test_json_empty_hits_with_metadata() {
let hits: Vec<SearchHit> = vec![];
let meta = SearchMetadata {
query: "nonexistent query".to_string(),
mode: "vector".to_string(),
hits: 0,
total_estimate: 0,
};
let output = format_hits_json_search(&hits, &meta).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
assert_eq!(parsed["total_matches"], 0);
assert_eq!(parsed["query"], "nonexistent query");
assert_eq!(parsed["mode"], "vector");
assert!(parsed["hits"].is_array());
assert_eq!(parsed["hits"].as_array().unwrap().len(), 0);
}
#[test]
fn test_agent_format_budget_zero() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/main.rs",
1,
10,
"primary",
"Target",
"fn main() {\n println!(\"test\");\n}",
100,
));
let output = format_context_agent(&bundle, 1001, 0);
assert!(output.starts_with("CONTEXT chunk_id=1001 | tokens=100/0"));
assert!(output.contains("primary | src/main.rs:1-10"));
assert!(output.lines().count() >= 2);
}
#[test]
fn test_agent_format_null_bytes_in_content() {
let content_with_nulls = "fn test() {\n let x\0 = 5;\n println!(\"test\0\");\n}";
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/test.rs",
1,
5,
"primary",
"Target",
content_with_nulls,
50,
));
let output = format_context_agent(&bundle, 1002, 1000);
assert!(!output.contains('\0'), "Output contains null bytes");
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
}
#[test]
fn test_agent_format_pipe_in_reason() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/core.rs",
10,
20,
"primary",
"Reason with | pipe character",
"fn process() {}",
100,
));
let output = format_context_agent(&bundle, 1003, 1000);
assert!(output.contains("src/core.rs:10-20"));
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
let segments: Vec<&str> = lines[1].splitn(6, " | ").collect();
assert_eq!(
segments.len(),
5,
"Primary line should have exactly 5 segments (role, location, tokens, reason, preview), got {}",
segments.len()
);
}
#[test]
fn test_format_agent_error_basic() {
let output = format_agent_error(
"database",
"Failed to connect to database",
"Check database connection settings",
);
assert_eq!(
output,
"ERROR | type=database | message=Failed to connect to database | suggestion=Check database connection settings"
);
}
#[test]
fn test_agent_format_pipe_in_relpath() {
let mut bundle = ContextBundle::new();
bundle.add_item(make_context_item(
"src/weird|name.rs",
5,
15,
"primary",
"Target",
"fn test() {}",
80,
));
let output = format_context_agent(&bundle, 1004, 1000);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 2);
let segments: Vec<&str> = lines[1].splitn(6, " | ").collect();
assert_eq!(
segments.len(),
5,
"Primary line should have exactly 5 segments even with pipe in relpath, got {}",
segments.len()
);
}
#[test]
fn test_format_agent_error_pipe_sanitization() {
let output = format_agent_error(
"unknown",
"Failed to parse config | invalid syntax",
"Fix the | characters in config",
);
assert_eq!(
output,
"ERROR | type=unknown | message=Failed to parse config - invalid syntax | suggestion=Fix the - characters in config"
);
}
#[test]
fn test_format_agent_error_multiple_consecutive_pipes() {
let output = format_agent_error(
"validation",
"Field validation failed||missing required field",
"Provide||check the field",
);
assert_eq!(
output,
"ERROR | type=validation | message=Field validation failed--missing required field | suggestion=Provide--check the field"
);
}
#[test]
fn test_format_agent_error_newline_sanitization() {
let output = format_agent_error(
"unknown",
"Error occurred\nLine two\r\nLine three\rLine four",
"Try restarting\nCheck logs\r\nVerify config\rContact support",
);
assert_eq!(
output,
"ERROR | type=unknown | message=Error occurred Line two Line three Line four | suggestion=Try restarting Check logs Verify config Contact support"
);
}
#[test]
fn test_format_agent_error_empty_message() {
let output = format_agent_error("unknown", "", "Check system logs");
assert_eq!(
output,
"ERROR | type=unknown | message= | suggestion=Check system logs"
);
}
#[test]
fn test_format_agent_error_empty_suggestion() {
let output = format_agent_error("timeout", "Request timed out", "");
assert_eq!(
output,
"ERROR | type=timeout | message=Request timed out | suggestion="
);
}
#[test]
fn test_format_agent_error_unicode() {
let output = format_agent_error(
"unknown",
"Failed to decode UTF-8: \u{00e9}\u{00f1}\u{00fc}",
"Use UTF-8 encoding: \u{3053}\u{3093}\u{306b}\u{3061}\u{306f}",
);
assert_eq!(
output,
"ERROR | type=unknown | message=Failed to decode UTF-8: \u{00e9}\u{00f1}\u{00fc} | suggestion=Use UTF-8 encoding: \u{3053}\u{3093}\u{306b}\u{3061}\u{306f}"
);
}
#[test]
fn test_format_agent_error_long_message() {
let long_message = "Error: ".to_string() + &"x".repeat(1200);
assert!(long_message.len() > 1000);
let output = format_agent_error("unknown", &long_message, "Reduce input size");
assert!(output.contains(&long_message));
assert!(output.len() >= long_message.len());
}
#[test]
fn test_format_agent_error_format_regex() {
let output = format_agent_error("unknown", "Test message", "Test suggestion");
let re = regex::Regex::new(r"^ERROR \| type=.+ \| message=.+ \| suggestion=.+$").unwrap();
assert!(
re.is_match(&output),
"Output does not match expected pattern: {}",
output
);
let output_empty = format_agent_error("unknown", "", "");
let re_empty =
regex::Regex::new(r"^ERROR \| type=.+ \| message=.* \| suggestion=.*$").unwrap();
assert!(
re_empty.is_match(&output_empty),
"Output with empty fields does not match relaxed pattern: {}",
output_empty
);
}
}