diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 04:27:07 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 04:27:07 +0000 |
| commit | eee0a521afe0492f04bee58c9a236683fb23601b (patch) | |
| tree | b0414e164de2946e39613a45f5a2724eadfe1821 /grasp-audit/src | |
| parent | b2a43ffbd965a2b561d7b4e4c582dfeadd099ed3 (diff) | |
audit: fix shared test context to minimise events sent to production relays
Diffstat (limited to 'grasp-audit/src')
| -rw-r--r-- | grasp-audit/src/bin/grasp-audit.rs | 42 | ||||
| -rw-r--r-- | grasp-audit/src/client.rs | 26 | ||||
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 109 |
3 files changed, 119 insertions, 58 deletions
diff --git a/grasp-audit/src/bin/grasp-audit.rs b/grasp-audit/src/bin/grasp-audit.rs index 29f9c9a..2aabefe 100644 --- a/grasp-audit/src/bin/grasp-audit.rs +++ b/grasp-audit/src/bin/grasp-audit.rs | |||
| @@ -120,7 +120,27 @@ async fn main() -> Result<()> { | |||
| 120 | "all" => { | 120 | "all" => { |
| 121 | println!("Running all tests...\n"); | 121 | println!("Running all tests...\n"); |
| 122 | let mut all_results = AuditResult::new("All GRASP-01 Tests"); | 122 | let mut all_results = AuditResult::new("All GRASP-01 Tests"); |
| 123 | 123 | ||
| 124 | // Repository creation tests | ||
| 125 | println!(" → Repository creation tests..."); | ||
| 126 | let repo_results = specs::RepositoryCreationTests::run_all(&client, &relay_domain).await; | ||
| 127 | all_results.merge(repo_results); | ||
| 128 | |||
| 129 | // Git clone tests | ||
| 130 | println!(" → Git clone tests..."); | ||
| 131 | let clone_results = specs::GitCloneTests::run_all(&client, &relay_domain).await; | ||
| 132 | all_results.merge(clone_results); | ||
| 133 | |||
| 134 | // Push authorization tests | ||
| 135 | println!(" → Push authorization tests..."); | ||
| 136 | let push_results = specs::PushAuthorizationTests::run_all(&client, &relay_domain).await; | ||
| 137 | all_results.merge(push_results); | ||
| 138 | |||
| 139 | // Event acceptance policy tests | ||
| 140 | println!(" → Event acceptance policy tests..."); | ||
| 141 | let event_results = specs::EventAcceptancePolicyTests::run_all(&client).await; | ||
| 142 | all_results.merge(event_results); | ||
| 143 | |||
| 124 | // NIP-01 smoke tests | 144 | // NIP-01 smoke tests |
| 125 | println!(" → NIP-01 smoke tests..."); | 145 | println!(" → NIP-01 smoke tests..."); |
| 126 | let nip01_results = specs::Nip01SmokeTests::run_all(&client).await; | 146 | let nip01_results = specs::Nip01SmokeTests::run_all(&client).await; |
| @@ -131,30 +151,10 @@ async fn main() -> Result<()> { | |||
| 131 | let nip11_results = specs::Nip11DocumentTests::run_all(&client).await; | 151 | let nip11_results = specs::Nip11DocumentTests::run_all(&client).await; |
| 132 | all_results.merge(nip11_results); | 152 | all_results.merge(nip11_results); |
| 133 | 153 | ||
| 134 | // Event acceptance policy tests | ||
| 135 | println!(" → Event acceptance policy tests..."); | ||
| 136 | let event_results = specs::EventAcceptancePolicyTests::run_all(&client).await; | ||
| 137 | all_results.merge(event_results); | ||
| 138 | |||
| 139 | // CORS tests | 154 | // CORS tests |
| 140 | println!(" → CORS tests..."); | 155 | println!(" → CORS tests..."); |
| 141 | let cors_results = specs::CorsTests::run_all(&client, &relay_domain).await; | 156 | let cors_results = specs::CorsTests::run_all(&client, &relay_domain).await; |
| 142 | all_results.merge(cors_results); | 157 | all_results.merge(cors_results); |
| 143 | |||
| 144 | // Git clone tests | ||
| 145 | println!(" → Git clone tests..."); | ||
| 146 | let clone_results = specs::GitCloneTests::run_all(&client, &relay_domain).await; | ||
| 147 | all_results.merge(clone_results); | ||
| 148 | |||
| 149 | // Push authorization tests | ||
| 150 | println!(" → Push authorization tests..."); | ||
| 151 | let push_results = specs::PushAuthorizationTests::run_all(&client, &relay_domain).await; | ||
| 152 | all_results.merge(push_results); | ||
| 153 | |||
| 154 | // Repository creation tests | ||
| 155 | println!(" → Repository creation tests..."); | ||
| 156 | let repo_results = specs::RepositoryCreationTests::run_all(&client, &relay_domain).await; | ||
| 157 | all_results.merge(repo_results); | ||
| 158 | 158 | ||
| 159 | println!(); | 159 | println!(); |
| 160 | all_results | 160 | all_results |
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index 8b96f4f..4f3401c 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs | |||
| @@ -1,11 +1,22 @@ | |||
| 1 | //! Audit client for testing GRASP implementations | 1 | //! Audit client for testing GRASP implementations |
| 2 | 2 | ||
| 3 | use crate::audit::{AuditConfig, AuditEventBuilder, AuditMode}; | 3 | use crate::audit::{AuditConfig, AuditEventBuilder, AuditMode}; |
| 4 | use crate::fixtures::FixtureKind; | ||
| 4 | use anyhow::{anyhow, Result}; | 5 | use anyhow::{anyhow, Result}; |
| 5 | use nostr_sdk::prelude::*; | 6 | use nostr_sdk::prelude::*; |
| 7 | use std::collections::HashMap; | ||
| 8 | use std::sync::{Arc, Mutex}; | ||
| 6 | use std::time::Duration; | 9 | use std::time::Duration; |
| 7 | 10 | ||
| 11 | /// Type alias for the fixture cache - shared across TestContext instances | ||
| 12 | pub type FixtureCache = Arc<Mutex<HashMap<FixtureKind, Event>>>; | ||
| 13 | |||
| 8 | /// Client for auditing GRASP implementations | 14 | /// Client for auditing GRASP implementations |
| 15 | /// | ||
| 16 | /// The AuditClient owns a fixture cache that is shared across all TestContext | ||
| 17 | /// instances created from this client. This provides natural cache sharing: | ||
| 18 | /// - CLI creates one AuditClient → fixtures shared across all tests | ||
| 19 | /// - cargo test creates one AuditClient per test → fixtures isolated per test | ||
| 9 | pub struct AuditClient { | 20 | pub struct AuditClient { |
| 10 | client: Client, | 21 | client: Client, |
| 11 | pub config: AuditConfig, | 22 | pub config: AuditConfig, |
| @@ -14,6 +25,8 @@ pub struct AuditClient { | |||
| 14 | maintainer_keys: Keys, | 25 | maintainer_keys: Keys, |
| 15 | /// Recursive maintainer keys for testing recursive authorization scenarios | 26 | /// Recursive maintainer keys for testing recursive authorization scenarios |
| 16 | recursive_maintainer_keys: Keys, | 27 | recursive_maintainer_keys: Keys, |
| 28 | /// Fixture cache for TestContext instances - shared across all contexts using this client | ||
| 29 | fixture_cache: FixtureCache, | ||
| 17 | } | 30 | } |
| 18 | 31 | ||
| 19 | impl AuditClient { | 32 | impl AuditClient { |
| @@ -30,6 +43,7 @@ impl AuditClient { | |||
| 30 | keys, | 43 | keys, |
| 31 | maintainer_keys, | 44 | maintainer_keys, |
| 32 | recursive_maintainer_keys, | 45 | recursive_maintainer_keys, |
| 46 | fixture_cache: Arc::new(Mutex::new(HashMap::new())), | ||
| 33 | } | 47 | } |
| 34 | } | 48 | } |
| 35 | 49 | ||
| @@ -88,9 +102,19 @@ impl AuditClient { | |||
| 88 | keys, | 102 | keys, |
| 89 | maintainer_keys, | 103 | maintainer_keys, |
| 90 | recursive_maintainer_keys, | 104 | recursive_maintainer_keys, |
| 105 | fixture_cache: Arc::new(Mutex::new(HashMap::new())), | ||
| 91 | }) | 106 | }) |
| 92 | } | 107 | } |
| 93 | 108 | ||
| 109 | /// Get the fixture cache for TestContext usage | ||
| 110 | /// | ||
| 111 | /// This cache is shared across all TestContext instances created from this client. | ||
| 112 | /// In CLI mode (one client for all tests), fixtures are reused. | ||
| 113 | /// In test mode (one client per test), fixtures are isolated. | ||
| 114 | pub fn fixture_cache(&self) -> &FixtureCache { | ||
| 115 | &self.fixture_cache | ||
| 116 | } | ||
| 117 | |||
| 94 | /// Get the public key for this audit client | 118 | /// Get the public key for this audit client |
| 95 | pub fn public_key(&self) -> PublicKey { | 119 | pub fn public_key(&self) -> PublicKey { |
| 96 | self.keys.public_key() | 120 | self.keys.public_key() |
| @@ -488,6 +512,7 @@ mod tests { | |||
| 488 | keys: keys.clone(), | 512 | keys: keys.clone(), |
| 489 | maintainer_keys, | 513 | maintainer_keys, |
| 490 | recursive_maintainer_keys, | 514 | recursive_maintainer_keys, |
| 515 | fixture_cache: Arc::new(Mutex::new(HashMap::new())), | ||
| 491 | }; | 516 | }; |
| 492 | 517 | ||
| 493 | let _builder = client.event_builder(Kind::TextNote, "test content"); | 518 | let _builder = client.event_builder(Kind::TextNote, "test content"); |
| @@ -508,6 +533,7 @@ mod tests { | |||
| 508 | keys: keys.clone(), | 533 | keys: keys.clone(), |
| 509 | maintainer_keys, | 534 | maintainer_keys, |
| 510 | recursive_maintainer_keys, | 535 | recursive_maintainer_keys, |
| 536 | fixture_cache: Arc::new(Mutex::new(HashMap::new())), | ||
| 511 | }; | 537 | }; |
| 512 | 538 | ||
| 513 | // Create an event with a custom tag | 539 | // Create an event with a custom tag |
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index 89531c6..81620f6 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -6,6 +6,17 @@ | |||
| 6 | //! - **CI Mode (Isolated)**: Creates fresh events for each test, ensuring complete isolation | 6 | //! - **CI Mode (Isolated)**: Creates fresh events for each test, ensuring complete isolation |
| 7 | //! - **Production Mode (Shared)**: Reuses shared fixtures to minimize event publication | 7 | //! - **Production Mode (Shared)**: Reuses shared fixtures to minimize event publication |
| 8 | //! | 8 | //! |
| 9 | //! # Cache Sharing Strategy | ||
| 10 | //! | ||
| 11 | //! The fixture cache lives on the `AuditClient`, not on `TestContext`. This provides | ||
| 12 | //! natural cache sharing semantics: | ||
| 13 | //! | ||
| 14 | //! - **CLI mode**: Creates one `AuditClient` → fixtures shared across all tests | ||
| 15 | //! - **cargo test**: Creates one `AuditClient` per test → fixtures isolated per test | ||
| 16 | //! | ||
| 17 | //! This eliminates the need for global state while still enabling fixture reuse | ||
| 18 | //! when appropriate. | ||
| 19 | //! | ||
| 9 | //! # Example | 20 | //! # Example |
| 10 | //! | 21 | //! |
| 11 | //! ```no_run | 22 | //! ```no_run |
| @@ -25,8 +36,6 @@ | |||
| 25 | use crate::{AuditClient, AuditMode}; | 36 | use crate::{AuditClient, AuditMode}; |
| 26 | use anyhow::{Context, Result}; | 37 | use anyhow::{Context, Result}; |
| 27 | use nostr_sdk::prelude::Event; | 38 | use nostr_sdk::prelude::Event; |
| 28 | use std::collections::HashMap; | ||
| 29 | use std::sync::{Arc, Mutex}; | ||
| 30 | 39 | ||
| 31 | /// Deterministic commit hash used in RepoState fixtures (Owner variant) | 40 | /// Deterministic commit hash used in RepoState fixtures (Owner variant) |
| 32 | /// This is the hash produced by creating a commit with: | 41 | /// This is the hash produced by creating a commit with: |
| @@ -161,6 +170,13 @@ impl From<AuditMode> for ContextMode { | |||
| 161 | /// - In Isolated mode: Creates fresh events for each test | 170 | /// - In Isolated mode: Creates fresh events for each test |
| 162 | /// - In Shared mode: Caches and reuses events across tests | 171 | /// - In Shared mode: Caches and reuses events across tests |
| 163 | /// | 172 | /// |
| 173 | /// # Cache Location | ||
| 174 | /// | ||
| 175 | /// The fixture cache lives on `AuditClient`, not on `TestContext`. This means: | ||
| 176 | /// - Multiple `TestContext` instances from the same client share the cache | ||
| 177 | /// - CLI mode (one client) naturally shares fixtures across all tests | ||
| 178 | /// - Test mode (one client per test) naturally isolates fixtures | ||
| 179 | /// | ||
| 164 | /// # Example | 180 | /// # Example |
| 165 | /// | 181 | /// |
| 166 | /// ```no_run | 182 | /// ```no_run |
| @@ -181,20 +197,18 @@ impl From<AuditMode> for ContextMode { | |||
| 181 | pub struct TestContext<'a> { | 197 | pub struct TestContext<'a> { |
| 182 | client: &'a AuditClient, | 198 | client: &'a AuditClient, |
| 183 | mode: ContextMode, | 199 | mode: ContextMode, |
| 184 | cache: Arc<Mutex<HashMap<FixtureKind, Event>>>, | ||
| 185 | } | 200 | } |
| 186 | 201 | ||
| 187 | impl<'a> TestContext<'a> { | 202 | impl<'a> TestContext<'a> { |
| 188 | /// Create a new test context | 203 | /// Create a new test context |
| 189 | /// | 204 | /// |
| 190 | /// The context mode is automatically determined from the client's audit config. | 205 | /// The context mode is automatically determined from the client's audit config. |
| 206 | /// The fixture cache is borrowed from the client, enabling natural sharing: | ||
| 207 | /// - Same client = shared cache (CLI mode behavior) | ||
| 208 | /// - Different clients = isolated caches (test mode behavior) | ||
| 191 | pub fn new(client: &'a AuditClient) -> Self { | 209 | pub fn new(client: &'a AuditClient) -> Self { |
| 192 | let mode = ContextMode::from(client.config.mode); | 210 | let mode = ContextMode::from(client.config.mode); |
| 193 | Self { | 211 | Self { client, mode } |
| 194 | client, | ||
| 195 | mode, | ||
| 196 | cache: Arc::new(Mutex::new(HashMap::new())), | ||
| 197 | } | ||
| 198 | } | 212 | } |
| 199 | 213 | ||
| 200 | /// Create a test context with explicit mode override | 214 | /// Create a test context with explicit mode override |
| @@ -202,11 +216,7 @@ impl<'a> TestContext<'a> { | |||
| 202 | /// This is useful for testing the context itself or for advanced use cases | 216 | /// This is useful for testing the context itself or for advanced use cases |
| 203 | /// where you want to override the default mode behavior. | 217 | /// where you want to override the default mode behavior. |
| 204 | pub fn with_mode(client: &'a AuditClient, mode: ContextMode) -> Self { | 218 | pub fn with_mode(client: &'a AuditClient, mode: ContextMode) -> Self { |
| 205 | Self { | 219 | Self { client, mode } |
| 206 | client, | ||
| 207 | mode, | ||
| 208 | cache: Arc::new(Mutex::new(HashMap::new())), | ||
| 209 | } | ||
| 210 | } | 220 | } |
| 211 | 221 | ||
| 212 | /// Get a fixture, creating it if needed based on mode | 222 | /// Get a fixture, creating it if needed based on mode |
| @@ -261,15 +271,28 @@ impl<'a> TestContext<'a> { | |||
| 261 | } | 271 | } |
| 262 | 272 | ||
| 263 | /// Get or create a shared fixture (caches for reuse) | 273 | /// Get or create a shared fixture (caches for reuse) |
| 274 | /// | ||
| 275 | /// Uses the client's fixture cache to ensure fixtures are reused across | ||
| 276 | /// all TestContext instances in Production mode. | ||
| 264 | async fn get_or_create_shared(&self, kind: FixtureKind) -> Result<Event> { | 277 | async fn get_or_create_shared(&self, kind: FixtureKind) -> Result<Event> { |
| 265 | // Check cache first | 278 | // Check client's cache first (shared across all TestContext instances using same client) |
| 266 | { | 279 | { |
| 267 | let cache = self.cache.lock().unwrap(); | 280 | let cache = self.client.fixture_cache().lock().unwrap(); |
| 268 | if let Some(event) = cache.get(&kind) { | 281 | if let Some(event) = cache.get(&kind) { |
| 282 | tracing::debug!("get_or_create_shared({:?}) found in client cache", kind); | ||
| 269 | return Ok(event.clone()); | 283 | return Ok(event.clone()); |
| 270 | } | 284 | } |
| 271 | } | 285 | } |
| 272 | 286 | ||
| 287 | // Check relay connection before attempting to build | ||
| 288 | let is_connected = self.client.is_connected().await; | ||
| 289 | if !is_connected { | ||
| 290 | return Err(anyhow::anyhow!( | ||
| 291 | "Relay connection lost before building {:?} fixture (shared cache mode)", | ||
| 292 | kind | ||
| 293 | )); | ||
| 294 | } | ||
| 295 | |||
| 273 | // Not in cache, create it | 296 | // Not in cache, create it |
| 274 | let event = self | 297 | let event = self |
| 275 | .build_fixture(kind) | 298 | .build_fixture(kind) |
| @@ -286,64 +309,76 @@ impl<'a> TestContext<'a> { | |||
| 286 | ) | 309 | ) |
| 287 | })?; | 310 | })?; |
| 288 | 311 | ||
| 289 | // Store in cache | 312 | // Store in client's cache (shared across all TestContext instances using same client) |
| 290 | { | 313 | { |
| 291 | let mut cache = self.cache.lock().unwrap(); | 314 | let mut cache = self.client.fixture_cache().lock().unwrap(); |
| 292 | cache.insert(kind, event.clone()); | 315 | cache.insert(kind, event.clone()); |
| 316 | tracing::debug!("get_or_create_shared({:?}) stored in client cache ({} entries)", kind, cache.len()); | ||
| 293 | } | 317 | } |
| 294 | 318 | ||
| 295 | Ok(event) | 319 | Ok(event) |
| 296 | } | 320 | } |
| 297 | 321 | ||
| 298 | /// Get or create a ValidRepo, with caching within the TestContext. | 322 | /// Get or create a ValidRepo, with caching. |
| 299 | /// This is a helper method that avoids async recursion by not going | 323 | /// This is a helper method that avoids async recursion by not going |
| 300 | /// through get_fixture. It handles the repo specifically. | 324 | /// through get_fixture. It handles the repo specifically. |
| 301 | /// | 325 | /// |
| 302 | /// IMPORTANT: We always cache within a TestContext instance to ensure | 326 | /// Uses client's fixture_cache for caching - in Shared mode this enables |
| 303 | /// fixture dependencies work correctly. The isolation between tests | 327 | /// cross-test reuse when the same client is used. |
| 304 | /// comes from each test having its own TestContext with a fresh cache. | ||
| 305 | async fn get_or_create_repo(&self) -> Result<Event> { | 328 | async fn get_or_create_repo(&self) -> Result<Event> { |
| 306 | // Always check cache first - this ensures fixture dependencies work | 329 | // Check client's cache first (shared across all TestContext instances using same client) |
| 307 | // (e.g., MaintainerRepoAndState needs the SAME repo_id as RepoState) | ||
| 308 | { | 330 | { |
| 309 | let cache = self.cache.lock().unwrap(); | 331 | let cache = self.client.fixture_cache().lock().unwrap(); |
| 310 | if let Some(event) = cache.get(&FixtureKind::ValidRepo) { | 332 | if let Some(event) = cache.get(&FixtureKind::ValidRepo) { |
| 333 | tracing::debug!("get_or_create_repo() found in client cache"); | ||
| 311 | return Ok(event.clone()); | 334 | return Ok(event.clone()); |
| 312 | } | 335 | } |
| 313 | } | 336 | } |
| 314 | 337 | ||
| 338 | // Check relay connection before creating repo | ||
| 339 | let is_connected = self.client.is_connected().await; | ||
| 340 | if !is_connected { | ||
| 341 | return Err(anyhow::anyhow!( | ||
| 342 | "Relay connection lost before creating ValidRepo fixture" | ||
| 343 | )); | ||
| 344 | } | ||
| 345 | |||
| 315 | // Create a new repo | 346 | // Create a new repo |
| 316 | let test_name = format!( | 347 | let test_name = format!( |
| 317 | "fixture-{:?}-{}", | 348 | "fixture-{:?}-{}", |
| 318 | FixtureKind::ValidRepo, | 349 | FixtureKind::ValidRepo, |
| 319 | &uuid::Uuid::new_v4().to_string()[..8] | 350 | &uuid::Uuid::new_v4().to_string()[..8] |
| 320 | ); | 351 | ); |
| 321 | let repo = self.client.create_repo_announcement(&test_name).await?; | 352 | |
| 353 | let repo = self.client.create_repo_announcement(&test_name).await | ||
| 354 | .with_context(|| format!("create_repo_announcement failed for {}", test_name))?; | ||
| 322 | 355 | ||
| 323 | // Send it | 356 | // Send it |
| 324 | self.client.send_event(repo.clone()).await?; | 357 | self.client.send_event(repo.clone()).await |
| 358 | .with_context(|| "Failed to send repo announcement to relay")?; | ||
| 325 | 359 | ||
| 326 | // Always cache it - isolation comes from each test having its own TestContext | 360 | // Store in client's cache (shared across all TestContext instances using same client) |
| 327 | { | 361 | { |
| 328 | let mut cache = self.cache.lock().unwrap(); | 362 | let mut cache = self.client.fixture_cache().lock().unwrap(); |
| 329 | cache.insert(FixtureKind::ValidRepo, repo.clone()); | 363 | cache.insert(FixtureKind::ValidRepo, repo.clone()); |
| 364 | tracing::debug!("get_or_create_repo() stored in client cache ({} entries)", cache.len()); | ||
| 330 | } | 365 | } |
| 331 | 366 | ||
| 332 | Ok(repo) | 367 | Ok(repo) |
| 333 | } | 368 | } |
| 334 | 369 | ||
| 335 | /// Get or create a RepoWithIssue, with caching within the TestContext. | 370 | /// Get or create a RepoWithIssue, with caching via the client. |
| 336 | /// Returns the issue event (repo is already sent/cached via get_or_create_repo). | 371 | /// Returns the issue event (repo is already sent/cached via get_or_create_repo). |
| 337 | async fn get_or_create_issue(&self) -> Result<Event> { | 372 | async fn get_or_create_issue(&self) -> Result<Event> { |
| 338 | // Always check cache first - ensures fixture dependencies work | 373 | // Check client's cache first |
| 339 | { | 374 | { |
| 340 | let cache = self.cache.lock().unwrap(); | 375 | let cache = self.client.fixture_cache().lock().unwrap(); |
| 341 | if let Some(event) = cache.get(&FixtureKind::RepoWithIssue) { | 376 | if let Some(event) = cache.get(&FixtureKind::RepoWithIssue) { |
| 342 | return Ok(event.clone()); | 377 | return Ok(event.clone()); |
| 343 | } | 378 | } |
| 344 | } | 379 | } |
| 345 | 380 | ||
| 346 | // Get or create repo (reuses cached within this TestContext) | 381 | // Get or create repo (reuses cached via client) |
| 347 | let repo = self.get_or_create_repo().await?; | 382 | let repo = self.get_or_create_repo().await?; |
| 348 | 383 | ||
| 349 | // Create the issue | 384 | // Create the issue |
| @@ -357,9 +392,9 @@ impl<'a> TestContext<'a> { | |||
| 357 | // Send it | 392 | // Send it |
| 358 | self.client.send_event(issue.clone()).await?; | 393 | self.client.send_event(issue.clone()).await?; |
| 359 | 394 | ||
| 360 | // Always cache it - isolation comes from each test having its own TestContext | 395 | // Store in client's cache |
| 361 | { | 396 | { |
| 362 | let mut cache = self.cache.lock().unwrap(); | 397 | let mut cache = self.client.fixture_cache().lock().unwrap(); |
| 363 | cache.insert(FixtureKind::RepoWithIssue, issue.clone()); | 398 | cache.insert(FixtureKind::RepoWithIssue, issue.clone()); |
| 364 | } | 399 | } |
| 365 | 400 | ||
| @@ -707,10 +742,10 @@ impl<'a> TestContext<'a> { | |||
| 707 | 742 | ||
| 708 | /// Clear the fixture cache | 743 | /// Clear the fixture cache |
| 709 | /// | 744 | /// |
| 710 | /// This is useful for tests that want to ensure fresh fixtures | 745 | /// This clears the client's fixture cache, affecting all TestContext |
| 711 | /// even in shared mode. | 746 | /// instances using the same client. |
| 712 | pub fn clear_cache(&self) { | 747 | pub fn clear_cache(&self) { |
| 713 | let mut cache = self.cache.lock().unwrap(); | 748 | let mut cache = self.client.fixture_cache().lock().unwrap(); |
| 714 | cache.clear(); | 749 | cache.clear(); |
| 715 | } | 750 | } |
| 716 | } | 751 | } |