diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-02 20:54:15 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-02 21:03:24 +0000 |
| commit | 5c10ca008413744b09136618eaa85275c997704c (patch) | |
| tree | af24387d8916bdec26315a31f67bd99c39544544 /grasp-audit | |
| parent | c07954f44f4c03cc17d4a83b144667cbcbb226cf (diff) | |
grasp-audit: show tests under GRASP-01 line
Diffstat (limited to 'grasp-audit')
| -rw-r--r-- | grasp-audit/src/result.rs | 188 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/event_acceptance_policy.rs | 57 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/git_clone.rs | 43 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/mod.rs | 5 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/nip01_smoke.rs | 12 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/nip11_document.rs | 16 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 168 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/repository_creation.rs | 35 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/spec_requirements.rs | 209 |
9 files changed, 531 insertions, 202 deletions
diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs index bc0008a..0de16ae 100644 --- a/grasp-audit/src/result.rs +++ b/grasp-audit/src/result.rs | |||
| @@ -1,35 +1,56 @@ | |||
| 1 | //! Test result types | 1 | //! Test result types |
| 2 | 2 | ||
| 3 | use crate::specs::grasp01::{get_sections, GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID}; | ||
| 3 | use std::collections::BTreeMap; | 4 | use std::collections::BTreeMap; |
| 4 | use std::time::{Duration, Instant}; | 5 | use std::time::{Duration, Instant}; |
| 5 | 6 | ||
| 6 | // ANSI color codes | 7 | // ANSI color codes |
| 7 | const GREEN: &str = "\x1b[32m"; | 8 | const GREEN: &str = "\x1b[32m"; |
| 8 | const RED: &str = "\x1b[31m"; | 9 | const RED: &str = "\x1b[31m"; |
| 10 | const YELLOW: &str = "\x1b[33m"; | ||
| 11 | const BLUE: &str = "\x1b[34m"; | ||
| 12 | const CYAN: &str = "\x1b[36m"; | ||
| 9 | const RESET: &str = "\x1b[0m"; | 13 | const RESET: &str = "\x1b[0m"; |
| 10 | const BOLD: &str = "\x1b[1m"; | 14 | const BOLD: &str = "\x1b[1m"; |
| 11 | 15 | ||
| 12 | /// Extract spec category from a spec_ref by removing trailing test number | 16 | /// Parse line number(s) from a spec_ref string |
| 13 | /// e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance" | 17 | /// Returns a vector of line numbers that this spec_ref covers |
| 14 | /// e.g., "NIP-01:basic:2" -> "NIP-01:basic" | 18 | /// |
| 15 | fn extract_spec_category(spec_ref: &str) -> String { | 19 | /// Examples: |
| 20 | /// - "GRASP-01:nostr-relay:7" -> [7] | ||
| 21 | /// - "GRASP-01:nostr-relay:7-9" -> [7, 8, 9] | ||
| 22 | /// - "NIP-01:basic:2" -> [] (not a GRASP-01 ref) | ||
| 23 | fn parse_spec_lines(spec_ref: &str) -> Vec<u32> { | ||
| 24 | // Only parse GRASP-01 refs | ||
| 25 | if !spec_ref.starts_with("GRASP-01:") { | ||
| 26 | return vec![]; | ||
| 27 | } | ||
| 28 | |||
| 29 | // Get the last part after the last colon | ||
| 16 | let parts: Vec<&str> = spec_ref.split(':').collect(); | 30 | let parts: Vec<&str> = spec_ref.split(':').collect(); |
| 17 | if parts.len() >= 2 { | 31 | if parts.len() < 3 { |
| 18 | // Check if the last part looks like a test number (starts with digit) | 32 | return vec![]; |
| 19 | if let Some(last) = parts.last() { | 33 | } |
| 20 | if last | 34 | |
| 21 | .chars() | 35 | let line_part = parts.last().unwrap(); |
| 22 | .next() | 36 | |
| 23 | .map(|c| c.is_ascii_digit()) | 37 | // Handle range format like "7-9" |
| 24 | .unwrap_or(false) | 38 | if line_part.contains('-') { |
| 25 | { | 39 | let range_parts: Vec<&str> = line_part.split('-').collect(); |
| 26 | // Remove the trailing number part | 40 | if range_parts.len() == 2 { |
| 27 | return parts[..parts.len() - 1].join(":"); | 41 | if let (Ok(start), Ok(end)) = (range_parts[0].parse::<u32>(), range_parts[1].parse::<u32>()) { |
| 42 | return (start..=end).collect(); | ||
| 28 | } | 43 | } |
| 29 | } | 44 | } |
| 45 | return vec![]; | ||
| 30 | } | 46 | } |
| 31 | // Return as-is if no trailing number found | 47 | |
| 32 | spec_ref.to_string() | 48 | // Handle single line number |
| 49 | if let Ok(line) = line_part.parse::<u32>() { | ||
| 50 | return vec![line]; | ||
| 51 | } | ||
| 52 | |||
| 53 | vec![] | ||
| 33 | } | 54 | } |
| 34 | 55 | ||
| 35 | /// Result of a single test | 56 | /// Result of a single test |
| @@ -138,53 +159,100 @@ impl AuditResult { | |||
| 138 | self.results.len() | 159 | self.results.len() |
| 139 | } | 160 | } |
| 140 | 161 | ||
| 141 | /// Print a detailed report with tests grouped by spec_ref | 162 | /// Print a detailed report aligned to GRASP-01 specification |
| 142 | pub fn print_report(&self) { | 163 | pub fn print_report(&self) { |
| 143 | println!("\n{}{}{}", BOLD, self.spec, RESET); | 164 | println!(); |
| 144 | println!("{}", "═".repeat(60)); | 165 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); |
| 145 | 166 | println!("{}GRASP-01 Compliance Report{}", BOLD, RESET); | |
| 146 | let passed = self.passed_count(); | 167 | println!("Source: github.com/nostr-protocol/grasp (commit: {})", GRASP_COMMIT_ID); |
| 147 | let total = self.total_count(); | 168 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); |
| 148 | 169 | ||
| 149 | // Group results by spec category (strip trailing test number like ":1.1") | 170 | // Build a map of spec line -> tests that cover it |
| 150 | let mut grouped: BTreeMap<String, Vec<&TestResult>> = BTreeMap::new(); | 171 | let mut tests_by_line: BTreeMap<u32, Vec<&TestResult>> = BTreeMap::new(); |
| 151 | for result in &self.results { | 172 | for result in &self.results { |
| 152 | // Extract category from spec_ref (e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance") | 173 | let lines = parse_spec_lines(&result.spec_ref); |
| 153 | let category = extract_spec_category(&result.spec_ref); | 174 | for line in lines { |
| 154 | grouped.entry(category).or_default().push(result); | 175 | tests_by_line.entry(line).or_default().push(result); |
| 176 | } | ||
| 155 | } | 177 | } |
| 156 | 178 | ||
| 157 | // Print grouped results | 179 | // Track how many spec requirements have tests |
| 158 | for (category, results) in &grouped { | 180 | let mut tested_requirements = 0; |
| 159 | println!("\n{}[{}]{}", BOLD, category, RESET); | 181 | let total_requirements = GRASP_01_REQUIREMENTS.len(); |
| 160 | 182 | ||
| 161 | for result in results { | 183 | // Print results organized by section and spec line |
| 162 | let (color, status) = if result.passed { | 184 | for section in get_sections() { |
| 163 | (GREEN, "✓") | 185 | println!(); |
| 186 | println!("{}{}## {}{}", CYAN, BOLD, section, RESET); | ||
| 187 | |||
| 188 | for req in GRASP_01_REQUIREMENTS.iter().filter(|r| r.section == section) { | ||
| 189 | println!(); | ||
| 190 | // Print spec requirement in blue | ||
| 191 | println!("{}📘 Line {}: {}{}", BLUE, req.line, req.text, RESET); | ||
| 192 | |||
| 193 | // Get tests for this line | ||
| 194 | if let Some(tests) = tests_by_line.get(&req.line) { | ||
| 195 | tested_requirements += 1; | ||
| 196 | for test in tests { | ||
| 197 | let (color, status) = if test.passed { | ||
| 198 | (GREEN, "✓") | ||
| 199 | } else { | ||
| 200 | (RED, "✗") | ||
| 201 | }; | ||
| 202 | println!(" {}{} {}{}", color, status, test.name, RESET); | ||
| 203 | |||
| 204 | if let Some(error) = &test.error { | ||
| 205 | // Truncate long errors | ||
| 206 | let error_display = if error.len() > 100 { | ||
| 207 | format!("{}...", &error[..100]) | ||
| 208 | } else { | ||
| 209 | error.clone() | ||
| 210 | }; | ||
| 211 | println!(" {}Error: {}{}", RED, error_display, RESET); | ||
| 212 | } | ||
| 213 | } | ||
| 164 | } else { | 214 | } else { |
| 165 | (RED, "✗") | 215 | println!(" {}⚠️ No Tests Implemented{}", YELLOW, RESET); |
| 166 | }; | ||
| 167 | |||
| 168 | println!(" {}{} {}{}", color, status, result.name, RESET); | ||
| 169 | |||
| 170 | if let Some(error) = &result.error { | ||
| 171 | println!(" {}Error: {}{}", RED, error, RESET); | ||
| 172 | } | 216 | } |
| 173 | } | 217 | } |
| 174 | } | 218 | } |
| 175 | 219 | ||
| 176 | println!(); | 220 | println!(); |
| 177 | let pass_rate = if total > 0 { | 221 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); |
| 178 | (passed as f64 / total as f64) * 100.0 | 222 | |
| 223 | // Summary statistics | ||
| 224 | let passed = self.passed_count(); | ||
| 225 | let total_tests = self.total_count(); | ||
| 226 | |||
| 227 | let spec_coverage = if total_requirements > 0 { | ||
| 228 | (tested_requirements as f64 / total_requirements as f64) * 100.0 | ||
| 229 | } else { | ||
| 230 | 0.0 | ||
| 231 | }; | ||
| 232 | |||
| 233 | let pass_rate = if total_tests > 0 { | ||
| 234 | (passed as f64 / total_tests as f64) * 100.0 | ||
| 179 | } else { | 235 | } else { |
| 180 | 0.0 | 236 | 0.0 |
| 181 | }; | 237 | }; |
| 182 | 238 | ||
| 183 | let summary_color = if passed == total { GREEN } else { RED }; | 239 | let summary_color = if passed == total_tests && tested_requirements == total_requirements { |
| 240 | GREEN | ||
| 241 | } else if passed == total_tests { | ||
| 242 | YELLOW | ||
| 243 | } else { | ||
| 244 | RED | ||
| 245 | }; | ||
| 246 | |||
| 184 | println!( | 247 | println!( |
| 185 | "{}Results: {}/{} passed ({:.1}%){}", | 248 | "{}Spec coverage: {}/{} requirements tested ({:.1}%){}", |
| 186 | summary_color, passed, total, pass_rate, RESET | 249 | summary_color, tested_requirements, total_requirements, spec_coverage, RESET |
| 187 | ); | 250 | ); |
| 251 | println!( | ||
| 252 | "{}Test results: {}/{} tests passed ({:.1}%){}", | ||
| 253 | summary_color, passed, total_tests, pass_rate, RESET | ||
| 254 | ); | ||
| 255 | println!("{}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{}", BOLD, RESET); | ||
| 188 | println!(); | 256 | println!(); |
| 189 | } | 257 | } |
| 190 | 258 | ||
| @@ -235,4 +303,28 @@ mod tests { | |||
| 235 | assert_eq!(audit.failed_count(), 1); | 303 | assert_eq!(audit.failed_count(), 1); |
| 236 | assert!(!audit.all_passed()); | 304 | assert!(!audit.all_passed()); |
| 237 | } | 305 | } |
| 238 | } | 306 | |
| 307 | #[test] | ||
| 308 | fn test_parse_spec_lines_single() { | ||
| 309 | assert_eq!(parse_spec_lines("GRASP-01:nostr-relay:7"), vec![7]); | ||
| 310 | assert_eq!(parse_spec_lines("GRASP-01:git-http:28"), vec![28]); | ||
| 311 | } | ||
| 312 | |||
| 313 | #[test] | ||
| 314 | fn test_parse_spec_lines_range() { | ||
| 315 | assert_eq!(parse_spec_lines("GRASP-01:nostr-relay:7-9"), vec![7, 8, 9]); | ||
| 316 | assert_eq!(parse_spec_lines("GRASP-01:cors:44-47"), vec![44, 45, 46, 47]); | ||
| 317 | } | ||
| 318 | |||
| 319 | #[test] | ||
| 320 | fn test_parse_spec_lines_non_grasp() { | ||
| 321 | assert_eq!(parse_spec_lines("NIP-01:basic:1"), Vec::<u32>::new()); | ||
| 322 | assert_eq!(parse_spec_lines("OTHER:spec:5"), Vec::<u32>::new()); | ||
| 323 | } | ||
| 324 | |||
| 325 | #[test] | ||
| 326 | fn test_parse_spec_lines_invalid() { | ||
| 327 | assert_eq!(parse_spec_lines("GRASP-01:invalid"), Vec::<u32>::new()); | ||
| 328 | assert_eq!(parse_spec_lines("GRASP-01:test:abc"), Vec::<u32>::new()); | ||
| 329 | } | ||
| 330 | } \ No newline at end of file | ||
diff --git a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs index 1fc7f73..fee51db 100644 --- a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs +++ b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs | |||
| @@ -138,7 +138,7 @@ impl EventAcceptancePolicyTests { | |||
| 138 | 138 | ||
| 139 | /// Test: Accept valid repository announcements | 139 | /// Test: Accept valid repository announcements |
| 140 | /// | 140 | /// |
| 141 | /// Spec: Lines 3-5 of ../grasp/01.md | 141 | /// Spec: Line 7 of ../grasp/01.md |
| 142 | /// Requirement: MUST accept repo announcements listing service in clone & relays tags | 142 | /// Requirement: MUST accept repo announcements listing service in clone & relays tags |
| 143 | /// | 143 | /// |
| 144 | /// **Using TestContext pattern:** | 144 | /// **Using TestContext pattern:** |
| @@ -147,7 +147,7 @@ impl EventAcceptancePolicyTests { | |||
| 147 | pub async fn test_accept_valid_repo_announcement(client: &AuditClient) -> TestResult { | 147 | pub async fn test_accept_valid_repo_announcement(client: &AuditClient) -> TestResult { |
| 148 | TestResult::new( | 148 | TestResult::new( |
| 149 | "accept_valid_repo_announcement", | 149 | "accept_valid_repo_announcement", |
| 150 | "GRASP-01:nostr-relay:3-5", | 150 | "GRASP-01:nostr-relay:7", |
| 151 | "Accept valid repository announcements with service in clone and relays tags", | 151 | "Accept valid repository announcements with service in clone and relays tags", |
| 152 | ) | 152 | ) |
| 153 | .run(|| async { | 153 | .run(|| async { |
| @@ -245,14 +245,14 @@ impl EventAcceptancePolicyTests { | |||
| 245 | 245 | ||
| 246 | /// Test: Reject repo announcements not listing service in clone tag | 246 | /// Test: Reject repo announcements not listing service in clone tag |
| 247 | /// | 247 | /// |
| 248 | /// Spec: Line 5 of ../grasp/01.md | 248 | /// Spec: Line 9 of ../grasp/01.md |
| 249 | /// Requirement: MUST reject announcements not listing service (unless GRASP-05) | 249 | /// Requirement: MUST reject announcements not listing service (unless GRASP-05) |
| 250 | pub async fn test_reject_repo_announcement_missing_clone_tag( | 250 | pub async fn test_reject_repo_announcement_missing_clone_tag( |
| 251 | client: &AuditClient, | 251 | client: &AuditClient, |
| 252 | ) -> TestResult { | 252 | ) -> TestResult { |
| 253 | TestResult::new( | 253 | TestResult::new( |
| 254 | "reject_repo_announcement_missing_clone_tag", | 254 | "reject_repo_announcement_missing_clone_tag", |
| 255 | "GRASP-01:nostr-relay:5", | 255 | "GRASP-01:nostr-relay:9", |
| 256 | "Reject repository announcements without service in clone tag", | 256 | "Reject repository announcements without service in clone tag", |
| 257 | ) | 257 | ) |
| 258 | .run(|| async { | 258 | .run(|| async { |
| @@ -321,14 +321,14 @@ impl EventAcceptancePolicyTests { | |||
| 321 | 321 | ||
| 322 | /// Test: Reject repo announcements not listing service in relays tag | 322 | /// Test: Reject repo announcements not listing service in relays tag |
| 323 | /// | 323 | /// |
| 324 | /// Spec: Line 5 of ../grasp/01.md | 324 | /// Spec: Line 9 of ../grasp/01.md |
| 325 | /// Requirement: MUST reject announcements not listing service in relays | 325 | /// Requirement: MUST reject announcements not listing service in relays |
| 326 | pub async fn test_reject_repo_announcement_missing_relays_tag( | 326 | pub async fn test_reject_repo_announcement_missing_relays_tag( |
| 327 | client: &AuditClient, | 327 | client: &AuditClient, |
| 328 | ) -> TestResult { | 328 | ) -> TestResult { |
| 329 | TestResult::new( | 329 | TestResult::new( |
| 330 | "reject_repo_announcement_missing_relays_tag", | 330 | "reject_repo_announcement_missing_relays_tag", |
| 331 | "GRASP-01:nostr-relay:5", | 331 | "GRASP-01:nostr-relay:9", |
| 332 | "Reject repository announcements without service in relays tag", | 332 | "Reject repository announcements without service in relays tag", |
| 333 | ) | 333 | ) |
| 334 | .run(|| async { | 334 | .run(|| async { |
| @@ -410,7 +410,7 @@ impl EventAcceptancePolicyTests { | |||
| 410 | 410 | ||
| 411 | /// Test: Accept valid repository state announcements | 411 | /// Test: Accept valid repository state announcements |
| 412 | /// | 412 | /// |
| 413 | /// Spec: Lines 6-7 of ../grasp/01.md | 413 | /// Spec: Line 7 of ../grasp/01.md |
| 414 | /// Requirement: MUST accept repo state announcements with d, maintainers, and r tags | 414 | /// Requirement: MUST accept repo state announcements with d, maintainers, and r tags |
| 415 | /// | 415 | /// |
| 416 | /// **EXAMPLE: Using TestContext pattern for fixture management** | 416 | /// **EXAMPLE: Using TestContext pattern for fixture management** |
| @@ -420,7 +420,7 @@ impl EventAcceptancePolicyTests { | |||
| 420 | pub async fn test_accept_valid_repo_state_announcement(client: &AuditClient) -> TestResult { | 420 | pub async fn test_accept_valid_repo_state_announcement(client: &AuditClient) -> TestResult { |
| 421 | TestResult::new( | 421 | TestResult::new( |
| 422 | "accept_valid_repo_state_announcement", | 422 | "accept_valid_repo_state_announcement", |
| 423 | "GRASP-01:nostr-relay:6-7", | 423 | "GRASP-01:nostr-relay:7", |
| 424 | "Accept valid state announcements after repo announcement accepted", | 424 | "Accept valid state announcements after repo announcement accepted", |
| 425 | ) | 425 | ) |
| 426 | .run(|| async { | 426 | .run(|| async { |
| @@ -526,12 +526,13 @@ impl EventAcceptancePolicyTests { | |||
| 526 | 526 | ||
| 527 | /// Test 1.1: Issue referencing repo via `a` tag should be accepted | 527 | /// Test 1.1: Issue referencing repo via `a` tag should be accepted |
| 528 | /// | 528 | /// |
| 529 | /// Spec: Line 13 of ../grasp/01.md | ||
| 529 | /// **EXAMPLE: Using TestContext for prerequisite events** | 530 | /// **EXAMPLE: Using TestContext for prerequisite events** |
| 530 | /// Demonstrates how TestContext simplifies test setup while supporting dual modes | 531 | /// Demonstrates how TestContext simplifies test setup while supporting dual modes |
| 531 | pub async fn test_accept_issue_via_a_tag(client: &AuditClient) -> TestResult { | 532 | pub async fn test_accept_issue_via_a_tag(client: &AuditClient) -> TestResult { |
| 532 | TestResult::new( | 533 | TestResult::new( |
| 533 | "accept_issue_via_a_tag", | 534 | "accept_issue_via_a_tag", |
| 534 | "GRASP-01:event-acceptance:1.1", | 535 | "GRASP-01:nostr-relay:13", |
| 535 | "Accept issue referencing repo via 'a' tag", | 536 | "Accept issue referencing repo via 'a' tag", |
| 536 | ) | 537 | ) |
| 537 | .run(|| async { | 538 | .run(|| async { |
| @@ -559,13 +560,14 @@ impl EventAcceptancePolicyTests { | |||
| 559 | 560 | ||
| 560 | /// Test 1.2: NIP-22 comment with root `A` tag referencing repo should be accepted | 561 | /// Test 1.2: NIP-22 comment with root `A` tag referencing repo should be accepted |
| 561 | /// | 562 | /// |
| 563 | /// Spec: Line 13 of ../grasp/01.md | ||
| 562 | /// **Using TestContext pattern:** | 564 | /// **Using TestContext pattern:** |
| 563 | /// - In CI mode: Creates fresh repo for full isolation | 565 | /// - In CI mode: Creates fresh repo for full isolation |
| 564 | /// - In Production mode: Reuses cached repo to minimize events | 566 | /// - In Production mode: Reuses cached repo to minimize events |
| 565 | pub async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult { | 567 | pub async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult { |
| 566 | TestResult::new( | 568 | TestResult::new( |
| 567 | "accept_comment_via_A_tag", | 569 | "accept_comment_via_A_tag", |
| 568 | "GRASP-01:event-acceptance:1.2", | 570 | "GRASP-01:nostr-relay:13", |
| 569 | "Accept NIP-22 comment with root 'A' tag referencing repo", | 571 | "Accept NIP-22 comment with root 'A' tag referencing repo", |
| 570 | ) | 572 | ) |
| 571 | .run(|| async { | 573 | .run(|| async { |
| @@ -611,13 +613,14 @@ impl EventAcceptancePolicyTests { | |||
| 611 | 613 | ||
| 612 | /// Test 1.3: Kind 1 text note quoting repo via `q` tag should be accepted | 614 | /// Test 1.3: Kind 1 text note quoting repo via `q` tag should be accepted |
| 613 | /// | 615 | /// |
| 616 | /// Spec: Line 13 of ../grasp/01.md | ||
| 614 | /// **Using TestContext pattern:** | 617 | /// **Using TestContext pattern:** |
| 615 | /// - In CI mode: Creates fresh repo for full isolation | 618 | /// - In CI mode: Creates fresh repo for full isolation |
| 616 | /// - In Production mode: Reuses cached repo to minimize events | 619 | /// - In Production mode: Reuses cached repo to minimize events |
| 617 | pub async fn test_accept_kind1_via_q_tag(client: &AuditClient) -> TestResult { | 620 | pub async fn test_accept_kind1_via_q_tag(client: &AuditClient) -> TestResult { |
| 618 | TestResult::new( | 621 | TestResult::new( |
| 619 | "accept_kind1_via_q_tag", | 622 | "accept_kind1_via_q_tag", |
| 620 | "GRASP-01:event-acceptance:1.3", | 623 | "GRASP-01:nostr-relay:13", |
| 621 | "Accept kind 1 note quoting repo via 'q' tag", | 624 | "Accept kind 1 note quoting repo via 'q' tag", |
| 622 | ) | 625 | ) |
| 623 | .run(|| async { | 626 | .run(|| async { |
| @@ -660,13 +663,14 @@ impl EventAcceptancePolicyTests { | |||
| 660 | 663 | ||
| 661 | /// Test 2.1: Issue quoting another accepted issue should be accepted (transitive) | 664 | /// Test 2.1: Issue quoting another accepted issue should be accepted (transitive) |
| 662 | /// | 665 | /// |
| 666 | /// Spec: Line 13 of ../grasp/01.md | ||
| 663 | /// **Using TestContext pattern:** | 667 | /// **Using TestContext pattern:** |
| 664 | /// - In CI mode: Creates fresh repo+issue for full isolation | 668 | /// - In CI mode: Creates fresh repo+issue for full isolation |
| 665 | /// - In Production mode: Reuses cached repo+issue to minimize events | 669 | /// - In Production mode: Reuses cached repo+issue to minimize events |
| 666 | pub async fn test_accept_issue_quoting_issue_via_q(client: &AuditClient) -> TestResult { | 670 | pub async fn test_accept_issue_quoting_issue_via_q(client: &AuditClient) -> TestResult { |
| 667 | TestResult::new( | 671 | TestResult::new( |
| 668 | "accept_issue_quoting_issue_via_q", | 672 | "accept_issue_quoting_issue_via_q", |
| 669 | "GRASP-01:event-acceptance:2.1", | 673 | "GRASP-01:nostr-relay:13", |
| 670 | "Accept issue quoting accepted issue (transitive)", | 674 | "Accept issue quoting accepted issue (transitive)", |
| 671 | ) | 675 | ) |
| 672 | .run(|| async { | 676 | .run(|| async { |
| @@ -705,13 +709,14 @@ impl EventAcceptancePolicyTests { | |||
| 705 | 709 | ||
| 706 | /// Test 2.2: NIP-22 comment with root 'E' tag to accepted issue should be accepted | 710 | /// Test 2.2: NIP-22 comment with root 'E' tag to accepted issue should be accepted |
| 707 | /// | 711 | /// |
| 712 | /// Spec: Line 13 of ../grasp/01.md | ||
| 708 | /// **Using TestContext pattern:** | 713 | /// **Using TestContext pattern:** |
| 709 | /// - In CI mode: Creates fresh repo+issue for full isolation | 714 | /// - In CI mode: Creates fresh repo+issue for full isolation |
| 710 | /// - In Production mode: Reuses cached repo+issue to minimize events | 715 | /// - In Production mode: Reuses cached repo+issue to minimize events |
| 711 | pub async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult { | 716 | pub async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult { |
| 712 | TestResult::new( | 717 | TestResult::new( |
| 713 | "accept_comment_via_E_tag", | 718 | "accept_comment_via_E_tag", |
| 714 | "GRASP-01:event-acceptance:2.2", | 719 | "GRASP-01:nostr-relay:13", |
| 715 | "Accept NIP-22 comment with root 'E' tag to accepted issue", | 720 | "Accept NIP-22 comment with root 'E' tag to accepted issue", |
| 716 | ) | 721 | ) |
| 717 | .run(|| async { | 722 | .run(|| async { |
| @@ -743,13 +748,14 @@ impl EventAcceptancePolicyTests { | |||
| 743 | 748 | ||
| 744 | /// Test 2.3: Kind 1 note with 'e' tag reply to accepted kind 1 should be accepted | 749 | /// Test 2.3: Kind 1 note with 'e' tag reply to accepted kind 1 should be accepted |
| 745 | /// | 750 | /// |
| 751 | /// Spec: Line 13 of ../grasp/01.md | ||
| 746 | /// **Using TestContext pattern:** | 752 | /// **Using TestContext pattern:** |
| 747 | /// - In CI mode: Creates fresh repo for full isolation | 753 | /// - In CI mode: Creates fresh repo for full isolation |
| 748 | /// - In Production mode: Reuses cached repo to minimize events | 754 | /// - In Production mode: Reuses cached repo to minimize events |
| 749 | pub async fn test_accept_kind1_via_e_tag(client: &AuditClient) -> TestResult { | 755 | pub async fn test_accept_kind1_via_e_tag(client: &AuditClient) -> TestResult { |
| 750 | TestResult::new( | 756 | TestResult::new( |
| 751 | "accept_kind1_via_e_tag", | 757 | "accept_kind1_via_e_tag", |
| 752 | "GRASP-01:event-acceptance:2.3", | 758 | "GRASP-01:nostr-relay:13", |
| 753 | "Accept kind 1 reply via 'e' tag to accepted kind 1", | 759 | "Accept kind 1 reply via 'e' tag to accepted kind 1", |
| 754 | ) | 760 | ) |
| 755 | .run(|| async { | 761 | .run(|| async { |
| @@ -798,13 +804,14 @@ impl EventAcceptancePolicyTests { | |||
| 798 | 804 | ||
| 799 | /// Test 3.1: Kind 1 note should be accepted when referenced by an accepted issue (forward ref) | 805 | /// Test 3.1: Kind 1 note should be accepted when referenced by an accepted issue (forward ref) |
| 800 | /// | 806 | /// |
| 807 | /// Spec: Line 13 of ../grasp/01.md | ||
| 801 | /// **Using TestContext pattern:** | 808 | /// **Using TestContext pattern:** |
| 802 | /// - In CI mode: Creates fresh repo for full isolation | 809 | /// - In CI mode: Creates fresh repo for full isolation |
| 803 | /// - In Production mode: Reuses cached repo to minimize events | 810 | /// - In Production mode: Reuses cached repo to minimize events |
| 804 | pub async fn test_accept_kind1_referenced_in_issue(client: &AuditClient) -> TestResult { | 811 | pub async fn test_accept_kind1_referenced_in_issue(client: &AuditClient) -> TestResult { |
| 805 | TestResult::new( | 812 | TestResult::new( |
| 806 | "accept_kind1_referenced_in_issue", | 813 | "accept_kind1_referenced_in_issue", |
| 807 | "GRASP-01:event-acceptance:3.1", | 814 | "GRASP-01:nostr-relay:13", |
| 808 | "Accept kind 1 referenced in accepted issue (forward ref)", | 815 | "Accept kind 1 referenced in accepted issue (forward ref)", |
| 809 | ) | 816 | ) |
| 810 | .run(|| async { | 817 | .run(|| async { |
| @@ -889,13 +896,14 @@ impl EventAcceptancePolicyTests { | |||
| 889 | 896 | ||
| 890 | /// Test 3.2: Comment should be accepted when referenced by another accepted comment (forward ref) | 897 | /// Test 3.2: Comment should be accepted when referenced by another accepted comment (forward ref) |
| 891 | /// | 898 | /// |
| 899 | /// Spec: Line 13 of ../grasp/01.md | ||
| 892 | /// **Using TestContext pattern:** | 900 | /// **Using TestContext pattern:** |
| 893 | /// - In CI mode: Creates fresh repo+issue for full isolation | 901 | /// - In CI mode: Creates fresh repo+issue for full isolation |
| 894 | /// - In Production mode: Reuses cached repo+issue to minimize events | 902 | /// - In Production mode: Reuses cached repo+issue to minimize events |
| 895 | pub async fn test_accept_comment_referenced_in_comment(client: &AuditClient) -> TestResult { | 903 | pub async fn test_accept_comment_referenced_in_comment(client: &AuditClient) -> TestResult { |
| 896 | TestResult::new( | 904 | TestResult::new( |
| 897 | "accept_comment_referenced_in_comment", | 905 | "accept_comment_referenced_in_comment", |
| 898 | "GRASP-01:event-acceptance:3.2", | 906 | "GRASP-01:nostr-relay:13", |
| 899 | "Accept comment referenced in another accepted comment (forward ref)", | 907 | "Accept comment referenced in another accepted comment (forward ref)", |
| 900 | ) | 908 | ) |
| 901 | .run(|| async { | 909 | .run(|| async { |
| @@ -949,13 +957,14 @@ impl EventAcceptancePolicyTests { | |||
| 949 | 957 | ||
| 950 | /// Test 3.3: Kind 1 note should be accepted when referenced by another accepted kind 1 (forward ref) | 958 | /// Test 3.3: Kind 1 note should be accepted when referenced by another accepted kind 1 (forward ref) |
| 951 | /// | 959 | /// |
| 960 | /// Spec: Line 13 of ../grasp/01.md | ||
| 952 | /// **Using TestContext pattern:** | 961 | /// **Using TestContext pattern:** |
| 953 | /// - In CI mode: Creates fresh repo for full isolation | 962 | /// - In CI mode: Creates fresh repo for full isolation |
| 954 | /// - In Production mode: Reuses cached repo to minimize events | 963 | /// - In Production mode: Reuses cached repo to minimize events |
| 955 | pub async fn test_accept_kind1_referenced_in_kind1(client: &AuditClient) -> TestResult { | 964 | pub async fn test_accept_kind1_referenced_in_kind1(client: &AuditClient) -> TestResult { |
| 956 | TestResult::new( | 965 | TestResult::new( |
| 957 | "accept_kind1_referenced_in_kind1", | 966 | "accept_kind1_referenced_in_kind1", |
| 958 | "GRASP-01:event-acceptance:3.3", | 967 | "GRASP-01:nostr-relay:13", |
| 959 | "Accept kind 1 referenced in another accepted kind 1 (forward ref)", | 968 | "Accept kind 1 referenced in another accepted kind 1 (forward ref)", |
| 960 | ) | 969 | ) |
| 961 | .run(|| async { | 970 | .run(|| async { |
| @@ -1007,10 +1016,13 @@ impl EventAcceptancePolicyTests { | |||
| 1007 | // ============================================================ | 1016 | // ============================================================ |
| 1008 | 1017 | ||
| 1009 | /// Test 4.1: Issue referencing unaccepted repo should be rejected | 1018 | /// Test 4.1: Issue referencing unaccepted repo should be rejected |
| 1019 | /// | ||
| 1020 | /// Spec: Line 18 of ../grasp/01.md | ||
| 1021 | /// (Rejecting non-git-related events falls under MAY reject for curation) | ||
| 1010 | pub async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult { | 1022 | pub async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult { |
| 1011 | TestResult::new( | 1023 | TestResult::new( |
| 1012 | "reject_orphan_issue", | 1024 | "reject_orphan_issue", |
| 1013 | "GRASP-01:event-acceptance:4.1", | 1025 | "GRASP-01:nostr-relay:18", |
| 1014 | "Reject issue referencing unaccepted repo", | 1026 | "Reject issue referencing unaccepted repo", |
| 1015 | ) | 1027 | ) |
| 1016 | .run(|| async { | 1028 | .run(|| async { |
| @@ -1031,10 +1043,13 @@ impl EventAcceptancePolicyTests { | |||
| 1031 | } | 1043 | } |
| 1032 | 1044 | ||
| 1033 | /// Test 4.2: Generic kind 1 note with no repo references should be rejected | 1045 | /// Test 4.2: Generic kind 1 note with no repo references should be rejected |
| 1046 | /// | ||
| 1047 | /// Spec: Line 18 of ../grasp/01.md | ||
| 1048 | /// (Rejecting non-git-related events falls under MAY reject for curation) | ||
| 1034 | pub async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult { | 1049 | pub async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult { |
| 1035 | TestResult::new( | 1050 | TestResult::new( |
| 1036 | "reject_orphan_kind1", | 1051 | "reject_orphan_kind1", |
| 1037 | "GRASP-01:event-acceptance:4.2", | 1052 | "GRASP-01:nostr-relay:18", |
| 1038 | "Reject kind 1 with no repo references", | 1053 | "Reject kind 1 with no repo references", |
| 1039 | ) | 1054 | ) |
| 1040 | .run(|| async { | 1055 | .run(|| async { |
| @@ -1054,6 +1069,8 @@ impl EventAcceptancePolicyTests { | |||
| 1054 | 1069 | ||
| 1055 | /// Test 4.3: Comment quoting unaccepted repo should be rejected | 1070 | /// Test 4.3: Comment quoting unaccepted repo should be rejected |
| 1056 | /// | 1071 | /// |
| 1072 | /// Spec: Line 18 of ../grasp/01.md | ||
| 1073 | /// (Rejecting non-git-related events falls under MAY reject for curation) | ||
| 1057 | /// **Using TestContext pattern:** | 1074 | /// **Using TestContext pattern:** |
| 1058 | /// - In CI mode: Creates fresh accepted repo for full isolation | 1075 | /// - In CI mode: Creates fresh accepted repo for full isolation |
| 1059 | /// - In Production mode: Reuses cached accepted repo to minimize events | 1076 | /// - In Production mode: Reuses cached accepted repo to minimize events |
| @@ -1061,7 +1078,7 @@ impl EventAcceptancePolicyTests { | |||
| 1061 | pub async fn test_reject_comment_quoting_other_repo(client: &AuditClient) -> TestResult { | 1078 | pub async fn test_reject_comment_quoting_other_repo(client: &AuditClient) -> TestResult { |
| 1062 | TestResult::new( | 1079 | TestResult::new( |
| 1063 | "reject_comment_quoting_other_repo", | 1080 | "reject_comment_quoting_other_repo", |
| 1064 | "GRASP-01:event-acceptance:4.3", | 1081 | "GRASP-01:nostr-relay:18", |
| 1065 | "Reject comment quoting unaccepted repo", | 1082 | "Reject comment quoting unaccepted repo", |
| 1066 | ) | 1083 | ) |
| 1067 | .run(|| async { | 1084 | .run(|| async { |
diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs index 95338e4..3d81964 100644 --- a/grasp-audit/src/specs/grasp01/git_clone.rs +++ b/grasp-audit/src/specs/grasp01/git_clone.rs | |||
| @@ -37,6 +37,7 @@ impl GitCloneTests { | |||
| 37 | 37 | ||
| 38 | /// Test that a repository can be cloned via Git HTTP backend | 38 | /// Test that a repository can be cloned via Git HTTP backend |
| 39 | /// | 39 | /// |
| 40 | /// Spec: Line 28 of ../grasp/01.md | ||
| 40 | /// This test: | 41 | /// This test: |
| 41 | /// 1. Creates a repository announcement | 42 | /// 1. Creates a repository announcement |
| 42 | /// 2. Waits for repository creation | 43 | /// 2. Waits for repository creation |
| @@ -52,7 +53,7 @@ impl GitCloneTests { | |||
| 52 | Err(e) => { | 53 | Err(e) => { |
| 53 | return TestResult::new( | 54 | return TestResult::new( |
| 54 | test_name, | 55 | test_name, |
| 55 | "GRASP-01", | 56 | "GRASP-01:git-http:28", |
| 56 | "Repository must be cloneable via Git HTTP backend", | 57 | "Repository must be cloneable via Git HTTP backend", |
| 57 | ) | 58 | ) |
| 58 | .fail(format!("Failed to create repo fixture: {}", e)) | 59 | .fail(format!("Failed to create repo fixture: {}", e)) |
| @@ -85,7 +86,7 @@ impl GitCloneTests { | |||
| 85 | Err(e) => { | 86 | Err(e) => { |
| 86 | return TestResult::new( | 87 | return TestResult::new( |
| 87 | test_name, | 88 | test_name, |
| 88 | "GRASP-01", | 89 | "GRASP-01:git-http:28", |
| 89 | "Repository must be cloneable via Git HTTP backend", | 90 | "Repository must be cloneable via Git HTTP backend", |
| 90 | ) | 91 | ) |
| 91 | .fail(format!("Failed to convert pubkey to npub: {}", e)) | 92 | .fail(format!("Failed to convert pubkey to npub: {}", e)) |
| @@ -120,7 +121,7 @@ impl GitCloneTests { | |||
| 120 | cleanup(); | 121 | cleanup(); |
| 121 | return TestResult::new( | 122 | return TestResult::new( |
| 122 | test_name, | 123 | test_name, |
| 123 | "GRASP-01", | 124 | "GRASP-01:git-http:28", |
| 124 | "Repository must be cloneable via Git HTTP backend", | 125 | "Repository must be cloneable via Git HTTP backend", |
| 125 | ) | 126 | ) |
| 126 | .fail(format!("Failed to execute git clone: {}", e)); | 127 | .fail(format!("Failed to execute git clone: {}", e)); |
| @@ -132,7 +133,7 @@ impl GitCloneTests { | |||
| 132 | let stderr = String::from_utf8_lossy(&output.stderr); | 133 | let stderr = String::from_utf8_lossy(&output.stderr); |
| 133 | return TestResult::new( | 134 | return TestResult::new( |
| 134 | test_name, | 135 | test_name, |
| 135 | "GRASP-01", | 136 | "GRASP-01:git-http:28", |
| 136 | "Repository must be cloneable via Git HTTP backend", | 137 | "Repository must be cloneable via Git HTTP backend", |
| 137 | ) | 138 | ) |
| 138 | .fail(format!("Git clone failed: {}", stderr)); | 139 | .fail(format!("Git clone failed: {}", stderr)); |
| @@ -143,7 +144,7 @@ impl GitCloneTests { | |||
| 143 | cleanup(); | 144 | cleanup(); |
| 144 | return TestResult::new( | 145 | return TestResult::new( |
| 145 | test_name, | 146 | test_name, |
| 146 | "GRASP-01", | 147 | "GRASP-01:git-http:28", |
| 147 | "Repository must be cloneable via Git HTTP backend", | 148 | "Repository must be cloneable via Git HTTP backend", |
| 148 | ) | 149 | ) |
| 149 | .fail("Cloned repository missing .git directory"); | 150 | .fail("Cloned repository missing .git directory"); |
| @@ -152,7 +153,7 @@ impl GitCloneTests { | |||
| 152 | cleanup(); | 153 | cleanup(); |
| 153 | TestResult::new( | 154 | TestResult::new( |
| 154 | test_name, | 155 | test_name, |
| 155 | "GRASP-01", | 156 | "GRASP-01:git-http:28", |
| 156 | "Repository must be cloneable via Git HTTP backend", | 157 | "Repository must be cloneable via Git HTTP backend", |
| 157 | ) | 158 | ) |
| 158 | .pass() | 159 | .pass() |
| @@ -160,6 +161,7 @@ impl GitCloneTests { | |||
| 160 | 161 | ||
| 161 | /// Test clone URL format validation | 162 | /// Test clone URL format validation |
| 162 | /// | 163 | /// |
| 164 | /// Spec: Line 28 of ../grasp/01.md | ||
| 163 | /// This test verifies: | 165 | /// This test verifies: |
| 164 | /// 1. URLs follow the pattern http://domain/npub/identifier.git | 166 | /// 1. URLs follow the pattern http://domain/npub/identifier.git |
| 165 | /// 2. Invalid URLs are rejected properly | 167 | /// 2. Invalid URLs are rejected properly |
| @@ -173,7 +175,7 @@ impl GitCloneTests { | |||
| 173 | Err(e) => { | 175 | Err(e) => { |
| 174 | return TestResult::new( | 176 | return TestResult::new( |
| 175 | test_name, | 177 | test_name, |
| 176 | "GRASP-01", | 178 | "GRASP-01:git-http:28", |
| 177 | "Clone URL must follow correct format", | 179 | "Clone URL must follow correct format", |
| 178 | ) | 180 | ) |
| 179 | .fail(format!("Failed to create repo fixture: {}", e)) | 181 | .fail(format!("Failed to create repo fixture: {}", e)) |
| @@ -201,7 +203,7 @@ impl GitCloneTests { | |||
| 201 | if !valid_url.contains(&npub) { | 203 | if !valid_url.contains(&npub) { |
| 202 | return TestResult::new( | 204 | return TestResult::new( |
| 203 | test_name, | 205 | test_name, |
| 204 | "GRASP-01", | 206 | "GRASP-01:git-http:28", |
| 205 | "Clone URL must follow correct format", | 207 | "Clone URL must follow correct format", |
| 206 | ) | 208 | ) |
| 207 | .fail("URL missing npub"); | 209 | .fail("URL missing npub"); |
| @@ -210,7 +212,7 @@ impl GitCloneTests { | |||
| 210 | if !valid_url.contains(&format!("{}.git", repo_id)) { | 212 | if !valid_url.contains(&format!("{}.git", repo_id)) { |
| 211 | return TestResult::new( | 213 | return TestResult::new( |
| 212 | test_name, | 214 | test_name, |
| 213 | "GRASP-01", | 215 | "GRASP-01:git-http:28", |
| 214 | "Clone URL must follow correct format", | 216 | "Clone URL must follow correct format", |
| 215 | ) | 217 | ) |
| 216 | .fail("URL missing repository identifier"); | 218 | .fail("URL missing repository identifier"); |
| @@ -239,7 +241,7 @@ impl GitCloneTests { | |||
| 239 | if output.status.success() { | 241 | if output.status.success() { |
| 240 | return TestResult::new( | 242 | return TestResult::new( |
| 241 | test_name, | 243 | test_name, |
| 242 | "GRASP-01", | 244 | "GRASP-01:git-http:28", |
| 243 | "Clone URL must follow correct format", | 245 | "Clone URL must follow correct format", |
| 244 | ) | 246 | ) |
| 245 | .fail("Invalid URL was accepted (should have been rejected)"); | 247 | .fail("Invalid URL was accepted (should have been rejected)"); |
| @@ -247,7 +249,7 @@ impl GitCloneTests { | |||
| 247 | 249 | ||
| 248 | TestResult::new( | 250 | TestResult::new( |
| 249 | test_name, | 251 | test_name, |
| 250 | "GRASP-01", | 252 | "GRASP-01:git-http:28", |
| 251 | "Clone URL must follow correct format", | 253 | "Clone URL must follow correct format", |
| 252 | ) | 254 | ) |
| 253 | .pass() | 255 | .pass() |
| @@ -255,6 +257,7 @@ impl GitCloneTests { | |||
| 255 | 257 | ||
| 256 | /// Test that SHA1 capabilities are advertised in git-upload-pack | 258 | /// Test that SHA1 capabilities are advertised in git-upload-pack |
| 257 | /// | 259 | /// |
| 260 | /// Spec: Line 36 of ../grasp/01.md | ||
| 258 | /// GRASP-01 requires: | 261 | /// GRASP-01 requires: |
| 259 | /// "MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` | 262 | /// "MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` |
| 260 | /// in advertisement and serve available oids." | 263 | /// in advertisement and serve available oids." |
| @@ -275,7 +278,7 @@ impl GitCloneTests { | |||
| 275 | Err(e) => { | 278 | Err(e) => { |
| 276 | return TestResult::new( | 279 | return TestResult::new( |
| 277 | test_name, | 280 | test_name, |
| 278 | "GRASP-01", | 281 | "GRASP-01:git-http:36", |
| 279 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 282 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 280 | ) | 283 | ) |
| 281 | .fail(format!("Failed to create repo fixture: {}", e)) | 284 | .fail(format!("Failed to create repo fixture: {}", e)) |
| @@ -296,7 +299,7 @@ impl GitCloneTests { | |||
| 296 | None => { | 299 | None => { |
| 297 | return TestResult::new( | 300 | return TestResult::new( |
| 298 | test_name, | 301 | test_name, |
| 299 | "GRASP-01", | 302 | "GRASP-01:git-http:36", |
| 300 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 303 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 301 | ) | 304 | ) |
| 302 | .fail("Repository announcement missing d tag") | 305 | .fail("Repository announcement missing d tag") |
| @@ -308,7 +311,7 @@ impl GitCloneTests { | |||
| 308 | Err(e) => { | 311 | Err(e) => { |
| 309 | return TestResult::new( | 312 | return TestResult::new( |
| 310 | test_name, | 313 | test_name, |
| 311 | "GRASP-01", | 314 | "GRASP-01:git-http:36", |
| 312 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 315 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 313 | ) | 316 | ) |
| 314 | .fail(format!("Failed to convert pubkey to npub: {}", e)) | 317 | .fail(format!("Failed to convert pubkey to npub: {}", e)) |
| @@ -328,7 +331,7 @@ impl GitCloneTests { | |||
| 328 | Err(e) => { | 331 | Err(e) => { |
| 329 | return TestResult::new( | 332 | return TestResult::new( |
| 330 | test_name, | 333 | test_name, |
| 331 | "GRASP-01", | 334 | "GRASP-01:git-http:36", |
| 332 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 335 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 333 | ) | 336 | ) |
| 334 | .fail(format!("HTTP request failed: {}", e)) | 337 | .fail(format!("HTTP request failed: {}", e)) |
| @@ -338,7 +341,7 @@ impl GitCloneTests { | |||
| 338 | if !response.status().is_success() { | 341 | if !response.status().is_success() { |
| 339 | return TestResult::new( | 342 | return TestResult::new( |
| 340 | test_name, | 343 | test_name, |
| 341 | "GRASP-01", | 344 | "GRASP-01:git-http:36", |
| 342 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 345 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 343 | ) | 346 | ) |
| 344 | .fail(format!( | 347 | .fail(format!( |
| @@ -353,7 +356,7 @@ impl GitCloneTests { | |||
| 353 | Err(e) => { | 356 | Err(e) => { |
| 354 | return TestResult::new( | 357 | return TestResult::new( |
| 355 | test_name, | 358 | test_name, |
| 356 | "GRASP-01", | 359 | "GRASP-01:git-http:36", |
| 357 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 360 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 358 | ) | 361 | ) |
| 359 | .fail(format!("Failed to read response body: {}", e)) | 362 | .fail(format!("Failed to read response body: {}", e)) |
| @@ -367,7 +370,7 @@ impl GitCloneTests { | |||
| 367 | if !has_allow_reachable { | 370 | if !has_allow_reachable { |
| 368 | return TestResult::new( | 371 | return TestResult::new( |
| 369 | test_name, | 372 | test_name, |
| 370 | "GRASP-01", | 373 | "GRASP-01:git-http:36", |
| 371 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 374 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 372 | ) | 375 | ) |
| 373 | .fail("Missing capability: allow-reachable-sha1-in-want"); | 376 | .fail("Missing capability: allow-reachable-sha1-in-want"); |
| @@ -376,7 +379,7 @@ impl GitCloneTests { | |||
| 376 | if !has_allow_tip { | 379 | if !has_allow_tip { |
| 377 | return TestResult::new( | 380 | return TestResult::new( |
| 378 | test_name, | 381 | test_name, |
| 379 | "GRASP-01", | 382 | "GRASP-01:git-http:36", |
| 380 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 383 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 381 | ) | 384 | ) |
| 382 | .fail("Missing capability: allow-tip-sha1-in-want"); | 385 | .fail("Missing capability: allow-tip-sha1-in-want"); |
| @@ -384,7 +387,7 @@ impl GitCloneTests { | |||
| 384 | 387 | ||
| 385 | TestResult::new( | 388 | TestResult::new( |
| 386 | test_name, | 389 | test_name, |
| 387 | "GRASP-01", | 390 | "GRASP-01:git-http:36", |
| 388 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | 391 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", |
| 389 | ) | 392 | ) |
| 390 | .pass() | 393 | .pass() |
diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs index e16a351..ba27fef 100644 --- a/grasp-audit/src/specs/grasp01/mod.rs +++ b/grasp-audit/src/specs/grasp01/mod.rs | |||
| @@ -19,6 +19,7 @@ pub mod nip01_smoke; | |||
| 19 | pub mod nip11_document; | 19 | pub mod nip11_document; |
| 20 | pub mod push_authorization; | 20 | pub mod push_authorization; |
| 21 | pub mod repository_creation; | 21 | pub mod repository_creation; |
| 22 | pub mod spec_requirements; | ||
| 22 | 23 | ||
| 23 | pub use cors::CorsTests; | 24 | pub use cors::CorsTests; |
| 24 | pub use event_acceptance_policy::EventAcceptancePolicyTests; | 25 | pub use event_acceptance_policy::EventAcceptancePolicyTests; |
| @@ -27,3 +28,7 @@ pub use nip01_smoke::Nip01SmokeTests; | |||
| 27 | pub use nip11_document::Nip11DocumentTests; | 28 | pub use nip11_document::Nip11DocumentTests; |
| 28 | pub use push_authorization::PushAuthorizationTests; | 29 | pub use push_authorization::PushAuthorizationTests; |
| 29 | pub use repository_creation::RepositoryCreationTests; | 30 | pub use repository_creation::RepositoryCreationTests; |
| 31 | pub use spec_requirements::{ | ||
| 32 | get_requirement, get_requirements_for_section, get_sections, RequirementLevel, | ||
| 33 | SpecRequirement, GRASP_01_REQUIREMENTS, GRASP_COMMIT_ID, | ||
| 34 | }; | ||
diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs index 5161da8..9a7d7d1 100644 --- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs +++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs | |||
| @@ -32,7 +32,7 @@ impl Nip01SmokeTests { | |||
| 32 | pub async fn test_websocket_connection(client: &AuditClient) -> TestResult { | 32 | pub async fn test_websocket_connection(client: &AuditClient) -> TestResult { |
| 33 | TestResult::new( | 33 | TestResult::new( |
| 34 | "websocket_connection", | 34 | "websocket_connection", |
| 35 | "NIP-01", | 35 | "GRASP-01:nostr-relay:7", |
| 36 | "Can establish WebSocket connection to /", | 36 | "Can establish WebSocket connection to /", |
| 37 | ) | 37 | ) |
| 38 | .run(|| async { | 38 | .run(|| async { |
| @@ -61,7 +61,7 @@ impl Nip01SmokeTests { | |||
| 61 | pub async fn test_send_receive_event(client: &AuditClient) -> TestResult { | 61 | pub async fn test_send_receive_event(client: &AuditClient) -> TestResult { |
| 62 | TestResult::new( | 62 | TestResult::new( |
| 63 | "send_receive_event", | 63 | "send_receive_event", |
| 64 | "NIP-01", | 64 | "GRASP-01:nostr-relay:7", |
| 65 | "Can send EVENT and receive OK response", | 65 | "Can send EVENT and receive OK response", |
| 66 | ) | 66 | ) |
| 67 | .run(|| async { | 67 | .run(|| async { |
| @@ -127,7 +127,7 @@ impl Nip01SmokeTests { | |||
| 127 | pub async fn test_create_subscription(client: &AuditClient) -> TestResult { | 127 | pub async fn test_create_subscription(client: &AuditClient) -> TestResult { |
| 128 | TestResult::new( | 128 | TestResult::new( |
| 129 | "create_subscription", | 129 | "create_subscription", |
| 130 | "NIP-01", | 130 | "GRASP-01:nostr-relay:7", |
| 131 | "Can create subscription with REQ and receive EOSE", | 131 | "Can create subscription with REQ and receive EOSE", |
| 132 | ) | 132 | ) |
| 133 | .run(|| async { | 133 | .run(|| async { |
| @@ -163,7 +163,7 @@ impl Nip01SmokeTests { | |||
| 163 | /// Spec: NIP-01 CLOSE message | 163 | /// Spec: NIP-01 CLOSE message |
| 164 | /// Requirement: Relay MUST support CLOSE to end subscriptions | 164 | /// Requirement: Relay MUST support CLOSE to end subscriptions |
| 165 | pub async fn test_close_subscription(client: &AuditClient) -> TestResult { | 165 | pub async fn test_close_subscription(client: &AuditClient) -> TestResult { |
| 166 | TestResult::new("close_subscription", "NIP-01", "Can close subscriptions") | 166 | TestResult::new("close_subscription", "GRASP-01:nostr-relay:7", "Can close subscriptions") |
| 167 | .run(|| async { | 167 | .run(|| async { |
| 168 | // For now, we just verify we can query events | 168 | // For now, we just verify we can query events |
| 169 | // Full subscription management with CLOSE would require | 169 | // Full subscription management with CLOSE would require |
| @@ -189,7 +189,7 @@ impl Nip01SmokeTests { | |||
| 189 | pub async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult { | 189 | pub async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult { |
| 190 | TestResult::new( | 190 | TestResult::new( |
| 191 | "reject_invalid_signature", | 191 | "reject_invalid_signature", |
| 192 | "NIP-01", | 192 | "GRASP-01:nostr-relay:7", |
| 193 | "Rejects events with invalid signatures", | 193 | "Rejects events with invalid signatures", |
| 194 | ) | 194 | ) |
| 195 | .run(|| async { | 195 | .run(|| async { |
| @@ -243,7 +243,7 @@ impl Nip01SmokeTests { | |||
| 243 | pub async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult { | 243 | pub async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult { |
| 244 | TestResult::new( | 244 | TestResult::new( |
| 245 | "reject_invalid_event_id", | 245 | "reject_invalid_event_id", |
| 246 | "NIP-01", | 246 | "GRASP-01:nostr-relay:7", |
| 247 | "Rejects events with invalid event IDs", | 247 | "Rejects events with invalid event IDs", |
| 248 | ) | 248 | ) |
| 249 | .run(|| async { | 249 | .run(|| async { |
diff --git a/grasp-audit/src/specs/grasp01/nip11_document.rs b/grasp-audit/src/specs/grasp01/nip11_document.rs index 51b147d..33599b1 100644 --- a/grasp-audit/src/specs/grasp01/nip11_document.rs +++ b/grasp-audit/src/specs/grasp01/nip11_document.rs | |||
| @@ -32,12 +32,12 @@ impl Nip11DocumentTests { | |||
| 32 | 32 | ||
| 33 | /// Test: Serve NIP-11 document | 33 | /// Test: Serve NIP-11 document |
| 34 | /// | 34 | /// |
| 35 | /// Spec: Line 11 of ../grasp/01.md | 35 | /// Spec: Line 20 of ../grasp/01.md |
| 36 | /// Requirement: MUST serve NIP-11 document | 36 | /// Requirement: MUST serve NIP-11 document |
| 37 | pub async fn test_nip11_document_exists(client: &AuditClient) -> TestResult { | 37 | pub async fn test_nip11_document_exists(client: &AuditClient) -> TestResult { |
| 38 | TestResult::new( | 38 | TestResult::new( |
| 39 | "nip11_document_exists", | 39 | "nip11_document_exists", |
| 40 | "GRASP-01:nostr-relay:11", | 40 | "GRASP-01:nostr-relay:20", |
| 41 | "Serve NIP-11 relay information document", | 41 | "Serve NIP-11 relay information document", |
| 42 | ) | 42 | ) |
| 43 | .run(|| async { | 43 | .run(|| async { |
| @@ -91,12 +91,12 @@ impl Nip11DocumentTests { | |||
| 91 | 91 | ||
| 92 | /// Test: NIP-11 includes supported_grasps field | 92 | /// Test: NIP-11 includes supported_grasps field |
| 93 | /// | 93 | /// |
| 94 | /// Spec: Line 12 of ../grasp/01.md | 94 | /// Spec: Line 22 of ../grasp/01.md |
| 95 | /// Requirement: MUST list supported GRASPs as string array | 95 | /// Requirement: MUST list supported GRASPs as string array |
| 96 | pub async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult { | 96 | pub async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult { |
| 97 | TestResult::new( | 97 | TestResult::new( |
| 98 | "nip11_supported_grasps_field", | 98 | "nip11_supported_grasps_field", |
| 99 | "GRASP-01:nostr-relay:12", | 99 | "GRASP-01:nostr-relay:22", |
| 100 | "NIP-11 document includes supported_grasps field with GRASP-01", | 100 | "NIP-11 document includes supported_grasps field with GRASP-01", |
| 101 | ) | 101 | ) |
| 102 | .run(|| async { | 102 | .run(|| async { |
| @@ -167,12 +167,12 @@ impl Nip11DocumentTests { | |||
| 167 | 167 | ||
| 168 | /// Test: NIP-11 includes repo_acceptance_criteria field | 168 | /// Test: NIP-11 includes repo_acceptance_criteria field |
| 169 | /// | 169 | /// |
| 170 | /// Spec: Line 13 of ../grasp/01.md | 170 | /// Spec: Line 23 of ../grasp/01.md |
| 171 | /// Requirement: MUST list repository acceptance criteria | 171 | /// Requirement: MUST list repository acceptance criteria |
| 172 | pub async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult { | 172 | pub async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult { |
| 173 | TestResult::new( | 173 | TestResult::new( |
| 174 | "nip11_repo_acceptance_criteria_field", | 174 | "nip11_repo_acceptance_criteria_field", |
| 175 | "GRASP-01:nostr-relay:13", | 175 | "GRASP-01:nostr-relay:23", |
| 176 | "NIP-11 document includes repo_acceptance_criteria field", | 176 | "NIP-11 document includes repo_acceptance_criteria field", |
| 177 | ) | 177 | ) |
| 178 | .run(|| async { | 178 | .run(|| async { |
| @@ -222,12 +222,12 @@ impl Nip11DocumentTests { | |||
| 222 | 222 | ||
| 223 | /// Test: NIP-11 curation field handling | 223 | /// Test: NIP-11 curation field handling |
| 224 | /// | 224 | /// |
| 225 | /// Spec: Line 14 of ../grasp/01.md | 225 | /// Spec: Line 24 of ../grasp/01.md |
| 226 | /// Requirement: MUST include curation if curated, omit otherwise | 226 | /// Requirement: MUST include curation if curated, omit otherwise |
| 227 | pub async fn test_nip11_curation_field(client: &AuditClient) -> TestResult { | 227 | pub async fn test_nip11_curation_field(client: &AuditClient) -> TestResult { |
| 228 | TestResult::new( | 228 | TestResult::new( |
| 229 | "nip11_curation_field", | 229 | "nip11_curation_field", |
| 230 | "GRASP-01:nostr-relay:14", | 230 | "GRASP-01:nostr-relay:24", |
| 231 | "NIP-11 curation field present if curated, absent otherwise", | 231 | "NIP-11 curation field present if curated, absent otherwise", |
| 232 | ) | 232 | ) |
| 233 | .run(|| async { | 233 | .run(|| async { |
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index af6247f..c06da0d 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -407,7 +407,7 @@ impl PushAuthorizationTests { | |||
| 407 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 407 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 408 | Ok(r) => r, | 408 | Ok(r) => r, |
| 409 | Err(e) => { | 409 | Err(e) => { |
| 410 | return TestResult::new(test_name, "GRASP-01", "Push rejected without state event") | 410 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") |
| 411 | .fail(format!("Failed to create repo: {}", e)) | 411 | .fail(format!("Failed to create repo: {}", e)) |
| 412 | } | 412 | } |
| 413 | }; | 413 | }; |
| @@ -427,7 +427,7 @@ impl PushAuthorizationTests { | |||
| 427 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | 427 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { |
| 428 | Ok(p) => p, | 428 | Ok(p) => p, |
| 429 | Err(e) => { | 429 | Err(e) => { |
| 430 | return TestResult::new(test_name, "GRASP-01", "Push rejected without state event") | 430 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") |
| 431 | .fail(&e) | 431 | .fail(&e) |
| 432 | } | 432 | } |
| 433 | }; | 433 | }; |
| @@ -437,7 +437,7 @@ impl PushAuthorizationTests { | |||
| 437 | 437 | ||
| 438 | if let Err(e) = create_commit(&clone_path, "Unauthorized commit") { | 438 | if let Err(e) = create_commit(&clone_path, "Unauthorized commit") { |
| 439 | cleanup(); | 439 | cleanup(); |
| 440 | return TestResult::new(test_name, "GRASP-01", "Push rejected without state event") | 440 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") |
| 441 | .fail(&e); | 441 | .fail(&e); |
| 442 | } | 442 | } |
| 443 | 443 | ||
| @@ -447,12 +447,12 @@ impl PushAuthorizationTests { | |||
| 447 | 447 | ||
| 448 | match push_result { | 448 | match push_result { |
| 449 | Ok(false) => { | 449 | Ok(false) => { |
| 450 | TestResult::new(test_name, "GRASP-01", "Push rejected without state event").pass() | 450 | TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event").pass() |
| 451 | } | 451 | } |
| 452 | Ok(true) => TestResult::new(test_name, "GRASP-01", "Push rejected without state event") | 452 | Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event") |
| 453 | .fail("Push accepted but should be rejected"), | 453 | .fail("Push accepted but should be rejected"), |
| 454 | Err(e) => { | 454 | Err(e) => { |
| 455 | TestResult::new(test_name, "GRASP-01", "Push rejected without state event").fail(&e) | 455 | TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected without state event").fail(&e) |
| 456 | } | 456 | } |
| 457 | } | 457 | } |
| 458 | } | 458 | } |
| @@ -481,9 +481,9 @@ impl PushAuthorizationTests { | |||
| 481 | // Generate → Send → Verify → DataPush | 481 | // Generate → Send → Verify → DataPush |
| 482 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { | 482 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { |
| 483 | Ok(_state_event) => { | 483 | Ok(_state_event) => { |
| 484 | TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass() | 484 | TestResult::new(test_name, "GRASP-01:git-http:30", "Push authorized with matching state").pass() |
| 485 | } | 485 | } |
| 486 | Err(e) => TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") | 486 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push authorized with matching state") |
| 487 | .fail(format!("{}", e)), | 487 | .fail(format!("{}", e)), |
| 488 | } | 488 | } |
| 489 | } | 489 | } |
| @@ -523,7 +523,7 @@ impl PushAuthorizationTests { | |||
| 523 | Err(e) => { | 523 | Err(e) => { |
| 524 | return TestResult::new( | 524 | return TestResult::new( |
| 525 | test_name, | 525 | test_name, |
| 526 | "GRASP-01", | 526 | "GRASP-01:git-http:30", |
| 527 | "Push rejected when commit not in state event", | 527 | "Push rejected when commit not in state event", |
| 528 | ) | 528 | ) |
| 529 | .fail(format!("Failed to create RepoState fixture: {}", e)); | 529 | .fail(format!("Failed to create RepoState fixture: {}", e)); |
| @@ -543,7 +543,7 @@ impl PushAuthorizationTests { | |||
| 543 | None => { | 543 | None => { |
| 544 | return TestResult::new( | 544 | return TestResult::new( |
| 545 | test_name, | 545 | test_name, |
| 546 | "GRASP-01", | 546 | "GRASP-01:git-http:30", |
| 547 | "Push rejected when commit not in state event", | 547 | "Push rejected when commit not in state event", |
| 548 | ) | 548 | ) |
| 549 | .fail("Missing repo_id in state event"); | 549 | .fail("Missing repo_id in state event"); |
| @@ -555,7 +555,7 @@ impl PushAuthorizationTests { | |||
| 555 | Err(e) => { | 555 | Err(e) => { |
| 556 | return TestResult::new( | 556 | return TestResult::new( |
| 557 | test_name, | 557 | test_name, |
| 558 | "GRASP-01", | 558 | "GRASP-01:git-http:30", |
| 559 | "Push rejected when commit not in state event", | 559 | "Push rejected when commit not in state event", |
| 560 | ) | 560 | ) |
| 561 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 561 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| @@ -571,7 +571,7 @@ impl PushAuthorizationTests { | |||
| 571 | Err(e) => { | 571 | Err(e) => { |
| 572 | return TestResult::new( | 572 | return TestResult::new( |
| 573 | test_name, | 573 | test_name, |
| 574 | "GRASP-01", | 574 | "GRASP-01:git-http:30", |
| 575 | "Push rejected when commit not in state event", | 575 | "Push rejected when commit not in state event", |
| 576 | ) | 576 | ) |
| 577 | .fail(format!("Failed to clone repo: {}", e)); | 577 | .fail(format!("Failed to clone repo: {}", e)); |
| @@ -594,7 +594,7 @@ impl PushAuthorizationTests { | |||
| 594 | cleanup(); | 594 | cleanup(); |
| 595 | return TestResult::new( | 595 | return TestResult::new( |
| 596 | test_name, | 596 | test_name, |
| 597 | "GRASP-01", | 597 | "GRASP-01:git-http:30", |
| 598 | "Push rejected when commit not in state event", | 598 | "Push rejected when commit not in state event", |
| 599 | ) | 599 | ) |
| 600 | .fail(format!("Failed to create/checkout main branch: {}", e)); | 600 | .fail(format!("Failed to create/checkout main branch: {}", e)); |
| @@ -603,7 +603,7 @@ impl PushAuthorizationTests { | |||
| 603 | cleanup(); | 603 | cleanup(); |
| 604 | return TestResult::new( | 604 | return TestResult::new( |
| 605 | test_name, | 605 | test_name, |
| 606 | "GRASP-01", | 606 | "GRASP-01:git-http:30", |
| 607 | "Push rejected when commit not in state event", | 607 | "Push rejected when commit not in state event", |
| 608 | ) | 608 | ) |
| 609 | .fail(format!( | 609 | .fail(format!( |
| @@ -620,7 +620,7 @@ impl PushAuthorizationTests { | |||
| 620 | cleanup(); | 620 | cleanup(); |
| 621 | return TestResult::new( | 621 | return TestResult::new( |
| 622 | test_name, | 622 | test_name, |
| 623 | "GRASP-01", | 623 | "GRASP-01:git-http:30", |
| 624 | "Push rejected when commit not in state event", | 624 | "Push rejected when commit not in state event", |
| 625 | ) | 625 | ) |
| 626 | .fail(format!("Failed to create wrong commit: {}", e)); | 626 | .fail(format!("Failed to create wrong commit: {}", e)); |
| @@ -634,10 +634,10 @@ impl PushAuthorizationTests { | |||
| 634 | cleanup(); | 634 | cleanup(); |
| 635 | 635 | ||
| 636 | match push_result { | 636 | match push_result { |
| 637 | Ok(false) => TestResult::new(test_name, "GRASP-01", "Push rejected when commit not in state event").pass(), | 637 | Ok(false) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected when commit not in state event").pass(), |
| 638 | Ok(true) => TestResult::new(test_name, "GRASP-01", "Push rejected when commit not in state event") | 638 | Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected when commit not in state event") |
| 639 | .fail("Push accepted but should be rejected. The pushed commit is not in the state event."), | 639 | .fail("Push accepted but should be rejected. The pushed commit is not in the state event."), |
| 640 | Err(e) => TestResult::new(test_name, "GRASP-01", "Push rejected when commit not in state event").fail(&e), | 640 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:30", "Push rejected when commit not in state event").fail(&e), |
| 641 | } | 641 | } |
| 642 | } | 642 | } |
| 643 | 643 | ||
| @@ -672,13 +672,13 @@ impl PushAuthorizationTests { | |||
| 672 | { | 672 | { |
| 673 | Ok(_maintainer_state_event) => TestResult::new( | 673 | Ok(_maintainer_state_event) => TestResult::new( |
| 674 | test_name, | 674 | test_name, |
| 675 | "GRASP-01", | 675 | "GRASP-01:git-http:30", |
| 676 | "Push authorized by maintainer state event only (no announcement)", | 676 | "Push authorized by maintainer state event only (no announcement)", |
| 677 | ) | 677 | ) |
| 678 | .pass(), | 678 | .pass(), |
| 679 | Err(e) => TestResult::new( | 679 | Err(e) => TestResult::new( |
| 680 | test_name, | 680 | test_name, |
| 681 | "GRASP-01", | 681 | "GRASP-01:git-http:30", |
| 682 | "Push authorized by maintainer state event only (no announcement)", | 682 | "Push authorized by maintainer state event only (no announcement)", |
| 683 | ) | 683 | ) |
| 684 | .fail(format!("{}", e)), | 684 | .fail(format!("{}", e)), |
| @@ -715,13 +715,13 @@ impl PushAuthorizationTests { | |||
| 715 | { | 715 | { |
| 716 | Ok(_recursive_maintainer_state_event) => TestResult::new( | 716 | Ok(_recursive_maintainer_state_event) => TestResult::new( |
| 717 | test_name, | 717 | test_name, |
| 718 | "GRASP-01", | 718 | "GRASP-01:git-http:30", |
| 719 | "Push authorized by recursive maintainer state event", | 719 | "Push authorized by recursive maintainer state event", |
| 720 | ) | 720 | ) |
| 721 | .pass(), | 721 | .pass(), |
| 722 | Err(e) => TestResult::new( | 722 | Err(e) => TestResult::new( |
| 723 | test_name, | 723 | test_name, |
| 724 | "GRASP-01", | 724 | "GRASP-01:git-http:30", |
| 725 | "Push authorized by recursive maintainer state event", | 725 | "Push authorized by recursive maintainer state event", |
| 726 | ) | 726 | ) |
| 727 | .fail(format!("{}", e)), | 727 | .fail(format!("{}", e)), |
| @@ -765,7 +765,7 @@ impl PushAuthorizationTests { | |||
| 765 | Err(e) => { | 765 | Err(e) => { |
| 766 | return TestResult::new( | 766 | return TestResult::new( |
| 767 | test_name, | 767 | test_name, |
| 768 | "GRASP-01", | 768 | "GRASP-01:git-http:30", |
| 769 | "Non-maintainer state events ignored", | 769 | "Non-maintainer state events ignored", |
| 770 | ) | 770 | ) |
| 771 | .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e)); | 771 | .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e)); |
| @@ -783,7 +783,7 @@ impl PushAuthorizationTests { | |||
| 783 | None => { | 783 | None => { |
| 784 | return TestResult::new( | 784 | return TestResult::new( |
| 785 | test_name, | 785 | test_name, |
| 786 | "GRASP-01", | 786 | "GRASP-01:git-http:30", |
| 787 | "Non-maintainer state events ignored", | 787 | "Non-maintainer state events ignored", |
| 788 | ) | 788 | ) |
| 789 | .fail("Missing repo_id in state event"); | 789 | .fail("Missing repo_id in state event"); |
| @@ -795,7 +795,7 @@ impl PushAuthorizationTests { | |||
| 795 | Err(e) => { | 795 | Err(e) => { |
| 796 | return TestResult::new( | 796 | return TestResult::new( |
| 797 | test_name, | 797 | test_name, |
| 798 | "GRASP-01", | 798 | "GRASP-01:git-http:30", |
| 799 | "Non-maintainer state events ignored", | 799 | "Non-maintainer state events ignored", |
| 800 | ) | 800 | ) |
| 801 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 801 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| @@ -810,7 +810,7 @@ impl PushAuthorizationTests { | |||
| 810 | Err(e) => { | 810 | Err(e) => { |
| 811 | return TestResult::new( | 811 | return TestResult::new( |
| 812 | test_name, | 812 | test_name, |
| 813 | "GRASP-01", | 813 | "GRASP-01:git-http:30", |
| 814 | "Non-maintainer state events ignored", | 814 | "Non-maintainer state events ignored", |
| 815 | ) | 815 | ) |
| 816 | .fail(format!("Failed to clone repo: {}", e)); | 816 | .fail(format!("Failed to clone repo: {}", e)); |
| @@ -832,7 +832,7 @@ impl PushAuthorizationTests { | |||
| 832 | cleanup(); | 832 | cleanup(); |
| 833 | return TestResult::new( | 833 | return TestResult::new( |
| 834 | test_name, | 834 | test_name, |
| 835 | "GRASP-01", | 835 | "GRASP-01:git-http:30", |
| 836 | "Non-maintainer state events ignored", | 836 | "Non-maintainer state events ignored", |
| 837 | ) | 837 | ) |
| 838 | .fail(format!("Failed to create commit: {}", e)); | 838 | .fail(format!("Failed to create commit: {}", e)); |
| @@ -858,7 +858,7 @@ impl PushAuthorizationTests { | |||
| 858 | cleanup(); | 858 | cleanup(); |
| 859 | return TestResult::new( | 859 | return TestResult::new( |
| 860 | test_name, | 860 | test_name, |
| 861 | "GRASP-01", | 861 | "GRASP-01:git-http:30", |
| 862 | "Non-maintainer state events ignored", | 862 | "Non-maintainer state events ignored", |
| 863 | ) | 863 | ) |
| 864 | .fail(format!("Failed to build rogue state event: {}", e)); | 864 | .fail(format!("Failed to build rogue state event: {}", e)); |
| @@ -868,7 +868,7 @@ impl PushAuthorizationTests { | |||
| 868 | // Send the rogue state event using the raw client to bypass AuditClient's key check | 868 | // Send the rogue state event using the raw client to bypass AuditClient's key check |
| 869 | if let Err(e) = client.client().send_event(&rogue_state).await { | 869 | if let Err(e) = client.client().send_event(&rogue_state).await { |
| 870 | cleanup(); | 870 | cleanup(); |
| 871 | return TestResult::new(test_name, "GRASP-01", "Non-maintainer state events ignored") | 871 | return TestResult::new(test_name, "GRASP-01:git-http:30", "Non-maintainer state events ignored") |
| 872 | .fail(format!("Failed to send rogue state event: {}", e)); | 872 | .fail(format!("Failed to send rogue state event: {}", e)); |
| 873 | } | 873 | } |
| 874 | 874 | ||
| @@ -883,8 +883,8 @@ impl PushAuthorizationTests { | |||
| 883 | cleanup(); | 883 | cleanup(); |
| 884 | 884 | ||
| 885 | match push_result { | 885 | match push_result { |
| 886 | Ok(false) => TestResult::new(test_name, "GRASP-01", "Non-maintainer state events ignored").pass(), | 886 | Ok(false) => TestResult::new(test_name, "GRASP-01:git-http:30", "Non-maintainer state events ignored").pass(), |
| 887 | Ok(true) => TestResult::new(test_name, "GRASP-01", "Non-maintainer state events ignored") | 887 | Ok(true) => TestResult::new(test_name, "GRASP-01:git-http:30", "Non-maintainer state events ignored") |
| 888 | .fail(format!( | 888 | .fail(format!( |
| 889 | "Push accepted but should be rejected. A non-maintainer (pubkey: {}) published \ | 889 | "Push accepted but should be rejected. A non-maintainer (pubkey: {}) published \ |
| 890 | a state event announcing commit {}, but the push was accepted. The relay should \ | 890 | a state event announcing commit {}, but the push was accepted. The relay should \ |
| @@ -893,7 +893,7 @@ impl PushAuthorizationTests { | |||
| 893 | new_commit, | 893 | new_commit, |
| 894 | client.public_key() | 894 | client.public_key() |
| 895 | )), | 895 | )), |
| 896 | Err(e) => TestResult::new(test_name, "GRASP-01", "Non-maintainer state events ignored").fail(&e), | 896 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:30", "Non-maintainer state events ignored").fail(&e), |
| 897 | } | 897 | } |
| 898 | } | 898 | } |
| 899 | 899 | ||
| @@ -924,7 +924,7 @@ impl PushAuthorizationTests { | |||
| 924 | Err(e) => { | 924 | Err(e) => { |
| 925 | return TestResult::new( | 925 | return TestResult::new( |
| 926 | test_name, | 926 | test_name, |
| 927 | "GRASP-01", | 927 | "GRASP-01:git-http:34", |
| 928 | "Push to refs/nostr/<invalid-event-id> rejected", | 928 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 929 | ) | 929 | ) |
| 930 | .fail(format!("Failed to create repo: {}", e)); | 930 | .fail(format!("Failed to create repo: {}", e)); |
| @@ -950,7 +950,7 @@ impl PushAuthorizationTests { | |||
| 950 | Err(e) => { | 950 | Err(e) => { |
| 951 | return TestResult::new( | 951 | return TestResult::new( |
| 952 | test_name, | 952 | test_name, |
| 953 | "GRASP-01", | 953 | "GRASP-01:git-http:34", |
| 954 | "Push to refs/nostr/<invalid-event-id> rejected", | 954 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 955 | ) | 955 | ) |
| 956 | .fail(&e); | 956 | .fail(&e); |
| @@ -965,7 +965,7 @@ impl PushAuthorizationTests { | |||
| 965 | cleanup(); | 965 | cleanup(); |
| 966 | return TestResult::new( | 966 | return TestResult::new( |
| 967 | test_name, | 967 | test_name, |
| 968 | "GRASP-01", | 968 | "GRASP-01:git-http:34", |
| 969 | "Push to refs/nostr/<invalid-event-id> rejected", | 969 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 970 | ) | 970 | ) |
| 971 | .fail(&e); | 971 | .fail(&e); |
| @@ -984,13 +984,13 @@ impl PushAuthorizationTests { | |||
| 984 | match push_result { | 984 | match push_result { |
| 985 | Ok(false) => TestResult::new( | 985 | Ok(false) => TestResult::new( |
| 986 | test_name, | 986 | test_name, |
| 987 | "GRASP-01", | 987 | "GRASP-01:git-http:34", |
| 988 | "Push to refs/nostr/<invalid-event-id> rejected", | 988 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 989 | ) | 989 | ) |
| 990 | .pass(), | 990 | .pass(), |
| 991 | Ok(true) => TestResult::new( | 991 | Ok(true) => TestResult::new( |
| 992 | test_name, | 992 | test_name, |
| 993 | "GRASP-01", | 993 | "GRASP-01:git-http:34", |
| 994 | "Push to refs/nostr/<invalid-event-id> rejected", | 994 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 995 | ) | 995 | ) |
| 996 | .fail(format!( | 996 | .fail(format!( |
| @@ -1001,7 +1001,7 @@ impl PushAuthorizationTests { | |||
| 1001 | )), | 1001 | )), |
| 1002 | Err(e) => TestResult::new( | 1002 | Err(e) => TestResult::new( |
| 1003 | test_name, | 1003 | test_name, |
| 1004 | "GRASP-01", | 1004 | "GRASP-01:git-http:34", |
| 1005 | "Push to refs/nostr/<invalid-event-id> rejected", | 1005 | "Push to refs/nostr/<invalid-event-id> rejected", |
| 1006 | ) | 1006 | ) |
| 1007 | .fail(format!("Push error: {}", e)), | 1007 | .fail(format!("Push error: {}", e)), |
| @@ -1035,8 +1035,8 @@ impl PushAuthorizationTests { | |||
| 1035 | .get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent) | 1035 | .get_fixture(FixtureKind::PRWrongCommitPushedBeforeEvent) |
| 1036 | .await | 1036 | .await |
| 1037 | { | 1037 | { |
| 1038 | Ok(_pr_event) => TestResult::new(test_name, "GRASP-01", desc).pass(), | 1038 | Ok(_pr_event) => TestResult::new(test_name, "GRASP-01:git-http:34", desc).pass(), |
| 1039 | Err(e) => TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)), | 1039 | Err(e) => TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)), |
| 1040 | } | 1040 | } |
| 1041 | } | 1041 | } |
| 1042 | 1042 | ||
| @@ -1062,7 +1062,7 @@ impl PushAuthorizationTests { | |||
| 1062 | { | 1062 | { |
| 1063 | Ok(e) => e, | 1063 | Ok(e) => e, |
| 1064 | Err(e) => { | 1064 | Err(e) => { |
| 1065 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)); | 1065 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); |
| 1066 | } | 1066 | } |
| 1067 | }; | 1067 | }; |
| 1068 | 1068 | ||
| @@ -1072,7 +1072,7 @@ impl PushAuthorizationTests { | |||
| 1072 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1072 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1073 | Ok(r) => r, | 1073 | Ok(r) => r, |
| 1074 | Err(e) => { | 1074 | Err(e) => { |
| 1075 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)); | 1075 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); |
| 1076 | } | 1076 | } |
| 1077 | }; | 1077 | }; |
| 1078 | 1078 | ||
| @@ -1087,7 +1087,7 @@ impl PushAuthorizationTests { | |||
| 1087 | let owner_npub = match repo.pubkey.to_bech32() { | 1087 | let owner_npub = match repo.pubkey.to_bech32() { |
| 1088 | Ok(n) => n, | 1088 | Ok(n) => n, |
| 1089 | Err(e) => { | 1089 | Err(e) => { |
| 1090 | return TestResult::new(test_name, "GRASP-01", desc) | 1090 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1091 | .fail(format!("Failed to get owner npub: {}", e)); | 1091 | .fail(format!("Failed to get owner npub: {}", e)); |
| 1092 | } | 1092 | } |
| 1093 | }; | 1093 | }; |
| @@ -1096,7 +1096,7 @@ impl PushAuthorizationTests { | |||
| 1096 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { | 1096 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { |
| 1097 | Ok(p) => p, | 1097 | Ok(p) => p, |
| 1098 | Err(e) => { | 1098 | Err(e) => { |
| 1099 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1099 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1100 | } | 1100 | } |
| 1101 | }; | 1101 | }; |
| 1102 | 1102 | ||
| @@ -1106,7 +1106,7 @@ impl PushAuthorizationTests { | |||
| 1106 | Ok(exists) => exists, | 1106 | Ok(exists) => exists, |
| 1107 | Err(e) => { | 1107 | Err(e) => { |
| 1108 | let _ = fs::remove_dir_all(&clone_path); | 1108 | let _ = fs::remove_dir_all(&clone_path); |
| 1109 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1109 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1110 | } | 1110 | } |
| 1111 | }; | 1111 | }; |
| 1112 | 1112 | ||
| @@ -1114,13 +1114,13 @@ impl PushAuthorizationTests { | |||
| 1114 | 1114 | ||
| 1115 | // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag | 1115 | // Ref should be deleted since the pushed commit doesn't match the PR event's `c` tag |
| 1116 | if refs_exist { | 1116 | if refs_exist { |
| 1117 | TestResult::new(test_name, "GRASP-01", desc).fail(format!( | 1117 | TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!( |
| 1118 | "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \ | 1118 | "Expected refs/nostr/{} to be deleted when PR event published with non-matching commit, \ |
| 1119 | but the ref still exists. The relay should delete refs that don't match the event's `c` tag.", | 1119 | but the ref still exists. The relay should delete refs that don't match the event's `c` tag.", |
| 1120 | pr_event_id | 1120 | pr_event_id |
| 1121 | )) | 1121 | )) |
| 1122 | } else { | 1122 | } else { |
| 1123 | TestResult::new(test_name, "GRASP-01", desc).pass() | 1123 | TestResult::new(test_name, "GRASP-01:git-http:34", desc).pass() |
| 1124 | } | 1124 | } |
| 1125 | } | 1125 | } |
| 1126 | 1126 | ||
| @@ -1146,7 +1146,7 @@ impl PushAuthorizationTests { | |||
| 1146 | { | 1146 | { |
| 1147 | Ok(e) => e, | 1147 | Ok(e) => e, |
| 1148 | Err(e) => { | 1148 | Err(e) => { |
| 1149 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)); | 1149 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); |
| 1150 | } | 1150 | } |
| 1151 | }; | 1151 | }; |
| 1152 | 1152 | ||
| @@ -1156,7 +1156,7 @@ impl PushAuthorizationTests { | |||
| 1156 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1156 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1157 | Ok(r) => r, | 1157 | Ok(r) => r, |
| 1158 | Err(e) => { | 1158 | Err(e) => { |
| 1159 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)); | 1159 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); |
| 1160 | } | 1160 | } |
| 1161 | }; | 1161 | }; |
| 1162 | 1162 | ||
| @@ -1171,7 +1171,7 @@ impl PushAuthorizationTests { | |||
| 1171 | let owner_npub = match repo.pubkey.to_bech32() { | 1171 | let owner_npub = match repo.pubkey.to_bech32() { |
| 1172 | Ok(n) => n, | 1172 | Ok(n) => n, |
| 1173 | Err(e) => { | 1173 | Err(e) => { |
| 1174 | return TestResult::new(test_name, "GRASP-01", desc) | 1174 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1175 | .fail(format!("Failed to get owner npub: {}", e)); | 1175 | .fail(format!("Failed to get owner npub: {}", e)); |
| 1176 | } | 1176 | } |
| 1177 | }; | 1177 | }; |
| @@ -1180,7 +1180,7 @@ impl PushAuthorizationTests { | |||
| 1180 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { | 1180 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { |
| 1181 | Ok(p) => p, | 1181 | Ok(p) => p, |
| 1182 | Err(e) => { | 1182 | Err(e) => { |
| 1183 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1183 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1184 | } | 1184 | } |
| 1185 | }; | 1185 | }; |
| 1186 | 1186 | ||
| @@ -1188,7 +1188,7 @@ impl PushAuthorizationTests { | |||
| 1188 | if let Err(e) = create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner) | 1188 | if let Err(e) = create_deterministic_commit_with_variant(&clone_path, CommitVariant::Owner) |
| 1189 | { | 1189 | { |
| 1190 | let _ = fs::remove_dir_all(&clone_path); | 1190 | let _ = fs::remove_dir_all(&clone_path); |
| 1191 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1191 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1192 | } | 1192 | } |
| 1193 | 1193 | ||
| 1194 | // Try to push with wrong commit (should be rejected since PR event exists) | 1194 | // Try to push with wrong commit (should be rejected since PR event exists) |
| @@ -1196,7 +1196,7 @@ impl PushAuthorizationTests { | |||
| 1196 | Ok(success) => success, | 1196 | Ok(success) => success, |
| 1197 | Err(e) => { | 1197 | Err(e) => { |
| 1198 | let _ = fs::remove_dir_all(&clone_path); | 1198 | let _ = fs::remove_dir_all(&clone_path); |
| 1199 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1199 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1200 | } | 1200 | } |
| 1201 | }; | 1201 | }; |
| 1202 | 1202 | ||
| @@ -1204,11 +1204,11 @@ impl PushAuthorizationTests { | |||
| 1204 | 1204 | ||
| 1205 | // Should REJECT - PR event exists with different commit hash | 1205 | // Should REJECT - PR event exists with different commit hash |
| 1206 | if push_succeeded { | 1206 | if push_succeeded { |
| 1207 | return TestResult::new(test_name, "GRASP-01", desc) | 1207 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1208 | .fail("Push accepted (expected rejection due to commit hash mismatch)"); | 1208 | .fail("Push accepted (expected rejection due to commit hash mismatch)"); |
| 1209 | } | 1209 | } |
| 1210 | 1210 | ||
| 1211 | TestResult::new(test_name, "GRASP-01", desc).pass() | 1211 | TestResult::new(test_name, "GRASP-01:git-http:34", desc).pass() |
| 1212 | } | 1212 | } |
| 1213 | 1213 | ||
| 1214 | /// Test 4: Push correct commit to refs/nostr/<pr-event-id> AFTER PR event exists | 1214 | /// Test 4: Push correct commit to refs/nostr/<pr-event-id> AFTER PR event exists |
| @@ -1233,7 +1233,7 @@ impl PushAuthorizationTests { | |||
| 1233 | { | 1233 | { |
| 1234 | Ok(e) => e, | 1234 | Ok(e) => e, |
| 1235 | Err(e) => { | 1235 | Err(e) => { |
| 1236 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)); | 1236 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); |
| 1237 | } | 1237 | } |
| 1238 | }; | 1238 | }; |
| 1239 | 1239 | ||
| @@ -1243,7 +1243,7 @@ impl PushAuthorizationTests { | |||
| 1243 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1243 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1244 | Ok(r) => r, | 1244 | Ok(r) => r, |
| 1245 | Err(e) => { | 1245 | Err(e) => { |
| 1246 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!("{}", e)); | 1246 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(format!("{}", e)); |
| 1247 | } | 1247 | } |
| 1248 | }; | 1248 | }; |
| 1249 | 1249 | ||
| @@ -1258,7 +1258,7 @@ impl PushAuthorizationTests { | |||
| 1258 | let owner_npub = match repo.pubkey.to_bech32() { | 1258 | let owner_npub = match repo.pubkey.to_bech32() { |
| 1259 | Ok(n) => n, | 1259 | Ok(n) => n, |
| 1260 | Err(e) => { | 1260 | Err(e) => { |
| 1261 | return TestResult::new(test_name, "GRASP-01", desc) | 1261 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1262 | .fail(format!("Failed to get owner npub: {}", e)); | 1262 | .fail(format!("Failed to get owner npub: {}", e)); |
| 1263 | } | 1263 | } |
| 1264 | }; | 1264 | }; |
| @@ -1267,14 +1267,14 @@ impl PushAuthorizationTests { | |||
| 1267 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { | 1267 | let clone_path = match clone_repo(relay_domain, &owner_npub, &repo_id) { |
| 1268 | Ok(p) => p, | 1268 | Ok(p) => p, |
| 1269 | Err(e) => { | 1269 | Err(e) => { |
| 1270 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1270 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1271 | } | 1271 | } |
| 1272 | }; | 1272 | }; |
| 1273 | 1273 | ||
| 1274 | // Create the CORRECT PR test commit (the one expected by PR event) | 1274 | // Create the CORRECT PR test commit (the one expected by PR event) |
| 1275 | if let Err(e) = reset_to_correct_pr_commit(&clone_path) { | 1275 | if let Err(e) = reset_to_correct_pr_commit(&clone_path) { |
| 1276 | let _ = fs::remove_dir_all(&clone_path); | 1276 | let _ = fs::remove_dir_all(&clone_path); |
| 1277 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1277 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1278 | } | 1278 | } |
| 1279 | 1279 | ||
| 1280 | // Push correct commit (should succeed) | 1280 | // Push correct commit (should succeed) |
| @@ -1282,7 +1282,7 @@ impl PushAuthorizationTests { | |||
| 1282 | Ok(success) => success, | 1282 | Ok(success) => success, |
| 1283 | Err(e) => { | 1283 | Err(e) => { |
| 1284 | let _ = fs::remove_dir_all(&clone_path); | 1284 | let _ = fs::remove_dir_all(&clone_path); |
| 1285 | return TestResult::new(test_name, "GRASP-01", desc).fail(&e); | 1285 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc).fail(&e); |
| 1286 | } | 1286 | } |
| 1287 | }; | 1287 | }; |
| 1288 | 1288 | ||
| @@ -1290,11 +1290,11 @@ impl PushAuthorizationTests { | |||
| 1290 | 1290 | ||
| 1291 | // Should ACCEPT - commit matches PR event's c tag | 1291 | // Should ACCEPT - commit matches PR event's c tag |
| 1292 | if !push_succeeded { | 1292 | if !push_succeeded { |
| 1293 | return TestResult::new(test_name, "GRASP-01", desc) | 1293 | return TestResult::new(test_name, "GRASP-01:git-http:34", desc) |
| 1294 | .fail("Push rejected (expected acceptance since commit matches PR event)"); | 1294 | .fail("Push rejected (expected acceptance since commit matches PR event)"); |
| 1295 | } | 1295 | } |
| 1296 | 1296 | ||
| 1297 | TestResult::new(test_name, "GRASP-01", desc).pass() | 1297 | TestResult::new(test_name, "GRASP-01:git-http:34", desc).pass() |
| 1298 | } | 1298 | } |
| 1299 | 1299 | ||
| 1300 | /// Test that HEAD is set after a state event is published with an existing commit | 1300 | /// Test that HEAD is set after a state event is published with an existing commit |
| @@ -1331,7 +1331,7 @@ impl PushAuthorizationTests { | |||
| 1331 | { | 1331 | { |
| 1332 | Ok(e) => e, | 1332 | Ok(e) => e, |
| 1333 | Err(e) => { | 1333 | Err(e) => { |
| 1334 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | 1334 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc).fail(format!( |
| 1335 | "Failed to create HeadSetToDevelopBranch fixture: {}", | 1335 | "Failed to create HeadSetToDevelopBranch fixture: {}", |
| 1336 | e | 1336 | e |
| 1337 | )); | 1337 | )); |
| @@ -1344,7 +1344,7 @@ impl PushAuthorizationTests { | |||
| 1344 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1344 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1345 | Ok(e) => e, | 1345 | Ok(e) => e, |
| 1346 | Err(e) => { | 1346 | Err(e) => { |
| 1347 | return TestResult::new(test_name, "GRASP-01", desc) | 1347 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1348 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); | 1348 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); |
| 1349 | } | 1349 | } |
| 1350 | }; | 1350 | }; |
| @@ -1357,7 +1357,7 @@ impl PushAuthorizationTests { | |||
| 1357 | { | 1357 | { |
| 1358 | Some(id) => id.to_string(), | 1358 | Some(id) => id.to_string(), |
| 1359 | None => { | 1359 | None => { |
| 1360 | return TestResult::new(test_name, "GRASP-01", desc) | 1360 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1361 | .fail("Missing repo_id in ValidRepo"); | 1361 | .fail("Missing repo_id in ValidRepo"); |
| 1362 | } | 1362 | } |
| 1363 | }; | 1363 | }; |
| @@ -1365,7 +1365,7 @@ impl PushAuthorizationTests { | |||
| 1365 | let npub = match valid_repo.pubkey.to_bech32() { | 1365 | let npub = match valid_repo.pubkey.to_bech32() { |
| 1366 | Ok(n) => n, | 1366 | Ok(n) => n, |
| 1367 | Err(e) => { | 1367 | Err(e) => { |
| 1368 | return TestResult::new(test_name, "GRASP-01", desc) | 1368 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1369 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 1369 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| 1370 | } | 1370 | } |
| 1371 | }; | 1371 | }; |
| @@ -1377,16 +1377,16 @@ impl PushAuthorizationTests { | |||
| 1377 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { | 1377 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { |
| 1378 | Ok(branch) => branch, | 1378 | Ok(branch) => branch, |
| 1379 | Err(e) => { | 1379 | Err(e) => { |
| 1380 | return TestResult::new(test_name, "GRASP-01", desc) | 1380 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1381 | .fail(format!("Failed to get default branch: {}", e)); | 1381 | .fail(format!("Failed to get default branch: {}", e)); |
| 1382 | } | 1382 | } |
| 1383 | }; | 1383 | }; |
| 1384 | 1384 | ||
| 1385 | // Verify HEAD points to refs/heads/develop | 1385 | // Verify HEAD points to refs/heads/develop |
| 1386 | if default_branch == "refs/heads/develop" { | 1386 | if default_branch == "refs/heads/develop" { |
| 1387 | TestResult::new(test_name, "GRASP-01", desc).pass() | 1387 | TestResult::new(test_name, "GRASP-01:git-http:32", desc).pass() |
| 1388 | } else { | 1388 | } else { |
| 1389 | TestResult::new(test_name, "GRASP-01", desc).fail(format!( | 1389 | TestResult::new(test_name, "GRASP-01:git-http:32", desc).fail(format!( |
| 1390 | "Expected HEAD to point to 'refs/heads/develop' but got '{}'. \ | 1390 | "Expected HEAD to point to 'refs/heads/develop' but got '{}'. \ |
| 1391 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ | 1391 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ |
| 1392 | as soon as the git data related to that branch has been received.'", | 1392 | as soon as the git data related to that branch has been received.'", |
| @@ -1435,7 +1435,7 @@ impl PushAuthorizationTests { | |||
| 1435 | let _develop_state = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await { | 1435 | let _develop_state = match ctx.get_fixture(FixtureKind::HeadSetToDevelopBranch).await { |
| 1436 | Ok(e) => e, | 1436 | Ok(e) => e, |
| 1437 | Err(e) => { | 1437 | Err(e) => { |
| 1438 | return TestResult::new(test_name, "GRASP-01", desc).fail(format!( | 1438 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc).fail(format!( |
| 1439 | "Failed to create HeadSetToDevelopBranch fixture: {}", | 1439 | "Failed to create HeadSetToDevelopBranch fixture: {}", |
| 1440 | e | 1440 | e |
| 1441 | )); | 1441 | )); |
| @@ -1448,7 +1448,7 @@ impl PushAuthorizationTests { | |||
| 1448 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | 1448 | let valid_repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { |
| 1449 | Ok(e) => e, | 1449 | Ok(e) => e, |
| 1450 | Err(e) => { | 1450 | Err(e) => { |
| 1451 | return TestResult::new(test_name, "GRASP-01", desc) | 1451 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1452 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); | 1452 | .fail(format!("Failed to get ValidRepo fixture: {}", e)); |
| 1453 | } | 1453 | } |
| 1454 | }; | 1454 | }; |
| @@ -1461,7 +1461,7 @@ impl PushAuthorizationTests { | |||
| 1461 | { | 1461 | { |
| 1462 | Some(id) => id.to_string(), | 1462 | Some(id) => id.to_string(), |
| 1463 | None => { | 1463 | None => { |
| 1464 | return TestResult::new(test_name, "GRASP-01", desc) | 1464 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1465 | .fail("Missing repo_id in ValidRepo"); | 1465 | .fail("Missing repo_id in ValidRepo"); |
| 1466 | } | 1466 | } |
| 1467 | }; | 1467 | }; |
| @@ -1469,7 +1469,7 @@ impl PushAuthorizationTests { | |||
| 1469 | let npub = match valid_repo.pubkey.to_bech32() { | 1469 | let npub = match valid_repo.pubkey.to_bech32() { |
| 1470 | Ok(n) => n, | 1470 | Ok(n) => n, |
| 1471 | Err(e) => { | 1471 | Err(e) => { |
| 1472 | return TestResult::new(test_name, "GRASP-01", desc) | 1472 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1473 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | 1473 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); |
| 1474 | } | 1474 | } |
| 1475 | }; | 1475 | }; |
| @@ -1480,7 +1480,7 @@ impl PushAuthorizationTests { | |||
| 1480 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | 1480 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { |
| 1481 | Ok(path) => path, | 1481 | Ok(path) => path, |
| 1482 | Err(e) => { | 1482 | Err(e) => { |
| 1483 | return TestResult::new(test_name, "GRASP-01", desc) | 1483 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1484 | .fail(format!("Failed to clone repo: {}", e)); | 1484 | .fail(format!("Failed to clone repo: {}", e)); |
| 1485 | } | 1485 | } |
| 1486 | }; | 1486 | }; |
| @@ -1495,7 +1495,7 @@ impl PushAuthorizationTests { | |||
| 1495 | 1495 | ||
| 1496 | if let Err(e) = output { | 1496 | if let Err(e) = output { |
| 1497 | let _ = fs::remove_dir_all(&clone_path); | 1497 | let _ = fs::remove_dir_all(&clone_path); |
| 1498 | return TestResult::new(test_name, "GRASP-01", desc) | 1498 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1499 | .fail(format!("Failed to create develop1 branch: {}", e)); | 1499 | .fail(format!("Failed to create develop1 branch: {}", e)); |
| 1500 | } | 1500 | } |
| 1501 | 1501 | ||
| @@ -1504,7 +1504,7 @@ impl PushAuthorizationTests { | |||
| 1504 | Ok(hash) => hash, | 1504 | Ok(hash) => hash, |
| 1505 | Err(e) => { | 1505 | Err(e) => { |
| 1506 | let _ = fs::remove_dir_all(&clone_path); | 1506 | let _ = fs::remove_dir_all(&clone_path); |
| 1507 | return TestResult::new(test_name, "GRASP-01", desc) | 1507 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1508 | .fail(format!("Failed to create commit: {}", e)); | 1508 | .fail(format!("Failed to create commit: {}", e)); |
| 1509 | } | 1509 | } |
| 1510 | }; | 1510 | }; |
| @@ -1529,7 +1529,7 @@ impl PushAuthorizationTests { | |||
| 1529 | Ok(e) => e, | 1529 | Ok(e) => e, |
| 1530 | Err(e) => { | 1530 | Err(e) => { |
| 1531 | let _ = fs::remove_dir_all(&clone_path); | 1531 | let _ = fs::remove_dir_all(&clone_path); |
| 1532 | return TestResult::new(test_name, "GRASP-01", desc) | 1532 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1533 | .fail(format!("Failed to build state event: {}", e)); | 1533 | .fail(format!("Failed to build state event: {}", e)); |
| 1534 | } | 1534 | } |
| 1535 | }; | 1535 | }; |
| @@ -1537,7 +1537,7 @@ impl PushAuthorizationTests { | |||
| 1537 | // Send the state event (commit doesn't exist on relay yet) | 1537 | // Send the state event (commit doesn't exist on relay yet) |
| 1538 | if let Err(e) = client.send_event(state_event).await { | 1538 | if let Err(e) = client.send_event(state_event).await { |
| 1539 | let _ = fs::remove_dir_all(&clone_path); | 1539 | let _ = fs::remove_dir_all(&clone_path); |
| 1540 | return TestResult::new(test_name, "GRASP-01", desc) | 1540 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1541 | .fail(format!("Failed to send state event: {}", e)); | 1541 | .fail(format!("Failed to send state event: {}", e)); |
| 1542 | } | 1542 | } |
| 1543 | 1543 | ||
| @@ -1550,11 +1550,11 @@ impl PushAuthorizationTests { | |||
| 1550 | match push_result { | 1550 | match push_result { |
| 1551 | Ok(true) => { /* Push succeeded, continue to verify */ } | 1551 | Ok(true) => { /* Push succeeded, continue to verify */ } |
| 1552 | Ok(false) => { | 1552 | Ok(false) => { |
| 1553 | return TestResult::new(test_name, "GRASP-01", desc) | 1553 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1554 | .fail("Push to refs/heads/develop1 was rejected"); | 1554 | .fail("Push to refs/heads/develop1 was rejected"); |
| 1555 | } | 1555 | } |
| 1556 | Err(e) => { | 1556 | Err(e) => { |
| 1557 | return TestResult::new(test_name, "GRASP-01", desc) | 1557 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1558 | .fail(format!("Failed to push develop1 branch: {}", e)); | 1558 | .fail(format!("Failed to push develop1 branch: {}", e)); |
| 1559 | } | 1559 | } |
| 1560 | } | 1560 | } |
| @@ -1567,16 +1567,16 @@ impl PushAuthorizationTests { | |||
| 1567 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { | 1567 | match get_default_branch_from_info_refs(relay_domain, &npub, &repo_id).await { |
| 1568 | Ok(branch) => branch, | 1568 | Ok(branch) => branch, |
| 1569 | Err(e) => { | 1569 | Err(e) => { |
| 1570 | return TestResult::new(test_name, "GRASP-01", desc) | 1570 | return TestResult::new(test_name, "GRASP-01:git-http:32", desc) |
| 1571 | .fail(format!("Failed to get default branch: {}", e)); | 1571 | .fail(format!("Failed to get default branch: {}", e)); |
| 1572 | } | 1572 | } |
| 1573 | }; | 1573 | }; |
| 1574 | 1574 | ||
| 1575 | // Verify HEAD points to refs/heads/develop1 | 1575 | // Verify HEAD points to refs/heads/develop1 |
| 1576 | if default_branch == "refs/heads/develop1" { | 1576 | if default_branch == "refs/heads/develop1" { |
| 1577 | TestResult::new(test_name, "GRASP-01", desc).pass() | 1577 | TestResult::new(test_name, "GRASP-01:git-http:32", desc).pass() |
| 1578 | } else { | 1578 | } else { |
| 1579 | TestResult::new(test_name, "GRASP-01", desc).fail(format!( | 1579 | TestResult::new(test_name, "GRASP-01:git-http:32", desc).fail(format!( |
| 1580 | "Expected HEAD to point to 'refs/heads/develop1' but got '{}'. \ | 1580 | "Expected HEAD to point to 'refs/heads/develop1' but got '{}'. \ |
| 1581 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ | 1581 | GRASP-01 requires: 'MUST set repository HEAD per repository state announcement \ |
| 1582 | as soon as the git data related to that branch has been received.'", | 1582 | as soon as the git data related to that branch has been received.'", |
diff --git a/grasp-audit/src/specs/grasp01/repository_creation.rs b/grasp-audit/src/specs/grasp01/repository_creation.rs index 1014aa3..a99b0d2 100644 --- a/grasp-audit/src/specs/grasp01/repository_creation.rs +++ b/grasp-audit/src/specs/grasp01/repository_creation.rs | |||
| @@ -36,6 +36,7 @@ impl RepositoryCreationTests { | |||
| 36 | /// Test that a bare repository is created when a valid announcement is accepted | 36 | /// Test that a bare repository is created when a valid announcement is accepted |
| 37 | /// and is accessible via Smart HTTP service | 37 | /// and is accessible via Smart HTTP service |
| 38 | /// | 38 | /// |
| 39 | /// Spec: Line 28 of ../grasp/01.md | ||
| 39 | /// This test verifies: | 40 | /// This test verifies: |
| 40 | /// 1. Sends a valid repository announcement via TestContext | 41 | /// 1. Sends a valid repository announcement via TestContext |
| 41 | /// 2. Verifies the announcement was accepted | 42 | /// 2. Verifies the announcement was accepted |
| @@ -54,7 +55,7 @@ impl RepositoryCreationTests { | |||
| 54 | Err(e) => { | 55 | Err(e) => { |
| 55 | return TestResult::new( | 56 | return TestResult::new( |
| 56 | test_name, | 57 | test_name, |
| 57 | "GRASP-01", | 58 | "GRASP-01:git-http:28", |
| 58 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", | 59 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 59 | ) | 60 | ) |
| 60 | .fail(format!("Failed to create repo fixture: {}", e)) | 61 | .fail(format!("Failed to create repo fixture: {}", e)) |
| @@ -75,7 +76,7 @@ impl RepositoryCreationTests { | |||
| 75 | None => { | 76 | None => { |
| 76 | return TestResult::new( | 77 | return TestResult::new( |
| 77 | test_name, | 78 | test_name, |
| 78 | "GRASP-01", | 79 | "GRASP-01:git-http:28", |
| 79 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", | 80 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 80 | ) | 81 | ) |
| 81 | .fail("Repository announcement missing d tag") | 82 | .fail("Repository announcement missing d tag") |
| @@ -87,7 +88,7 @@ impl RepositoryCreationTests { | |||
| 87 | Err(e) => { | 88 | Err(e) => { |
| 88 | return TestResult::new( | 89 | return TestResult::new( |
| 89 | test_name, | 90 | test_name, |
| 90 | "GRASP-01", | 91 | "GRASP-01:git-http:28", |
| 91 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", | 92 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 92 | ) | 93 | ) |
| 93 | .fail(format!("Failed to convert pubkey to npub: {}", e)) | 94 | .fail(format!("Failed to convert pubkey to npub: {}", e)) |
| @@ -98,7 +99,7 @@ impl RepositoryCreationTests { | |||
| 98 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { | 99 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { |
| 99 | return TestResult::new( | 100 | return TestResult::new( |
| 100 | test_name, | 101 | test_name, |
| 101 | "GRASP-01", | 102 | "GRASP-01:git-http:28", |
| 102 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", | 103 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 103 | ) | 104 | ) |
| 104 | .fail(format!("Repository not accessible via HTTP: {}", e)); | 105 | .fail(format!("Repository not accessible via HTTP: {}", e)); |
| @@ -106,7 +107,7 @@ impl RepositoryCreationTests { | |||
| 106 | 107 | ||
| 107 | TestResult::new( | 108 | TestResult::new( |
| 108 | test_name, | 109 | test_name, |
| 109 | "GRASP-01", | 110 | "GRASP-01:git-http:28", |
| 110 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", | 111 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 111 | ) | 112 | ) |
| 112 | .pass() | 113 | .pass() |
| @@ -114,6 +115,7 @@ impl RepositoryCreationTests { | |||
| 114 | 115 | ||
| 115 | /// Test that a webpage is served for an existing repository | 116 | /// Test that a webpage is served for an existing repository |
| 116 | /// | 117 | /// |
| 118 | /// Spec: Line 38 of ../grasp/01.md | ||
| 117 | /// This test verifies: | 119 | /// This test verifies: |
| 118 | /// 1. Creates a valid repository announcement | 120 | /// 1. Creates a valid repository announcement |
| 119 | /// 2. Accesses the repository URL without git service parameters | 121 | /// 2. Accesses the repository URL without git service parameters |
| @@ -133,7 +135,7 @@ impl RepositoryCreationTests { | |||
| 133 | Err(e) => { | 135 | Err(e) => { |
| 134 | return TestResult::new( | 136 | return TestResult::new( |
| 135 | test_name, | 137 | test_name, |
| 136 | "GRASP-01", | 138 | "GRASP-01:git-http:38", |
| 137 | "Relay SHOULD serve a webpage for existing repositories", | 139 | "Relay SHOULD serve a webpage for existing repositories", |
| 138 | ) | 140 | ) |
| 139 | .fail(format!("Failed to create repo fixture: {}", e)) | 141 | .fail(format!("Failed to create repo fixture: {}", e)) |
| @@ -154,7 +156,7 @@ impl RepositoryCreationTests { | |||
| 154 | None => { | 156 | None => { |
| 155 | return TestResult::new( | 157 | return TestResult::new( |
| 156 | test_name, | 158 | test_name, |
| 157 | "GRASP-01", | 159 | "GRASP-01:git-http:38", |
| 158 | "Relay SHOULD serve a webpage for existing repositories", | 160 | "Relay SHOULD serve a webpage for existing repositories", |
| 159 | ) | 161 | ) |
| 160 | .fail("Repository announcement missing d tag") | 162 | .fail("Repository announcement missing d tag") |
| @@ -166,7 +168,7 @@ impl RepositoryCreationTests { | |||
| 166 | Err(e) => { | 168 | Err(e) => { |
| 167 | return TestResult::new( | 169 | return TestResult::new( |
| 168 | test_name, | 170 | test_name, |
| 169 | "GRASP-01", | 171 | "GRASP-01:git-http:38", |
| 170 | "Relay SHOULD serve a webpage for existing repositories", | 172 | "Relay SHOULD serve a webpage for existing repositories", |
| 171 | ) | 173 | ) |
| 172 | .fail(format!("Failed to convert pubkey to npub: {}", e)) | 174 | .fail(format!("Failed to convert pubkey to npub: {}", e)) |
| @@ -177,7 +179,7 @@ impl RepositoryCreationTests { | |||
| 177 | if let Err(e) = check_webpage_served(relay_domain, &npub, &repo_id).await { | 179 | if let Err(e) = check_webpage_served(relay_domain, &npub, &repo_id).await { |
| 178 | return TestResult::new( | 180 | return TestResult::new( |
| 179 | test_name, | 181 | test_name, |
| 180 | "GRASP-01", | 182 | "GRASP-01:git-http:38", |
| 181 | "Relay SHOULD serve a webpage for existing repositories", | 183 | "Relay SHOULD serve a webpage for existing repositories", |
| 182 | ) | 184 | ) |
| 183 | .fail(format!("Webpage not served: {}", e)); | 185 | .fail(format!("Webpage not served: {}", e)); |
| @@ -185,7 +187,7 @@ impl RepositoryCreationTests { | |||
| 185 | 187 | ||
| 186 | TestResult::new( | 188 | TestResult::new( |
| 187 | test_name, | 189 | test_name, |
| 188 | "GRASP-01", | 190 | "GRASP-01:git-http:38", |
| 189 | "Relay SHOULD serve a webpage for existing repositories", | 191 | "Relay SHOULD serve a webpage for existing repositories", |
| 190 | ) | 192 | ) |
| 191 | .pass() | 193 | .pass() |
| @@ -193,6 +195,7 @@ impl RepositoryCreationTests { | |||
| 193 | 195 | ||
| 194 | /// Test that 404 is returned for non-existent repositories | 196 | /// Test that 404 is returned for non-existent repositories |
| 195 | /// | 197 | /// |
| 198 | /// Spec: Line 38 of ../grasp/01.md | ||
| 196 | /// This test verifies: | 199 | /// This test verifies: |
| 197 | /// 1. Accesses a URL for a repository that doesn't exist | 200 | /// 1. Accesses a URL for a repository that doesn't exist |
| 198 | /// 2. Verifies a 404 status is returned | 201 | /// 2. Verifies a 404 status is returned |
| @@ -211,8 +214,8 @@ impl RepositoryCreationTests { | |||
| 211 | Err(e) => { | 214 | Err(e) => { |
| 212 | return TestResult::new( | 215 | return TestResult::new( |
| 213 | test_name, | 216 | test_name, |
| 214 | "GRASP-01", | 217 | "GRASP-01:git-http:38", |
| 215 | "Relay SHOULD serve a webpage for existing repositories", | 218 | "Relay SHOULD return 404 for repositories it doesn't host", |
| 216 | ) | 219 | ) |
| 217 | .fail(format!("Failed to create repo fixture: {}", e)) | 220 | .fail(format!("Failed to create repo fixture: {}", e)) |
| 218 | } | 221 | } |
| @@ -223,8 +226,8 @@ impl RepositoryCreationTests { | |||
| 223 | Err(e) => { | 226 | Err(e) => { |
| 224 | return TestResult::new( | 227 | return TestResult::new( |
| 225 | test_name, | 228 | test_name, |
| 226 | "GRASP-01", | 229 | "GRASP-01:git-http:38", |
| 227 | "Relay SHOULD serve a webpage for existing repositories", | 230 | "Relay SHOULD return 404 for repositories it doesn't host", |
| 228 | ) | 231 | ) |
| 229 | .fail(format!("Failed to convert pubkey to npub: {}", e)) | 232 | .fail(format!("Failed to convert pubkey to npub: {}", e)) |
| 230 | } | 233 | } |
| @@ -236,7 +239,7 @@ impl RepositoryCreationTests { | |||
| 236 | if let Err(e) = check_404_for_nonexistent_repo(relay_domain, &npub, fake_repo_id).await { | 239 | if let Err(e) = check_404_for_nonexistent_repo(relay_domain, &npub, fake_repo_id).await { |
| 237 | return TestResult::new( | 240 | return TestResult::new( |
| 238 | test_name, | 241 | test_name, |
| 239 | "GRASP-01", | 242 | "GRASP-01:git-http:38", |
| 240 | "Relay SHOULD return 404 for repositories it doesn't host", | 243 | "Relay SHOULD return 404 for repositories it doesn't host", |
| 241 | ) | 244 | ) |
| 242 | .fail(format!("Expected 404, got: {}", e)); | 245 | .fail(format!("Expected 404, got: {}", e)); |
| @@ -244,7 +247,7 @@ impl RepositoryCreationTests { | |||
| 244 | 247 | ||
| 245 | TestResult::new( | 248 | TestResult::new( |
| 246 | test_name, | 249 | test_name, |
| 247 | "GRASP-01", | 250 | "GRASP-01:git-http:38", |
| 248 | "Relay SHOULD return 404 for repositories it doesn't host", | 251 | "Relay SHOULD return 404 for repositories it doesn't host", |
| 249 | ) | 252 | ) |
| 250 | .pass() | 253 | .pass() |
diff --git a/grasp-audit/src/specs/grasp01/spec_requirements.rs b/grasp-audit/src/specs/grasp01/spec_requirements.rs new file mode 100644 index 0000000..591fef1 --- /dev/null +++ b/grasp-audit/src/specs/grasp01/spec_requirements.rs | |||
| @@ -0,0 +1,209 @@ | |||
| 1 | //! GRASP-01 Specification Requirements | ||
| 2 | //! | ||
| 3 | //! Embedded specification requirements from the GRASP-01 spec document. | ||
| 4 | //! This is the single source of truth for spec text displayed in audit reports. | ||
| 5 | |||
| 6 | /// GRASP spec repository commit ID that this version is based on | ||
| 7 | pub const GRASP_COMMIT_ID: &str = "1fdb8f7"; | ||
| 8 | |||
| 9 | /// A single specification requirement | ||
| 10 | #[derive(Debug, Clone)] | ||
| 11 | pub struct SpecRequirement { | ||
| 12 | /// Line number in the spec document | ||
| 13 | pub line: u32, | ||
| 14 | /// Section name (e.g., "Nostr Relay", "Git Smart HTTP Service", "CORS Support") | ||
| 15 | pub section: &'static str, | ||
| 16 | /// The full requirement text | ||
| 17 | pub text: &'static str, | ||
| 18 | /// Requirement level: MUST, SHOULD, or MAY | ||
| 19 | pub level: RequirementLevel, | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Requirement level per RFC 2119 | ||
| 23 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 24 | pub enum RequirementLevel { | ||
| 25 | Must, | ||
| 26 | Should, | ||
| 27 | May, | ||
| 28 | } | ||
| 29 | |||
| 30 | impl std::fmt::Display for RequirementLevel { | ||
| 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 32 | match self { | ||
| 33 | RequirementLevel::Must => write!(f, "MUST"), | ||
| 34 | RequirementLevel::Should => write!(f, "SHOULD"), | ||
| 35 | RequirementLevel::May => write!(f, "MAY"), | ||
| 36 | } | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | /// All GRASP-01 specification requirements | ||
| 41 | pub const GRASP_01_REQUIREMENTS: &[SpecRequirement] = &[ | ||
| 42 | // Nostr Relay section | ||
| 43 | SpecRequirement { | ||
| 44 | line: 7, | ||
| 45 | section: "Nostr Relay", | ||
| 46 | text: "MUST serve a NIP-01 compliant nostr relay at `/` that accepts git repository announcements and their corresponding repo state announcements.", | ||
| 47 | level: RequirementLevel::Must, | ||
| 48 | }, | ||
| 49 | SpecRequirement { | ||
| 50 | line: 9, | ||
| 51 | section: "Nostr Relay", | ||
| 52 | text: "MUST reject git repository announcements that do not list the service in both `clone` and `relays` tags unless implementing `GRASP-05`.", | ||
| 53 | level: RequirementLevel::Must, | ||
| 54 | }, | ||
| 55 | SpecRequirement { | ||
| 56 | line: 11, | ||
| 57 | section: "Nostr Relay", | ||
| 58 | text: "MAY reject git repository announcements based on other criteria such as pre-payment, quotas, WoT, whitelist, SPAM prevention, etc.", | ||
| 59 | level: RequirementLevel::May, | ||
| 60 | }, | ||
| 61 | SpecRequirement { | ||
| 62 | line: 13, | ||
| 63 | section: "Nostr Relay", | ||
| 64 | text: "MUST accept other events that tag, or are tagged by, either: 1. accepted git repository announcements; or 2. accepted issues or patches", | ||
| 65 | level: RequirementLevel::Must, | ||
| 66 | }, | ||
| 67 | SpecRequirement { | ||
| 68 | line: 18, | ||
| 69 | section: "Nostr Relay", | ||
| 70 | text: "MAY reject or delete events for generic SPAM prevention reasons or curation eg. WoT, whitelist, user bans and banned topics.", | ||
| 71 | level: RequirementLevel::May, | ||
| 72 | }, | ||
| 73 | SpecRequirement { | ||
| 74 | line: 20, | ||
| 75 | section: "Nostr Relay", | ||
| 76 | text: "MUST serve a NIP-11 document", | ||
| 77 | level: RequirementLevel::Must, | ||
| 78 | }, | ||
| 79 | SpecRequirement { | ||
| 80 | line: 22, | ||
| 81 | section: "Nostr Relay", | ||
| 82 | text: "MUST list each supported GRASP under `supported_grasps` in format `GRASP-XX` eg `GRASP-01` as a string array", | ||
| 83 | level: RequirementLevel::Must, | ||
| 84 | }, | ||
| 85 | SpecRequirement { | ||
| 86 | line: 23, | ||
| 87 | section: "Nostr Relay", | ||
| 88 | text: "MUST list repository acceptance criteria under `repo_acceptance_criteria` as a human readable string", | ||
| 89 | level: RequirementLevel::Must, | ||
| 90 | }, | ||
| 91 | SpecRequirement { | ||
| 92 | line: 24, | ||
| 93 | section: "Nostr Relay", | ||
| 94 | text: "MUST list brief summary of curation policy under `curation` if events are curated beyond generic SPAM prevention; otherwise `curation` MUST be omitted", | ||
| 95 | level: RequirementLevel::Must, | ||
| 96 | }, | ||
| 97 | // Git Smart HTTP Service section | ||
| 98 | SpecRequirement { | ||
| 99 | line: 28, | ||
| 100 | section: "Git Smart HTTP Service", | ||
| 101 | text: "MUST serve a git repository via an unauthenticated git smart http service at `/<npub>/<identifier>.git` for each accepted git repository announcement.", | ||
| 102 | level: RequirementLevel::Must, | ||
| 103 | }, | ||
| 104 | SpecRequirement { | ||
| 105 | line: 30, | ||
| 106 | section: "Git Smart HTTP Service", | ||
| 107 | text: "MUST accept pushes via this service that match the latest repo state announcement on the relay, respecting the recursive maintainer set.", | ||
| 108 | level: RequirementLevel::Must, | ||
| 109 | }, | ||
| 110 | SpecRequirement { | ||
| 111 | line: 32, | ||
| 112 | section: "Git Smart HTTP Service", | ||
| 113 | text: "MUST set repository HEAD per repo state announcement as soon as the git data related to that branch has been received.", | ||
| 114 | level: RequirementLevel::Must, | ||
| 115 | }, | ||
| 116 | SpecRequirement { | ||
| 117 | line: 34, | ||
| 118 | section: "Git Smart HTTP Service", | ||
| 119 | text: "MUST accept pushes via this service to `refs/nostr/<event-id>` but SHOULD reject if event exists on relay listing a different tip and MAY reject based on criteria such as size, SPAM prevention, etc. SHOULD delete and MAY garbage collect these refs if no corresponding git PR event or git PR update event, with a `c` tag that matches the ref tip, is accepted by relay within 20 minutes.", | ||
| 120 | level: RequirementLevel::Must, | ||
| 121 | }, | ||
| 122 | SpecRequirement { | ||
| 123 | line: 36, | ||
| 124 | section: "Git Smart HTTP Service", | ||
| 125 | text: "MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` in advertisement and serve available oids.", | ||
| 126 | level: RequirementLevel::Must, | ||
| 127 | }, | ||
| 128 | SpecRequirement { | ||
| 129 | line: 38, | ||
| 130 | section: "Git Smart HTTP Service", | ||
| 131 | text: "SHOULD serve a webpage at the same endpoint linking to git nostr client(s) to browse the repository and a 404 page for repositories it doesn't host.", | ||
| 132 | level: RequirementLevel::Should, | ||
| 133 | }, | ||
| 134 | // CORS Support section | ||
| 135 | SpecRequirement { | ||
| 136 | line: 44, | ||
| 137 | section: "CORS Support", | ||
| 138 | text: "Set `Access-Control-Allow-Origin: *` on ALL responses", | ||
| 139 | level: RequirementLevel::Must, | ||
| 140 | }, | ||
| 141 | SpecRequirement { | ||
| 142 | line: 45, | ||
| 143 | section: "CORS Support", | ||
| 144 | text: "Set `Access-Control-Allow-Methods: GET, POST` on ALL responses", | ||
| 145 | level: RequirementLevel::Must, | ||
| 146 | }, | ||
| 147 | SpecRequirement { | ||
| 148 | line: 46, | ||
| 149 | section: "CORS Support", | ||
| 150 | text: "Set `Access-Control-Allow-Headers: Content-Type` on ALL responses", | ||
| 151 | level: RequirementLevel::Must, | ||
| 152 | }, | ||
| 153 | SpecRequirement { | ||
| 154 | line: 47, | ||
| 155 | section: "CORS Support", | ||
| 156 | text: "Respond to OPTIONS requests with 204 No Content", | ||
| 157 | level: RequirementLevel::Must, | ||
| 158 | }, | ||
| 159 | ]; | ||
| 160 | |||
| 161 | /// Get a requirement by line number | ||
| 162 | pub fn get_requirement(line: u32) -> Option<&'static SpecRequirement> { | ||
| 163 | GRASP_01_REQUIREMENTS.iter().find(|r| r.line == line) | ||
| 164 | } | ||
| 165 | |||
| 166 | /// Get all requirements for a section | ||
| 167 | pub fn get_requirements_for_section(section: &str) -> Vec<&'static SpecRequirement> { | ||
| 168 | GRASP_01_REQUIREMENTS | ||
| 169 | .iter() | ||
| 170 | .filter(|r| r.section == section) | ||
| 171 | .collect() | ||
| 172 | } | ||
| 173 | |||
| 174 | /// Get all unique section names in order | ||
| 175 | pub fn get_sections() -> Vec<&'static str> { | ||
| 176 | let mut sections = Vec::new(); | ||
| 177 | for req in GRASP_01_REQUIREMENTS { | ||
| 178 | if !sections.contains(&req.section) { | ||
| 179 | sections.push(req.section); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | sections | ||
| 183 | } | ||
| 184 | |||
| 185 | #[cfg(test)] | ||
| 186 | mod tests { | ||
| 187 | use super::*; | ||
| 188 | |||
| 189 | #[test] | ||
| 190 | fn test_get_requirement() { | ||
| 191 | let req = get_requirement(7).expect("Line 7 should exist"); | ||
| 192 | assert_eq!(req.section, "Nostr Relay"); | ||
| 193 | assert!(req.text.contains("NIP-01")); | ||
| 194 | } | ||
| 195 | |||
| 196 | #[test] | ||
| 197 | fn test_get_sections() { | ||
| 198 | let sections = get_sections(); | ||
| 199 | assert_eq!(sections.len(), 3); | ||
| 200 | assert_eq!(sections[0], "Nostr Relay"); | ||
| 201 | assert_eq!(sections[1], "Git Smart HTTP Service"); | ||
| 202 | assert_eq!(sections[2], "CORS Support"); | ||
| 203 | } | ||
| 204 | |||
| 205 | #[test] | ||
| 206 | fn test_requirement_count() { | ||
| 207 | assert_eq!(GRASP_01_REQUIREMENTS.len(), 19); | ||
| 208 | } | ||
| 209 | } \ No newline at end of file | ||