From 158d3f0722e731f2b534951069c322c5cbb5a721 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 26 Nov 2025 10:23:47 +0000 Subject: feat(fixtures): reuse prerequisite fixtures in production mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixtures now reuse their prerequisites in Shared (production) mode, significantly reducing events published to production relays: Before: Each fixture created its own prerequisite events - ValidRepo: 1 event - RepoWithIssue: 2 events (repo + issue) - RepoWithComment: 3 events (repo + issue + comment) - RepoState: 2 events (repo + state) After: Fixtures share prerequisites via caching - ValidRepo: 1 event - RepoWithIssue: 1 new event (issue), reuses cached repo - RepoWithComment: 1 new event (comment), reuses cached repo+issue - RepoState: 1 new event (state), reuses cached repo Total for all 4 fixtures: 8 events → 4 events (50% reduction) In CI/Isolated mode, each test still gets fresh fixtures for test isolation - behavior unchanged. Implemented via get_or_create_repo() and get_or_create_issue() helpers that handle mode-aware caching without async recursion. --- grasp-audit/src/fixtures.rs | 108 ++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 29 deletions(-) (limited to 'grasp-audit/src') diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index 4b30cb6..9ccd703 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs @@ -214,6 +214,72 @@ impl<'a> TestContext<'a> { Ok(event) } + /// Get or create a ValidRepo, with mode-appropriate caching. + /// This is a helper method that avoids async recursion by not going + /// through get_fixture. It handles the repo specifically. + async fn get_or_create_repo(&self) -> Result { + // In Shared mode, check cache first + if self.mode == ContextMode::Shared { + let cache = self.cache.lock().unwrap(); + if let Some(event) = cache.get(&FixtureKind::ValidRepo) { + return Ok(event.clone()); + } + } + + // Create a new repo + let test_name = format!( + "fixture-{:?}-{}", + FixtureKind::ValidRepo, + &uuid::Uuid::new_v4().to_string()[..8] + ); + let repo = self.client.create_repo_announcement(&test_name).await?; + + // Send it + self.client.send_event(repo.clone()).await?; + + // Cache it in Shared mode + if self.mode == ContextMode::Shared { + let mut cache = self.cache.lock().unwrap(); + cache.insert(FixtureKind::ValidRepo, repo.clone()); + } + + Ok(repo) + } + + /// Get or create a RepoWithIssue, with mode-appropriate caching. + /// Returns the issue event (repo is already sent/cached via get_or_create_repo). + async fn get_or_create_issue(&self) -> Result { + // In Shared mode, check cache first + if self.mode == ContextMode::Shared { + let cache = self.cache.lock().unwrap(); + if let Some(event) = cache.get(&FixtureKind::RepoWithIssue) { + return Ok(event.clone()); + } + } + + // Get or create repo (reuses cached in Shared mode) + let repo = self.get_or_create_repo().await?; + + // Create the issue + let issue = self.client.create_issue( + &repo, + "Test Issue", + "Issue content for testing", + vec![], + )?; + + // Send it + self.client.send_event(issue.clone()).await?; + + // Cache it in Shared mode + if self.mode == ContextMode::Shared { + let mut cache = self.cache.lock().unwrap(); + cache.insert(FixtureKind::RepoWithIssue, issue.clone()); + } + + Ok(issue) + } + /// Build a fixture event (doesn't send it) async fn build_fixture(&self, kind: FixtureKind) -> Result { match kind { @@ -227,14 +293,11 @@ impl<'a> TestContext<'a> { } FixtureKind::RepoWithIssue => { - // First create and send repo - let test_name = format!( - "fixture-{:?}-{}", - FixtureKind::ValidRepo, - &uuid::Uuid::new_v4().to_string()[..8] - ); - let repo = self.client.create_repo_announcement(&test_name).await?; - self.client.send_event(repo.clone()).await?; + // Reuse ValidRepo fixture - this leverages caching in Shared mode + // In Isolated mode: creates fresh repo + // In Shared mode: returns cached repo (no duplicate events!) + // Uses direct helper to avoid async recursion through get_fixture + let repo = self.get_or_create_repo().await?; // Then create issue referencing it - this will have 'a' tag to repo // Note: We build the issue but DON'T send it here - the caller will send it @@ -251,35 +314,21 @@ impl<'a> TestContext<'a> { } FixtureKind::RepoWithComment => { - // First create repo with issue - let test_name = format!( - "fixture-{:?}-{}", - FixtureKind::ValidRepo, - &uuid::Uuid::new_v4().to_string()[..8] - ); - let repo = self.client.create_repo_announcement(&test_name).await?; - self.client.send_event(repo.clone()).await?; - - let issue = - self.client - .create_issue(&repo, "Test Issue", "Issue content", vec![])?; - self.client.send_event(issue.clone()).await?; + // Reuse RepoWithIssue fixture - this leverages caching in Shared mode + // In Isolated mode: creates fresh repo + issue + // In Shared mode: returns cached issue (repo already cached too!) + let issue = self.get_or_create_issue().await?; // Then create comment on issue + // Note: We build the comment but DON'T send it here - the caller will send it self.client.create_comment(&issue, "Test comment", vec![]) } FixtureKind::RepoState => { use nostr_sdk::prelude::*; - // First create repo announcement - let test_name = format!( - "fixture-{:?}-{}", - FixtureKind::ValidRepo, - &uuid::Uuid::new_v4().to_string()[..8] - ); - let repo = self.client.create_repo_announcement(&test_name).await?; - self.client.send_event(repo.clone()).await?; + // Reuse ValidRepo fixture - this leverages caching in Shared mode + let repo = self.get_or_create_repo().await?; // Extract repo_id from repo announcement let repo_id = repo @@ -292,6 +341,7 @@ impl<'a> TestContext<'a> { // Create state announcement with deterministic commit hash // Tag format: ["refs/heads/main", ""] + // Note: We build the state but DON'T send it here - the caller will send it self.client .event_builder(Kind::Custom(30618), "") .tag(Tag::identifier(&repo_id)) -- cgit v1.2.3