upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-27 15:49:27 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-27 15:49:27 +0000
commitbf93f737aeec7b0ba6d007e867a55a8528615c23 (patch)
tree986dabfa0d1486d3f7b83c2a982e8424c296c964 /grasp-audit/src
parent6a77173127b5915c4c1b9219924e793795e0d051 (diff)
Task 2: Refactor owner push authorization test to fixture-first pattern
- Refactored test_push_authorized_by_owner_state to use fixture-first pattern - Test now creates its own TestContext and uses FixtureKind::RepoState - Uses git helper functions from fixtures.rs (clone_repo, create_deterministic_commit, try_push) - Follows the 3-step pattern: Generate fixtures → Send to relay → Verify behavior - Deprecated setup_repo_with_deterministic_commit with migration guide - Test passes: cargo test --test push_authorization test_push_authorized_by_owner_state - No API changes required for main project tests
Diffstat (limited to 'grasp-audit/src')
-rw-r--r--grasp-audit/src/fixtures.rs29
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs168
2 files changed, 188 insertions, 9 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs
index 45a413d..02e9810 100644
--- a/grasp-audit/src/fixtures.rs
+++ b/grasp-audit/src/fixtures.rs
@@ -1108,6 +1108,31 @@ impl Drop for RepoSetup {
1108 1108
1109/// Set up a repository with deterministic commit for testing 1109/// Set up a repository with deterministic commit for testing
1110/// 1110///
1111/// # Deprecated
1112///
1113/// This function is deprecated in favor of the fixture-first pattern.
1114/// Tests should create their own TestContext and use `FixtureKind::RepoState`
1115/// directly, following the Generate → Send → Verify pattern.
1116///
1117/// See `test_push_authorized_by_owner_state` in `push_authorization.rs` for
1118/// an example of the fixture-first pattern.
1119///
1120/// ## Migration Guide
1121///
1122/// Instead of:
1123/// ```ignore
1124/// let setup = setup_repo_with_deterministic_commit(client, git_data_dir, relay_domain).await?;
1125/// ```
1126///
1127/// Use:
1128/// ```ignore
1129/// let ctx = TestContext::new(client);
1130/// let state_event = ctx.get_fixture(FixtureKind::RepoState).await?;
1131/// // Then clone, create deterministic commit, and push inline
1132/// ```
1133///
1134/// ---
1135///
1111/// This performs all the common setup steps needed for push authorization tests: 1136/// This performs all the common setup steps needed for push authorization tests:
1112/// 1. Gets RepoState fixture (repo announcement + state event with deterministic commit) 1137/// 1. Gets RepoState fixture (repo announcement + state event with deterministic commit)
1113/// 2. Extracts repo_id and npub 1138/// 2. Extracts repo_id and npub
@@ -1128,6 +1153,10 @@ impl Drop for RepoSetup {
1128/// # Returns 1153/// # Returns
1129/// * `Ok(RepoSetup)` - The setup data 1154/// * `Ok(RepoSetup)` - The setup data
1130/// * `Err(String)` - Error message if setup failed 1155/// * `Err(String)` - Error message if setup failed
1156#[deprecated(
1157 since = "0.1.0",
1158 note = "Use fixture-first pattern with TestContext and FixtureKind::RepoState instead. See test_push_authorized_by_owner_state for example."
1159)]
1131pub async fn setup_repo_with_deterministic_commit( 1160pub async fn setup_repo_with_deterministic_commit(
1132 client: &crate::AuditClient, 1161 client: &crate::AuditClient,
1133 git_data_dir: &Path, 1162 git_data_dir: &Path,
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs
index d58247d..7b7e9dc 100644
--- a/grasp-audit/src/specs/grasp01/push_authorization.rs
+++ b/grasp-audit/src/specs/grasp01/push_authorization.rs
@@ -17,9 +17,9 @@
17//! ``` 17//! ```
18 18
19use crate::{ 19use crate::{
20 clone_repo, create_commit, setup_repo_for_maintainer, setup_repo_for_recursive_maintainer, 20 clone_repo, create_commit, create_deterministic_commit, setup_repo_for_maintainer,
21 setup_repo_with_deterministic_commit, try_push, AuditClient, FixtureKind, TestContext, 21 setup_repo_for_recursive_maintainer, setup_repo_with_deterministic_commit, try_push,
22 TestResult, 22 AuditClient, FixtureKind, TestContext, TestResult, DETERMINISTIC_COMMIT_HASH,
23}; 23};
24use nostr_sdk::prelude::*; 24use nostr_sdk::prelude::*;
25use std::fs; 25use std::fs;
@@ -33,23 +33,173 @@ impl PushAuthorizationTests {
33 /// 33 ///
34 /// GRASP-01: "MUST accept pushes via this service that match the latest 34 /// GRASP-01: "MUST accept pushes via this service that match the latest
35 /// repo state announcement on the relay" 35 /// repo state announcement on the relay"
36 ///
37 /// ## Fixture-First Pattern
38 ///
39 /// 1. **Generate**: Create TestContext and get RepoState fixture
40 /// (repo announcement + state event pointing to deterministic commit)
41 /// 2. **Send**: Clone repo, create deterministic commit locally, push to relay
42 /// 3. **Verify**: Push should succeed because state event authorizes this commit
36 pub async fn test_push_authorized_by_owner_state( 43 pub async fn test_push_authorized_by_owner_state(
37 client: &AuditClient, 44 client: &AuditClient,
38 git_data_dir: &Path, 45 git_data_dir: &Path,
39 relay_domain: &str, 46 relay_domain: &str,
40 ) -> TestResult { 47 ) -> TestResult {
48 use std::process::Command;
49
41 let test_name = "test_push_authorized_by_owner_state"; 50 let test_name = "test_push_authorized_by_owner_state";
42 51
43 // this setup is exactly what we are testing 52 // ============================================================
44 match setup_repo_with_deterministic_commit(client, git_data_dir, relay_domain).await { 53 // Step 1: GENERATE - Create TestContext and get RepoState fixture
45 Ok(_) => { 54 // ============================================================
46 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass() 55 let ctx = TestContext::new(client);
47 }, 56
57 let state_event = match ctx.get_fixture(FixtureKind::RepoState).await {
58 Ok(e) => e,
59 Err(e) => {
60 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
61 .fail(&format!("Failed to create RepoState fixture: {}", e));
62 }
63 };
64
65 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
66
67 // Extract repo_id and npub from state event
68 let repo_id = match state_event
69 .tags
70 .iter()
71 .find(|t| t.kind() == TagKind::d())
72 .and_then(|t| t.content())
73 {
74 Some(id) => id.to_string(),
75 None => {
76 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
77 .fail("Missing repo_id in state event");
78 }
79 };
80
81 let npub = match state_event.pubkey.to_bech32() {
82 Ok(n) => n,
83 Err(e) => {
84 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
85 .fail(&format!("Failed to convert pubkey to bech32: {}", e));
86 }
87 };
88
89 // Verify repo exists on disk
90 let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id));
91 if !repo_path.exists() {
92 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
93 .fail(&format!("Repo not found: {}", repo_path.display()));
94 }
95
96 // ============================================================
97 // Step 2: SEND - Clone repo, create deterministic commit, push
98 // ============================================================
99 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) {
100 Ok(p) => p,
101 Err(e) => {
102 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
103 .fail(&format!("Failed to clone repo: {}", e));
104 }
105 };
106
107 // Cleanup helper
108 let cleanup = || {
109 let _ = fs::remove_dir_all(&clone_path);
110 };
111
112 // Create deterministic commit locally
113 let commit_hash = match create_deterministic_commit(&clone_path, "Initial commit") {
114 Ok(h) => h,
48 Err(e) => { 115 Err(e) => {
116 cleanup();
49 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") 117 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
50 .fail(&format!("Failed: {}", e)) 118 .fail(&format!("Failed to create deterministic commit: {}", e));
51 } 119 }
52 }; 120 };
121
122 // Verify commit hash matches expected
123 if commit_hash != DETERMINISTIC_COMMIT_HASH {
124 cleanup();
125 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
126 .fail(&format!(
127 "Commit hash mismatch: got {}, expected {}",
128 commit_hash, DETERMINISTIC_COMMIT_HASH
129 ));
130 }
131
132 // Create main branch pointing to our deterministic commit
133 let branch_output = Command::new("git")
134 .args(["branch", "main"])
135 .current_dir(&clone_path)
136 .output();
137
138 match branch_output {
139 Err(e) => {
140 cleanup();
141 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
142 .fail(&format!("Failed to create main branch: {}", e));
143 }
144 Ok(output) if !output.status.success() => {
145 cleanup();
146 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
147 .fail(&format!(
148 "Failed to create main branch: {}",
149 String::from_utf8_lossy(&output.stderr)
150 ));
151 }
152 _ => {}
153 }
154
155 // Checkout main branch
156 let checkout_output = Command::new("git")
157 .args(["checkout", "main"])
158 .current_dir(&clone_path)
159 .output();
160
161 match checkout_output {
162 Err(e) => {
163 cleanup();
164 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
165 .fail(&format!("Failed to checkout main branch: {}", e));
166 }
167 Ok(output) if !output.status.success() => {
168 cleanup();
169 return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
170 .fail(&format!(
171 "Failed to checkout main branch: {}",
172 String::from_utf8_lossy(&output.stderr)
173 ));
174 }
175 _ => {}
176 }
177
178 // ============================================================
179 // Step 3: VERIFY - Push should succeed because state event
180 // authorizes this commit
181 // ============================================================
182 let push_result = try_push(&clone_path);
183 cleanup();
184
185 match push_result {
186 Ok(true) => {
187 TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass()
188 }
189 Ok(false) => {
190 TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").fail(
191 &format!(
192 "Push was rejected but should have been accepted. \
193 The state event points to commit {} which matches the pushed commit.",
194 DETERMINISTIC_COMMIT_HASH
195 ),
196 )
197 }
198 Err(e) => {
199 TestResult::new(test_name, "GRASP-01", "Push authorized with matching state")
200 .fail(&format!("Push error: {}", e))
201 }
202 }
53 } 203 }
54 204
55 /// Test that push is rejected when no state event exists 205 /// Test that push is rejected when no state event exists