diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-26 10:23:47 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-26 10:23:47 +0000 |
| commit | 158d3f0722e731f2b534951069c322c5cbb5a721 (patch) | |
| tree | 6170dcbc978f795ec52a7c6a832671db13ff33f1 /grasp-audit/src | |
| parent | a6edb42dfc653b6826b59b7f296e0d0c4ee74557 (diff) | |
feat(fixtures): reuse prerequisite fixtures in production mode
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.
Diffstat (limited to 'grasp-audit/src')
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 108 |
1 files changed, 79 insertions, 29 deletions
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> { | |||
| 214 | Ok(event) | 214 | Ok(event) |
| 215 | } | 215 | } |
| 216 | 216 | ||
| 217 | /// Get or create a ValidRepo, with mode-appropriate caching. | ||
| 218 | /// This is a helper method that avoids async recursion by not going | ||
| 219 | /// through get_fixture. It handles the repo specifically. | ||
| 220 | async fn get_or_create_repo(&self) -> Result<Event> { | ||
| 221 | // In Shared mode, check cache first | ||
| 222 | if self.mode == ContextMode::Shared { | ||
| 223 | let cache = self.cache.lock().unwrap(); | ||
| 224 | if let Some(event) = cache.get(&FixtureKind::ValidRepo) { | ||
| 225 | return Ok(event.clone()); | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | // Create a new repo | ||
| 230 | let test_name = format!( | ||
| 231 | "fixture-{:?}-{}", | ||
| 232 | FixtureKind::ValidRepo, | ||
| 233 | &uuid::Uuid::new_v4().to_string()[..8] | ||
| 234 | ); | ||
| 235 | let repo = self.client.create_repo_announcement(&test_name).await?; | ||
| 236 | |||
| 237 | // Send it | ||
| 238 | self.client.send_event(repo.clone()).await?; | ||
| 239 | |||
| 240 | // Cache it in Shared mode | ||
| 241 | if self.mode == ContextMode::Shared { | ||
| 242 | let mut cache = self.cache.lock().unwrap(); | ||
| 243 | cache.insert(FixtureKind::ValidRepo, repo.clone()); | ||
| 244 | } | ||
| 245 | |||
| 246 | Ok(repo) | ||
| 247 | } | ||
| 248 | |||
| 249 | /// Get or create a RepoWithIssue, with mode-appropriate caching. | ||
| 250 | /// Returns the issue event (repo is already sent/cached via get_or_create_repo). | ||
| 251 | async fn get_or_create_issue(&self) -> Result<Event> { | ||
| 252 | // In Shared mode, check cache first | ||
| 253 | if self.mode == ContextMode::Shared { | ||
| 254 | let cache = self.cache.lock().unwrap(); | ||
| 255 | if let Some(event) = cache.get(&FixtureKind::RepoWithIssue) { | ||
| 256 | return Ok(event.clone()); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | // Get or create repo (reuses cached in Shared mode) | ||
| 261 | let repo = self.get_or_create_repo().await?; | ||
| 262 | |||
| 263 | // Create the issue | ||
| 264 | let issue = self.client.create_issue( | ||
| 265 | &repo, | ||
| 266 | "Test Issue", | ||
| 267 | "Issue content for testing", | ||
| 268 | vec![], | ||
| 269 | )?; | ||
| 270 | |||
| 271 | // Send it | ||
| 272 | self.client.send_event(issue.clone()).await?; | ||
| 273 | |||
| 274 | // Cache it in Shared mode | ||
| 275 | if self.mode == ContextMode::Shared { | ||
| 276 | let mut cache = self.cache.lock().unwrap(); | ||
| 277 | cache.insert(FixtureKind::RepoWithIssue, issue.clone()); | ||
| 278 | } | ||
| 279 | |||
| 280 | Ok(issue) | ||
| 281 | } | ||
| 282 | |||
| 217 | /// Build a fixture event (doesn't send it) | 283 | /// Build a fixture event (doesn't send it) |
| 218 | async fn build_fixture(&self, kind: FixtureKind) -> Result<Event> { | 284 | async fn build_fixture(&self, kind: FixtureKind) -> Result<Event> { |
| 219 | match kind { | 285 | match kind { |
| @@ -227,14 +293,11 @@ impl<'a> TestContext<'a> { | |||
| 227 | } | 293 | } |
| 228 | 294 | ||
| 229 | FixtureKind::RepoWithIssue => { | 295 | FixtureKind::RepoWithIssue => { |
| 230 | // First create and send repo | 296 | // Reuse ValidRepo fixture - this leverages caching in Shared mode |
| 231 | let test_name = format!( | 297 | // In Isolated mode: creates fresh repo |
| 232 | "fixture-{:?}-{}", | 298 | // In Shared mode: returns cached repo (no duplicate events!) |
| 233 | FixtureKind::ValidRepo, | 299 | // Uses direct helper to avoid async recursion through get_fixture |
| 234 | &uuid::Uuid::new_v4().to_string()[..8] | 300 | let repo = self.get_or_create_repo().await?; |
| 235 | ); | ||
| 236 | let repo = self.client.create_repo_announcement(&test_name).await?; | ||
| 237 | self.client.send_event(repo.clone()).await?; | ||
| 238 | 301 | ||
| 239 | // Then create issue referencing it - this will have 'a' tag to repo | 302 | // Then create issue referencing it - this will have 'a' tag to repo |
| 240 | // Note: We build the issue but DON'T send it here - the caller will send it | 303 | // Note: We build the issue but DON'T send it here - the caller will send it |
| @@ -251,35 +314,21 @@ impl<'a> TestContext<'a> { | |||
| 251 | } | 314 | } |
| 252 | 315 | ||
| 253 | FixtureKind::RepoWithComment => { | 316 | FixtureKind::RepoWithComment => { |
| 254 | // First create repo with issue | 317 | // Reuse RepoWithIssue fixture - this leverages caching in Shared mode |
| 255 | let test_name = format!( | 318 | // In Isolated mode: creates fresh repo + issue |
| 256 | "fixture-{:?}-{}", | 319 | // In Shared mode: returns cached issue (repo already cached too!) |
| 257 | FixtureKind::ValidRepo, | 320 | let issue = self.get_or_create_issue().await?; |
| 258 | &uuid::Uuid::new_v4().to_string()[..8] | ||
| 259 | ); | ||
| 260 | let repo = self.client.create_repo_announcement(&test_name).await?; | ||
| 261 | self.client.send_event(repo.clone()).await?; | ||
| 262 | |||
| 263 | let issue = | ||
| 264 | self.client | ||
| 265 | .create_issue(&repo, "Test Issue", "Issue content", vec![])?; | ||
| 266 | self.client.send_event(issue.clone()).await?; | ||
| 267 | 321 | ||
| 268 | // Then create comment on issue | 322 | // Then create comment on issue |
| 323 | // Note: We build the comment but DON'T send it here - the caller will send it | ||
| 269 | self.client.create_comment(&issue, "Test comment", vec![]) | 324 | self.client.create_comment(&issue, "Test comment", vec![]) |
| 270 | } | 325 | } |
| 271 | 326 | ||
| 272 | FixtureKind::RepoState => { | 327 | FixtureKind::RepoState => { |
| 273 | use nostr_sdk::prelude::*; | 328 | use nostr_sdk::prelude::*; |
| 274 | 329 | ||
| 275 | // First create repo announcement | 330 | // Reuse ValidRepo fixture - this leverages caching in Shared mode |
| 276 | let test_name = format!( | 331 | let repo = self.get_or_create_repo().await?; |
| 277 | "fixture-{:?}-{}", | ||
| 278 | FixtureKind::ValidRepo, | ||
| 279 | &uuid::Uuid::new_v4().to_string()[..8] | ||
| 280 | ); | ||
| 281 | let repo = self.client.create_repo_announcement(&test_name).await?; | ||
| 282 | self.client.send_event(repo.clone()).await?; | ||
| 283 | 332 | ||
| 284 | // Extract repo_id from repo announcement | 333 | // Extract repo_id from repo announcement |
| 285 | let repo_id = repo | 334 | let repo_id = repo |
| @@ -292,6 +341,7 @@ impl<'a> TestContext<'a> { | |||
| 292 | 341 | ||
| 293 | // Create state announcement with deterministic commit hash | 342 | // Create state announcement with deterministic commit hash |
| 294 | // Tag format: ["refs/heads/main", "<commit_hash>"] | 343 | // Tag format: ["refs/heads/main", "<commit_hash>"] |
| 344 | // Note: We build the state but DON'T send it here - the caller will send it | ||
| 295 | self.client | 345 | self.client |
| 296 | .event_builder(Kind::Custom(30618), "") | 346 | .event_builder(Kind::Custom(30618), "") |
| 297 | .tag(Tag::identifier(&repo_id)) | 347 | .tag(Tag::identifier(&repo_id)) |