1use crate::cli::CliOutput;
8use crate::{config, db};
9use anyhow::Result;
10use std::path::Path;
11
12pub fn run_gc(
14 db_path: &Path,
15 json_out: bool,
16 app_config: &config::AppConfig,
17 out: &mut CliOutput<'_>,
18) -> Result<()> {
19 let conn = db::open(db_path)?;
20 let count = db::gc(&conn, app_config.effective_archive_on_gc())?;
21 if json_out {
22 writeln!(
23 out.stdout,
24 "{}",
25 serde_json::json!({(crate::models::field_names::EXPIRED_DELETED): count})
26 )?;
27 } else {
28 writeln!(out.stdout, "expired memories deleted: {count}")?;
29 }
30 Ok(())
31}
32
33pub fn run_stats(db_path: &Path, json_out: bool, out: &mut CliOutput<'_>) -> Result<()> {
35 let conn = db::open(db_path)?;
36 let stats = db::stats(&conn, db_path)?;
37 if json_out {
38 writeln!(out.stdout, "{}", serde_json::to_string(&stats)?)?;
39 return Ok(());
40 }
41 writeln!(out.stdout, "total memories: {}", stats.total)?;
42 writeln!(out.stdout, "expiring within 1h: {}", stats.expiring_soon)?;
43 writeln!(out.stdout, "links: {}", stats.links_count)?;
44 writeln!(out.stdout, "database size: {} bytes", stats.db_size_bytes)?;
45 writeln!(out.stdout, "\nby tier:")?;
46 for t in &stats.by_tier {
47 writeln!(out.stdout, " {}: {}", t.tier, t.count)?;
48 }
49 writeln!(out.stdout, "\nby namespace:")?;
50 for ns in &stats.by_namespace {
51 writeln!(out.stdout, " {}: {}", ns.namespace, ns.count)?;
52 }
53 Ok(())
54}
55
56pub fn run_namespaces(db_path: &Path, json_out: bool, out: &mut CliOutput<'_>) -> Result<()> {
58 let conn = db::open(db_path)?;
59 let ns = db::list_namespaces(&conn)?;
60 if json_out {
61 writeln!(
62 out.stdout,
63 "{}",
64 serde_json::to_string(
65 &serde_json::json!({(crate::models::field_names::NAMESPACES): ns})
66 )?
67 )?;
68 return Ok(());
69 }
70 if ns.is_empty() {
71 writeln!(out.stderr, "no namespaces")?;
72 } else {
73 for n in &ns {
74 writeln!(out.stdout, " {}: {} memories", n.namespace, n.count)?;
75 }
76 }
77 Ok(())
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::cli::test_utils::{TestEnv, seed_memory};
84
85 #[test]
86 fn test_gc_empty_db() {
87 let mut env = TestEnv::fresh();
88 let db = env.db_path.clone();
89 let cfg = config::AppConfig::default();
90 {
91 let mut out = env.output();
92 run_gc(&db, false, &cfg, &mut out).unwrap();
93 }
94 assert!(env.stdout_str().contains("expired memories deleted: 0"));
95 }
96
97 #[test]
98 fn test_gc_json_output() {
99 let mut env = TestEnv::fresh();
100 let db = env.db_path.clone();
101 let cfg = config::AppConfig::default();
102 {
103 let mut out = env.output();
104 run_gc(&db, true, &cfg, &mut out).unwrap();
105 }
106 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
107 assert_eq!(v["expired_deleted"].as_u64().unwrap(), 0);
108 }
109
110 #[test]
111 fn test_gc_with_data_present() {
112 let mut env = TestEnv::fresh();
114 let db = env.db_path.clone();
115 seed_memory(&db, "test-ns", "live", "still kicking");
116 let cfg = config::AppConfig::default();
117 {
118 let mut out = env.output();
119 run_gc(&db, false, &cfg, &mut out).unwrap();
120 }
121 assert!(env.stdout_str().contains("expired memories deleted:"));
122 }
123
124 #[test]
125 fn test_stats_on_empty_db() {
126 let mut env = TestEnv::fresh();
127 let db = env.db_path.clone();
128 {
129 let mut out = env.output();
130 run_stats(&db, false, &mut out).unwrap();
131 }
132 let s = env.stdout_str();
133 assert!(s.contains("total memories: 0"));
134 assert!(s.contains("links: 0"));
135 }
136
137 #[test]
138 fn test_stats_with_data() {
139 let mut env = TestEnv::fresh();
140 let db = env.db_path.clone();
141 seed_memory(&db, "ns-a", "t1", "c1");
142 seed_memory(&db, "ns-b", "t2", "c2");
143 {
144 let mut out = env.output();
145 run_stats(&db, false, &mut out).unwrap();
146 }
147 let s = env.stdout_str();
148 assert!(s.contains("total memories: 2"));
149 assert!(s.contains("by tier:"));
150 assert!(s.contains("by namespace:"));
151 }
152
153 #[test]
154 fn test_stats_json_output() {
155 let mut env = TestEnv::fresh();
156 let db = env.db_path.clone();
157 seed_memory(&db, "ns", "t", "c");
158 {
159 let mut out = env.output();
160 run_stats(&db, true, &mut out).unwrap();
161 }
162 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
163 assert_eq!(v["total"].as_u64().unwrap(), 1);
164 }
165
166 #[test]
167 fn test_stats_by_tier_breakdown() {
168 let mut env = TestEnv::fresh();
169 let db = env.db_path.clone();
170 seed_memory(&db, "ns", "t", "c");
171 {
172 let mut out = env.output();
173 run_stats(&db, true, &mut out).unwrap();
174 }
175 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
176 assert!(v["by_tier"].is_array());
177 assert!(v["by_namespace"].is_array());
178 }
179
180 #[test]
181 fn test_namespaces_empty_writes_stderr() {
182 let mut env = TestEnv::fresh();
183 let db = env.db_path.clone();
184 {
185 let mut out = env.output();
186 run_namespaces(&db, false, &mut out).unwrap();
187 }
188 assert!(env.stderr_str().contains("no namespaces"));
189 assert_eq!(env.stdout_str(), "");
190 }
191
192 #[test]
193 fn test_namespaces_with_data() {
194 let mut env = TestEnv::fresh();
195 let db = env.db_path.clone();
196 seed_memory(&db, "alpha", "t", "c");
197 seed_memory(&db, "beta", "t2", "c2");
198 {
199 let mut out = env.output();
200 run_namespaces(&db, false, &mut out).unwrap();
201 }
202 let s = env.stdout_str();
203 assert!(s.contains("alpha"));
204 assert!(s.contains("beta"));
205 }
206
207 #[test]
208 fn test_namespaces_json_output() {
209 let mut env = TestEnv::fresh();
210 let db = env.db_path.clone();
211 seed_memory(&db, "alpha", "t", "c");
212 {
213 let mut out = env.output();
214 run_namespaces(&db, true, &mut out).unwrap();
215 }
216 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
217 let arr = v["namespaces"].as_array().unwrap();
218 assert!(!arr.is_empty());
219 }
220
221 #[test]
222 fn test_namespaces_json_empty_array() {
223 let mut env = TestEnv::fresh();
224 let db = env.db_path.clone();
225 {
226 let mut out = env.output();
227 run_namespaces(&db, true, &mut out).unwrap();
228 }
229 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
230 assert_eq!(v["namespaces"].as_array().unwrap().len(), 0);
231 }
232}