diff options
Diffstat (limited to 'tests/purgatory_sync.rs')
| -rw-r--r-- | tests/purgatory_sync.rs | 365 |
1 files changed, 151 insertions, 214 deletions
diff --git a/tests/purgatory_sync.rs b/tests/purgatory_sync.rs index 72f3d81..eefd6bc 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] |
| 295 | async fn test_pr_event_syncs_from_remote() { | 300 | async 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] |
| 455 | async fn test_concurrent_state_and_pr_sync() { | 507 | async 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 |
| @@ -921,162 +980,43 @@ async fn test_pr_event_clone_tag_sync_with_partial_oid_aggregation_from_multiple | |||
| 921 | .expect("PR event should be served on mock_relay immediately"); | 980 | .expect("PR event should be served on mock_relay immediately"); |
| 922 | 981 | ||
| 923 | // ======================================================================== | 982 | // ======================================================================== |
| 924 | // Step 5: Start syncing_relay WITHOUT bootstrap and publish announcement directly | 983 | // Step 5: Start syncing_relay with source_grasp as bootstrap |
| 925 | // ======================================================================== | 984 | // ======================================================================== |
| 926 | 985 | ||
| 927 | // Start syncing_relay with sync enabled but NO bootstrap relay | 986 | // Start syncing_relay with source_grasp as bootstrap relay. |
| 928 | // This tests relay discovery from announcement's `relays` tag | 987 | // Negentropy is disabled because MockRelay doesn't support NIP-77, and the |
| 929 | // Note: We disable negentropy because MockRelay doesn't support NIP-77, | 988 | // sync system doesn't properly fall back to REQ+EOSE when negentropy fails. |
| 930 | // and the sync system doesn't properly fall back to REQ+EOSE when negentropy fails. | 989 | // |
| 990 | // We do NOT publish the announcement directly to syncing_relay. Instead, | ||
| 991 | // syncing_relay discovers it via the bootstrap connection to source_grasp, | ||
| 992 | // which has the promoted announcement in its database. | ||
| 931 | let syncing_relay = TestRelay::start_on_port_with_options( | 993 | let syncing_relay = TestRelay::start_on_port_with_options( |
| 932 | syncing_port, | 994 | syncing_port, |
| 933 | None, // NO bootstrap - relay discovery via announcement tags | 995 | Some(source_grasp.url().to_string()), // Bootstrap from source_grasp |
| 934 | true, // Disable negentropy - MockRelay doesn't support NIP-77 | 996 | true, // Disable negentropy - MockRelay doesn't support NIP-77 |
| 935 | ) | 997 | ) |
| 936 | .await; | 998 | .await; |
| 937 | 999 | ||
| 938 | // Publish announcement DIRECTLY to syncing_relay | ||
| 939 | // This triggers relay discovery from the announcement's `relays` tag | ||
| 940 | let syncing_client = Client::new(owner_keys.clone()); | ||
| 941 | syncing_client | ||
| 942 | .add_relay(syncing_relay.url()) | ||
| 943 | .await | ||
| 944 | .expect("Failed to add syncing_relay"); | ||
| 945 | syncing_client.connect().await; | ||
| 946 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 947 | |||
| 948 | syncing_client | ||
| 949 | .send_event(&announcement) | ||
| 950 | .await | ||
| 951 | .expect("Failed to send announcement to syncing_relay"); | ||
| 952 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 953 | |||
| 954 | // Wait for relay discovery and sync connections to establish | ||
| 955 | // syncing_relay should discover source_grasp and mock_relay from announcement's relays tag | ||
| 956 | println!("=== Waiting for sync connections ==="); | ||
| 957 | println!("syncing_relay URL: {}", syncing_relay.url()); | ||
| 958 | println!("source_grasp URL: {}", source_grasp.url()); | ||
| 959 | println!("mock_relay URL: {}", mock_relay.url()); | ||
| 960 | println!("git_server URL: {}", git_server.url()); | ||
| 961 | |||
| 962 | wait_for_sync_connection(syncing_relay.url(), 2, Duration::from_secs(10)) | ||
| 963 | .await | ||
| 964 | .expect( | ||
| 965 | "Sync connections should establish to discovered relays (source_grasp + mock_relay)", | ||
| 966 | ); | ||
| 967 | println!("Sync connections established!"); | ||
| 968 | |||
| 969 | // Debug: Check metrics to see what relays are connected | ||
| 970 | let metrics_url = syncing_relay | ||
| 971 | .url() | ||
| 972 | .replace("ws://", "http://") | ||
| 973 | .replace("/", "") | ||
| 974 | + "/metrics"; | ||
| 975 | println!("Checking metrics at: {}", metrics_url); | ||
| 976 | if let Ok(response) = reqwest::get(&metrics_url).await { | ||
| 977 | if let Ok(metrics) = response.text().await { | ||
| 978 | // Print sync-related metrics | ||
| 979 | for line in metrics.lines() { | ||
| 980 | if line.contains("sync") && !line.starts_with('#') { | ||
| 981 | println!(" {}", line); | ||
| 982 | } | ||
| 983 | } | ||
| 984 | } | ||
| 985 | } | ||
| 986 | |||
| 987 | // Give some time for sync to happen | ||
| 988 | println!("Waiting 10s for events to sync..."); | ||
| 989 | tokio::time::sleep(Duration::from_secs(10)).await; | ||
| 990 | |||
| 991 | // Check metrics again after waiting | ||
| 992 | println!("=== Checking metrics after sync wait ==="); | ||
| 993 | if let Ok(response) = reqwest::get(&metrics_url).await { | ||
| 994 | if let Ok(metrics) = response.text().await { | ||
| 995 | for line in metrics.lines() { | ||
| 996 | if line.contains("sync") && !line.starts_with('#') { | ||
| 997 | println!(" {}", line); | ||
| 998 | } | ||
| 999 | } | ||
| 1000 | } | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | // Debug: Check if PR event is still on mock_relay | ||
| 1004 | println!("=== Debug: Checking PR event on mock_relay ==="); | ||
| 1005 | let pr_on_mock = | ||
| 1006 | wait_for_event_served(mock_relay.url(), &pr_event_id, Duration::from_secs(2)).await; | ||
| 1007 | println!("PR event on mock_relay: {:?}", pr_on_mock.is_ok()); | ||
| 1008 | if let Ok(ref pr) = pr_on_mock { | ||
| 1009 | println!("PR event tags:"); | ||
| 1010 | for tag in pr.tags.iter() { | ||
| 1011 | println!(" {:?}", tag.as_slice()); | ||
| 1012 | } | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | // Debug: Check repo coordinate | ||
| 1016 | let repo_coord = build_repo_coord(&owner_keys, identifier); | ||
| 1017 | println!("Expected repo coordinate: {}", repo_coord); | ||
| 1018 | |||
| 1019 | // Debug: Test if mock_relay responds to tag-based filter (Layer 2 style) | ||
| 1020 | println!("=== Debug: Testing mock_relay tag filter response ==="); | ||
| 1021 | let test_client = Client::new(Keys::generate()); | ||
| 1022 | test_client | ||
| 1023 | .add_relay(mock_relay.url()) | ||
| 1024 | .await | ||
| 1025 | .expect("Failed to add mock_relay"); | ||
| 1026 | test_client.connect().await; | ||
| 1027 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 1028 | |||
| 1029 | // Build a Layer 2 style filter (by 'a' tag) | ||
| 1030 | let tag_filter = | ||
| 1031 | Filter::new().custom_tag(SingleLetterTag::lowercase(Alphabet::A), repo_coord.as_str()); | ||
| 1032 | println!("Tag filter: {:?}", tag_filter); | ||
| 1033 | |||
| 1034 | let tag_results = test_client | ||
| 1035 | .fetch_events(tag_filter, Duration::from_secs(5)) | ||
| 1036 | .await; | ||
| 1037 | match tag_results { | ||
| 1038 | Ok(events) => { | ||
| 1039 | println!("Tag filter returned {} events", events.len()); | ||
| 1040 | for event in events.iter() { | ||
| 1041 | println!(" Event ID: {}, Kind: {}", event.id, event.kind.as_u16()); | ||
| 1042 | } | ||
| 1043 | } | ||
| 1044 | Err(e) => { | ||
| 1045 | println!("Tag filter query failed: {:?}", e); | ||
| 1046 | } | ||
| 1047 | } | ||
| 1048 | test_client.disconnect().await; | ||
| 1049 | |||
| 1050 | // The syncing relay will: | 1000 | // The syncing relay will: |
| 1051 | // 1. Receive announcement directly (creates bare repo) | 1001 | // 1. Sync promoted announcement from source_grasp via bootstrap connection → purgatory (no local git data) |
| 1052 | // 2. Discover source_grasp and mock_relay from announcement's `relays` tag | 1002 | // 2. EOSE triggers StateOnly subscription → syncs state event from source_grasp → purgatory sync |
| 1053 | // 3. Connect to discovered relays | 1003 | // 3. Purgatory sync fetches commit_a from source_grasp clone URL → announcement + state promoted |
| 1054 | // 4. Sync state event from source_grasp → purgatory (no commit_a locally) | 1004 | // 4. SelfSubscriber sees promoted announcement → upgrades to Full → connects to mock_relay |
| 1055 | // 5. Sync PR event from mock_relay → purgatory (no commit_b locally) | 1005 | // 5. Syncs PR event from mock_relay → purgatory (no commit_b locally) |
| 1056 | // 6. Purgatory sync triggers | 1006 | // 6. Purgatory sync fetches commit_b from git_server via PR clone tag |
| 1057 | // 7. Fetches commit_a from source_grasp clone URL (from announcement clone tag) | 1007 | // 7. PR event promoted → served |
| 1058 | // 8. Fetches commit_b from git_server (from PR event's clone tag) | ||
| 1059 | // 9. Both events released when all OIDs available | ||
| 1060 | 1008 | ||
| 1061 | // ======================================================================== | 1009 | // ======================================================================== |
| 1062 | // Step 6: Verify Results | 1010 | // Step 6: Verify Results |
| 1063 | // ======================================================================== | 1011 | // ======================================================================== |
| 1064 | 1012 | ||
| 1065 | println!("=== Step 6: Verify Results ==="); | ||
| 1066 | println!("State event ID: {}", state_event_id); | ||
| 1067 | println!("PR event ID: {}", pr_event_id); | ||
| 1068 | println!("commit_a: {}", commit_a); | ||
| 1069 | println!("commit_b: {}", commit_b); | ||
| 1070 | |||
| 1071 | // Wait for state event to be served on syncing_relay | 1013 | // Wait for state event to be served on syncing_relay |
| 1072 | println!("Waiting for state event on syncing_relay..."); | ||
| 1073 | let state_found = wait_for_event_served( | 1014 | let state_found = wait_for_event_served( |
| 1074 | syncing_relay.url(), | 1015 | syncing_relay.url(), |
| 1075 | &state_event_id, | 1016 | &state_event_id, |
| 1076 | Duration::from_secs(30), | 1017 | Duration::from_secs(30), |
| 1077 | ) | 1018 | ) |
| 1078 | .await; | 1019 | .await; |
| 1079 | println!("State event result: {:?}", state_found); | ||
| 1080 | assert!( | 1020 | assert!( |
| 1081 | state_found.is_ok(), | 1021 | state_found.is_ok(), |
| 1082 | "State event should be served on syncing_relay: {:?}", | 1022 | "State event should be served on syncing_relay: {:?}", |
| @@ -1084,10 +1024,8 @@ async fn test_pr_event_clone_tag_sync_with_partial_oid_aggregation_from_multiple | |||
| 1084 | ); | 1024 | ); |
| 1085 | 1025 | ||
| 1086 | // Wait for PR event to be served on syncing_relay | 1026 | // Wait for PR event to be served on syncing_relay |
| 1087 | println!("Waiting for PR event on syncing_relay..."); | ||
| 1088 | let pr_found = | 1027 | let pr_found = |
| 1089 | wait_for_event_served(syncing_relay.url(), &pr_event_id, Duration::from_secs(30)).await; | 1028 | wait_for_event_served(syncing_relay.url(), &pr_event_id, Duration::from_secs(30)).await; |
| 1090 | println!("PR event result: {:?}", pr_found); | ||
| 1091 | assert!( | 1029 | assert!( |
| 1092 | pr_found.is_ok(), | 1030 | pr_found.is_ok(), |
| 1093 | "PR event should be served on syncing_relay (fetched commit_b from git_server via PR clone tag): {:?}", | 1031 | "PR event should be served on syncing_relay (fetched commit_b from git_server via PR clone tag): {:?}", |
| @@ -1128,7 +1066,6 @@ async fn test_pr_event_clone_tag_sync_with_partial_oid_aggregation_from_multiple | |||
| 1128 | source_client.disconnect().await; | 1066 | source_client.disconnect().await; |
| 1129 | mock_client.disconnect().await; | 1067 | mock_client.disconnect().await; |
| 1130 | pr_client.disconnect().await; | 1068 | pr_client.disconnect().await; |
| 1131 | syncing_client.disconnect().await; | ||
| 1132 | git_server.stop().await; | 1069 | git_server.stop().await; |
| 1133 | mock_relay.stop().await; | 1070 | mock_relay.stop().await; |
| 1134 | syncing_relay.stop().await; | 1071 | syncing_relay.stop().await; |