From d2ac69816567f092fe0d4661723bc43778cb481b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Mon, 1 Dec 2025 14:31:32 +0000 Subject: fix cargo clippy and fmt warnings --- grasp-audit/src/bin/grasp-audit.rs | 16 ++- grasp-audit/src/lib.rs | 22 +++- grasp-audit/src/result.rs | 12 +- grasp-audit/src/specs/grasp01/cors.rs | 51 ++------- .../src/specs/grasp01/event_acceptance_policy.rs | 62 ++++------ grasp-audit/src/specs/grasp01/git_clone.rs | 54 +++------ grasp-audit/src/specs/grasp01/mod.rs | 2 +- grasp-audit/src/specs/grasp01/nip01_smoke.rs | 32 +++--- grasp-audit/src/specs/grasp01/nip11_document.rs | 61 ++++++---- .../src/specs/grasp01/push_authorization.rs | 10 +- .../src/specs/grasp01/repository_creation.rs | 20 +--- src/config.rs | 8 +- src/git/handlers.rs | 1 - src/git/mod.rs | 13 ++- src/git/protocol.rs | 18 +-- src/git/subprocess.rs | 34 +++--- src/http/mod.rs | 125 +++++++++++++-------- src/http/nip11.rs | 34 +++--- src/nostr/builder.rs | 14 +-- src/nostr/events.rs | 18 +-- tests/common/relay.rs | 45 +------- tests/cors.rs | 31 +---- tests/git_clone.rs | 8 +- tests/nip11_document.rs | 2 +- tests/nip34_announcements.rs | 2 +- tests/push_authorization.rs | 7 +- 26 files changed, 300 insertions(+), 402 deletions(-) diff --git a/grasp-audit/src/bin/grasp-audit.rs b/grasp-audit/src/bin/grasp-audit.rs index 2aabefe..48c1580 100644 --- a/grasp-audit/src/bin/grasp-audit.rs +++ b/grasp-audit/src/bin/grasp-audit.rs @@ -47,14 +47,18 @@ async fn main() -> Result<()> { let cli = Cli::parse(); match cli.command { - Commands::Audit { relay, mode, spec, git_data_dir } => { - + Commands::Audit { + relay, + mode, + spec, + git_data_dir, + } => { let mut config = match mode.as_str() { "ci" => AuditConfig::ci(), "production" => AuditConfig::production(), _ => return Err(anyhow!("Invalid mode: {}. Use 'ci' or 'production'", mode)), }; - + // Audit needs to create events to test the relay, so disable read-only mode config.read_only = false; @@ -145,17 +149,17 @@ async fn main() -> Result<()> { 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 } diff --git a/grasp-audit/src/lib.rs b/grasp-audit/src/lib.rs index fb52ba7..6df240f 100644 --- a/grasp-audit/src/lib.rs +++ b/grasp-audit/src/lib.rs @@ -39,14 +39,24 @@ pub use audit::{AuditConfig, AuditEventBuilder, AuditMode}; pub use client::AuditClient; pub use fixtures::{ // Git operation helpers - clone_repo, create_commit, create_deterministic_commit, create_deterministic_commit_with_variant, - try_push, try_push_to_ref, + clone_repo, + create_commit, + create_deterministic_commit, + create_deterministic_commit_with_variant, // Verification helpers - send_and_verify_accepted, send_and_verify_rejected, + send_and_verify_accepted, + send_and_verify_rejected, + try_push, + try_push_to_ref, // Types and constants - CommitVariant, ContextMode, FixtureKind, TestContext, - DETERMINISTIC_COMMIT_HASH, MAINTAINER_DETERMINISTIC_COMMIT_HASH, - PR_TEST_COMMIT_HASH, RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH, + CommitVariant, + ContextMode, + FixtureKind, + TestContext, + DETERMINISTIC_COMMIT_HASH, + MAINTAINER_DETERMINISTIC_COMMIT_HASH, + PR_TEST_COMMIT_HASH, + RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH, }; pub use result::{AuditResult, TestResult}; diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs index 2bec5c8..bc0008a 100644 --- a/grasp-audit/src/result.rs +++ b/grasp-audit/src/result.rs @@ -17,7 +17,12 @@ fn extract_spec_category(spec_ref: &str) -> String { if parts.len() >= 2 { // Check if the last part looks like a test number (starts with digit) if let Some(last) = parts.last() { - if last.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) { + if last + .chars() + .next() + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) + { // Remove the trailing number part return parts[..parts.len() - 1].join(":"); } @@ -146,10 +151,7 @@ impl AuditResult { for result in &self.results { // Extract category from spec_ref (e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance") let category = extract_spec_category(&result.spec_ref); - grouped - .entry(category) - .or_default() - .push(result); + grouped.entry(category).or_default().push(result); } // Print grouped results diff --git a/grasp-audit/src/specs/grasp01/cors.rs b/grasp-audit/src/specs/grasp01/cors.rs index 08c5ab6..c877c04 100644 --- a/grasp-audit/src/specs/grasp01/cors.rs +++ b/grasp-audit/src/specs/grasp01/cors.rs @@ -16,7 +16,6 @@ use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult}; use nostr_sdk::prelude::*; -use std::path::Path; pub struct CorsTests; @@ -42,10 +41,7 @@ impl CorsTests { /// /// Spec: Line 44 of ../grasp/01.md /// Requirement: Set `Access-Control-Allow-Origin: *` on ALL responses - pub async fn test_cors_allow_origin( - _client: &AuditClient, - relay_domain: &str, - ) -> TestResult { + pub async fn test_cors_allow_origin(_client: &AuditClient, relay_domain: &str) -> TestResult { TestResult::new( "cors_allow_origin", "GRASP-01:git-http:cors:44", @@ -91,10 +87,7 @@ impl CorsTests { /// /// Spec: Line 45 of ../grasp/01.md /// Requirement: Set `Access-Control-Allow-Methods: GET, POST` on ALL responses - pub async fn test_cors_allow_methods( - _client: &AuditClient, - relay_domain: &str, - ) -> TestResult { + pub async fn test_cors_allow_methods(_client: &AuditClient, relay_domain: &str) -> TestResult { TestResult::new( "cors_allow_methods", "GRASP-01:git-http:cors:45", @@ -138,10 +131,7 @@ impl CorsTests { /// /// Spec: Line 46 of ../grasp/01.md /// Requirement: Set `Access-Control-Allow-Headers: Content-Type` on ALL responses - pub async fn test_cors_allow_headers( - _client: &AuditClient, - relay_domain: &str, - ) -> TestResult { + pub async fn test_cors_allow_headers(_client: &AuditClient, relay_domain: &str) -> TestResult { TestResult::new( "cors_allow_headers", "GRASP-01:git-http:cors:46", @@ -212,10 +202,8 @@ impl CorsTests { check_options_response(&response, "root endpoint")?; // 2. Test OPTIONS on git-upload-pack endpoint - let repo_url = format!( - "http://{}/npub1test/test.git/git-upload-pack", - relay_domain - ); + let repo_url = + format!("http://{}/npub1test/test.git/git-upload-pack", relay_domain); let response = http_client .request(reqwest::Method::OPTIONS, &repo_url) .header("Origin", "https://example.com") @@ -227,10 +215,7 @@ impl CorsTests { check_options_response(&response, "git-upload-pack endpoint")?; // 3. Test OPTIONS on info/refs endpoint - let refs_url = format!( - "http://{}/npub1test/test.git/info/refs", - relay_domain - ); + let refs_url = format!("http://{}/npub1test/test.git/info/refs", relay_domain); let response = http_client .request(reqwest::Method::OPTIONS, &refs_url) .header("Origin", "https://example.com") @@ -255,10 +240,7 @@ impl CorsTests { /// Integration test: CORS Allow-Origin header with repository creation /// /// For integration tests that want to test against real repositories - pub async fn test_cors_on_real_repo( - client: &AuditClient, - relay_domain: &str, - ) -> TestResult { + pub async fn test_cors_on_real_repo(client: &AuditClient, relay_domain: &str) -> TestResult { let test_name = "test_cors_on_real_repo"; let ctx = TestContext::new(client); @@ -271,7 +253,7 @@ impl CorsTests { "GRASP-01", "CORS headers on real repository endpoint", ) - .fail(&format!("Failed to create repo fixture: {}", e)) + .fail(format!("Failed to create repo fixture: {}", e)) } }; @@ -304,7 +286,7 @@ impl CorsTests { "GRASP-01", "CORS headers on real repository endpoint", ) - .fail(&format!("Failed to convert pubkey to npub: {}", e)) + .fail(format!("Failed to convert pubkey to npub: {}", e)) } }; @@ -323,7 +305,7 @@ impl CorsTests { "GRASP-01", "CORS headers on real repository endpoint", ) - .fail(&format!("Failed to GET info/refs: {}", e)) + .fail(format!("Failed to GET info/refs: {}", e)) } }; @@ -492,15 +474,6 @@ mod tests { results.print_report(); // Assert all tests passed - assert!( - results.all_passed(), - "Some GRASP-01 CORS tests failed" - ); + assert!(results.all_passed(), "Some GRASP-01 CORS tests failed"); } - - #[test] - fn test_module_exists() { - // Simple compilation test - assert!(true); - } -} \ 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 3a8f18d..1fc7f73 100644 --- a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs +++ b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs @@ -247,7 +247,9 @@ impl EventAcceptancePolicyTests { /// /// Spec: Line 5 of ../grasp/01.md /// Requirement: MUST reject announcements not listing service (unless GRASP-05) - pub async fn test_reject_repo_announcement_missing_clone_tag(client: &AuditClient) -> TestResult { + pub async fn test_reject_repo_announcement_missing_clone_tag( + client: &AuditClient, + ) -> TestResult { TestResult::new( "reject_repo_announcement_missing_clone_tag", "GRASP-01:nostr-relay:5", @@ -321,7 +323,9 @@ impl EventAcceptancePolicyTests { /// /// Spec: Line 5 of ../grasp/01.md /// Requirement: MUST reject announcements not listing service in relays - pub async fn test_reject_repo_announcement_missing_relays_tag(client: &AuditClient) -> TestResult { + pub async fn test_reject_repo_announcement_missing_relays_tag( + client: &AuditClient, + ) -> TestResult { TestResult::new( "reject_repo_announcement_missing_relays_tag", "GRASP-01:nostr-relay:5", @@ -546,8 +550,7 @@ impl EventAcceptancePolicyTests { let issue = Self::create_issue_for_repo(client, &repo, "Test Issue 1")?; // 3. Send issue and verify it's accepted - send_and_verify_accepted(client, issue, "issue referencing repo via 'a' tag") - .await?; + send_and_verify_accepted(client, issue, "issue referencing repo via 'a' tag").await?; Ok(()) }) @@ -693,8 +696,7 @@ impl EventAcceptancePolicyTests { .map_err(|e| format!("Failed to build issue B: {}", e))?; // Send Issue B and verify it's ACCEPTED (via transitive quote to Issue A) - send_and_verify_accepted(client, issue_b, "issue B quoting accepted issue A") - .await?; + send_and_verify_accepted(client, issue_b, "issue B quoting accepted issue A").await?; Ok(()) }) @@ -772,8 +774,7 @@ impl EventAcceptancePolicyTests { .build(client.keys()) .map_err(|e| format!("Failed to build kind1 A: {}", e))?; - send_and_verify_accepted(client, kind1_a.clone(), "kind 1 A quoting repo") - .await?; + send_and_verify_accepted(client, kind1_a.clone(), "kind 1 A quoting repo").await?; // Create Kind 1 B that replies to Kind 1 A via 'e' tag let kind1_b = client @@ -783,12 +784,8 @@ impl EventAcceptancePolicyTests { .map_err(|e| format!("Failed to build kind1 B: {}", e))?; // Send Kind 1 B and verify it's accepted (via 'e' tag to accepted kind 1 A) - send_and_verify_accepted( - client, - kind1_b, - "kind 1 B replying to accepted kind 1 A", - ) - .await?; + send_and_verify_accepted(client, kind1_b, "kind 1 B replying to accepted kind 1 A") + .await?; Ok(()) }) @@ -828,17 +825,19 @@ impl EventAcceptancePolicyTests { .kind(Kind::GitRepoAnnouncement) .author(repo.pubkey) .identifier(repo_id); - + // Poll until repo is available (with timeout) for _ in 0..10 { - let events = client.query(verify_filter.clone()).await + let events = client + .query(verify_filter.clone()) + .await .map_err(|e| format!("Failed to verify repo: {}", e))?; if !events.is_empty() { break; } tokio::time::sleep(Duration::from_millis(50)).await; } - + // Extra delay to ensure relay's internal database is fully synchronized tokio::time::sleep(Duration::from_millis(200)).await; @@ -907,12 +906,7 @@ impl EventAcceptancePolicyTests { let issue = ctx .get_fixture(FixtureKind::RepoWithIssue) .await - .map_err(|e| { - format!( - "Test setup failed: could not get issue fixture: {}", - e - ) - })?; + .map_err(|e| format!("Test setup failed: could not get issue fixture: {}", e))?; // Create Comment A locally but DON'T send it yet let comment_a = Self::create_comment_for_event(client, &issue, "Comment A")?; @@ -997,16 +991,11 @@ impl EventAcceptancePolicyTests { .build(client.keys()) .map_err(|e| format!("Failed to build kind1 B: {}", e))?; - send_and_verify_accepted(client, kind1_b, "kind1 B mentioning unsent kind1 A") - .await?; + send_and_verify_accepted(client, kind1_b, "kind1 B mentioning unsent kind1 A").await?; // NOW send Kind 1 A - should be accepted because accepted Kind 1 B mentions it - send_and_verify_accepted( - client, - kind1_a, - "kind1 A referenced by accepted kind1 B", - ) - .await?; + send_and_verify_accepted(client, kind1_a, "kind1 A referenced by accepted kind1 B") + .await?; Ok(()) }) @@ -1033,12 +1022,8 @@ impl EventAcceptancePolicyTests { Self::create_issue_for_repo(client, &unaccepted_repo, "Orphan Issue")?; // 3. Send issue and verify it's REJECTED - send_and_verify_rejected( - client, - orphan_issue, - "issue referencing unaccepted repo", - ) - .await?; + send_and_verify_rejected(client, orphan_issue, "issue referencing unaccepted repo") + .await?; Ok(()) }) @@ -1060,8 +1045,7 @@ impl EventAcceptancePolicyTests { .map_err(|e| format!("Failed to build note: {}", e))?; // 2. Send note and verify it's REJECTED - send_and_verify_rejected(client, orphan_note, "kind 1 with no repo references") - .await?; + send_and_verify_rejected(client, orphan_note, "kind 1 with no repo references").await?; Ok(()) }) diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs index 9ee6ed7..95338e4 100644 --- a/grasp-audit/src/specs/grasp01/git_clone.rs +++ b/grasp-audit/src/specs/grasp01/git_clone.rs @@ -25,10 +25,7 @@ pub struct GitCloneTests; impl GitCloneTests { /// Run all Git clone tests - pub async fn run_all( - client: &AuditClient, - relay_domain: &str, - ) -> crate::AuditResult { + pub async fn run_all(client: &AuditClient, relay_domain: &str) -> crate::AuditResult { let mut results = crate::AuditResult::new("GRASP-01 Git Clone Tests"); results.add(Self::test_basic_git_clone(client, relay_domain).await); @@ -45,10 +42,7 @@ impl GitCloneTests { /// 2. Waits for repository creation /// 3. Attempts to clone the repository using git clone /// 4. Verifies the clone succeeded - pub async fn test_basic_git_clone( - client: &AuditClient, - relay_domain: &str, - ) -> TestResult { + pub async fn test_basic_git_clone(client: &AuditClient, relay_domain: &str) -> TestResult { let test_name = "test_basic_git_clone"; let ctx = TestContext::new(client); @@ -61,7 +55,7 @@ impl GitCloneTests { "GRASP-01", "Repository must be cloneable via Git HTTP backend", ) - .fail(&format!("Failed to create repo fixture: {}", e)) + .fail(format!("Failed to create repo fixture: {}", e)) } }; @@ -94,7 +88,7 @@ impl GitCloneTests { "GRASP-01", "Repository must be cloneable via Git HTTP backend", ) - .fail(&format!("Failed to convert pubkey to npub: {}", e)) + .fail(format!("Failed to convert pubkey to npub: {}", e)) } }; @@ -102,7 +96,7 @@ impl GitCloneTests { let temp_base = std::env::temp_dir(); let clone_dir_name = format!("grasp-test-clone-{}", uuid::Uuid::new_v4()); let clone_path = temp_base.join(&clone_dir_name); - + // Ensure clean state let _ = fs::remove_dir_all(&clone_path); @@ -114,7 +108,7 @@ impl GitCloneTests { .args(["clone", &clone_url, clone_path.to_str().unwrap()]) .env("GIT_TERMINAL_PROMPT", "0") // Disable password prompts .output(); - + // Clean up on success or failure let cleanup = || { let _ = fs::remove_dir_all(&clone_path); @@ -129,7 +123,7 @@ impl GitCloneTests { "GRASP-01", "Repository must be cloneable via Git HTTP backend", ) - .fail(&format!("Failed to execute git clone: {}", e)) + .fail(format!("Failed to execute git clone: {}", e)); } }; @@ -141,7 +135,7 @@ impl GitCloneTests { "GRASP-01", "Repository must be cloneable via Git HTTP backend", ) - .fail(&format!("Git clone failed: {}", stderr)); + .fail(format!("Git clone failed: {}", stderr)); } // Verify clone succeeded by checking for .git directory @@ -169,10 +163,7 @@ impl GitCloneTests { /// This test verifies: /// 1. URLs follow the pattern http://domain/npub/identifier.git /// 2. Invalid URLs are rejected properly - pub async fn test_clone_url_format( - client: &AuditClient, - relay_domain: &str, - ) -> TestResult { + pub async fn test_clone_url_format(client: &AuditClient, relay_domain: &str) -> TestResult { let test_name = "test_clone_url_format"; let ctx = TestContext::new(client); @@ -185,7 +176,7 @@ impl GitCloneTests { "GRASP-01", "Clone URL must follow correct format", ) - .fail(&format!("Failed to create repo fixture: {}", e)) + .fail(format!("Failed to create repo fixture: {}", e)) } }; @@ -205,7 +196,7 @@ impl GitCloneTests { // Test valid URL format let valid_url = format!("http://{}/{}/{}.git", relay_domain, npub, repo_id); - + // Verify URL contains expected components if !valid_url.contains(&npub) { return TestResult::new( @@ -229,10 +220,10 @@ impl GitCloneTests { let temp_base = std::env::temp_dir(); let clone_dir_name = format!("grasp-test-invalid-{}", uuid::Uuid::new_v4()); let clone_path = temp_base.join(&clone_dir_name); - + // Ensure clean state let _ = fs::remove_dir_all(&clone_path); - + let invalid_url = format!("http://{}/invalid/path", relay_domain); let output = Command::new("git") @@ -287,7 +278,7 @@ impl GitCloneTests { "GRASP-01", "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", ) - .fail(&format!("Failed to create repo fixture: {}", e)) + .fail(format!("Failed to create repo fixture: {}", e)) } }; @@ -320,7 +311,7 @@ impl GitCloneTests { "GRASP-01", "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", ) - .fail(&format!("Failed to convert pubkey to npub: {}", e)) + .fail(format!("Failed to convert pubkey to npub: {}", e)) } }; @@ -340,7 +331,7 @@ impl GitCloneTests { "GRASP-01", "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", ) - .fail(&format!("HTTP request failed: {}", e)) + .fail(format!("HTTP request failed: {}", e)) } }; @@ -350,7 +341,7 @@ impl GitCloneTests { "GRASP-01", "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", ) - .fail(&format!( + .fail(format!( "info/refs request failed with status: {}", response.status() )); @@ -365,7 +356,7 @@ impl GitCloneTests { "GRASP-01", "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", ) - .fail(&format!("Failed to read response body: {}", e)) + .fail(format!("Failed to read response body: {}", e)) } }; @@ -399,12 +390,3 @@ impl GitCloneTests { .pass() } } - -#[cfg(test)] -mod tests { - #[test] - fn test_module_exists() { - // Simple compilation test - assert!(true); - } -} \ No newline at end of file diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs index 6f58b96..e16a351 100644 --- a/grasp-audit/src/specs/grasp01/mod.rs +++ b/grasp-audit/src/specs/grasp01/mod.rs @@ -26,4 +26,4 @@ pub use git_clone::GitCloneTests; pub use nip01_smoke::Nip01SmokeTests; pub use nip11_document::Nip11DocumentTests; pub use push_authorization::PushAuthorizationTests; -pub use repository_creation::{RepositoryCreationTests}; +pub use repository_creation::RepositoryCreationTests; diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs index b16d61a..5161da8 100644 --- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs +++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs @@ -163,27 +163,23 @@ impl Nip01SmokeTests { /// Spec: NIP-01 CLOSE message /// Requirement: Relay MUST support CLOSE to end subscriptions pub async fn test_close_subscription(client: &AuditClient) -> TestResult { - TestResult::new( - "close_subscription", - "NIP-01", - "Can close subscriptions", - ) - .run(|| async { - // For now, we just verify we can query events - // Full subscription management with CLOSE would require - // lower-level WebSocket access + TestResult::new("close_subscription", "NIP-01", "Can close subscriptions") + .run(|| async { + // For now, we just verify we can query events + // Full subscription management with CLOSE would require + // lower-level WebSocket access - let filter = Filter::new().kind(Kind::TextNote).limit(1); + let filter = Filter::new().kind(Kind::TextNote).limit(1); - let _events = client - .subscribe(vec![filter], Some(std::time::Duration::from_secs(2))) - .await - .map_err(|e| format!("Failed to subscribe: {}", e))?; + let _events = client + .subscribe(vec![filter], Some(std::time::Duration::from_secs(2))) + .await + .map_err(|e| format!("Failed to subscribe: {}", e))?; - // If we got here, subscription worked - Ok(()) - }) - .await + // If we got here, subscription worked + Ok(()) + }) + .await } /// Test 5: Rejects events with invalid signatures diff --git a/grasp-audit/src/specs/grasp01/nip11_document.rs b/grasp-audit/src/specs/grasp01/nip11_document.rs index bb864f2..51b147d 100644 --- a/grasp-audit/src/specs/grasp01/nip11_document.rs +++ b/grasp-audit/src/specs/grasp01/nip11_document.rs @@ -42,7 +42,9 @@ impl Nip11DocumentTests { ) .run(|| async { // 1. Extract HTTP(S) URL from client's WebSocket URL - let ws_url = client.relay_url().await + let ws_url = client + .relay_url() + .await .map_err(|e| format!("Failed to get relay URL: {}", e))?; let http_url = AuditClient::ws_to_http_url(&ws_url) .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?; @@ -66,16 +68,18 @@ impl Nip11DocumentTests { } // 4. Verify response is valid JSON - let json_text = response.text().await + let json_text = response + .text() + .await .map_err(|e| format!("Failed to read response body: {}", e))?; - + let doc: serde_json::Value = serde_json::from_str(&json_text) .map_err(|e| format!("Response is not valid JSON: {}", e))?; // 5. Verify has required NIP-11 fields let required_fields = ["name", "description", "software", "version"]; for field in &required_fields { - if !doc.get(field).is_some() { + if doc.get(field).is_none() { return Err(format!("Missing required NIP-11 field: {}", field)); } } @@ -97,7 +101,9 @@ impl Nip11DocumentTests { ) .run(|| async { // 1. Fetch NIP-11 document - let ws_url = client.relay_url().await + let ws_url = client + .relay_url() + .await .map_err(|e| format!("Failed to get relay URL: {}", e))?; let http_url = AuditClient::ws_to_http_url(&ws_url) .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?; @@ -110,18 +116,22 @@ impl Nip11DocumentTests { .await .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?; - let json_text = response.text().await + let json_text = response + .text() + .await .map_err(|e| format!("Failed to read response body: {}", e))?; - + let doc: serde_json::Value = serde_json::from_str(&json_text) .map_err(|e| format!("Response is not valid JSON: {}", e))?; // 2. Verify `supported_grasps` field exists - let supported_grasps = doc.get("supported_grasps") + let supported_grasps = doc + .get("supported_grasps") .ok_or_else(|| "Missing required field: supported_grasps".to_string())?; // 3. Verify it's a JSON array - let grasps_array = supported_grasps.as_array() + let grasps_array = supported_grasps + .as_array() .ok_or_else(|| "supported_grasps must be an array".to_string())?; // 4. Verify array includes "GRASP-01" @@ -140,7 +150,7 @@ impl Nip11DocumentTests { // 5. Verify format: each entry should match pattern "GRASP-\d{2}" let grasp_pattern = regex::Regex::new(r"^GRASP-\d{2}$") .map_err(|e| format!("Failed to compile regex: {}", e))?; - + for grasp in &grasp_strings { if !grasp_pattern.is_match(grasp) { return Err(format!( @@ -167,7 +177,9 @@ impl Nip11DocumentTests { ) .run(|| async { // 1. Fetch NIP-11 document - let ws_url = client.relay_url().await + let ws_url = client + .relay_url() + .await .map_err(|e| format!("Failed to get relay URL: {}", e))?; let http_url = AuditClient::ws_to_http_url(&ws_url) .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?; @@ -180,18 +192,22 @@ impl Nip11DocumentTests { .await .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?; - let json_text = response.text().await + let json_text = response + .text() + .await .map_err(|e| format!("Failed to read response body: {}", e))?; - + let doc: serde_json::Value = serde_json::from_str(&json_text) .map_err(|e| format!("Response is not valid JSON: {}", e))?; // 2. Verify `repo_acceptance_criteria` field exists - let criteria = doc.get("repo_acceptance_criteria") + let criteria = doc + .get("repo_acceptance_criteria") .ok_or_else(|| "Missing required field: repo_acceptance_criteria".to_string())?; // 3. Verify it's a string - let criteria_str = criteria.as_str() + let criteria_str = criteria + .as_str() .ok_or_else(|| "repo_acceptance_criteria must be a string".to_string())?; // 4. Verify non-empty @@ -216,7 +232,9 @@ impl Nip11DocumentTests { ) .run(|| async { // 1. Fetch NIP-11 document - let ws_url = client.relay_url().await + let ws_url = client + .relay_url() + .await .map_err(|e| format!("Failed to get relay URL: {}", e))?; let http_url = AuditClient::ws_to_http_url(&ws_url) .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?; @@ -229,16 +247,19 @@ impl Nip11DocumentTests { .await .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?; - let json_text = response.text().await + let json_text = response + .text() + .await .map_err(|e| format!("Failed to read response body: {}", e))?; - + let doc: serde_json::Value = serde_json::from_str(&json_text) .map_err(|e| format!("Response is not valid JSON: {}", e))?; // 2. Check if `curation` field exists if let Some(curation) = doc.get("curation") { // 3. If present: verify it's a non-empty string - let curation_str = curation.as_str() + let curation_str = curation + .as_str() .ok_or_else(|| "curation field must be a string when present".to_string())?; if curation_str.trim().is_empty() { @@ -284,4 +305,4 @@ mod tests { // Don't assert all passed yet - tests not implemented // assert!(results.all_passed(), "Some GRASP-01 NIP-11 document tests failed"); } -} \ No newline at end of file +} diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index d8652ae..24eae1d 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs @@ -351,10 +351,7 @@ async fn setup_repo_with_wrong_commit_pushed( /// IMPORTANT: We must publish the EXACT same event that was used during setup, /// otherwise the event ID won't match the refs/nostr/ ref that was pushed. #[allow(dead_code)] -async fn publish_pr_event_and_wait( - ctx: &TestContext<'_>, - pr_event: &Event, -) -> Result<(), String> { +async fn publish_pr_event_and_wait(ctx: &TestContext<'_>, pr_event: &Event) -> Result<(), String> { // Publish the exact same PR event that was created during setup ctx.client() .send_event(pr_event.clone()) @@ -1892,11 +1889,6 @@ impl PushAuthorizationTests { mod tests { use super::*; - #[test] - fn test_module_exists() { - assert!(true); - } - /// Test to discover the PR test commit hash /// /// This test creates a deterministic commit with PR-specific parameters diff --git a/grasp-audit/src/specs/grasp01/repository_creation.rs b/grasp-audit/src/specs/grasp01/repository_creation.rs index 63b3dee..0b3eed5 100644 --- a/grasp-audit/src/specs/grasp01/repository_creation.rs +++ b/grasp-audit/src/specs/grasp01/repository_creation.rs @@ -23,10 +23,7 @@ pub struct RepositoryCreationTests; impl RepositoryCreationTests { /// Run all repository creation tests - pub async fn run_all( - client: &AuditClient, - relay_domain: &str, - ) -> crate::AuditResult { + pub async fn run_all(client: &AuditClient, relay_domain: &str) -> crate::AuditResult { let mut results = crate::AuditResult::new("GRASP-01 Repository Creation Tests"); results.add(Self::test_bare_repo_created_on_announcement(client, relay_domain).await); @@ -58,7 +55,7 @@ impl RepositoryCreationTests { "GRASP-01", "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", ) - .fail(&format!("Failed to create repo fixture: {}", e)) + .fail(format!("Failed to create repo fixture: {}", e)) } }; @@ -91,7 +88,7 @@ impl RepositoryCreationTests { "GRASP-01", "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", ) - .fail(&format!("Failed to convert pubkey to npub: {}", e)) + .fail(format!("Failed to convert pubkey to npub: {}", e)) } }; @@ -102,7 +99,7 @@ impl RepositoryCreationTests { "GRASP-01", "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", ) - .fail(&format!("Repository not accessible via HTTP: {}", e)); + .fail(format!("Repository not accessible via HTTP: {}", e)); } TestResult::new( @@ -159,12 +156,3 @@ async fn check_repo_accessible_via_http( Ok(()) } - -#[cfg(test)] -mod tests { - #[test] - fn test_module_exists() { - // Simple compilation test - assert!(true); - } -} diff --git a/src/config.rs b/src/config.rs index f04b7d8..9b0d0b8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,8 +5,10 @@ use std::env; /// Database backend type for the relay #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum DatabaseBackend { /// In-memory database (default, fastest, no persistence) + #[default] Memory, /// NostrDB backend (persistent, optimized for Nostr) NostrDb, @@ -14,12 +16,6 @@ pub enum DatabaseBackend { Lmdb, } -impl Default for DatabaseBackend { - fn default() -> Self { - Self::Memory - } -} - impl std::str::FromStr for DatabaseBackend { type Err = anyhow::Error; diff --git a/src/git/handlers.rs b/src/git/handlers.rs index 00f2449..e84cabb 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -5,7 +5,6 @@ use http_body_util::Full; use hyper::{body::Bytes, Response, StatusCode}; use nostr_relay_builder::prelude::MemoryDatabase; -use nostr_sdk::EventId; use std::path::PathBuf; use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; diff --git a/src/git/mod.rs b/src/git/mod.rs index 494f8b9..a783782 100644 --- a/src/git/mod.rs +++ b/src/git/mod.rs @@ -306,11 +306,7 @@ pub fn parse_git_url(path: &str) -> Option<(&str, &str, &str)> { let subpath = parts[2]; // Extract identifier (remove .git suffix if present for the middle part) - let identifier = if repo_part.ends_with(".git") { - &repo_part[..repo_part.len() - 4] - } else { - repo_part - }; + let identifier = repo_part.strip_suffix(".git").unwrap_or(repo_part); Some((npub, identifier, subpath)) } @@ -343,7 +339,12 @@ mod tests { // Initialize bare repository Command::new("git") - .args(["init", "--bare", "--initial-branch=main", bare_repo.to_str().unwrap()]) + .args([ + "init", + "--bare", + "--initial-branch=main", + bare_repo.to_str().unwrap(), + ]) .output() .unwrap(); diff --git a/src/git/protocol.rs b/src/git/protocol.rs index 93177de..8592c27 100644 --- a/src/git/protocol.rs +++ b/src/git/protocol.rs @@ -55,11 +55,11 @@ impl PktLine { return Err(ProtocolError::InsufficientData); } - let len_str = std::str::from_utf8(&input[0..4]) - .map_err(|_| ProtocolError::InvalidLength)?; - - let len = u16::from_str_radix(len_str, 16) - .map_err(|_| ProtocolError::InvalidLength)? as usize; + let len_str = + std::str::from_utf8(&input[0..4]).map_err(|_| ProtocolError::InvalidLength)?; + + let len = + u16::from_str_radix(len_str, 16).map_err(|_| ProtocolError::InvalidLength)? as usize; if len == 0 { // Flush packet @@ -81,19 +81,19 @@ impl PktLine { /// Parse all pkt-lines from bytes pub fn parse_all(mut input: &[u8]) -> Result, ProtocolError> { let mut packets = Vec::new(); - + while !input.is_empty() { let (packet, remaining) = Self::parse(input)?; let is_flush = matches!(packet, PktLine::Flush); packets.push(packet); input = remaining; - + // Stop at flush packet if is_flush { break; } } - + Ok(packets) } } @@ -259,4 +259,4 @@ mod tests { "application/x-git-upload-pack-result" ); } -} \ No newline at end of file +} diff --git a/src/git/subprocess.rs b/src/git/subprocess.rs index c95bce5..2d9a981 100644 --- a/src/git/subprocess.rs +++ b/src/git/subprocess.rs @@ -28,9 +28,9 @@ impl GitSubprocess { advertise: bool, ) -> std::io::Result { let repo_path = repo_path.as_ref(); - + let mut cmd = Command::new("git"); - + // GRASP-01 requirement: MUST include `allow-reachable-sha1-in-want` and // `allow-tip-sha1-in-want` in advertisement and serve available oids. // These config options must be passed before the command name. @@ -38,22 +38,22 @@ impl GitSubprocess { cmd.arg("uploadpack.allowReachableSHA1InWant=true"); cmd.arg("-c"); cmd.arg("uploadpack.allowTipSHA1InWant=true"); - + cmd.arg(service.command_name()); - + if advertise { cmd.arg("--advertise-refs"); } - + cmd.arg("--stateless-rpc"); cmd.arg(repo_path); - + cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); - + let child = cmd.spawn()?; - + Ok(Self { child }) } @@ -101,8 +101,8 @@ impl GitSubprocess { #[cfg(test)] mod tests { use super::*; - use tempfile::TempDir; use std::process::Command as StdCommand; + use tempfile::TempDir; fn create_bare_repo() -> TempDir { let dir = TempDir::new().unwrap(); @@ -118,11 +118,8 @@ mod tests { #[tokio::test] async fn test_spawn_upload_pack_advertise() { let repo = create_bare_repo(); - let mut proc = GitSubprocess::spawn( - GitService::UploadPack, - repo.path(), - true, - ).expect("Failed to spawn git"); + let mut proc = GitSubprocess::spawn(GitService::UploadPack, repo.path(), true) + .expect("Failed to spawn git"); // Should have spawned successfully assert!(proc.stdout().is_some()); @@ -135,15 +132,12 @@ mod tests { #[tokio::test] async fn test_spawn_receive_pack() { let repo = create_bare_repo(); - let mut proc = GitSubprocess::spawn( - GitService::ReceivePack, - repo.path(), - false, - ).expect("Failed to spawn git"); + let mut proc = GitSubprocess::spawn(GitService::ReceivePack, repo.path(), false) + .expect("Failed to spawn git"); assert!(proc.stdout().is_some()); assert!(proc.stdin().is_some()); let _ = proc.kill().await; } -} \ No newline at end of file +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 07b47ee..f43cf86 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -9,20 +9,20 @@ use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; +use base64::Engine; +use http_body_util::{BodyExt, Full}; use hyper::body::{Bytes, Incoming}; use hyper::header::{CONNECTION, SEC_WEBSOCKET_ACCEPT, UPGRADE}; use hyper::server::conn::http1; use hyper::service::Service; use hyper::{Method, Request, Response}; use hyper_util::rt::TokioIo; -use http_body_util::{BodyExt, Full}; +use nostr_relay_builder::prelude::MemoryDatabase; +use nostr_relay_builder::LocalRelay; use nostr_sdk::hashes::sha1::Hash as Sha1Hash; use nostr_sdk::hashes::{Hash, HashEngine}; use nostr_sdk::PublicKey; -use nostr_relay_builder::prelude::MemoryDatabase; -use nostr_relay_builder::LocalRelay; use tokio::net::TcpListener; -use base64::Engine; use crate::config::Config; use crate::git; @@ -50,7 +50,12 @@ struct HttpService { } impl HttpService { - fn new(relay: LocalRelay, config: Config, remote: SocketAddr, database: Arc) -> Self { + fn new( + relay: LocalRelay, + config: Config, + remote: SocketAddr, + database: Arc, + ) -> Self { Self { relay, config, @@ -77,10 +82,12 @@ impl Service> for HttpService { // GRASP-01 spec line 47: Respond to OPTIONS with 204 No Content if method == Method::OPTIONS { return Box::pin(async move { - Ok(add_cors_headers(Response::builder().header("server", "ngit-grasp")) - .status(204) - .body(Full::new(Bytes::new())) - .unwrap()) + Ok( + add_cors_headers(Response::builder().header("server", "ngit-grasp")) + .status(204) + .body(Full::new(Bytes::new())) + .unwrap(), + ) }); } @@ -89,41 +96,47 @@ impl Service> for HttpService { let npub = npub.to_string(); let identifier = identifier.to_string(); let subpath = subpath.to_string(); - - tracing::debug!("Git request: {} {} (npub={}, id={}, subpath={})", - method, path, npub, identifier, subpath); + + tracing::debug!( + "Git request: {} {} (npub={}, id={}, subpath={})", + method, + path, + npub, + identifier, + subpath + ); let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier); return Box::pin(async move { // Collect request body once before the match statement - let body_bytes = req.collect().await + let body_bytes = req + .collect() + .await .map(|collected| collected.to_bytes()) .unwrap_or_else(|_| Bytes::new()); - + let result = match (method.as_ref(), subpath.as_str()) { // GET /info/refs?service=git-upload-pack or git-receive-pack (m, sp) if m == Method::GET && sp.starts_with("info/refs") => { // Parse query string for service parameter - let service = query.as_deref().unwrap_or("") + let service = query + .as_deref() + .unwrap_or("") .strip_prefix("service=") .and_then(git::protocol::GitService::from_query_param); match service { - Some(svc) => { - git::handlers::handle_info_refs(repo_path, svc).await - } - None => { - Err(git::handlers::GitError::RepositoryNotFound) - } + Some(svc) => git::handlers::handle_info_refs(repo_path, svc).await, + None => Err(git::handlers::GitError::RepositoryNotFound), } } - + // POST /git-upload-pack (clone/fetch) (m, "git-upload-pack") if m == Method::POST => { git::handlers::handle_upload_pack(repo_path, body_bytes).await } - + // POST /git-receive-pack (push) - with GRASP authorization via database (m, "git-receive-pack") if m == Method::POST => { // Convert npub (bech32) to hex pubkey for authorization @@ -137,33 +150,41 @@ impl Service> for HttpService { .unwrap()); } }; - + git::handlers::handle_receive_pack( repo_path, body_bytes.clone(), Some(database.clone()), &identifier, &owner_pubkey_hex, - ).await - } - - _ => { - Err(git::handlers::GitError::RepositoryNotFound) + ) + .await } + + _ => Err(git::handlers::GitError::RepositoryNotFound), }; match result { Ok(response) => { // Add CORS headers to successful Git responses let (parts, body) = response.into_parts(); - Ok(add_cors_headers(Response::builder() - .status(parts.status)) - .header("content-type", parts.headers.get("content-type") - .and_then(|v| v.to_str().ok()) - .unwrap_or("application/octet-stream")) - .header("cache-control", parts.headers.get("cache-control") - .and_then(|v| v.to_str().ok()) - .unwrap_or("no-cache")) + Ok(add_cors_headers(Response::builder().status(parts.status)) + .header( + "content-type", + parts + .headers + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"), + ) + .header( + "cache-control", + parts + .headers + .get("cache-control") + .and_then(|v| v.to_str().ok()) + .unwrap_or("no-cache"), + ) .body(body) .unwrap()) } @@ -191,15 +212,20 @@ impl Service> for HttpService { tracing::error!("Failed to serialize NIP-11 document: {}", e); "{}".to_string() }); - - tracing::debug!("Serving NIP-11 relay information document to {}", self.remote); - + + tracing::debug!( + "Serving NIP-11 relay information document to {}", + self.remote + ); + return Box::pin(async move { - Ok(add_cors_headers(Response::builder().header("server", "ngit-grasp")) - .status(200) - .header("content-type", "application/nostr+json") - .body(Full::new(Bytes::from(json))) - .unwrap()) + Ok( + add_cors_headers(Response::builder().header("server", "ngit-grasp")) + .status(200) + .header("content-type", "application/nostr+json") + .body(Full::new(Bytes::from(json))) + .unwrap(), + ) }); } } @@ -221,12 +247,13 @@ impl Service> for HttpService { let addr = self.remote; let relay = self.relay.clone(); - + tokio::spawn(async move { match hyper::upgrade::on(req).await { Ok(upgraded) => { tracing::info!("WebSocket connection established from {}", addr); - if let Err(e) = relay.take_connection(TokioIo::new(upgraded), addr).await + if let Err(e) = + relay.take_connection(TokioIo::new(upgraded), addr).await { tracing::error!("Relay error for {}: {}", addr, e); } @@ -288,12 +315,12 @@ pub async fn run_server( tracing::info!("Domain: {}", config.domain); let listener = TcpListener::bind(&bind_addr).await?; - + loop { let (socket, addr) = listener.accept().await?; let io = TokioIo::new(socket); let service = HttpService::new(relay.clone(), config.clone(), addr, database.clone()); - + tokio::spawn(async move { if let Err(e) = http1::Builder::new() .serve_connection(io, service) diff --git a/src/http/nip11.rs b/src/http/nip11.rs index a93ee5f..593ef9a 100644 --- a/src/http/nip11.rs +++ b/src/http/nip11.rs @@ -1,10 +1,9 @@ +use crate::config::Config; /// NIP-11 Relay Information Document /// /// Implements NIP-11 relay information endpoint with GRASP-01 extensions. /// See: https://github.com/nostr-protocol/nips/blob/master/11.md - use serde::{Deserialize, Serialize}; -use crate::config::Config; /// NIP-11 Relay Information Document /// @@ -14,37 +13,36 @@ use crate::config::Config; pub struct RelayInformationDocument { /// Relay name pub name: String, - + /// Relay description pub description: String, - + /// Relay owner's public key (hex format) #[serde(skip_serializing_if = "Option::is_none")] pub pubkey: Option, - + /// Contact information for relay admin #[serde(skip_serializing_if = "Option::is_none")] pub contact: Option, - + /// List of NIPs supported by this relay pub supported_nips: Vec, - + /// Relay software identifier pub software: String, - + /// Software version pub version: String, - + // GRASP-01 Extensions (lines 11-14 of GRASP-01 spec) - /// List of supported GRASPs (e.g., ["GRASP-01"]) /// Required by GRASP-01 specification line 12 pub supported_grasps: Vec, - + /// Repository acceptance criteria description /// Required by GRASP-01 specification line 13 pub repo_acceptance_criteria: String, - + /// Curation policy (present if curated, absent otherwise) /// Optional per GRASP-01 specification line 14 #[serde(skip_serializing_if = "Option::is_none")] @@ -66,7 +64,7 @@ impl RelayInformationDocument { ], software: env!("CARGO_PKG_NAME").to_string(), version: env!("CARGO_PKG_VERSION").to_string(), - + // GRASP-01 Extensions supported_grasps: vec!["GRASP-01".to_string()], repo_acceptance_criteria: format!( @@ -77,7 +75,7 @@ impl RelayInformationDocument { curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy } } - + /// Serialize to JSON string pub fn to_json(&self) -> Result { serde_json::to_string_pretty(self) @@ -102,7 +100,7 @@ mod tests { }; let doc = RelayInformationDocument::from_config(&config); - + assert_eq!(doc.name, "Test Relay"); assert_eq!(doc.description, "A test relay"); assert_eq!(doc.pubkey, Some("npub1test".to_string())); @@ -129,7 +127,7 @@ mod tests { let doc = RelayInformationDocument::from_config(&config); let json = doc.to_json().expect("Failed to serialize to JSON"); - + // Verify JSON contains expected fields assert!(json.contains("\"name\"")); assert!(json.contains("\"description\"")); @@ -137,10 +135,10 @@ mod tests { assert!(json.contains("\"supported_grasps\"")); assert!(json.contains("\"repo_acceptance_criteria\"")); assert!(json.contains("GRASP-01")); - + // Verify it's valid JSON by parsing let parsed: serde_json::Value = serde_json::from_str(&json).expect("Invalid JSON"); assert_eq!(parsed["name"], "Test Relay"); assert_eq!(parsed["supported_grasps"][0], "GRASP-01"); } -} \ No newline at end of file +} diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 8e9926a..97fd17e 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs @@ -51,7 +51,7 @@ impl Nip34WritePolicy { /// Create a bare git repository if it doesn't exist /// Path format: //.git fn ensure_bare_repository(&self, announcement: &RepositoryAnnouncement) -> Result<(), String> { - let repo_path = self.git_data_path.join(&announcement.repo_path()); + let repo_path = self.git_data_path.join(announcement.repo_path()); // Check if repository already exists if repo_path.exists() { @@ -69,7 +69,7 @@ impl Nip34WritePolicy { // Initialize bare repository using git command let output = std::process::Command::new("git") - .args(&["init", "--bare", repo_path.to_str().unwrap()]) + .args(["init", "--bare", repo_path.to_str().unwrap()]) .output() .map_err(|e| format!("Failed to execute git init: {}", e))?; @@ -482,7 +482,7 @@ impl Nip34WritePolicy { }; // Build repository path - let repo_path = self.git_data_path.join(&announcement.repo_path()); + let repo_path = self.git_data_path.join(announcement.repo_path()); // Validate the ref match git::validate_nostr_ref(&repo_path, &event_id, &expected_commit) { @@ -631,8 +631,8 @@ impl Nip34WritePolicy { let kind_u16 = event.kind.as_u16(); // Check if this is any kind of replaceable event - let is_regular_replaceable = kind_u16 >= 10000 && kind_u16 < 20000; - let is_parameterized_replaceable = kind_u16 >= 30000 && kind_u16 < 40000; + let is_regular_replaceable = (10000..20000).contains(&kind_u16); + let is_parameterized_replaceable = (30000..40000).contains(&kind_u16); if is_regular_replaceable || is_parameterized_replaceable { // Build the appropriate address format based on event type @@ -669,7 +669,7 @@ impl Nip34WritePolicy { ]; for tag_type in &addressable_tags { - let filter = Filter::new().custom_tag(tag_type.clone(), address.clone()); + let filter = Filter::new().custom_tag(*tag_type, address.clone()); match database.query(filter).await { Ok(events) => { @@ -691,7 +691,7 @@ impl Nip34WritePolicy { ]; for tag_type in &event_id_tags { - let filter = Filter::new().custom_tag(tag_type.clone(), event_id_hex.clone()); + let filter = Filter::new().custom_tag(*tag_type, event_id_hex.clone()); match database.query(filter).await { Ok(events) => { diff --git a/src/nostr/events.rs b/src/nostr/events.rs index 6a62ccd..050bfdd 100644 --- a/src/nostr/events.rs +++ b/src/nostr/events.rs @@ -322,9 +322,9 @@ impl RepositoryState { /// Get the HEAD branch name (without refs/heads/ prefix) pub fn get_head_branch(&self) -> Option<&str> { - self.head.as_ref().and_then(|h| { - h.strip_prefix("refs/heads/") - }) + self.head + .as_ref() + .and_then(|h| h.strip_prefix("refs/heads/")) } /// Check if the HEAD commit is available in the git repository @@ -397,7 +397,7 @@ pub fn validate_state(event: &Event) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use nostr_sdk::{EventBuilder, Keys, Tag}; + use nostr_sdk::{EventBuilder, Keys}; fn create_test_keys() -> Keys { Keys::generate() @@ -618,7 +618,10 @@ mod tests { let announcement = RepositoryAnnouncement::from_event(event).unwrap(); assert_eq!(announcement.maintainers.len(), 1); - assert_eq!(announcement.maintainers[0], maintainer_keys.public_key().to_hex()); + assert_eq!( + announcement.maintainers[0], + maintainer_keys.public_key().to_hex() + ); } #[test] @@ -727,10 +730,7 @@ mod tests { let keys = create_test_keys(); let tags = vec![ - Tag::custom( - nostr_sdk::TagKind::d(), - vec!["test-repo".to_string()], - ), + Tag::custom(nostr_sdk::TagKind::d(), vec!["test-repo".to_string()]), Tag::custom( nostr_sdk::TagKind::Custom("refs/heads/main".into()), vec!["a1b2c3d4".to_string()], diff --git a/tests/common/relay.rs b/tests/common/relay.rs index 6b512cd..449b4cb 100644 --- a/tests/common/relay.rs +++ b/tests/common/relay.rs @@ -3,7 +3,6 @@ //! Provides automatic relay lifecycle management for integration tests. use nostr_sdk::ToBech32; -use std::path::PathBuf; use std::process::{Child, Command, Stdio}; use std::time::Duration; use tokio::time::sleep; @@ -16,7 +15,6 @@ pub struct TestRelay { process: Child, url: String, port: u16, - git_data_dir: tempfile::TempDir, } impl TestRelay { @@ -44,8 +42,8 @@ impl TestRelay { let url = format!("ws://127.0.0.1:{}", port); // Create temporary directory for git repositories - let git_data_dir = tempfile::tempdir() - .expect("Failed to create temporary git data directory"); + let git_data_dir = + tempfile::tempdir().expect("Failed to create temporary git data directory"); // Use the built binary directly (faster than cargo run) let binary_path = std::env::current_exe() @@ -58,7 +56,9 @@ impl TestRelay { // Generate a test owner npub (using a random keypair) let test_keys = nostr_sdk::Keys::generate(); - let test_npub = test_keys.public_key().to_bech32() + let test_npub = test_keys + .public_key() + .to_bech32() .expect("Failed to generate test npub"); // Start the relay process @@ -73,12 +73,7 @@ impl TestRelay { .spawn() .expect("Failed to start relay process"); - let relay = Self { - process, - url, - port, - git_data_dir, - }; + let relay = Self { process, url, port }; // Wait for relay to be ready relay.wait_for_ready().await; @@ -91,30 +86,11 @@ impl TestRelay { &self.url } - /// Get the relay port - pub fn port(&self) -> u16 { - self.port - } - /// Get the relay domain (host:port) pub fn domain(&self) -> String { format!("127.0.0.1:{}", self.port) } - /// Get the git data directory path - pub fn git_data_dir(&self) -> &std::path::Path { - self.git_data_dir.path() - } - - /// Get the expected repository path for a given npub and repo identifier - /// - /// Repositories are stored at: //.git - pub fn repo_path(&self, npub: &str, identifier: &str) -> PathBuf { - self.git_data_dir.path() - .join(npub) - .join(format!("{}.git", identifier)) - } - /// Wait for the relay to be ready to accept connections async fn wait_for_ready(&self) { let max_attempts = 50; // 5 seconds total @@ -183,15 +159,6 @@ impl Drop for TestRelay { mod tests { use super::*; - #[tokio::test] - #[ignore] // Requires relay binary to be built - async fn test_relay_lifecycle() { - let relay = TestRelay::start().await; - assert!(relay.url().starts_with("ws://127.0.0.1:")); - assert!(relay.port() > 0); - relay.stop().await; - } - #[test] fn test_find_free_port() { let port = TestRelay::find_free_port(); diff --git a/tests/cors.rs b/tests/cors.rs index a27c145..b5a0a87 100644 --- a/tests/cors.rs +++ b/tests/cors.rs @@ -55,39 +55,10 @@ macro_rules! isolated_cors_test { }; } -/// Macro for CORS tests that need git_data_dir (the full integration test) -macro_rules! isolated_cors_test_with_repo { - ($test_name:ident) => { - #[tokio::test] - async fn $test_name() { - let relay = TestRelay::start().await; - let config = AuditConfig::ci(); - let client = AuditClient::new(relay.url(), config) - .await - .expect("Failed to create audit client"); - - let result = CorsTests::$test_name( - &client, - &relay.domain(), - ) - .await; - - relay.stop().await; - - assert!( - result.passed, - "{} failed: {}", - stringify!($test_name), - result.error.as_deref().unwrap_or("unknown error") - ); - } - }; -} - // Generate isolated tests for all CORS tests isolated_cors_test!(test_cors_allow_origin); isolated_cors_test!(test_cors_allow_methods); isolated_cors_test!(test_cors_allow_headers); isolated_cors_test!(test_cors_options_preflight); -isolated_cors_test!(test_cors_on_real_repo); \ No newline at end of file +isolated_cors_test!(test_cors_on_real_repo); diff --git a/tests/git_clone.rs b/tests/git_clone.rs index ffb04a3..c8a91a2 100644 --- a/tests/git_clone.rs +++ b/tests/git_clone.rs @@ -42,11 +42,7 @@ macro_rules! isolated_test { .await .expect("Failed to create audit client"); - let result = GitCloneTests::$test_name( - &client, - &relay.domain(), - ) - .await; + let result = GitCloneTests::$test_name(&client, &relay.domain()).await; relay.stop().await; @@ -63,4 +59,4 @@ macro_rules! isolated_test { // Generate isolated tests for all git clone tests isolated_test!(test_basic_git_clone); isolated_test!(test_clone_url_format); -isolated_test!(test_sha1_capabilities_advertised); \ No newline at end of file +isolated_test!(test_sha1_capabilities_advertised); diff --git a/tests/nip11_document.rs b/tests/nip11_document.rs index da8e9ce..2104ad0 100644 --- a/tests/nip11_document.rs +++ b/tests/nip11_document.rs @@ -59,4 +59,4 @@ macro_rules! isolated_test { isolated_test!(test_nip11_document_exists); isolated_test!(test_nip11_supported_grasps_field); isolated_test!(test_nip11_repo_acceptance_criteria_field); -isolated_test!(test_nip11_curation_field); \ No newline at end of file +isolated_test!(test_nip11_curation_field); diff --git a/tests/nip34_announcements.rs b/tests/nip34_announcements.rs index 09d9c8f..2a83886 100644 --- a/tests/nip34_announcements.rs +++ b/tests/nip34_announcements.rs @@ -71,4 +71,4 @@ isolated_test!(test_accept_comment_referenced_in_comment); isolated_test!(test_accept_kind1_referenced_in_kind1); isolated_test!(test_reject_orphan_issue); isolated_test!(test_reject_orphan_kind1); -isolated_test!(test_reject_comment_quoting_other_repo); \ No newline at end of file +isolated_test!(test_reject_comment_quoting_other_repo); diff --git a/tests/push_authorization.rs b/tests/push_authorization.rs index 1f8e0ca..357fefb 100644 --- a/tests/push_authorization.rs +++ b/tests/push_authorization.rs @@ -43,10 +43,7 @@ macro_rules! isolated_push_test { .await .expect("Failed to create audit client"); - let result = PushAuthorizationTests::$test_name( - &client, - &relay.domain() - ).await; + let result = PushAuthorizationTests::$test_name(&client, &relay.domain()).await; relay.stop().await; @@ -70,4 +67,4 @@ isolated_push_test!(test_push_to_nostr_ref_with_invalid_event_id_rejected); isolated_push_test!(test_pr_push_to_nostr_ref_with_wrong_commit_accepted_before_event_received); isolated_push_test!(test_pr_event_published_removes_nostr_ref_at_incorrect_commit); isolated_push_test!(test_push_to_nostr_ref_with_wrong_commit_after_event_received_rejected); -isolated_push_test!(test_push_to_nostr_ref_with_correct_commit_after_event_received_accepted); \ No newline at end of file +isolated_push_test!(test_push_to_nostr_ref_with_correct_commit_after_event_received_accepted); -- cgit v1.2.3