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!({"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(&serde_json::json!({"namespaces": ns}))?
65 )?;
66 return Ok(());
67 }
68 if ns.is_empty() {
69 writeln!(out.stderr, "no namespaces")?;
70 } else {
71 for n in &ns {
72 writeln!(out.stdout, " {}: {} memories", n.namespace, n.count)?;
73 }
74 }
75 Ok(())
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::cli::test_utils::{TestEnv, seed_memory};
82
83 #[test]
84 fn test_gc_empty_db() {
85 let mut env = TestEnv::fresh();
86 let db = env.db_path.clone();
87 let cfg = config::AppConfig::default();
88 {
89 let mut out = env.output();
90 run_gc(&db, false, &cfg, &mut out).unwrap();
91 }
92 assert!(env.stdout_str().contains("expired memories deleted: 0"));
93 }
94
95 #[test]
96 fn test_gc_json_output() {
97 let mut env = TestEnv::fresh();
98 let db = env.db_path.clone();
99 let cfg = config::AppConfig::default();
100 {
101 let mut out = env.output();
102 run_gc(&db, true, &cfg, &mut out).unwrap();
103 }
104 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
105 assert_eq!(v["expired_deleted"].as_u64().unwrap(), 0);
106 }
107
108 #[test]
109 fn test_gc_with_data_present() {
110 let mut env = TestEnv::fresh();
112 let db = env.db_path.clone();
113 seed_memory(&db, "test-ns", "live", "still kicking");
114 let cfg = config::AppConfig::default();
115 {
116 let mut out = env.output();
117 run_gc(&db, false, &cfg, &mut out).unwrap();
118 }
119 assert!(env.stdout_str().contains("expired memories deleted:"));
120 }
121
122 #[test]
123 fn test_stats_on_empty_db() {
124 let mut env = TestEnv::fresh();
125 let db = env.db_path.clone();
126 {
127 let mut out = env.output();
128 run_stats(&db, false, &mut out).unwrap();
129 }
130 let s = env.stdout_str();
131 assert!(s.contains("total memories: 0"));
132 assert!(s.contains("links: 0"));
133 }
134
135 #[test]
136 fn test_stats_with_data() {
137 let mut env = TestEnv::fresh();
138 let db = env.db_path.clone();
139 seed_memory(&db, "ns-a", "t1", "c1");
140 seed_memory(&db, "ns-b", "t2", "c2");
141 {
142 let mut out = env.output();
143 run_stats(&db, false, &mut out).unwrap();
144 }
145 let s = env.stdout_str();
146 assert!(s.contains("total memories: 2"));
147 assert!(s.contains("by tier:"));
148 assert!(s.contains("by namespace:"));
149 }
150
151 #[test]
152 fn test_stats_json_output() {
153 let mut env = TestEnv::fresh();
154 let db = env.db_path.clone();
155 seed_memory(&db, "ns", "t", "c");
156 {
157 let mut out = env.output();
158 run_stats(&db, true, &mut out).unwrap();
159 }
160 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
161 assert_eq!(v["total"].as_u64().unwrap(), 1);
162 }
163
164 #[test]
165 fn test_stats_by_tier_breakdown() {
166 let mut env = TestEnv::fresh();
167 let db = env.db_path.clone();
168 seed_memory(&db, "ns", "t", "c");
169 {
170 let mut out = env.output();
171 run_stats(&db, true, &mut out).unwrap();
172 }
173 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
174 assert!(v["by_tier"].is_array());
175 assert!(v["by_namespace"].is_array());
176 }
177
178 #[test]
179 fn test_namespaces_empty_writes_stderr() {
180 let mut env = TestEnv::fresh();
181 let db = env.db_path.clone();
182 {
183 let mut out = env.output();
184 run_namespaces(&db, false, &mut out).unwrap();
185 }
186 assert!(env.stderr_str().contains("no namespaces"));
187 assert_eq!(env.stdout_str(), "");
188 }
189
190 #[test]
191 fn test_namespaces_with_data() {
192 let mut env = TestEnv::fresh();
193 let db = env.db_path.clone();
194 seed_memory(&db, "alpha", "t", "c");
195 seed_memory(&db, "beta", "t2", "c2");
196 {
197 let mut out = env.output();
198 run_namespaces(&db, false, &mut out).unwrap();
199 }
200 let s = env.stdout_str();
201 assert!(s.contains("alpha"));
202 assert!(s.contains("beta"));
203 }
204
205 #[test]
206 fn test_namespaces_json_output() {
207 let mut env = TestEnv::fresh();
208 let db = env.db_path.clone();
209 seed_memory(&db, "alpha", "t", "c");
210 {
211 let mut out = env.output();
212 run_namespaces(&db, true, &mut out).unwrap();
213 }
214 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
215 let arr = v["namespaces"].as_array().unwrap();
216 assert!(!arr.is_empty());
217 }
218
219 #[test]
220 fn test_namespaces_json_empty_array() {
221 let mut env = TestEnv::fresh();
222 let db = env.db_path.clone();
223 {
224 let mut out = env.output();
225 run_namespaces(&db, true, &mut out).unwrap();
226 }
227 let v: serde_json::Value = serde_json::from_str(env.stdout_str().trim()).unwrap();
228 assert_eq!(v["namespaces"].as_array().unwrap().len(), 0);
229 }
230}