1mod compact;
4mod jsonl;
5
6pub use compact::CompactWriter;
7pub use jsonl::JsonlWriter;
8
9#[cfg(test)]
10mod tests {
11 use super::*;
12 use topo_core::{FileRole, Language, ScoredFile, SignalBreakdown};
13
14 fn sample_files() -> Vec<ScoredFile> {
15 vec![
16 ScoredFile {
17 path: "src/auth/middleware.rs".to_string(),
18 score: 0.95,
19 signals: SignalBreakdown {
20 bm25f: 0.8,
21 heuristic: 0.7,
22 ..Default::default()
23 },
24 tokens: 1200,
25 language: Language::Rust,
26 role: FileRole::Implementation,
27 },
28 ScoredFile {
29 path: "src/auth/handler.rs".to_string(),
30 score: 0.72,
31 signals: SignalBreakdown {
32 bm25f: 0.5,
33 heuristic: 0.6,
34 ..Default::default()
35 },
36 tokens: 800,
37 language: Language::Rust,
38 role: FileRole::Implementation,
39 },
40 ]
41 }
42
43 #[test]
44 fn jsonl_output_has_three_lines() {
45 let files = sample_files();
46 let output = JsonlWriter::new("auth middleware", "balanced")
47 .max_bytes(Some(100_000))
48 .min_score(0.01)
49 .render(&files, 358)
50 .unwrap();
51
52 let lines: Vec<&str> = output.trim().lines().collect();
53 assert_eq!(lines.len(), 4); }
55
56 #[test]
57 fn jsonl_header_contains_version() {
58 let files = sample_files();
59 let output = JsonlWriter::new("test query", "balanced")
60 .render(&files, 100)
61 .unwrap();
62
63 let first_line = output.lines().next().unwrap();
64 let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
65 assert_eq!(header["Version"], "0.3");
66 }
67
68 #[test]
69 fn jsonl_header_contains_query() {
70 let files = sample_files();
71 let output = JsonlWriter::new("auth middleware", "balanced")
72 .render(&files, 100)
73 .unwrap();
74
75 let first_line = output.lines().next().unwrap();
76 let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
77 assert_eq!(header["Query"], "auth middleware");
78 }
79
80 #[test]
81 fn jsonl_file_entries_have_required_fields() {
82 let files = sample_files();
83 let output = JsonlWriter::new("test", "balanced")
84 .render(&files, 100)
85 .unwrap();
86
87 let lines: Vec<&str> = output.trim().lines().collect();
88 let file_entry: serde_json::Value = serde_json::from_str(lines[1]).unwrap();
89
90 assert!(file_entry["Path"].is_string());
91 assert!(file_entry["Score"].is_number());
92 assert!(file_entry["Tokens"].is_number());
93 assert!(file_entry["Language"].is_string());
94 assert!(file_entry["Role"].is_string());
95 }
96
97 #[test]
98 fn jsonl_footer_has_totals() {
99 let files = sample_files();
100 let output = JsonlWriter::new("test", "balanced")
101 .render(&files, 358)
102 .unwrap();
103
104 let last_line = output.trim().lines().last().unwrap();
105 let footer: serde_json::Value = serde_json::from_str(last_line).unwrap();
106
107 assert_eq!(footer["TotalFiles"], 2);
108 assert_eq!(footer["TotalTokens"], 2000); assert_eq!(footer["ScannedFiles"], 358);
110 }
111
112 #[test]
113 fn jsonl_empty_files_produces_header_and_footer() {
114 let output = JsonlWriter::new("test", "balanced").render(&[], 0).unwrap();
115
116 let lines: Vec<&str> = output.trim().lines().collect();
117 assert_eq!(lines.len(), 2); }
119
120 #[test]
121 fn jsonl_each_line_is_valid_json() {
122 let files = sample_files();
123 let output = JsonlWriter::new("test", "balanced")
124 .render(&files, 100)
125 .unwrap();
126
127 for line in output.trim().lines() {
128 let parsed: Result<serde_json::Value, _> = serde_json::from_str(line);
129 assert!(parsed.is_ok(), "Invalid JSON line: {line}");
130 }
131 }
132
133 #[test]
134 fn jsonl_max_bytes_in_header() {
135 let output = JsonlWriter::new("test", "balanced")
136 .max_bytes(Some(50_000))
137 .render(&[], 0)
138 .unwrap();
139
140 let first_line = output.lines().next().unwrap();
141 let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
142 assert_eq!(header["Budget"]["MaxBytes"], 50_000);
143 }
144
145 #[test]
146 fn jsonl_preset_in_header() {
147 let output = JsonlWriter::new("test", "deep").render(&[], 0).unwrap();
148
149 let first_line = output.lines().next().unwrap();
150 let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
151 assert_eq!(header["Preset"], "deep");
152 }
153}