diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 02:19:47 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 02:19:47 +0000 |
| commit | 43e53b4ccd9fcebf20cc9c1bdbfe568ddd8051b9 (patch) | |
| tree | 4aea212e5b1d307affb8e0ce5db1cc898e217889 /grasp-audit | |
| parent | f053827e0a157f348d9cf834f026a8de322abfe2 (diff) | |
grasp-audit improve cli output eg. add colors and condense
Diffstat (limited to 'grasp-audit')
| -rw-r--r-- | grasp-audit/README.md | 29 | ||||
| -rw-r--r-- | grasp-audit/src/result.rs | 76 |
2 files changed, 76 insertions, 29 deletions
diff --git a/grasp-audit/README.md b/grasp-audit/README.md index 44deec2..ab3bddc 100644 --- a/grasp-audit/README.md +++ b/grasp-audit/README.md | |||
| @@ -21,13 +21,13 @@ nix develop -c bash test-ngit-relay.sh --mode test | |||
| 21 | ``` | 21 | ``` |
| 22 | 22 | ||
| 23 | This automatically: | 23 | This automatically: |
| 24 | |||
| 24 | - ✅ Starts ngit-relay in an isolated Docker container | 25 | - ✅ Starts ngit-relay in an isolated Docker container |
| 25 | - ✅ Runs all GRASP-01 compliance tests | 26 | - ✅ Runs all GRASP-01 compliance tests |
| 26 | - ✅ Cleans up resources when finished | 27 | - ✅ Cleans up resources when finished |
| 27 | 28 | ||
| 28 | **Currently Passing:** 4/18 tests (14 tests stubbed with "Not implemented yet") | ||
| 29 | |||
| 30 | For more options: | 29 | For more options: |
| 30 | |||
| 31 | ```bash | 31 | ```bash |
| 32 | ./test-ngit-relay.sh --help | 32 | ./test-ngit-relay.sh --help |
| 33 | ``` | 33 | ``` |
| @@ -218,16 +218,16 @@ pub async fn test_something(client: &AuditClient) -> TestResult { | |||
| 218 | .run(|| async { | 218 | .run(|| async { |
| 219 | // 1. Context | 219 | // 1. Context |
| 220 | let ctx = TestContext::new(client); | 220 | let ctx = TestContext::new(client); |
| 221 | 221 | ||
| 222 | // 2. Prerequisites (cached per-TestContext) | 222 | // 2. Prerequisites (cached per-TestContext) |
| 223 | let repo = ctx.get_fixture(FixtureKind::ValidRepo).await?; | 223 | let repo = ctx.get_fixture(FixtureKind::ValidRepo).await?; |
| 224 | 224 | ||
| 225 | // 3. Test-specific event | 225 | // 3. Test-specific event |
| 226 | let my_event = client.create_issue(&repo, "Title", "Content", vec![])?; | 226 | let my_event = client.create_issue(&repo, "Title", "Content", vec![])?; |
| 227 | 227 | ||
| 228 | // 4. Verify | 228 | // 4. Verify |
| 229 | send_and_verify_accepted(client, my_event, "description").await?; | 229 | send_and_verify_accepted(client, my_event, "description").await?; |
| 230 | 230 | ||
| 231 | Ok(()) | 231 | Ok(()) |
| 232 | }) | 232 | }) |
| 233 | .await | 233 | .await |
| @@ -252,14 +252,14 @@ pub async fn test_something(client: &AuditClient) -> TestResult { | |||
| 252 | 252 | ||
| 253 | ### Available Fixtures | 253 | ### Available Fixtures |
| 254 | 254 | ||
| 255 | | FixtureKind | Provides | Use When | | 255 | | FixtureKind | Provides | Use When | |
| 256 | |-------------|----------|----------| | 256 | | ------------------------ | --------------------------------------- | ------------------------------------- | |
| 257 | | `ValidRepo` | Accepted repo announcement (kind 30617) | Need a repo as prerequisite | | 257 | | `ValidRepo` | Accepted repo announcement (kind 30617) | Need a repo as prerequisite | |
| 258 | | `RepoState` | Repo + state event (kind 30618) | Testing owner push authorization | | 258 | | `RepoState` | Repo + state event (kind 30618) | Testing owner push authorization | |
| 259 | | `MaintainerAnnouncement` | Maintainer's repo announcement | Testing maintainer chain setup | | 259 | | `MaintainerAnnouncement` | Maintainer's repo announcement | Testing maintainer chain setup | |
| 260 | | `MaintainerState` | Maintainer's state event | Testing maintainer push authorization | | 260 | | `MaintainerState` | Maintainer's state event | Testing maintainer push authorization | |
| 261 | | `RepoWithIssue` | Repo + accepted issue (kind 1621) | Testing issue-dependent events | | 261 | | `RepoWithIssue` | Repo + accepted issue (kind 1621) | Testing issue-dependent events | |
| 262 | | `RepoWithComment` | Repo + issue + comment | Testing comment-dependent events | | 262 | | `RepoWithComment` | Repo + issue + comment | Testing comment-dependent events | |
| 263 | 263 | ||
| 264 | ### Fixture Lifecycle: Generate → Send → Verify | 264 | ### Fixture Lifecycle: Generate → Send → Verify |
| 265 | 265 | ||
| @@ -283,6 +283,7 @@ Each TestContext shares a `run_id` with all events: | |||
| 283 | ``` | 283 | ``` |
| 284 | 284 | ||
| 285 | This enables: | 285 | This enables: |
| 286 | |||
| 286 | - Event correlation within a test run | 287 | - Event correlation within a test run |
| 287 | - Production relay cleanup scripts | 288 | - Production relay cleanup scripts |
| 288 | - Test isolation between runs | 289 | - Test isolation between runs |
diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs index de377e5..2bec5c8 100644 --- a/grasp-audit/src/result.rs +++ b/grasp-audit/src/result.rs | |||
| @@ -1,7 +1,32 @@ | |||
| 1 | //! Test result types | 1 | //! Test result types |
| 2 | 2 | ||
| 3 | use std::collections::BTreeMap; | ||
| 3 | use std::time::{Duration, Instant}; | 4 | use std::time::{Duration, Instant}; |
| 4 | 5 | ||
| 6 | // ANSI color codes | ||
| 7 | const GREEN: &str = "\x1b[32m"; | ||
| 8 | const RED: &str = "\x1b[31m"; | ||
| 9 | const RESET: &str = "\x1b[0m"; | ||
| 10 | const BOLD: &str = "\x1b[1m"; | ||
| 11 | |||
| 12 | /// Extract spec category from a spec_ref by removing trailing test number | ||
| 13 | /// e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance" | ||
| 14 | /// e.g., "NIP-01:basic:2" -> "NIP-01:basic" | ||
| 15 | fn extract_spec_category(spec_ref: &str) -> String { | ||
| 16 | let parts: Vec<&str> = spec_ref.split(':').collect(); | ||
| 17 | if parts.len() >= 2 { | ||
| 18 | // Check if the last part looks like a test number (starts with digit) | ||
| 19 | if let Some(last) = parts.last() { | ||
| 20 | if last.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) { | ||
| 21 | // Remove the trailing number part | ||
| 22 | return parts[..parts.len() - 1].join(":"); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | // Return as-is if no trailing number found | ||
| 27 | spec_ref.to_string() | ||
| 28 | } | ||
| 29 | |||
| 5 | /// Result of a single test | 30 | /// Result of a single test |
| 6 | #[derive(Debug, Clone)] | 31 | #[derive(Debug, Clone)] |
| 7 | pub struct TestResult { | 32 | pub struct TestResult { |
| @@ -108,34 +133,55 @@ impl AuditResult { | |||
| 108 | self.results.len() | 133 | self.results.len() |
| 109 | } | 134 | } |
| 110 | 135 | ||
| 111 | /// Print a detailed report | 136 | /// Print a detailed report with tests grouped by spec_ref |
| 112 | pub fn print_report(&self) { | 137 | pub fn print_report(&self) { |
| 113 | println!("\n{}", self.spec); | 138 | println!("\n{}{}{}", BOLD, self.spec, RESET); |
| 114 | println!("{}", "═".repeat(60)); | 139 | println!("{}", "═".repeat(60)); |
| 115 | println!(); | ||
| 116 | 140 | ||
| 117 | let passed = self.passed_count(); | 141 | let passed = self.passed_count(); |
| 118 | let total = self.total_count(); | 142 | let total = self.total_count(); |
| 119 | 143 | ||
| 144 | // Group results by spec category (strip trailing test number like ":1.1") | ||
| 145 | let mut grouped: BTreeMap<String, Vec<&TestResult>> = BTreeMap::new(); | ||
| 120 | for result in &self.results { | 146 | for result in &self.results { |
| 121 | let status = if result.passed { "✓" } else { "✗" }; | 147 | // Extract category from spec_ref (e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance") |
| 148 | let category = extract_spec_category(&result.spec_ref); | ||
| 149 | grouped | ||
| 150 | .entry(category) | ||
| 151 | .or_default() | ||
| 152 | .push(result); | ||
| 153 | } | ||
| 122 | 154 | ||
| 123 | println!("{} {} ({})", status, result.name, result.spec_ref); | 155 | // Print grouped results |
| 124 | println!(" Requirement: {}", result.requirement); | 156 | for (category, results) in &grouped { |
| 157 | println!("\n{}[{}]{}", BOLD, category, RESET); | ||
| 125 | 158 | ||
| 126 | if let Some(error) = &result.error { | 159 | for result in results { |
| 127 | println!(" Error: {}", error); | 160 | let (color, status) = if result.passed { |
| 128 | } | 161 | (GREEN, "✓") |
| 162 | } else { | ||
| 163 | (RED, "✗") | ||
| 164 | }; | ||
| 165 | |||
| 166 | println!(" {}{} {}{}", color, status, result.name, RESET); | ||
| 129 | 167 | ||
| 130 | println!(" Duration: {:?}", result.duration); | 168 | if let Some(error) = &result.error { |
| 131 | println!(); | 169 | println!(" {}Error: {}{}", RED, error, RESET); |
| 170 | } | ||
| 171 | } | ||
| 132 | } | 172 | } |
| 133 | 173 | ||
| 134 | println!( | 174 | println!(); |
| 135 | "Results: {}/{} passed ({:.1}%)", | 175 | let pass_rate = if total > 0 { |
| 136 | passed, | ||
| 137 | total, | ||
| 138 | (passed as f64 / total as f64) * 100.0 | 176 | (passed as f64 / total as f64) * 100.0 |
| 177 | } else { | ||
| 178 | 0.0 | ||
| 179 | }; | ||
| 180 | |||
| 181 | let summary_color = if passed == total { GREEN } else { RED }; | ||
| 182 | println!( | ||
| 183 | "{}Results: {}/{} passed ({:.1}%){}", | ||
| 184 | summary_color, passed, total, pass_rate, RESET | ||
| 139 | ); | 185 | ); |
| 140 | println!(); | 186 | println!(); |
| 141 | } | 187 | } |