diff options
Diffstat (limited to 'tests/sync/maintainer_reprocessing.rs')
| -rw-r--r-- | tests/sync/maintainer_reprocessing.rs | 278 |
1 files changed, 202 insertions, 76 deletions
diff --git a/tests/sync/maintainer_reprocessing.rs b/tests/sync/maintainer_reprocessing.rs index df1bf78..ff1eb43 100644 --- a/tests/sync/maintainer_reprocessing.rs +++ b/tests/sync/maintainer_reprocessing.rs | |||
| @@ -2,6 +2,25 @@ | |||
| 2 | //! | 2 | //! |
| 3 | //! Tests the two-tier rejected events index and immediate re-processing of | 3 | //! Tests the two-tier rejected events index and immediate re-processing of |
| 4 | //! maintainer announcements when owner announcements are accepted. | 4 | //! maintainer announcements when owner announcements are accepted. |
| 5 | //! | ||
| 6 | //! ## Test design | ||
| 7 | //! | ||
| 8 | //! Announcements now require git data before they are released from purgatory and | ||
| 9 | //! served to other relays. The hot-cache re-processing path we want to exercise is: | ||
| 10 | //! | ||
| 11 | //! relay_b syncs maintainer announcement from relay_a | ||
| 12 | //! → write policy rejects it (no owner announcement in DB yet) | ||
| 13 | //! → event stored in hot cache | ||
| 14 | //! owner git push to relay_b promotes owner announcement from purgatory | ||
| 15 | //! → our new code calls rejected_events_index.invalidate_and_get() | ||
| 16 | //! → maintainer announcement re-processed and accepted | ||
| 17 | //! | ||
| 18 | //! To guarantee the maintainer announcements arrive at relay_b *before* the owner | ||
| 19 | //! git push, relay_b is started with relay_a as its bootstrap relay. That way | ||
| 20 | //! relay_b's SyncManager connects to relay_a immediately and syncs whatever is | ||
| 21 | //! already in relay_a's DB. We push the maintainer git data first (so the | ||
| 22 | //! announcements are in relay_a's DB), wait briefly for the sync round-trip, then | ||
| 23 | //! send the owner announcement + git push. | ||
| 5 | 24 | ||
| 6 | use std::time::Duration; | 25 | use std::time::Duration; |
| 7 | 26 | ||
| @@ -9,66 +28,91 @@ use nostr_sdk::prelude::*; | |||
| 9 | 28 | ||
| 10 | use crate::common::{sync_helpers::*, TestRelay}; | 29 | use crate::common::{sync_helpers::*, TestRelay}; |
| 11 | 30 | ||
| 12 | /// Test that maintainer announcements are re-processed immediately when owner announcement accepted | 31 | /// Test that a maintainer announcement is re-processed immediately when the owner |
| 32 | /// announcement is promoted from purgatory via a git push. | ||
| 13 | /// | 33 | /// |
| 14 | /// Flow: | 34 | /// Flow: |
| 15 | /// 1. relay_a: Maintainer sends announcement (gets rejected - doesn't list relay_b) | 35 | /// 1. relay_a: Maintainer sends announcement + git data → accepted into relay_a's DB |
| 16 | /// 2. relay_b: Owner sends announcement (lists relay_a + maintainer) | 36 | /// 2. relay_b (bootstrapped from relay_a): SyncManager syncs maintainer announcement |
| 17 | /// 3. relay_b syncs from relay_a, maintainer announcement enters rejected index | 37 | /// → rejected by write policy (no owner in DB) → stored in hot cache |
| 18 | /// 4. relay_b processes owner announcement, invalidates and re-processes maintainer announcement | 38 | /// 3. relay_b: Owner sends announcement → purgatory (no git data yet) |
| 39 | /// 4. relay_b: Owner git push → owner announcement promoted from purgatory | ||
| 40 | /// → hot-cache re-processing fires → maintainer announcement accepted | ||
| 19 | /// 5. Both announcements should be in relay_b's database | 41 | /// 5. Both announcements should be in relay_b's database |
| 20 | /// | ||
| 21 | /// Expected time: <5 seconds (vs 24 hours without hot cache) | ||
| 22 | #[tokio::test] | 42 | #[tokio::test] |
| 23 | async fn test_maintainer_announcement_reprocessed_immediately() { | 43 | async fn test_maintainer_announcement_reprocessed_immediately() { |
| 24 | // Start relay_a (where maintainer announcement will be sent) | 44 | // Start relay_a (where maintainer announcement will be sent) |
| 25 | let relay_a = TestRelay::start().await; | 45 | let relay_a = TestRelay::start().await; |
| 26 | println!("relay_a started at {}", relay_a.url()); | 46 | println!("relay_a started at {}", relay_a.url()); |
| 27 | 47 | ||
| 28 | // Start relay_b with sync enabled (will sync from relay_a) | ||
| 29 | let relay_b = TestRelay::start_with_sync(None).await; | ||
| 30 | println!("relay_b started at {}", relay_b.url()); | ||
| 31 | |||
| 32 | // Create keys | 48 | // Create keys |
| 33 | let owner_keys = Keys::generate(); | 49 | let owner_keys = Keys::generate(); |
| 34 | let maintainer_keys = Keys::generate(); | 50 | let maintainer_keys = Keys::generate(); |
| 35 | |||
| 36 | let identifier = "test-repo"; | 51 | let identifier = "test-repo"; |
| 37 | 52 | ||
| 38 | let start = std::time::Instant::now(); | 53 | // Step 1: Send maintainer announcement to relay_a then push git data so it lands in |
| 39 | 54 | // relay_a's DB. The announcement lists relay_a only (not relay_b), so relay_b's write | |
| 40 | // Step 1: Send maintainer announcement to relay_a (will be rejected - doesn't list relay_b) | 55 | // policy will reject it when it arrives via sync. |
| 41 | let client_a = TestClient::new(relay_a.url(), maintainer_keys.clone()) | 56 | let maintainer_npub = maintainer_keys |
| 42 | .await | 57 | .public_key() |
| 43 | .expect("Failed to connect to relay_a"); | 58 | .to_bech32() |
| 44 | 59 | .expect("Failed to get npub"); | |
| 45 | let maintainer_announcement = | 60 | let maintainer_announcement = |
| 46 | EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") | 61 | EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") |
| 47 | .tags(vec![ | 62 | .tags(vec![ |
| 48 | Tag::identifier(identifier), | 63 | Tag::identifier(identifier), |
| 49 | Tag::custom( | 64 | Tag::custom( |
| 50 | TagKind::custom("clone"), | 65 | TagKind::custom("clone"), |
| 51 | vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], | 66 | vec![format!( |
| 67 | "http://{}/{}/{}.git", | ||
| 68 | relay_a.domain(), | ||
| 69 | maintainer_npub, | ||
| 70 | identifier | ||
| 71 | )], | ||
| 72 | ), | ||
| 73 | Tag::custom( | ||
| 74 | TagKind::custom("relays"), | ||
| 75 | vec![relay_a.url().to_string()], | ||
| 52 | ), | 76 | ), |
| 53 | Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), | ||
| 54 | ]) | 77 | ]) |
| 55 | .sign_with_keys(&maintainer_keys) | 78 | .sign_with_keys(&maintainer_keys) |
| 56 | .unwrap(); | 79 | .unwrap(); |
| 80 | send_to_relay(&relay_a, &maintainer_announcement).await.unwrap(); | ||
| 81 | let _git_dir_maintainer = | ||
| 82 | push_git_data_to_relay(&relay_a, &maintainer_keys, identifier, &[&relay_a.domain()]) | ||
| 83 | .await; | ||
| 84 | println!("✓ Maintainer announcement + git data pushed to relay_a"); | ||
| 85 | |||
| 86 | // Step 2: Start relay_b with relay_a as bootstrap so its SyncManager connects immediately. | ||
| 87 | // relay_b's initial negentropy sync will pick up the maintainer announcement and reject it | ||
| 88 | // (no owner announcement in relay_b's DB yet), storing it in the hot cache. | ||
| 89 | let relay_b = TestRelay::start_with_sync(Some(relay_a.url().to_string())).await; | ||
| 90 | println!("relay_b started at {}", relay_b.url()); | ||
| 57 | 91 | ||
| 58 | client_a.send_event(&maintainer_announcement).await.unwrap(); | 92 | // Give relay_b's SyncManager time to complete the initial negentropy sync with relay_a. |
| 59 | println!("✓ Maintainer announcement sent to relay_a"); | 93 | tokio::time::sleep(Duration::from_secs(3)).await; |
| 94 | println!("✓ relay_b synced from relay_a (maintainer announcement should be in hot cache)"); | ||
| 60 | 95 | ||
| 61 | // Step 2: Send owner announcement to relay_b (lists relay_a + maintainer) | 96 | let start = std::time::Instant::now(); |
| 62 | let client_b = TestClient::new(relay_b.url(), owner_keys.clone()) | 97 | |
| 63 | .await | 98 | // Step 3: Send owner announcement to relay_b → goes to purgatory (no git data yet). |
| 64 | .expect("Failed to connect to relay_b"); | 99 | // The announcement lists relay_a + relay_b and names the maintainer. |
| 100 | let owner_npub = owner_keys | ||
| 101 | .public_key() | ||
| 102 | .to_bech32() | ||
| 103 | .expect("Failed to get npub"); | ||
| 65 | 104 | ||
| 66 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") | 105 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 67 | .tags(vec![ | 106 | .tags(vec![ |
| 68 | Tag::identifier(identifier), | 107 | Tag::identifier(identifier), |
| 69 | Tag::custom( | 108 | Tag::custom( |
| 70 | TagKind::custom("clone"), | 109 | TagKind::custom("clone"), |
| 71 | vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], | 110 | vec![format!( |
| 111 | "http://{}/{}/{}.git", | ||
| 112 | relay_b.domain(), | ||
| 113 | owner_npub, | ||
| 114 | identifier | ||
| 115 | )], | ||
| 72 | ), | 116 | ), |
| 73 | Tag::custom( | 117 | Tag::custom( |
| 74 | TagKind::custom("relays"), | 118 | TagKind::custom("relays"), |
| @@ -82,15 +126,22 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 82 | .sign_with_keys(&owner_keys) | 126 | .sign_with_keys(&owner_keys) |
| 83 | .unwrap(); | 127 | .unwrap(); |
| 84 | 128 | ||
| 85 | client_b.send_event(&owner_announcement).await.unwrap(); | 129 | send_to_relay(&relay_b, &owner_announcement).await.unwrap(); |
| 86 | println!("✓ Owner announcement sent to relay_b"); | 130 | println!("✓ Owner announcement sent to relay_b (now in purgatory)"); |
| 87 | 131 | ||
| 88 | // Step 3: Wait for sync and re-processing (relay_b discovers relay_a, syncs, re-processes) | 132 | // Step 4: Push owner git data to relay_b. |
| 89 | tokio::time::sleep(Duration::from_secs(3)).await; | 133 | // This promotes the owner announcement from purgatory, which triggers hot-cache |
| 134 | // re-processing of the maintainer announcement via our new code path. | ||
| 135 | let _git_dir_owner = | ||
| 136 | push_git_data_to_relay(&relay_b, &owner_keys, identifier, &[&relay_b.domain()]).await; | ||
| 137 | println!("✓ Owner git data pushed to relay_b (owner announcement promoted, hot cache re-processed)"); | ||
| 138 | |||
| 139 | // Step 5: Wait briefly for async processing to complete. | ||
| 140 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 90 | 141 | ||
| 91 | let elapsed = start.elapsed(); | 142 | let elapsed = start.elapsed(); |
| 92 | 143 | ||
| 93 | // Step 4: Verify both announcements are in relay_b's database | 144 | // Step 6: Verify both announcements are in relay_b's database. |
| 94 | let owner_filter = Filter::new() | 145 | let owner_filter = Filter::new() |
| 95 | .kind(Kind::GitRepoAnnouncement) | 146 | .kind(Kind::GitRepoAnnouncement) |
| 96 | .author(owner_keys.public_key()) | 147 | .author(owner_keys.public_key()) |
| @@ -112,17 +163,14 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 112 | "Maintainer announcement should be re-processed and accepted in relay_b" | 163 | "Maintainer announcement should be re-processed and accepted in relay_b" |
| 113 | ); | 164 | ); |
| 114 | 165 | ||
| 115 | // Step 5: Verify it happened quickly (not 24 hours!) | ||
| 116 | assert!( | 166 | assert!( |
| 117 | elapsed.as_secs() < 10, | 167 | elapsed.as_secs() < 15, |
| 118 | "Re-processing should happen in <10 seconds, took {:?}", | 168 | "Re-processing should happen in <15 seconds, took {:?}", |
| 119 | elapsed | 169 | elapsed |
| 120 | ); | 170 | ); |
| 121 | 171 | ||
| 122 | println!("✅ Maintainer announcement re-processed in {:?}", elapsed); | 172 | println!("✅ Maintainer announcement re-processed in {:?}", elapsed); |
| 123 | 173 | ||
| 124 | client_a.disconnect().await; | ||
| 125 | client_b.disconnect().await; | ||
| 126 | relay_a.stop().await; | 174 | relay_a.stop().await; |
| 127 | relay_b.stop().await; | 175 | relay_b.stop().await; |
| 128 | } | 176 | } |
| @@ -227,13 +275,16 @@ async fn test_maintainer_announcement_cold_index_prevents_refetch() { | |||
| 227 | relay.stop().await; | 275 | relay.stop().await; |
| 228 | } | 276 | } |
| 229 | 277 | ||
| 230 | /// Test multiple maintainers are all re-processed when owner announcement accepted | 278 | /// Test that all maintainer announcements are re-processed when the owner announcement |
| 279 | /// is promoted from purgatory via a git push. | ||
| 231 | /// | 280 | /// |
| 232 | /// Flow: | 281 | /// Flow: |
| 233 | /// 1. relay_a: Three maintainers send announcements (get rejected - don't list relay_b) | 282 | /// 1. relay_a: Three maintainers send announcements + git data → in relay_a's DB |
| 234 | /// 2. relay_b: Owner sends announcement (lists relay_a + all three maintainers) | 283 | /// 2. relay_b (bootstrapped from relay_a): SyncManager syncs all three maintainer |
| 235 | /// 3. relay_b syncs from relay_a, all maintainer announcements enter rejected index | 284 | /// announcements → all rejected (no owner in DB) → all in hot cache |
| 236 | /// 4. relay_b processes owner announcement, invalidates and re-processes all maintainer announcements | 285 | /// 3. relay_b: Owner sends announcement → purgatory |
| 286 | /// 4. relay_b: Owner git push → owner promoted → hot-cache re-processing fires for | ||
| 287 | /// all three maintainers | ||
| 237 | /// 5. All four announcements should be in relay_b's database | 288 | /// 5. All four announcements should be in relay_b's database |
| 238 | #[tokio::test] | 289 | #[tokio::test] |
| 239 | async fn test_multiple_maintainers_all_reprocessed() { | 290 | async fn test_multiple_maintainers_all_reprocessed() { |
| @@ -241,57 +292,113 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 241 | let relay_a = TestRelay::start().await; | 292 | let relay_a = TestRelay::start().await; |
| 242 | println!("relay_a started at {}", relay_a.url()); | 293 | println!("relay_a started at {}", relay_a.url()); |
| 243 | 294 | ||
| 244 | // Start relay_b with sync enabled (will sync from relay_a) | ||
| 245 | let relay_b = TestRelay::start_with_sync(None).await; | ||
| 246 | println!("relay_b started at {}", relay_b.url()); | ||
| 247 | |||
| 248 | // Create keys | 295 | // Create keys |
| 249 | let owner_keys = Keys::generate(); | 296 | let owner_keys = Keys::generate(); |
| 250 | let maintainer1_keys = Keys::generate(); | 297 | let maintainer1_keys = Keys::generate(); |
| 251 | let maintainer2_keys = Keys::generate(); | 298 | let maintainer2_keys = Keys::generate(); |
| 252 | let maintainer3_keys = Keys::generate(); | 299 | let maintainer3_keys = Keys::generate(); |
| 253 | 300 | ||
| 254 | let identifier = "multi-maintainer-repo"; | 301 | // Use a unique identifier per test run to avoid cross-test interference when |
| 255 | 302 | // tests run in parallel (each test gets its own namespace on relay_a). | |
| 256 | // Step 1: Send three maintainer announcements to relay_a | 303 | let identifier = &format!( |
| 257 | let client_a = TestClient::new(relay_a.url(), maintainer1_keys.clone()) | 304 | "multi-maintainer-repo-{}", |
| 258 | .await | 305 | owner_keys.public_key().to_hex()[..8].to_string() |
| 259 | .expect("Failed to connect to relay_a"); | 306 | ); |
| 260 | 307 | ||
| 308 | // Step 1: Send each maintainer announcement to relay_a then push git data so all three | ||
| 309 | // land in relay_a's DB. Each announcement lists relay_a only, so relay_b will reject | ||
| 310 | // them when syncing (no owner announcement in relay_b's DB yet). | ||
| 311 | let mut git_dirs = Vec::new(); | ||
| 261 | for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys] | 312 | for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys] |
| 262 | .iter() | 313 | .iter() |
| 263 | .enumerate() | 314 | .enumerate() |
| 264 | { | 315 | { |
| 316 | let m_npub = maintainer_keys | ||
| 317 | .public_key() | ||
| 318 | .to_bech32() | ||
| 319 | .expect("Failed to get npub"); | ||
| 265 | let announcement = EventBuilder::new( | 320 | let announcement = EventBuilder::new( |
| 266 | Kind::GitRepoAnnouncement, | 321 | Kind::GitRepoAnnouncement, |
| 267 | format!("Maintainer {} repository", idx + 1), | 322 | format!("Maintainer {} repository", idx + 1), |
| 268 | ) | 323 | ) |
| 269 | .tags(vec![ | 324 | .tags(vec![ |
| 270 | Tag::identifier(identifier), | 325 | Tag::identifier(identifier.as_str()), |
| 271 | Tag::custom( | 326 | Tag::custom( |
| 272 | TagKind::custom("clone"), | 327 | TagKind::custom("clone"), |
| 273 | vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], | 328 | vec![format!( |
| 329 | "http://{}/{}/{}.git", | ||
| 330 | relay_a.domain(), | ||
| 331 | m_npub, | ||
| 332 | identifier | ||
| 333 | )], | ||
| 274 | ), | 334 | ), |
| 275 | Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), | 335 | Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), |
| 276 | ]) | 336 | ]) |
| 277 | .sign_with_keys(maintainer_keys) | 337 | .sign_with_keys(maintainer_keys) |
| 278 | .unwrap(); | 338 | .unwrap(); |
| 339 | send_to_relay(&relay_a, &announcement).await.unwrap(); | ||
| 340 | // Use push_unique_git_data_to_relay so each maintainer gets a distinct commit | ||
| 341 | // hash. Identical hashes cause git to skip pack transfer when the object | ||
| 342 | // already exists on the server, leaving the announcement in purgatory. | ||
| 343 | let git_dir = push_unique_git_data_to_relay( | ||
| 344 | &relay_a, | ||
| 345 | maintainer_keys, | ||
| 346 | identifier, | ||
| 347 | &[&relay_a.domain()], | ||
| 348 | &m_npub, | ||
| 349 | ) | ||
| 350 | .await; | ||
| 351 | git_dirs.push(git_dir); | ||
| 352 | } | ||
| 353 | println!("✓ Three maintainer announcements + git data pushed to relay_a"); | ||
| 279 | 354 | ||
| 280 | client_a.send_event(&announcement).await.unwrap(); | 355 | // Confirm all three announcements are queryable on relay_a before starting relay_b. |
| 356 | // This eliminates the race between relay_a's DB writes and relay_b's initial negentropy sync. | ||
| 357 | for (name, keys) in [ | ||
| 358 | ("maintainer1", &maintainer1_keys), | ||
| 359 | ("maintainer2", &maintainer2_keys), | ||
| 360 | ("maintainer3", &maintainer3_keys), | ||
| 361 | ] { | ||
| 362 | let filter = Filter::new() | ||
| 363 | .kind(Kind::GitRepoAnnouncement) | ||
| 364 | .author(keys.public_key()) | ||
| 365 | .identifier(identifier); | ||
| 366 | let found = | ||
| 367 | wait_for_event_on_relay(relay_a.url(), filter, Duration::from_secs(10)).await; | ||
| 368 | assert!(found, "{} announcement should be in relay_a before starting relay_b", name); | ||
| 281 | } | 369 | } |
| 282 | println!("✓ Three maintainer announcements sent to relay_a"); | 370 | println!("✓ All three maintainer announcements confirmed in relay_a's DB"); |
| 283 | 371 | ||
| 284 | // Step 2: Send owner announcement to relay_b (lists relay_a + all three maintainers) | 372 | // Step 2: Start relay_b with relay_a as bootstrap so its SyncManager connects immediately. |
| 285 | let client_b = TestClient::new(relay_b.url(), owner_keys.clone()) | 373 | // Because all three maintainer announcements are confirmed in relay_a's DB, relay_b's |
| 286 | .await | 374 | // initial negentropy sync will pick them all up and reject them (no owner announcement |
| 287 | .expect("Failed to connect to relay_b"); | 375 | // in relay_b's DB yet), storing them in the hot cache. |
| 376 | let relay_b = TestRelay::start_with_sync(Some(relay_a.url().to_string())).await; | ||
| 377 | println!("relay_b started at {}", relay_b.url()); | ||
| 378 | |||
| 379 | // Give relay_b's SyncManager time to complete the initial negentropy sync with relay_a. | ||
| 380 | // The negentropy sync completes within ~200ms (NGIT_TEST=1 sets batch window to 200ms), but we | ||
| 381 | // allow extra time for slow CI environments. | ||
| 382 | tokio::time::sleep(Duration::from_secs(3)).await; | ||
| 383 | println!("✓ relay_b synced from relay_a (maintainer announcements should be in hot cache)"); | ||
| 384 | |||
| 385 | // Step 3: Send owner announcement to relay_b → goes to purgatory. | ||
| 386 | let owner_npub = owner_keys | ||
| 387 | .public_key() | ||
| 388 | .to_bech32() | ||
| 389 | .expect("Failed to get npub"); | ||
| 288 | 390 | ||
| 289 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") | 391 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 290 | .tags(vec![ | 392 | .tags(vec![ |
| 291 | Tag::identifier(identifier), | 393 | Tag::identifier(identifier), |
| 292 | Tag::custom( | 394 | Tag::custom( |
| 293 | TagKind::custom("clone"), | 395 | TagKind::custom("clone"), |
| 294 | vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], | 396 | vec![format!( |
| 397 | "http://{}/{}/{}.git", | ||
| 398 | relay_b.domain(), | ||
| 399 | owner_npub, | ||
| 400 | identifier | ||
| 401 | )], | ||
| 295 | ), | 402 | ), |
| 296 | Tag::custom( | 403 | Tag::custom( |
| 297 | TagKind::custom("relays"), | 404 | TagKind::custom("relays"), |
| @@ -309,13 +416,20 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 309 | .sign_with_keys(&owner_keys) | 416 | .sign_with_keys(&owner_keys) |
| 310 | .unwrap(); | 417 | .unwrap(); |
| 311 | 418 | ||
| 312 | client_b.send_event(&owner_announcement).await.unwrap(); | 419 | send_to_relay(&relay_b, &owner_announcement).await.unwrap(); |
| 313 | println!("✓ Owner announcement sent to relay_b"); | 420 | println!("✓ Owner announcement sent to relay_b (now in purgatory)"); |
| 314 | 421 | ||
| 315 | // Step 3: Wait for sync and re-processing | 422 | // Step 4: Push owner git data to relay_b. |
| 316 | tokio::time::sleep(Duration::from_secs(3)).await; | 423 | // This promotes the owner announcement from purgatory and triggers hot-cache |
| 424 | // re-processing for all three maintainer announcements. | ||
| 425 | let _git_dir_owner = | ||
| 426 | push_git_data_to_relay(&relay_b, &owner_keys, identifier, &[&relay_b.domain()]).await; | ||
| 427 | println!("✓ Owner git data pushed to relay_b (hot-cache re-processing should fire)"); | ||
| 428 | |||
| 429 | // Step 5: Wait briefly for async processing to complete. | ||
| 430 | tokio::time::sleep(Duration::from_secs(1)).await; | ||
| 317 | 431 | ||
| 318 | // Step 4: Verify all four announcements are in relay_b's database | 432 | // Step 6: Verify all four announcements are in relay_b's database. |
| 319 | for (name, keys) in [ | 433 | for (name, keys) in [ |
| 320 | ("owner", &owner_keys), | 434 | ("owner", &owner_keys), |
| 321 | ("maintainer1", &maintainer1_keys), | 435 | ("maintainer1", &maintainer1_keys), |
| @@ -333,8 +447,6 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 333 | 447 | ||
| 334 | println!("✅ All three maintainer announcements re-processed successfully"); | 448 | println!("✅ All three maintainer announcements re-processed successfully"); |
| 335 | 449 | ||
| 336 | client_a.disconnect().await; | ||
| 337 | client_b.disconnect().await; | ||
| 338 | relay_a.stop().await; | 450 | relay_a.stop().await; |
| 339 | relay_b.stop().await; | 451 | relay_b.stop().await; |
| 340 | } | 452 | } |
| @@ -342,10 +454,10 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 342 | /// Test that invalid maintainer public keys don't cause panics | 454 | /// Test that invalid maintainer public keys don't cause panics |
| 343 | /// | 455 | /// |
| 344 | /// Flow: | 456 | /// Flow: |
| 345 | /// 1. Maintainer announcement arrives → Rejected | 457 | /// 1. Maintainer announcement arrives → Rejected (doesn't list our relay) |
| 346 | /// 2. Owner announcement arrives with INVALID maintainer hex → Should handle gracefully | 458 | /// 2. Owner announcement + git push → accepted, with INVALID maintainer hex in maintainers tag |
| 347 | /// 3. Owner announcement should still be accepted | 459 | /// 3. Owner announcement should be accepted |
| 348 | /// 4. Maintainer announcement should NOT be re-processed (invalid pubkey) | 460 | /// 4. Maintainer announcement should NOT be re-processed (invalid pubkey can't be parsed) |
| 349 | #[tokio::test] | 461 | #[tokio::test] |
| 350 | async fn test_invalid_maintainer_pubkey_handled_gracefully() { | 462 | async fn test_invalid_maintainer_pubkey_handled_gracefully() { |
| 351 | let relay = TestRelay::start().await; | 463 | let relay = TestRelay::start().await; |
| @@ -382,13 +494,25 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() { | |||
| 382 | let _ = client.send_event(&maintainer_announcement).await; | 494 | let _ = client.send_event(&maintainer_announcement).await; |
| 383 | tokio::time::sleep(Duration::from_millis(200)).await; | 495 | tokio::time::sleep(Duration::from_millis(200)).await; |
| 384 | 496 | ||
| 385 | // Step 2: Send owner announcement with INVALID maintainer hex | 497 | // Step 2: Send owner announcement with INVALID maintainer hex, then push git data. |
| 498 | // The announcement goes to purgatory first; the git push promotes it. | ||
| 499 | // The invalid maintainer hex should be handled gracefully (no panic). | ||
| 500 | let owner_npub = owner_keys | ||
| 501 | .public_key() | ||
| 502 | .to_bech32() | ||
| 503 | .expect("Failed to get npub"); | ||
| 504 | |||
| 386 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") | 505 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 387 | .tags(vec![ | 506 | .tags(vec![ |
| 388 | Tag::identifier(identifier), | 507 | Tag::identifier(identifier), |
| 389 | Tag::custom( | 508 | Tag::custom( |
| 390 | TagKind::custom("clone"), | 509 | TagKind::custom("clone"), |
| 391 | vec![format!("https://{}/{}.git", relay.domain(), identifier)], | 510 | vec![format!( |
| 511 | "http://{}/{}/{}.git", | ||
| 512 | relay.domain(), | ||
| 513 | owner_npub, | ||
| 514 | identifier | ||
| 515 | )], | ||
| 392 | ), | 516 | ), |
| 393 | Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]), | 517 | Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]), |
| 394 | Tag::custom( | 518 | Tag::custom( |
| @@ -399,7 +523,9 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() { | |||
| 399 | .sign_with_keys(&owner_keys) | 523 | .sign_with_keys(&owner_keys) |
| 400 | .unwrap(); | 524 | .unwrap(); |
| 401 | 525 | ||
| 402 | client.send_event(&owner_announcement).await.unwrap(); | 526 | send_to_relay(&relay, &owner_announcement).await.unwrap(); |
| 527 | let _git_dir = | ||
| 528 | push_git_data_to_relay(&relay, &owner_keys, identifier, &[&relay.domain()]).await; | ||
| 403 | tokio::time::sleep(Duration::from_millis(500)).await; | 529 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 404 | 530 | ||
| 405 | // Step 3: Verify owner announcement accepted, maintainer not re-processed | 531 | // Step 3: Verify owner announcement accepted, maintainer not re-processed |