upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-21 14:27:01 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-21 14:27:01 +0000
commit97e21b62eab89bab1456db7df27df8f1c85399f0 (patch)
treed1315263d92a08ebe995823dab7794992c7522a5 /grasp-audit/src
parent7d4bbb3f4954be163c28a42736852bb26e7abc4b (diff)
add http clone tests
Diffstat (limited to 'grasp-audit/src')
-rw-r--r--grasp-audit/src/specs/grasp01/git_clone.rs403
-rw-r--r--grasp-audit/src/specs/grasp01/mod.rs2
2 files changed, 405 insertions, 0 deletions
diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs
new file mode 100644
index 0000000..cad17d2
--- /dev/null
+++ b/grasp-audit/src/specs/grasp01/git_clone.rs
@@ -0,0 +1,403 @@
1//! GRASP-01 Git Clone Tests
2//!
3//! Tests that verify Git clone operations work correctly through the HTTP backend.
4//!
5//! ## Test Coverage
6//!
7//! - Basic clone operation via HTTP
8//! - Cloned repository structure validation
9//! - Clone URL format verification
10//!
11//! ## Running Tests
12//!
13//! ```bash
14//! cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test
15//! ```
16
17use crate::{AuditClient, FixtureKind, TestContext, TestResult};
18use nostr_sdk::prelude::*;
19use std::fs;
20use std::path::Path;
21use std::process::Command;
22
23/// Test suite for Git clone operations
24pub struct GitCloneTests;
25
26impl GitCloneTests {
27 /// Test that a repository can be cloned via Git HTTP backend
28 ///
29 /// This test:
30 /// 1. Creates a repository announcement
31 /// 2. Waits for repository creation
32 /// 3. Attempts to clone the repository using git clone
33 /// 4. Verifies the clone succeeded
34 pub async fn test_basic_git_clone(
35 client: &AuditClient,
36 git_data_dir: &Path,
37 relay_domain: &str,
38 ) -> TestResult {
39 let test_name = "test_basic_git_clone";
40 let ctx = TestContext::new(client);
41
42 // Create repository announcement
43 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
44 Ok(r) => r,
45 Err(e) => {
46 return TestResult::new(
47 test_name,
48 "GRASP-01",
49 "Repository must be cloneable via Git HTTP backend",
50 )
51 .fail(&format!("Failed to create repo fixture: {}", e))
52 }
53 };
54
55 // Wait for repository creation
56 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
57
58 // Extract repo identifier and npub
59 let repo_id = match repo
60 .tags
61 .iter()
62 .find(|t| t.kind() == TagKind::d())
63 .and_then(|t| t.content())
64 {
65 Some(id) => id.to_string(),
66 None => {
67 return TestResult::new(
68 test_name,
69 "GRASP-01",
70 "Repository must be cloneable via Git HTTP backend",
71 )
72 .fail("Repository announcement missing d tag")
73 }
74 };
75
76 let npub = match repo.pubkey.to_bech32() {
77 Ok(n) => n,
78 Err(e) => {
79 return TestResult::new(
80 test_name,
81 "GRASP-01",
82 "Repository must be cloneable via Git HTTP backend",
83 )
84 .fail(&format!("Failed to convert pubkey to npub: {}", e))
85 }
86 };
87
88 // Verify repository exists
89 let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id));
90 if !repo_path.exists() {
91 return TestResult::new(
92 test_name,
93 "GRASP-01",
94 "Repository must be cloneable via Git HTTP backend",
95 )
96 .fail(&format!(
97 "Repository not found at: {}",
98 repo_path.display()
99 ));
100 }
101
102 // Create a test clone directory using standard library
103 let temp_base = std::env::temp_dir();
104 let clone_dir_name = format!("grasp-test-clone-{}", uuid::Uuid::new_v4());
105 let clone_path = temp_base.join(&clone_dir_name);
106
107 // Ensure clean state
108 let _ = fs::remove_dir_all(&clone_path);
109
110 // Build clone URL: http://domain/npub/identifier.git
111 let clone_url = format!("http://{}/{}/{}.git", relay_domain, npub, repo_id);
112
113 // Attempt to clone the repository
114 let output = Command::new("git")
115 .args(&["clone", &clone_url, clone_path.to_str().unwrap()])
116 .env("GIT_TERMINAL_PROMPT", "0") // Disable password prompts
117 .output();
118
119 // Clean up on success or failure
120 let cleanup = || {
121 let _ = fs::remove_dir_all(&clone_path);
122 };
123
124 let output = match output {
125 Ok(o) => o,
126 Err(e) => {
127 cleanup();
128 return TestResult::new(
129 test_name,
130 "GRASP-01",
131 "Repository must be cloneable via Git HTTP backend",
132 )
133 .fail(&format!("Failed to execute git clone: {}", e))
134 }
135 };
136
137 if !output.status.success() {
138 cleanup();
139 let stderr = String::from_utf8_lossy(&output.stderr);
140 return TestResult::new(
141 test_name,
142 "GRASP-01",
143 "Repository must be cloneable via Git HTTP backend",
144 )
145 .fail(&format!("Git clone failed: {}", stderr));
146 }
147
148 // Verify clone succeeded by checking for .git directory
149 if !clone_path.join(".git").is_dir() {
150 cleanup();
151 return TestResult::new(
152 test_name,
153 "GRASP-01",
154 "Repository must be cloneable via Git HTTP backend",
155 )
156 .fail("Cloned repository missing .git directory");
157 }
158
159 cleanup();
160 TestResult::new(
161 test_name,
162 "GRASP-01",
163 "Repository must be cloneable via Git HTTP backend",
164 )
165 .pass()
166 }
167
168 /// Test that cloned repository has correct structure
169 ///
170 /// This test verifies:
171 /// 1. Clone creates a valid Git repository
172 /// 2. Repository has proper Git structure (.git/config, .git/HEAD, etc.)
173 /// 3. Repository is properly initialized
174 pub async fn test_cloned_repo_structure(
175 client: &AuditClient,
176 _git_data_dir: &Path,
177 relay_domain: &str,
178 ) -> TestResult {
179 let test_name = "test_cloned_repo_structure";
180 let ctx = TestContext::new(client);
181
182 // Create repository announcement
183 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
184 Ok(r) => r,
185 Err(e) => {
186 return TestResult::new(
187 test_name,
188 "GRASP-01",
189 "Cloned repository must have correct structure",
190 )
191 .fail(&format!("Failed to create repo fixture: {}", e))
192 }
193 };
194
195 // Wait for repository creation
196 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
197
198 // Extract repo identifier and npub
199 let repo_id = repo
200 .tags
201 .iter()
202 .find(|t| t.kind() == TagKind::d())
203 .and_then(|t| t.content())
204 .ok_or("Missing d tag")
205 .unwrap()
206 .to_string();
207
208 let npub = repo.pubkey.to_bech32().unwrap();
209
210 // Create temp clone directory using standard library
211 let temp_base = std::env::temp_dir();
212 let clone_dir_name = format!("grasp-test-clone-struct-{}", uuid::Uuid::new_v4());
213 let clone_path = temp_base.join(&clone_dir_name);
214
215 // Ensure clean state
216 let _ = fs::remove_dir_all(&clone_path);
217
218 // Cleanup helper
219 let cleanup = || {
220 let _ = fs::remove_dir_all(&clone_path);
221 };
222
223 // Clone the repository
224 let clone_url = format!("http://{}/{}/{}.git", relay_domain, npub, repo_id);
225 let output = Command::new("git")
226 .args(&["clone", &clone_url, clone_path.to_str().unwrap()])
227 .env("GIT_TERMINAL_PROMPT", "0")
228 .output()
229 .unwrap();
230
231 if !output.status.success() {
232 cleanup();
233 let stderr = String::from_utf8_lossy(&output.stderr);
234 return TestResult::new(
235 test_name,
236 "GRASP-01",
237 "Cloned repository must have correct structure",
238 )
239 .fail(&format!("Git clone failed: {}", stderr));
240 }
241
242 // Verify Git repository structure
243 let git_dir = clone_path.join(".git");
244
245 if !git_dir.join("config").is_file() {
246 cleanup();
247 return TestResult::new(
248 test_name,
249 "GRASP-01",
250 "Cloned repository must have correct structure",
251 )
252 .fail("Missing .git/config file");
253 }
254
255 if !git_dir.join("HEAD").is_file() {
256 cleanup();
257 return TestResult::new(
258 test_name,
259 "GRASP-01",
260 "Cloned repository must have correct structure",
261 )
262 .fail("Missing .git/HEAD file");
263 }
264
265 if !git_dir.join("objects").is_dir() {
266 cleanup();
267 return TestResult::new(
268 test_name,
269 "GRASP-01",
270 "Cloned repository must have correct structure",
271 )
272 .fail("Missing .git/objects directory");
273 }
274
275 if !git_dir.join("refs").is_dir() {
276 cleanup();
277 return TestResult::new(
278 test_name,
279 "GRASP-01",
280 "Cloned repository must have correct structure",
281 )
282 .fail("Missing .git/refs directory");
283 }
284
285 cleanup();
286 TestResult::new(
287 test_name,
288 "GRASP-01",
289 "Cloned repository must have correct structure",
290 )
291 .pass()
292 }
293
294 /// Test clone URL format validation
295 ///
296 /// This test verifies:
297 /// 1. URLs follow the pattern http://domain/npub/identifier.git
298 /// 2. Invalid URLs are rejected properly
299 pub async fn test_clone_url_format(
300 client: &AuditClient,
301 _git_data_dir: &Path,
302 relay_domain: &str,
303 ) -> TestResult {
304 let test_name = "test_clone_url_format";
305 let ctx = TestContext::new(client);
306
307 // Create repository announcement
308 let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await {
309 Ok(r) => r,
310 Err(e) => {
311 return TestResult::new(
312 test_name,
313 "GRASP-01",
314 "Clone URL must follow correct format",
315 )
316 .fail(&format!("Failed to create repo fixture: {}", e))
317 }
318 };
319
320 // Wait for repository creation
321 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
322
323 let repo_id = repo
324 .tags
325 .iter()
326 .find(|t| t.kind() == TagKind::d())
327 .and_then(|t| t.content())
328 .ok_or("Missing d tag")
329 .unwrap()
330 .to_string();
331
332 let npub = repo.pubkey.to_bech32().unwrap();
333
334 // Test valid URL format
335 let valid_url = format!("http://{}/{}/{}.git", relay_domain, npub, repo_id);
336
337 // Verify URL contains expected components
338 if !valid_url.contains(&npub) {
339 return TestResult::new(
340 test_name,
341 "GRASP-01",
342 "Clone URL must follow correct format",
343 )
344 .fail("URL missing npub");
345 }
346
347 if !valid_url.contains(&format!("{}.git", repo_id)) {
348 return TestResult::new(
349 test_name,
350 "GRASP-01",
351 "Clone URL must follow correct format",
352 )
353 .fail("URL missing repository identifier");
354 }
355
356 // Test that invalid URL fails (wrong format)
357 let temp_base = std::env::temp_dir();
358 let clone_dir_name = format!("grasp-test-invalid-{}", uuid::Uuid::new_v4());
359 let clone_path = temp_base.join(&clone_dir_name);
360
361 // Ensure clean state
362 let _ = fs::remove_dir_all(&clone_path);
363
364 let invalid_url = format!("http://{}/invalid/path", relay_domain);
365
366 let output = Command::new("git")
367 .args(&["clone", &invalid_url, clone_path.to_str().unwrap()])
368 .env("GIT_TERMINAL_PROMPT", "0")
369 .output()
370 .unwrap();
371
372 // Cleanup after test
373 let _ = fs::remove_dir_all(&clone_path);
374
375 // Invalid URL should fail
376 if output.status.success() {
377 return TestResult::new(
378 test_name,
379 "GRASP-01",
380 "Clone URL must follow correct format",
381 )
382 .fail("Invalid URL was accepted (should have been rejected)");
383 }
384
385 TestResult::new(
386 test_name,
387 "GRASP-01",
388 "Clone URL must follow correct format",
389 )
390 .pass()
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_module_exists() {
400 // Simple compilation test
401 assert!(true);
402 }
403} \ 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 fd6d9b3..495672a 100644
--- a/grasp-audit/src/specs/grasp01/mod.rs
+++ b/grasp-audit/src/specs/grasp01/mod.rs
@@ -1,11 +1,13 @@
1//! GRASP-01 specification tests 1//! GRASP-01 specification tests
2 2
3pub mod event_acceptance_policy; 3pub mod event_acceptance_policy;
4pub mod git_clone;
4pub mod nip01_smoke; 5pub mod nip01_smoke;
5pub mod nip11_document; 6pub mod nip11_document;
6pub mod repository_creation; 7pub mod repository_creation;
7 8
8pub use event_acceptance_policy::EventAcceptancePolicyTests; 9pub use event_acceptance_policy::EventAcceptancePolicyTests;
10pub use git_clone::GitCloneTests;
9pub use nip01_smoke::Nip01SmokeTests; 11pub use nip01_smoke::Nip01SmokeTests;
10pub use nip11_document::Nip11DocumentTests; 12pub use nip11_document::Nip11DocumentTests;
11pub use repository_creation::RepositoryCreationTests; 13pub use repository_creation::RepositoryCreationTests;