diff options
| -rw-r--r-- | tests/common/sync_helpers.rs | 15 | ||||
| -rw-r--r-- | tests/sync.rs | 4 | ||||
| -rw-r--r-- | tests/sync/historic_sync.rs | 392 |
3 files changed, 134 insertions, 277 deletions
diff --git a/tests/common/sync_helpers.rs b/tests/common/sync_helpers.rs index 67dec3c..8971369 100644 --- a/tests/common/sync_helpers.rs +++ b/tests/common/sync_helpers.rs | |||
| @@ -1060,6 +1060,7 @@ mod tests { | |||
| 1060 | let metrics = ParsedMetrics::parse(text); | 1060 | let metrics = ParsedMetrics::parse(text); |
| 1061 | assert_eq!(metrics.relay_connected("ws://127.0.0.1:12345"), Some(true)); | 1061 | assert_eq!(metrics.relay_connected("ws://127.0.0.1:12345"), Some(true)); |
| 1062 | } | 1062 | } |
| 1063 | } | ||
| 1063 | 1064 | ||
| 1064 | // ============================================================================ | 1065 | // ============================================================================ |
| 1065 | // Unified Sync Test Helper | 1066 | // Unified Sync Test Helper |
| @@ -1125,16 +1126,14 @@ pub async fn run_sync_test( | |||
| 1125 | historic_events: &[Event], | 1126 | historic_events: &[Event], |
| 1126 | live_events: &[Event], | 1127 | live_events: &[Event], |
| 1127 | ) -> SyncTestResult { | 1128 | ) -> SyncTestResult { |
| 1128 | // Validate usage - exactly one slice must have content | 1129 | // Validate usage - cannot provide events in both slices |
| 1129 | let historic_mode = !historic_events.is_empty(); | 1130 | let historic_mode = !historic_events.is_empty(); |
| 1130 | let live_mode = !live_events.is_empty(); | 1131 | let live_mode = !live_events.is_empty(); |
| 1131 | 1132 | ||
| 1132 | if historic_mode && live_mode { | 1133 | if historic_mode && live_mode { |
| 1133 | panic!("Invalid usage: both historic_events and live_events provided. Use one or the other."); | 1134 | panic!("Invalid usage: both historic_events and live_events provided. Use one or the other."); |
| 1134 | } | 1135 | } |
| 1135 | if !historic_mode && !live_mode { | 1136 | // Note: Both slices can be empty - this tests just the announcement sync |
| 1136 | panic!("Invalid usage: both historic_events and live_events are empty. Provide at least one."); | ||
| 1137 | } | ||
| 1138 | 1137 | ||
| 1139 | // 1. Pre-allocate syncing relay port for announcement tags | 1138 | // 1. Pre-allocate syncing relay port for announcement tags |
| 1140 | let syncing_port = TestRelay::find_free_port(); | 1139 | let syncing_port = TestRelay::find_free_port(); |
| @@ -1218,11 +1217,5 @@ mod sync_helper_tests { | |||
| 1218 | let _result = run_sync_test(&[historic], &[live]).await; | 1217 | let _result = run_sync_test(&[historic], &[live]).await; |
| 1219 | } | 1218 | } |
| 1220 | 1219 | ||
| 1221 | #[tokio::test] | 1220 | // Note: Empty slices are now allowed - tests just the announcement sync |
| 1222 | #[should_panic(expected = "both historic_events and live_events are empty")] | ||
| 1223 | async fn test_run_sync_test_panics_with_empty_slices() { | ||
| 1224 | // Should panic - both slices empty | ||
| 1225 | let _result = run_sync_test(&[], &[]).await; | ||
| 1226 | } | ||
| 1227 | } | ||
| 1228 | } | 1221 | } |
diff --git a/tests/sync.rs b/tests/sync.rs index 2e09fb8..64fd10a 100644 --- a/tests/sync.rs +++ b/tests/sync.rs | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | //! This test file organizes tests for ngit-grasp's proactive sync functionality. | 3 | //! This test file organizes tests for ngit-grasp's proactive sync functionality. |
| 4 | //! Tests are grouped into submodules by sync scenario: | 4 | //! Tests are grouped into submodules by sync scenario: |
| 5 | //! | 5 | //! |
| 6 | //! - `bootstrap` - Tests for sync from pre-configured bootstrap relay | 6 | //! - `historic_sync` - Tests for sync from pre-configured bootstrap relay (historic events) |
| 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 | 8 | //! - `live_sync` - Tests for real-time sync after connection established |
| 9 | //! - `tag_variations` - Tests for different Layer 2/3 tag types | 9 | //! - `tag_variations` - Tests for different Layer 2/3 tag types |
| @@ -31,7 +31,7 @@ mod common; | |||
| 31 | 31 | ||
| 32 | // Include sync test submodules (located in tests/sync/) | 32 | // Include sync test submodules (located in tests/sync/) |
| 33 | mod sync { | 33 | mod sync { |
| 34 | pub mod bootstrap; | 34 | pub mod historic_sync; |
| 35 | pub mod catchup; | 35 | pub mod catchup; |
| 36 | pub mod discovery; | 36 | pub mod discovery; |
| 37 | pub mod live_sync; | 37 | pub mod live_sync; |
diff --git a/tests/sync/historic_sync.rs b/tests/sync/historic_sync.rs index 8f0c79b..d13886f 100644 --- a/tests/sync/historic_sync.rs +++ b/tests/sync/historic_sync.rs | |||
| @@ -1,12 +1,11 @@ | |||
| 1 | //! Bootstrap Sync Tests | 1 | //! Historic Sync Tests |
| 2 | //! | 2 | //! |
| 3 | //! Tests for relay synchronization from a pre-configured bootstrap relay. | 3 | //! Tests for relay synchronization from a pre-configured bootstrap relay. |
| 4 | //! These tests verify that a relay can sync events from another relay | 4 | //! These tests verify that a relay can sync events from another relay |
| 5 | //! that it's configured to connect to on startup. | 5 | //! that it's configured to connect to on startup. |
| 6 | //! | 6 | //! |
| 7 | //! # Tests | 7 | //! "Historic sync" refers to events that existed on the source relay BEFORE |
| 8 | //! - Test 1: Bootstrap sync on startup (existing events sync) | 8 | //! the syncing relay connected (bootstrap scenario). |
| 9 | //! - Test 4: Replay after restart (events persist and replay) | ||
| 10 | 9 | ||
| 11 | use std::time::Duration; | 10 | use std::time::Duration; |
| 12 | 11 | ||
| @@ -17,308 +16,183 @@ use crate::common::{sync_helpers::*, TestRelay}; | |||
| 17 | /// Test 1: Bootstrap sync - relay syncs existing events from bootstrap relay on startup | 16 | /// Test 1: Bootstrap sync - relay syncs existing events from bootstrap relay on startup |
| 18 | /// | 17 | /// |
| 19 | /// Scenario: | 18 | /// Scenario: |
| 20 | /// 1. Start relay_a (source) with an announcement | 19 | /// 1. Source relay has announcement (sent before syncing relay starts) |
| 21 | /// 2. Start relay_b configured to sync from relay_a | 20 | /// 2. Start syncing relay configured to sync from source |
| 22 | /// 3. Verify relay_b syncs the announcement from relay_a | 21 | /// 3. Verify announcement syncs via bootstrap/historic sync |
| 23 | /// | 22 | /// |
| 24 | /// This tests that when a relay starts with a bootstrap relay configured, | 23 | /// This tests that when a relay starts with a bootstrap relay configured, |
| 25 | /// it connects and syncs existing events. | 24 | /// it connects and syncs existing events. |
| 26 | #[tokio::test] | 25 | #[tokio::test] |
| 27 | async fn test_bootstrap_syncs_existing_layer2_events() { | 26 | async fn test_bootstrap_syncs_existing_layer2_events() { |
| 28 | // 1. Start source relay (relay_a) | 27 | // Use run_sync_test helper - announcement auto-created and sent as historic event |
| 29 | let relay_a = TestRelay::start().await; | 28 | let result = run_sync_test(&[], &[]).await; |
| 30 | println!( | ||
| 31 | "relay_a started at {} (domain: {})", | ||
| 32 | relay_a.url(), | ||
| 33 | relay_a.domain() | ||
| 34 | ); | ||
| 35 | |||
| 36 | // 2. Pre-allocate port for relay_b so we can include it in the announcement | ||
| 37 | let relay_b_port = TestRelay::find_free_port(); | ||
| 38 | let relay_b_domain = format!("127.0.0.1:{}", relay_b_port); | ||
| 39 | println!("Pre-allocated relay_b domain: {}", relay_b_domain); | ||
| 40 | |||
| 41 | // 3. Create test keys | ||
| 42 | let keys = Keys::generate(); | ||
| 43 | |||
| 44 | // 4. Create a repository announcement that lists BOTH relays | ||
| 45 | // This is required because relay_b's write policy checks that events reference its domain | ||
| 46 | let announcement = create_repo_announcement( | ||
| 47 | &keys, | ||
| 48 | &[&relay_a.domain(), &relay_b_domain], | ||
| 49 | "test-repo-bootstrap", | ||
| 50 | ); | ||
| 51 | let announcement_id = announcement.id; | ||
| 52 | |||
| 53 | println!( | ||
| 54 | "Created announcement {} (kind {})", | ||
| 55 | announcement_id, | ||
| 56 | announcement.kind.as_u16() | ||
| 57 | ); | ||
| 58 | for tag in announcement.tags.iter() { | ||
| 59 | println!(" Tag: {:?}", tag.as_slice()); | ||
| 60 | } | ||
| 61 | |||
| 62 | // 5. Send announcement to relay_a BEFORE relay_b starts | ||
| 63 | // This is key for testing bootstrap sync | ||
| 64 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 65 | .await | ||
| 66 | .expect("Failed to connect to relay_a"); | ||
| 67 | 29 | ||
| 68 | client_a | 30 | // Verify announcement synced to syncing relay |
| 69 | .send_event(&announcement) | ||
| 70 | .await | ||
| 71 | .expect("Failed to send announcement to relay_a"); | ||
| 72 | println!("Announcement sent to relay_a"); | ||
| 73 | |||
| 74 | client_a.disconnect().await; | ||
| 75 | |||
| 76 | // 6. Wait briefly to ensure event is persisted on relay_a | ||
| 77 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 78 | |||
| 79 | // 7. NOW start relay_b on the pre-allocated port, configured to sync from relay_a | ||
| 80 | // The announcement already exists on relay_a, so this tests bootstrap sync | ||
| 81 | let relay_b = TestRelay::start_on_port_with_options( | ||
| 82 | relay_b_port, | ||
| 83 | Some(relay_a.url().into()), | ||
| 84 | false, | ||
| 85 | ) | ||
| 86 | .await; | ||
| 87 | println!( | ||
| 88 | "relay_b started at {} (domain: {})", | ||
| 89 | relay_b.url(), | ||
| 90 | relay_b.domain() | ||
| 91 | ); | ||
| 92 | |||
| 93 | // 8. Wait for bootstrap sync to complete | ||
| 94 | // Bootstrap sync should happen automatically on startup | ||
| 95 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 96 | |||
| 97 | // 9. Verify announcement synced to relay_b | ||
| 98 | let filter = Filter::new() | 31 | let filter = Filter::new() |
| 99 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) | 32 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) |
| 100 | .author(keys.public_key()); | 33 | .author(result.maintainer_keys.public_key()); |
| 101 | 34 | ||
| 102 | let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; | 35 | let synced = wait_for_event_on_relay( |
| 36 | result.syncing_relay.url(), | ||
| 37 | filter, | ||
| 38 | Duration::from_secs(5) | ||
| 39 | ).await; | ||
| 103 | 40 | ||
| 104 | // 10. Cleanup | 41 | // Cleanup |
| 105 | relay_b.stop().await; | 42 | result.syncing_relay.stop().await; |
| 106 | relay_a.stop().await; | 43 | result.source_relay.stop().await; |
| 107 | 44 | ||
| 108 | assert!( | 45 | assert!( |
| 109 | synced, | 46 | synced, |
| 110 | "Announcement {} should have synced from relay_a to relay_b via bootstrap sync", | 47 | "Announcement should have synced from source to syncing relay via bootstrap sync" |
| 111 | announcement_id | ||
| 112 | ); | 48 | ); |
| 113 | } | 49 | } |
| 114 | 50 | ||
| 115 | /// Test 4: Replay after restart - relay re-syncs events from bootstrap after restart | 51 | /// Test 4: Replay after restart - relay re-syncs events from bootstrap after restart |
| 116 | /// | 52 | /// |
| 117 | /// Scenario: | 53 | /// Scenario: |
| 118 | /// 1. Start relay_a (bootstrap) with announcement | 54 | /// 1. Start source relay with announcement |
| 119 | /// 2. Start relay_b, sync events from relay_a | 55 | /// 2. Start syncing relay, sync events from source |
| 120 | /// 3. Verify sync worked | 56 | /// 3. Verify sync worked |
| 121 | /// 4. Stop relay_b | 57 | /// 4. Stop syncing relay |
| 122 | /// 5. Restart relay_b (should re-sync from relay_a) | 58 | /// 5. Restart syncing relay (should re-sync from source) |
| 123 | /// 6. Verify events are available again | 59 | /// 6. Verify events are available again |
| 124 | /// | 60 | /// |
| 125 | /// Note: Since we use in-memory database, relay_b loses events on stop. | 61 | /// Note: Since we use in-memory database, syncing relay loses events on stop. |
| 126 | /// This tests that the sync mechanism reconnects and re-syncs on restart. | 62 | /// This tests that the sync mechanism reconnects and re-syncs on restart. |
| 127 | #[tokio::test] | 63 | #[tokio::test] |
| 128 | async fn test_relay_replays_events_after_restart() { | 64 | async fn test_relay_replays_events_after_restart() { |
| 129 | // 1. Start source relay (relay_a) | 65 | // First run: establish sync |
| 130 | let relay_a = TestRelay::start().await; | 66 | let result = run_sync_test(&[], &[]).await; |
| 131 | println!( | ||
| 132 | "relay_a started at {} (domain: {})", | ||
| 133 | relay_a.url(), | ||
| 134 | relay_a.domain() | ||
| 135 | ); | ||
| 136 | |||
| 137 | // 2. Start relay_b first to get its domain | ||
| 138 | let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await; | ||
| 139 | println!( | ||
| 140 | "relay_b (first instance) started at {} (domain: {})", | ||
| 141 | relay_b.url(), | ||
| 142 | relay_b.domain() | ||
| 143 | ); | ||
| 144 | |||
| 145 | // 3. Create test keys | ||
| 146 | let keys = Keys::generate(); | ||
| 147 | |||
| 148 | // 4. Create announcement listing BOTH domains (so both relays will accept it) | ||
| 149 | let announcement = create_repo_announcement( | ||
| 150 | &keys, | ||
| 151 | &[&relay_a.domain(), &relay_b.domain()], | ||
| 152 | "test-repo-replay", | ||
| 153 | ); | ||
| 154 | let announcement_id = announcement.id; | ||
| 155 | 67 | ||
| 156 | println!( | 68 | // Verify announcement synced on first run |
| 157 | "Created announcement {} (kind {})", | ||
| 158 | announcement_id, | ||
| 159 | announcement.kind.as_u16() | ||
| 160 | ); | ||
| 161 | |||
| 162 | // 5. Send announcement to relay_a | ||
| 163 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | ||
| 164 | .await | ||
| 165 | .expect("Failed to connect to relay_a"); | ||
| 166 | |||
| 167 | client_a | ||
| 168 | .send_event(&announcement) | ||
| 169 | .await | ||
| 170 | .expect("Failed to send announcement to relay_a"); | ||
| 171 | println!("Announcement sent to relay_a"); | ||
| 172 | client_a.disconnect().await; | ||
| 173 | |||
| 174 | // 6. Wait for sync | ||
| 175 | tokio::time::sleep(Duration::from_secs(2)).await; | ||
| 176 | |||
| 177 | // 7. Verify announcement synced to relay_b (first time) | ||
| 178 | let filter = Filter::new() | 69 | let filter = Filter::new() |
| 179 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) | 70 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) |
| 180 | .author(keys.public_key()); | 71 | .author(result.maintainer_keys.public_key()); |
| 181 | 72 | ||
| 182 | let synced_first = | 73 | let synced_first = wait_for_event_on_relay( |
| 183 | wait_for_event_on_relay(relay_b.url(), filter.clone(), Duration::from_secs(5)).await; | 74 | result.syncing_relay.url(), |
| 184 | println!("First sync check: {}", synced_first); | 75 | filter.clone(), |
| 76 | Duration::from_secs(5) | ||
| 77 | ).await; | ||
| 185 | 78 | ||
| 186 | // 8. Stop relay_b | 79 | println!("First sync check: {}", synced_first); |
| 187 | relay_b.stop().await; | ||
| 188 | println!("relay_b stopped"); | ||
| 189 | 80 | ||
| 190 | // 9. Wait a moment | 81 | // Stop syncing relay (simulates restart) |
| 82 | result.syncing_relay.stop().await; | ||
| 191 | tokio::time::sleep(Duration::from_millis(500)).await; | 83 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 192 | 84 | ||
| 193 | // 10. Restart relay_b (new instance with same bootstrap config) | 85 | // Restart syncing relay (new instance with same bootstrap config) |
| 194 | // Note: The new relay_b will have a different domain, so we need to check | 86 | // Note: The new syncing relay will have a different domain, so it may not |
| 195 | // if it can still sync the event from relay_a (which already has it) | 87 | // accept the event if it doesn't list its domain. This is expected behavior. |
| 196 | let relay_b_new = TestRelay::start_with_sync(Some(relay_a.url().into())).await; | 88 | let syncing_new = TestRelay::start_with_sync(Some(result.source_relay.url().into())).await; |
| 197 | println!( | 89 | println!( |
| 198 | "relay_b (second instance) started at {} (domain: {})", | 90 | "Syncing relay (second instance) started at {} (domain: {})", |
| 199 | relay_b_new.url(), | 91 | syncing_new.url(), |
| 200 | relay_b_new.domain() | 92 | syncing_new.domain() |
| 201 | ); | 93 | ); |
| 202 | 94 | ||
| 203 | // 11. Wait for re-sync | 95 | // Wait for re-sync |
| 204 | tokio::time::sleep(Duration::from_secs(2)).await; | 96 | tokio::time::sleep(Duration::from_secs(2)).await; |
| 205 | 97 | ||
| 206 | // 12. Verify announcement is available on new relay_b | 98 | // Verify announcement is available on restarted syncing relay |
| 207 | // The announcement listed the OLD relay_b domain, but since relay_a still | 99 | let synced_after_restart = wait_for_event_on_relay( |
| 208 | // has the event, new relay_b should be able to sync it via bootstrap | 100 | syncing_new.url(), |
| 209 | let synced_after_restart = | 101 | filter, |
| 210 | wait_for_event_on_relay(relay_b_new.url(), filter, Duration::from_secs(5)).await; | 102 | Duration::from_secs(5) |
| 103 | ).await; | ||
| 211 | 104 | ||
| 212 | // 13. Cleanup | 105 | // Cleanup |
| 213 | relay_b_new.stop().await; | 106 | syncing_new.stop().await; |
| 214 | relay_a.stop().await; | 107 | result.source_relay.stop().await; |
| 215 | 108 | ||
| 216 | assert!( | 109 | assert!( |
| 217 | synced_first, | 110 | synced_first, |
| 218 | "Announcement {} should have synced on first connection", | 111 | "Announcement should have synced on first connection" |
| 219 | announcement_id | ||
| 220 | ); | 112 | ); |
| 221 | // Note: synced_after_restart may be false because the new relay_b has a different | 113 | // Note: synced_after_restart may be false because the new syncing relay has a different |
| 222 | // domain, and the announcement only lists the old relay_b domain. This is expected | 114 | // domain, and the announcement only lists the old syncing relay domain. This is expected. |
| 223 | // and tests realistic behavior - relay_b_new won't accept an event that doesn't | ||
| 224 | // list its domain. The important test is that sync MECHANISM works (synced_first). | ||
| 225 | println!( | 115 | println!( |
| 226 | "After restart sync result: {} (may be false due to domain change)", | 116 | "After restart sync result: {} (may be false due to domain change)", |
| 227 | synced_after_restart | 117 | synced_after_restart |
| 228 | ); | 118 | ); |
| 229 | } | 119 | } |
| 230 | 120 | ||
| 231 | /// Test 4: Rejection - announcement not listing relay should NOT sync | 121 | /// Test: Rejection - announcement not listing relay should NOT sync |
| 232 | /// | 122 | /// |
| 233 | /// Scenario: | 123 | /// Scenario: |
| 234 | /// 1. relay_a (source), relay_b (sync from relay_a) | 124 | /// 1. source relay, syncing relay (syncs from source) |
| 235 | /// 2. Create announcement listing ONLY relay_a domain | 125 | /// 2. Create announcement listing ONLY source domain |
| 236 | /// 3. Send to relay_a | 126 | /// 3. Send to source |
| 237 | /// 4. Verify NOT synced to relay_b (write policy rejects) | 127 | /// 4. Verify NOT synced to syncing relay (write policy rejects) |
| 238 | /// | 128 | /// |
| 239 | /// This tests that the relay's write policy correctly rejects events | 129 | /// This tests that the relay's write policy correctly rejects events |
| 240 | /// that don't list its domain in the clone tag. | 130 | /// that don't list its domain in the clone tag. |
| 241 | #[tokio::test] | 131 | #[tokio::test] |
| 242 | async fn test_announcement_not_listing_relay_is_not_synced() { | 132 | async fn test_announcement_not_listing_relay_is_not_synced() { |
| 243 | // 1. Start source relay (relay_a) | 133 | // Start source relay |
| 244 | let relay_a = TestRelay::start().await; | 134 | let source = TestRelay::start().await; |
| 245 | println!( | ||
| 246 | "relay_a started at {} (domain: {})", | ||
| 247 | relay_a.url(), | ||
| 248 | relay_a.domain() | ||
| 249 | ); | ||
| 250 | 135 | ||
| 251 | // 2. Start syncing relay (relay_b) configured to sync from relay_a | 136 | // Start syncing relay |
| 252 | let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await; | 137 | let syncing = TestRelay::start_with_sync(Some(source.url().into())).await; |
| 253 | println!( | ||
| 254 | "relay_b started at {} (domain: {})", | ||
| 255 | relay_b.url(), | ||
| 256 | relay_b.domain() | ||
| 257 | ); | ||
| 258 | 138 | ||
| 259 | // 3. Create test keys | 139 | // Create keys |
| 260 | let keys = Keys::generate(); | 140 | let keys = Keys::generate(); |
| 261 | 141 | ||
| 262 | // 4. Wait for relay_b's sync connection to establish | 142 | // Wait for sync connection to establish |
| 263 | // Use the sync connection helper for more reliable connection verification | 143 | match wait_for_sync_connection(syncing.url(), 1, Duration::from_secs(5)).await { |
| 264 | match wait_for_sync_connection(relay_b.url(), 1, Duration::from_secs(5)).await { | ||
| 265 | Ok(()) => println!("Sync connection established (verified via metrics)"), | 144 | Ok(()) => println!("Sync connection established (verified via metrics)"), |
| 266 | Err(e) => println!("Sync connection check: {} (continuing with test)", e), | 145 | Err(e) => println!("Sync connection check: {} (continuing with test)", e), |
| 267 | } | 146 | } |
| 268 | 147 | ||
| 269 | // 5. Create a repository announcement that lists ONLY relay_a | 148 | // Create announcement that lists ONLY source domain (NOT syncing) |
| 270 | // This should NOT sync to relay_b because relay_b's write policy | 149 | // This should NOT sync because syncing relay's write policy will reject it |
| 271 | // will reject events that don't list its domain | ||
| 272 | let announcement = create_repo_announcement( | 150 | let announcement = create_repo_announcement( |
| 273 | &keys, | 151 | &keys, |
| 274 | &[&relay_a.domain()], // Only relay_a, NOT relay_b | 152 | &[&source.domain()], // Only source, NOT syncing |
| 275 | "test-repo-rejection", | 153 | "test-repo-rejection", |
| 276 | ); | 154 | ); |
| 277 | let announcement_id = announcement.id; | 155 | let announcement_id = announcement.id; |
| 278 | 156 | ||
| 279 | println!( | 157 | println!( |
| 280 | "Created announcement {} (kind {}) - lists ONLY relay_a", | 158 | "Created announcement {} (kind {}) - lists ONLY source relay", |
| 281 | announcement_id, | 159 | announcement_id, |
| 282 | announcement.kind.as_u16() | 160 | announcement.kind.as_u16() |
| 283 | ); | 161 | ); |
| 284 | for tag in announcement.tags.iter() { | ||
| 285 | println!(" Tag: {:?}", tag.as_slice()); | ||
| 286 | } | ||
| 287 | 162 | ||
| 288 | // 6. Send announcement to relay_a | 163 | // Send announcement to source |
| 289 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 164 | let client = TestClient::new(source.url(), keys.clone()) |
| 290 | .await | 165 | .await |
| 291 | .expect("Failed to connect to relay_a"); | 166 | .expect("Failed to connect to source"); |
| 292 | 167 | ||
| 293 | client_a | 168 | client |
| 294 | .send_event(&announcement) | 169 | .send_event(&announcement) |
| 295 | .await | 170 | .await |
| 296 | .expect("Failed to send announcement to relay_a"); | 171 | .expect("Failed to send announcement to source"); |
| 297 | println!("Announcement sent to relay_a"); | 172 | println!("Announcement sent to source"); |
| 298 | 173 | ||
| 299 | client_a.disconnect().await; | 174 | client.disconnect().await; |
| 300 | 175 | ||
| 301 | // 7. Wait for potential sync attempt | 176 | // Wait for potential sync attempt |
| 302 | // Give enough time for sync to complete if it were to happen | ||
| 303 | tokio::time::sleep(Duration::from_secs(3)).await; | 177 | tokio::time::sleep(Duration::from_secs(3)).await; |
| 304 | 178 | ||
| 305 | // 8. Verify announcement did NOT sync to relay_b | 179 | // Verify announcement did NOT sync to syncing relay |
| 306 | let filter = Filter::new() | 180 | let filter = Filter::new() |
| 307 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) | 181 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) |
| 308 | .author(keys.public_key()); | 182 | .author(keys.public_key()); |
| 309 | 183 | ||
| 310 | let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(2)).await; | 184 | let synced = wait_for_event_on_relay(syncing.url(), filter, Duration::from_secs(2)).await; |
| 311 | 185 | ||
| 312 | // 9. Cleanup | 186 | // Cleanup |
| 313 | relay_b.stop().await; | 187 | syncing.stop().await; |
| 314 | relay_a.stop().await; | 188 | source.stop().await; |
| 315 | 189 | ||
| 316 | assert!( | 190 | assert!( |
| 317 | !synced, | 191 | !synced, |
| 318 | "Announcement {} should NOT have synced to relay_b because it doesn't list relay_b's domain", | 192 | "Announcement {} should NOT have synced to syncing relay because it doesn't list syncing relay's domain", |
| 319 | announcement_id | 193 | announcement_id |
| 320 | ); | 194 | ); |
| 321 | println!("SUCCESS: Announcement was correctly rejected by relay_b (not synced)"); | 195 | println!("SUCCESS: Announcement was correctly rejected by syncing relay (not synced)"); |
| 322 | } | 196 | } |
| 323 | 197 | ||
| 324 | /// Test: History sync (bootstrap) works without NIP-77 negentropy | 198 | /// Test: History sync (bootstrap) works without NIP-77 negentropy |
| @@ -328,41 +202,34 @@ async fn test_announcement_not_listing_relay_is_not_synced() { | |||
| 328 | /// the syncing relay connected. | 202 | /// the syncing relay connected. |
| 329 | /// | 203 | /// |
| 330 | /// Scenario: | 204 | /// Scenario: |
| 331 | /// 1. Start relay_b temporarily to get its domain (then stop it) | 205 | /// 1. Pre-allocate port for syncing relay to get its domain |
| 332 | /// 2. Start relay_a (source) | 206 | /// 2. Start source relay |
| 333 | /// 3. Create announcement listing both relay domains | 207 | /// 3. Create announcement listing both relay domains |
| 334 | /// 4. Send announcement to relay_a (event exists BEFORE relay_b connects) | 208 | /// 4. Send announcement to source (event exists BEFORE syncing relay connects) |
| 335 | /// 5. Start relay_b AGAIN on same port, with negentropy DISABLED | 209 | /// 5. Start syncing relay on pre-allocated port, with negentropy DISABLED |
| 336 | /// 6. relay_b should sync the pre-existing event via REQ+EOSE (history sync) | 210 | /// 6. Syncing relay should sync the pre-existing event via REQ+EOSE (history sync) |
| 337 | /// 7. Verify relay_b has the event | 211 | /// 7. Verify syncing relay has the event |
| 338 | /// | 212 | /// |
| 339 | /// This is different from "live sync" where events arrive after connection. | 213 | /// This is different from "live sync" where events arrive after connection. |
| 340 | #[tokio::test] | 214 | #[tokio::test] |
| 341 | async fn test_history_sync_without_negentropy() { | 215 | async fn test_history_sync_without_negentropy() { |
| 342 | // 1. First, start relay_b temporarily just to reserve a port and get its domain | 216 | // Pre-allocate syncing relay port to get its domain |
| 343 | let relay_b_port = TestRelay::find_free_port(); | 217 | let syncing_port = TestRelay::find_free_port(); |
| 344 | let relay_b_domain = format!("127.0.0.1:{}", relay_b_port); | 218 | let syncing_domain = format!("127.0.0.1:{}", syncing_port); |
| 345 | println!( | 219 | println!("Pre-allocated syncing relay domain: {}", syncing_domain); |
| 346 | "Reserved port {} for relay_b (domain: {})", | ||
| 347 | relay_b_port, relay_b_domain | ||
| 348 | ); | ||
| 349 | 220 | ||
| 350 | // 2. Start relay_a (source relay) | 221 | // Start source relay |
| 351 | let relay_a = TestRelay::start().await; | 222 | let source = TestRelay::start().await; |
| 352 | println!( | 223 | println!("Source started at {} (domain: {})", source.url(), source.domain()); |
| 353 | "relay_a started at {} (domain: {})", | ||
| 354 | relay_a.url(), | ||
| 355 | relay_a.domain() | ||
| 356 | ); | ||
| 357 | 224 | ||
| 358 | // 3. Create test keys | 225 | // Create keys |
| 359 | let keys = Keys::generate(); | 226 | let keys = Keys::generate(); |
| 360 | 227 | ||
| 361 | // 4. Create announcement listing BOTH relay domains | 228 | // Create announcement listing BOTH relay domains |
| 362 | // This event will exist on relay_a BEFORE relay_b ever connects | 229 | // This event will exist on source BEFORE syncing relay ever connects |
| 363 | let announcement = create_repo_announcement( | 230 | let announcement = create_repo_announcement( |
| 364 | &keys, | 231 | &keys, |
| 365 | &[&relay_a.domain(), &relay_b_domain], | 232 | &[&source.domain(), &syncing_domain], |
| 366 | "test-repo-history-no-negentropy", | 233 | "test-repo-history-no-negentropy", |
| 367 | ); | 234 | ); |
| 368 | let announcement_id = announcement.id; | 235 | let announcement_id = announcement.id; |
| @@ -372,58 +239,55 @@ async fn test_history_sync_without_negentropy() { | |||
| 372 | announcement_id, | 239 | announcement_id, |
| 373 | announcement.kind.as_u16() | 240 | announcement.kind.as_u16() |
| 374 | ); | 241 | ); |
| 375 | for tag in announcement.tags.iter() { | ||
| 376 | println!(" Tag: {:?}", tag.as_slice()); | ||
| 377 | } | ||
| 378 | 242 | ||
| 379 | // 5. Send announcement to relay_a (event now exists BEFORE relay_b connects) | 243 | // Send announcement to source (event now exists BEFORE syncing relay connects) |
| 380 | let client_a = TestClient::new(relay_a.url(), keys.clone()) | 244 | let client = TestClient::new(source.url(), keys.clone()) |
| 381 | .await | 245 | .await |
| 382 | .expect("Failed to connect to relay_a"); | 246 | .expect("Failed to connect to source"); |
| 383 | 247 | ||
| 384 | client_a | 248 | client |
| 385 | .send_event(&announcement) | 249 | .send_event(&announcement) |
| 386 | .await | 250 | .await |
| 387 | .expect("Failed to send announcement to relay_a"); | 251 | .expect("Failed to send announcement to source"); |
| 388 | println!("Announcement sent to relay_a (event exists BEFORE relay_b connects)"); | 252 | println!("Announcement sent to source (event exists BEFORE syncing relay connects)"); |
| 389 | 253 | ||
| 390 | client_a.disconnect().await; | 254 | client.disconnect().await; |
| 391 | 255 | ||
| 392 | // 6. Wait a moment to ensure the event is stored | 256 | // Wait to ensure event is stored |
| 393 | tokio::time::sleep(Duration::from_millis(500)).await; | 257 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 394 | 258 | ||
| 395 | // 7. NOW start relay_b on the reserved port, with negentropy DISABLED | 259 | // NOW start syncing relay on the reserved port, with negentropy DISABLED |
| 396 | // This relay_b has never connected before - it needs to do HISTORY sync | 260 | // This syncing relay has never connected before - it needs to do HISTORY sync |
| 397 | let relay_b = TestRelay::start_on_port_with_options( | 261 | let syncing = TestRelay::start_on_port_with_options( |
| 398 | relay_b_port, | 262 | syncing_port, |
| 399 | Some(relay_a.url().into()), | 263 | Some(source.url().into()), |
| 400 | true, // disable_negentropy = true | 264 | true, // disable_negentropy = true |
| 401 | ) | 265 | ) |
| 402 | .await; | 266 | .await; |
| 403 | println!( | 267 | println!( |
| 404 | "relay_b started at {} (domain: {}) - negentropy DISABLED, will do HISTORY sync", | 268 | "Syncing relay started at {} (domain: {}) - negentropy DISABLED, will do HISTORY sync", |
| 405 | relay_b.url(), | 269 | syncing.url(), |
| 406 | relay_b.domain() | 270 | syncing.domain() |
| 407 | ); | 271 | ); |
| 408 | 272 | ||
| 409 | // 8. Wait for history sync to complete (using REQ+EOSE, not negentropy) | 273 | // Wait for history sync to complete (using REQ+EOSE, not negentropy) |
| 410 | tokio::time::sleep(Duration::from_secs(3)).await; | 274 | tokio::time::sleep(Duration::from_secs(3)).await; |
| 411 | 275 | ||
| 412 | // 9. Verify announcement synced to relay_b via HISTORY sync | 276 | // Verify announcement synced to syncing relay via HISTORY sync |
| 413 | let filter = Filter::new() | 277 | let filter = Filter::new() |
| 414 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) | 278 | .kind(Kind::Custom(KIND_REPOSITORY_STATE)) |
| 415 | .author(keys.public_key()); | 279 | .author(keys.public_key()); |
| 416 | 280 | ||
| 417 | let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; | 281 | let synced = wait_for_event_on_relay(syncing.url(), filter, Duration::from_secs(5)).await; |
| 418 | 282 | ||
| 419 | // 10. Cleanup | 283 | // Cleanup |
| 420 | relay_b.stop().await; | 284 | syncing.stop().await; |
| 421 | relay_a.stop().await; | 285 | source.stop().await; |
| 422 | 286 | ||
| 423 | assert!( | 287 | assert!( |
| 424 | synced, | 288 | synced, |
| 425 | "Announcement {} should have synced from relay_a to relay_b via HISTORY sync (REQ+EOSE, negentropy disabled)", | 289 | "Announcement {} should have synced from source to syncing relay via HISTORY sync (REQ+EOSE, negentropy disabled)", |
| 426 | announcement_id | 290 | announcement_id |
| 427 | ); | 291 | ); |
| 428 | println!("SUCCESS: History sync works without negentropy (using REQ+EOSE fallback)"); | 292 | println!("SUCCESS: History sync works without negentropy (using REQ+EOSE fallback)"); |
| 429 | } | 293 | } \ No newline at end of file |