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.rs243
1 files changed, 239 insertions, 4 deletions
diff --git a/tests/purgatory_sync.rs b/tests/purgatory_sync.rs
index bb99f46..0b4d864 100644
--- a/tests/purgatory_sync.rs
+++ b/tests/purgatory_sync.rs
@@ -28,10 +28,10 @@
28mod common; 28mod common;
29 29
30use common::{ 30use common::{
31 build_repo_coord, check_ref_at_commit, create_pr_event, create_repo_announcement, 31 add_commit_to_repo, build_repo_coord, check_ref_at_commit, create_pr_event,
32 create_state_event, create_test_repo_with_commit, push_ref_to_relay, push_to_relay, 32 create_repo_announcement, create_state_event, create_test_repo_with_commit, push_ref_to_relay,
33 verify_event_not_served, wait_for_event_served, wait_for_sync_connection, CommitVariant, 33 push_to_relay, verify_event_not_served, wait_for_event_served, wait_for_sync_connection,
34 TestRelay, 34 CommitVariant, TestRelay,
35}; 35};
36use nostr_sdk::prelude::*; 36use nostr_sdk::prelude::*;
37use std::time::Duration; 37use std::time::Duration;
@@ -439,3 +439,238 @@ async fn test_pr_event_syncs_from_remote() {
439 syncing_relay.stop().await; 439 syncing_relay.stop().await;
440 source_relay.stop().await; 440 source_relay.stop().await;
441} 441}
442
443/// Test that concurrent state and PR events for the same repository
444/// both sync correctly.
445///
446/// Scenario:
447/// 1. Start source relay with repo containing two commits (main branch + PR commit)
448/// 2. Create and push both commits to source relay
449/// 3. Send both state event and PR event to source relay
450/// 4. Start syncing relay
451/// 5. Wait for sync to fetch git data and release both events
452/// 6. Verify both state event and PR event are served
453/// 7. Verify refs are correct for both (main branch and refs/nostr/<event-id>)
454#[tokio::test]
455async fn test_concurrent_state_and_pr_sync() {
456 // 1. Start source relay
457 let source_relay = TestRelay::start().await;
458 let owner_keys = Keys::generate();
459 let pr_author_keys = Keys::generate();
460 let identifier = "concurrent-sync-test-repo";
461
462 // Pre-allocate syncing relay port so we can include it in announcement
463 let syncing_port = TestRelay::find_free_port();
464 let syncing_domain = format!("127.0.0.1:{}", syncing_port);
465
466 // 2. Create test repository with two commits
467 // First commit establishes the repo, second commit is used for both state and PR events
468 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)
470 .expect("Failed to create test repo");
471
472 // Add second commit - this becomes HEAD of main and is referenced by both events
473 // In a real scenario, the state event would reference the current branch state,
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");
477
478 let npub = owner_keys
479 .public_key()
480 .to_bech32()
481 .expect("Failed to get npub");
482
483 // 3. Create and send announcement listing BOTH relays
484 let announcement = create_repo_announcement(
485 &owner_keys,
486 &[&source_relay.domain(), &syncing_domain],
487 identifier,
488 );
489
490 let source_client = Client::new(owner_keys.clone());
491 source_client
492 .add_relay(source_relay.url())
493 .await
494 .expect("Failed to add source relay");
495 source_client.connect().await;
496
497 // Wait for connection
498 tokio::time::sleep(Duration::from_millis(500)).await;
499
500 // Send announcement to source relay (creates bare repo)
501 source_client
502 .send_event(&announcement)
503 .await
504 .expect("Failed to send announcement to source");
505
506 tokio::time::sleep(Duration::from_millis(200)).await;
507
508 // 4. Create state event referencing the HEAD commit (pr_commit)
509 // After add_commit_to_repo, main points to pr_commit (which includes state_commit in history)
510 let clone_urls = [
511 format!(
512 "http://{}/{}/{}.git",
513 source_relay.domain(),
514 npub,
515 identifier
516 ),
517 format!("http://{}/{}/{}.git", syncing_domain, npub, identifier),
518 ];
519 let relay_urls = [
520 source_relay.url().to_string(),
521 format!("ws://{}", syncing_domain),
522 ];
523
524 // State event references main at head_commit (the current HEAD)
525 let state_event = create_state_event(
526 &owner_keys,
527 identifier,
528 &[("main", &head_commit)],
529 &[],
530 &[&clone_urls[0], &clone_urls[1]],
531 &[&relay_urls[0], &relay_urls[1]],
532 )
533 .expect("Failed to create state event");
534
535 let state_event_id = state_event.id;
536
537 // Send state event to source relay (goes to purgatory - no git data yet)
538 source_client
539 .send_event(&state_event)
540 .await
541 .expect("Failed to send state event to source");
542
543 // 5. Create PR event referencing the same commit (head_commit)
544 // This simulates a PR that proposes the changes in head_commit
545 let repo_coord = build_repo_coord(&owner_keys, identifier);
546
547 let pr_event = create_pr_event(
548 &pr_author_keys,
549 &repo_coord,
550 &head_commit,
551 "Test PR for concurrent sync",
552 )
553 .expect("Failed to create PR event");
554
555 let pr_event_id = pr_event.id;
556
557 // Send PR event to source relay using PR author's client
558 let pr_client = Client::new(pr_author_keys.clone());
559 pr_client
560 .add_relay(source_relay.url())
561 .await
562 .expect("Failed to add source relay for PR");
563 pr_client.connect().await;
564 tokio::time::sleep(Duration::from_millis(500)).await;
565
566 pr_client
567 .send_event(&pr_event)
568 .await
569 .expect("Failed to send PR event to source");
570
571 tokio::time::sleep(Duration::from_millis(200)).await;
572
573 // 6. Push git data to source relay
574 // Push all branches (main contains both commits due to linear history)
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());
580 push_ref_to_relay(
581 temp_dir.path(),
582 &source_relay.domain(),
583 &npub,
584 identifier,
585 &head_commit,
586 &pr_ref_name,
587 )
588 .expect("Push PR ref to source should succeed");
589
590 // After push, both events should 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))
596 .await
597 .expect("PR event should be served on source relay after push");
598
599 // 7. Start syncing relay (syncs from source)
600 let syncing_relay = TestRelay::start_on_port_with_options(
601 syncing_port,
602 Some(source_relay.url().to_string()),
603 false,
604 )
605 .await;
606
607 // Wait for sync connection to establish
608 wait_for_sync_connection(syncing_relay.url(), 1, Duration::from_secs(5))
609 .await
610 .expect("Sync connection should establish");
611
612 // 8. Wait for BOTH events to be released on syncing relay
613 // The sync should fetch git data and release both events
614 let state_found = wait_for_event_served(
615 syncing_relay.url(),
616 &state_event_id,
617 Duration::from_secs(30),
618 )
619 .await;
620
621 assert!(
622 state_found.is_ok(),
623 "State event should be served after sync fetches git data: {:?}",
624 state_found.err()
625 );
626
627 let pr_found = wait_for_event_served(syncing_relay.url(), &pr_event_id, Duration::from_secs(30))
628 .await;
629
630 assert!(
631 pr_found.is_ok(),
632 "PR event should be served after sync fetches git data: {:?}",
633 pr_found.err()
634 );
635
636 // 9. Verify refs are correct on syncing relay
637 // Check main branch points to head_commit (the HEAD)
638 let main_ref_correct = check_ref_at_commit(
639 &syncing_domain,
640 &npub,
641 identifier,
642 "refs/heads/main",
643 &head_commit,
644 )
645 .await
646 .expect("Failed to check main ref");
647
648 assert!(
649 main_ref_correct,
650 "main branch should point to HEAD commit ({})",
651 head_commit
652 );
653
654 // Check refs/nostr/<event-id> points to the same commit
655 let pr_ref_correct = check_ref_at_commit(
656 &syncing_domain,
657 &npub,
658 identifier,
659 &pr_ref_name,
660 &head_commit,
661 )
662 .await
663 .expect("Failed to check PR ref");
664
665 assert!(
666 pr_ref_correct,
667 "refs/nostr/<event-id> should point to commit ({})",
668 head_commit
669 );
670
671 // Cleanup
672 source_client.disconnect().await;
673 pr_client.disconnect().await;
674 syncing_relay.stop().await;
675 source_relay.stop().await;
676}