diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 01:44:58 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 01:44:58 +0000 |
| commit | f053827e0a157f348d9cf834f026a8de322abfe2 (patch) | |
| tree | 4dcde0f1e92dfe26fde131ef0f3f35e677e56b5b | |
| parent | 0c1d60a2ad69e79e83d36ed8a001743fde3d6666 (diff) | |
grasp-audit run all tests in audit mode
| -rw-r--r-- | grasp-audit/src/bin/grasp-audit.rs | 105 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/git_clone.rs | 14 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/mod.rs | 14 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 16 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/repository_creation.rs | 14 | ||||
| -rw-r--r-- | grasp-audit/src/specs/mod.rs | 7 | ||||
| -rwxr-xr-x | grasp-audit/test-ngit-relay.sh | 22 |
7 files changed, 179 insertions, 13 deletions
diff --git a/grasp-audit/src/bin/grasp-audit.rs b/grasp-audit/src/bin/grasp-audit.rs index b56a8e3..b810bea 100644 --- a/grasp-audit/src/bin/grasp-audit.rs +++ b/grasp-audit/src/bin/grasp-audit.rs | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | use clap::{Parser, Subcommand}; | 3 | use clap::{Parser, Subcommand}; |
| 4 | use grasp_audit::*; | 4 | use grasp_audit::*; |
| 5 | use std::path::PathBuf; | ||
| 5 | 6 | ||
| 6 | #[derive(Parser)] | 7 | #[derive(Parser)] |
| 7 | #[command(name = "grasp-audit")] | 8 | #[command(name = "grasp-audit")] |
| @@ -23,9 +24,13 @@ enum Commands { | |||
| 23 | #[arg(short, long, default_value = "ci")] | 24 | #[arg(short, long, default_value = "ci")] |
| 24 | mode: String, | 25 | mode: String, |
| 25 | 26 | ||
| 26 | /// Spec to test (nip01-smoke, all) | 27 | /// Spec to test (nip01-smoke, nip11, event-acceptance, cors, git-clone, push-auth, repo-creation, all) |
| 27 | #[arg(short, long, default_value = "nip01-smoke")] | 28 | #[arg(short, long, default_value = "all")] |
| 28 | spec: String, | 29 | spec: String, |
| 30 | |||
| 31 | /// Git data directory (required for cors, git-clone, push-auth, repo-creation specs) | ||
| 32 | #[arg(short, long)] | ||
| 33 | git_data_dir: Option<PathBuf>, | ||
| 29 | }, | 34 | }, |
| 30 | } | 35 | } |
| 31 | 36 | ||
| @@ -42,19 +47,29 @@ async fn main() -> Result<()> { | |||
| 42 | let cli = Cli::parse(); | 47 | let cli = Cli::parse(); |
| 43 | 48 | ||
| 44 | match cli.command { | 49 | match cli.command { |
| 45 | Commands::Audit { relay, mode, spec } => { | 50 | Commands::Audit { relay, mode, spec, git_data_dir } => { |
| 46 | let config = match mode.as_str() { | 51 | let config = match mode.as_str() { |
| 47 | "ci" => AuditConfig::ci(), | 52 | "ci" => AuditConfig::ci(), |
| 48 | "production" => AuditConfig::production(), | 53 | "production" => AuditConfig::production(), |
| 49 | _ => return Err(anyhow!("Invalid mode: {}. Use 'ci' or 'production'", mode)), | 54 | _ => return Err(anyhow!("Invalid mode: {}. Use 'ci' or 'production'", mode)), |
| 50 | }; | 55 | }; |
| 51 | 56 | ||
| 57 | // Derive relay_domain from relay URL (e.g., "ws://localhost:8081" -> "localhost:8081") | ||
| 58 | let relay_domain = relay | ||
| 59 | .replace("ws://", "") | ||
| 60 | .replace("wss://", "") | ||
| 61 | .trim_end_matches('/') | ||
| 62 | .to_string(); | ||
| 63 | |||
| 52 | println!("🔍 GRASP Audit Tool"); | 64 | println!("🔍 GRASP Audit Tool"); |
| 53 | println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); | 65 | println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); |
| 54 | println!("Relay: {}", relay); | 66 | println!("Relay: {}", relay); |
| 55 | println!("Mode: {}", mode); | 67 | println!("Mode: {}", mode); |
| 56 | println!("Spec: {}", spec); | 68 | println!("Spec: {}", spec); |
| 57 | println!("Run ID: {}", config.run_id); | 69 | println!("Run ID: {}", config.run_id); |
| 70 | if let Some(ref dir) = git_data_dir { | ||
| 71 | println!("Git Dir: {}", dir.display()); | ||
| 72 | } | ||
| 58 | println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); | 73 | println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); |
| 59 | println!(); | 74 | println!(); |
| 60 | 75 | ||
| @@ -69,18 +84,98 @@ async fn main() -> Result<()> { | |||
| 69 | 84 | ||
| 70 | println!("✓ Connected\n"); | 85 | println!("✓ Connected\n"); |
| 71 | 86 | ||
| 87 | // Helper to check if git_data_dir is required | ||
| 88 | let require_git_data_dir = |spec_name: &str| -> Result<PathBuf> { | ||
| 89 | git_data_dir.clone().ok_or_else(|| { | ||
| 90 | anyhow!( | ||
| 91 | "The '{}' spec requires --git-data-dir to be specified", | ||
| 92 | spec_name | ||
| 93 | ) | ||
| 94 | }) | ||
| 95 | }; | ||
| 96 | |||
| 72 | let results = match spec.as_str() { | 97 | let results = match spec.as_str() { |
| 73 | "nip01-smoke" => { | 98 | "nip01-smoke" => { |
| 74 | println!("Running NIP-01 smoke tests...\n"); | 99 | println!("Running NIP-01 smoke tests...\n"); |
| 75 | specs::Nip01SmokeTests::run_all(&client).await | 100 | specs::Nip01SmokeTests::run_all(&client).await |
| 76 | } | 101 | } |
| 102 | "nip11" => { | ||
| 103 | println!("Running NIP-11 document tests...\n"); | ||
| 104 | specs::Nip11DocumentTests::run_all(&client).await | ||
| 105 | } | ||
| 106 | "event-acceptance" => { | ||
| 107 | println!("Running event acceptance policy tests...\n"); | ||
| 108 | specs::EventAcceptancePolicyTests::run_all(&client).await | ||
| 109 | } | ||
| 110 | "cors" => { | ||
| 111 | println!("Running CORS tests...\n"); | ||
| 112 | specs::CorsTests::run_all(&client, &relay_domain).await | ||
| 113 | } | ||
| 114 | "git-clone" => { | ||
| 115 | let dir = require_git_data_dir("git-clone")?; | ||
| 116 | println!("Running Git clone tests...\n"); | ||
| 117 | specs::GitCloneTests::run_all(&client, &dir, &relay_domain).await | ||
| 118 | } | ||
| 119 | "push-auth" => { | ||
| 120 | let dir = require_git_data_dir("push-auth")?; | ||
| 121 | println!("Running push authorization tests...\n"); | ||
| 122 | specs::PushAuthorizationTests::run_all(&client, &dir, &relay_domain).await | ||
| 123 | } | ||
| 124 | "repo-creation" => { | ||
| 125 | let dir = require_git_data_dir("repo-creation")?; | ||
| 126 | println!("Running repository creation tests...\n"); | ||
| 127 | specs::RepositoryCreationTests::run_all(&client, &dir).await | ||
| 128 | } | ||
| 77 | "all" => { | 129 | "all" => { |
| 78 | println!("Running all tests...\n"); | 130 | println!("Running all tests...\n"); |
| 79 | specs::Nip01SmokeTests::run_all(&client).await | 131 | let mut all_results = AuditResult::new("All GRASP-01 Tests"); |
| 132 | |||
| 133 | // NIP-01 smoke tests | ||
| 134 | println!(" → NIP-01 smoke tests..."); | ||
| 135 | let nip01_results = specs::Nip01SmokeTests::run_all(&client).await; | ||
| 136 | all_results.merge(nip01_results); | ||
| 137 | |||
| 138 | // NIP-11 document tests | ||
| 139 | println!(" → NIP-11 document tests..."); | ||
| 140 | let nip11_results = specs::Nip11DocumentTests::run_all(&client).await; | ||
| 141 | all_results.merge(nip11_results); | ||
| 142 | |||
| 143 | // Event acceptance policy tests | ||
| 144 | println!(" → Event acceptance policy tests..."); | ||
| 145 | let event_results = specs::EventAcceptancePolicyTests::run_all(&client).await; | ||
| 146 | all_results.merge(event_results); | ||
| 147 | |||
| 148 | // CORS tests | ||
| 149 | println!(" → CORS tests..."); | ||
| 150 | let cors_results = specs::CorsTests::run_all(&client, &relay_domain).await; | ||
| 151 | all_results.merge(cors_results); | ||
| 152 | |||
| 153 | // Tests that require git_data_dir | ||
| 154 | if let Some(ref dir) = git_data_dir { | ||
| 155 | // Git clone tests | ||
| 156 | println!(" → Git clone tests..."); | ||
| 157 | let clone_results = specs::GitCloneTests::run_all(&client, dir, &relay_domain).await; | ||
| 158 | all_results.merge(clone_results); | ||
| 159 | |||
| 160 | // Push authorization tests | ||
| 161 | println!(" → Push authorization tests..."); | ||
| 162 | let push_results = specs::PushAuthorizationTests::run_all(&client, dir, &relay_domain).await; | ||
| 163 | all_results.merge(push_results); | ||
| 164 | |||
| 165 | // Repository creation tests | ||
| 166 | println!(" → Repository creation tests..."); | ||
| 167 | let repo_results = specs::RepositoryCreationTests::run_all(&client, dir).await; | ||
| 168 | all_results.merge(repo_results); | ||
| 169 | } else { | ||
| 170 | println!(" ⚠ Skipping git-clone, push-auth, repo-creation tests (no --git-data-dir)"); | ||
| 171 | } | ||
| 172 | |||
| 173 | println!(); | ||
| 174 | all_results | ||
| 80 | } | 175 | } |
| 81 | _ => { | 176 | _ => { |
| 82 | return Err(anyhow!( | 177 | return Err(anyhow!( |
| 83 | "Unknown spec: {}. Use 'nip01-smoke' or 'all'", | 178 | "Unknown spec: {}. Use 'nip01-smoke', 'nip11', 'event-acceptance', 'cors', 'git-clone', 'push-auth', 'repo-creation', or 'all'", |
| 84 | spec | 179 | spec |
| 85 | )) | 180 | )) |
| 86 | } | 181 | } |
diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs index da60f26..8c91c04 100644 --- a/grasp-audit/src/specs/grasp01/git_clone.rs +++ b/grasp-audit/src/specs/grasp01/git_clone.rs | |||
| @@ -24,6 +24,20 @@ use std::process::Command; | |||
| 24 | pub struct GitCloneTests; | 24 | pub struct GitCloneTests; |
| 25 | 25 | ||
| 26 | impl GitCloneTests { | 26 | impl GitCloneTests { |
| 27 | /// Run all Git clone tests | ||
| 28 | pub async fn run_all( | ||
| 29 | client: &AuditClient, | ||
| 30 | git_data_dir: &Path, | ||
| 31 | relay_domain: &str, | ||
| 32 | ) -> crate::AuditResult { | ||
| 33 | let mut results = crate::AuditResult::new("GRASP-01 Git Clone Tests"); | ||
| 34 | |||
| 35 | results.add(Self::test_basic_git_clone(client, git_data_dir, relay_domain).await); | ||
| 36 | results.add(Self::test_clone_url_format(client, git_data_dir, relay_domain).await); | ||
| 37 | |||
| 38 | results | ||
| 39 | } | ||
| 40 | |||
| 27 | /// Test that a repository can be cloned via Git HTTP backend | 41 | /// Test that a repository can be cloned via Git HTTP backend |
| 28 | /// | 42 | /// |
| 29 | /// This test: | 43 | /// This test: |
diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs index 0d0bd9c..b5471d1 100644 --- a/grasp-audit/src/specs/grasp01/mod.rs +++ b/grasp-audit/src/specs/grasp01/mod.rs | |||
| @@ -1,4 +1,16 @@ | |||
| 1 | //! GRASP-01 specification tests | 1 | //! GRASP-01 specification tests |
| 2 | //! | ||
| 3 | //! This module contains all test suites for GRASP-01 compliance testing. | ||
| 4 | //! | ||
| 5 | //! ## Test Suites | ||
| 6 | //! | ||
| 7 | //! - [`Nip01SmokeTests`] - Basic NIP-01 relay functionality (WebSocket-only) | ||
| 8 | //! - [`Nip11DocumentTests`] - NIP-11 relay information document (WebSocket-only) | ||
| 9 | //! - [`EventAcceptancePolicyTests`] - Event acceptance rules (WebSocket-only) | ||
| 10 | //! - [`CorsTests`] - CORS headers on Git HTTP endpoints (requires git-data-dir) | ||
| 11 | //! - [`GitCloneTests`] - Git clone operations (requires git-data-dir) | ||
| 12 | //! - [`PushAuthorizationTests`] - Push authorization (requires git-data-dir) | ||
| 13 | //! - [`RepositoryCreationTests`] - Repository creation (requires git-data-dir) | ||
| 2 | 14 | ||
| 3 | pub mod cors; | 15 | pub mod cors; |
| 4 | pub mod event_acceptance_policy; | 16 | pub mod event_acceptance_policy; |
| @@ -14,4 +26,4 @@ pub use git_clone::GitCloneTests; | |||
| 14 | pub use nip01_smoke::Nip01SmokeTests; | 26 | pub use nip01_smoke::Nip01SmokeTests; |
| 15 | pub use nip11_document::Nip11DocumentTests; | 27 | pub use nip11_document::Nip11DocumentTests; |
| 16 | pub use push_authorization::PushAuthorizationTests; | 28 | pub use push_authorization::PushAuthorizationTests; |
| 17 | pub use repository_creation::RepositoryCreationTests; | 29 | pub use repository_creation::{is_bare_repository, RepositoryCreationTests}; |
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index fad77fb..4599ea5 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -30,6 +30,22 @@ use std::path::Path; | |||
| 30 | pub struct PushAuthorizationTests; | 30 | pub struct PushAuthorizationTests; |
| 31 | 31 | ||
| 32 | impl PushAuthorizationTests { | 32 | impl PushAuthorizationTests { |
| 33 | /// Run all push authorization tests | ||
| 34 | pub async fn run_all( | ||
| 35 | client: &AuditClient, | ||
| 36 | git_data_dir: &Path, | ||
| 37 | relay_domain: &str, | ||
| 38 | ) -> crate::AuditResult { | ||
| 39 | let mut results = crate::AuditResult::new("GRASP-01 Push Authorization Tests"); | ||
| 40 | |||
| 41 | results.add(Self::test_push_authorized_by_owner_state(client, git_data_dir, relay_domain).await); | ||
| 42 | results.add(Self::test_push_rejected_without_state_event(client, git_data_dir, relay_domain).await); | ||
| 43 | results.add(Self::test_push_rejected_wrong_commit(client, git_data_dir, relay_domain).await); | ||
| 44 | results.add(Self::test_push_authorized_by_maintainer_state_only(client, git_data_dir, relay_domain).await); | ||
| 45 | |||
| 46 | results | ||
| 47 | } | ||
| 48 | |||
| 33 | /// Test that push is authorized when state event matches the commit | 49 | /// Test that push is authorized when state event matches the commit |
| 34 | /// | 50 | /// |
| 35 | /// GRASP-01: "MUST accept pushes via this service that match the latest | 51 | /// GRASP-01: "MUST accept pushes via this service that match the latest |
diff --git a/grasp-audit/src/specs/grasp01/repository_creation.rs b/grasp-audit/src/specs/grasp01/repository_creation.rs index 31ef400..2eaf32f 100644 --- a/grasp-audit/src/specs/grasp01/repository_creation.rs +++ b/grasp-audit/src/specs/grasp01/repository_creation.rs | |||
| @@ -24,6 +24,20 @@ use std::path::Path; | |||
| 24 | pub struct RepositoryCreationTests; | 24 | pub struct RepositoryCreationTests; |
| 25 | 25 | ||
| 26 | impl RepositoryCreationTests { | 26 | impl RepositoryCreationTests { |
| 27 | /// Run all repository creation tests | ||
| 28 | pub async fn run_all( | ||
| 29 | client: &AuditClient, | ||
| 30 | git_data_dir: &Path, | ||
| 31 | ) -> crate::AuditResult { | ||
| 32 | let mut results = crate::AuditResult::new("GRASP-01 Repository Creation Tests"); | ||
| 33 | |||
| 34 | results.add(Self::test_bare_repo_created_on_announcement(client, git_data_dir).await); | ||
| 35 | results.add(Self::test_repo_creation_idempotent(client, git_data_dir).await); | ||
| 36 | results.add(Self::test_bare_repo_structure(client, git_data_dir).await); | ||
| 37 | |||
| 38 | results | ||
| 39 | } | ||
| 40 | |||
| 27 | /// Test that a bare repository is created when a valid announcement is accepted | 41 | /// Test that a bare repository is created when a valid announcement is accepted |
| 28 | /// | 42 | /// |
| 29 | /// This test: | 43 | /// This test: |
diff --git a/grasp-audit/src/specs/mod.rs b/grasp-audit/src/specs/mod.rs index a502866..1444c80 100644 --- a/grasp-audit/src/specs/mod.rs +++ b/grasp-audit/src/specs/mod.rs | |||
| @@ -1,6 +1,11 @@ | |||
| 1 | //! Test specifications | 1 | //! Test specifications |
| 2 | //! | ||
| 3 | //! This module contains all GRASP specification test suites. | ||
| 2 | 4 | ||
| 3 | pub mod grasp01; | 5 | pub mod grasp01; |
| 4 | 6 | ||
| 5 | // Re-export all test structs from grasp01 module | 7 | // Re-export all test structs from grasp01 module |
| 6 | pub use grasp01::{EventAcceptancePolicyTests, Nip01SmokeTests, Nip11DocumentTests}; | 8 | pub use grasp01::{ |
| 9 | CorsTests, EventAcceptancePolicyTests, GitCloneTests, Nip01SmokeTests, Nip11DocumentTests, | ||
| 10 | PushAuthorizationTests, RepositoryCreationTests, | ||
| 11 | }; | ||
diff --git a/grasp-audit/test-ngit-relay.sh b/grasp-audit/test-ngit-relay.sh index 2f43042..2e013b1 100755 --- a/grasp-audit/test-ngit-relay.sh +++ b/grasp-audit/test-ngit-relay.sh | |||
| @@ -35,19 +35,27 @@ OPTIONS: | |||
| 35 | --mode <audit|test> Execution mode (default: audit) | 35 | --mode <audit|test> Execution mode (default: audit) |
| 36 | audit: Run grasp-audit CLI tool | 36 | audit: Run grasp-audit CLI tool |
| 37 | test: Run cargo test suite | 37 | test: Run cargo test suite |
| 38 | --spec <spec> Specification to test (default: nip01-smoke) | 38 | --spec <spec> Specification to test (default: all) |
| 39 | Available specs: nip01-smoke, nip11, event-acceptance, | ||
| 40 | cors, git-clone, push-auth, repo-creation, all | ||
| 39 | Only used in audit mode | 41 | Only used in audit mode |
| 40 | --help Show this help message | 42 | --help Show this help message |
| 41 | 43 | ||
| 42 | EXAMPLES: | 44 | EXAMPLES: |
| 43 | # Run audit with default settings (current behavior) | 45 | # Run audit with all tests (default) |
| 44 | ./test-ngit-relay.sh | 46 | ./test-ngit-relay.sh |
| 45 | 47 | ||
| 46 | # Run cargo tests instead | 48 | # Run cargo tests instead |
| 47 | ./test-ngit-relay.sh --mode test | 49 | ./test-ngit-relay.sh --mode test |
| 48 | 50 | ||
| 49 | # Run audit with specific spec | 51 | # Run audit with specific spec |
| 50 | ./test-ngit-relay.sh --mode audit --spec grasp01 | 52 | ./test-ngit-relay.sh --mode audit --spec nip01-smoke |
| 53 | ./test-ngit-relay.sh --mode audit --spec nip11 | ||
| 54 | ./test-ngit-relay.sh --mode audit --spec event-acceptance | ||
| 55 | ./test-ngit-relay.sh --mode audit --spec cors | ||
| 56 | ./test-ngit-relay.sh --mode audit --spec git-clone | ||
| 57 | ./test-ngit-relay.sh --mode audit --spec push-auth | ||
| 58 | ./test-ngit-relay.sh --mode audit --spec repo-creation | ||
| 51 | 59 | ||
| 52 | EOF | 60 | EOF |
| 53 | exit 0 | 61 | exit 0 |
| @@ -56,9 +64,9 @@ EOF | |||
| 56 | # ----------------------------------------------------------------------------- | 64 | # ----------------------------------------------------------------------------- |
| 57 | # Argument Parsing | 65 | # Argument Parsing |
| 58 | # ----------------------------------------------------------------------------- | 66 | # ----------------------------------------------------------------------------- |
| 59 | # Default values maintain backward compatibility | 67 | # Default: run audit mode with all specs |
| 60 | MODE="audit" | 68 | MODE="audit" |
| 61 | SPEC="nip01-smoke" | 69 | SPEC="all" |
| 62 | 70 | ||
| 63 | # Parse command-line arguments | 71 | # Parse command-line arguments |
| 64 | while [[ $# -gt 0 ]]; do | 72 | while [[ $# -gt 0 ]]; do |
| @@ -184,11 +192,13 @@ if [ "$MODE" = "audit" ]; then | |||
| 184 | # - --relay: Command-line parameter for relay address | 192 | # - --relay: Command-line parameter for relay address |
| 185 | # - --mode ci: Continuous integration mode (structured output) | 193 | # - --mode ci: Continuous integration mode (structured output) |
| 186 | # - --spec: Which specification to test | 194 | # - --spec: Which specification to test |
| 195 | # - --git-data-dir: Path to git data directory (for git-clone, push-auth, repo-creation) | ||
| 187 | # The '|| { }' block provides user-friendly messaging on failure | 196 | # The '|| { }' block provides user-friendly messaging on failure |
| 188 | RELAY_URL="ws://localhost:$PORT" cargo run -- audit \ | 197 | RELAY_URL="ws://localhost:$PORT" cargo run -- audit \ |
| 189 | --relay "ws://localhost:$PORT" \ | 198 | --relay "ws://localhost:$PORT" \ |
| 190 | --mode ci \ | 199 | --mode ci \ |
| 191 | --spec "$SPEC" || { | 200 | --spec "$SPEC" \ |
| 201 | --git-data-dir "$TEST_DIR/repos" || { | ||
| 192 | echo "⚠️ Some tests failed (expected for ngit-relay)" | 202 | echo "⚠️ Some tests failed (expected for ngit-relay)" |
| 193 | echo " Validation tests should have passed" | 203 | echo " Validation tests should have passed" |
| 194 | } | 204 | } |