diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-02 21:20:17 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-02 21:20:17 +0000 |
| commit | 72683beea066d066637e747c40dc859fb709babf (patch) | |
| tree | 2db3462ebbcb7501e56491148cc3ffa7aa294071 /grasp-audit/src | |
| parent | 5c10ca008413744b09136618eaa85275c997704c (diff) | |
refactor: rename AuditMode variants and change CLI default to shared
Breaking change: Renamed AuditMode enum variants for clarity:
- AuditMode::CI -> AuditMode::Isolated (fresh fixtures per test)
- AuditMode::Production -> AuditMode::Shared (reuse fixtures across tests)
Config constructors renamed (with deprecated aliases):
- AuditConfig::ci() -> AuditConfig::isolated()
- AuditConfig::production() -> AuditConfig::shared()
CLI default changed from 'ci' to 'shared' mode, which enables
fixture caching across tests. This fixes the issue where fixtures
were being re-created for every test in CLI mode.
Fixture caching behavior:
- Shared mode (CLI default): Uses client's cache, fixtures reused
- Isolated mode (for cargo test): Local cache per TestContext
Diffstat (limited to 'grasp-audit/src')
| -rw-r--r-- | grasp-audit/src/audit.rs | 100 | ||||
| -rw-r--r-- | grasp-audit/src/bin/grasp-audit.rs | 13 | ||||
| -rw-r--r-- | grasp-audit/src/client.rs | 20 | ||||
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 63 | ||||
| -rw-r--r-- | grasp-audit/src/lib.rs | 4 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/cors.rs | 2 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/event_acceptance_policy.rs | 2 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/nip01_smoke.rs | 2 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/nip11_document.rs | 2 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/spec_requirements.rs | 2 |
10 files changed, 129 insertions, 81 deletions
diff --git a/grasp-audit/src/audit.rs b/grasp-audit/src/audit.rs index b97ddb6..713c948 100644 --- a/grasp-audit/src/audit.rs +++ b/grasp-audit/src/audit.rs | |||
| @@ -18,46 +18,75 @@ pub struct AuditConfig { | |||
| 18 | pub read_only: bool, | 18 | pub read_only: bool, |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | /// Audit mode | 21 | /// Audit mode for fixture management |
| 22 | /// | ||
| 23 | /// Controls how test fixtures are cached and shared between tests. | ||
| 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 23 | pub enum AuditMode { | 25 | pub enum AuditMode { |
| 24 | /// Isolated CI/CD tests - only see own events | 26 | /// Isolated mode - each test creates fresh fixtures |
| 25 | CI, | 27 | /// |
| 28 | /// Use this mode when running tests in parallel (e.g., `cargo test`) | ||
| 29 | /// where each test needs complete isolation from other tests. | ||
| 30 | /// Each TestContext gets its own local cache. | ||
| 31 | Isolated, | ||
| 26 | 32 | ||
| 27 | /// Production audit - see all events, minimal writes | 33 | /// Shared mode - fixtures are cached and reused across tests |
| 28 | Production, | 34 | /// |
| 35 | /// Use this mode when running the CLI audit tool where tests run | ||
| 36 | /// sequentially and build on each other's fixtures. This is more | ||
| 37 | /// efficient as it avoids re-creating the same prerequisite events. | ||
| 38 | /// All TestContexts share the client's cache. | ||
| 39 | Shared, | ||
| 29 | } | 40 | } |
| 30 | 41 | ||
| 31 | impl AuditConfig { | 42 | impl AuditConfig { |
| 32 | /// Create config for CI/CD testing | 43 | /// Create config for isolated testing (e.g., cargo test) |
| 33 | pub fn ci() -> Self { | 44 | /// |
| 34 | let run_id = format!("ci-{}", &uuid::Uuid::new_v4().to_string()[..8]); | 45 | /// Each test creates fresh fixtures for complete test isolation. |
| 46 | /// Use this when running tests in parallel. | ||
| 47 | pub fn isolated() -> Self { | ||
| 48 | let run_id = format!("isolated-{}", &uuid::Uuid::new_v4().to_string()[..8]); | ||
| 35 | Self { | 49 | Self { |
| 36 | run_id, | 50 | run_id, |
| 37 | mode: AuditMode::CI, | 51 | mode: AuditMode::Isolated, |
| 38 | cleanup_after: Timestamp::now() + 3600, // 1 hour from now | 52 | cleanup_after: Timestamp::now() + 3600, // 1 hour from now |
| 39 | read_only: false, | 53 | read_only: false, |
| 40 | } | 54 | } |
| 41 | } | 55 | } |
| 42 | 56 | ||
| 43 | /// Create config for production audit | 57 | /// Create config for shared fixture mode (default for CLI) |
| 44 | pub fn production() -> Self { | 58 | /// |
| 45 | let run_id = format!("prod-audit-{}", Timestamp::now().as_u64()); | 59 | /// Fixtures are cached and reused across tests. Use this when |
| 60 | /// running the CLI audit tool where tests run sequentially. | ||
| 61 | pub fn shared() -> Self { | ||
| 62 | let run_id = format!("audit-{}", &uuid::Uuid::new_v4().to_string()[..8]); | ||
| 46 | Self { | 63 | Self { |
| 47 | run_id, | 64 | run_id, |
| 48 | mode: AuditMode::Production, | 65 | mode: AuditMode::Shared, |
| 49 | cleanup_after: Timestamp::now() + 300, // 5 minutes from now | 66 | cleanup_after: Timestamp::now() + 3600, // 1 hour from now |
| 50 | read_only: true, // Default to read-only for production | 67 | read_only: false, |
| 51 | } | 68 | } |
| 52 | } | 69 | } |
| 53 | 70 | ||
| 71 | /// Alias for isolated() - for backwards compatibility | ||
| 72 | #[deprecated(since = "0.2.0", note = "Use isolated() instead")] | ||
| 73 | pub fn ci() -> Self { | ||
| 74 | Self::isolated() | ||
| 75 | } | ||
| 76 | |||
| 77 | /// Alias for shared() - for backwards compatibility | ||
| 78 | #[deprecated(since = "0.2.0", note = "Use shared() instead")] | ||
| 79 | pub fn production() -> Self { | ||
| 80 | Self::shared() | ||
| 81 | } | ||
| 82 | |||
| 54 | /// Create config with custom run ID | 83 | /// Create config with custom run ID |
| 55 | pub fn with_run_id(run_id: String, mode: AuditMode) -> Self { | 84 | pub fn with_run_id(run_id: String, mode: AuditMode) -> Self { |
| 56 | Self { | 85 | Self { |
| 57 | run_id, | 86 | run_id, |
| 58 | mode, | 87 | mode, |
| 59 | cleanup_after: Timestamp::now() + 3600, | 88 | cleanup_after: Timestamp::now() + 3600, |
| 60 | read_only: mode == AuditMode::Production, | 89 | read_only: false, |
| 61 | } | 90 | } |
| 62 | } | 91 | } |
| 63 | 92 | ||
| @@ -72,11 +101,10 @@ impl AuditConfig { | |||
| 72 | /// | 101 | /// |
| 73 | /// 1. `["t", "grasp-audit-test-event"]` - Identifies all audit-related events | 102 | /// 1. `["t", "grasp-audit-test-event"]` - Identifies all audit-related events |
| 74 | /// 2. `["t", "audit-{run_id}"]` - Unique identifier for this audit run | 103 | /// 2. `["t", "audit-{run_id}"]` - Unique identifier for this audit run |
| 75 | /// - CI mode: `audit-ci-{uuid}` | 104 | /// - Isolated mode: `audit-isolated-{uuid}` |
| 76 | /// - Production mode: `audit-prod-audit-{timestamp}` | 105 | /// - Shared mode: `audit-audit-{uuid}` |
| 77 | /// 3. `["t", "audit-cleanup-after-{unix_timestamp}"]` - Cleanup timestamp | 106 | /// 3. `["t", "audit-cleanup-after-{unix_timestamp}"]` - Cleanup timestamp |
| 78 | /// - CI mode: Current time + 3600 seconds (1 hour) | 107 | /// - Default: Current time + 3600 seconds (1 hour) |
| 79 | /// - Production mode: Current time + 300 seconds (5 minutes) | ||
| 80 | /// | 108 | /// |
| 81 | /// # Purpose | 109 | /// # Purpose |
| 82 | /// | 110 | /// |
| @@ -90,7 +118,7 @@ impl AuditConfig { | |||
| 90 | /// ```rust | 118 | /// ```rust |
| 91 | /// use grasp_audit::AuditConfig; | 119 | /// use grasp_audit::AuditConfig; |
| 92 | /// | 120 | /// |
| 93 | /// let config = AuditConfig::ci(); | 121 | /// let config = AuditConfig::isolated(); |
| 94 | /// let tags = config.audit_tags(); | 122 | /// let tags = config.audit_tags(); |
| 95 | /// | 123 | /// |
| 96 | /// // Tags will look like: | 124 | /// // Tags will look like: |
| @@ -168,7 +196,7 @@ impl AuditEventBuilder { | |||
| 168 | /// use nostr_sdk::prelude::*; | 196 | /// use nostr_sdk::prelude::*; |
| 169 | /// use grasp_audit::{AuditConfig, AuditEventBuilder}; | 197 | /// use grasp_audit::{AuditConfig, AuditEventBuilder}; |
| 170 | /// | 198 | /// |
| 171 | /// let config = AuditConfig::ci(); | 199 | /// let config = AuditConfig::isolated(); |
| 172 | /// let keys = Keys::generate(); | 200 | /// let keys = Keys::generate(); |
| 173 | /// | 201 | /// |
| 174 | /// // Create an event with a past timestamp | 202 | /// // Create an event with a past timestamp |
| @@ -209,26 +237,26 @@ mod tests { | |||
| 209 | use super::*; | 237 | use super::*; |
| 210 | 238 | ||
| 211 | #[test] | 239 | #[test] |
| 212 | fn test_ci_config() { | 240 | fn test_isolated_config() { |
| 213 | let config = AuditConfig::ci(); | 241 | let config = AuditConfig::isolated(); |
| 214 | assert_eq!(config.mode, AuditMode::CI); | 242 | assert_eq!(config.mode, AuditMode::Isolated); |
| 215 | assert!(!config.read_only); | 243 | assert!(!config.read_only); |
| 216 | assert!(config.run_id.starts_with("ci-")); | 244 | assert!(config.run_id.starts_with("isolated-")); |
| 217 | } | 245 | } |
| 218 | 246 | ||
| 219 | #[test] | 247 | #[test] |
| 220 | fn test_production_config() { | 248 | fn test_shared_config() { |
| 221 | let config = AuditConfig::production(); | 249 | let config = AuditConfig::shared(); |
| 222 | assert_eq!(config.mode, AuditMode::Production); | 250 | assert_eq!(config.mode, AuditMode::Shared); |
| 223 | assert!(config.read_only); | 251 | assert!(!config.read_only); |
| 224 | assert!(config.run_id.starts_with("prod-audit-")); | 252 | assert!(config.run_id.starts_with("audit-")); |
| 225 | } | 253 | } |
| 226 | 254 | ||
| 227 | #[test] | 255 | #[test] |
| 228 | fn test_audit_tags() { | 256 | fn test_audit_tags() { |
| 229 | use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; | 257 | use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; |
| 230 | 258 | ||
| 231 | let config = AuditConfig::ci(); | 259 | let config = AuditConfig::isolated(); |
| 232 | let tags = config.audit_tags(); | 260 | let tags = config.audit_tags(); |
| 233 | 261 | ||
| 234 | assert_eq!(tags.len(), 3); | 262 | assert_eq!(tags.len(), 3); |
| @@ -252,7 +280,7 @@ mod tests { | |||
| 252 | // Check for "t" tag with "audit-{run_id}" | 280 | // Check for "t" tag with "audit-{run_id}" |
| 253 | assert!(tags.iter().any(|t| { | 281 | assert!(tags.iter().any(|t| { |
| 254 | t.content() | 282 | t.content() |
| 255 | .map(|c| c.starts_with("audit-ci-")) | 283 | .map(|c| c.starts_with("audit-isolated-")) |
| 256 | .unwrap_or(false) | 284 | .unwrap_or(false) |
| 257 | })); | 285 | })); |
| 258 | 286 | ||
| @@ -266,7 +294,7 @@ mod tests { | |||
| 266 | 294 | ||
| 267 | #[test] | 295 | #[test] |
| 268 | fn test_audit_event_builder() { | 296 | fn test_audit_event_builder() { |
| 269 | let config = AuditConfig::ci(); | 297 | let config = AuditConfig::isolated(); |
| 270 | let keys = Keys::generate(); | 298 | let keys = Keys::generate(); |
| 271 | 299 | ||
| 272 | let event = AuditEventBuilder::new(Kind::TextNote, "test", config.clone()) | 300 | let event = AuditEventBuilder::new(Kind::TextNote, "test", config.clone()) |
| @@ -283,7 +311,7 @@ mod tests { | |||
| 283 | 311 | ||
| 284 | #[test] | 312 | #[test] |
| 285 | fn test_custom_timestamp_applied() { | 313 | fn test_custom_timestamp_applied() { |
| 286 | let config = AuditConfig::ci(); | 314 | let config = AuditConfig::isolated(); |
| 287 | let keys = Keys::generate(); | 315 | let keys = Keys::generate(); |
| 288 | let custom_ts = Timestamp::from(1700000000); | 316 | let custom_ts = Timestamp::from(1700000000); |
| 289 | 317 | ||
| @@ -302,7 +330,7 @@ mod tests { | |||
| 302 | 330 | ||
| 303 | #[test] | 331 | #[test] |
| 304 | fn test_default_timestamp_uses_current_time() { | 332 | fn test_default_timestamp_uses_current_time() { |
| 305 | let config = AuditConfig::ci(); | 333 | let config = AuditConfig::isolated(); |
| 306 | let keys = Keys::generate(); | 334 | let keys = Keys::generate(); |
| 307 | 335 | ||
| 308 | let before = Timestamp::now(); | 336 | let before = Timestamp::now(); |
diff --git a/grasp-audit/src/bin/grasp-audit.rs b/grasp-audit/src/bin/grasp-audit.rs index 48c1580..0d88bce 100644 --- a/grasp-audit/src/bin/grasp-audit.rs +++ b/grasp-audit/src/bin/grasp-audit.rs | |||
| @@ -20,8 +20,11 @@ enum Commands { | |||
| 20 | #[arg(short, long)] | 20 | #[arg(short, long)] |
| 21 | relay: String, | 21 | relay: String, |
| 22 | 22 | ||
| 23 | /// Mode: ci or production | 23 | /// Fixture mode: shared (default) or isolated |
| 24 | #[arg(short, long, default_value = "ci")] | 24 | /// |
| 25 | /// - shared: Fixtures are cached and reused across tests (efficient for sequential test runs) | ||
| 26 | /// - isolated: Each test creates fresh fixtures (for parallel tests like cargo test) | ||
| 27 | #[arg(short, long, default_value = "shared")] | ||
| 25 | mode: String, | 28 | mode: String, |
| 26 | 29 | ||
| 27 | /// Spec to test (nip01-smoke, nip11, event-acceptance, cors, git-clone, push-auth, repo-creation, all) | 30 | /// Spec to test (nip01-smoke, nip11, event-acceptance, cors, git-clone, push-auth, repo-creation, all) |
| @@ -53,10 +56,14 @@ async fn main() -> Result<()> { | |||
| 53 | spec, | 56 | spec, |
| 54 | git_data_dir, | 57 | git_data_dir, |
| 55 | } => { | 58 | } => { |
| 59 | #[allow(deprecated)] | ||
| 56 | let mut config = match mode.as_str() { | 60 | let mut config = match mode.as_str() { |
| 61 | "shared" => AuditConfig::shared(), | ||
| 62 | "isolated" => AuditConfig::isolated(), | ||
| 63 | // Backwards compatibility aliases | ||
| 57 | "ci" => AuditConfig::ci(), | 64 | "ci" => AuditConfig::ci(), |
| 58 | "production" => AuditConfig::production(), | 65 | "production" => AuditConfig::production(), |
| 59 | _ => return Err(anyhow!("Invalid mode: {}. Use 'ci' or 'production'", mode)), | 66 | _ => return Err(anyhow!("Invalid mode: {}. Use 'shared' or 'isolated'", mode)), |
| 60 | }; | 67 | }; |
| 61 | 68 | ||
| 62 | // Audit needs to create events to test the relay, so disable read-only mode | 69 | // Audit needs to create events to test the relay, so disable read-only mode |
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index e4e9f07..21c70be 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs | |||
| @@ -199,7 +199,7 @@ impl AuditClient { | |||
| 199 | /// ```no_run | 199 | /// ```no_run |
| 200 | /// # use grasp_audit::*; | 200 | /// # use grasp_audit::*; |
| 201 | /// # async fn example() -> anyhow::Result<()> { | 201 | /// # async fn example() -> anyhow::Result<()> { |
| 202 | /// let config = AuditConfig::ci(); | 202 | /// let config = AuditConfig::shared(); |
| 203 | /// let client = AuditClient::new("ws://localhost:7000", config).await?; | 203 | /// let client = AuditClient::new("ws://localhost:7000", config).await?; |
| 204 | /// | 204 | /// |
| 205 | /// // Create event with automatic audit tags | 205 | /// // Create event with automatic audit tags |
| @@ -221,8 +221,8 @@ impl AuditClient { | |||
| 221 | pub async fn query(&self, mut filter: Filter) -> Result<Vec<Event>> { | 221 | pub async fn query(&self, mut filter: Filter) -> Result<Vec<Event>> { |
| 222 | use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; | 222 | use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; |
| 223 | 223 | ||
| 224 | if self.config.mode == AuditMode::CI { | 224 | if self.config.mode == AuditMode::Isolated { |
| 225 | // In CI mode, only see our own audit events | 225 | // In Isolated mode, only see our own audit events |
| 226 | // Filter by "t" tags (hashtags) | 226 | // Filter by "t" tags (hashtags) |
| 227 | let t_tag = SingleLetterTag::lowercase(Alphabet::T); | 227 | let t_tag = SingleLetterTag::lowercase(Alphabet::T); |
| 228 | filter = filter | 228 | filter = filter |
| @@ -505,7 +505,7 @@ mod tests { | |||
| 505 | 505 | ||
| 506 | #[tokio::test] | 506 | #[tokio::test] |
| 507 | async fn test_client_creation() { | 507 | async fn test_client_creation() { |
| 508 | let config = AuditConfig::ci(); | 508 | let config = AuditConfig::isolated(); |
| 509 | 509 | ||
| 510 | // This will fail if no relay is running, which is expected in tests | 510 | // This will fail if no relay is running, which is expected in tests |
| 511 | // In real usage, there should be a relay at the URL | 511 | // In real usage, there should be a relay at the URL |
| @@ -514,13 +514,13 @@ mod tests { | |||
| 514 | // We can't test connection without a running relay | 514 | // We can't test connection without a running relay |
| 515 | // But we can test that the client is created | 515 | // But we can test that the client is created |
| 516 | if let Ok(client) = result { | 516 | if let Ok(client) = result { |
| 517 | assert_eq!(client.config.mode, AuditMode::CI); | 517 | assert_eq!(client.config.mode, AuditMode::Isolated); |
| 518 | } | 518 | } |
| 519 | } | 519 | } |
| 520 | 520 | ||
| 521 | #[test] | 521 | #[test] |
| 522 | fn test_event_builder() { | 522 | fn test_event_builder() { |
| 523 | let config = AuditConfig::ci(); | 523 | let config = AuditConfig::isolated(); |
| 524 | let keys = Keys::generate(); | 524 | let keys = Keys::generate(); |
| 525 | let maintainer_keys = Keys::generate(); | 525 | let maintainer_keys = Keys::generate(); |
| 526 | let recursive_maintainer_keys = Keys::generate(); | 526 | let recursive_maintainer_keys = Keys::generate(); |
| @@ -543,7 +543,7 @@ mod tests { | |||
| 543 | 543 | ||
| 544 | #[test] | 544 | #[test] |
| 545 | fn test_audit_tags_automatically_added() { | 545 | fn test_audit_tags_automatically_added() { |
| 546 | let config = AuditConfig::ci(); | 546 | let config = AuditConfig::isolated(); |
| 547 | let keys = Keys::generate(); | 547 | let keys = Keys::generate(); |
| 548 | let maintainer_keys = Keys::generate(); | 548 | let maintainer_keys = Keys::generate(); |
| 549 | let recursive_maintainer_keys = Keys::generate(); | 549 | let recursive_maintainer_keys = Keys::generate(); |
| @@ -585,8 +585,8 @@ mod tests { | |||
| 585 | "Missing 'grasp-audit-test-event' tag" | 585 | "Missing 'grasp-audit-test-event' tag" |
| 586 | ); | 586 | ); |
| 587 | assert!( | 587 | assert!( |
| 588 | tag_contents.iter().any(|t| t.starts_with("audit-ci-")), | 588 | tag_contents.iter().any(|t| t.starts_with("audit-isolated-")), |
| 589 | "Missing 'audit-ci-*' tag" | 589 | "Missing 'audit-isolated-*' tag" |
| 590 | ); | 590 | ); |
| 591 | assert!( | 591 | assert!( |
| 592 | tag_contents | 592 | tag_contents |
| @@ -604,7 +604,7 @@ mod tests { | |||
| 604 | 604 | ||
| 605 | #[tokio::test] | 605 | #[tokio::test] |
| 606 | async fn test_create_repo_announcement_with_maintainers() { | 606 | async fn test_create_repo_announcement_with_maintainers() { |
| 607 | let config = AuditConfig::ci(); | 607 | let config = AuditConfig::isolated(); |
| 608 | let client = AuditClient::new_test(config); | 608 | let client = AuditClient::new_test(config); |
| 609 | 609 | ||
| 610 | // Create test maintainer pubkeys (hex format) | 610 | // Create test maintainer pubkeys (hex format) |
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index e5a80d7..cc2d2f6 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -3,19 +3,25 @@ | |||
| 3 | //! This module provides a TestContext abstraction that manages prerequisite events | 3 | //! This module provides a TestContext abstraction that manages prerequisite events |
| 4 | //! differently based on the audit mode: | 4 | //! differently based on the audit mode: |
| 5 | //! | 5 | //! |
| 6 | //! - **CI Mode (Isolated)**: Creates fresh events for each test, ensuring complete isolation | 6 | //! - **Isolated Mode**: Creates fresh events for each test, ensuring complete isolation. |
| 7 | //! - **Production Mode (Shared)**: Reuses shared fixtures to minimize event publication | 7 | //! Use this for `cargo test` where tests run in parallel and need isolation. |
| 8 | //! - **Shared Mode**: Reuses shared fixtures across tests to minimize event publication. | ||
| 9 | //! Use this for CLI audit where tests run sequentially and build on each other. | ||
| 8 | //! | 10 | //! |
| 9 | //! # Cache Sharing Strategy | 11 | //! # Cache Sharing Strategy |
| 10 | //! | 12 | //! |
| 11 | //! The fixture cache lives on the `AuditClient`, not on `TestContext`. This provides | 13 | //! The caching behavior depends on the mode: |
| 12 | //! natural cache sharing semantics: | ||
| 13 | //! | 14 | //! |
| 14 | //! - **CLI mode**: Creates one `AuditClient` → fixtures shared across all tests | 15 | //! - **Shared mode** (default for CLI): Uses the client's fixture cache, shared across |
| 15 | //! - **cargo test**: Creates one `AuditClient` per test → fixtures isolated per test | 16 | //! all TestContext instances. Fixtures are created once and reused. |
| 17 | //! - **Isolated mode**: Each TestContext has its own local cache. Fixtures are created | ||
| 18 | //! fresh for each TestContext, providing complete test isolation. | ||
| 16 | //! | 19 | //! |
| 17 | //! This eliminates the need for global state while still enabling fixture reuse | 20 | //! # When to Use Each Mode |
| 18 | //! when appropriate. | 21 | //! |
| 22 | //! - **CLI audit tool**: Use Shared mode (default). Tests run sequentially and fixtures | ||
| 23 | //! build on each other efficiently. | ||
| 24 | //! - **cargo test**: Use Isolated mode. Tests run in parallel and need complete isolation. | ||
| 19 | //! | 25 | //! |
| 20 | //! # What is a Fixture? | 26 | //! # What is a Fixture? |
| 21 | //! A fixture represents the state of a repository on a grasp server (events and git refs) | 27 | //! A fixture represents the state of a repository on a grasp server (events and git refs) |
| @@ -36,7 +42,7 @@ | |||
| 36 | //! use grasp_audit::*; | 42 | //! use grasp_audit::*; |
| 37 | //! | 43 | //! |
| 38 | //! # async fn example() -> anyhow::Result<()> { | 44 | //! # async fn example() -> anyhow::Result<()> { |
| 39 | //! let config = AuditConfig::ci(); | 45 | //! let config = AuditConfig::shared(); // Use shared() for CLI, isolated() for cargo test |
| 40 | //! let client = AuditClient::new("ws://localhost:7000", config).await?; | 46 | //! let client = AuditClient::new("ws://localhost:7000", config).await?; |
| 41 | //! let ctx = TestContext::new(&client); | 47 | //! let ctx = TestContext::new(&client); |
| 42 | //! | 48 | //! |
| @@ -341,8 +347,8 @@ pub enum ContextMode { | |||
| 341 | impl From<AuditMode> for ContextMode { | 347 | impl From<AuditMode> for ContextMode { |
| 342 | fn from(mode: AuditMode) -> Self { | 348 | fn from(mode: AuditMode) -> Self { |
| 343 | match mode { | 349 | match mode { |
| 344 | AuditMode::CI => ContextMode::Isolated, | 350 | AuditMode::Isolated => ContextMode::Isolated, |
| 345 | AuditMode::Production => ContextMode::Shared, | 351 | AuditMode::Shared => ContextMode::Shared, |
| 346 | } | 352 | } |
| 347 | } | 353 | } |
| 348 | } | 354 | } |
| @@ -350,30 +356,37 @@ impl From<AuditMode> for ContextMode { | |||
| 350 | /// Test context for managing prerequisite events | 356 | /// Test context for managing prerequisite events |
| 351 | /// | 357 | /// |
| 352 | /// The TestContext provides mode-aware fixture management: | 358 | /// The TestContext provides mode-aware fixture management: |
| 353 | /// - In Isolated mode: Creates fresh events for each test | 359 | /// - In **Isolated mode**: Each TestContext has its own local cache, creating fresh |
| 354 | /// - In Shared mode: Caches and reuses events across tests | 360 | /// fixtures for each test. Use this for `cargo test` where tests run in parallel. |
| 361 | /// - In **Shared mode**: Uses the client's fixture cache, shared across all TestContexts. | ||
| 362 | /// Use this for CLI audit where tests run sequentially and build on each other. | ||
| 355 | /// | 363 | /// |
| 356 | /// # Cache Location | 364 | /// # Mode Selection |
| 357 | /// | 365 | /// |
| 358 | /// The fixture cache lives on `AuditClient`, not on `TestContext`. This means: | 366 | /// The mode is determined by `AuditConfig::mode`: |
| 359 | /// - Multiple `TestContext` instances from the same client share the cache | 367 | /// - `AuditConfig::isolated()` → Creates fresh fixtures per TestContext |
| 360 | /// - CLI mode (one client) naturally shares fixtures across all tests | 368 | /// - `AuditConfig::shared()` → Reuses fixtures across all TestContexts (default for CLI) |
| 361 | /// - Test mode (one client per test) naturally isolates fixtures | ||
| 362 | /// | 369 | /// |
| 363 | /// # Example | 370 | /// # Example |
| 364 | /// | 371 | /// |
| 365 | /// ```no_run | 372 | /// ```no_run |
| 366 | /// # use grasp_audit::*; | 373 | /// # use grasp_audit::*; |
| 367 | /// # async fn example() -> anyhow::Result<()> { | 374 | /// # async fn example() -> anyhow::Result<()> { |
| 368 | /// let config = AuditConfig::ci(); | 375 | /// // For CLI audit (shared fixtures - default) |
| 376 | /// let config = AuditConfig::shared(); | ||
| 369 | /// let client = AuditClient::new("ws://localhost:7000", config).await?; | 377 | /// let client = AuditClient::new("ws://localhost:7000", config).await?; |
| 370 | /// let ctx = TestContext::new(&client); | 378 | /// let ctx = TestContext::new(&client); |
| 371 | /// | 379 | /// |
| 372 | /// // Get a repository fixture | 380 | /// // Get a repository fixture - will be reused by subsequent TestContexts |
| 373 | /// let repo = ctx.get_fixture(FixtureKind::ValidRepo).await?; | 381 | /// let repo = ctx.get_fixture(FixtureKind::ValidRepo).await?; |
| 374 | /// | 382 | /// |
| 375 | /// // In CI mode: Creates new repo | 383 | /// // For cargo test (isolated fixtures) |
| 376 | /// // In Production mode: Returns cached repo | 384 | /// let config = AuditConfig::isolated(); |
| 385 | /// let client = AuditClient::new("ws://localhost:7000", config).await?; | ||
| 386 | /// let ctx = TestContext::new(&client); | ||
| 387 | /// | ||
| 388 | /// // Get a repository fixture - fresh for this TestContext only | ||
| 389 | /// let repo = ctx.get_fixture(FixtureKind::ValidRepo).await?; | ||
| 377 | /// # Ok(()) | 390 | /// # Ok(()) |
| 378 | /// # } | 391 | /// # } |
| 379 | /// ``` | 392 | /// ``` |
| @@ -1960,9 +1973,9 @@ mod tests { | |||
| 1960 | 1973 | ||
| 1961 | #[test] | 1974 | #[test] |
| 1962 | fn test_context_mode_from_audit_mode() { | 1975 | fn test_context_mode_from_audit_mode() { |
| 1963 | assert_eq!(ContextMode::from(AuditMode::CI), ContextMode::Isolated); | 1976 | assert_eq!(ContextMode::from(AuditMode::Isolated), ContextMode::Isolated); |
| 1964 | assert_eq!( | 1977 | assert_eq!( |
| 1965 | ContextMode::from(AuditMode::Production), | 1978 | ContextMode::from(AuditMode::Shared), |
| 1966 | ContextMode::Shared | 1979 | ContextMode::Shared |
| 1967 | ); | 1980 | ); |
| 1968 | } | 1981 | } |
| @@ -1981,7 +1994,7 @@ mod tests { | |||
| 1981 | 1994 | ||
| 1982 | #[tokio::test] | 1995 | #[tokio::test] |
| 1983 | async fn test_context_creation() { | 1996 | async fn test_context_creation() { |
| 1984 | let config = AuditConfig::ci(); | 1997 | let config = AuditConfig::isolated(); |
| 1985 | let client = crate::AuditClient::new_test(config); | 1998 | let client = crate::AuditClient::new_test(config); |
| 1986 | 1999 | ||
| 1987 | let ctx = TestContext::new(&client); | 2000 | let ctx = TestContext::new(&client); |
diff --git a/grasp-audit/src/lib.rs b/grasp-audit/src/lib.rs index 6df240f..655ee83 100644 --- a/grasp-audit/src/lib.rs +++ b/grasp-audit/src/lib.rs | |||
| @@ -16,8 +16,8 @@ | |||
| 16 | //! | 16 | //! |
| 17 | //! #[tokio::main] | 17 | //! #[tokio::main] |
| 18 | //! async fn main() -> Result<(), Box<dyn std::error::Error>> { | 18 | //! async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 19 | //! // Create audit client for CI testing | 19 | //! // Create audit client with shared fixtures (default for CLI) |
| 20 | //! let config = AuditConfig::ci(); | 20 | //! let config = AuditConfig::shared(); |
| 21 | //! let client = AuditClient::new("ws://localhost:7000", config).await?; | 21 | //! let client = AuditClient::new("ws://localhost:7000", config).await?; |
| 22 | //! | 22 | //! |
| 23 | //! // Run smoke tests | 23 | //! // Run smoke tests |
diff --git a/grasp-audit/src/specs/grasp01/cors.rs b/grasp-audit/src/specs/grasp01/cors.rs index c877c04..409f2d3 100644 --- a/grasp-audit/src/specs/grasp01/cors.rs +++ b/grasp-audit/src/specs/grasp01/cors.rs | |||
| @@ -459,7 +459,7 @@ mod tests { | |||
| 459 | .trim_end_matches('/') | 459 | .trim_end_matches('/') |
| 460 | .to_string(); | 460 | .to_string(); |
| 461 | 461 | ||
| 462 | let config = AuditConfig::ci(); | 462 | let config = AuditConfig::isolated(); |
| 463 | let client = AuditClient::new(&relay_url, config) | 463 | let client = AuditClient::new(&relay_url, config) |
| 464 | .await | 464 | .await |
| 465 | .unwrap_or_else(|_| { | 465 | .unwrap_or_else(|_| { |
diff --git a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs index fee51db..89220ea 100644 --- a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs +++ b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs | |||
| @@ -1139,7 +1139,7 @@ mod tests { | |||
| 1139 | "RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081", | 1139 | "RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081", |
| 1140 | ); | 1140 | ); |
| 1141 | 1141 | ||
| 1142 | let config = AuditConfig::ci(); | 1142 | let config = AuditConfig::isolated(); |
| 1143 | let client = AuditClient::new(&relay_url, config) | 1143 | let client = AuditClient::new(&relay_url, config) |
| 1144 | .await | 1144 | .await |
| 1145 | .unwrap_or_else(|_| { | 1145 | .unwrap_or_else(|_| { |
diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs index 9a7d7d1..8a0a4d1 100644 --- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs +++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs | |||
| @@ -297,7 +297,7 @@ mod tests { | |||
| 297 | let relay_url = std::env::var("RELAY_URL") | 297 | let relay_url = std::env::var("RELAY_URL") |
| 298 | .expect("RELAY_URL environment variable must be set for integration tests"); | 298 | .expect("RELAY_URL environment variable must be set for integration tests"); |
| 299 | 299 | ||
| 300 | let config = AuditConfig::ci(); | 300 | let config = AuditConfig::isolated(); |
| 301 | let client = AuditClient::new(&relay_url, config) | 301 | let client = AuditClient::new(&relay_url, config) |
| 302 | .await | 302 | .await |
| 303 | .expect("Failed to connect to relay"); | 303 | .expect("Failed to connect to relay"); |
diff --git a/grasp-audit/src/specs/grasp01/nip11_document.rs b/grasp-audit/src/specs/grasp01/nip11_document.rs index 33599b1..f4ca7b4 100644 --- a/grasp-audit/src/specs/grasp01/nip11_document.rs +++ b/grasp-audit/src/specs/grasp01/nip11_document.rs | |||
| @@ -288,7 +288,7 @@ mod tests { | |||
| 288 | "RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081", | 288 | "RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081", |
| 289 | ); | 289 | ); |
| 290 | 290 | ||
| 291 | let config = AuditConfig::ci(); | 291 | let config = AuditConfig::isolated(); |
| 292 | let client = AuditClient::new(&relay_url, config) | 292 | let client = AuditClient::new(&relay_url, config) |
| 293 | .await | 293 | .await |
| 294 | .unwrap_or_else(|_| { | 294 | .unwrap_or_else(|_| { |
diff --git a/grasp-audit/src/specs/grasp01/spec_requirements.rs b/grasp-audit/src/specs/grasp01/spec_requirements.rs index 591fef1..9a833d8 100644 --- a/grasp-audit/src/specs/grasp01/spec_requirements.rs +++ b/grasp-audit/src/specs/grasp01/spec_requirements.rs | |||
| @@ -206,4 +206,4 @@ mod tests { | |||
| 206 | fn test_requirement_count() { | 206 | fn test_requirement_count() { |
| 207 | assert_eq!(GRASP_01_REQUIREMENTS.len(), 19); | 207 | assert_eq!(GRASP_01_REQUIREMENTS.len(), 19); |
| 208 | } | 208 | } |
| 209 | } \ No newline at end of file | 209 | } |