upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/purgatory_sync.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/purgatory_sync.rs')
-rw-r--r--tests/purgatory_sync.rs209
1 files changed, 134 insertions, 75 deletions
diff --git a/tests/purgatory_sync.rs b/tests/purgatory_sync.rs
index 72f3d81..304865d 100644
--- a/tests/purgatory_sync.rs
+++ b/tests/purgatory_sync.rs
@@ -282,15 +282,20 @@ async fn test_state_event_syncs_from_remote() {
282/// Test that a PR event entering purgatory triggers remote commit fetch 282/// Test that a PR event entering purgatory triggers remote commit fetch
283/// and is released once the commit is available. 283/// and is released once the commit is available.
284/// 284///
285/// Scenario: 285/// Flow on source relay:
286/// 1. Start source relay with repository announcement 286/// 1. Send announcement → purgatory (StateOnly - no git data yet)
287/// 2. Create PR event (goes to purgatory - no git data yet) 287/// 2. Send state event → purgatory (refs point to non-existent commits)
288/// 3. Push commit to refs/nostr/<event-id> (authorized by PR event in purgatory) 288/// 3. Push git data → promotes announcement to Full + releases state event
289/// 4. PR event gets released from purgatory on source relay 289/// 4. Send PR event → purgatory (announcement now Full, so PR events accepted)
290/// 5. Start syncing relay 290/// 5. Push PR commit → releases PR event
291/// 6. Syncing relay syncs PR event (goes to purgatory - no local git data) 291///
292/// 7. Syncing relay fetches commit from source's clone URL 292/// Flow on syncing relay:
293/// 8. Verify PR event is released and refs/nostr/<event-id> created on syncing relay 293/// 6. Start syncing relay
294/// 7. Syncs announcement → purgatory (StateOnly)
295/// 8. Syncs state event → purgatory
296/// 9. Fetches git data → promotes announcement (Full) + releases state event
297/// 10. Syncs PR event → purgatory (announcement now Full)
298/// 11. Fetches PR commit → releases PR event
294#[tokio::test] 299#[tokio::test]
295async fn test_pr_event_syncs_from_remote() { 300async fn test_pr_event_syncs_from_remote() {
296 // 1. Start source relay 301 // 1. Start source relay
@@ -313,8 +318,7 @@ async fn test_pr_event_syncs_from_remote() {
313 .to_bech32() 318 .to_bech32()
314 .expect("Failed to get npub"); 319 .expect("Failed to get npub");
315 320
316 // 3. Create and send announcement listing BOTH relays 321 // 3. Create announcement listing BOTH relays
317 // This ensures the syncing relay will accept the PR event when it syncs
318 let announcement = create_repo_announcement( 322 let announcement = create_repo_announcement(
319 &owner_keys, 323 &owner_keys,
320 &[&source_relay.domain(), &syncing_domain], 324 &[&source_relay.domain(), &syncing_domain],
@@ -331,7 +335,7 @@ async fn test_pr_event_syncs_from_remote() {
331 // Wait for connection 335 // Wait for connection
332 tokio::time::sleep(Duration::from_millis(500)).await; 336 tokio::time::sleep(Duration::from_millis(500)).await;
333 337
334 // Send announcement to source relay (creates bare repo) 338 // Step 1: Send announcement to source relay → purgatory (StateOnly)
335 source_client 339 source_client
336 .send_event(&announcement) 340 .send_event(&announcement)
337 .await 341 .await
@@ -339,8 +343,52 @@ async fn test_pr_event_syncs_from_remote() {
339 343
340 tokio::time::sleep(Duration::from_millis(200)).await; 344 tokio::time::sleep(Duration::from_millis(200)).await;
341 345
342 // 4. Create and send PR event BEFORE pushing 346 // Step 2: Create and send state event → purgatory (no git data yet)
343 // The PR event goes to purgatory on source relay, which authorizes the push 347 let clone_urls = [
348 format!(
349 "http://{}/{}/{}.git",
350 source_relay.domain(),
351 npub,
352 identifier
353 ),
354 format!("http://{}/{}/{}.git", syncing_domain, npub, identifier),
355 ];
356 let relay_urls = [
357 source_relay.url().to_string(),
358 format!("ws://{}", syncing_domain),
359 ];
360
361 let state_event = create_state_event(
362 &owner_keys,
363 identifier,
364 &[("main", &commit_hash)],
365 &[],
366 &[&clone_urls[0], &clone_urls[1]],
367 &[&relay_urls[0], &relay_urls[1]],
368 )
369 .expect("Failed to create state event");
370
371 let state_event_id = state_event.id;
372
373 source_client
374 .send_event(&state_event)
375 .await
376 .expect("Failed to send state event to source");
377
378 tokio::time::sleep(Duration::from_millis(200)).await;
379
380 // Step 3: Push git data to source relay
381 // This promotes the announcement from StateOnly to Full AND releases state event
382 push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier)
383 .expect("Push to source should succeed");
384
385 // Wait for state event to be released from purgatory on source relay
386 wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5))
387 .await
388 .expect("State event should be served on source relay after push");
389
390 // Step 4: Create and send PR event → purgatory
391 // NOW the announcement is promoted (Full), so PR events are accepted
344 let repo_coord = build_repo_coord(&owner_keys, identifier); 392 let repo_coord = build_repo_coord(&owner_keys, identifier);
345 393
346 let pr_event = create_pr_event( 394 let pr_event = create_pr_event(
@@ -367,11 +415,10 @@ async fn test_pr_event_syncs_from_remote() {
367 .await 415 .await
368 .expect("Failed to send PR event to source"); 416 .expect("Failed to send PR event to source");
369 417
370 // Small delay to ensure PR event is processed into purgatory
371 tokio::time::sleep(Duration::from_millis(200)).await; 418 tokio::time::sleep(Duration::from_millis(200)).await;
372 419
373 // 5. Push commit to refs/nostr/<event-id> on source relay 420 // Step 5: Push PR commit to refs/nostr/<event-id> on source relay
374 // The PR event in purgatory authorizes this push 421 // This releases the PR event from purgatory
375 let ref_name = format!("refs/nostr/{}", pr_event_id.to_hex()); 422 let ref_name = format!("refs/nostr/{}", pr_event_id.to_hex());
376 push_ref_to_relay( 423 push_ref_to_relay(
377 temp_dir.path(), 424 temp_dir.path(),
@@ -383,12 +430,12 @@ async fn test_pr_event_syncs_from_remote() {
383 ) 430 )
384 .expect("Push to refs/nostr/<event-id> should succeed"); 431 .expect("Push to refs/nostr/<event-id> should succeed");
385 432
386 // After push, PR event should be released from purgatory on source relay 433 // Wait for PR event to be released from purgatory on source relay
387 wait_for_event_served(source_relay.url(), &pr_event_id, Duration::from_secs(5)) 434 wait_for_event_served(source_relay.url(), &pr_event_id, Duration::from_secs(5))
388 .await 435 .await
389 .expect("PR event should be served on source relay after push"); 436 .expect("PR event should be served on source relay after push");
390 437
391 // 6. Start syncing relay (syncs from source) 438 // Step 6: Start syncing relay (syncs from source)
392 let syncing_relay = TestRelay::start_on_port_with_options( 439 let syncing_relay = TestRelay::start_on_port_with_options(
393 syncing_port, 440 syncing_port,
394 Some(source_relay.url().to_string()), 441 Some(source_relay.url().to_string()),
@@ -401,14 +448,13 @@ async fn test_pr_event_syncs_from_remote() {
401 .await 448 .await
402 .expect("Sync connection should establish"); 449 .expect("Sync connection should establish");
403 450
404 // 7. Wait for PR event to be released on syncing relay 451 // Steps 7-11: Syncing relay syncs events
405 // The sync should: 452 // The sync should:
406 // a) Fetch the announcement and PR event from source relay 453 // a) Sync announcement → purgatory (StateOnly)
407 // b) Accept announcement (creates bare repo structure) 454 // b) Sync state event → purgatory
408 // c) Put PR event in purgatory (commit missing on syncing relay) 455 // c) Fetch git data → promotes announcement (Full) + releases state event
409 // d) Fetch commit from source relay's clone URL 456 // d) Sync PR event → purgatory (announcement now Full)
410 // e) Release the PR event from purgatory 457 // e) Fetch PR commit → releases PR event
411 // f) Create refs/nostr/<event-id> pointing to the commit
412 let found = wait_for_event_served( 458 let found = wait_for_event_served(
413 syncing_relay.url(), 459 syncing_relay.url(),
414 &pr_event_id, 460 &pr_event_id,
@@ -422,7 +468,7 @@ async fn test_pr_event_syncs_from_remote() {
422 found.err() 468 found.err()
423 ); 469 );
424 470
425 // 8. Verify refs/nostr/<event-id> was created on syncing relay 471 // Verify refs/nostr/<event-id> was created on syncing relay
426 let ref_correct = 472 let ref_correct =
427 check_ref_at_commit(&syncing_domain, &npub, identifier, &ref_name, &commit_hash) 473 check_ref_at_commit(&syncing_domain, &npub, identifier, &ref_name, &commit_hash)
428 .await 474 .await
@@ -443,14 +489,20 @@ async fn test_pr_event_syncs_from_remote() {
443/// Test that concurrent state and PR events for the same repository 489/// Test that concurrent state and PR events for the same repository
444/// both sync correctly. 490/// both sync correctly.
445/// 491///
446/// Scenario: 492/// Flow on source relay:
447/// 1. Start source relay with repo containing two commits (main branch + PR commit) 493/// 1. Send announcement → purgatory (StateOnly - no git data yet)
448/// 2. Create and push both commits to source relay 494/// 2. Send state event → purgatory (refs point to non-existent commits)
449/// 3. Send both state event and PR event to source relay 495/// 3. Push git data → promotes announcement to Full + releases state event
450/// 4. Start syncing relay 496/// 4. THEN send PR event → purgatory (announcement now Full, so PR events accepted)
451/// 5. Wait for sync to fetch git data and release both events 497/// 5. Push PR commit → releases PR event
452/// 6. Verify both state event and PR event are served 498///
453/// 7. Verify refs are correct for both (main branch and refs/nostr/<event-id>) 499/// Flow on syncing relay:
500/// 6. Start syncing relay
501/// 7. Syncs announcement → purgatory (StateOnly)
502/// 8. Syncs state event → purgatory
503/// 9. Fetches git data → promotes announcement (Full) + releases state event
504/// 10. Syncs PR event → purgatory (announcement now Full)
505/// 11. Fetches PR commit → releases PR event
454#[tokio::test] 506#[tokio::test]
455async fn test_concurrent_state_and_pr_sync() { 507async fn test_concurrent_state_and_pr_sync() {
456 // 1. Start source relay 508 // 1. Start source relay
@@ -464,15 +516,13 @@ async fn test_concurrent_state_and_pr_sync() {
464 let syncing_domain = format!("127.0.0.1:{}", syncing_port); 516 let syncing_domain = format!("127.0.0.1:{}", syncing_port);
465 517
466 // 2. Create test repository with two commits 518 // 2. Create test repository with two commits
467 // First commit establishes the repo, second commit is used for both state and PR events 519 // First commit establishes the repo (for state event), second commit is for PR
468 let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); 520 let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
469 let _first_commit = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest) 521 let _state_commit = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest)
470 .expect("Failed to create test repo"); 522 .expect("Failed to create test repo");
471 523
472 // Add second commit - this becomes HEAD of main and is referenced by both events 524 // Add second commit - this is used for the PR event
473 // In a real scenario, the state event would reference the current branch state, 525 let pr_commit =
474 // and the PR would propose changes (which happen to be the same commit here for simplicity)
475 let head_commit =
476 add_commit_to_repo(temp_dir.path(), CommitVariant::PrTest).expect("Failed to add commit"); 526 add_commit_to_repo(temp_dir.path(), CommitVariant::PrTest).expect("Failed to add commit");
477 527
478 let npub = owner_keys 528 let npub = owner_keys
@@ -480,7 +530,7 @@ async fn test_concurrent_state_and_pr_sync() {
480 .to_bech32() 530 .to_bech32()
481 .expect("Failed to get npub"); 531 .expect("Failed to get npub");
482 532
483 // 3. Create and send announcement listing BOTH relays 533 // 3. Create announcement listing BOTH relays
484 let announcement = create_repo_announcement( 534 let announcement = create_repo_announcement(
485 &owner_keys, 535 &owner_keys,
486 &[&source_relay.domain(), &syncing_domain], 536 &[&source_relay.domain(), &syncing_domain],
@@ -497,7 +547,7 @@ async fn test_concurrent_state_and_pr_sync() {
497 // Wait for connection 547 // Wait for connection
498 tokio::time::sleep(Duration::from_millis(500)).await; 548 tokio::time::sleep(Duration::from_millis(500)).await;
499 549
500 // Send announcement to source relay (creates bare repo) 550 // Step 1: Send announcement to source relay → purgatory (StateOnly)
501 source_client 551 source_client
502 .send_event(&announcement) 552 .send_event(&announcement)
503 .await 553 .await
@@ -505,8 +555,7 @@ async fn test_concurrent_state_and_pr_sync() {
505 555
506 tokio::time::sleep(Duration::from_millis(200)).await; 556 tokio::time::sleep(Duration::from_millis(200)).await;
507 557
508 // 4. Create state event referencing the HEAD commit (pr_commit) 558 // Step 2: Create and send state event → purgatory (no git data yet)
509 // After add_commit_to_repo, main points to pr_commit (which includes state_commit in history)
510 let clone_urls = [ 559 let clone_urls = [
511 format!( 560 format!(
512 "http://{}/{}/{}.git", 561 "http://{}/{}/{}.git",
@@ -521,11 +570,13 @@ async fn test_concurrent_state_and_pr_sync() {
521 format!("ws://{}", syncing_domain), 570 format!("ws://{}", syncing_domain),
522 ]; 571 ];
523 572
524 // State event references main at head_commit (the current HEAD) 573 // State event references main at pr_commit (HEAD after add_commit_to_repo).
574 // push_to_relay uses `git push --all` which pushes main -> pr_commit (HEAD),
575 // so the state event must reference pr_commit for push validation to succeed.
525 let state_event = create_state_event( 576 let state_event = create_state_event(
526 &owner_keys, 577 &owner_keys,
527 identifier, 578 identifier,
528 &[("main", &head_commit)], 579 &[("main", &pr_commit)],
529 &[], 580 &[],
530 &[&clone_urls[0], &clone_urls[1]], 581 &[&clone_urls[0], &clone_urls[1]],
531 &[&relay_urls[0], &relay_urls[1]], 582 &[&relay_urls[0], &relay_urls[1]],
@@ -534,20 +585,31 @@ async fn test_concurrent_state_and_pr_sync() {
534 585
535 let state_event_id = state_event.id; 586 let state_event_id = state_event.id;
536 587
537 // Send state event to source relay (goes to purgatory - no git data yet)
538 source_client 588 source_client
539 .send_event(&state_event) 589 .send_event(&state_event)
540 .await 590 .await
541 .expect("Failed to send state event to source"); 591 .expect("Failed to send state event to source");
542 592
543 // 5. Create PR event referencing the same commit (head_commit) 593 tokio::time::sleep(Duration::from_millis(200)).await;
544 // This simulates a PR that proposes the changes in head_commit 594
595 // Step 3: Push git data to source relay
596 // This promotes the announcement from StateOnly to Full AND releases state event
597 push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier)
598 .expect("Push to source should succeed");
599
600 // Wait for state event to be released from purgatory on source relay
601 wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5))
602 .await
603 .expect("State event should be served on source relay after push");
604
605 // Step 4: Create and send PR event → purgatory
606 // NOW the announcement is promoted (Full), so PR events are accepted
545 let repo_coord = build_repo_coord(&owner_keys, identifier); 607 let repo_coord = build_repo_coord(&owner_keys, identifier);
546 608
547 let pr_event = create_pr_event( 609 let pr_event = create_pr_event(
548 &pr_author_keys, 610 &pr_author_keys,
549 &repo_coord, 611 &repo_coord,
550 &head_commit, 612 &pr_commit,
551 "Test PR for concurrent sync", 613 "Test PR for concurrent sync",
552 ) 614 )
553 .expect("Failed to create PR event"); 615 .expect("Failed to create PR event");
@@ -570,33 +632,25 @@ async fn test_concurrent_state_and_pr_sync() {
570 632
571 tokio::time::sleep(Duration::from_millis(200)).await; 633 tokio::time::sleep(Duration::from_millis(200)).await;
572 634
573 // 6. Push git data to source relay 635 // Step 5: Push PR commit to refs/nostr/<event-id> on source relay
574 // Push all branches (main contains both commits due to linear history) 636 // This releases the PR event from purgatory
575 push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier)
576 .expect("Push to source should succeed");
577
578 // Also push the PR ref
579 let pr_ref_name = format!("refs/nostr/{}", pr_event_id.to_hex()); 637 let pr_ref_name = format!("refs/nostr/{}", pr_event_id.to_hex());
580 push_ref_to_relay( 638 push_ref_to_relay(
581 temp_dir.path(), 639 temp_dir.path(),
582 &source_relay.domain(), 640 &source_relay.domain(),
583 &npub, 641 &npub,
584 identifier, 642 identifier,
585 &head_commit, 643 &pr_commit,
586 &pr_ref_name, 644 &pr_ref_name,
587 ) 645 )
588 .expect("Push PR ref to source should succeed"); 646 .expect("Push PR ref to source should succeed");
589 647
590 // After push, both events should be released from purgatory on source relay 648 // Wait for PR event to be released from purgatory on source relay
591 wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5))
592 .await
593 .expect("State event should be served on source relay after push");
594
595 wait_for_event_served(source_relay.url(), &pr_event_id, Duration::from_secs(5)) 649 wait_for_event_served(source_relay.url(), &pr_event_id, Duration::from_secs(5))
596 .await 650 .await
597 .expect("PR event should be served on source relay after push"); 651 .expect("PR event should be served on source relay after push");
598 652
599 // 7. Start syncing relay (syncs from source) 653 // Step 6: Start syncing relay (syncs from source)
600 let syncing_relay = TestRelay::start_on_port_with_options( 654 let syncing_relay = TestRelay::start_on_port_with_options(
601 syncing_port, 655 syncing_port,
602 Some(source_relay.url().to_string()), 656 Some(source_relay.url().to_string()),
@@ -609,8 +663,13 @@ async fn test_concurrent_state_and_pr_sync() {
609 .await 663 .await
610 .expect("Sync connection should establish"); 664 .expect("Sync connection should establish");
611 665
612 // 8. Wait for BOTH events to be released on syncing relay 666 // Steps 7-11: Syncing relay syncs events
613 // The sync should fetch git data and release both events 667 // The sync should:
668 // a) Sync announcement → purgatory (StateOnly)
669 // b) Sync state event → purgatory
670 // c) Fetch git data → promotes announcement (Full) + releases state event
671 // d) Sync PR event → purgatory (announcement now Full)
672 // e) Fetch PR commit → releases PR event
614 let state_found = wait_for_event_served( 673 let state_found = wait_for_event_served(
615 syncing_relay.url(), 674 syncing_relay.url(),
616 &state_event_id, 675 &state_event_id,
@@ -629,18 +688,18 @@ async fn test_concurrent_state_and_pr_sync() {
629 688
630 assert!( 689 assert!(
631 pr_found.is_ok(), 690 pr_found.is_ok(),
632 "PR event should be served after sync fetches git data: {:?}", 691 "PR event should be served after sync fetches commit: {:?}",
633 pr_found.err() 692 pr_found.err()
634 ); 693 );
635 694
636 // 9. Verify refs are correct on syncing relay 695 // Verify refs are correct on syncing relay
637 // Check main branch points to head_commit (the HEAD) 696 // Check main branch points to pr_commit (HEAD after both commits)
638 let main_ref_correct = check_ref_at_commit( 697 let main_ref_correct = check_ref_at_commit(
639 &syncing_domain, 698 &syncing_domain,
640 &npub, 699 &npub,
641 identifier, 700 identifier,
642 "refs/heads/main", 701 "refs/heads/main",
643 &head_commit, 702 &pr_commit, // After push, main points to pr_commit (HEAD)
644 ) 703 )
645 .await 704 .await
646 .expect("Failed to check main ref"); 705 .expect("Failed to check main ref");
@@ -648,24 +707,24 @@ async fn test_concurrent_state_and_pr_sync() {
648 assert!( 707 assert!(
649 main_ref_correct, 708 main_ref_correct,
650 "main branch should point to HEAD commit ({})", 709 "main branch should point to HEAD commit ({})",
651 head_commit 710 pr_commit
652 ); 711 );
653 712
654 // Check refs/nostr/<event-id> points to the same commit 713 // Check refs/nostr/<event-id> points to pr_commit
655 let pr_ref_correct = check_ref_at_commit( 714 let pr_ref_correct = check_ref_at_commit(
656 &syncing_domain, 715 &syncing_domain,
657 &npub, 716 &npub,
658 identifier, 717 identifier,
659 &pr_ref_name, 718 &pr_ref_name,
660 &head_commit, 719 &pr_commit,
661 ) 720 )
662 .await 721 .await
663 .expect("Failed to check PR ref"); 722 .expect("Failed to check PR ref");
664 723
665 assert!( 724 assert!(
666 pr_ref_correct, 725 pr_ref_correct,
667 "refs/nostr/<event-id> should point to commit ({})", 726 "refs/nostr/<event-id> should point to PR commit ({})",
668 head_commit 727 pr_commit
669 ); 728 );
670 729
671 // Cleanup 730 // Cleanup