upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/sync/maintainer_reprocessing.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-23 15:41:32 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-23 15:41:32 +0000
commitc54ce061d6d278cce8362d5af085808ca60c239b (patch)
treeec967d6195d9f7ec4f061449596611afe3a0950f /tests/sync/maintainer_reprocessing.rs
parente0ad39a489b3398f8208713bf728db0cb11475b0 (diff)
parent113928aa84894ea8f65c247d9987527e792b32a9 (diff)
feat: announcement purgatory
Extends purgatory to hold repository announcements until git data arrives, preventing empty repositories from being served to clients. When an announcement is received, a bare repo is created immediately and the announcement is held in purgatory. It is only promoted and served once a git push confirms real content exists. If no push arrives before expiry, the bare repo is deleted and the announcement is silently discarded. Key behaviours: - Soft expiry: announcements are hidden from clients but kept alive while git pushes are in progress, reviving on successful push - Expiry is extended when a matching state event or git push is observed - NIP-09 deletion events remove announcements from purgatory - Purgatory state (announcements, state events, PR events, expired set) is persisted to disk on graceful shutdown and restored on startup, with elapsed downtime subtracted from expiry deadlines - Purgatory announcements drive StateOnly sync in the sync system so state events are fetched from listed relays before promotion - SyncLevel added to RepoSyncIndex to distinguish purgatory repos (StateOnly) from promoted repos (Full L2+L3 sync)
Diffstat (limited to 'tests/sync/maintainer_reprocessing.rs')
-rw-r--r--tests/sync/maintainer_reprocessing.rs278
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
6use std::time::Duration; 25use std::time::Duration;
7 26
@@ -9,66 +28,91 @@ use nostr_sdk::prelude::*;
9 28
10use crate::common::{sync_helpers::*, TestRelay}; 29use 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]
23async fn test_maintainer_announcement_reprocessed_immediately() { 43async 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]
239async fn test_multiple_maintainers_all_reprocessed() { 290async 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]
350async fn test_invalid_maintainer_pubkey_handled_gracefully() { 462async 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