diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 03:38:50 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 03:38:50 +0000 |
| commit | f41550ea1898be2ec6c4be205e4cad0085400313 (patch) | |
| tree | 00cc474031bf81fe382c6276e52fd769b275cd3f /grasp-audit/src/specs | |
| parent | 3f74ababf338d65ac5e29e7eb5541ce416b7fe75 (diff) | |
audit: stop checking git_data_directory
Diffstat (limited to 'grasp-audit/src/specs')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/cors.rs | 1 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/git_clone.rs | 45 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/mod.rs | 2 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 64 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/repository_creation.rs | 208 |
5 files changed, 82 insertions, 238 deletions
diff --git a/grasp-audit/src/specs/grasp01/cors.rs b/grasp-audit/src/specs/grasp01/cors.rs index 4e0513e..08c5ab6 100644 --- a/grasp-audit/src/specs/grasp01/cors.rs +++ b/grasp-audit/src/specs/grasp01/cors.rs | |||
| @@ -257,7 +257,6 @@ impl CorsTests { | |||
| 257 | /// For integration tests that want to test against real repositories | 257 | /// For integration tests that want to test against real repositories |
| 258 | pub async fn test_cors_on_real_repo( | 258 | pub async fn test_cors_on_real_repo( |
| 259 | client: &AuditClient, | 259 | client: &AuditClient, |
| 260 | _git_data_dir: &Path, | ||
| 261 | relay_domain: &str, | 260 | relay_domain: &str, |
| 262 | ) -> TestResult { | 261 | ) -> TestResult { |
| 263 | let test_name = "test_cors_on_real_repo"; | 262 | let test_name = "test_cors_on_real_repo"; |
diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs index 4666a40..9ee6ed7 100644 --- a/grasp-audit/src/specs/grasp01/git_clone.rs +++ b/grasp-audit/src/specs/grasp01/git_clone.rs | |||
| @@ -18,7 +18,6 @@ | |||
| 18 | use crate::{AuditClient, FixtureKind, TestContext, TestResult}; | 18 | use crate::{AuditClient, FixtureKind, TestContext, TestResult}; |
| 19 | use nostr_sdk::prelude::*; | 19 | use nostr_sdk::prelude::*; |
| 20 | use std::fs; | 20 | use std::fs; |
| 21 | use std::path::Path; | ||
| 22 | use std::process::Command; | 21 | use std::process::Command; |
| 23 | 22 | ||
| 24 | /// Test suite for Git clone operations | 23 | /// Test suite for Git clone operations |
| @@ -28,14 +27,13 @@ impl GitCloneTests { | |||
| 28 | /// Run all Git clone tests | 27 | /// Run all Git clone tests |
| 29 | pub async fn run_all( | 28 | pub async fn run_all( |
| 30 | client: &AuditClient, | 29 | client: &AuditClient, |
| 31 | git_data_dir: &Path, | ||
| 32 | relay_domain: &str, | 30 | relay_domain: &str, |
| 33 | ) -> crate::AuditResult { | 31 | ) -> crate::AuditResult { |
| 34 | let mut results = crate::AuditResult::new("GRASP-01 Git Clone Tests"); | 32 | let mut results = crate::AuditResult::new("GRASP-01 Git Clone Tests"); |
| 35 | 33 | ||
| 36 | results.add(Self::test_basic_git_clone(client, git_data_dir, relay_domain).await); | 34 | results.add(Self::test_basic_git_clone(client, relay_domain).await); |
| 37 | results.add(Self::test_clone_url_format(client, git_data_dir, relay_domain).await); | 35 | results.add(Self::test_clone_url_format(client, relay_domain).await); |
| 38 | results.add(Self::test_sha1_capabilities_advertised(client, git_data_dir, relay_domain).await); | 36 | results.add(Self::test_sha1_capabilities_advertised(client, relay_domain).await); |
| 39 | 37 | ||
| 40 | results | 38 | results |
| 41 | } | 39 | } |
| @@ -49,7 +47,6 @@ impl GitCloneTests { | |||
| 49 | /// 4. Verifies the clone succeeded | 47 | /// 4. Verifies the clone succeeded |
| 50 | pub async fn test_basic_git_clone( | 48 | pub async fn test_basic_git_clone( |
| 51 | client: &AuditClient, | 49 | client: &AuditClient, |
| 52 | git_data_dir: &Path, | ||
| 53 | relay_domain: &str, | 50 | relay_domain: &str, |
| 54 | ) -> TestResult { | 51 | ) -> TestResult { |
| 55 | let test_name = "test_basic_git_clone"; | 52 | let test_name = "test_basic_git_clone"; |
| @@ -101,20 +98,6 @@ impl GitCloneTests { | |||
| 101 | } | 98 | } |
| 102 | }; | 99 | }; |
| 103 | 100 | ||
| 104 | // Verify repository exists | ||
| 105 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 106 | if !repo_path.exists() { | ||
| 107 | return TestResult::new( | ||
| 108 | test_name, | ||
| 109 | "GRASP-01", | ||
| 110 | "Repository must be cloneable via Git HTTP backend", | ||
| 111 | ) | ||
| 112 | .fail(&format!( | ||
| 113 | "Repository not found at: {}", | ||
| 114 | repo_path.display() | ||
| 115 | )); | ||
| 116 | } | ||
| 117 | |||
| 118 | // Create a test clone directory using standard library | 101 | // Create a test clone directory using standard library |
| 119 | let temp_base = std::env::temp_dir(); | 102 | let temp_base = std::env::temp_dir(); |
| 120 | let clone_dir_name = format!("grasp-test-clone-{}", uuid::Uuid::new_v4()); | 103 | let clone_dir_name = format!("grasp-test-clone-{}", uuid::Uuid::new_v4()); |
| @@ -128,7 +111,7 @@ impl GitCloneTests { | |||
| 128 | 111 | ||
| 129 | // Attempt to clone the repository | 112 | // Attempt to clone the repository |
| 130 | let output = Command::new("git") | 113 | let output = Command::new("git") |
| 131 | .args(&["clone", &clone_url, clone_path.to_str().unwrap()]) | 114 | .args(["clone", &clone_url, clone_path.to_str().unwrap()]) |
| 132 | .env("GIT_TERMINAL_PROMPT", "0") // Disable password prompts | 115 | .env("GIT_TERMINAL_PROMPT", "0") // Disable password prompts |
| 133 | .output(); | 116 | .output(); |
| 134 | 117 | ||
| @@ -188,7 +171,6 @@ impl GitCloneTests { | |||
| 188 | /// 2. Invalid URLs are rejected properly | 171 | /// 2. Invalid URLs are rejected properly |
| 189 | pub async fn test_clone_url_format( | 172 | pub async fn test_clone_url_format( |
| 190 | client: &AuditClient, | 173 | client: &AuditClient, |
| 191 | _git_data_dir: &Path, | ||
| 192 | relay_domain: &str, | 174 | relay_domain: &str, |
| 193 | ) -> TestResult { | 175 | ) -> TestResult { |
| 194 | let test_name = "test_clone_url_format"; | 176 | let test_name = "test_clone_url_format"; |
| @@ -254,7 +236,7 @@ impl GitCloneTests { | |||
| 254 | let invalid_url = format!("http://{}/invalid/path", relay_domain); | 236 | let invalid_url = format!("http://{}/invalid/path", relay_domain); |
| 255 | 237 | ||
| 256 | let output = Command::new("git") | 238 | let output = Command::new("git") |
| 257 | .args(&["clone", &invalid_url, clone_path.to_str().unwrap()]) | 239 | .args(["clone", &invalid_url, clone_path.to_str().unwrap()]) |
| 258 | .env("GIT_TERMINAL_PROMPT", "0") | 240 | .env("GIT_TERMINAL_PROMPT", "0") |
| 259 | .output() | 241 | .output() |
| 260 | .unwrap(); | 242 | .unwrap(); |
| @@ -291,7 +273,6 @@ impl GitCloneTests { | |||
| 291 | /// 2. Both allow-reachable-sha1-in-want and allow-tip-sha1-in-want are present | 273 | /// 2. Both allow-reachable-sha1-in-want and allow-tip-sha1-in-want are present |
| 292 | pub async fn test_sha1_capabilities_advertised( | 274 | pub async fn test_sha1_capabilities_advertised( |
| 293 | client: &AuditClient, | 275 | client: &AuditClient, |
| 294 | git_data_dir: &Path, | ||
| 295 | relay_domain: &str, | 276 | relay_domain: &str, |
| 296 | ) -> TestResult { | 277 | ) -> TestResult { |
| 297 | let test_name = "test_sha1_capabilities_advertised"; | 278 | let test_name = "test_sha1_capabilities_advertised"; |
| @@ -343,20 +324,6 @@ impl GitCloneTests { | |||
| 343 | } | 324 | } |
| 344 | }; | 325 | }; |
| 345 | 326 | ||
| 346 | // Verify repository exists | ||
| 347 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 348 | if !repo_path.exists() { | ||
| 349 | return TestResult::new( | ||
| 350 | test_name, | ||
| 351 | "GRASP-01", | ||
| 352 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 353 | ) | ||
| 354 | .fail(&format!( | ||
| 355 | "Repository not found at: {}", | ||
| 356 | repo_path.display() | ||
| 357 | )); | ||
| 358 | } | ||
| 359 | |||
| 360 | // Build info/refs URL for git-upload-pack service | 327 | // Build info/refs URL for git-upload-pack service |
| 361 | let info_refs_url = format!( | 328 | let info_refs_url = format!( |
| 362 | "http://{}/{}/{}.git/info/refs?service=git-upload-pack", | 329 | "http://{}/{}/{}.git/info/refs?service=git-upload-pack", |
| @@ -435,8 +402,6 @@ impl GitCloneTests { | |||
| 435 | 402 | ||
| 436 | #[cfg(test)] | 403 | #[cfg(test)] |
| 437 | mod tests { | 404 | mod tests { |
| 438 | |||
| 439 | |||
| 440 | #[test] | 405 | #[test] |
| 441 | fn test_module_exists() { | 406 | fn test_module_exists() { |
| 442 | // Simple compilation test | 407 | // Simple compilation test |
diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs index b5471d1..6f58b96 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; | |||
| 26 | pub use nip01_smoke::Nip01SmokeTests; | 26 | pub use nip01_smoke::Nip01SmokeTests; |
| 27 | pub use nip11_document::Nip11DocumentTests; | 27 | pub use nip11_document::Nip11DocumentTests; |
| 28 | pub use push_authorization::PushAuthorizationTests; | 28 | pub use push_authorization::PushAuthorizationTests; |
| 29 | pub use repository_creation::{is_bare_repository, RepositoryCreationTests}; | 29 | pub use repository_creation::{RepositoryCreationTests}; |
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index 4599ea5..69664d6 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -33,15 +33,14 @@ impl PushAuthorizationTests { | |||
| 33 | /// Run all push authorization tests | 33 | /// Run all push authorization tests |
| 34 | pub async fn run_all( | 34 | pub async fn run_all( |
| 35 | client: &AuditClient, | 35 | client: &AuditClient, |
| 36 | git_data_dir: &Path, | ||
| 37 | relay_domain: &str, | 36 | relay_domain: &str, |
| 38 | ) -> crate::AuditResult { | 37 | ) -> crate::AuditResult { |
| 39 | let mut results = crate::AuditResult::new("GRASP-01 Push Authorization Tests"); | 38 | let mut results = crate::AuditResult::new("GRASP-01 Push Authorization Tests"); |
| 40 | 39 | ||
| 41 | results.add(Self::test_push_authorized_by_owner_state(client, git_data_dir, relay_domain).await); | 40 | results.add(Self::test_push_authorized_by_owner_state(client, relay_domain).await); |
| 42 | results.add(Self::test_push_rejected_without_state_event(client, git_data_dir, relay_domain).await); | 41 | results.add(Self::test_push_rejected_without_state_event(client, relay_domain).await); |
| 43 | results.add(Self::test_push_rejected_wrong_commit(client, git_data_dir, relay_domain).await); | 42 | results.add(Self::test_push_rejected_wrong_commit(client, relay_domain).await); |
| 44 | results.add(Self::test_push_authorized_by_maintainer_state_only(client, git_data_dir, relay_domain).await); | 43 | results.add(Self::test_push_authorized_by_maintainer_state_only(client, relay_domain).await); |
| 45 | 44 | ||
| 46 | results | 45 | results |
| 47 | } | 46 | } |
| @@ -59,7 +58,6 @@ impl PushAuthorizationTests { | |||
| 59 | /// 3. **Verify**: Push should succeed because state event authorizes this commit | 58 | /// 3. **Verify**: Push should succeed because state event authorizes this commit |
| 60 | pub async fn test_push_authorized_by_owner_state( | 59 | pub async fn test_push_authorized_by_owner_state( |
| 61 | client: &AuditClient, | 60 | client: &AuditClient, |
| 62 | git_data_dir: &Path, | ||
| 63 | relay_domain: &str, | 61 | relay_domain: &str, |
| 64 | ) -> TestResult { | 62 | ) -> TestResult { |
| 65 | use std::process::Command; | 63 | use std::process::Command; |
| @@ -103,13 +101,6 @@ impl PushAuthorizationTests { | |||
| 103 | } | 101 | } |
| 104 | }; | 102 | }; |
| 105 | 103 | ||
| 106 | // Verify repo exists on disk | ||
| 107 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 108 | if !repo_path.exists() { | ||
| 109 | return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") | ||
| 110 | .fail(&format!("Repo not found: {}", repo_path.display())); | ||
| 111 | } | ||
| 112 | |||
| 113 | // ============================================================ | 104 | // ============================================================ |
| 114 | // Step 2: SEND - Clone repo, create deterministic commit, push | 105 | // Step 2: SEND - Clone repo, create deterministic commit, push |
| 115 | // ============================================================ | 106 | // ============================================================ |
| @@ -222,7 +213,6 @@ impl PushAuthorizationTests { | |||
| 222 | /// Test that push is rejected when no state event exists | 213 | /// Test that push is rejected when no state event exists |
| 223 | pub async fn test_push_rejected_without_state_event( | 214 | pub async fn test_push_rejected_without_state_event( |
| 224 | client: &AuditClient, | 215 | client: &AuditClient, |
| 225 | git_data_dir: &Path, | ||
| 226 | relay_domain: &str, | 216 | relay_domain: &str, |
| 227 | ) -> TestResult { | 217 | ) -> TestResult { |
| 228 | let test_name = "test_push_rejected_without_state_event"; | 218 | let test_name = "test_push_rejected_without_state_event"; |
| @@ -243,12 +233,6 @@ impl PushAuthorizationTests { | |||
| 243 | .and_then(|t| t.content()).unwrap().to_string(); | 233 | .and_then(|t| t.content()).unwrap().to_string(); |
| 244 | let npub = repo.pubkey.to_bech32().unwrap(); | 234 | let npub = repo.pubkey.to_bech32().unwrap(); |
| 245 | 235 | ||
| 246 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 247 | if !repo_path.exists() { | ||
| 248 | return TestResult::new(test_name, "GRASP-01", "Push rejected without state event") | ||
| 249 | .fail(&format!("Repo not found: {}", repo_path.display())); | ||
| 250 | } | ||
| 251 | |||
| 252 | // Clone and create commit | 236 | // Clone and create commit |
| 253 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | 237 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { |
| 254 | Ok(p) => p, | 238 | Ok(p) => p, |
| @@ -286,7 +270,6 @@ impl PushAuthorizationTests { | |||
| 286 | /// 4. **Verify**: Push should be rejected because new commit doesn't match state event | 270 | /// 4. **Verify**: Push should be rejected because new commit doesn't match state event |
| 287 | pub async fn test_push_rejected_wrong_commit( | 271 | pub async fn test_push_rejected_wrong_commit( |
| 288 | client: &AuditClient, | 272 | client: &AuditClient, |
| 289 | git_data_dir: &Path, | ||
| 290 | relay_domain: &str, | 273 | relay_domain: &str, |
| 291 | ) -> TestResult { | 274 | ) -> TestResult { |
| 292 | use std::process::Command; | 275 | use std::process::Command; |
| @@ -330,13 +313,6 @@ impl PushAuthorizationTests { | |||
| 330 | } | 313 | } |
| 331 | }; | 314 | }; |
| 332 | 315 | ||
| 333 | // Verify repo exists on disk | ||
| 334 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 335 | if !repo_path.exists() { | ||
| 336 | return TestResult::new(test_name, "GRASP-01", "Push rejected when commit not in state event") | ||
| 337 | .fail(&format!("Repo not found: {}", repo_path.display())); | ||
| 338 | } | ||
| 339 | |||
| 340 | // ============================================================ | 316 | // ============================================================ |
| 341 | // Step 2: SEND - Clone repo, create deterministic commit, push | 317 | // Step 2: SEND - Clone repo, create deterministic commit, push |
| 342 | // (establishes the state on the relay) | 318 | // (establishes the state on the relay) |
| @@ -495,7 +471,6 @@ impl PushAuthorizationTests { | |||
| 495 | /// 4. The push should be ACCEPTED because maintainer's state event authorizes it | 471 | /// 4. The push should be ACCEPTED because maintainer's state event authorizes it |
| 496 | pub async fn test_push_authorized_by_maintainer_state_only( | 472 | pub async fn test_push_authorized_by_maintainer_state_only( |
| 497 | client: &AuditClient, | 473 | client: &AuditClient, |
| 498 | git_data_dir: &Path, | ||
| 499 | relay_domain: &str, | 474 | relay_domain: &str, |
| 500 | ) -> TestResult { | 475 | ) -> TestResult { |
| 501 | use std::process::Command; | 476 | use std::process::Command; |
| @@ -566,17 +541,6 @@ impl PushAuthorizationTests { | |||
| 566 | } | 541 | } |
| 567 | }; | 542 | }; |
| 568 | 543 | ||
| 569 | // Verify repo exists on disk | ||
| 570 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 571 | if !repo_path.exists() { | ||
| 572 | return TestResult::new( | ||
| 573 | test_name, | ||
| 574 | "GRASP-01", | ||
| 575 | "Push authorized by maintainer state event only (no announcement)", | ||
| 576 | ) | ||
| 577 | .fail(&format!("Repo not found: {}", repo_path.display())); | ||
| 578 | } | ||
| 579 | |||
| 580 | // ============================================================ | 544 | // ============================================================ |
| 581 | // Step 2: SEND - Clone, create maintainer commit, push | 545 | // Step 2: SEND - Clone, create maintainer commit, push |
| 582 | // ============================================================ | 546 | // ============================================================ |
| @@ -741,7 +705,6 @@ impl PushAuthorizationTests { | |||
| 741 | /// Each level publishes announcements that authorize the next level. | 705 | /// Each level publishes announcements that authorize the next level. |
| 742 | pub async fn test_push_authorized_by_recursive_maintainer_state( | 706 | pub async fn test_push_authorized_by_recursive_maintainer_state( |
| 743 | client: &AuditClient, | 707 | client: &AuditClient, |
| 744 | git_data_dir: &Path, | ||
| 745 | relay_domain: &str, | 708 | relay_domain: &str, |
| 746 | ) -> TestResult { | 709 | ) -> TestResult { |
| 747 | use std::process::Command; | 710 | use std::process::Command; |
| @@ -837,17 +800,6 @@ impl PushAuthorizationTests { | |||
| 837 | } | 800 | } |
| 838 | }; | 801 | }; |
| 839 | 802 | ||
| 840 | // Verify repo exists on disk | ||
| 841 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 842 | if !repo_path.exists() { | ||
| 843 | return TestResult::new( | ||
| 844 | test_name, | ||
| 845 | "GRASP-01", | ||
| 846 | "Push authorized by recursive maintainer state event", | ||
| 847 | ) | ||
| 848 | .fail(&format!("Repo not found: {}", repo_path.display())); | ||
| 849 | } | ||
| 850 | |||
| 851 | // ============================================================ | 803 | // ============================================================ |
| 852 | // Step 2: SEND - Clone, create recursive maintainer commit, push | 804 | // Step 2: SEND - Clone, create recursive maintainer commit, push |
| 853 | // ============================================================ | 805 | // ============================================================ |
| @@ -1007,7 +959,6 @@ impl PushAuthorizationTests { | |||
| 1007 | /// 5. **Verify**: Push should be rejected because rogue state event is ignored | 959 | /// 5. **Verify**: Push should be rejected because rogue state event is ignored |
| 1008 | pub async fn test_non_maintainer_state_rejected( | 960 | pub async fn test_non_maintainer_state_rejected( |
| 1009 | client: &AuditClient, | 961 | client: &AuditClient, |
| 1010 | git_data_dir: &Path, | ||
| 1011 | relay_domain: &str, | 962 | relay_domain: &str, |
| 1012 | ) -> TestResult { | 963 | ) -> TestResult { |
| 1013 | use std::process::Command; | 964 | use std::process::Command; |
| @@ -1051,13 +1002,6 @@ impl PushAuthorizationTests { | |||
| 1051 | } | 1002 | } |
| 1052 | }; | 1003 | }; |
| 1053 | 1004 | ||
| 1054 | // Verify repo exists on disk | ||
| 1055 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 1056 | if !repo_path.exists() { | ||
| 1057 | return TestResult::new(test_name, "GRASP-01", "Non-maintainer state events ignored") | ||
| 1058 | .fail(&format!("Repo not found: {}", repo_path.display())); | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | // ============================================================ | 1005 | // ============================================================ |
| 1062 | // Step 2: SEND - Clone repo, create deterministic commit, push | 1006 | // Step 2: SEND - Clone repo, create deterministic commit, push |
| 1063 | // (establishes the state on the relay) | 1007 | // (establishes the state on the relay) |
diff --git a/grasp-audit/src/specs/grasp01/repository_creation.rs b/grasp-audit/src/specs/grasp01/repository_creation.rs index 2eaf32f..588187b 100644 --- a/grasp-audit/src/specs/grasp01/repository_creation.rs +++ b/grasp-audit/src/specs/grasp01/repository_creation.rs | |||
| @@ -5,10 +5,9 @@ | |||
| 5 | //! | 5 | //! |
| 6 | //! ## Test Coverage | 6 | //! ## Test Coverage |
| 7 | //! | 7 | //! |
| 8 | //! - Repository creation on valid announcement | 8 | //! - Repository creation on valid announcement (verified via HTTP) |
| 9 | //! - Idempotent creation (no error if repo already exists) | 9 | //! - Idempotent creation (no error if repo already exists) |
| 10 | //! - Proper directory structure (<npub>/<identifier>.git) | 10 | //! - Repository accessibility via Smart HTTP service |
| 11 | //! - Bare repository validation (has HEAD, config, objects, refs) | ||
| 12 | //! | 11 | //! |
| 13 | //! ## Running Tests | 12 | //! ## Running Tests |
| 14 | //! | 13 | //! |
| @@ -18,7 +17,6 @@ | |||
| 18 | 17 | ||
| 19 | use crate::{AuditClient, FixtureKind, TestContext, TestResult}; | 18 | use crate::{AuditClient, FixtureKind, TestContext, TestResult}; |
| 20 | use nostr_sdk::prelude::*; | 19 | use nostr_sdk::prelude::*; |
| 21 | use std::path::Path; | ||
| 22 | 20 | ||
| 23 | /// Test suite for repository creation | 21 | /// Test suite for repository creation |
| 24 | pub struct RepositoryCreationTests; | 22 | pub struct RepositoryCreationTests; |
| @@ -27,13 +25,13 @@ impl RepositoryCreationTests { | |||
| 27 | /// Run all repository creation tests | 25 | /// Run all repository creation tests |
| 28 | pub async fn run_all( | 26 | pub async fn run_all( |
| 29 | client: &AuditClient, | 27 | client: &AuditClient, |
| 30 | git_data_dir: &Path, | 28 | relay_domain: &str, |
| 31 | ) -> crate::AuditResult { | 29 | ) -> crate::AuditResult { |
| 32 | let mut results = crate::AuditResult::new("GRASP-01 Repository Creation Tests"); | 30 | let mut results = crate::AuditResult::new("GRASP-01 Repository Creation Tests"); |
| 33 | 31 | ||
| 34 | results.add(Self::test_bare_repo_created_on_announcement(client, git_data_dir).await); | 32 | results.add(Self::test_bare_repo_created_on_announcement(client, relay_domain).await); |
| 35 | results.add(Self::test_repo_creation_idempotent(client, git_data_dir).await); | 33 | results.add(Self::test_repo_creation_idempotent(client, relay_domain).await); |
| 36 | results.add(Self::test_bare_repo_structure(client, git_data_dir).await); | 34 | results.add(Self::test_repo_accessible_via_http(client, relay_domain).await); |
| 37 | 35 | ||
| 38 | results | 36 | results |
| 39 | } | 37 | } |
| @@ -43,10 +41,10 @@ impl RepositoryCreationTests { | |||
| 43 | /// This test: | 41 | /// This test: |
| 44 | /// 1. Sends a valid repository announcement via TestContext | 42 | /// 1. Sends a valid repository announcement via TestContext |
| 45 | /// 2. Verifies the announcement was accepted | 43 | /// 2. Verifies the announcement was accepted |
| 46 | /// 3. Checks that a bare git repository was created at the expected path | 44 | /// 3. Verifies the repository is accessible via Smart HTTP service |
| 47 | pub async fn test_bare_repo_created_on_announcement( | 45 | pub async fn test_bare_repo_created_on_announcement( |
| 48 | client: &AuditClient, | 46 | client: &AuditClient, |
| 49 | git_data_dir: &Path, | 47 | relay_domain: &str, |
| 50 | ) -> TestResult { | 48 | ) -> TestResult { |
| 51 | let test_name = "test_bare_repo_created_on_announcement"; | 49 | let test_name = "test_bare_repo_created_on_announcement"; |
| 52 | let ctx = TestContext::new(client); | 50 | let ctx = TestContext::new(client); |
| @@ -97,19 +95,14 @@ impl RepositoryCreationTests { | |||
| 97 | } | 95 | } |
| 98 | }; | 96 | }; |
| 99 | 97 | ||
| 100 | // Check if repository was created | 98 | // Verify repository exists via HTTP (info/refs endpoint) |
| 101 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | 99 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { |
| 102 | |||
| 103 | if !is_bare_repository(&repo_path) { | ||
| 104 | return TestResult::new( | 100 | return TestResult::new( |
| 105 | test_name, | 101 | test_name, |
| 106 | "GRASP-01", | 102 | "GRASP-01", |
| 107 | "Bare repository must be created when announcement is accepted", | 103 | "Bare repository must be created when announcement is accepted", |
| 108 | ) | 104 | ) |
| 109 | .fail(&format!( | 105 | .fail(&format!("Repository not accessible via HTTP: {}", e)); |
| 110 | "Bare repository not found at: {}", | ||
| 111 | repo_path.display() | ||
| 112 | )); | ||
| 113 | } | 106 | } |
| 114 | 107 | ||
| 115 | TestResult::new( | 108 | TestResult::new( |
| @@ -128,7 +121,7 @@ impl RepositoryCreationTests { | |||
| 128 | /// 3. Verifies no error occurs and repo still exists | 121 | /// 3. Verifies no error occurs and repo still exists |
| 129 | pub async fn test_repo_creation_idempotent( | 122 | pub async fn test_repo_creation_idempotent( |
| 130 | client: &AuditClient, | 123 | client: &AuditClient, |
| 131 | git_data_dir: &Path, | 124 | relay_domain: &str, |
| 132 | ) -> TestResult { | 125 | ) -> TestResult { |
| 133 | let test_name = "test_repo_creation_idempotent"; | 126 | let test_name = "test_repo_creation_idempotent"; |
| 134 | let ctx = TestContext::new(client); | 127 | let ctx = TestContext::new(client); |
| @@ -162,7 +155,7 @@ impl RepositoryCreationTests { | |||
| 162 | // Wait again | 155 | // Wait again |
| 163 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | 156 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; |
| 164 | 157 | ||
| 165 | // Verify repository still exists and is valid | 158 | // Verify repository still exists and is accessible via HTTP |
| 166 | let repo_id = repo | 159 | let repo_id = repo |
| 167 | .tags | 160 | .tags |
| 168 | .iter() | 161 | .iter() |
| @@ -173,15 +166,14 @@ impl RepositoryCreationTests { | |||
| 173 | .to_string(); | 166 | .to_string(); |
| 174 | 167 | ||
| 175 | let npub = repo.pubkey.to_bech32().unwrap(); | 168 | let npub = repo.pubkey.to_bech32().unwrap(); |
| 176 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 177 | 169 | ||
| 178 | if !is_bare_repository(&repo_path) { | 170 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { |
| 179 | return TestResult::new( | 171 | return TestResult::new( |
| 180 | test_name, | 172 | test_name, |
| 181 | "GRASP-01", | 173 | "GRASP-01", |
| 182 | "Repository creation must be idempotent", | 174 | "Repository creation must be idempotent", |
| 183 | ) | 175 | ) |
| 184 | .fail("Repository not found after second send"); | 176 | .fail(&format!("Repository not accessible after second send: {}", e)); |
| 185 | } | 177 | } |
| 186 | 178 | ||
| 187 | TestResult::new( | 179 | TestResult::new( |
| @@ -192,14 +184,16 @@ impl RepositoryCreationTests { | |||
| 192 | .pass() | 184 | .pass() |
| 193 | } | 185 | } |
| 194 | 186 | ||
| 195 | /// Test that the repository has the correct structure | 187 | /// Test that the repository is accessible via Smart HTTP service |
| 196 | /// | 188 | /// |
| 197 | /// This test verifies: | 189 | /// This test verifies: |
| 198 | /// 1. Repository is at <git_data_path>/<npub>/<identifier>.git | 190 | /// 1. Repository responds to git-upload-pack service discovery |
| 199 | /// 2. Repository is bare (no working directory) | 191 | /// 2. URL format follows http://domain/npub/identifier.git pattern |
| 200 | /// 3. Repository has required git structure (HEAD, config, objects/, refs/) | 192 | pub async fn test_repo_accessible_via_http( |
| 201 | pub async fn test_bare_repo_structure(client: &AuditClient, git_data_dir: &Path) -> TestResult { | 193 | client: &AuditClient, |
| 202 | let test_name = "test_bare_repo_structure"; | 194 | relay_domain: &str, |
| 195 | ) -> TestResult { | ||
| 196 | let test_name = "test_repo_accessible_via_http"; | ||
| 203 | let ctx = TestContext::new(client); | 197 | let ctx = TestContext::new(client); |
| 204 | 198 | ||
| 205 | // Create and send repository announcement via TestContext | 199 | // Create and send repository announcement via TestContext |
| @@ -209,7 +203,7 @@ impl RepositoryCreationTests { | |||
| 209 | return TestResult::new( | 203 | return TestResult::new( |
| 210 | test_name, | 204 | test_name, |
| 211 | "GRASP-01", | 205 | "GRASP-01", |
| 212 | "Bare repository must have correct structure", | 206 | "Repository must be accessible via Smart HTTP service", |
| 213 | ) | 207 | ) |
| 214 | .fail(&format!("Failed to create repo fixture: {}", e)) | 208 | .fail(&format!("Failed to create repo fixture: {}", e)) |
| 215 | } | 209 | } |
| @@ -230,134 +224,76 @@ impl RepositoryCreationTests { | |||
| 230 | 224 | ||
| 231 | let npub = repo.pubkey.to_bech32().unwrap(); | 225 | let npub = repo.pubkey.to_bech32().unwrap(); |
| 232 | 226 | ||
| 233 | // Verify correct path structure: <git_data_path>/<npub>/<identifier>.git | 227 | // Verify repository is accessible via HTTP |
| 234 | let expected_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | 228 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { |
| 235 | |||
| 236 | if !expected_path.exists() { | ||
| 237 | return TestResult::new( | ||
| 238 | test_name, | ||
| 239 | "GRASP-01", | ||
| 240 | "Bare repository must have correct structure", | ||
| 241 | ) | ||
| 242 | .fail(&format!( | ||
| 243 | "Repository not at expected path: {}", | ||
| 244 | expected_path.display() | ||
| 245 | )); | ||
| 246 | } | ||
| 247 | |||
| 248 | // Verify it's a bare repository with correct structure | ||
| 249 | if !expected_path.join("HEAD").is_file() { | ||
| 250 | return TestResult::new( | ||
| 251 | test_name, | ||
| 252 | "GRASP-01", | ||
| 253 | "Bare repository must have correct structure", | ||
| 254 | ) | ||
| 255 | .fail("Missing HEAD file"); | ||
| 256 | } | ||
| 257 | |||
| 258 | if !expected_path.join("config").is_file() { | ||
| 259 | return TestResult::new( | ||
| 260 | test_name, | ||
| 261 | "GRASP-01", | ||
| 262 | "Bare repository must have correct structure", | ||
| 263 | ) | ||
| 264 | .fail("Missing config file"); | ||
| 265 | } | ||
| 266 | |||
| 267 | if !expected_path.join("objects").is_dir() { | ||
| 268 | return TestResult::new( | 229 | return TestResult::new( |
| 269 | test_name, | 230 | test_name, |
| 270 | "GRASP-01", | 231 | "GRASP-01", |
| 271 | "Bare repository must have correct structure", | 232 | "Repository must be accessible via Smart HTTP service", |
| 272 | ) | 233 | ) |
| 273 | .fail("Missing objects/ directory"); | 234 | .fail(&e); |
| 274 | } | ||
| 275 | |||
| 276 | if !expected_path.join("refs").is_dir() { | ||
| 277 | return TestResult::new( | ||
| 278 | test_name, | ||
| 279 | "GRASP-01", | ||
| 280 | "Bare repository must have correct structure", | ||
| 281 | ) | ||
| 282 | .fail("Missing refs/ directory"); | ||
| 283 | } | ||
| 284 | |||
| 285 | // Verify the helper function agrees | ||
| 286 | if !is_bare_repository(&expected_path) { | ||
| 287 | return TestResult::new( | ||
| 288 | test_name, | ||
| 289 | "GRASP-01", | ||
| 290 | "Bare repository must have correct structure", | ||
| 291 | ) | ||
| 292 | .fail("Helper function does not recognize repository as bare"); | ||
| 293 | } | 235 | } |
| 294 | 236 | ||
| 295 | TestResult::new( | 237 | TestResult::new( |
| 296 | test_name, | 238 | test_name, |
| 297 | "GRASP-01", | 239 | "GRASP-01", |
| 298 | "Bare repository must have correct structure", | 240 | "Repository must be accessible via Smart HTTP service", |
| 299 | ) | 241 | ) |
| 300 | .pass() | 242 | .pass() |
| 301 | } | 243 | } |
| 302 | } | 244 | } |
| 303 | 245 | ||
| 304 | /// Helper function to check if a path is a valid bare git repository | 246 | /// Helper function to check if a repository is accessible via Smart HTTP service |
| 305 | /// | 247 | /// |
| 306 | /// A bare repository must have: | 248 | /// Verifies that the repository responds correctly to git-upload-pack service discovery |
| 307 | /// - HEAD file | 249 | /// at the URL: http://domain/npub/identifier.git/info/refs?service=git-upload-pack |
| 308 | /// - config file | 250 | async fn check_repo_accessible_via_http( |
| 309 | /// - objects/ directory | 251 | relay_domain: &str, |
| 310 | /// - refs/ directory | 252 | npub: &str, |
| 311 | pub fn is_bare_repository(path: &Path) -> bool { | 253 | repo_id: &str, |
| 312 | if !path.exists() { | 254 | ) -> Result<(), String> { |
| 313 | return false; | 255 | let info_refs_url = format!( |
| 256 | "http://{}/{}/{}.git/info/refs?service=git-upload-pack", | ||
| 257 | relay_domain, npub, repo_id | ||
| 258 | ); | ||
| 259 | |||
| 260 | let http_client = reqwest::Client::new(); | ||
| 261 | let response = http_client | ||
| 262 | .get(&info_refs_url) | ||
| 263 | .send() | ||
| 264 | .await | ||
| 265 | .map_err(|e| format!("HTTP request failed: {}", e))?; | ||
| 266 | |||
| 267 | if !response.status().is_success() { | ||
| 268 | return Err(format!( | ||
| 269 | "info/refs returned status {} for URL: {}", | ||
| 270 | response.status(), | ||
| 271 | info_refs_url | ||
| 272 | )); | ||
| 314 | } | 273 | } |
| 315 | 274 | ||
| 316 | // Check for required bare repository components | 275 | // Verify Content-Type indicates git-upload-pack service |
| 317 | let has_head = path.join("HEAD").is_file(); | 276 | let content_type = response |
| 318 | let has_config = path.join("config").is_file(); | 277 | .headers() |
| 319 | let has_objects = path.join("objects").is_dir(); | 278 | .get("content-type") |
| 320 | let has_refs = path.join("refs").is_dir(); | 279 | .and_then(|v| v.to_str().ok()) |
| 280 | .unwrap_or(""); | ||
| 281 | |||
| 282 | if !content_type.contains("application/x-git-upload-pack-advertisement") { | ||
| 283 | return Err(format!( | ||
| 284 | "Expected Content-Type: application/x-git-upload-pack-advertisement, got: {}", | ||
| 285 | content_type | ||
| 286 | )); | ||
| 287 | } | ||
| 321 | 288 | ||
| 322 | has_head && has_config && has_objects && has_refs | 289 | Ok(()) |
| 323 | } | 290 | } |
| 324 | 291 | ||
| 325 | #[cfg(test)] | 292 | #[cfg(test)] |
| 326 | mod tests { | 293 | mod tests { |
| 327 | use super::*; | ||
| 328 | use std::process::Command; | ||
| 329 | |||
| 330 | #[test] | ||
| 331 | fn test_is_bare_repository_detects_valid_repo() { | ||
| 332 | // Create a temporary bare repository for testing | ||
| 333 | let temp_dir = tempfile::tempdir().unwrap(); | ||
| 334 | let repo_path = temp_dir.path().join("test.git"); | ||
| 335 | |||
| 336 | // Initialize a bare repository | ||
| 337 | Command::new("git") | ||
| 338 | .args(&["init", "--bare", repo_path.to_str().unwrap()]) | ||
| 339 | .output() | ||
| 340 | .expect("Failed to create test repository"); | ||
| 341 | |||
| 342 | // Verify our helper function detects it | ||
| 343 | assert!( | ||
| 344 | is_bare_repository(&repo_path), | ||
| 345 | "Should detect valid bare repository" | ||
| 346 | ); | ||
| 347 | } | ||
| 348 | |||
| 349 | #[test] | ||
| 350 | fn test_is_bare_repository_rejects_non_repo() { | ||
| 351 | let temp_dir = tempfile::tempdir().unwrap(); | ||
| 352 | assert!( | ||
| 353 | !is_bare_repository(temp_dir.path()), | ||
| 354 | "Should reject non-repository directory" | ||
| 355 | ); | ||
| 356 | } | ||
| 357 | |||
| 358 | #[test] | 294 | #[test] |
| 359 | fn test_is_bare_repository_rejects_nonexistent() { | 295 | fn test_module_exists() { |
| 360 | let path = Path::new("/nonexistent/path/to/repo.git"); | 296 | // Simple compilation test |
| 361 | assert!(!is_bare_repository(path), "Should reject nonexistent path"); | 297 | assert!(true); |
| 362 | } | 298 | } |
| 363 | } | 299 | } |