diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-10 16:31:47 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-10 16:31:47 +0000 |
| commit | 5f3c8b7085f6652fdde4443983df0aad561e3c67 (patch) | |
| tree | 7ce2bfda0ab5bd3871112f0e9bb396928a7652e6 /tests | |
| parent | bb9abe353024878e6bf1884cf3fb5a115e75bd03 (diff) | |
Phase 8: Create catchup sync stub test
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/sync.rs | 7 | ||||
| -rw-r--r-- | tests/sync/catchup.rs | 234 | ||||
| -rw-r--r-- | tests/sync/mod.rs | 19 |
3 files changed, 249 insertions, 11 deletions
diff --git a/tests/sync.rs b/tests/sync.rs index 7756c17..2836d8d 100644 --- a/tests/sync.rs +++ b/tests/sync.rs | |||
| @@ -5,6 +5,9 @@ | |||
| 5 | //! | 5 | //! |
| 6 | //! - `bootstrap` - Tests for sync from pre-configured bootstrap relay | 6 | //! - `bootstrap` - Tests for sync from pre-configured bootstrap relay |
| 7 | //! - `discovery` - Tests for relay discovery from announcement events | 7 | //! - `discovery` - Tests for relay discovery from announcement events |
| 8 | //! - `live_sync` - Tests for real-time sync after connection established | ||
| 9 | //! - `tag_variations` - Tests for different Layer 2/3 tag types | ||
| 10 | //! - `catchup` - Tests for catchup sync after disconnect (not yet implemented) | ||
| 8 | //! | 11 | //! |
| 9 | //! # Running Tests | 12 | //! # Running Tests |
| 10 | //! | 13 | //! |
| @@ -17,6 +20,9 @@ | |||
| 17 | //! | 20 | //! |
| 18 | //! # Run specific test | 21 | //! # Run specific test |
| 19 | //! cargo test --test sync test_bootstrap_syncs -- --nocapture | 22 | //! cargo test --test sync test_bootstrap_syncs -- --nocapture |
| 23 | //! | ||
| 24 | //! # Run ignored tests (like catchup) | ||
| 25 | //! cargo test --test sync -- --ignored | ||
| 20 | //! ``` | 26 | //! ``` |
| 21 | 27 | ||
| 22 | // Include the common test utilities | 28 | // Include the common test utilities |
| @@ -25,6 +31,7 @@ mod common; | |||
| 25 | // Include sync test submodules (located in tests/sync/) | 31 | // Include sync test submodules (located in tests/sync/) |
| 26 | mod sync { | 32 | mod sync { |
| 27 | pub mod bootstrap; | 33 | pub mod bootstrap; |
| 34 | pub mod catchup; | ||
| 28 | pub mod discovery; | 35 | pub mod discovery; |
| 29 | pub mod live_sync; | 36 | pub mod live_sync; |
| 30 | pub mod tag_variations; | 37 | pub mod tag_variations; |
diff --git a/tests/sync/catchup.rs b/tests/sync/catchup.rs new file mode 100644 index 0000000..2d0af16 --- /dev/null +++ b/tests/sync/catchup.rs | |||
| @@ -0,0 +1,234 @@ | |||
| 1 | //! Catchup Sync Tests | ||
| 2 | //! | ||
| 3 | //! Tests for the catchup synchronization feature (Test 0). | ||
| 4 | //! | ||
| 5 | //! # Catchup Sync Overview | ||
| 6 | //! | ||
| 7 | //! Catchup sync refers to the ability of a relay to synchronize historical events | ||
| 8 | //! that were published while it was offline or unreachable. This is critical for | ||
| 9 | //! ensuring data consistency across the relay network. | ||
| 10 | //! | ||
| 11 | //! ## Expected Behavior | ||
| 12 | //! | ||
| 13 | //! When a relay comes back online after being offline: | ||
| 14 | //! 1. Detect gap in event history by comparing timestamps | ||
| 15 | //! 2. Query connected relays for events in the gap period | ||
| 16 | //! 3. Backfill Layer 2 events (kind 1618) from bootstrap relays | ||
| 17 | //! 4. Discover and sync Layer 3 events (kinds 1, 1111) referencing Layer 2 events | ||
| 18 | //! 5. Maintain chronological ordering during backfill | ||
| 19 | //! | ||
| 20 | //! ## Implementation Status | ||
| 21 | //! | ||
| 22 | //! ⚠ **NOT YET IMPLEMENTED** - Tests marked with `#[ignore]` | ||
| 23 | //! | ||
| 24 | //! These tests are ready to enable once catchup sync is implemented in the relay. | ||
| 25 | //! | ||
| 26 | //! ## See Also | ||
| 27 | //! | ||
| 28 | //! - Bootstrap sync: [`tests/sync/bootstrap.rs`](bootstrap.rs) | ||
| 29 | //! - Live sync: [`tests/sync/live_sync.rs`](live_sync.rs) | ||
| 30 | //! - Discovery sync: [`tests/sync/discovery.rs`](discovery.rs) | ||
| 31 | |||
| 32 | use std::time::Duration; | ||
| 33 | |||
| 34 | use nostr_sdk::prelude::*; | ||
| 35 | |||
| 36 | use crate::common::{sync_helpers::*, TestRelay}; | ||
| 37 | |||
| 38 | /// Create a valid repository announcement event for testing sync. | ||
| 39 | /// | ||
| 40 | /// This creates a kind 30617 event with required clone and relays tags. | ||
| 41 | /// The event lists all provided domains so it will be accepted by each | ||
| 42 | /// relay's write policy. | ||
| 43 | /// | ||
| 44 | /// # Arguments | ||
| 45 | /// * `keys` - Keys for signing | ||
| 46 | /// * `domains` - Slice of domain strings (e.g., "127.0.0.1:8080") | ||
| 47 | /// * `identifier` - Repository identifier (d-tag) | ||
| 48 | fn create_repo_announcement(keys: &Keys, domains: &[&str], identifier: &str) -> Event { | ||
| 49 | // Build clone URLs for all domains (with .git suffix) | ||
| 50 | let clone_urls: Vec<String> = domains | ||
| 51 | .iter() | ||
| 52 | .map(|d| format!("http://{}/{}.git", d, identifier)) | ||
| 53 | .collect(); | ||
| 54 | |||
| 55 | // Build relay URLs for all domains | ||
| 56 | let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect(); | ||
| 57 | |||
| 58 | // Build tags for repository announcement | ||
| 59 | let tags = vec![ | ||
| 60 | Tag::identifier(identifier), | ||
| 61 | Tag::custom(TagKind::custom("clone"), clone_urls), | ||
| 62 | Tag::custom(TagKind::custom("relays"), relay_urls), | ||
| 63 | ]; | ||
| 64 | |||
| 65 | EventBuilder::new(Kind::Custom(KIND_REPOSITORY_STATE), "Repository state") | ||
| 66 | .tags(tags) | ||
| 67 | .sign_with_keys(keys) | ||
| 68 | .expect("Failed to sign repo announcement") | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Test that relay performs catchup sync after being offline | ||
| 72 | /// | ||
| 73 | /// # Scenario | ||
| 74 | /// | ||
| 75 | /// 1. Start two relays (relay1, relay2) with discovery configured | ||
| 76 | /// 2. Publish several Layer 2 events to relay2 | ||
| 77 | /// 3. Stop relay1 (simulating offline state) | ||
| 78 | /// 4. Publish more Layer 2 events to relay2 while relay1 is offline | ||
| 79 | /// 5. Restart relay1 | ||
| 80 | /// 6. Verify relay1 catches up and syncs events it missed | ||
| 81 | /// | ||
| 82 | /// # Expected Result | ||
| 83 | /// | ||
| 84 | /// All events published while relay1 was offline should be synced | ||
| 85 | /// to relay1 after it comes back online, maintaining chronological order. | ||
| 86 | /// | ||
| 87 | /// # TODO | ||
| 88 | /// | ||
| 89 | /// - Implement catchup sync mechanism in relay | ||
| 90 | /// - Add timestamp-based gap detection | ||
| 91 | /// - Add backfill query generation | ||
| 92 | /// - Enable this test by removing `#[ignore]` | ||
| 93 | #[tokio::test] | ||
| 94 | #[ignore = "Catchup sync not yet implemented"] | ||
| 95 | async fn test_catchup_sync_after_relay_restart() { | ||
| 96 | // NOTE: This is a skeleton implementation ready for when catchup sync is added | ||
| 97 | |||
| 98 | // 1. Start two relays | ||
| 99 | let relay1 = TestRelay::start().await; | ||
| 100 | let relay2 = TestRelay::start().await; | ||
| 101 | |||
| 102 | // 2. Set up discovery between relays via shared announcement | ||
| 103 | let keys = Keys::generate(); | ||
| 104 | let identifier = "catchup-test-repo"; | ||
| 105 | |||
| 106 | // Create announcement listing both relays | ||
| 107 | let domain1 = relay1.domain(); | ||
| 108 | let domain2 = relay2.domain(); | ||
| 109 | let announcement = create_repo_announcement( | ||
| 110 | &keys, | ||
| 111 | &[&domain1, &domain2], | ||
| 112 | identifier, | ||
| 113 | ); | ||
| 114 | |||
| 115 | // Publish announcement to both relays | ||
| 116 | let client1 = TestClient::new(relay1.url(), keys.clone()) | ||
| 117 | .await | ||
| 118 | .expect("Failed to connect to relay1"); | ||
| 119 | let client2 = TestClient::new(relay2.url(), keys.clone()) | ||
| 120 | .await | ||
| 121 | .expect("Failed to connect to relay2"); | ||
| 122 | |||
| 123 | client1 | ||
| 124 | .send_event(&announcement) | ||
| 125 | .await | ||
| 126 | .expect("Failed to send announcement to relay1"); | ||
| 127 | client2 | ||
| 128 | .send_event(&announcement) | ||
| 129 | .await | ||
| 130 | .expect("Failed to send announcement to relay2"); | ||
| 131 | |||
| 132 | // Wait for discovery connections to establish | ||
| 133 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 134 | |||
| 135 | // 3. Publish initial Layer 2 event (while both relays are online) | ||
| 136 | let repo_coord_str = repo_coord(&keys, identifier); | ||
| 137 | let event1 = build_layer2_issue_event(&keys, &repo_coord_str, "Issue 1 - before offline") | ||
| 138 | .expect("Failed to build event1"); | ||
| 139 | let event1_id = client2 | ||
| 140 | .send_event(&event1) | ||
| 141 | .await | ||
| 142 | .expect("Failed to send event1"); | ||
| 143 | |||
| 144 | // Verify initial sync works (baseline check) | ||
| 145 | let synced = wait_for_event_on_relay( | ||
| 146 | relay1.url(), | ||
| 147 | Filter::new().id(event1_id), | ||
| 148 | Duration::from_secs(5), | ||
| 149 | ) | ||
| 150 | .await; | ||
| 151 | assert!(synced, "Initial event should sync normally via live sync"); | ||
| 152 | |||
| 153 | // 4. Stop relay1 (simulating offline state) | ||
| 154 | // Note: In a real implementation, we'd need a way to stop and restart a relay | ||
| 155 | // For now, this skeleton demonstrates the intended test flow | ||
| 156 | relay1.stop().await; | ||
| 157 | |||
| 158 | // Small delay to ensure relay1 is fully stopped | ||
| 159 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 160 | |||
| 161 | // 5. Publish events while relay1 is offline | ||
| 162 | let event2 = build_layer2_issue_event(&keys, &repo_coord_str, "Issue 2 - during offline") | ||
| 163 | .expect("Failed to build event2"); | ||
| 164 | let event2_id = client2 | ||
| 165 | .send_event(&event2) | ||
| 166 | .await | ||
| 167 | .expect("Failed to send event2"); | ||
| 168 | |||
| 169 | let event3 = build_layer2_issue_event(&keys, &repo_coord_str, "Issue 3 - during offline") | ||
| 170 | .expect("Failed to build event3"); | ||
| 171 | let event3_id = client2 | ||
| 172 | .send_event(&event3) | ||
| 173 | .await | ||
| 174 | .expect("Failed to send event3"); | ||
| 175 | |||
| 176 | // Give time for events to be stored in relay2 | ||
| 177 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 178 | |||
| 179 | // 6. Restart relay1 | ||
| 180 | // Note: TestRelay doesn't currently support restart, so we start a new instance | ||
| 181 | // A real implementation would need persistent storage and relay restart capability | ||
| 182 | let relay1_restarted = TestRelay::start().await; | ||
| 183 | |||
| 184 | // Reconnect client to the new relay instance | ||
| 185 | let client1_restarted = TestClient::new(relay1_restarted.url(), keys.clone()) | ||
| 186 | .await | ||
| 187 | .expect("Failed to connect to restarted relay1"); | ||
| 188 | |||
| 189 | // Re-publish announcement to establish discovery | ||
| 190 | let domain1_restarted = relay1_restarted.domain(); | ||
| 191 | let announcement_restarted = create_repo_announcement( | ||
| 192 | &keys, | ||
| 193 | &[&domain1_restarted, &domain2], | ||
| 194 | identifier, | ||
| 195 | ); | ||
| 196 | client1_restarted | ||
| 197 | .send_event(&announcement_restarted) | ||
| 198 | .await | ||
| 199 | .expect("Failed to send announcement to restarted relay1"); | ||
| 200 | |||
| 201 | // 7. Wait for catchup sync to complete | ||
| 202 | // This is where the catchup sync mechanism would kick in | ||
| 203 | tokio::time::sleep(Duration::from_secs(5)).await; | ||
| 204 | |||
| 205 | // 8. Verify missed events were synced via catchup | ||
| 206 | let event2_synced = wait_for_event_on_relay( | ||
| 207 | relay1_restarted.url(), | ||
| 208 | Filter::new().id(event2_id), | ||
| 209 | Duration::from_secs(5), | ||
| 210 | ) | ||
| 211 | .await; | ||
| 212 | |||
| 213 | let event3_synced = wait_for_event_on_relay( | ||
| 214 | relay1_restarted.url(), | ||
| 215 | Filter::new().id(event3_id), | ||
| 216 | Duration::from_secs(5), | ||
| 217 | ) | ||
| 218 | .await; | ||
| 219 | |||
| 220 | assert!( | ||
| 221 | event2_synced, | ||
| 222 | "Event 2 (missed while offline) should be synced via catchup" | ||
| 223 | ); | ||
| 224 | assert!( | ||
| 225 | event3_synced, | ||
| 226 | "Event 3 (missed while offline) should be synced via catchup" | ||
| 227 | ); | ||
| 228 | |||
| 229 | // 9. Cleanup | ||
| 230 | client1_restarted.disconnect().await; | ||
| 231 | client2.disconnect().await; | ||
| 232 | relay1_restarted.stop().await; | ||
| 233 | relay2.stop().await; | ||
| 234 | } \ No newline at end of file | ||
diff --git a/tests/sync/mod.rs b/tests/sync/mod.rs index a3d7bb5..b0da8b8 100644 --- a/tests/sync/mod.rs +++ b/tests/sync/mod.rs | |||
| @@ -9,13 +9,13 @@ | |||
| 9 | //! - Tag variations (testing different Layer 2/3 tag types: a/A/q, e/E/q) | 9 | //! - Tag variations (testing different Layer 2/3 tag types: a/A/q, e/E/q) |
| 10 | //! - Catchup sync (events from disconnected period sync on reconnect) | 10 | //! - Catchup sync (events from disconnected period sync on reconnect) |
| 11 | //! | 11 | //! |
| 12 | //! # Test Files (to be added in subsequent phases) | 12 | //! # Test Files |
| 13 | //! | 13 | //! |
| 14 | //! - `bootstrap.rs` - Tests 1, 4: sync from bootstrap relay | 14 | //! - `bootstrap.rs` - Tests 1, 4: sync from bootstrap relay |
| 15 | //! - `discovery.rs` - Tests 2, 3: relay discovery from announcements | 15 | //! - `discovery.rs` - Tests 2, 3: relay discovery from announcements |
| 16 | //! - `live_sync.rs` - Tests 5, 6, 7: real-time sync after connection | 16 | //! - `live_sync.rs` - Tests 5, 6, 7: real-time sync after connection |
| 17 | //! - `tag_variations.rs` - Tests 8, 9: Layer 2/3 tag type coverage | 17 | //! - `tag_variations.rs` - Tests 8, 9: Layer 2/3 tag type coverage |
| 18 | //! - `catchup.rs` - Test 0: catchup after disconnect (stub) | 18 | //! - `catchup.rs` - Test 0: catchup after disconnect (stub, `#[ignore]`) |
| 19 | //! | 19 | //! |
| 20 | //! # Shared Imports | 20 | //! # Shared Imports |
| 21 | //! | 21 | //! |
| @@ -23,13 +23,10 @@ | |||
| 23 | //! - `TestClient` - Client with retry logic | 23 | //! - `TestClient` - Client with retry logic |
| 24 | //! - Event builders for Layer 2/3 events | 24 | //! - Event builders for Layer 2/3 events |
| 25 | //! - `wait_for_event_on_relay()` - Non-panicking assertion helper | 25 | //! - `wait_for_event_on_relay()` - Non-panicking assertion helper |
| 26 | //! | ||
| 27 | //! See `work/proactive-sync-test-implementation-plan.md` for full design. | ||
| 28 | |||
| 29 | // Re-export sync helpers for convenient access in test files | ||
| 30 | // Tests in this module can use: | ||
| 31 | // use super::*; | ||
| 32 | // to get access to these helpers. | ||
| 33 | 26 | ||
| 34 | // Note: The actual test file modules will be added in Phase 5+ | 27 | // Test modules |
| 35 | // For now, this module serves as the organizational root. \ No newline at end of file | 28 | pub mod bootstrap; |
| 29 | pub mod catchup; | ||
| 30 | pub mod discovery; | ||
| 31 | pub mod live_sync; | ||
| 32 | pub mod tag_variations; \ No newline at end of file | ||