diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-01 21:20:58 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-01 21:20:58 +0000 |
| commit | 80053758daf365896cdfd2b9a40496adad229ce9 (patch) | |
| tree | 8fe2c6c59ebe420dc5cdda2e24d332f16bc38b1f /grasp-audit/src/specs/grasp01 | |
| parent | b95460cb136f3e022896148afb9c67755af5d832 (diff) | |
better fixtures: MaintainerStateDataPushed
Diffstat (limited to 'grasp-audit/src/specs/grasp01')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 200 |
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 | ||