upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/specs/grasp01
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 21:20:58 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 21:20:58 +0000
commit80053758daf365896cdfd2b9a40496adad229ce9 (patch)
tree8fe2c6c59ebe420dc5cdda2e24d332f16bc38b1f /grasp-audit/src/specs/grasp01
parentb95460cb136f3e022896148afb9c67755af5d832 (diff)
better fixtures: MaintainerStateDataPushed
Diffstat (limited to 'grasp-audit/src/specs/grasp01')
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs200
1 files changed, 22 insertions, 178 deletions
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs
index 1e28f8c..f6a2314 100644
--- a/grasp-audit/src/specs/grasp01/push_authorization.rs
+++ b/grasp-audit/src/specs/grasp01/push_authorization.rs
@@ -594,7 +594,7 @@ impl PushAuthorizationTests {
594 /// 4. **DataPushed**: Clones repo, creates deterministic commit, pushes to relay 594 /// 4. **DataPushed**: Clones repo, creates deterministic commit, pushes to relay
595 /// 595 ///
596 /// The test wraps the fixture result in pass/fail using the error message. 596 /// The test wraps the fixture result in pass/fail using the error message.
597 #[allow(unused_variables)] // relay_domain is now handled by fixture 597 #[allow(unused_variables)] // relay_domain is now handled by fixture
598 pub async fn test_push_authorized_by_owner_state( 598 pub async fn test_push_authorized_by_owner_state(
599 client: &AuditClient, 599 client: &AuditClient,
600 relay_domain: &str, 600 relay_domain: &str,
@@ -608,10 +608,8 @@ impl PushAuthorizationTests {
608 Ok(_state_event) => { 608 Ok(_state_event) => {
609 TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass() 609 TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass()
610 } 610 }
611 Err(e) => { 611 Err(e) => TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
612 TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") 612 .fail(format!("{}", e)),
613 .fail(format!("{}", e))
614 }
615 } 613 }
616 } 614 }
617 615
@@ -622,7 +620,7 @@ impl PushAuthorizationTests {
622 /// 620 ///
623 /// ## Fixture-First Pattern 621 /// ## Fixture-First Pattern
624 /// 622 ///
625 /// 1. **Generate**: Create TestContext and get RepoState fixture 623 /// 1. **Generate**: Create TestContext and get OwnerStateDataPushed fixture
626 /// (repo announcement + state event pointing to DETERMINISTIC_COMMIT_HASH) 624 /// (repo announcement + state event pointing to DETERMINISTIC_COMMIT_HASH)
627 /// 2. **Send**: Clone repo, create WRONG deterministic commit (Maintainer variant), 625 /// 2. **Send**: Clone repo, create WRONG deterministic commit (Maintainer variant),
628 /// try to push 626 /// try to push
@@ -640,12 +638,12 @@ impl PushAuthorizationTests {
640 let test_name = "test_push_rejected_wrong_commit"; 638 let test_name = "test_push_rejected_wrong_commit";
641 639
642 // ============================================================ 640 // ============================================================
643 // Step 1: GENERATE - Create TestContext and get RepoState fixture 641 // Step 1: GENERATE - Create TestContext and get OwnerStateDataPushed fixture
644 // The state event points to DETERMINISTIC_COMMIT_HASH 642 // The state event points to DETERMINISTIC_COMMIT_HASH
645 // ============================================================ 643 // ============================================================
646 let ctx = TestContext::new(client); 644 let ctx = TestContext::new(client);
647 645
648 let state_event = match ctx.get_fixture(FixtureKind::RepoState).await { 646 let state_event = match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await {
649 Ok(e) => e, 647 Ok(e) => e,
650 Err(e) => { 648 Err(e) => {
651 return TestResult::new( 649 return TestResult::new(
@@ -775,195 +773,41 @@ impl PushAuthorizationTests {
775 /// without publishing their own repo announcement. The maintainer is still 773 /// without publishing their own repo announcement. The maintainer is still
776 /// listed in the owner's announcement, so they're a valid maintainer. 774 /// listed in the owner's announcement, so they're a valid maintainer.
777 /// 775 ///
778 /// ## Fixture-First Pattern 776 /// This test uses the MaintainerStateDataPushed fixture which handles all 4 stages:
779 /// 777 /// 1. **Generated**: Creates ValidRepo (owner's announcement with maintainer in maintainers tag)
780 /// 1. **Generate**: Create TestContext, get RepoState (owner) and MaintainerState fixtures 778 /// + MaintainerState (maintainer's state event ONLY - no announcement)
781 /// 2. **Send**: Clone repo, create maintainer deterministic commit, push to relay 779 /// 2. **Sent**: Sends events to relay
782 /// 3. **Verify**: Push should succeed because maintainer's state event authorizes this commit 780 /// 3. **Verified**: Confirms events accepted by relay
781 /// 4. **DataPushed**: Clones repo, creates maintainer deterministic commit, pushes to relay
783 /// 782 ///
784 /// Scenario: 783 /// The test wraps the fixture result in pass/fail using the error message.
785 /// 1. Owner's repo announcement lists maintainer in maintainers tag 784 #[allow(unused_variables)] // relay_domain is now handled by fixture
786 /// 2. Maintainer publishes ONLY a state event (no announcement)
787 /// 3. Clone, create maintainer commit, verify hash, push
788 /// 4. The push should be ACCEPTED because maintainer's state event authorizes it
789 pub async fn test_push_authorized_by_maintainer_state_only( 785 pub async fn test_push_authorized_by_maintainer_state_only(
790 client: &AuditClient, 786 client: &AuditClient,
791 relay_domain: &str, 787 relay_domain: &str,
792 ) -> TestResult { 788 ) -> TestResult {
793 use std::process::Command;
794
795 let test_name = "test_push_authorized_by_maintainer_state_only"; 789 let test_name = "test_push_authorized_by_maintainer_state_only";
796
797 // ============================================================
798 // Step 1: GENERATE - Create TestContext and get fixtures
799 // ============================================================
800 let ctx = TestContext::new(client); 790 let ctx = TestContext::new(client);
801 791
802 // Get RepoState fixture (owner's repo announcement + state event) 792 // The MaintainerStateDataPushed fixture handles all stages:
803 let state_event = match ctx.get_fixture(FixtureKind::RepoState).await { 793 // Generate → Send → Verify → DataPush
804 Ok(e) => e, 794 match ctx.get_fixture(FixtureKind::MaintainerStateDataPushed).await {
805 Err(e) => { 795 Ok(_maintainer_state_event) => {
806 return TestResult::new( 796 TestResult::new(
807 test_name,
808 "GRASP-01",
809 "Push authorized by maintainer state event only (no announcement)",
810 )
811 .fail(format!("Failed to create RepoState fixture: {}", e));
812 }
813 };
814
815 // Get MaintainerState fixture (maintainer's state event ONLY - no announcement)
816 // This tests that state-only authorization works without a maintainer announcement
817 match ctx.get_fixture(FixtureKind::MaintainerState).await {
818 Ok(_) => {}
819 Err(e) => {
820 return TestResult::new(
821 test_name,
822 "GRASP-01",
823 "Push authorized by maintainer state event only (no announcement)",
824 )
825 .fail(format!("Failed to create MaintainerState fixture: {}", e));
826 }
827 };
828
829 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
830
831 // Extract repo_id and npub from owner's state event
832 let repo_id = match state_event
833 .tags
834 .iter()
835 .find(|t| t.kind() == TagKind::d())
836 .and_then(|t| t.content())
837 {
838 Some(id) => id.to_string(),
839 None => {
840 return TestResult::new(
841 test_name,
842 "GRASP-01",
843 "Push authorized by maintainer state event only (no announcement)",
844 )
845 .fail("Missing repo_id in state event");
846 }
847 };
848
849 let npub = match state_event.pubkey.to_bech32() {
850 Ok(n) => n,
851 Err(e) => {
852 return TestResult::new(
853 test_name,
854 "GRASP-01",
855 "Push authorized by maintainer state event only (no announcement)",
856 )
857 .fail(format!("Failed to convert pubkey to bech32: {}", e));
858 }
859 };
860
861 // ============================================================
862 // Step 2: SEND - Clone, create maintainer commit, push
863 // ============================================================
864 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) {
865 Ok(p) => p,
866 Err(e) => {
867 return TestResult::new(
868 test_name, 797 test_name,
869 "GRASP-01", 798 "GRASP-01",
870 "Push authorized by maintainer state event only (no announcement)", 799 "Push authorized by maintainer state event only (no announcement)",
871 ) 800 )
872 .fail(&e); 801 .pass()
873 } 802 }
874 };
875 let cleanup = || {
876 let _ = fs::remove_dir_all(&clone_path);
877 };
878
879 // Reset to orphan state and create deterministic root commit
880 // Step 1: Create orphan branch (removes all history)
881 let _ = Command::new("git")
882 .args(["checkout", "--orphan", "main-new"])
883 .current_dir(&clone_path)
884 .output();
885
886 // Step 2: Clear staged files (orphan keeps files staged from previous branch)
887 let _ = Command::new("git")
888 .args(["rm", "-rf", "--cached", "."])
889 .current_dir(&clone_path)
890 .output();
891
892 // Step 3: Create deterministic commit using existing function
893 let commit_hash = match create_deterministic_commit_with_variant(
894 &clone_path,
895 CommitVariant::Maintainer,
896 ) {
897 Ok(h) => h,
898 Err(e) => { 803 Err(e) => {
899 cleanup(); 804 TestResult::new(
900 return TestResult::new(
901 test_name, 805 test_name,
902 "GRASP-01", 806 "GRASP-01",
903 "Push authorized by maintainer state event only (no announcement)", 807 "Push authorized by maintainer state event only (no announcement)",
904 ) 808 )
905 .fail(format!("Failed to create maintainer commit: {}", e)); 809 .fail(format!("{}", e))
906 } 810 }
907 };
908
909 // Step 4: Replace main branch with our new orphan branch
910 let _ = Command::new("git")
911 .args(["branch", "-D", "main"])
912 .current_dir(&clone_path)
913 .output();
914
915 let _ = Command::new("git")
916 .args(["branch", "-m", "main"])
917 .current_dir(&clone_path)
918 .output();
919
920 // Verify commit hash matches expected
921 if commit_hash != MAINTAINER_DETERMINISTIC_COMMIT_HASH {
922 cleanup();
923 return TestResult::new(
924 test_name,
925 "GRASP-01",
926 "Push authorized by maintainer state event only (no announcement)",
927 )
928 .fail(format!(
929 "Maintainer commit hash mismatch: got {}, expected {}",
930 commit_hash, MAINTAINER_DETERMINISTIC_COMMIT_HASH
931 ));
932 }
933
934 // ============================================================
935 // Step 3: VERIFY - Push should succeed because maintainer's
936 // state event authorizes this commit
937 // ============================================================
938 let push_result = try_push(&clone_path);
939 cleanup();
940
941 match push_result {
942 Ok(true) => TestResult::new(
943 test_name,
944 "GRASP-01",
945 "Push authorized by maintainer state event only (no announcement)",
946 )
947 .pass(),
948 Ok(false) => TestResult::new(
949 test_name,
950 "GRASP-01",
951 "Push authorized by maintainer state event only (no announcement)",
952 )
953 .fail(format!(
954 "Push was rejected but should have been accepted. \
955 The maintainer published a state event with commit {}, \
956 and even without a separate announcement, the relay should \
957 authorize pushes matching this state event since the maintainer \
958 is listed in the owner's announcement.",
959 MAINTAINER_DETERMINISTIC_COMMIT_HASH
960 )),
961 Err(e) => TestResult::new(
962 test_name,
963 "GRASP-01",
964 "Push authorized by maintainer state event only (no announcement)",
965 )
966 .fail(format!("Push error: {}", e)),
967 } 811 }
968 } 812 }
969 813