upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 22:13:37 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 22:13:37 +0000
commit11870a0f810accf0431d82a74b6fd3adec9d23df (patch)
tree82a61995b1a18b4a618544b159e886d5c2e95015 /grasp-audit
parent883774c7785c57bded21ba3be63d0fdac8a51dcf (diff)
better fixtures: refs/nostr tests
Diffstat (limited to 'grasp-audit')
-rw-r--r--grasp-audit/src/fixtures.rs225
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs331
2 files changed, 373 insertions, 183 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs
index 5e8c50a..d054e36 100644
--- a/grasp-audit/src/fixtures.rs
+++ b/grasp-audit/src/fixtures.rs
@@ -188,6 +188,50 @@ pub enum FixtureKind {
188 /// - Timestamp: 1 second in the past 188 /// - Timestamp: 1 second in the past
189 PREvent, 189 PREvent,
190 190
191 /// PR event generated (built) but NOT sent to relay
192 ///
193 /// This is a "Generated" stage fixture - the event is created but not published.
194 /// Useful for tests that need the PR event ID before the event exists on the relay.
195 ///
196 /// - Requires ValidRepo (uses same repo_id)
197 /// - Signed by `client.pr_author_keys()`
198 /// - Kind 1618 (NIP-34 PR)
199 /// - Includes `c` tag pointing to PR_TEST_COMMIT_HASH
200 /// - NOT sent to relay (use `client.send_event()` to publish when ready)
201 PREventGenerated,
202
203 /// Wrong commit pushed to refs/nostr/<pr-event-id> BEFORE PR event is sent
204 ///
205 /// This is a "DataPushed" stage fixture for testing pre-event ref behavior.
206 /// The server has refs/nostr/<pr-event-id> pointing to DETERMINISTIC_COMMIT_HASH
207 /// (the "wrong" commit), but no PR event exists yet on the relay.
208 ///
209 /// Server state after this fixture:
210 /// - ValidRepo announcement on relay
211 /// - refs/nostr/<pr-event-id> exists on git server with wrong commit
212 /// - PR event is NOT on relay (but returned for tests to publish later)
213 ///
214 /// - Requires PREventGenerated (for the event ID)
215 /// - Clones repo, creates wrong commit, pushes to refs/nostr/<event-id>
216 /// - Returns: the unsent PR event (tests can publish it later)
217 PRWrongCommitPushedBeforeEvent,
218
219 /// PR event sent to relay AFTER wrong commit was pushed to refs/nostr/<pr-event-id>
220 ///
221 /// This is a compound fixture testing post-event behavior.
222 /// The server had refs/nostr/<pr-event-id> pointing to wrong commit,
223 /// then the PR event was published (which may trigger cleanup).
224 ///
225 /// Server state after this fixture:
226 /// - ValidRepo announcement on relay
227 /// - PR event is on relay
228 /// - refs/nostr/<pr-event-id> may have been cleaned up (that's what tests verify)
229 ///
230 /// - Requires PRWrongCommitPushedBeforeEvent
231 /// - Sends the PR event to relay
232 /// - Returns: the sent PR event
233 PREventSentAfterWrongPush,
234
191 /// Owner's state event with git data successfully pushed (full 4-stage fixture) 235 /// Owner's state event with git data successfully pushed (full 4-stage fixture)
192 /// 236 ///
193 /// This fixture represents the complete flow for testing push authorization: 237 /// This fixture represents the complete flow for testing push authorization:
@@ -268,6 +312,9 @@ impl FixtureKind {
268 Self::RecursiveMaintainerState => vec![Self::ValidRepo], 312 Self::RecursiveMaintainerState => vec![Self::ValidRepo],
269 Self::RecursiveMaintainerRepoAndState => vec![Self::ValidRepo], 313 Self::RecursiveMaintainerRepoAndState => vec![Self::ValidRepo],
270 Self::PREvent => vec![Self::ValidRepo], 314 Self::PREvent => vec![Self::ValidRepo],
315 Self::PREventGenerated => vec![Self::ValidRepo],
316 Self::PRWrongCommitPushedBeforeEvent => vec![Self::PREventGenerated],
317 Self::PREventSentAfterWrongPush => vec![Self::PRWrongCommitPushedBeforeEvent],
271 Self::OwnerStateDataPushed => vec![Self::ValidRepo], 318 Self::OwnerStateDataPushed => vec![Self::ValidRepo],
272 319
273 // Fixtures that depend on RepoWithIssue 320 // Fixtures that depend on RepoWithIssue
@@ -296,6 +343,12 @@ impl FixtureKind {
296 Self::RecursiveMaintainerStateDataPushed => true, 343 Self::RecursiveMaintainerStateDataPushed => true,
297 // RecursiveMaintainerRepoAndState sends multiple events internally 344 // RecursiveMaintainerRepoAndState sends multiple events internally
298 Self::RecursiveMaintainerRepoAndState => true, 345 Self::RecursiveMaintainerRepoAndState => true,
346 // PREventGenerated builds but does NOT send the PR event (that's the point)
347 Self::PREventGenerated => true,
348 // PRWrongCommitPushedBeforeEvent pushes git data but doesn't send event
349 Self::PRWrongCommitPushedBeforeEvent => true,
350 // PREventSentAfterWrongPush sends the PR event internally
351 Self::PREventSentAfterWrongPush => true,
299 // All other fixtures return a single event for the caller to send 352 // All other fixtures return a single event for the caller to send
300 _ => false, 353 _ => false,
301 } 354 }
@@ -753,6 +806,57 @@ impl<'a> TestContext<'a> {
753 .map_err(|e| anyhow::anyhow!("Failed to build PR event: {}", e)) 806 .map_err(|e| anyhow::anyhow!("Failed to build PR event: {}", e))
754 } 807 }
755 808
809 FixtureKind::PREventGenerated => {
810 // Same as PREvent but will NOT be sent to relay (caller may send it later)
811 // This fixture is for "Generated" stage only
812 use nostr_sdk::prelude::*;
813
814 // ValidRepo is ensured by ensure_fixture before this is called
815 let repo = self.get_cached_dependency(FixtureKind::ValidRepo)?;
816
817 let repo_id = repo
818 .tags
819 .iter()
820 .find(|t| t.kind() == TagKind::d())
821 .and_then(|t| t.content())
822 .ok_or_else(|| anyhow::anyhow!("Missing repo_id in ValidRepo fixture"))?
823 .to_string();
824
825 // Create PR event 1 second in the past
826 let base_time = Timestamp::now().as_u64();
827 let pr_timestamp = Timestamp::from(base_time - 1);
828
829 // Build NIP-34 PR event (kind 1618)
830 self.client
831 .event_builder(
832 Kind::Custom(1618), // NIP-34 PR kind (has 'c' tag for commit)
833 "Test PR for GRASP validation",
834 )
835 .tag(Tag::custom(
836 TagKind::custom("a"),
837 vec![format!(
838 "30617:{}:{}",
839 self.client.public_key().to_hex(), // Owner pubkey
840 repo_id
841 )],
842 ))
843 .tag(Tag::custom(
844 TagKind::custom("c"),
845 vec![PR_TEST_COMMIT_HASH.to_string()],
846 ))
847 .custom_time(pr_timestamp)
848 .build(self.client.pr_author_keys())
849 .map_err(|e| anyhow::anyhow!("Failed to build PR event: {}", e))
850 }
851
852 FixtureKind::PRWrongCommitPushedBeforeEvent => {
853 self.build_pr_wrong_commit_pushed_before_event().await
854 }
855
856 FixtureKind::PREventSentAfterWrongPush => {
857 self.build_pr_event_sent_after_wrong_push().await
858 }
859
756 FixtureKind::OwnerStateDataPushed => { 860 FixtureKind::OwnerStateDataPushed => {
757 self.build_owner_state_data_pushed().await 861 self.build_owner_state_data_pushed().await
758 } 862 }
@@ -1370,6 +1474,127 @@ impl<'a> TestContext<'a> {
1370 } 1474 }
1371 } 1475 }
1372 1476
1477 /// Build PRWrongCommitPushedBeforeEvent fixture
1478 ///
1479 /// This fixture sets up a scenario where:
1480 /// 1. A repo exists on the relay
1481 /// 2. A PR event is generated (but NOT sent to relay)
1482 /// 3. A wrong commit is pushed to refs/nostr/<pr-event-id>
1483 ///
1484 /// Server state after:
1485 /// - ValidRepo announcement on relay
1486 /// - refs/nostr/<pr-event-id> on git server pointing to DETERMINISTIC_COMMIT_HASH (wrong)
1487 /// - NO PR event on relay
1488 ///
1489 /// Returns: the unsent PR event (tests can publish it later)
1490 async fn build_pr_wrong_commit_pushed_before_event(&self) -> Result<Event> {
1491 use nostr_sdk::prelude::*;
1492
1493 // Get the cached PREventGenerated (the unsent PR event)
1494 let pr_event = self.get_cached_dependency(FixtureKind::PREventGenerated)?;
1495 let pr_event_id = pr_event.id.to_hex();
1496
1497 // Get the ValidRepo to extract repo info
1498 let repo = self.get_cached_dependency(FixtureKind::ValidRepo)?;
1499 let repo_id = self.extract_repo_id(&repo)?;
1500
1501 // Get relay domain for cloning
1502 let relay_domain = self.get_relay_domain().await?;
1503
1504 // Owner npub for clone URL
1505 let npub = repo
1506 .pubkey
1507 .to_bech32()
1508 .map_err(|e| anyhow::anyhow!("Failed to convert pubkey to bech32: {}", e))?;
1509
1510 // Clone the repository (fresh clone - local repos are never cached)
1511 let clone_path = clone_repo(&relay_domain, &npub, &repo_id)
1512 .map_err(|e| anyhow::anyhow!("Failed to clone repo: {}", e))?;
1513
1514 // Cleanup helper
1515 let cleanup = |path: &PathBuf| {
1516 let _ = fs::remove_dir_all(path);
1517 };
1518
1519 // Create a WRONG commit (Owner variant, not PRTestCommit)
1520 // This commit hash will NOT match what's in the PR event's `c` tag
1521 let wrong_commit_hash = match create_deterministic_commit_with_variant(
1522 &clone_path,
1523 CommitVariant::Owner,
1524 ) {
1525 Ok(h) => h,
1526 Err(e) => {
1527 cleanup(&clone_path);
1528 return Err(anyhow::anyhow!("Failed to create wrong commit: {}", e));
1529 }
1530 };
1531
1532 // Verify it's actually different from expected PR commit
1533 if wrong_commit_hash == PR_TEST_COMMIT_HASH {
1534 cleanup(&clone_path);
1535 return Err(anyhow::anyhow!(
1536 "Test setup error: wrong_commit_hash {} equals PR_TEST_COMMIT_HASH",
1537 wrong_commit_hash
1538 ));
1539 }
1540
1541 // Create master branch if needed and push to refs/nostr/<pr-event-id>
1542 let _ = Command::new("git")
1543 .args(["branch", "-M", "master"])
1544 .current_dir(&clone_path)
1545 .output();
1546
1547 let push_output = Command::new("git")
1548 .args([
1549 "push",
1550 "origin",
1551 &format!("master:refs/nostr/{}", pr_event_id),
1552 ])
1553 .current_dir(&clone_path)
1554 .output()
1555 .map_err(|e| {
1556 cleanup(&clone_path);
1557 anyhow::anyhow!("Failed to execute git push: {}", e)
1558 })?;
1559
1560 cleanup(&clone_path);
1561
1562 if !push_output.status.success() {
1563 let stderr = String::from_utf8_lossy(&push_output.stderr);
1564 return Err(anyhow::anyhow!(
1565 "Initial push to refs/nostr/{} failed (expected success before PR event exists): {}",
1566 pr_event_id,
1567 stderr
1568 ));
1569 }
1570
1571 // Return the unsent PR event (tests can publish it later)
1572 Ok(pr_event)
1573 }
1574
1575 /// Build PREventSentAfterWrongPush fixture
1576 ///
1577 /// This fixture builds on PRWrongCommitPushedBeforeEvent by sending the PR event.
1578 /// After this fixture, the relay has:
1579 /// - ValidRepo announcement
1580 /// - PR event
1581 /// - refs/nostr/<pr-event-id> may have been cleaned up (that's what tests verify)
1582 ///
1583 /// Returns: the sent PR event
1584 async fn build_pr_event_sent_after_wrong_push(&self) -> Result<Event> {
1585 // Get the PR event that was cached by PRWrongCommitPushedBeforeEvent
1586 let pr_event = self.get_cached_dependency(FixtureKind::PRWrongCommitPushedBeforeEvent)?;
1587
1588 // Send the PR event to relay
1589 self.client.send_event(pr_event.clone()).await?;
1590
1591 // Wait for relay to process
1592 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
1593
1594 // Return the now-sent PR event
1595 Ok(pr_event)
1596 }
1597
1373 /// Get relay domain (host:port) from the connected relay 1598 /// Get relay domain (host:port) from the connected relay
1374 /// 1599 ///
1375 /// Extracts the domain from the relay URL for git HTTP operations. 1600 /// 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 36abd30..0119ab3 100644
--- a/grasp-audit/src/specs/grasp01/push_authorization.rs
+++ b/grasp-audit/src/specs/grasp01/push_authorization.rs
@@ -32,9 +32,8 @@
32const PR_TEST_COMMIT_HASH: &str = "5d40fb1555a0c28bf4d650515a73aaa54d4d9bfb"; 32const PR_TEST_COMMIT_HASH: &str = "5d40fb1555a0c28bf4d650515a73aaa54d4d9bfb";
33 33
34use crate::{ 34use crate::{
35 clone_repo, create_commit, create_deterministic_commit, 35 clone_repo, create_commit, create_deterministic_commit_with_variant, try_push, try_push_to_ref,
36 create_deterministic_commit_with_variant, try_push, try_push_to_ref, AuditClient, 36 AuditClient, CommitVariant, FixtureKind, TestContext, TestResult,
37 CommitVariant, FixtureKind, TestContext, TestResult, DETERMINISTIC_COMMIT_HASH,
38 MAINTAINER_DETERMINISTIC_COMMIT_HASH, 37 MAINTAINER_DETERMINISTIC_COMMIT_HASH,
39}; 38};
40use nostr_sdk::prelude::*; 39use nostr_sdk::prelude::*;
@@ -238,132 +237,9 @@ async fn setup_pr_test_repo(
238} 237}
239 238
240// ============================================================ 239// ============================================================
241// PR Ref Push Test Setup Helpers - Minimize Test Duplication 240// PR Ref Push Test Helpers
242// ============================================================ 241// ============================================================
243 242
244/// Result of setting up a repo with a wrong commit pushed before PR event exists.
245/// Used as shared setup for tests 3, 4, 5 which all depend on this scenario.
246#[allow(dead_code)]
247struct PrRefTestSetup {
248 clone_path: PathBuf,
249 pr_event_id: String,
250 repo_id: String,
251 owner_npub: String,
252 wrong_commit_hash: String,
253 /// The unpublished PR event - store it so we can publish the SAME event later
254 pr_event: Event,
255}
256
257impl PrRefTestSetup {
258 fn cleanup(&self) {
259 let _ = std::fs::remove_dir_all(&self.clone_path);
260 }
261}
262
263/// Sets up a repo and pushes a WRONG commit to refs/nostr/<pr-event-id> BEFORE PR event exists.
264///
265/// This is the shared setup for PR ref lifecycle tests:
266/// - Creates repo (gets PREvent fixture for event-id but doesn't publish yet)
267/// - Clones repo
268/// - Creates a commit that does NOT match PR_TEST_COMMIT_HASH
269/// - Pushes to refs/nostr/<pr-event-id> (should succeed - no event to validate against)
270///
271/// Tests using this setup:
272/// - test_pr_push_to_nostr_ref_with_wrong_commit_accepted_before_event_received: verify initial push accepted
273/// - test_pr_event_published_removes_nostr_ref_at_incorrect_commit: publish event, verify cleanup
274/// - test_push_to_nostr_ref_with_wrong_commit_after_event_received_rejected: publish event, try push wrong commit
275/// - test_push_to_nostr_ref_with_correct_commit_after_event_received_accepted: publish event, push correct commit
276#[allow(dead_code)]
277async fn setup_repo_with_wrong_commit_pushed(
278 ctx: &TestContext<'_>,
279 relay_domain: &str,
280) -> Result<PrRefTestSetup, String> {
281 // Get ValidRepo fixture (publishes repo announcement to relay)
282 let repo_event = ctx
283 .get_fixture(FixtureKind::ValidRepo)
284 .await
285 .map_err(|e| format!("Failed to get repo announcement: {}", e))?;
286
287 // Build PR event WITHOUT publishing - we need its ID before the event exists on relay
288 // This allows testing refs/nostr/<event-id> push behavior before the event is received
289 let pr_event = ctx
290 .build_fixture_only(FixtureKind::PREvent)
291 .await
292 .map_err(|e| format!("Failed to build PR event fixture: {}", e))?;
293
294 let repo_id = repo_event
295 .tags
296 .iter()
297 .find(|t| t.kind() == TagKind::d())
298 .and_then(|t| t.content())
299 .ok_or("No repo identifier in announcement")?
300 .to_string();
301
302 let owner_npub = repo_event.pubkey.to_bech32().map_err(|e| e.to_string())?;
303 let pr_event_id = pr_event.id.to_hex();
304
305 // Clone the repository
306 let clone_path = clone_repo(relay_domain, &owner_npub, &repo_id)?;
307
308 // Create a WRONG commit (not the one expected by PR event)
309 let wrong_commit_hash =
310 create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner)?;
311
312 // Verify it's actually different from expected
313 if wrong_commit_hash == PR_TEST_COMMIT_HASH {
314 let _ = std::fs::remove_dir_all(&clone_path);
315 return Err("Test setup error: wrong_commit_hash equals PR_TEST_COMMIT_HASH".to_string());
316 }
317
318 // Push to refs/nostr/<pr-event-id> (no event published yet, should succeed)
319 let push_output = Command::new("git")
320 .args([
321 "push",
322 "origin",
323 &format!("master:refs/nostr/{}", pr_event_id),
324 ])
325 .current_dir(&clone_path)
326 .output()
327 .map_err(|e| format!("Failed to execute git push: {}", e))?;
328
329 if !push_output.status.success() {
330 let stderr = String::from_utf8_lossy(&push_output.stderr);
331 let _ = std::fs::remove_dir_all(&clone_path);
332 return Err(format!(
333 "Initial push failed (expected success before PR event): {}",
334 stderr
335 ));
336 }
337
338 Ok(PrRefTestSetup {
339 clone_path,
340 pr_event_id,
341 repo_id,
342 owner_npub,
343 wrong_commit_hash,
344 pr_event,
345 })
346}
347
348/// Publishes the SAME PR event that was built during setup.
349/// Call this after setup_repo_with_wrong_commit_pushed to test post-event behavior.
350///
351/// IMPORTANT: We must publish the EXACT same event that was used during setup,
352/// otherwise the event ID won't match the refs/nostr/<event-id> ref that was pushed.
353#[allow(dead_code)]
354async fn publish_pr_event_and_wait(ctx: &TestContext<'_>, pr_event: &Event) -> Result<(), String> {
355 // Publish the exact same PR event that was created during setup
356 ctx.client()
357 .send_event(pr_event.clone())
358 .await
359 .map_err(|e| format!("Failed to publish PR event: {}", e))?;
360
361 // Wait for relay to process
362 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
363
364 Ok(())
365}
366
367/// Creates the correct PR test commit (matching PR_TEST_COMMIT_HASH) in an existing clone. 243/// Creates the correct PR test commit (matching PR_TEST_COMMIT_HASH) in an existing clone.
368/// Used after wrong commit was pushed to test pushing the correct commit. 244/// Used after wrong commit was pushed to test pushing the correct commit.
369#[allow(dead_code)] 245#[allow(dead_code)]
@@ -1139,7 +1015,9 @@ impl PushAuthorizationTests {
1139 /// when no corresponding event exists yet. This is expected behavior because 1015 /// when no corresponding event exists yet. This is expected behavior because
1140 /// there's no validation event to check against. 1016 /// there's no validation event to check against.
1141 /// 1017 ///
1142 /// Uses `setup_repo_with_wrong_commit_pushed` helper which handles all setup. 1018 /// Uses `PRWrongCommitPushedBeforeEvent` fixture which handles all setup
1019 /// and verifies the push succeeded.
1020 #[allow(unused_variables)] // relay_domain is now handled by fixture
1143 pub async fn test_pr_push_to_nostr_ref_with_wrong_commit_accepted_before_event_received( 1021 pub async fn test_pr_push_to_nostr_ref_with_wrong_commit_accepted_before_event_received(
1144 client: &AuditClient, 1022 client: &AuditClient,
1145 relay_domain: &str, 1023 relay_domain: &str,
@@ -1149,19 +1027,15 @@ impl PushAuthorizationTests {
1149 let desc = "Push wrong commit to refs/nostr/<pr-event-id> before PR event (should accept)"; 1027 let desc = "Push wrong commit to refs/nostr/<pr-event-id> before PR event (should accept)";
1150 let ctx = TestContext::new(client); 1028 let ctx = TestContext::new(client);
1151 1029
1152 // Setup includes: create repo, clone, create wrong commit, push to refs/nostr/<event-id> 1030 // The PRWrongCommitPushedBeforeEvent fixture handles:
1153 // The push happens BEFORE PR event is published, so should succeed 1031 // 1. Create repo announcement
1154 let setup = match setup_repo_with_wrong_commit_pushed(&ctx, relay_domain).await { 1032 // 2. Build PR event (but don't send it)
1155 Ok(s) => s, 1033 // 3. Clone repo, create wrong commit, push to refs/nostr/<event-id>
1156 Err(e) => { 1034 // If the push fails, the fixture will return an error
1157 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1035 match ctx.get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent).await {
1158 } 1036 Ok(_pr_event) => TestResult::new(test_name, "GRASP-01", desc).pass(),
1159 }; 1037 Err(e) => TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)),
1160 1038 }
1161 // Setup already pushed and verified success - just cleanup and report pass
1162 setup.cleanup();
1163
1164 TestResult::new(test_name, "GRASP-01", desc).pass()
1165 } 1039 }
1166 1040
1167 /// Test 2: After publishing PR event, verify that incorrect refs get cleaned up 1041 /// Test 2: After publishing PR event, verify that incorrect refs get cleaned up
@@ -1170,7 +1044,7 @@ impl PushAuthorizationTests {
1170 /// the relay should validate any existing refs/nostr/<event-id> refs and 1044 /// the relay should validate any existing refs/nostr/<event-id> refs and
1171 /// delete those that don't match the commit in the PR event's `c` tag. 1045 /// delete those that don't match the commit in the PR event's `c` tag.
1172 /// 1046 ///
1173 /// Depends on: `setup_repo_with_wrong_commit_pushed` (wrong commit already pushed) 1047 /// Uses `PREventSentAfterWrongPush` fixture which builds on the wrong push fixture.
1174 pub async fn test_pr_event_published_removes_nostr_ref_at_incorrect_commit( 1048 pub async fn test_pr_event_published_removes_nostr_ref_at_incorrect_commit(
1175 client: &AuditClient, 1049 client: &AuditClient,
1176 relay_domain: &str, 1050 relay_domain: &str,
@@ -1179,38 +1053,66 @@ impl PushAuthorizationTests {
1179 let desc = "Publishing PR event should trigger cleanup of incorrect refs"; 1053 let desc = "Publishing PR event should trigger cleanup of incorrect refs";
1180 let ctx = TestContext::new(client); 1054 let ctx = TestContext::new(client);
1181 1055
1182 // Setup: wrong commit already pushed to refs/nostr/<pr-event-id> 1056 // Get fixture: wrong commit was pushed, then PR event was sent
1183 let setup = match setup_repo_with_wrong_commit_pushed(&ctx, relay_domain).await { 1057 let pr_event = match ctx.get_fixture(FixtureKind::PREventSentAfterWrongPush).await {
1184 Ok(s) => s, 1058 Ok(e) => e,
1185 Err(e) => { 1059 Err(e) => {
1186 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1060 return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e));
1187 } 1061 }
1188 }; 1062 };
1189 1063
1190 // NOW publish the PR event - this should trigger cleanup validation 1064 let pr_event_id = pr_event.id.to_hex();
1191 if let Err(e) = publish_pr_event_and_wait(&ctx, &setup.pr_event).await { 1065
1192 setup.cleanup(); 1066 // Get repo info for cloning (fresh clone for verification)
1193 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1067 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1194 } 1068 Ok(r) => r,
1069 Err(e) => {
1070 return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e));
1071 }
1072 };
1073
1074 let repo_id = repo
1075 .tags
1076 .iter()
1077 .find(|t| t.kind() == TagKind::d())
1078 .and_then(|t| t.content())
1079 .unwrap_or("unknown")
1080 .to_string();
1081
1082 let owner_npub = match repo.pubkey.to_bech32() {
1083 Ok(n) => n,
1084 Err(e) => {
1085 return TestResult::new(test_name, "GRASP-01", desc)
1086 .fail(format!("Failed to get owner npub: {}", e));
1087 }
1088 };
1089
1090 // Clone fresh for verification
1091 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) {
1092 Ok(p) => p,
1093 Err(e) => {
1094 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1095 }
1096 };
1195 1097
1196 // Check if the incorrect ref was deleted 1098 // Check if the incorrect ref was deleted
1197 let ref_name = format!("refs/nostr/{}", setup.pr_event_id); 1099 let ref_name = format!("refs/nostr/{}", pr_event_id);
1198 let refs_exist = match ref_exists_on_remote(&setup.clone_path, &ref_name) { 1100 let refs_exist = match ref_exists_on_remote(&clone_path, &ref_name) {
1199 Ok(exists) => exists, 1101 Ok(exists) => exists,
1200 Err(e) => { 1102 Err(e) => {
1201 setup.cleanup(); 1103 let _ = fs::remove_dir_all(&clone_path);
1202 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1104 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1203 } 1105 }
1204 }; 1106 };
1205 1107
1206 setup.cleanup(); 1108 let _ = fs::remove_dir_all(&clone_path);
1207 1109
1208 // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag 1110 // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag
1209 if refs_exist { 1111 if refs_exist {
1210 TestResult::new(test_name, "GRASP-01", desc).fail(format!( 1112 TestResult::new(test_name, "GRASP-01", desc).fail(format!(
1211 "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \ 1113 "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \
1212 but the ref still exists. The relay should delete refs that don't match the event's `c` tag.", 1114 but the ref still exists. The relay should delete refs that don't match the event's `c` tag.",
1213 setup.pr_event_id 1115 pr_event_id
1214 )) 1116 ))
1215 } else { 1117 } else {
1216 TestResult::new(test_name, "GRASP-01", desc).pass() 1118 TestResult::new(test_name, "GRASP-01", desc).pass()
@@ -1223,7 +1125,7 @@ impl PushAuthorizationTests {
1223 /// when a corresponding event exists but the pushed commit doesn't match 1125 /// when a corresponding event exists but the pushed commit doesn't match
1224 /// the commit in the PR event's `c` tag. 1126 /// the commit in the PR event's `c` tag.
1225 /// 1127 ///
1226 /// Depends on: `setup_repo_with_wrong_commit_pushed` for repo/clone setup, then publishes PR event 1128 /// Uses `PREventSentAfterWrongPush` fixture, then attempts to push wrong commit again.
1227 pub async fn test_push_to_nostr_ref_with_wrong_commit_after_event_received_rejected( 1129 pub async fn test_push_to_nostr_ref_with_wrong_commit_after_event_received_rejected(
1228 client: &AuditClient, 1130 client: &AuditClient,
1229 relay_domain: &str, 1131 relay_domain: &str,
@@ -1232,30 +1134,65 @@ impl PushAuthorizationTests {
1232 let desc = "Push wrong commit to refs/nostr/<pr-event-id> after PR event (should reject)"; 1134 let desc = "Push wrong commit to refs/nostr/<pr-event-id> after PR event (should reject)";
1233 let ctx = TestContext::new(client); 1135 let ctx = TestContext::new(client);
1234 1136
1235 // Setup: wrong commit already pushed (we'll use the same setup, but publish PR first) 1137 // Get fixture: PR event exists on relay (wrong commit was previously pushed but may have been cleaned up)
1236 let setup = match setup_repo_with_wrong_commit_pushed(&ctx, relay_domain).await { 1138 let pr_event = match ctx.get_fixture(FixtureKind::PREventSentAfterWrongPush).await {
1237 Ok(s) => s, 1139 Ok(e) => e,
1140 Err(e) => {
1141 return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e));
1142 }
1143 };
1144
1145 let pr_event_id = pr_event.id.to_hex();
1146
1147 // Get repo info for cloning (fresh clone for this test)
1148 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1149 Ok(r) => r,
1150 Err(e) => {
1151 return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e));
1152 }
1153 };
1154
1155 let repo_id = repo
1156 .tags
1157 .iter()
1158 .find(|t| t.kind() == TagKind::d())
1159 .and_then(|t| t.content())
1160 .unwrap_or("unknown")
1161 .to_string();
1162
1163 let owner_npub = match repo.pubkey.to_bech32() {
1164 Ok(n) => n,
1165 Err(e) => {
1166 return TestResult::new(test_name, "GRASP-01", desc)
1167 .fail(format!("Failed to get owner npub: {}", e));
1168 }
1169 };
1170
1171 // Clone fresh for this test
1172 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) {
1173 Ok(p) => p,
1238 Err(e) => { 1174 Err(e) => {
1239 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1175 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1240 } 1176 }
1241 }; 1177 };
1242 1178
1243 // Publish PR event FIRST (before our test push) 1179 // Create a wrong commit (Owner variant, not PRTestCommit)
1244 if let Err(e) = publish_pr_event_and_wait(&ctx, &setup.pr_event).await { 1180 if let Err(e) = create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner)
1245 setup.cleanup(); 1181 {
1182 let _ = fs::remove_dir_all(&clone_path);
1246 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1183 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1247 } 1184 }
1248 1185
1249 // Try to push again with wrong commit (should be rejected now that PR event exists) 1186 // Try to push with wrong commit (should be rejected since PR event exists)
1250 let push_succeeded = match push_to_pr_ref(&setup.clone_path, &setup.pr_event_id) { 1187 let push_succeeded = match push_to_pr_ref(&clone_path, &pr_event_id) {
1251 Ok(success) => success, 1188 Ok(success) => success,
1252 Err(e) => { 1189 Err(e) => {
1253 setup.cleanup(); 1190 let _ = fs::remove_dir_all(&clone_path);
1254 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1191 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1255 } 1192 }
1256 }; 1193 };
1257 1194
1258 setup.cleanup(); 1195 let _ = fs::remove_dir_all(&clone_path);
1259 1196
1260 // Should REJECT - PR event exists with different commit hash 1197 // Should REJECT - PR event exists with different commit hash
1261 if push_succeeded { 1198 if push_succeeded {
@@ -1272,7 +1209,7 @@ impl PushAuthorizationTests {
1272 /// when a corresponding event exists AND the pushed commit matches 1209 /// when a corresponding event exists AND the pushed commit matches
1273 /// the commit in the PR event's `c` tag. 1210 /// the commit in the PR event's `c` tag.
1274 /// 1211 ///
1275 /// Depends on: `setup_repo_with_wrong_commit_pushed` for setup, then resets to correct commit 1212 /// Uses `PREventSentAfterWrongPush` fixture, then creates correct commit and pushes.
1276 pub async fn test_push_to_nostr_ref_with_correct_commit_after_event_received_accepted( 1213 pub async fn test_push_to_nostr_ref_with_correct_commit_after_event_received_accepted(
1277 client: &AuditClient, 1214 client: &AuditClient,
1278 relay_domain: &str, 1215 relay_domain: &str,
@@ -1281,36 +1218,64 @@ impl PushAuthorizationTests {
1281 let desc = "Push correct commit to refs/nostr/<pr-event-id> after PR event (should accept)"; 1218 let desc = "Push correct commit to refs/nostr/<pr-event-id> after PR event (should accept)";
1282 let ctx = TestContext::new(client); 1219 let ctx = TestContext::new(client);
1283 1220
1284 // Setup: wrong commit already pushed 1221 // Get fixture: PR event exists on relay
1285 let setup = match setup_repo_with_wrong_commit_pushed(&ctx, relay_domain).await { 1222 let pr_event = match ctx.get_fixture(FixtureKind::PREventSentAfterWrongPush).await {
1286 Ok(s) => s, 1223 Ok(e) => e,
1287 Err(e) => { 1224 Err(e) => {
1288 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1225 return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e));
1289 } 1226 }
1290 }; 1227 };
1291 1228
1292 // Publish PR event FIRST 1229 let pr_event_id = pr_event.id.to_hex();
1293 if let Err(e) = publish_pr_event_and_wait(&ctx, &setup.pr_event).await { 1230
1294 setup.cleanup(); 1231 // Get repo info for cloning (fresh clone for this test)
1295 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1232 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
1296 } 1233 Ok(r) => r,
1234 Err(e) => {
1235 return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e));
1236 }
1237 };
1238
1239 let repo_id = repo
1240 .tags
1241 .iter()
1242 .find(|t| t.kind() == TagKind::d())
1243 .and_then(|t| t.content())
1244 .unwrap_or("unknown")
1245 .to_string();
1297 1246
1298 // Reset to CORRECT commit (the one expected by PR event) 1247 let owner_npub = match repo.pubkey.to_bech32() {
1299 if let Err(e) = reset_to_correct_pr_commit(&setup.clone_path) { 1248 Ok(n) => n,
1300 setup.cleanup(); 1249 Err(e) => {
1250 return TestResult::new(test_name, "GRASP-01", desc)
1251 .fail(format!("Failed to get owner npub: {}", e));
1252 }
1253 };
1254
1255 // Clone fresh for this test
1256 let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) {
1257 Ok(p) => p,
1258 Err(e) => {
1259 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1260 }
1261 };
1262
1263 // Create the CORRECT PR test commit (the one expected by PR event)
1264 if let Err(e) = reset_to_correct_pr_commit(&clone_path) {
1265 let _ = fs::remove_dir_all(&clone_path);
1301 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1266 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1302 } 1267 }
1303 1268
1304 // Push correct commit (should succeed) 1269 // Push correct commit (should succeed)
1305 let push_succeeded = match push_to_pr_ref(&setup.clone_path, &setup.pr_event_id) { 1270 let push_succeeded = match push_to_pr_ref(&clone_path, &pr_event_id) {
1306 Ok(success) => success, 1271 Ok(success) => success,
1307 Err(e) => { 1272 Err(e) => {
1308 setup.cleanup(); 1273 let _ = fs::remove_dir_all(&clone_path);
1309 return TestResult::new(test_name, "GRASP-01", desc).fail(&e); 1274 return TestResult::new(test_name, "GRASP-01", desc).fail(&e);
1310 } 1275 }
1311 }; 1276 };
1312 1277
1313 setup.cleanup(); 1278 let _ = fs::remove_dir_all(&clone_path);
1314 1279
1315 // Should ACCEPT - commit matches PR event's c tag 1280 // Should ACCEPT - commit matches PR event's c tag
1316 if !push_succeeded { 1281 if !push_succeeded {