diff options
Diffstat (limited to 'grasp-audit')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/repository_creation.rs | 155 |
1 files changed, 13 insertions, 142 deletions
diff --git a/grasp-audit/src/specs/grasp01/repository_creation.rs b/grasp-audit/src/specs/grasp01/repository_creation.rs index 588187b..63b3dee 100644 --- a/grasp-audit/src/specs/grasp01/repository_creation.rs +++ b/grasp-audit/src/specs/grasp01/repository_creation.rs | |||
| @@ -5,9 +5,9 @@ | |||
| 5 | //! | 5 | //! |
| 6 | //! ## Test Coverage | 6 | //! ## Test Coverage |
| 7 | //! | 7 | //! |
| 8 | //! - Repository creation on valid announcement (verified via HTTP) | 8 | //! - Repository creation on valid announcement |
| 9 | //! - Idempotent creation (no error if repo already exists) | 9 | //! - Repository accessibility via Smart HTTP service (git-upload-pack) |
| 10 | //! - Repository accessibility via Smart HTTP service | 10 | //! - URL format: http://domain/npub/identifier.git |
| 11 | //! | 11 | //! |
| 12 | //! ## Running Tests | 12 | //! ## Running Tests |
| 13 | //! | 13 | //! |
| @@ -30,18 +30,18 @@ impl RepositoryCreationTests { | |||
| 30 | let mut results = crate::AuditResult::new("GRASP-01 Repository Creation Tests"); | 30 | let mut results = crate::AuditResult::new("GRASP-01 Repository Creation Tests"); |
| 31 | 31 | ||
| 32 | results.add(Self::test_bare_repo_created_on_announcement(client, relay_domain).await); | 32 | results.add(Self::test_bare_repo_created_on_announcement(client, relay_domain).await); |
| 33 | results.add(Self::test_repo_creation_idempotent(client, relay_domain).await); | ||
| 34 | results.add(Self::test_repo_accessible_via_http(client, relay_domain).await); | ||
| 35 | 33 | ||
| 36 | results | 34 | results |
| 37 | } | 35 | } |
| 38 | 36 | ||
| 39 | /// Test that a bare repository is created when a valid announcement is accepted | 37 | /// Test that a bare repository is created when a valid announcement is accepted |
| 38 | /// and is accessible via Smart HTTP service | ||
| 40 | /// | 39 | /// |
| 41 | /// This test: | 40 | /// This test verifies: |
| 42 | /// 1. Sends a valid repository announcement via TestContext | 41 | /// 1. Sends a valid repository announcement via TestContext |
| 43 | /// 2. Verifies the announcement was accepted | 42 | /// 2. Verifies the announcement was accepted |
| 44 | /// 3. Verifies the repository is accessible via Smart HTTP service | 43 | /// 3. Repository responds to git-upload-pack service discovery |
| 44 | /// 4. URL format follows http://domain/npub/identifier.git pattern | ||
| 45 | pub async fn test_bare_repo_created_on_announcement( | 45 | pub async fn test_bare_repo_created_on_announcement( |
| 46 | client: &AuditClient, | 46 | client: &AuditClient, |
| 47 | relay_domain: &str, | 47 | relay_domain: &str, |
| @@ -56,7 +56,7 @@ impl RepositoryCreationTests { | |||
| 56 | return TestResult::new( | 56 | return TestResult::new( |
| 57 | test_name, | 57 | test_name, |
| 58 | "GRASP-01", | 58 | "GRASP-01", |
| 59 | "Bare repository must be created when announcement is accepted", | 59 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 60 | ) | 60 | ) |
| 61 | .fail(&format!("Failed to create repo fixture: {}", e)) | 61 | .fail(&format!("Failed to create repo fixture: {}", e)) |
| 62 | } | 62 | } |
| @@ -77,7 +77,7 @@ impl RepositoryCreationTests { | |||
| 77 | return TestResult::new( | 77 | return TestResult::new( |
| 78 | test_name, | 78 | test_name, |
| 79 | "GRASP-01", | 79 | "GRASP-01", |
| 80 | "Bare repository must be created when announcement is accepted", | 80 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 81 | ) | 81 | ) |
| 82 | .fail("Repository announcement missing d tag") | 82 | .fail("Repository announcement missing d tag") |
| 83 | } | 83 | } |
| @@ -89,18 +89,18 @@ impl RepositoryCreationTests { | |||
| 89 | return TestResult::new( | 89 | return TestResult::new( |
| 90 | test_name, | 90 | test_name, |
| 91 | "GRASP-01", | 91 | "GRASP-01", |
| 92 | "Bare repository must be created when announcement is accepted", | 92 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 93 | ) | 93 | ) |
| 94 | .fail(&format!("Failed to convert pubkey to npub: {}", e)) | 94 | .fail(&format!("Failed to convert pubkey to npub: {}", e)) |
| 95 | } | 95 | } |
| 96 | }; | 96 | }; |
| 97 | 97 | ||
| 98 | // Verify repository exists via HTTP (info/refs endpoint) | 98 | // Verify repository exists and is accessible via HTTP (info/refs endpoint) |
| 99 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { | 99 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { |
| 100 | return TestResult::new( | 100 | return TestResult::new( |
| 101 | test_name, | 101 | test_name, |
| 102 | "GRASP-01", | 102 | "GRASP-01", |
| 103 | "Bare repository must be created when announcement is accepted", | 103 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 104 | ) | 104 | ) |
| 105 | .fail(&format!("Repository not accessible via HTTP: {}", e)); | 105 | .fail(&format!("Repository not accessible via HTTP: {}", e)); |
| 106 | } | 106 | } |
| @@ -108,136 +108,7 @@ impl RepositoryCreationTests { | |||
| 108 | TestResult::new( | 108 | TestResult::new( |
| 109 | test_name, | 109 | test_name, |
| 110 | "GRASP-01", | 110 | "GRASP-01", |
| 111 | "Bare repository must be created when announcement is accepted", | 111 | "Bare repository must be created and accessible via Smart HTTP when announcement is accepted", |
| 112 | ) | ||
| 113 | .pass() | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Test that repository creation is idempotent | ||
| 117 | /// | ||
| 118 | /// This test: | ||
| 119 | /// 1. Sends a repository announcement (creates repo) via TestContext | ||
| 120 | /// 2. Sends the same announcement again | ||
| 121 | /// 3. Verifies no error occurs and repo still exists | ||
| 122 | pub async fn test_repo_creation_idempotent( | ||
| 123 | client: &AuditClient, | ||
| 124 | relay_domain: &str, | ||
| 125 | ) -> TestResult { | ||
| 126 | let test_name = "test_repo_creation_idempotent"; | ||
| 127 | let ctx = TestContext::new(client); | ||
| 128 | |||
| 129 | // Create and send repository announcement first time via TestContext | ||
| 130 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | ||
| 131 | Ok(r) => r, | ||
| 132 | Err(e) => { | ||
| 133 | return TestResult::new( | ||
| 134 | test_name, | ||
| 135 | "GRASP-01", | ||
| 136 | "Repository creation must be idempotent", | ||
| 137 | ) | ||
| 138 | .fail(&format!("Failed to create repo fixture: {}", e)) | ||
| 139 | } | ||
| 140 | }; | ||
| 141 | |||
| 142 | // Wait for repository creation | ||
| 143 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 144 | |||
| 145 | // Send the same announcement again (should be idempotent) | ||
| 146 | if let Err(e) = client.send_event(repo.clone()).await { | ||
| 147 | return TestResult::new( | ||
| 148 | test_name, | ||
| 149 | "GRASP-01", | ||
| 150 | "Repository creation must be idempotent", | ||
| 151 | ) | ||
| 152 | .fail(&format!("Second send failed (not idempotent): {}", e)); | ||
| 153 | } | ||
| 154 | |||
| 155 | // Wait again | ||
| 156 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 157 | |||
| 158 | // Verify repository still exists and is accessible via HTTP | ||
| 159 | let repo_id = repo | ||
| 160 | .tags | ||
| 161 | .iter() | ||
| 162 | .find(|t| t.kind() == TagKind::d()) | ||
| 163 | .and_then(|t| t.content()) | ||
| 164 | .ok_or("Missing d tag") | ||
| 165 | .unwrap() | ||
| 166 | .to_string(); | ||
| 167 | |||
| 168 | let npub = repo.pubkey.to_bech32().unwrap(); | ||
| 169 | |||
| 170 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { | ||
| 171 | return TestResult::new( | ||
| 172 | test_name, | ||
| 173 | "GRASP-01", | ||
| 174 | "Repository creation must be idempotent", | ||
| 175 | ) | ||
| 176 | .fail(&format!("Repository not accessible after second send: {}", e)); | ||
| 177 | } | ||
| 178 | |||
| 179 | TestResult::new( | ||
| 180 | test_name, | ||
| 181 | "GRASP-01", | ||
| 182 | "Repository creation must be idempotent", | ||
| 183 | ) | ||
| 184 | .pass() | ||
| 185 | } | ||
| 186 | |||
| 187 | /// Test that the repository is accessible via Smart HTTP service | ||
| 188 | /// | ||
| 189 | /// This test verifies: | ||
| 190 | /// 1. Repository responds to git-upload-pack service discovery | ||
| 191 | /// 2. URL format follows http://domain/npub/identifier.git pattern | ||
| 192 | pub async fn test_repo_accessible_via_http( | ||
| 193 | client: &AuditClient, | ||
| 194 | relay_domain: &str, | ||
| 195 | ) -> TestResult { | ||
| 196 | let test_name = "test_repo_accessible_via_http"; | ||
| 197 | let ctx = TestContext::new(client); | ||
| 198 | |||
| 199 | // Create and send repository announcement via TestContext | ||
| 200 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | ||
| 201 | Ok(r) => r, | ||
| 202 | Err(e) => { | ||
| 203 | return TestResult::new( | ||
| 204 | test_name, | ||
| 205 | "GRASP-01", | ||
| 206 | "Repository must be accessible via Smart HTTP service", | ||
| 207 | ) | ||
| 208 | .fail(&format!("Failed to create repo fixture: {}", e)) | ||
| 209 | } | ||
| 210 | }; | ||
| 211 | |||
| 212 | // Wait for repository creation | ||
| 213 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 214 | |||
| 215 | // Extract repo identifier and npub | ||
| 216 | let repo_id = repo | ||
| 217 | .tags | ||
| 218 | .iter() | ||
| 219 | .find(|t| t.kind() == TagKind::d()) | ||
| 220 | .and_then(|t| t.content()) | ||
| 221 | .ok_or("Missing d tag") | ||
| 222 | .unwrap() | ||
| 223 | .to_string(); | ||
| 224 | |||
| 225 | let npub = repo.pubkey.to_bech32().unwrap(); | ||
| 226 | |||
| 227 | // Verify repository is accessible via HTTP | ||
| 228 | if let Err(e) = check_repo_accessible_via_http(relay_domain, &npub, &repo_id).await { | ||
| 229 | return TestResult::new( | ||
| 230 | test_name, | ||
| 231 | "GRASP-01", | ||
| 232 | "Repository must be accessible via Smart HTTP service", | ||
| 233 | ) | ||
| 234 | .fail(&e); | ||
| 235 | } | ||
| 236 | |||
| 237 | TestResult::new( | ||
| 238 | test_name, | ||
| 239 | "GRASP-01", | ||
| 240 | "Repository must be accessible via Smart HTTP service", | ||
| 241 | ) | 112 | ) |
| 242 | .pass() | 113 | .pass() |
| 243 | } | 114 | } |