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>2026-02-24 14:15:04 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-24 14:15:04 +0000
commit7f71a2e75a66bcacad9057f5e339e511e689b828 (patch)
treeb1bc6e9d2df28f9b740cde37f836c76bc1bb1c8e /grasp-audit/src/specs/grasp01
parentef279a881fc1694fe2d868a32224874eb50cd358 (diff)
fix grasp-audit test isolation to prevent cross-spec relay state corruption
Add Purgatory-prefixed fixture variants (PurgatoryValidRepoSent, PurgatoryOwnerStateDataPushed) that create independent repos never shared with the main fixture chain. Purgatory tests that mutate relay state (replacement announcements, new state events, deletions) now use these isolated fixtures so they cannot corrupt the repo that push-authorization tests depend on. Run purgatory tests before push-auth in the full suite, since push-auth sends new replaceable state events (kind 30618) for the shared repo_id that would displace the original served state event.
Diffstat (limited to 'grasp-audit/src/specs/grasp01')
-rw-r--r--grasp-audit/src/specs/grasp01/purgatory.rs107
1 files changed, 52 insertions, 55 deletions
diff --git a/grasp-audit/src/specs/grasp01/purgatory.rs b/grasp-audit/src/specs/grasp01/purgatory.rs
index 29eabad..0686da8 100644
--- a/grasp-audit/src/specs/grasp01/purgatory.rs
+++ b/grasp-audit/src/specs/grasp01/purgatory.rs
@@ -46,7 +46,11 @@ impl PurgatoryTests {
46 results.add(Self::test_announcement_not_served_before_git_data(client).await); 46 results.add(Self::test_announcement_not_served_before_git_data(client).await);
47 results.add(Self::test_announcement_served_after_git_push(client).await); 47 results.add(Self::test_announcement_served_after_git_push(client).await);
48 results.add(Self::test_bare_repo_exists_for_purgatory_announcement(client).await); 48 results.add(Self::test_bare_repo_exists_for_purgatory_announcement(client).await);
49
50 // State event purgatory tests
49 results.add(Self::test_state_event_accepted_for_purgatory_announcement(client).await); 51 results.add(Self::test_state_event_accepted_for_purgatory_announcement(client).await);
52 results.add(Self::test_state_event_not_served_before_git_data(client).await);
53 results.add(Self::test_state_event_served_after_git_push(client).await);
50 54
51 // Deletion event tests (NIP-09) 55 // Deletion event tests (NIP-09)
52 results.add(Self::test_deletion_by_event_id_removes_purgatory_state_event(client).await); 56 results.add(Self::test_deletion_by_event_id_removes_purgatory_state_event(client).await);
@@ -54,10 +58,6 @@ impl PurgatoryTests {
54 Self::test_deletion_by_coordinate_removes_purgatory_state_event(client).await, 58 Self::test_deletion_by_coordinate_removes_purgatory_state_event(client).await,
55 ); 59 );
56 60
57 // State event purgatory tests (already implemented)
58 results.add(Self::test_state_event_not_served_before_git_data(client).await);
59 results.add(Self::test_state_event_served_after_git_push(client).await);
60
61 // PR purgatory tests 61 // PR purgatory tests
62 results.add(Self::test_pr_event_accepted_into_purgatory_and_isnt_served(client).await); 62 results.add(Self::test_pr_event_accepted_into_purgatory_and_isnt_served(client).await);
63 results.add(Self::test_pr_event_in_purgatory_git_push_accepted(client).await); 63 results.add(Self::test_pr_event_in_purgatory_git_push_accepted(client).await);
@@ -92,9 +92,12 @@ impl PurgatoryTests {
92 .run(|| async { 92 .run(|| async {
93 let ctx = TestContext::new(client); 93 let ctx = TestContext::new(client);
94 94
95 // Create a fresh repo announcement (not the served variant) 95 // Use the purgatory-specific fixture which creates its own independent repo.
96 // The shared ValidRepoSent may already be promoted (served) by the time this
97 // test runs if earlier specs triggered OwnerStateDataPushed. PurgatoryValidRepoSent
98 // is never promoted by any other test so the announcement stays in purgatory.
96 let repo = ctx 99 let repo = ctx
97 .get_fixture(FixtureKind::ValidRepoSent) 100 .get_fixture(FixtureKind::PurgatoryValidRepoSent)
98 .await 101 .await
99 .map_err(|e| format!("Failed to create repo announcement: {}", e))?; 102 .map_err(|e| format!("Failed to create repo announcement: {}", e))?;
100 103
@@ -106,7 +109,7 @@ impl PurgatoryTests {
106 .ok_or("Missing d tag in repo announcement")? 109 .ok_or("Missing d tag in repo announcement")?
107 .to_string(); 110 .to_string();
108 111
109 // Query for the announcement - should NOT be served 112 // Query for the announcement - should NOT be served (purgatory)
110 let filter = Filter::new() 113 let filter = Filter::new()
111 .kind(Kind::GitRepoAnnouncement) 114 .kind(Kind::GitRepoAnnouncement)
112 .author(client.public_key()) 115 .author(client.public_key())
@@ -153,13 +156,13 @@ impl PurgatoryTests {
153 .run(|| async { 156 .run(|| async {
154 let ctx = TestContext::new(client); 157 let ctx = TestContext::new(client);
155 158
156 // OwnerStateDataPushed fixture handles the full lifecycle: 159 // PurgatoryOwnerStateDataPushed fixture handles the full lifecycle:
157 // 1. Creates repo announcement (purgatory) 160 // 1. Creates repo announcement (purgatory)
158 // 2. Creates state event (purgatory) 161 // 2. Creates state event (purgatory)
159 // 3. Pushes git data 162 // 3. Pushes git data
160 // 4. Verifies events are served 163 // 4. Verifies events are served
161 let state_event = ctx 164 let state_event = ctx
162 .get_fixture(FixtureKind::OwnerStateDataPushed) 165 .get_fixture(FixtureKind::PurgatoryOwnerStateDataPushed)
163 .await 166 .await
164 .map_err(|e| format!("Failed to complete full lifecycle: {}", e))?; 167 .map_err(|e| format!("Failed to complete full lifecycle: {}", e))?;
165 168
@@ -190,18 +193,16 @@ impl PurgatoryTests {
190 )); 193 ));
191 } 194 }
192 195
193 // Verify state event is served 196 // Verify state event is served by querying its specific event ID.
194 let state_filter = Filter::new() 197 // We intentionally query by ID rather than kind+author+identifier because
195 .kind(Kind::RepoState) 198 // other tests (e.g. push-auth) may have sent a newer replaceable state event
196 .author(client.public_key()) 199 // for the same repo_id, which would displace this one in an identifier query.
197 .identifier(&repo_id); 200 let served = client
198 201 .is_event_on_relay(state_event.id)
199 let state_events = client
200 .query(state_filter)
201 .await 202 .await
202 .map_err(|e| format!("Failed to query state events: {}", e))?; 203 .map_err(|e| format!("Failed to query state event: {}", e))?;
203 204
204 if !state_events.iter().any(|e| e.id == state_event.id) { 205 if !served {
205 return Err(format!( 206 return Err(format!(
206 "State event not served after git push. Event ID: {}", 207 "State event not served after git push. Event ID: {}",
207 state_event.id 208 state_event.id
@@ -234,9 +235,9 @@ impl PurgatoryTests {
234 .run(|| async { 235 .run(|| async {
235 let ctx = TestContext::new(client); 236 let ctx = TestContext::new(client);
236 237
237 // Get a repo announcement (in purgatory, no git data yet) 238 // Get the purgatory-specific repo announcement (never promoted by other tests)
238 let repo = ctx 239 let repo = ctx
239 .get_fixture(FixtureKind::ValidRepoSent) 240 .get_fixture(FixtureKind::PurgatoryValidRepoSent)
240 .await 241 .await
241 .map_err(|e| format!("Failed to create repo announcement: {}", e))?; 242 .map_err(|e| format!("Failed to create repo announcement: {}", e))?;
242 243
@@ -314,9 +315,9 @@ impl PurgatoryTests {
314 .run(|| async { 315 .run(|| async {
315 let ctx = TestContext::new(client); 316 let ctx = TestContext::new(client);
316 317
317 // Get a repo announcement (in purgatory) 318 // Get the purgatory-specific repo announcement (never promoted by other tests)
318 let repo = ctx 319 let repo = ctx
319 .get_fixture(FixtureKind::ValidRepoSent) 320 .get_fixture(FixtureKind::PurgatoryValidRepoSent)
320 .await 321 .await
321 .map_err(|e| format!("Failed to create repo announcement: {}", e))?; 322 .map_err(|e| format!("Failed to create repo announcement: {}", e))?;
322 323
@@ -407,11 +408,13 @@ impl PurgatoryTests {
407 .run(|| async { 408 .run(|| async {
408 let ctx = TestContext::new(client); 409 let ctx = TestContext::new(client);
409 410
410 // Get a repo with git data already pushed 411 // Use the isolated purgatory repo so this test's new state event
412 // does not displace the shared OwnerStateDataPushed state event
413 // that push-authorization tests depend on.
411 let existing_state = ctx 414 let existing_state = ctx
412 .get_fixture(FixtureKind::OwnerStateDataPushed) 415 .get_fixture(FixtureKind::PurgatoryOwnerStateDataPushed)
413 .await 416 .await
414 .map_err(|e| format!("Failed to get existing repo: {}", e))?; 417 .map_err(|e| format!("Failed to get purgatory test repo: {}", e))?;
415 418
416 let repo_id = existing_state 419 let repo_id = existing_state
417 .tags 420 .tags
@@ -461,7 +464,7 @@ impl PurgatoryTests {
461 /// Spec: GRASP-01 Line 22 464 /// Spec: GRASP-01 Line 22
462 /// "...kept in purgatory (not served) until the related git data arrives" 465 /// "...kept in purgatory (not served) until the related git data arrives"
463 /// 466 ///
464 /// This test verifies the full lifecycle using OwnerStateDataPushed fixture: 467 /// This test verifies the full lifecycle using PurgatoryOwnerStateDataPushed fixture:
465 /// 1. State event is sent (enters purgatory) 468 /// 1. State event is sent (enters purgatory)
466 /// 2. Git data is pushed matching the state event 469 /// 2. Git data is pushed matching the state event
467 /// 3. State event is now served 470 /// 3. State event is now served
@@ -474,32 +477,22 @@ impl PurgatoryTests {
474 .run(|| async { 477 .run(|| async {
475 let ctx = TestContext::new(client); 478 let ctx = TestContext::new(client);
476 479
477 // OwnerStateDataPushed handles the full lifecycle 480 // PurgatoryOwnerStateDataPushed handles the full lifecycle
478 let state_event = ctx 481 let state_event = ctx
479 .get_fixture(FixtureKind::OwnerStateDataPushed) 482 .get_fixture(FixtureKind::PurgatoryOwnerStateDataPushed)
480 .await 483 .await
481 .map_err(|e| format!("Failed to complete full lifecycle: {}", e))?; 484 .map_err(|e| format!("Failed to complete full lifecycle: {}", e))?;
482 485
483 // Verify state event is now served 486 // Verify state event is served by querying its specific event ID.
484 let repo_id = state_event 487 // We intentionally query by ID rather than kind+author+identifier because
485 .tags 488 // other tests (e.g. push-auth) may have sent a newer replaceable state event
486 .iter() 489 // for the same repo_id, which would displace this one in an identifier query.
487 .find(|t| t.kind() == TagKind::d()) 490 let served = client
488 .and_then(|t| t.content()) 491 .is_event_on_relay(state_event.id)
489 .ok_or("Missing d tag in state event")?
490 .to_string();
491
492 let filter = Filter::new()
493 .kind(Kind::RepoState)
494 .author(client.public_key())
495 .identifier(&repo_id);
496
497 let events = client
498 .query(filter)
499 .await 492 .await
500 .map_err(|e| format!("Failed to query state events: {}", e))?; 493 .map_err(|e| format!("Failed to query state event: {}", e))?;
501 494
502 if !events.iter().any(|e| e.id == state_event.id) { 495 if !served {
503 return Err(format!( 496 return Err(format!(
504 "State event not served after git push. Event ID: {}", 497 "State event not served after git push. Event ID: {}",
505 state_event.id 498 state_event.id
@@ -665,7 +658,7 @@ impl PurgatoryTests {
665 /// each referencing an event the author is requesting to be deleted." 658 /// each referencing an event the author is requesting to be deleted."
666 /// 659 ///
667 /// This test verifies: 660 /// This test verifies:
668 /// 1. Get a promoted repo (OwnerStateDataPushed) so git pushes are possible 661 /// 1. Get a promoted repo (PurgatoryOwnerStateDataPushed) so git pushes are possible
669 /// 2. Clone the repo and create a unique commit (not yet pushed) 662 /// 2. Clone the repo and create a unique commit (not yet pushed)
670 /// 3. Submit a state event pointing to that unique commit (enters purgatory) 663 /// 3. Submit a state event pointing to that unique commit (enters purgatory)
671 /// 4. Send a kind 5 deletion event referencing the state event by event ID 664 /// 4. Send a kind 5 deletion event referencing the state event by event ID
@@ -681,11 +674,12 @@ impl PurgatoryTests {
681 .run(|| async { 674 .run(|| async {
682 let ctx = TestContext::new(client); 675 let ctx = TestContext::new(client);
683 676
684 // Stage 1: get a promoted repo with git data already on the relay 677 // Stage 1: get the isolated purgatory repo (independent from the shared
678 // OwnerStateDataPushed chain that push-authorization tests depend on)
685 let existing_state = ctx 679 let existing_state = ctx
686 .get_fixture(FixtureKind::OwnerStateDataPushed) 680 .get_fixture(FixtureKind::PurgatoryOwnerStateDataPushed)
687 .await 681 .await
688 .map_err(|e| format!("Failed to get promoted repo: {}", e))?; 682 .map_err(|e| format!("Failed to get purgatory test repo: {}", e))?;
689 683
690 let repo_id = existing_state 684 let repo_id = existing_state
691 .tags 685 .tags
@@ -788,7 +782,7 @@ impl PurgatoryTests {
788 /// event up to the `created_at` timestamp of the deletion request event." 782 /// event up to the `created_at` timestamp of the deletion request event."
789 /// 783 ///
790 /// This test verifies: 784 /// This test verifies:
791 /// 1. Get a promoted repo (OwnerStateDataPushed) so git pushes are possible 785 /// 1. Get a promoted repo (PurgatoryOwnerStateDataPushed) so git pushes are possible
792 /// 2. Generate a fresh keypair for a new maintainer 786 /// 2. Generate a fresh keypair for a new maintainer
793 /// 3. Send a replacement owner announcement adding the new maintainer (goes to DB) 787 /// 3. Send a replacement owner announcement adding the new maintainer (goes to DB)
794 /// 4. Send a state event signed by the new maintainer pointing to a unique commit 788 /// 4. Send a state event signed by the new maintainer pointing to a unique commit
@@ -807,11 +801,14 @@ impl PurgatoryTests {
807 .run(|| async { 801 .run(|| async {
808 let ctx = TestContext::new(client); 802 let ctx = TestContext::new(client);
809 803
810 // Stage 1: get a promoted repo with git data already on the relay 804 // Stage 1: get the isolated purgatory repo (independent from the shared
805 // OwnerStateDataPushed chain that push-authorization tests depend on).
806 // This test sends a replacement announcement (kind 30617) for the repo which
807 // would corrupt the shared repo's maintainer set if we used OwnerStateDataPushed.
811 let existing_state = ctx 808 let existing_state = ctx
812 .get_fixture(FixtureKind::OwnerStateDataPushed) 809 .get_fixture(FixtureKind::PurgatoryOwnerStateDataPushed)
813 .await 810 .await
814 .map_err(|e| format!("Failed to get promoted repo: {}", e))?; 811 .map_err(|e| format!("Failed to get purgatory test repo: {}", e))?;
815 812
816 let repo_id = existing_state 813 let repo_id = existing_state
817 .tags 814 .tags