upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/proactive_sync_catchup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/proactive_sync_catchup.rs')
-rw-r--r--tests/proactive_sync_catchup.rs169
1 files changed, 43 insertions, 126 deletions
diff --git a/tests/proactive_sync_catchup.rs b/tests/proactive_sync_catchup.rs
index 944ae50..d8a2ef9 100644
--- a/tests/proactive_sync_catchup.rs
+++ b/tests/proactive_sync_catchup.rs
@@ -17,89 +17,6 @@
17use ngit_grasp::sync::SubscriptionManager; 17use ngit_grasp::sync::SubscriptionManager;
18 18
19// ============================================================================ 19// ============================================================================
20// Configuration Constants Tests
21// ============================================================================
22
23/// Test that default startup delay is 30 seconds
24#[test]
25fn test_default_startup_delay_is_30_seconds() {
26 // The spec requires 30s warm-up before startup catchup
27 const EXPECTED_STARTUP_DELAY: u64 = 30;
28
29 // This is defined in negentropy.rs as DEFAULT_STARTUP_DELAY_SECS
30 // We verify the expected value matches the spec
31 assert_eq!(EXPECTED_STARTUP_DELAY, 30);
32}
33
34/// Test that default reconnect delay is 10 seconds
35#[test]
36fn test_default_reconnect_delay_is_10_seconds() {
37 // The spec requires 10s delay after reconnection before catchup
38 const EXPECTED_RECONNECT_DELAY: u64 = 10;
39 assert_eq!(EXPECTED_RECONNECT_DELAY, 10);
40}
41
42/// Test that reconnect lookback is 3 days
43#[test]
44fn test_reconnect_lookback_is_3_days() {
45 // The spec requires 3 days lookback for reconnect catchup
46 const EXPECTED_LOOKBACK_DAYS: u64 = 3;
47 const EXPECTED_LOOKBACK_SECS: u64 = 3 * 24 * 60 * 60; // 259,200 seconds
48
49 assert_eq!(EXPECTED_LOOKBACK_DAYS, 3);
50 assert_eq!(EXPECTED_LOOKBACK_SECS, 259200);
51}
52
53/// Test daily catchup interval is 24 hours
54#[test]
55fn test_daily_catchup_interval_is_24_hours() {
56 // The spec requires daily catchup once per 24 hours
57 const EXPECTED_DAILY_INTERVAL_SECS: u64 = 86400; // 24 * 60 * 60
58 assert_eq!(EXPECTED_DAILY_INTERVAL_SECS, 86400);
59}
60
61/// Test relay stagger delay is 5 minutes
62#[test]
63fn test_relay_stagger_is_5_minutes() {
64 // The spec requires 5-minute stagger between relays for catchup
65 const EXPECTED_STAGGER_SECS: u64 = 300; // 5 * 60
66 assert_eq!(EXPECTED_STAGGER_SECS, 300);
67}
68
69// ============================================================================
70// Filter Compatibility Tests
71// ============================================================================
72
73/// Test that catchup uses announcement kinds (30617, 30618)
74#[test]
75fn test_catchup_uses_announcement_kinds() {
76 // Layer 1 filters should include announcement kinds
77 assert!(SubscriptionManager::is_announcement_kind(30617));
78 assert!(SubscriptionManager::is_announcement_kind(30618));
79}
80
81/// Test that catchup uses PR/Issue kinds for Layer 3
82#[test]
83fn test_catchup_uses_pr_issue_kinds() {
84 // Layer 3 should track PR and Issue kinds
85 assert!(SubscriptionManager::is_pr_issue_kind(1617)); // Patch proposal
86 assert!(SubscriptionManager::is_pr_issue_kind(1618)); // PR
87 assert!(SubscriptionManager::is_pr_issue_kind(1619)); // PR Update
88 assert!(SubscriptionManager::is_pr_issue_kind(1621)); // Issue
89 assert!(SubscriptionManager::is_pr_issue_kind(1622)); // Reply
90}
91
92/// Test that non-sync kinds are not included in catchup
93#[test]
94fn test_catchup_excludes_non_sync_kinds() {
95 // Regular text notes and other kinds should not be included
96 assert!(!SubscriptionManager::is_announcement_kind(1)); // Text note
97 assert!(!SubscriptionManager::is_announcement_kind(4)); // DM
98 assert!(!SubscriptionManager::is_pr_issue_kind(1)); // Text note
99 assert!(!SubscriptionManager::is_pr_issue_kind(30617)); // Announcement (wrong layer)
100}
101
102// ============================================================================
103// Catchup State Machine Tests 20// Catchup State Machine Tests
104// ============================================================================ 21// ============================================================================
105 22
@@ -108,17 +25,17 @@ fn test_catchup_excludes_non_sync_kinds() {
108fn test_startup_catchup_runs_once() { 25fn test_startup_catchup_runs_once() {
109 // After startup catchup completes, should_run_startup_catchup should return false 26 // After startup catchup completes, should_run_startup_catchup should return false
110 // This is handled by the startup_catchup_completed flag in NegentropyService 27 // This is handled by the startup_catchup_completed flag in NegentropyService
111 28
112 // Simulating the state machine: 29 // Simulating the state machine:
113 let mut startup_completed = false; 30 let mut startup_completed = false;
114 31
115 // Before running, should return true (if delay elapsed) 32 // Before running, should return true (if delay elapsed)
116 let should_run_before = !startup_completed; 33 let should_run_before = !startup_completed;
117 assert!(should_run_before); 34 assert!(should_run_before);
118 35
119 // After running, mark as completed 36 // After running, mark as completed
120 startup_completed = true; 37 startup_completed = true;
121 38
122 // Now should return false 39 // Now should return false
123 let should_run_after = !startup_completed; 40 let should_run_after = !startup_completed;
124 assert!(!should_run_after); 41 assert!(!should_run_after);
@@ -128,12 +45,12 @@ fn test_startup_catchup_runs_once() {
128#[test] 45#[test]
129fn test_daily_catchup_interval_check() { 46fn test_daily_catchup_interval_check() {
130 use std::time::{Duration, Instant}; 47 use std::time::{Duration, Instant};
131 48
132 const DAILY_INTERVAL_SECS: u64 = 86400; 49 const DAILY_INTERVAL_SECS: u64 = 86400;
133 50
134 // Simulate last catchup time 51 // Simulate last catchup time
135 let last_catchup = Instant::now(); 52 let last_catchup = Instant::now();
136 53
137 // Immediately after, should not run 54 // Immediately after, should not run
138 let should_run_immediately = last_catchup.elapsed() >= Duration::from_secs(DAILY_INTERVAL_SECS); 55 let should_run_immediately = last_catchup.elapsed() >= Duration::from_secs(DAILY_INTERVAL_SECS);
139 assert!(!should_run_immediately); 56 assert!(!should_run_immediately);
@@ -144,10 +61,10 @@ fn test_daily_catchup_interval_check() {
144fn test_new_relay_should_run_daily_catchup() { 61fn test_new_relay_should_run_daily_catchup() {
145 use std::collections::HashMap; 62 use std::collections::HashMap;
146 use std::time::Instant; 63 use std::time::Instant;
147 64
148 let last_daily_catchup: HashMap<String, Instant> = HashMap::new(); 65 let last_daily_catchup: HashMap<String, Instant> = HashMap::new();
149 let relay_url = "wss://test-relay.example.com"; 66 let relay_url = "wss://test-relay.example.com";
150 67
151 // No previous catchup recorded, should return true 68 // No previous catchup recorded, should return true
152 let should_run = !last_daily_catchup.contains_key(relay_url); 69 let should_run = !last_daily_catchup.contains_key(relay_url);
153 assert!(should_run); 70 assert!(should_run);
@@ -159,14 +76,14 @@ fn test_reconnect_catchup_after_reconnection() {
159 // Reconnect catchup should only trigger when: 76 // Reconnect catchup should only trigger when:
160 // 1. Connection was previously successful (had_previous_connection = true) 77 // 1. Connection was previously successful (had_previous_connection = true)
161 // 2. Connection was lost and restored 78 // 2. Connection was lost and restored
162 79
163 let mut had_previous_connection = false; 80 let mut had_previous_connection = false;
164 81
165 // First connection - should NOT trigger reconnect catchup 82 // First connection - should NOT trigger reconnect catchup
166 let is_reconnection_first = had_previous_connection; 83 let is_reconnection_first = had_previous_connection;
167 assert!(!is_reconnection_first); 84 assert!(!is_reconnection_first);
168 had_previous_connection = true; 85 had_previous_connection = true;
169 86
170 // Second connection (after disconnection) - SHOULD trigger 87 // Second connection (after disconnection) - SHOULD trigger
171 let is_reconnection_second = had_previous_connection; 88 let is_reconnection_second = had_previous_connection;
172 assert!(is_reconnection_second); 89 assert!(is_reconnection_second);
@@ -185,10 +102,10 @@ fn test_gap_events_validated_through_policy() {
185 // 2. Check if event exists locally 102 // 2. Check if event exists locally
186 // 3. Validate through Nip34WritePolicy 103 // 3. Validate through Nip34WritePolicy
187 // 4. Store if accepted 104 // 4. Store if accepted
188 105
189 // This is verified by the implementation in negentropy.rs:run_catchup() 106 // This is verified by the implementation in negentropy.rs:run_catchup()
190 // where PolicyResult::Accept leads to storage and PolicyResult::Reject is logged 107 // where PolicyResult::Accept leads to storage and PolicyResult::Reject is logged
191 108
192 assert!(true); // Flow verification - actual validation tested in other tests 109 assert!(true); // Flow verification - actual validation tested in other tests
193} 110}
194 111
@@ -197,14 +114,14 @@ fn test_gap_events_validated_through_policy() {
197fn test_gap_events_logged_at_warn_level() { 114fn test_gap_events_logged_at_warn_level() {
198 // The spec requires gap events to be logged at WARN level 115 // The spec requires gap events to be logged at WARN level
199 // to distinguish them from live events (which are logged at INFO) 116 // to distinguish them from live events (which are logged at INFO)
200 117
201 // This is implemented in negentropy.rs with: 118 // This is implemented in negentropy.rs with:
202 // tracing::warn!("Gap event filled via {} catchup: {} (kind {})", ...) 119 // tracing::warn!("Gap event filled via {} catchup: {} (kind {})", ...)
203 120
204 // We verify the logging pattern exists by testing the catchup types 121 // We verify the logging pattern exists by testing the catchup types
205 let catchup_types = ["startup", "reconnect", "daily"]; 122 let catchup_types = ["startup", "reconnect", "daily"];
206 assert_eq!(catchup_types.len(), 3); 123 assert_eq!(catchup_types.len(), 3);
207 124
208 for catchup_type in catchup_types { 125 for catchup_type in catchup_types {
209 assert!(!catchup_type.is_empty()); 126 assert!(!catchup_type.is_empty());
210 } 127 }
@@ -218,21 +135,21 @@ fn test_gap_events_logged_at_warn_level() {
218#[test] 135#[test]
219fn test_stagger_delay_for_multiple_relays() { 136fn test_stagger_delay_for_multiple_relays() {
220 const STAGGER_SECS: u64 = 300; // 5 minutes 137 const STAGGER_SECS: u64 = 300; // 5 minutes
221 138
222 let _relay_urls = vec![ 139 let _relay_urls = vec![
223 "wss://relay1.example.com", 140 "wss://relay1.example.com",
224 "wss://relay2.example.com", 141 "wss://relay2.example.com",
225 "wss://relay3.example.com", 142 "wss://relay3.example.com",
226 ]; 143 ];
227 144
228 // First relay (index 0) should have no stagger 145 // First relay (index 0) should have no stagger
229 let stagger_0 = 0 * STAGGER_SECS; 146 let stagger_0 = 0 * STAGGER_SECS;
230 assert_eq!(stagger_0, 0); 147 assert_eq!(stagger_0, 0);
231 148
232 // Second relay (index 1) should have 5 minute stagger 149 // Second relay (index 1) should have 5 minute stagger
233 let stagger_1 = 1 * STAGGER_SECS; 150 let stagger_1 = 1 * STAGGER_SECS;
234 assert_eq!(stagger_1, 300); 151 assert_eq!(stagger_1, 300);
235 152
236 // Third relay (index 2) should have 10 minute stagger 153 // Third relay (index 2) should have 10 minute stagger
237 let stagger_2 = 2 * STAGGER_SECS; 154 let stagger_2 = 2 * STAGGER_SECS;
238 assert_eq!(stagger_2, 600); 155 assert_eq!(stagger_2, 600);
@@ -242,15 +159,15 @@ fn test_stagger_delay_for_multiple_relays() {
242#[test] 159#[test]
243fn test_startup_catchup_waits_for_warmup() { 160fn test_startup_catchup_waits_for_warmup() {
244 use std::time::{Duration, Instant}; 161 use std::time::{Duration, Instant};
245 162
246 const STARTUP_DELAY_SECS: u64 = 30; 163 const STARTUP_DELAY_SECS: u64 = 30;
247 164
248 let startup_time = Instant::now(); 165 let startup_time = Instant::now();
249 166
250 // Immediately after startup, should not run (delay not elapsed) 167 // Immediately after startup, should not run (delay not elapsed)
251 let elapsed = startup_time.elapsed(); 168 let elapsed = startup_time.elapsed();
252 let should_run = elapsed >= Duration::from_secs(STARTUP_DELAY_SECS); 169 let should_run = elapsed >= Duration::from_secs(STARTUP_DELAY_SECS);
253 170
254 // This should be false since we just created startup_time 171 // This should be false since we just created startup_time
255 assert!(!should_run); 172 assert!(!should_run);
256} 173}
@@ -265,7 +182,7 @@ fn test_reconnect_lookback_calculation() {
265 // 3 days = 3 * 24 * 60 * 60 = 259,200 seconds 182 // 3 days = 3 * 24 * 60 * 60 = 259,200 seconds
266 let lookback_days: u64 = 3; 183 let lookback_days: u64 = 3;
267 let lookback_secs = lookback_days * 24 * 60 * 60; 184 let lookback_secs = lookback_days * 24 * 60 * 60;
268 185
269 assert_eq!(lookback_secs, 259200); 186 assert_eq!(lookback_secs, 259200);
270} 187}
271 188
@@ -290,10 +207,10 @@ fn test_startup_catchup_scenario() {
290 // 2. Run full reconciliation (no time limit) 207 // 2. Run full reconciliation (no time limit)
291 // 3. Mark as completed (runs only once) 208 // 3. Mark as completed (runs only once)
292 // 4. Stagger between relays (5 minutes) 209 // 4. Stagger between relays (5 minutes)
293 210
294 const STARTUP_DELAY: u64 = 30; 211 const STARTUP_DELAY: u64 = 30;
295 const STAGGER: u64 = 300; 212 const STAGGER: u64 = 300;
296 213
297 assert_eq!(STARTUP_DELAY, 30); 214 assert_eq!(STARTUP_DELAY, 30);
298 assert_eq!(STAGGER, 300); 215 assert_eq!(STAGGER, 300);
299} 216}
@@ -306,10 +223,10 @@ fn test_reconnect_catchup_scenario() {
306 // 2. Wait 10s reconnect delay 223 // 2. Wait 10s reconnect delay
307 // 3. Only fetch last 3 days of events 224 // 3. Only fetch last 3 days of events
308 // 4. Runs in background (doesn't block connection) 225 // 4. Runs in background (doesn't block connection)
309 226
310 const RECONNECT_DELAY: u64 = 10; 227 const RECONNECT_DELAY: u64 = 10;
311 const LOOKBACK_DAYS: u64 = 3; 228 const LOOKBACK_DAYS: u64 = 3;
312 229
313 assert_eq!(RECONNECT_DELAY, 10); 230 assert_eq!(RECONNECT_DELAY, 10);
314 assert_eq!(LOOKBACK_DAYS, 3); 231 assert_eq!(LOOKBACK_DAYS, 3);
315} 232}
@@ -322,11 +239,11 @@ fn test_daily_catchup_scenario() {
322 // 2. Run if 24h elapsed since last catchup for that relay 239 // 2. Run if 24h elapsed since last catchup for that relay
323 // 3. Full reconciliation (no time limit) 240 // 3. Full reconciliation (no time limit)
324 // 4. Stagger between relays (5 minutes) 241 // 4. Stagger between relays (5 minutes)
325 242
326 const CHECK_INTERVAL: u64 = 3600; // 1 hour 243 const CHECK_INTERVAL: u64 = 3600; // 1 hour
327 const DAILY_INTERVAL: u64 = 86400; // 24 hours 244 const DAILY_INTERVAL: u64 = 86400; // 24 hours
328 const STAGGER: u64 = 300; // 5 minutes 245 const STAGGER: u64 = 300; // 5 minutes
329 246
330 assert_eq!(CHECK_INTERVAL, 3600); 247 assert_eq!(CHECK_INTERVAL, 3600);
331 assert_eq!(DAILY_INTERVAL, 86400); 248 assert_eq!(DAILY_INTERVAL, 86400);
332 assert_eq!(STAGGER, 300); 249 assert_eq!(STAGGER, 300);
@@ -343,10 +260,10 @@ fn test_existing_events_skipped() {
343 // 1. Fetch events from relay 260 // 1. Fetch events from relay
344 // 2. For each event, check if it exists locally 261 // 2. For each event, check if it exists locally
345 // 3. Skip if exists, validate and store if not 262 // 3. Skip if exists, validate and store if not
346 263
347 // This is implemented in negentropy.rs:event_exists_locally() 264 // This is implemented in negentropy.rs:event_exists_locally()
348 // which queries the database for the event by ID 265 // which queries the database for the event by ID
349 266
350 const SKIP_EXISTING: bool = true; 267 const SKIP_EXISTING: bool = true;
351 assert!(SKIP_EXISTING); 268 assert!(SKIP_EXISTING);
352} 269}
@@ -355,15 +272,15 @@ fn test_existing_events_skipped() {
355#[test] 272#[test]
356fn test_duplicate_prevention() { 273fn test_duplicate_prevention() {
357 use std::collections::HashSet; 274 use std::collections::HashSet;
358 275
359 let mut processed_ids: HashSet<String> = HashSet::new(); 276 let mut processed_ids: HashSet<String> = HashSet::new();
360 let event_id = "abc123def456".to_string(); 277 let event_id = "abc123def456".to_string();
361 278
362 // First time seeing this event - should process 279 // First time seeing this event - should process
363 let is_new = !processed_ids.contains(&event_id); 280 let is_new = !processed_ids.contains(&event_id);
364 assert!(is_new); 281 assert!(is_new);
365 processed_ids.insert(event_id.clone()); 282 processed_ids.insert(event_id.clone());
366 283
367 // Second time - should skip 284 // Second time - should skip
368 let is_duplicate = processed_ids.contains(&event_id); 285 let is_duplicate = processed_ids.contains(&event_id);
369 assert!(is_duplicate); 286 assert!(is_duplicate);
@@ -380,18 +297,18 @@ fn test_config_fields_for_catchup() {
380 // - sync_startup_delay_secs (default: 30) 297 // - sync_startup_delay_secs (default: 30)
381 // - sync_reconnect_delay_secs (default: 10) 298 // - sync_reconnect_delay_secs (default: 10)
382 // - sync_reconnect_lookback_days (default: 3) 299 // - sync_reconnect_lookback_days (default: 3)
383 300
384 // Environment variables: 301 // Environment variables:
385 // - NGIT_SYNC_STARTUP_DELAY_SECS 302 // - NGIT_SYNC_STARTUP_DELAY_SECS
386 // - NGIT_SYNC_RECONNECT_DELAY_SECS 303 // - NGIT_SYNC_RECONNECT_DELAY_SECS
387 // - NGIT_SYNC_RECONNECT_LOOKBACK_DAYS 304 // - NGIT_SYNC_RECONNECT_LOOKBACK_DAYS
388 305
389 let expected_defaults = vec![ 306 let expected_defaults = vec![
390 ("startup_delay_secs", 30u64), 307 ("startup_delay_secs", 30u64),
391 ("reconnect_delay_secs", 10u64), 308 ("reconnect_delay_secs", 10u64),
392 ("reconnect_lookback_days", 3u64), 309 ("reconnect_lookback_days", 3u64),
393 ]; 310 ];
394 311
395 assert_eq!(expected_defaults.len(), 3); 312 assert_eq!(expected_defaults.len(), 3);
396 assert_eq!(expected_defaults[0].1, 30); 313 assert_eq!(expected_defaults[0].1, 30);
397 assert_eq!(expected_defaults[1].1, 10); 314 assert_eq!(expected_defaults[1].1, 10);
@@ -405,9 +322,9 @@ fn test_catchup_respects_config() {
405 let custom_startup_delay: u64 = 60; 322 let custom_startup_delay: u64 = 60;
406 let custom_reconnect_delay: u64 = 20; 323 let custom_reconnect_delay: u64 = 20;
407 let custom_lookback_days: u64 = 7; 324 let custom_lookback_days: u64 = 7;
408 325
409 // All should be configurable to non-default values 326 // All should be configurable to non-default values
410 assert_ne!(custom_startup_delay, 30); 327 assert_ne!(custom_startup_delay, 30);
411 assert_ne!(custom_reconnect_delay, 10); 328 assert_ne!(custom_reconnect_delay, 10);
412 assert_ne!(custom_lookback_days, 3); 329 assert_ne!(custom_lookback_days, 3);
413} \ No newline at end of file 330}