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 | |
| parent | b95460cb136f3e022896148afb9c67755af5d832 (diff) | |
better fixtures: MaintainerStateDataPushed
Diffstat (limited to 'grasp-audit/src')
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 165 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 200 |
2 files changed, 187 insertions, 178 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index 062bb9b..fef5c5c 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -201,6 +201,27 @@ pub enum FixtureKind { | |||
| 201 | /// - Points to DETERMINISTIC_COMMIT_HASH | 201 | /// - Points to DETERMINISTIC_COMMIT_HASH |
| 202 | /// - Git push verified to succeed (state matches pushed commit) | 202 | /// - Git push verified to succeed (state matches pushed commit) |
| 203 | OwnerStateDataPushed, | 203 | OwnerStateDataPushed, |
| 204 | |||
| 205 | /// Maintainer's state event with git data successfully pushed (full 4-stage fixture) | ||
| 206 | /// | ||
| 207 | /// This fixture tests that a maintainer can authorize pushes with ONLY a state event, | ||
| 208 | /// without publishing their own repo announcement. The maintainer is still listed in | ||
| 209 | /// the owner's announcement, so they're a valid maintainer. | ||
| 210 | /// | ||
| 211 | /// GRASP-01: "respecting the recursive maintainer set" | ||
| 212 | /// | ||
| 213 | /// Stages: | ||
| 214 | /// 1. **Generated**: Creates ValidRepo (owner's announcement with maintainer in maintainers tag) | ||
| 215 | /// + MaintainerState (maintainer's state event ONLY - no announcement) | ||
| 216 | /// 2. **Sent**: Sends events to relay | ||
| 217 | /// 3. **Verified**: Confirms events accepted by relay | ||
| 218 | /// 4. **DataPushed**: Clones repo, creates maintainer deterministic commit, pushes to relay | ||
| 219 | /// | ||
| 220 | /// - Requires ValidRepo (owner's announcement lists maintainer) | ||
| 221 | /// - State event signed by maintainer keys (`client.maintainer_keys()`) | ||
| 222 | /// - Points to MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 223 | /// - Git push verified to succeed (maintainer's state event authorizes the commit) | ||
| 224 | MaintainerStateDataPushed, | ||
| 204 | } | 225 | } |
| 205 | 226 | ||
| 206 | /// Context mode for fixture management | 227 | /// Context mode for fixture management |
| @@ -781,6 +802,10 @@ impl<'a> TestContext<'a> { | |||
| 781 | FixtureKind::OwnerStateDataPushed => { | 802 | FixtureKind::OwnerStateDataPushed => { |
| 782 | self.build_owner_state_data_pushed().await | 803 | self.build_owner_state_data_pushed().await |
| 783 | } | 804 | } |
| 805 | |||
| 806 | FixtureKind::MaintainerStateDataPushed => { | ||
| 807 | self.build_maintainer_state_data_pushed().await | ||
| 808 | } | ||
| 784 | } | 809 | } |
| 785 | } | 810 | } |
| 786 | 811 | ||
| @@ -1099,6 +1124,146 @@ impl<'a> TestContext<'a> { | |||
| 1099 | } | 1124 | } |
| 1100 | } | 1125 | } |
| 1101 | 1126 | ||
| 1127 | /// Build MaintainerStateDataPushed fixture: full 4-stage fixture for maintainer push authorization | ||
| 1128 | /// | ||
| 1129 | /// This tests that a maintainer can authorize pushes with ONLY a state event, | ||
| 1130 | /// without publishing their own repo announcement. | ||
| 1131 | /// | ||
| 1132 | /// # Returns | ||
| 1133 | /// The maintainer's state event (kind 30618) after all stages complete successfully | ||
| 1134 | async fn build_maintainer_state_data_pushed(&self) -> Result<Event> { | ||
| 1135 | use nostr_sdk::prelude::*; | ||
| 1136 | |||
| 1137 | // ============================================================ | ||
| 1138 | // Stage 1 & 2: Generate and Send ValidRepo + MaintainerState fixtures | ||
| 1139 | // ============================================================ | ||
| 1140 | |||
| 1141 | // Get owner's repo (ValidRepo) - this includes maintainer in maintainers tag | ||
| 1142 | let repo = self.get_or_create_repo().await?; | ||
| 1143 | |||
| 1144 | // Extract repo_id from repo announcement | ||
| 1145 | let repo_id = repo | ||
| 1146 | .tags | ||
| 1147 | .iter() | ||
| 1148 | .find(|t| t.kind() == TagKind::d()) | ||
| 1149 | .and_then(|t| t.content()) | ||
| 1150 | .ok_or_else(|| anyhow::anyhow!("Missing d tag in repo announcement"))? | ||
| 1151 | .to_string(); | ||
| 1152 | |||
| 1153 | // Build maintainer's state event (state event ONLY - no announcement) | ||
| 1154 | let base_time = Timestamp::now().as_u64(); | ||
| 1155 | let maintainer_timestamp = Timestamp::from(base_time - 5); // 5 seconds ago (more recent than owner's state) | ||
| 1156 | |||
| 1157 | let maintainer_state_event = self | ||
| 1158 | .client | ||
| 1159 | .event_builder(Kind::Custom(30618), "") | ||
| 1160 | .tag(Tag::identifier(&repo_id)) | ||
| 1161 | .tag(Tag::custom( | ||
| 1162 | TagKind::custom("refs/heads/main"), | ||
| 1163 | vec![MAINTAINER_DETERMINISTIC_COMMIT_HASH.to_string()], | ||
| 1164 | )) | ||
| 1165 | .tag(Tag::custom( | ||
| 1166 | TagKind::custom("HEAD"), | ||
| 1167 | vec!["ref: refs/heads/main".to_string()], | ||
| 1168 | )) | ||
| 1169 | .custom_time(maintainer_timestamp) | ||
| 1170 | .build(self.client.maintainer_keys()) | ||
| 1171 | .map_err(|e| anyhow::anyhow!("Failed to build maintainer state event: {}", e))?; | ||
| 1172 | |||
| 1173 | // Send maintainer state event to relay | ||
| 1174 | self.client.send_event(maintainer_state_event.clone()).await?; | ||
| 1175 | |||
| 1176 | // ============================================================ | ||
| 1177 | // Stage 3: Verify state event was accepted | ||
| 1178 | // ============================================================ | ||
| 1179 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 1180 | |||
| 1181 | // ============================================================ | ||
| 1182 | // Stage 4: DataPushed - Clone repo, create maintainer commit, push | ||
| 1183 | // ============================================================ | ||
| 1184 | |||
| 1185 | // Get relay domain from connected relay | ||
| 1186 | let relay_domain = self.get_relay_domain().await?; | ||
| 1187 | |||
| 1188 | // Use owner's npub for cloning (repo belongs to owner) | ||
| 1189 | let npub = repo | ||
| 1190 | .pubkey | ||
| 1191 | .to_bech32() | ||
| 1192 | .map_err(|e| anyhow::anyhow!("Failed to convert pubkey to bech32: {}", e))?; | ||
| 1193 | |||
| 1194 | // Clone the repository | ||
| 1195 | let clone_path = clone_repo(&relay_domain, &npub, &repo_id) | ||
| 1196 | .map_err(|e| anyhow::anyhow!("Failed to clone repo: {}", e))?; | ||
| 1197 | |||
| 1198 | // Cleanup helper (always clean up on error or success) | ||
| 1199 | let cleanup = |path: &PathBuf| { | ||
| 1200 | let _ = fs::remove_dir_all(path); | ||
| 1201 | }; | ||
| 1202 | |||
| 1203 | // Reset to orphan state and create deterministic root commit | ||
| 1204 | // Step 1: Create orphan branch (removes all history) | ||
| 1205 | let _ = Command::new("git") | ||
| 1206 | .args(["checkout", "--orphan", "main-new"]) | ||
| 1207 | .current_dir(&clone_path) | ||
| 1208 | .output(); | ||
| 1209 | |||
| 1210 | // Step 2: Clear staged files (orphan keeps files staged from previous branch) | ||
| 1211 | let _ = Command::new("git") | ||
| 1212 | .args(["rm", "-rf", "--cached", "."]) | ||
| 1213 | .current_dir(&clone_path) | ||
| 1214 | .output(); | ||
| 1215 | |||
| 1216 | // Step 3: Create deterministic commit using maintainer variant | ||
| 1217 | let commit_hash = match create_deterministic_commit_with_variant( | ||
| 1218 | &clone_path, | ||
| 1219 | CommitVariant::Maintainer, | ||
| 1220 | ) { | ||
| 1221 | Ok(h) => h, | ||
| 1222 | Err(e) => { | ||
| 1223 | cleanup(&clone_path); | ||
| 1224 | return Err(anyhow::anyhow!("Failed to create maintainer commit: {}", e)); | ||
| 1225 | } | ||
| 1226 | }; | ||
| 1227 | |||
| 1228 | // Step 4: Replace main branch with our new orphan branch | ||
| 1229 | let _ = Command::new("git") | ||
| 1230 | .args(["branch", "-D", "main"]) | ||
| 1231 | .current_dir(&clone_path) | ||
| 1232 | .output(); | ||
| 1233 | |||
| 1234 | let _ = Command::new("git") | ||
| 1235 | .args(["branch", "-m", "main"]) | ||
| 1236 | .current_dir(&clone_path) | ||
| 1237 | .output(); | ||
| 1238 | |||
| 1239 | // Verify commit hash matches expected | ||
| 1240 | if commit_hash != MAINTAINER_DETERMINISTIC_COMMIT_HASH { | ||
| 1241 | cleanup(&clone_path); | ||
| 1242 | return Err(anyhow::anyhow!( | ||
| 1243 | "Maintainer commit hash mismatch: got {}, expected {}", | ||
| 1244 | commit_hash, | ||
| 1245 | MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1246 | )); | ||
| 1247 | } | ||
| 1248 | |||
| 1249 | // Push to relay | ||
| 1250 | let push_result = try_push(&clone_path); | ||
| 1251 | cleanup(&clone_path); | ||
| 1252 | |||
| 1253 | match push_result { | ||
| 1254 | Ok(true) => Ok(maintainer_state_event), | ||
| 1255 | Ok(false) => Err(anyhow::anyhow!( | ||
| 1256 | "Push was rejected but should have been accepted. \ | ||
| 1257 | The maintainer published a state event with commit {}, \ | ||
| 1258 | and even without a separate announcement, the relay should \ | ||
| 1259 | authorize pushes matching this state event since the maintainer \ | ||
| 1260 | is listed in the owner's announcement.", | ||
| 1261 | MAINTAINER_DETERMINISTIC_COMMIT_HASH | ||
| 1262 | )), | ||
| 1263 | Err(e) => Err(anyhow::anyhow!("Push error: {}", e)), | ||
| 1264 | } | ||
| 1265 | } | ||
| 1266 | |||
| 1102 | /// Get relay domain (host:port) from the connected relay | 1267 | /// Get relay domain (host:port) from the connected relay |
| 1103 | /// | 1268 | /// |
| 1104 | /// Extracts the domain from the relay URL for git HTTP operations. | 1269 | /// Extracts the domain from the relay URL for git HTTP operations. |
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 | ||