diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:41:32 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:41:32 +0000 |
| commit | c54ce061d6d278cce8362d5af085808ca60c239b (patch) | |
| tree | ec967d6195d9f7ec4f061449596611afe3a0950f /grasp-audit/src/specs/grasp01/push_authorization.rs | |
| parent | e0ad39a489b3398f8208713bf728db0cb11475b0 (diff) | |
| parent | 113928aa84894ea8f65c247d9987527e792b32a9 (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 'grasp-audit/src/specs/grasp01/push_authorization.rs')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 232 |
1 files changed, 119 insertions, 113 deletions
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index c1003b9..73cbe1f 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -19,7 +19,7 @@ | |||
| 19 | /// Expected hash for PR test deterministic commit | 19 | /// Expected hash for PR test deterministic commit |
| 20 | /// | 20 | /// |
| 21 | /// This hash is produced by creating a commit with: | 21 | /// This hash is produced by creating a commit with: |
| 22 | /// - File: test.txt containing "PR test deterministic commit" | 22 | /// - File: test.txt containing "PR test deterministic commit\n" (with trailing newline) |
| 23 | /// - Message: "PR test deterministic commit" | 23 | /// - Message: "PR test deterministic commit" |
| 24 | /// - Author: "GRASP Audit Test <test@grasp-audit.local>" | 24 | /// - Author: "GRASP Audit Test <test@grasp-audit.local>" |
| 25 | /// - Author date: 2024-01-01T00:00:00Z | 25 | /// - Author date: 2024-01-01T00:00:00Z |
| @@ -29,8 +29,9 @@ | |||
| 29 | /// | 29 | /// |
| 30 | /// Run `test_pr_test_commit_hash_discovery` to discover/verify this value. | 30 | /// Run `test_pr_test_commit_hash_discovery` to discover/verify this value. |
| 31 | #[allow(dead_code)] | 31 | #[allow(dead_code)] |
| 32 | const PR_TEST_COMMIT_HASH: &str = "5d40fb1555a0c28bf4d650515a73aaa54d4d9bfb"; | 32 | const PR_TEST_COMMIT_HASH: &str = "5a51b30e4615b572dcd5b9e487861b58605a5c21"; |
| 33 | 33 | ||
| 34 | use crate::specs::grasp01::SpecRef; | ||
| 34 | use crate::{ | 35 | use crate::{ |
| 35 | clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref, | 36 | clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref, |
| 36 | AuditClient, CommitVariant, FixtureKind, TestContext, TestResult, | 37 | AuditClient, CommitVariant, FixtureKind, TestContext, TestResult, |
| @@ -207,7 +208,7 @@ async fn setup_pr_test_repo( | |||
| 207 | ) -> Result<(PathBuf, String, String, String), String> { | 208 | ) -> Result<(PathBuf, String, String, String), String> { |
| 208 | // Get fixtures | 209 | // Get fixtures |
| 209 | let repo_event = ctx | 210 | let repo_event = ctx |
| 210 | .get_fixture(FixtureKind::ValidRepo) | 211 | .get_fixture(FixtureKind::ValidRepoServed) |
| 211 | .await | 212 | .await |
| 212 | .map_err(|e| format!("Failed to get repo announcement: {}", e))?; | 213 | .map_err(|e| format!("Failed to get repo announcement: {}", e))?; |
| 213 | 214 | ||
| @@ -406,12 +407,12 @@ impl PushAuthorizationTests { | |||
| 406 | let ctx = TestContext::new(client); | 407 | let ctx = TestContext::new(client); |
| 407 | 408 | ||
| 408 | // Create repository (no state event) | 409 | // Create repository (no state event) |
| 409 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 410 | let repo = match ctx.get_fixture(FixtureKind::ValidRepoSent).await { |
| 410 | Ok(r) => r, | 411 | Ok(r) => r, |
| 411 | Err(e) => { | 412 | Err(e) => { |
| 412 | return TestResult::new( | 413 | return TestResult::new( |
| 413 | test_name, | 414 | test_name, |
| 414 | "GRASP-01:git-http:36", | 415 | SpecRef::GitAcceptPushesAlignState, |
| 415 | "Push rejected without state event", | 416 | "Push rejected without state event", |
| 416 | ) | 417 | ) |
| 417 | .fail(format!("Failed to create repo: {}", e)) | 418 | .fail(format!("Failed to create repo: {}", e)) |
| @@ -435,7 +436,7 @@ impl PushAuthorizationTests { | |||
| 435 | Err(e) => { | 436 | Err(e) => { |
| 436 | return TestResult::new( | 437 | return TestResult::new( |
| 437 | test_name, | 438 | test_name, |
| 438 | "GRASP-01:git-http:36", | 439 | SpecRef::GitAcceptPushesAlignState, |
| 439 | "Push rejected without state event", | 440 | "Push rejected without state event", |
| 440 | ) | 441 | ) |
| 441 | .fail(&e) | 442 | .fail(&e) |
| @@ -449,7 +450,7 @@ impl PushAuthorizationTests { | |||
| 449 | cleanup(); | 450 | cleanup(); |
| 450 | return TestResult::new( | 451 | return TestResult::new( |
| 451 | test_name, | 452 | test_name, |
| 452 | "GRASP-01:git-http:36", | 453 | SpecRef::GitAcceptPushesAlignState, |
| 453 | "Push rejected without state event", | 454 | "Push rejected without state event", |
| 454 | ) | 455 | ) |
| 455 | .fail(&e); | 456 | .fail(&e); |
| @@ -462,19 +463,19 @@ impl PushAuthorizationTests { | |||
| 462 | match push_result { | 463 | match push_result { |
| 463 | Ok(false) => TestResult::new( | 464 | Ok(false) => TestResult::new( |
| 464 | test_name, | 465 | test_name, |
| 465 | "GRASP-01:git-http:36", | 466 | SpecRef::GitAcceptPushesAlignState, |
| 466 | "Push rejected without state event", | 467 | "Push rejected without state event", |
| 467 | ) | 468 | ) |
| 468 | .pass(), | 469 | .pass(), |
| 469 | Ok(true) => TestResult::new( | 470 | Ok(true) => TestResult::new( |
| 470 | test_name, | 471 | test_name, |
| 471 | "GRASP-01:git-http:36", | 472 | SpecRef::GitAcceptPushesAlignState, |
| 472 | "Push rejected without state event", | 473 | "Push rejected without state event", |
| 473 | ) | 474 | ) |
| 474 | .fail("Push accepted but should be rejected"), | 475 | .fail("Push accepted but should be rejected"), |
| 475 | Err(e) => TestResult::new( | 476 | Err(e) => TestResult::new( |
| 476 | test_name, | 477 | test_name, |
| 477 | "GRASP-01:git-http:36", | 478 | SpecRef::GitAcceptPushesAlignState, |
| 478 | "Push rejected without state event", | 479 | "Push rejected without state event", |
| 479 | ) | 480 | ) |
| 480 | .fail(&e), | 481 | .fail(&e), |
| @@ -507,13 +508,13 @@ impl PushAuthorizationTests { | |||
| 507 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { | 508 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { |
| 508 | Ok(_state_event) => TestResult::new( | 509 | Ok(_state_event) => TestResult::new( |
| 509 | test_name, | 510 | test_name, |
| 510 | "GRASP-01:git-http:36", // TODO do we add purgatory line here? | 511 | SpecRef::GitAcceptPushesAlignState, |
| 511 | "Push authorized with matching state", | 512 | "Push authorized with matching state", |
| 512 | ) | 513 | ) |
| 513 | .pass(), | 514 | .pass(), |
| 514 | Err(e) => TestResult::new( | 515 | Err(e) => TestResult::new( |
| 515 | test_name, | 516 | test_name, |
| 516 | "GRASP-01:git-http:36", | 517 | SpecRef::GitAcceptPushesAlignState, |
| 517 | "Push authorized with matching state", | 518 | "Push authorized with matching state", |
| 518 | ) | 519 | ) |
| 519 | .fail(format!("{}", e)), | 520 | .fail(format!("{}", e)), |
| @@ -555,7 +556,7 @@ impl PushAuthorizationTests { | |||
| 555 | Err(e) => { | 556 | Err(e) => { |
| 556 | return TestResult::new( | 557 | return TestResult::new( |
| 557 | test_name, | 558 | test_name, |
| 558 | "GRASP-01:git-http:36", | 559 | SpecRef::GitAcceptPushesAlignState, |
| 559 | "Push rejected when commit not in state event", | 560 | "Push rejected when commit not in state event", |
| 560 | ) | 561 | ) |
| 561 | .fail(format!("Failed to create RepoState fixture: {}", e)); | 562 | .fail(format!("Failed to create RepoState fixture: {}", e)); |
| @@ -575,7 +576,7 @@ impl PushAuthorizationTests { | |||
| 575 | None => { | 576 | None => { |
| 576 | return TestResult::new( | 577 | return TestResult::new( |
| 577 | test_name, | 578 | test_name, |
| 578 | "GRASP-01:git-http:36", | 579 | SpecRef::GitAcceptPushesAlignState, |
| 579 | "Push rejected when commit not in state event", | 580 | "Push rejected when commit not in state event", |
| 580 | ) | 581 | ) |
| 581 | .fail("Missing repo_id in state event"); | 582 | .fail("Missing repo_id in state event"); |
| @@ -587,7 +588,7 @@ impl PushAuthorizationTests { | |||
| 587 | Err(e) => { | 588 | Err(e) => { |
| 588 | return TestResult::new( | 589 | return TestResult::new( |
| 589 | test_name, | 590 | test_name, |
| 590 | "GRASP-01:git-http:36", | 591 | SpecRef::GitAcceptPushesAlignState, |
| 591 | "Push rejected when commit not in state event", | 592 | "Push rejected when commit not in state event", |
| 592 | ) | 593 | ) |
| 593 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 594 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| @@ -603,7 +604,7 @@ impl PushAuthorizationTests { | |||
| 603 | Err(e) => { | 604 | Err(e) => { |
| 604 | return TestResult::new( | 605 | return TestResult::new( |
| 605 | test_name, | 606 | test_name, |
| 606 | "GRASP-01:git-http:36", | 607 | SpecRef::GitAcceptPushesAlignState, |
| 607 | "Push rejected when commit not in state event", | 608 | "Push rejected when commit not in state event", |
| 608 | ) | 609 | ) |
| 609 | .fail(format!("Failed to clone repo: {}", e)); | 610 | .fail(format!("Failed to clone repo: {}", e)); |
| @@ -626,7 +627,7 @@ impl PushAuthorizationTests { | |||
| 626 | cleanup(); | 627 | cleanup(); |
| 627 | return TestResult::new( | 628 | return TestResult::new( |
| 628 | test_name, | 629 | test_name, |
| 629 | "GRASP-01:git-http:36", | 630 | SpecRef::GitAcceptPushesAlignState, |
| 630 | "Push rejected when commit not in state event", | 631 | "Push rejected when commit not in state event", |
| 631 | ) | 632 | ) |
| 632 | .fail(format!("Failed to create/checkout main branch: {}", e)); | 633 | .fail(format!("Failed to create/checkout main branch: {}", e)); |
| @@ -635,7 +636,7 @@ impl PushAuthorizationTests { | |||
| 635 | cleanup(); | 636 | cleanup(); |
| 636 | return TestResult::new( | 637 | return TestResult::new( |
| 637 | test_name, | 638 | test_name, |
| 638 | "GRASP-01:git-http:36", | 639 | SpecRef::GitAcceptPushesAlignState, |
| 639 | "Push rejected when commit not in state event", | 640 | "Push rejected when commit not in state event", |
| 640 | ) | 641 | ) |
| 641 | .fail(format!( | 642 | .fail(format!( |
| @@ -652,7 +653,7 @@ impl PushAuthorizationTests { | |||
| 652 | cleanup(); | 653 | cleanup(); |
| 653 | return TestResult::new( | 654 | return TestResult::new( |
| 654 | test_name, | 655 | test_name, |
| 655 | "GRASP-01:git-http:36", | 656 | SpecRef::GitAcceptPushesAlignState, |
| 656 | "Push rejected when commit not in state event", | 657 | "Push rejected when commit not in state event", |
| 657 | ) | 658 | ) |
| 658 | .fail(format!("Failed to create wrong commit: {}", e)); | 659 | .fail(format!("Failed to create wrong commit: {}", e)); |
| @@ -666,10 +667,10 @@ impl PushAuthorizationTests { | |||
| 666 | cleanup(); | 667 | cleanup(); |
| 667 | 668 | ||
| 668 | match push_result { | 669 | match push_result { |
| 669 | Ok(false) => TestResult::new(test_name, "GRASP-01:git-http:36", "Push rejected when commit not in state event").pass(), | 670 | Ok(false) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Push rejected when commit not in state event").pass(), |
| 670 | Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:36", "Push rejected when commit not in state event") | 671 | Ok(true) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Push rejected when commit not in state event") |
| 671 | .fail("Push accepted but should be rejected. The pushed commit is not in the state event."), | 672 | .fail("Push accepted but should be rejected. The pushed commit is not in the state event."), |
| 672 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:36", "Push rejected when commit not in state event").fail(&e), | 673 | Err(e) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Push rejected when commit not in state event").fail(&e), |
| 673 | } | 674 | } |
| 674 | } | 675 | } |
| 675 | 676 | ||
| @@ -704,13 +705,13 @@ impl PushAuthorizationTests { | |||
| 704 | { | 705 | { |
| 705 | Ok(_maintainer_state_event) => TestResult::new( | 706 | Ok(_maintainer_state_event) => TestResult::new( |
| 706 | test_name, | 707 | test_name, |
| 707 | "GRASP-01:git-http:36", | 708 | SpecRef::GitAcceptPushesAlignState, |
| 708 | "Push authorized by maintainer state event only (no announcement)", | 709 | "Push authorized by maintainer state event only (no announcement)", |
| 709 | ) | 710 | ) |
| 710 | .pass(), | 711 | .pass(), |
| 711 | Err(e) => TestResult::new( | 712 | Err(e) => TestResult::new( |
| 712 | test_name, | 713 | test_name, |
| 713 | "GRASP-01:git-http:36", | 714 | SpecRef::GitAcceptPushesAlignState, |
| 714 | "Push authorized by maintainer state event only (no announcement)", | 715 | "Push authorized by maintainer state event only (no announcement)", |
| 715 | ) | 716 | ) |
| 716 | .fail(format!("{}", e)), | 717 | .fail(format!("{}", e)), |
| @@ -747,13 +748,13 @@ impl PushAuthorizationTests { | |||
| 747 | { | 748 | { |
| 748 | Ok(_recursive_maintainer_state_event) => TestResult::new( | 749 | Ok(_recursive_maintainer_state_event) => TestResult::new( |
| 749 | test_name, | 750 | test_name, |
| 750 | "GRASP-01:git-http:36", | 751 | SpecRef::GitAcceptPushesAlignState, |
| 751 | "Push authorized by recursive maintainer state event", | 752 | "Push authorized by recursive maintainer state event", |
| 752 | ) | 753 | ) |
| 753 | .pass(), | 754 | .pass(), |
| 754 | Err(e) => TestResult::new( | 755 | Err(e) => TestResult::new( |
| 755 | test_name, | 756 | test_name, |
| 756 | "GRASP-01:git-http:36", | 757 | SpecRef::GitAcceptPushesAlignState, |
| 757 | "Push authorized by recursive maintainer state event", | 758 | "Push authorized by recursive maintainer state event", |
| 758 | ) | 759 | ) |
| 759 | .fail(format!("{}", e)), | 760 | .fail(format!("{}", e)), |
| @@ -797,7 +798,7 @@ impl PushAuthorizationTests { | |||
| 797 | Err(e) => { | 798 | Err(e) => { |
| 798 | return TestResult::new( | 799 | return TestResult::new( |
| 799 | test_name, | 800 | test_name, |
| 800 | "GRASP-01:git-http:36", | 801 | SpecRef::GitAcceptPushesAlignState, |
| 801 | "Non-maintainer state events ignored", | 802 | "Non-maintainer state events ignored", |
| 802 | ) | 803 | ) |
| 803 | .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e)); | 804 | .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e)); |
| @@ -815,7 +816,7 @@ impl PushAuthorizationTests { | |||
| 815 | None => { | 816 | None => { |
| 816 | return TestResult::new( | 817 | return TestResult::new( |
| 817 | test_name, | 818 | test_name, |
| 818 | "GRASP-01:git-http:36", | 819 | SpecRef::GitAcceptPushesAlignState, |
| 819 | "Non-maintainer state events ignored", | 820 | "Non-maintainer state events ignored", |
| 820 | ) | 821 | ) |
| 821 | .fail("Missing repo_id in state event"); | 822 | .fail("Missing repo_id in state event"); |
| @@ -827,7 +828,7 @@ impl PushAuthorizationTests { | |||
| 827 | Err(e) => { | 828 | Err(e) => { |
| 828 | return TestResult::new( | 829 | return TestResult::new( |
| 829 | test_name, | 830 | test_name, |
| 830 | "GRASP-01:git-http:36", | 831 | SpecRef::GitAcceptPushesAlignState, |
| 831 | "Non-maintainer state events ignored", | 832 | "Non-maintainer state events ignored", |
| 832 | ) | 833 | ) |
| 833 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 834 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| @@ -842,7 +843,7 @@ impl PushAuthorizationTests { | |||
| 842 | Err(e) => { | 843 | Err(e) => { |
| 843 | return TestResult::new( | 844 | return TestResult::new( |
| 844 | test_name, | 845 | test_name, |
| 845 | "GRASP-01:git-http:36", | 846 | SpecRef::GitAcceptPushesAlignState, |
| 846 | "Non-maintainer state events ignored", | 847 | "Non-maintainer state events ignored", |
| 847 | ) | 848 | ) |
| 848 | .fail(format!("Failed to clone repo: {}", e)); | 849 | .fail(format!("Failed to clone repo: {}", e)); |
| @@ -864,7 +865,7 @@ impl PushAuthorizationTests { | |||
| 864 | cleanup(); | 865 | cleanup(); |
| 865 | return TestResult::new( | 866 | return TestResult::new( |
| 866 | test_name, | 867 | test_name, |
| 867 | "GRASP-01:git-http:36", | 868 | SpecRef::GitAcceptPushesAlignState, |
| 868 | "Non-maintainer state events ignored", | 869 | "Non-maintainer state events ignored", |
| 869 | ) | 870 | ) |
| 870 | .fail(format!("Failed to create commit: {}", e)); | 871 | .fail(format!("Failed to create commit: {}", e)); |
| @@ -890,7 +891,7 @@ impl PushAuthorizationTests { | |||
| 890 | cleanup(); | 891 | cleanup(); |
| 891 | return TestResult::new( | 892 | return TestResult::new( |
| 892 | test_name, | 893 | test_name, |
| 893 | "GRASP-01:git-http:36", | 894 | SpecRef::GitAcceptPushesAlignState, |
| 894 | "Non-maintainer state events ignored", | 895 | "Non-maintainer state events ignored", |
| 895 | ) | 896 | ) |
| 896 | .fail(format!("Failed to build rogue state event: {}", e)); | 897 | .fail(format!("Failed to build rogue state event: {}", e)); |
| @@ -902,7 +903,7 @@ impl PushAuthorizationTests { | |||
| 902 | cleanup(); | 903 | cleanup(); |
| 903 | return TestResult::new( | 904 | return TestResult::new( |
| 904 | test_name, | 905 | test_name, |
| 905 | "GRASP-01:git-http:36", | 906 | SpecRef::GitAcceptPushesAlignState, |
| 906 | "Non-maintainer state events ignored", | 907 | "Non-maintainer state events ignored", |
| 907 | ) | 908 | ) |
| 908 | .fail(format!("Failed to send rogue state event: {}", e)); | 909 | .fail(format!("Failed to send rogue state event: {}", e)); |
| @@ -919,8 +920,8 @@ impl PushAuthorizationTests { | |||
| 919 | cleanup(); | 920 | cleanup(); |
| 920 | 921 | ||
| 921 | match push_result { | 922 | match push_result { |
| 922 | Ok(false) => TestResult::new(test_name, "GRASP-01:git-http:36", "Non-maintainer state events ignored").pass(), | 923 | Ok(false) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Non-maintainer state events ignored").pass(), |
| 923 | Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:36", "Non-maintainer state events ignored") | 924 | Ok(true) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Non-maintainer state events ignored") |
| 924 | .fail(format!( | 925 | .fail(format!( |
| 925 | "Push accepted but should be rejected. A non-maintainer (pubkey: {}) published \ | 926 | "Push accepted but should be rejected. A non-maintainer (pubkey: {}) published \ |
| 926 | a state event announcing commit {}, but the push was accepted. The relay should \ | 927 | a state event announcing commit {}, but the push was accepted. The relay should \ |
| @@ -929,7 +930,7 @@ impl PushAuthorizationTests { | |||
| 929 | new_commit, | 930 | new_commit, |
| 930 | client.public_key() | 931 | client.public_key() |
| 931 | )), | 932 | )), |
| 932 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:36", "Non-maintainer state events ignored").fail(&e), | 933 | Err(e) => TestResult::new(test_name, SpecRef::GitAcceptPushesAlignState, "Non-maintainer state events ignored").fail(&e), |
| 933 | } | 934 | } |
| 934 | } | 935 | } |
| 935 | 936 | ||
| @@ -955,12 +956,12 @@ impl PushAuthorizationTests { | |||
| 955 | // ============================================================ | 956 | // ============================================================ |
| 956 | let ctx = TestContext::new(client); | 957 | let ctx = TestContext::new(client); |
| 957 | 958 | ||
| 958 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 959 | let repo = match ctx.get_fixture(FixtureKind::ValidRepoSent).await { |
| 959 | Ok(r) => r, | 960 | Ok(r) => r, |
| 960 | Err(e) => { | 961 | Err(e) => { |
| 961 | return TestResult::new( | 962 | return TestResult::new( |
| 962 | test_name, | 963 | test_name, |
| 963 | "GRASP-01:git-http:40", | 964 | SpecRef::GitAcceptRefsNostrEventId, |
| 964 | "Push to refs/nostr/<invalid-event-id> rejected", | 965 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 965 | ) | 966 | ) |
| 966 | .fail(format!("Failed to create repo: {}", e)); | 967 | .fail(format!("Failed to create repo: {}", e)); |
| @@ -986,7 +987,7 @@ impl PushAuthorizationTests { | |||
| 986 | Err(e) => { | 987 | Err(e) => { |
| 987 | return TestResult::new( | 988 | return TestResult::new( |
| 988 | test_name, | 989 | test_name, |
| 989 | "GRASP-01:git-http:40", | 990 | SpecRef::GitAcceptRefsNostrEventId, |
| 990 | "Push to refs/nostr/<invalid-event-id> rejected", | 991 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 991 | ) | 992 | ) |
| 992 | .fail(&e); | 993 | .fail(&e); |
| @@ -1001,7 +1002,7 @@ impl PushAuthorizationTests { | |||
| 1001 | cleanup(); | 1002 | cleanup(); |
| 1002 | return TestResult::new( | 1003 | return TestResult::new( |
| 1003 | test_name, | 1004 | test_name, |
| 1004 | "GRASP-01:git-http:40", | 1005 | SpecRef::GitAcceptRefsNostrEventId, |
| 1005 | "Push to refs/nostr/<invalid-event-id> rejected", | 1006 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 1006 | ) | 1007 | ) |
| 1007 | .fail(&e); | 1008 | .fail(&e); |
| @@ -1020,13 +1021,13 @@ impl PushAuthorizationTests { | |||
| 1020 | match push_result { | 1021 | match push_result { |
| 1021 | Ok(false) => TestResult::new( | 1022 | Ok(false) => TestResult::new( |
| 1022 | test_name, | 1023 | test_name, |
| 1023 | "GRASP-01:git-http:40", | 1024 | SpecRef::GitAcceptRefsNostrEventId, |
| 1024 | "Push to refs/nostr/<invalid-event-id> rejected", | 1025 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 1025 | ) | 1026 | ) |
| 1026 | .pass(), | 1027 | .pass(), |
| 1027 | Ok(true) => TestResult::new( | 1028 | Ok(true) => TestResult::new( |
| 1028 | test_name, | 1029 | test_name, |
| 1029 | "GRASP-01:git-http:40", | 1030 | SpecRef::GitAcceptRefsNostrEventId, |
| 1030 | "Push to refs/nostr/<invalid-event-id> rejected", | 1031 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 1031 | ) | 1032 | ) |
| 1032 | .fail(format!( | 1033 | .fail(format!( |
| @@ -1037,7 +1038,7 @@ impl PushAuthorizationTests { | |||
| 1037 | )), | 1038 | )), |
| 1038 | Err(e) => TestResult::new( | 1039 | Err(e) => TestResult::new( |
| 1039 | test_name, | 1040 | test_name, |
| 1040 | "GRASP-01:git-http:40", | 1041 | SpecRef::GitAcceptRefsNostrEventId, |
| 1041 | "Push to refs/nostr/<invalid-event-id> rejected", | 1042 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 1042 | ) | 1043 | ) |
| 1043 | .fail(format!("Push error: {}", e)), | 1044 | .fail(format!("Push error: {}", e)), |
| @@ -1071,10 +1072,11 @@ impl PushAuthorizationTests { | |||
| 1071 | .get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent) | 1072 | .get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent) |
| 1072 | .await | 1073 | .await |
| 1073 | { | 1074 | { |
| 1074 | Ok(_pr_event) => TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass(), | 1075 | Ok(_pr_event) => { |
| 1075 | Err(e) => { | 1076 | TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass() |
| 1076 | TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(format!("{}", e)) | ||
| 1077 | } | 1077 | } |
| 1078 | Err(e) => TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) | ||
| 1079 | .fail(format!("{}", e)), | ||
| 1078 | } | 1080 | } |
| 1079 | } | 1081 | } |
| 1080 | 1082 | ||
| @@ -1100,7 +1102,7 @@ impl PushAuthorizationTests { | |||
| 1100 | { | 1102 | { |
| 1101 | Ok(e) => e, | 1103 | Ok(e) => e, |
| 1102 | Err(e) => { | 1104 | Err(e) => { |
| 1103 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1105 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1104 | .fail(format!("{}", e)); | 1106 | .fail(format!("{}", e)); |
| 1105 | } | 1107 | } |
| 1106 | }; | 1108 | }; |
| @@ -1108,10 +1110,10 @@ impl PushAuthorizationTests { | |||
| 1108 | let pr_event_id = pr_event.id.to_hex(); | 1110 | let pr_event_id = pr_event.id.to_hex(); |
| 1109 | 1111 | ||
| 1110 | // Get repo info for cloning (fresh clone for verification) | 1112 | // Get repo info for cloning (fresh clone for verification) |
| 1111 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1113 | let repo = match ctx.get_fixture(FixtureKind::ValidRepoServed).await { |
| 1112 | Ok(r) => r, | 1114 | Ok(r) => r, |
| 1113 | Err(e) => { | 1115 | Err(e) => { |
| 1114 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1116 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1115 | .fail(format!("{}", e)); | 1117 | .fail(format!("{}", e)); |
| 1116 | } | 1118 | } |
| 1117 | }; | 1119 | }; |
| @@ -1127,7 +1129,7 @@ impl PushAuthorizationTests { | |||
| 1127 | let owner_npub = match repo.pubkey.to_bech32() { | 1129 | let owner_npub = match repo.pubkey.to_bech32() { |
| 1128 | Ok(n) => n, | 1130 | Ok(n) => n, |
| 1129 | Err(e) => { | 1131 | Err(e) => { |
| 1130 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1132 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1131 | .fail(format!("Failed to get owner npub: {}", e)); | 1133 | .fail(format!("Failed to get owner npub: {}", e)); |
| 1132 | } | 1134 | } |
| 1133 | }; | 1135 | }; |
| @@ -1136,7 +1138,8 @@ impl PushAuthorizationTests { | |||
| 1136 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { | 1138 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { |
| 1137 | Ok(p) => p, | 1139 | Ok(p) => p, |
| 1138 | Err(e) => { | 1140 | Err(e) => { |
| 1139 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1141 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1142 | .fail(&e); | ||
| 1140 | } | 1143 | } |
| 1141 | }; | 1144 | }; |
| 1142 | 1145 | ||
| @@ -1146,7 +1149,8 @@ impl PushAuthorizationTests { | |||
| 1146 | Ok(exists) => exists, | 1149 | Ok(exists) => exists, |
| 1147 | Err(e) => { | 1150 | Err(e) => { |
| 1148 | let _ = fs::remove_dir_all(&clone_path); | 1151 | let _ = fs::remove_dir_all(&clone_path); |
| 1149 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1152 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1153 | .fail(&e); | ||
| 1150 | } | 1154 | } |
| 1151 | }; | 1155 | }; |
| 1152 | 1156 | ||
| @@ -1154,13 +1158,13 @@ impl PushAuthorizationTests { | |||
| 1154 | 1158 | ||
| 1155 | // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag | 1159 | // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag |
| 1156 | if refs_exist { | 1160 | if refs_exist { |
| 1157 | TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(format!( | 1161 | TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).fail(format!( |
| 1158 | "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \ | 1162 | "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \ |
| 1159 | but the ref still exists. The relay should delete refs that don't match the event's `c` tag.", | 1163 | but the ref still exists. The relay should delete refs that don't match the event's `c` tag.", |
| 1160 | pr_event_id | 1164 | pr_event_id |
| 1161 | )) | 1165 | )) |
| 1162 | } else { | 1166 | } else { |
| 1163 | TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass() | 1167 | TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass() |
| 1164 | } | 1168 | } |
| 1165 | } | 1169 | } |
| 1166 | 1170 | ||
| @@ -1186,7 +1190,7 @@ impl PushAuthorizationTests { | |||
| 1186 | { | 1190 | { |
| 1187 | Ok(e) => e, | 1191 | Ok(e) => e, |
| 1188 | Err(e) => { | 1192 | Err(e) => { |
| 1189 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1193 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1190 | .fail(format!("{}", e)); | 1194 | .fail(format!("{}", e)); |
| 1191 | } | 1195 | } |
| 1192 | }; | 1196 | }; |
| @@ -1194,10 +1198,10 @@ impl PushAuthorizationTests { | |||
| 1194 | let pr_event_id = pr_event.id.to_hex(); | 1198 | let pr_event_id = pr_event.id.to_hex(); |
| 1195 | 1199 | ||
| 1196 | // Get repo info for cloning (fresh clone for this test) | 1200 | // Get repo info for cloning (fresh clone for this test) |
| 1197 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1201 | let repo = match ctx.get_fixture(FixtureKind::ValidRepoServed).await { |
| 1198 | Ok(r) => r, | 1202 | Ok(r) => r, |
| 1199 | Err(e) => { | 1203 | Err(e) => { |
| 1200 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1204 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1201 | .fail(format!("{}", e)); | 1205 | .fail(format!("{}", e)); |
| 1202 | } | 1206 | } |
| 1203 | }; | 1207 | }; |
| @@ -1213,7 +1217,7 @@ impl PushAuthorizationTests { | |||
| 1213 | let owner_npub = match repo.pubkey.to_bech32() { | 1217 | let owner_npub = match repo.pubkey.to_bech32() { |
| 1214 | Ok(n) => n, | 1218 | Ok(n) => n, |
| 1215 | Err(e) => { | 1219 | Err(e) => { |
| 1216 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1220 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1217 | .fail(format!("Failed to get owner npub: {}", e)); | 1221 | .fail(format!("Failed to get owner npub: {}", e)); |
| 1218 | } | 1222 | } |
| 1219 | }; | 1223 | }; |
| @@ -1222,15 +1226,16 @@ impl PushAuthorizationTests { | |||
| 1222 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { | 1226 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { |
| 1223 | Ok(p) => p, | 1227 | Ok(p) => p, |
| 1224 | Err(e) => { | 1228 | Err(e) => { |
| 1225 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1229 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1230 | .fail(&e); | ||
| 1226 | } | 1231 | } |
| 1227 | }; | 1232 | }; |
| 1228 | 1233 | ||
| 1229 | // Create a wrong commit (Owner variant, not PRTestCommit) | 1234 | // Create a wrong commit (unique, not PRTestCommit) - use create_commit so it always |
| 1230 | if let Err(e) = create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner) | 1235 | // succeeds even when the clone already has the Owner deterministic content on disk. |
| 1231 | { | 1236 | if let Err(e) = create_commit(&clone_path, "wrong commit - not the PR test commit") { |
| 1232 | let _ = fs::remove_dir_all(&clone_path); | 1237 | let _ = fs::remove_dir_all(&clone_path); |
| 1233 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1238 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).fail(&e); |
| 1234 | } | 1239 | } |
| 1235 | 1240 | ||
| 1236 | // Try to push with wrong commit (should be rejected since PR event exists) | 1241 | // Try to push with wrong commit (should be rejected since PR event exists) |
| @@ -1238,7 +1243,8 @@ impl PushAuthorizationTests { | |||
| 1238 | Ok(success) => success, | 1243 | Ok(success) => success, |
| 1239 | Err(e) => { | 1244 | Err(e) => { |
| 1240 | let _ = fs::remove_dir_all(&clone_path); | 1245 | let _ = fs::remove_dir_all(&clone_path); |
| 1241 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1246 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1247 | .fail(&e); | ||
| 1242 | } | 1248 | } |
| 1243 | }; | 1249 | }; |
| 1244 | 1250 | ||
| @@ -1246,11 +1252,11 @@ impl PushAuthorizationTests { | |||
| 1246 | 1252 | ||
| 1247 | // Should REJECT - PR event exists with different commit hash | 1253 | // Should REJECT - PR event exists with different commit hash |
| 1248 | if push_succeeded { | 1254 | if push_succeeded { |
| 1249 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1255 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1250 | .fail("Push accepted (expected rejection due to commit hash mismatch)"); | 1256 | .fail("Push accepted (expected rejection due to commit hash mismatch)"); |
| 1251 | } | 1257 | } |
| 1252 | 1258 | ||
| 1253 | TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass() | 1259 | TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass() |
| 1254 | } | 1260 | } |
| 1255 | 1261 | ||
| 1256 | /// Test 4: Push correct commit to refs/nostr/<pr-event-id> AFTER PR event exists | 1262 | /// Test 4: Push correct commit to refs/nostr/<pr-event-id> AFTER PR event exists |
| @@ -1275,7 +1281,7 @@ impl PushAuthorizationTests { | |||
| 1275 | { | 1281 | { |
| 1276 | Ok(e) => e, | 1282 | Ok(e) => e, |
| 1277 | Err(e) => { | 1283 | Err(e) => { |
| 1278 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1284 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1279 | .fail(format!("{}", e)); | 1285 | .fail(format!("{}", e)); |
| 1280 | } | 1286 | } |
| 1281 | }; | 1287 | }; |
| @@ -1283,10 +1289,10 @@ impl PushAuthorizationTests { | |||
| 1283 | let pr_event_id = pr_event.id.to_hex(); | 1289 | let pr_event_id = pr_event.id.to_hex(); |
| 1284 | 1290 | ||
| 1285 | // Get repo info for cloning (fresh clone for this test) | 1291 | // Get repo info for cloning (fresh clone for this test) |
| 1286 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1292 | let repo = match ctx.get_fixture(FixtureKind::ValidRepoServed).await { |
| 1287 | Ok(r) => r, | 1293 | Ok(r) => r, |
| 1288 | Err(e) => { | 1294 | Err(e) => { |
| 1289 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1295 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1290 | .fail(format!("{}", e)); | 1296 | .fail(format!("{}", e)); |
| 1291 | } | 1297 | } |
| 1292 | }; | 1298 | }; |
| @@ -1302,7 +1308,7 @@ impl PushAuthorizationTests { | |||
| 1302 | let owner_npub = match repo.pubkey.to_bech32() { | 1308 | let owner_npub = match repo.pubkey.to_bech32() { |
| 1303 | Ok(n) => n, | 1309 | Ok(n) => n, |
| 1304 | Err(e) => { | 1310 | Err(e) => { |
| 1305 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1311 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1306 | .fail(format!("Failed to get owner npub: {}", e)); | 1312 | .fail(format!("Failed to get owner npub: {}", e)); |
| 1307 | } | 1313 | } |
| 1308 | }; | 1314 | }; |
| @@ -1311,26 +1317,27 @@ impl PushAuthorizationTests { | |||
| 1311 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { | 1317 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { |
| 1312 | Ok(p) => p, | 1318 | Ok(p) => p, |
| 1313 | Err(e) => { | 1319 | Err(e) => { |
| 1314 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1320 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1321 | .fail(&e); | ||
| 1315 | } | 1322 | } |
| 1316 | }; | 1323 | }; |
| 1317 | 1324 | ||
| 1318 | // Create the CORRECT PR test commit (the one expected by PR event) | 1325 | // Create the CORRECT PR test commit (the one expected by PR event) |
| 1319 | if let Err(e) = reset_to_correct_pr_commit(&clone_path) { | 1326 | if let Err(e) = reset_to_correct_pr_commit(&clone_path) { |
| 1320 | let _ = fs::remove_dir_all(&clone_path); | 1327 | let _ = fs::remove_dir_all(&clone_path); |
| 1321 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1328 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).fail(&e); |
| 1322 | } | 1329 | } |
| 1323 | 1330 | ||
| 1324 | // Check event is not yet served by relay (still in purgatory) | 1331 | // Check event is not yet served by relay (still in purgatory) |
| 1325 | match client.is_event_on_relay(pr_event.id).await { | 1332 | match client.is_event_on_relay(pr_event.id).await { |
| 1326 | Ok(on_relay) => { | 1333 | Ok(on_relay) => { |
| 1327 | if on_relay { | 1334 | if on_relay { |
| 1328 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1335 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1329 | .fail("PR event not in purgatory before correct commit pushed to refs/nostr/<event-id> (the relay serve the PR event)"); | 1336 | .fail("PR event not in purgatory before correct commit pushed to refs/nostr/<event-id> (the relay serve the PR event)"); |
| 1330 | } | 1337 | } |
| 1331 | } | 1338 | } |
| 1332 | Err(_) => { | 1339 | Err(_) => { |
| 1333 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1340 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1334 | .fail("failed to query relay"); | 1341 | .fail("failed to query relay"); |
| 1335 | } | 1342 | } |
| 1336 | } | 1343 | } |
| @@ -1340,7 +1347,8 @@ impl PushAuthorizationTests { | |||
| 1340 | Ok(success) => success, | 1347 | Ok(success) => success, |
| 1341 | Err(e) => { | 1348 | Err(e) => { |
| 1342 | let _ = fs::remove_dir_all(&clone_path); | 1349 | let _ = fs::remove_dir_all(&clone_path); |
| 1343 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc).fail(&e); | 1350 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1351 | .fail(&e); | ||
| 1344 | } | 1352 | } |
| 1345 | }; | 1353 | }; |
| 1346 | 1354 | ||
| @@ -1348,7 +1356,7 @@ impl PushAuthorizationTests { | |||
| 1348 | 1356 | ||
| 1349 | // Should ACCEPT - commit matches PR event's c tag | 1357 | // Should ACCEPT - commit matches PR event's c tag |
| 1350 | if !push_succeeded { | 1358 | if !push_succeeded { |
| 1351 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1359 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1352 | .fail("Push rejected (expected acceptance since commit matches PR event)"); | 1360 | .fail("Push rejected (expected acceptance since commit matches PR event)"); |
| 1353 | } | 1361 | } |
| 1354 | 1362 | ||
| @@ -1361,17 +1369,17 @@ impl PushAuthorizationTests { | |||
| 1361 | match client.is_event_on_relay(pr_event.id).await { | 1369 | match client.is_event_on_relay(pr_event.id).await { |
| 1362 | Ok(on_relay) => { | 1370 | Ok(on_relay) => { |
| 1363 | if !on_relay { | 1371 | if !on_relay { |
| 1364 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1372 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1365 | .fail("PR event not served after correct commit at refs/nostr/<event-id>"); | 1373 | .fail("PR event not served after correct commit at refs/nostr/<event-id>"); |
| 1366 | } | 1374 | } |
| 1367 | } | 1375 | } |
| 1368 | Err(_) => { | 1376 | Err(_) => { |
| 1369 | return TestResult::new(test_name, "GRASP-01:git-http:40", desc) | 1377 | return TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc) |
| 1370 | .fail("failed to query relay"); | 1378 | .fail("failed to query relay"); |
| 1371 | } | 1379 | } |
| 1372 | } | 1380 | } |
| 1373 | 1381 | ||
| 1374 | TestResult::new(test_name, "GRASP-01:git-http:40", desc).pass() | 1382 | TestResult::new(test_name, SpecRef::GitAcceptRefsNostrEventId, desc).pass() |
| 1375 | } | 1383 | } |
| 1376 | 1384 | ||
| 1377 | /// Test that HEAD is set after a state event is published with an existing commit | 1385 | /// Test that HEAD is set after a state event is published with an existing commit |
| @@ -1408,20 +1416,19 @@ impl PushAuthorizationTests { | |||
| 1408 | { | 1416 | { |
| 1409 | Ok(e) => e, | 1417 | Ok(e) => e, |
| 1410 | Err(e) => { | 1418 | Err(e) => { |
| 1411 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( | 1419 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail( |
| 1412 | "Failed to create HeadSetToDevelopBranch fixture: {}", | 1420 | format!("Failed to create HeadSetToDevelopBranch fixture: {}", e), |
| 1413 | e | 1421 | ); |
| 1414 | )); | ||
| 1415 | } | 1422 | } |
| 1416 | }; | 1423 | }; |
| 1417 | 1424 | ||
| 1418 | // ============================================================ | 1425 | // ============================================================ |
| 1419 | // Step 2: Extract repo_id and owner npub from ValidRepo (cached by fixture) | 1426 | // Step 2: Extract repo_id and owner npub from ValidRepo (cached by fixture) |
| 1420 | // ============================================================ | 1427 | // ============================================================ |
| 1421 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1428 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepoSent).await { |
| 1422 | Ok(e) => e, | 1429 | Ok(e) => e, |
| 1423 | Err(e) => { | 1430 | Err(e) => { |
| 1424 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1431 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1425 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); | 1432 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); |
| 1426 | } | 1433 | } |
| 1427 | }; | 1434 | }; |
| @@ -1434,7 +1441,7 @@ impl PushAuthorizationTests { | |||
| 1434 | { | 1441 | { |
| 1435 | Some(id) => id.to_string(), | 1442 | Some(id) => id.to_string(), |
| 1436 | None => { | 1443 | None => { |
| 1437 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1444 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1438 | .fail("Missing repo_id in ValidRepo"); | 1445 | .fail("Missing repo_id in ValidRepo"); |
| 1439 | } | 1446 | } |
| 1440 | }; | 1447 | }; |
| @@ -1442,7 +1449,7 @@ impl PushAuthorizationTests { | |||
| 1442 | let npub = match valid_repo.pubkey.to_bech32() { | 1449 | let npub = match valid_repo.pubkey.to_bech32() { |
| 1443 | Ok(n) => n, | 1450 | Ok(n) => n, |
| 1444 | Err(e) => { | 1451 | Err(e) => { |
| 1445 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1452 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1446 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 1453 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| 1447 | } | 1454 | } |
| 1448 | }; | 1455 | }; |
| @@ -1454,16 +1461,16 @@ impl PushAuthorizationTests { | |||
| 1454 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { | 1461 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { |
| 1455 | Ok(branch) => branch, | 1462 | Ok(branch) => branch, |
| 1456 | Err(e) => { | 1463 | Err(e) => { |
| 1457 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1464 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1458 | .fail(format!("Failed to get default branch: {}", e)); | 1465 | .fail(format!("Failed to get default branch: {}", e)); |
| 1459 | } | 1466 | } |
| 1460 | }; | 1467 | }; |
| 1461 | 1468 | ||
| 1462 | // Verify HEAD points to refs/heads/develop | 1469 | // Verify HEAD points to refs/heads/develop |
| 1463 | if default_branch == "refs/heads/develop" { | 1470 | if default_branch == "refs/heads/develop" { |
| 1464 | TestResult::new(test_name, "GRASP-01:git-http:38", desc).pass() | 1471 | TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).pass() |
| 1465 | } else { | 1472 | } else { |
| 1466 | TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( | 1473 | TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail(format!( |
| 1467 | "Expected HEAD to point to 'refs/heads/develop' but got '{}'. \ | 1474 | "Expected HEAD to point to 'refs/heads/develop' but got '{}'. \ |
| 1468 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ | 1475 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ |
| 1469 | as soon as the git data related to that branch has been received.'", | 1476 | as soon as the git data related to that branch has been received.'", |
| @@ -1512,20 +1519,19 @@ impl PushAuthorizationTests { | |||
| 1512 | let _develop_state = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await { | 1519 | let _develop_state = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await { |
| 1513 | Ok(e) => e, | 1520 | Ok(e) => e, |
| 1514 | Err(e) => { | 1521 | Err(e) => { |
| 1515 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( | 1522 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail( |
| 1516 | "Failed to create HeadSetToDevelopBranch fixture: {}", | 1523 | format!("Failed to create HeadSetToDevelopBranch fixture: {}", e), |
| 1517 | e | 1524 | ); |
| 1518 | )); | ||
| 1519 | } | 1525 | } |
| 1520 | }; | 1526 | }; |
| 1521 | 1527 | ||
| 1522 | // ============================================================ | 1528 | // ============================================================ |
| 1523 | // Step 2: Extract repo_id and owner npub from ValidRepo (cached by fixture) | 1529 | // Step 2: Extract repo_id and owner npub from ValidRepo (cached by fixture) |
| 1524 | // ============================================================ | 1530 | // ============================================================ |
| 1525 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1531 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepoSent).await { |
| 1526 | Ok(e) => e, | 1532 | Ok(e) => e, |
| 1527 | Err(e) => { | 1533 | Err(e) => { |
| 1528 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1534 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1529 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); | 1535 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); |
| 1530 | } | 1536 | } |
| 1531 | }; | 1537 | }; |
| @@ -1538,7 +1544,7 @@ impl PushAuthorizationTests { | |||
| 1538 | { | 1544 | { |
| 1539 | Some(id) => id.to_string(), | 1545 | Some(id) => id.to_string(), |
| 1540 | None => { | 1546 | None => { |
| 1541 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1547 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1542 | .fail("Missing repo_id in ValidRepo"); | 1548 | .fail("Missing repo_id in ValidRepo"); |
| 1543 | } | 1549 | } |
| 1544 | }; | 1550 | }; |
| @@ -1546,7 +1552,7 @@ impl PushAuthorizationTests { | |||
| 1546 | let npub = match valid_repo.pubkey.to_bech32() { | 1552 | let npub = match valid_repo.pubkey.to_bech32() { |
| 1547 | Ok(n) => n, | 1553 | Ok(n) => n, |
| 1548 | Err(e) => { | 1554 | Err(e) => { |
| 1549 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1555 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1550 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 1556 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| 1551 | } | 1557 | } |
| 1552 | }; | 1558 | }; |
| @@ -1557,7 +1563,7 @@ impl PushAuthorizationTests { | |||
| 1557 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | 1563 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { |
| 1558 | Ok(path) => path, | 1564 | Ok(path) => path, |
| 1559 | Err(e) => { | 1565 | Err(e) => { |
| 1560 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1566 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1561 | .fail(format!("Failed to clone repo: {}", e)); | 1567 | .fail(format!("Failed to clone repo: {}", e)); |
| 1562 | } | 1568 | } |
| 1563 | }; | 1569 | }; |
| @@ -1572,7 +1578,7 @@ impl PushAuthorizationTests { | |||
| 1572 | 1578 | ||
| 1573 | if let Err(e) = output { | 1579 | if let Err(e) = output { |
| 1574 | let _ = fs::remove_dir_all(&clone_path); | 1580 | let _ = fs::remove_dir_all(&clone_path); |
| 1575 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1581 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1576 | .fail(format!("Failed to create develop1 branch: {}", e)); | 1582 | .fail(format!("Failed to create develop1 branch: {}", e)); |
| 1577 | } | 1583 | } |
| 1578 | 1584 | ||
| @@ -1581,7 +1587,7 @@ impl PushAuthorizationTests { | |||
| 1581 | Ok(hash) => hash, | 1587 | Ok(hash) => hash, |
| 1582 | Err(e) => { | 1588 | Err(e) => { |
| 1583 | let _ = fs::remove_dir_all(&clone_path); | 1589 | let _ = fs::remove_dir_all(&clone_path); |
| 1584 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1590 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1585 | .fail(format!("Failed to create commit: {}", e)); | 1591 | .fail(format!("Failed to create commit: {}", e)); |
| 1586 | } | 1592 | } |
| 1587 | }; | 1593 | }; |
| @@ -1610,7 +1616,7 @@ impl PushAuthorizationTests { | |||
| 1610 | Ok(e) => e, | 1616 | Ok(e) => e, |
| 1611 | Err(e) => { | 1617 | Err(e) => { |
| 1612 | let _ = fs::remove_dir_all(&clone_path); | 1618 | let _ = fs::remove_dir_all(&clone_path); |
| 1613 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1619 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1614 | .fail(format!("Failed to build state event: {}", e)); | 1620 | .fail(format!("Failed to build state event: {}", e)); |
| 1615 | } | 1621 | } |
| 1616 | }; | 1622 | }; |
| @@ -1621,7 +1627,7 @@ impl PushAuthorizationTests { | |||
| 1621 | .await | 1627 | .await |
| 1622 | { | 1628 | { |
| 1623 | let _ = fs::remove_dir_all(&clone_path); | 1629 | let _ = fs::remove_dir_all(&clone_path); |
| 1624 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1630 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1625 | .fail(format!("Failed to send state event: {}", e)); | 1631 | .fail(format!("Failed to send state event: {}", e)); |
| 1626 | } | 1632 | } |
| 1627 | 1633 | ||
| @@ -1634,11 +1640,11 @@ impl PushAuthorizationTests { | |||
| 1634 | match push_result { | 1640 | match push_result { |
| 1635 | Ok(true) => { /* Push succeeded, continue to verify */ } | 1641 | Ok(true) => { /* Push succeeded, continue to verify */ } |
| 1636 | Ok(false) => { | 1642 | Ok(false) => { |
| 1637 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1643 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1638 | .fail("Push to refs/heads/develop1 was rejected"); | 1644 | .fail("Push to refs/heads/develop1 was rejected"); |
| 1639 | } | 1645 | } |
| 1640 | Err(e) => { | 1646 | Err(e) => { |
| 1641 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1647 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1642 | .fail(format!("Failed to push develop1 branch: {}", e)); | 1648 | .fail(format!("Failed to push develop1 branch: {}", e)); |
| 1643 | } | 1649 | } |
| 1644 | } | 1650 | } |
| @@ -1651,16 +1657,16 @@ impl PushAuthorizationTests { | |||
| 1651 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { | 1657 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { |
| 1652 | Ok(branch) => branch, | 1658 | Ok(branch) => branch, |
| 1653 | Err(e) => { | 1659 | Err(e) => { |
| 1654 | return TestResult::new(test_name, "GRASP-01:git-http:38", desc) | 1660 | return TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc) |
| 1655 | .fail(format!("Failed to get default branch: {}", e)); | 1661 | .fail(format!("Failed to get default branch: {}", e)); |
| 1656 | } | 1662 | } |
| 1657 | }; | 1663 | }; |
| 1658 | 1664 | ||
| 1659 | // Verify HEAD points to refs/heads/develop1 | 1665 | // Verify HEAD points to refs/heads/develop1 |
| 1660 | if default_branch == "refs/heads/develop1" { | 1666 | if default_branch == "refs/heads/develop1" { |
| 1661 | TestResult::new(test_name, "GRASP-01:git-http:38", desc).pass() | 1667 | TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).pass() |
| 1662 | } else { | 1668 | } else { |
| 1663 | TestResult::new(test_name, "GRASP-01:git-http:38", desc).fail(format!( | 1669 | TestResult::new(test_name, SpecRef::GitSetHeadOnReceive, desc).fail(format!( |
| 1664 | "Expected HEAD to point to 'refs/heads/develop1' but got '{}'. \ | 1670 | "Expected HEAD to point to 'refs/heads/develop1' but got '{}'. \ |
| 1665 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ | 1671 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ |
| 1666 | as soon as the git data related to that branch has been received.'", | 1672 | as soon as the git data related to that branch has been received.'", |
| @@ -1701,24 +1707,24 @@ mod tests { | |||
| 1701 | String::from_utf8_lossy(&output.stderr) | 1707 | String::from_utf8_lossy(&output.stderr) |
| 1702 | ); | 1708 | ); |
| 1703 | 1709 | ||
| 1704 | // Configure git user - use PR Test Author identity | 1710 | // Configure git user - use same identity as clone_repo in fixtures.rs |
| 1705 | let output = Command::new("git") | 1711 | let output = Command::new("git") |
| 1706 | .args(["config", "user.email", "pr-test@example.com"]) | 1712 | .args(["config", "user.email", "test@grasp-audit.local"]) |
| 1707 | .current_dir(path) | 1713 | .current_dir(path) |
| 1708 | .output() | 1714 | .output() |
| 1709 | .expect("git config email failed"); | 1715 | .expect("git config email failed"); |
| 1710 | assert!(output.status.success(), "git config email failed"); | 1716 | assert!(output.status.success(), "git config email failed"); |
| 1711 | 1717 | ||
| 1712 | let output = Command::new("git") | 1718 | let output = Command::new("git") |
| 1713 | .args(["config", "user.name", "PR Test Author"]) | 1719 | .args(["config", "user.name", "GRASP Audit Test"]) |
| 1714 | .current_dir(path) | 1720 | .current_dir(path) |
| 1715 | .output() | 1721 | .output() |
| 1716 | .expect("git config name failed"); | 1722 | .expect("git config name failed"); |
| 1717 | assert!(output.status.success(), "git config name failed"); | 1723 | assert!(output.status.success(), "git config name failed"); |
| 1718 | 1724 | ||
| 1719 | // Create the deterministic file content | 1725 | // Create the deterministic file content (must match CommitVariant::PRTestCommit exactly) |
| 1720 | let test_file = path.join("test.txt"); | 1726 | let test_file = path.join("test.txt"); |
| 1721 | fs::write(&test_file, "PR test deterministic commit").expect("Failed to write test file"); | 1727 | fs::write(&test_file, "PR test deterministic commit\n").expect("Failed to write test file"); |
| 1722 | 1728 | ||
| 1723 | // Add the file | 1729 | // Add the file |
| 1724 | let output = Command::new("git") | 1730 | let output = Command::new("git") |