upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/bin/grasp-audit.rs
blob: 0d88bcedc5d7daf00ecf83b78ea384a3b6a8d9c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! GRASP Audit CLI Tool

use clap::{Parser, Subcommand};
use grasp_audit::*;
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "grasp-audit")]
#[command(about = "GRASP audit and compliance testing tool", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Run audit tests against a server
    Audit {
        /// Relay URL (e.g., ws://localhost:7000)
        #[arg(short, long)]
        relay: String,

        /// Fixture mode: shared (default) or isolated
        ///
        /// - shared: Fixtures are cached and reused across tests (efficient for sequential test runs)
        /// - isolated: Each test creates fresh fixtures (for parallel tests like cargo test)
        #[arg(short, long, default_value = "shared")]
        mode: String,

        /// Spec to test (nip01-smoke, nip11, event-acceptance, cors, git-clone, push-auth, repo-creation, all)
        #[arg(short, long, default_value = "all")]
        spec: String,

        /// Git data directory (required for cors, git-clone, push-auth, repo-creation specs)
        #[arg(short, long)]
        git_data_dir: Option<PathBuf>,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive(tracing::Level::INFO.into()),
        )
        .init();

    let cli = Cli::parse();

    match cli.command {
        Commands::Audit {
            relay,
            mode,
            spec,
            git_data_dir,
        } => {
            #[allow(deprecated)]
            let mut config = match mode.as_str() {
                "shared" => AuditConfig::shared(),
                "isolated" => AuditConfig::isolated(),
                // Backwards compatibility aliases
                "ci" => AuditConfig::ci(),
                "production" => AuditConfig::production(),
                _ => return Err(anyhow!("Invalid mode: {}. Use 'shared' or 'isolated'", mode)),
            };

            // Audit needs to create events to test the relay, so disable read-only mode
            config.read_only = false;

            // Derive relay_domain from relay URL (e.g., "ws://localhost:8081" -> "localhost:8081")
            let relay_domain = relay
                .replace("ws://", "")
                .replace("wss://", "")
                .trim_end_matches('/')
                .to_string();

            println!("🔍 GRASP Audit Tool");
            println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            println!("Relay:   {}", relay);
            println!("Mode:    {}", mode);
            println!("Spec:    {}", spec);
            println!("Run ID:  {}", config.run_id);
            if let Some(ref dir) = git_data_dir {
                println!("Git Dir: {}", dir.display());
            }
            println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            println!();

            println!("Connecting to relay...");
            let client = AuditClient::new(&relay, config)
                .await
                .map_err(|e| anyhow!("Failed to connect to relay: {}", e))?;

            if !client.is_connected().await {
                return Err(anyhow!("Could not establish connection to relay"));
            }

            println!("✓ Connected\n");

            let results = match spec.as_str() {
                "nip01-smoke" => {
                    println!("Running NIP-01 smoke tests...\n");
                    specs::Nip01SmokeTests::run_all(&client).await
                }
                "nip11" => {
                    println!("Running NIP-11 document tests...\n");
                    specs::Nip11DocumentTests::run_all(&client).await
                }
                "event-acceptance" => {
                    println!("Running event acceptance policy tests...\n");
                    specs::EventAcceptancePolicyTests::run_all(&client).await
                }
                "cors" => {
                    println!("Running CORS tests...\n");
                    specs::CorsTests::run_all(&client, &relay_domain).await
                }
                "git-clone" => {
                    println!("Running Git clone tests...\n");
                    specs::GitCloneTests::run_all(&client, &relay_domain).await
                }
                "push-auth" => {
                    println!("Running push authorization tests...\n");
                    specs::PushAuthorizationTests::run_all(&client, &relay_domain).await
                }
                "repo-creation" => {
                    println!("Running repository creation tests...\n");
                    specs::RepositoryCreationTests::run_all(&client, &relay_domain).await
                }
                "all" => {
                    println!("Running all tests...\n");
                    let mut all_results = AuditResult::new("All GRASP-01 Tests");

                    // Repository creation tests
                    println!("  → Repository creation tests...");
                    let repo_results = specs::RepositoryCreationTests::run_all(&client, &relay_domain).await;
                    all_results.merge(repo_results);

                    // Git clone tests
                    println!("  → Git clone tests...");
                    let clone_results = specs::GitCloneTests::run_all(&client, &relay_domain).await;
                    all_results.merge(clone_results);

                    // Push authorization tests
                    println!("  → Push authorization tests...");
                    let push_results = specs::PushAuthorizationTests::run_all(&client, &relay_domain).await;
                    all_results.merge(push_results);

                    // Event acceptance policy tests
                    println!("  → Event acceptance policy tests...");
                    let event_results = specs::EventAcceptancePolicyTests::run_all(&client).await;
                    all_results.merge(event_results);

                    // NIP-01 smoke tests
                    println!("  → NIP-01 smoke tests...");
                    let nip01_results = specs::Nip01SmokeTests::run_all(&client).await;
                    all_results.merge(nip01_results);

                    // NIP-11 document tests
                    println!("  → NIP-11 document tests...");
                    let nip11_results = specs::Nip11DocumentTests::run_all(&client).await;
                    all_results.merge(nip11_results);

                    // CORS tests
                    println!("  → CORS tests...");
                    let cors_results = specs::CorsTests::run_all(&client, &relay_domain).await;
                    all_results.merge(cors_results);

                    println!();
                    all_results
                }
                _ => {
                    return Err(anyhow!(
                        "Unknown spec: {}. Use 'nip01-smoke', 'nip11', 'event-acceptance', 'cors', 'git-clone', 'push-auth', 'repo-creation', or 'all'",
                        spec
                    ))
                }
            };

            results.print_report();

            if !results.all_passed() {
                println!("❌ Some tests failed");
                std::process::exit(1);
            } else {
                println!("✅ All tests passed!");
            }
        }
    }

    Ok(())
}