⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd
crates/modules/semantic_dht/minga-cli/tests/watcher.rs
Historia
Quién tocó qué
crudo
chore: monorepo inicial con arje + minga + yahweh absorbidos
1//! Tests del file watcher: el "puente humano" que convierte Minga en
2//! un VCS de fondo — el usuario edita archivos y Minga los versiona
3//! sin acción explícita.
4
5use std::fs;
6use std::time::Duration;
7
8use minga_cli::{cmd_init, cmd_status, cmd_watch};
9use tempfile::TempDir;
10
11/// Espera hasta que el `cmd_status` reporte `expected` claves en MST,
12/// o hasta `timeout`. Devuelve `true` si se alcanzó la cuenta.
13async fn wait_until_mst_size(
14 repo: &std::path::Path,
15 pass: &str,
16 expected: usize,
17 timeout: Duration,
18) -> bool {
19 let deadline = std::time::Instant::now() + timeout;
20 loop {
21 if let Ok(s) = cmd_status(repo, pass) {
22 if s.mst_len >= expected {
23 return true;
24 }
25 }
26 if std::time::Instant::now() >= deadline {
27 return false;
28 }
29 tokio::time::sleep(Duration::from_millis(80)).await;
30 }
31}
32
33#[tokio::test]
34async fn watcher_initial_scan_picks_up_existing_files() {
35 let dir = TempDir::new().unwrap();
36 let repo = dir.path().join("repo");
37 let watch = dir.path().join("src");
38 fs::create_dir(&watch).unwrap();
39 cmd_init(&repo, "p").unwrap();
40
41 // Escribimos archivos ANTES de arrancar el watcher.
42 fs::write(watch.join("a.rs"), "fn a() -> i32 { 1 }").unwrap();
43 fs::write(watch.join("b.rs"), "fn b() -> i32 { 2 }").unwrap();
44
45 // Arrancamos el watcher en una task. La pasada inicial debería
46 // ingerir ambos.
47 let repo_clone = repo.clone();
48 let handle = tokio::spawn(async move {
49 let _ = cmd_watch(&repo_clone, "p", &watch).await;
50 });
51
52 // Damos margen para la pasada inicial. cmd_watch tiene el repo
53 // abierto, pero cmd_status no puede mientras tanto (sled lock).
54 // Solución: cancelamos el watcher antes de medir.
55 tokio::time::sleep(Duration::from_millis(500)).await;
56 handle.abort();
57 let _ = handle.await;
58 tokio::time::sleep(Duration::from_millis(150)).await;
59
60 let s = cmd_status(&repo, "p").unwrap();
61 assert_eq!(s.mst_len, 2, "esperaba 2 funciones del initial scan");
62 assert_eq!(s.attestations_len, 2);
63}
64
65#[tokio::test]
66async fn watcher_ingests_new_file_after_creation() {
67 let dir = TempDir::new().unwrap();
68 let repo = dir.path().join("repo");
69 let watch = dir.path().join("src");
70 fs::create_dir(&watch).unwrap();
71 cmd_init(&repo, "p").unwrap();
72
73 // Watcher arranca con directorio vacío.
74 let repo_clone = repo.clone();
75 let watch_clone = watch.clone();
76 let handle = tokio::spawn(async move {
77 let _ = cmd_watch(&repo_clone, "p", &watch_clone).await;
78 });
79
80 // Margen para que el watcher se inicialice y registre con notify.
81 tokio::time::sleep(Duration::from_millis(300)).await;
82
83 // Creamos un archivo. notify debería emitir un evento y el
84 // watcher debería ingerirlo.
85 fs::write(watch.join("new.rs"), "fn new() -> i32 { 42 }").unwrap();
86
87 // Esperamos a que el evento se procese.
88 tokio::time::sleep(Duration::from_millis(800)).await;
89
90 // Detenemos el watcher para liberar el lock de sled antes de
91 // hacer cmd_status.
92 handle.abort();
93 let _ = handle.await;
94 tokio::time::sleep(Duration::from_millis(150)).await;
95
96 // Polling con timeout — algunos sistemas de archivos tienen
97 // latencia de eventos.
98 assert!(
99 wait_until_mst_size(&repo, "p", 1, Duration::from_secs(3)).await,
100 "el watcher no ingirió el archivo creado",
101 );
102}
103
104#[tokio::test]
105async fn watcher_ignores_non_rs_files() {
106 let dir = TempDir::new().unwrap();
107 let repo = dir.path().join("repo");
108 let watch = dir.path().join("src");
109 fs::create_dir(&watch).unwrap();
110 cmd_init(&repo, "p").unwrap();
111
112 // Pre-poblamos con un .rs y varios archivos no-Rust.
113 fs::write(watch.join("real.rs"), "fn real() -> i32 { 0 }").unwrap();
114 fs::write(watch.join("readme.md"), "# proyecto").unwrap();
115 fs::write(watch.join("data.json"), "{}").unwrap();
116
117 let repo_clone = repo.clone();
118 let handle = tokio::spawn(async move {
119 let _ = cmd_watch(&repo_clone, "p", &watch).await;
120 });
121
122 tokio::time::sleep(Duration::from_millis(500)).await;
123 handle.abort();
124 let _ = handle.await;
125 tokio::time::sleep(Duration::from_millis(150)).await;
126
127 let s = cmd_status(&repo, "p").unwrap();
128 assert_eq!(s.mst_len, 1, "solo el .rs debe haberse ingerido");
129}