upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-21 13:28:37 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-21 13:38:11 +0000
commit46fbcc0a4c8a8dbf6cd345d6eaa6fe33a82100bb (patch)
tree6ab52486732077dbab80907d974c195b1e2f7216 /tests
parent780d09b0c1eb823f02fc61de6dbf99b2d5cefaca (diff)
feat: add archive-grasp-services configuration option
Enables relay operators to backup/archive specific GRASP servers by domain. Includes configuration, validation, documentation, and integration tests.
Diffstat (limited to 'tests')
-rw-r--r--tests/archive_grasp_services.rs378
1 files changed, 378 insertions, 0 deletions
diff --git a/tests/archive_grasp_services.rs b/tests/archive_grasp_services.rs
new file mode 100644
index 0000000..a47fc55
--- /dev/null
+++ b/tests/archive_grasp_services.rs
@@ -0,0 +1,378 @@
1//! Archive GRASP Services Integration Tests
2//!
3//! Tests that verify archive_grasp_services filtering behavior:
4//! - Announcements with matching GRASP service domains are accepted
5//! - Announcements with non-matching GRASP service domains are rejected
6//! - Multiple configured services work correctly
7//! - Case-insensitive domain matching
8//!
9//! # Test Strategy
10//!
11//! These tests verify the GRASP-05 archive mode with grasp_services filtering:
12//! 1. Configure relay with specific GRASP service domains
13//! 2. Send announcements with various clone URLs
14//! 3. Verify announcements are accepted/rejected based on domain matching
15//! 4. Verify repositories are created only for accepted announcements
16//!
17//! # Running Tests
18//!
19//! ```bash
20//! # Run all archive grasp services tests
21//! cargo test --test archive_grasp_services
22//!
23//! # Run specific test
24//! cargo test --test archive_grasp_services test_archive_accepts_matching_grasp_service
25//!
26//! # With output for debugging
27//! cargo test --test archive_grasp_services -- --nocapture
28//! ```
29
30mod common;
31
32use common::TestRelay;
33use nostr_sdk::prelude::*;
34use std::path::PathBuf;
35use std::process::{Child, Command, Stdio};
36use std::time::Duration;
37
38/// Helper to start a relay with archive_grasp_services configuration
39///
40/// This is a specialized version of TestRelay::start_with_archive_and_sync
41/// that adds the NGIT_ARCHIVE_GRASP_SERVICES environment variable.
42async fn start_relay_with_grasp_services(services: &str) -> (Child, String, PathBuf) {
43 let port = TestRelay::find_free_port();
44 let bind_address = format!("127.0.0.1:{}", port);
45 let url = format!("ws://127.0.0.1:{}", port);
46
47 // Create temporary directory for git repositories
48 let git_data_dir = tempfile::tempdir().expect("Failed to create temporary git data directory");
49
50 // Use the built binary directly
51 let binary_path = std::env::current_exe()
52 .expect("Failed to get current exe")
53 .parent()
54 .expect("Failed to get parent dir")
55 .parent()
56 .expect("Failed to get grandparent dir")
57 .join("ngit-grasp");
58
59 // Generate a test owner npub
60 let test_keys = nostr_sdk::Keys::generate();
61 let test_npub = test_keys
62 .public_key()
63 .to_bech32()
64 .expect("Failed to generate test npub");
65
66 // Start the relay process with archive_grasp_services
67 let mut cmd = Command::new(&binary_path);
68 cmd.env("NGIT_BIND_ADDRESS", &bind_address)
69 .env("NGIT_DOMAIN", &bind_address)
70 .env("NGIT_GIT_DATA_PATH", git_data_dir.path())
71 .env("NGIT_DATABASE_BACKEND", "memory")
72 .env("NGIT_OWNER_NPUB", &test_npub)
73 .env("NGIT_ARCHIVE_GRASP_SERVICES", services)
74 .env(
75 "RUST_LOG",
76 std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
77 )
78 .stdout(Stdio::null())
79 .stderr(Stdio::null());
80
81 let process = cmd.spawn().expect("Failed to start relay process");
82
83 // Store git data path for test assertions
84 let git_data_path = git_data_dir.path().to_path_buf();
85
86 // Wait for relay to be ready
87 wait_for_relay_ready(port).await;
88
89 (process, url, git_data_path)
90}
91
92/// Wait for the relay to be ready to accept connections
93async fn wait_for_relay_ready(port: u16) {
94 let max_attempts = 50; // 5 seconds total
95 let delay = Duration::from_millis(100);
96
97 for attempt in 0..max_attempts {
98 // Try to connect to the relay
99 match tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port)).await {
100 Ok(_) => {
101 // Connection successful, relay is ready
102 // Give it a tiny bit more time to fully initialize
103 tokio::time::sleep(Duration::from_millis(100)).await;
104 return;
105 }
106 Err(_) => {
107 if attempt == max_attempts - 1 {
108 panic!("Relay failed to start after {} attempts", max_attempts);
109 }
110 tokio::time::sleep(delay).await;
111 }
112 }
113 }
114}
115
116/// Test that announcements with matching GRASP service domains are accepted.
117///
118/// Scenario:
119/// 1. Start relay with archive_grasp_services="git.example.com"
120/// 2. Send announcement with clone URL from git.example.com
121/// 3. Verify announcement is accepted (repository is created)
122#[tokio::test]
123async fn test_archive_accepts_matching_grasp_service() {
124 let (mut process, url, git_data_path) =
125 start_relay_with_grasp_services("git.example.com").await;
126 let keys = Keys::generate();
127 let identifier = "test-repo";
128
129 // Create announcement with clone URL from git.example.com
130 let npub = keys.public_key().to_bech32().expect("Failed to get npub");
131 let tags = vec![
132 Tag::identifier(identifier),
133 Tag::custom(
134 TagKind::custom("clone"),
135 vec![format!("https://git.example.com/user/{}.git", identifier)],
136 ),
137 Tag::custom(
138 TagKind::custom("relays"),
139 vec!["wss://relay.example.com".to_string()],
140 ),
141 ];
142
143 let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
144 .tags(tags)
145 .sign_with_keys(&keys)
146 .expect("Failed to sign announcement");
147
148 // Send announcement to relay
149 let client = Client::new(keys.clone());
150 client.add_relay(&url).await.expect("Failed to add relay");
151 client.connect().await;
152
153 tokio::time::sleep(Duration::from_millis(500)).await;
154
155 client
156 .send_event(&announcement)
157 .await
158 .expect("Failed to send announcement");
159
160 tokio::time::sleep(Duration::from_millis(500)).await;
161
162 // Verify repository was created (announcement was accepted)
163 let repo_path = git_data_path.join(format!("{}/{}.git", npub, identifier));
164
165 assert!(
166 repo_path.exists(),
167 "Repository should be created for announcement with matching GRASP service domain"
168 );
169
170 // Cleanup
171 client.disconnect().await;
172 let _ = process.kill();
173 let _ = process.wait();
174}
175
176/// Test that announcements with non-matching GRASP service domains are rejected.
177///
178/// Scenario:
179/// 1. Start relay with archive_grasp_services="git.example.com"
180/// 2. Send announcement with clone URL from github.com (not in services list)
181/// 3. Verify announcement is rejected (repository is NOT created)
182#[tokio::test]
183async fn test_archive_rejects_non_matching_grasp_service() {
184 let (mut process, url, git_data_path) =
185 start_relay_with_grasp_services("git.example.com").await;
186 let keys = Keys::generate();
187 let identifier = "test-repo";
188
189 // Create announcement with clone URL from github.com (NOT in services list)
190 let npub = keys.public_key().to_bech32().expect("Failed to get npub");
191 let tags = vec![
192 Tag::identifier(identifier),
193 Tag::custom(
194 TagKind::custom("clone"),
195 vec![format!("https://github.com/user/{}.git", identifier)],
196 ),
197 Tag::custom(
198 TagKind::custom("relays"),
199 vec!["wss://relay.example.com".to_string()],
200 ),
201 ];
202
203 let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
204 .tags(tags)
205 .sign_with_keys(&keys)
206 .expect("Failed to sign announcement");
207
208 // Send announcement to relay
209 let client = Client::new(keys.clone());
210 client.add_relay(&url).await.expect("Failed to add relay");
211 client.connect().await;
212
213 tokio::time::sleep(Duration::from_millis(500)).await;
214
215 client
216 .send_event(&announcement)
217 .await
218 .expect("Failed to send announcement");
219
220 tokio::time::sleep(Duration::from_millis(500)).await;
221
222 // Verify repository was NOT created (announcement was rejected)
223 let repo_path = git_data_path.join(format!("{}/{}.git", npub, identifier));
224
225 assert!(
226 !repo_path.exists(),
227 "Repository should NOT be created for announcement with non-matching GRASP service domain"
228 );
229
230 // Cleanup
231 client.disconnect().await;
232 let _ = process.kill();
233 let _ = process.wait();
234}
235
236/// Test that multiple configured GRASP services work correctly.
237///
238/// Scenario:
239/// 1. Start relay with archive_grasp_services="git.example.com,gitlab.example.org"
240/// 2. Send announcements with clone URLs from both services
241/// 3. Verify both announcements are accepted
242/// 4. Send announcement from non-listed service
243/// 5. Verify it is rejected
244#[tokio::test]
245async fn test_archive_multiple_grasp_services() {
246 let (mut process, url, git_data_path) =
247 start_relay_with_grasp_services("git.example.com,gitlab.example.org").await;
248
249 // Test first service (git.example.com)
250 let keys1 = Keys::generate();
251 let identifier1 = "test-repo-1";
252 let npub1 = keys1.public_key().to_bech32().expect("Failed to get npub");
253
254 let tags1 = vec![
255 Tag::identifier(identifier1),
256 Tag::custom(
257 TagKind::custom("clone"),
258 vec![format!("https://git.example.com/user/{}.git", identifier1)],
259 ),
260 Tag::custom(
261 TagKind::custom("relays"),
262 vec!["wss://relay.example.com".to_string()],
263 ),
264 ];
265
266 let announcement1 = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
267 .tags(tags1)
268 .sign_with_keys(&keys1)
269 .expect("Failed to sign announcement");
270
271 let client1 = Client::new(keys1.clone());
272 client1.add_relay(&url).await.expect("Failed to add relay");
273 client1.connect().await;
274 tokio::time::sleep(Duration::from_millis(500)).await;
275
276 client1
277 .send_event(&announcement1)
278 .await
279 .expect("Failed to send announcement");
280 tokio::time::sleep(Duration::from_millis(500)).await;
281
282 // Test second service (gitlab.example.org)
283 let keys2 = Keys::generate();
284 let identifier2 = "test-repo-2";
285 let npub2 = keys2.public_key().to_bech32().expect("Failed to get npub");
286
287 let tags2 = vec![
288 Tag::identifier(identifier2),
289 Tag::custom(
290 TagKind::custom("clone"),
291 vec![format!(
292 "https://gitlab.example.org/user/{}.git",
293 identifier2
294 )],
295 ),
296 Tag::custom(
297 TagKind::custom("relays"),
298 vec!["wss://relay.example.com".to_string()],
299 ),
300 ];
301
302 let announcement2 = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
303 .tags(tags2)
304 .sign_with_keys(&keys2)
305 .expect("Failed to sign announcement");
306
307 let client2 = Client::new(keys2.clone());
308 client2.add_relay(&url).await.expect("Failed to add relay");
309 client2.connect().await;
310 tokio::time::sleep(Duration::from_millis(500)).await;
311
312 client2
313 .send_event(&announcement2)
314 .await
315 .expect("Failed to send announcement");
316 tokio::time::sleep(Duration::from_millis(500)).await;
317
318 // Test non-listed service (github.com)
319 let keys3 = Keys::generate();
320 let identifier3 = "test-repo-3";
321 let npub3 = keys3.public_key().to_bech32().expect("Failed to get npub");
322
323 let tags3 = vec![
324 Tag::identifier(identifier3),
325 Tag::custom(
326 TagKind::custom("clone"),
327 vec![format!("https://github.com/user/{}.git", identifier3)],
328 ),
329 Tag::custom(
330 TagKind::custom("relays"),
331 vec!["wss://relay.example.com".to_string()],
332 ),
333 ];
334
335 let announcement3 = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
336 .tags(tags3)
337 .sign_with_keys(&keys3)
338 .expect("Failed to sign announcement");
339
340 let client3 = Client::new(keys3.clone());
341 client3.add_relay(&url).await.expect("Failed to add relay");
342 client3.connect().await;
343 tokio::time::sleep(Duration::from_millis(500)).await;
344
345 client3
346 .send_event(&announcement3)
347 .await
348 .expect("Failed to send announcement");
349 tokio::time::sleep(Duration::from_millis(500)).await;
350
351 // Verify first service announcement was accepted
352 let repo_path1 = git_data_path.join(format!("{}/{}.git", npub1, identifier1));
353 assert!(
354 repo_path1.exists(),
355 "Repository should be created for first GRASP service (git.example.com)"
356 );
357
358 // Verify second service announcement was accepted
359 let repo_path2 = git_data_path.join(format!("{}/{}.git", npub2, identifier2));
360 assert!(
361 repo_path2.exists(),
362 "Repository should be created for second GRASP service (gitlab.example.org)"
363 );
364
365 // Verify non-listed service announcement was rejected
366 let repo_path3 = git_data_path.join(format!("{}/{}.git", npub3, identifier3));
367 assert!(
368 !repo_path3.exists(),
369 "Repository should NOT be created for non-listed service (github.com)"
370 );
371
372 // Cleanup
373 client1.disconnect().await;
374 client2.disconnect().await;
375 client3.disconnect().await;
376 let _ = process.kill();
377 let _ = process.wait();
378}