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-07 15:50:50 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-07 15:50:50 +0000
commit049cff14fa731c95b9b0074f67469df3af19870b (patch)
tree78f87b52385eaaaa1d221745cdf367ee81b598ba /tests
parent1db877d53c4ff45971c69fecc5165c352ec316c9 (diff)
test: add test_pr_event_syncs_from_remote
Diffstat (limited to 'tests')
-rw-r--r--tests/common/purgatory_helpers.rs67
-rw-r--r--tests/purgatory_sync.rs168
2 files changed, 232 insertions, 3 deletions
diff --git a/tests/common/purgatory_helpers.rs b/tests/common/purgatory_helpers.rs
index e61e2e2..7d8e908 100644
--- a/tests/common/purgatory_helpers.rs
+++ b/tests/common/purgatory_helpers.rs
@@ -529,6 +529,73 @@ pub fn push_to_relay(
529 Ok(()) 529 Ok(())
530} 530}
531 531
532/// Push a specific ref to a relay.
533///
534/// This is used for pushing to refs/nostr/<event-id> for PR events.
535/// Unlike `push_to_relay` which pushes all refs, this pushes a specific
536/// commit to a specific ref name.
537///
538/// # Arguments
539/// * `local_path` - Path to local git repository
540/// * `relay_domain` - The relay domain (e.g., "127.0.0.1:8080")
541/// * `npub` - Owner's npub
542/// * `repo_id` - Repository identifier
543/// * `commit_hash` - The commit to push
544/// * `ref_name` - The ref name to push to (e.g., "refs/nostr/<event-id>")
545///
546/// # Returns
547/// * `Ok(())` - Push successful
548/// * `Err(String)` - Push failed
549pub fn push_ref_to_relay(
550 local_path: &Path,
551 relay_domain: &str,
552 npub: &str,
553 repo_id: &str,
554 commit_hash: &str,
555 ref_name: &str,
556) -> Result<(), String> {
557 let remote_url = format!("http://{}/{}/{}.git", relay_domain, npub, repo_id);
558
559 // Check if origin already exists
560 let check_output = Command::new("git")
561 .args(["remote", "get-url", "origin"])
562 .current_dir(local_path)
563 .output()
564 .map_err(|e| format!("Failed to check remote: {}", e))?;
565
566 if check_output.status.success() {
567 // Remote exists, update it
568 let _ = Command::new("git")
569 .args(["remote", "set-url", "origin", &remote_url])
570 .current_dir(local_path)
571 .output();
572 } else {
573 // Add new remote
574 let _ = Command::new("git")
575 .args(["remote", "add", "origin", &remote_url])
576 .current_dir(local_path)
577 .output();
578 }
579
580 // Push specific commit to specific ref
581 // Format: git push origin <commit>:<ref>
582 let refspec = format!("{}:{}", commit_hash, ref_name);
583 let output = Command::new("git")
584 .args(["push", "origin", &refspec])
585 .current_dir(local_path)
586 .output()
587 .map_err(|e| format!("Failed to run git push: {}", e))?;
588
589 if !output.status.success() {
590 return Err(format!(
591 "git push failed: {}",
592 String::from_utf8_lossy(&output.stderr)
593 ));
594 }
595
596 Ok(())
597}
598
532#[cfg(test)] 599#[cfg(test)]
533mod tests { 600mod tests {
534 use super::*; 601 use super::*;
diff --git a/tests/purgatory_sync.rs b/tests/purgatory_sync.rs
index 3da086c..bb99f46 100644
--- a/tests/purgatory_sync.rs
+++ b/tests/purgatory_sync.rs
@@ -28,9 +28,10 @@
28mod common; 28mod common;
29 29
30use common::{ 30use common::{
31 check_ref_at_commit, create_repo_announcement, create_state_event, 31 build_repo_coord, check_ref_at_commit, create_pr_event, create_repo_announcement,
32 create_test_repo_with_commit, push_to_relay, verify_event_not_served, wait_for_event_served, 32 create_state_event, create_test_repo_with_commit, push_ref_to_relay, push_to_relay,
33 wait_for_sync_connection, CommitVariant, TestRelay, 33 verify_event_not_served, wait_for_event_served, wait_for_sync_connection, CommitVariant,
34 TestRelay,
34}; 35};
35use nostr_sdk::prelude::*; 36use nostr_sdk::prelude::*;
36use std::time::Duration; 37use std::time::Duration;
@@ -277,3 +278,164 @@ async fn test_state_event_syncs_from_remote() {
277 syncing_relay.stop().await; 278 syncing_relay.stop().await;
278 source_relay.stop().await; 279 source_relay.stop().await;
279} 280}
281
282/// Test that a PR event entering purgatory triggers remote commit fetch
283/// and is released once the commit is available.
284///
285/// Scenario:
286/// 1. Start source relay with repository announcement
287/// 2. Create PR event (goes to purgatory - no git data yet)
288/// 3. Push commit to refs/nostr/<event-id> (authorized by PR event in purgatory)
289/// 4. PR event gets released from purgatory on source relay
290/// 5. Start syncing relay
291/// 6. Syncing relay syncs PR event (goes to purgatory - no local git data)
292/// 7. Syncing relay fetches commit from source's clone URL
293/// 8. Verify PR event is released and refs/nostr/<event-id> created on syncing relay
294#[tokio::test]
295async fn test_pr_event_syncs_from_remote() {
296 // 1. Start source relay
297 let source_relay = TestRelay::start().await;
298 let owner_keys = Keys::generate();
299 let pr_author_keys = Keys::generate();
300 let identifier = "pr-sync-test-repo";
301
302 // Pre-allocate syncing relay port so we can include it in announcement
303 let syncing_port = TestRelay::find_free_port();
304 let syncing_domain = format!("127.0.0.1:{}", syncing_port);
305
306 // 2. Create test repository locally with PR commit
307 let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
308 let commit_hash = create_test_repo_with_commit(temp_dir.path(), CommitVariant::PrTest)
309 .expect("Failed to create test repo");
310
311 let npub = owner_keys
312 .public_key()
313 .to_bech32()
314 .expect("Failed to get npub");
315
316 // 3. Create and send announcement listing BOTH relays
317 // This ensures the syncing relay will accept the PR event when it syncs
318 let announcement = create_repo_announcement(
319 &owner_keys,
320 &[&source_relay.domain(), &syncing_domain],
321 identifier,
322 );
323
324 let source_client = Client::new(owner_keys.clone());
325 source_client
326 .add_relay(source_relay.url())
327 .await
328 .expect("Failed to add source relay");
329 source_client.connect().await;
330
331 // Wait for connection
332 tokio::time::sleep(Duration::from_millis(500)).await;
333
334 // Send announcement to source relay (creates bare repo)
335 source_client
336 .send_event(&announcement)
337 .await
338 .expect("Failed to send announcement to source");
339
340 tokio::time::sleep(Duration::from_millis(200)).await;
341
342 // 4. Create and send PR event BEFORE pushing
343 // The PR event goes to purgatory on source relay, which authorizes the push
344 let repo_coord = build_repo_coord(&owner_keys, identifier);
345
346 let pr_event = create_pr_event(&pr_author_keys, &repo_coord, &commit_hash, "Test PR for sync")
347 .expect("Failed to create PR event");
348
349 let pr_event_id = pr_event.id;
350
351 // Send PR event to source relay using PR author's client
352 let pr_client = Client::new(pr_author_keys.clone());
353 pr_client
354 .add_relay(source_relay.url())
355 .await
356 .expect("Failed to add source relay for PR");
357 pr_client.connect().await;
358 tokio::time::sleep(Duration::from_millis(500)).await;
359
360 pr_client
361 .send_event(&pr_event)
362 .await
363 .expect("Failed to send PR event to source");
364
365 // Small delay to ensure PR event is processed into purgatory
366 tokio::time::sleep(Duration::from_millis(200)).await;
367
368 // 5. Push commit to refs/nostr/<event-id> on source relay
369 // The PR event in purgatory authorizes this push
370 let ref_name = format!("refs/nostr/{}", pr_event_id.to_hex());
371 push_ref_to_relay(
372 temp_dir.path(),
373 &source_relay.domain(),
374 &npub,
375 identifier,
376 &commit_hash,
377 &ref_name,
378 )
379 .expect("Push to refs/nostr/<event-id> should succeed");
380
381 // After push, PR event should be released from purgatory on source relay
382 wait_for_event_served(source_relay.url(), &pr_event_id, Duration::from_secs(5))
383 .await
384 .expect("PR event should be served on source relay after push");
385
386 // 6. Start syncing relay (syncs from source)
387 let syncing_relay = TestRelay::start_on_port_with_options(
388 syncing_port,
389 Some(source_relay.url().to_string()),
390 false,
391 )
392 .await;
393
394 // Wait for sync connection to establish
395 wait_for_sync_connection(syncing_relay.url(), 1, Duration::from_secs(5))
396 .await
397 .expect("Sync connection should establish");
398
399 // 7. Wait for PR event to be released on syncing relay
400 // The sync should:
401 // a) Fetch the announcement and PR event from source relay
402 // b) Accept announcement (creates bare repo structure)
403 // c) Put PR event in purgatory (commit missing on syncing relay)
404 // d) Fetch commit from source relay's clone URL
405 // e) Release the PR event from purgatory
406 // f) Create refs/nostr/<event-id> pointing to the commit
407 let found = wait_for_event_served(
408 syncing_relay.url(),
409 &pr_event_id,
410 Duration::from_secs(30), // Allow time for sync + git fetch
411 )
412 .await;
413
414 assert!(
415 found.is_ok(),
416 "PR event should be served after sync fetches commit: {:?}",
417 found.err()
418 );
419
420 // 8. Verify refs/nostr/<event-id> was created on syncing relay
421 let ref_correct = check_ref_at_commit(
422 &syncing_domain,
423 &npub,
424 identifier,
425 &ref_name,
426 &commit_hash,
427 )
428 .await
429 .expect("Failed to check PR ref");
430
431 assert!(
432 ref_correct,
433 "refs/nostr/<event-id> should point to PR commit"
434 );
435
436 // Cleanup
437 source_client.disconnect().await;
438 pr_client.disconnect().await;
439 syncing_relay.stop().await;
440 source_relay.stop().await;
441}