mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 23:18:24 +00:00
test: add test_state_event_syncs_from_remote integration test
Implements Phase 3 of the purgatory sync integration test plan. Key changes: - Add immediate sync triggering for sync-received events that go to purgatory (instead of default 3-minute delay for user-submitted events) - TestRelay now respects RUST_LOG environment variable for debugging - New test verifies end-to-end flow: state event syncs from source relay, enters purgatory, git data is fetched from source's clone URL, and event is released and served
This commit is contained in:
@@ -189,6 +189,8 @@ impl Purgatory {
|
||||
///
|
||||
/// Automatically enqueues the identifier for background sync with the default delay
|
||||
/// (3 minutes), giving time for a git push to arrive after the nostr event.
|
||||
/// For sync-triggered events, the SyncManager calls `enqueue_sync_immediate` separately
|
||||
/// to override this delay.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `event` - The state event (kind 30618) to hold
|
||||
@@ -210,6 +212,7 @@ impl Purgatory {
|
||||
.push(entry);
|
||||
|
||||
// Enqueue for background sync with default delay
|
||||
// (SyncManager will call enqueue_sync_immediate for sync-triggered events)
|
||||
self.enqueue_sync_default(&identifier);
|
||||
}
|
||||
|
||||
|
||||
@@ -1046,6 +1046,40 @@ impl SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
// For sync-triggered events that go to purgatory, trigger immediate sync
|
||||
// (instead of the default 3-minute delay for user-submitted events)
|
||||
if result == ProcessResult::Purgatory {
|
||||
// State events (kind 30618) - extract identifier and trigger immediate sync
|
||||
if event.kind.as_u16() == 30618 {
|
||||
if let Some(identifier) = event.tags.iter().find_map(|tag| {
|
||||
let tag_vec = tag.clone().to_vec();
|
||||
if tag_vec.len() >= 2 && tag_vec[0] == "d" {
|
||||
Some(tag_vec[1].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
tracing::debug!(
|
||||
event_id = %event.id,
|
||||
identifier = %identifier,
|
||||
"Triggering immediate sync for synced state event in purgatory"
|
||||
);
|
||||
write_policy.purgatory().enqueue_sync_immediate(&identifier);
|
||||
}
|
||||
}
|
||||
// PR events (kind 1617/1618) - extract identifier from 'a' tag
|
||||
else if event.kind.as_u16() == 1617 || event.kind.as_u16() == 1618 {
|
||||
if let Some(identifier) = crate::git::sync::extract_identifier_from_pr_event(&event) {
|
||||
tracing::debug!(
|
||||
event_id = %event.id,
|
||||
identifier = %identifier,
|
||||
"Triggering immediate sync for synced PR event in purgatory"
|
||||
);
|
||||
write_policy.purgatory().enqueue_sync_immediate(&identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track pagination state for this subscription
|
||||
if result == ProcessResult::Saved || result == ProcessResult::Duplicate {
|
||||
let mut pending = pending_sync_index.write().await;
|
||||
|
||||
@@ -144,10 +144,9 @@ impl TestRelay {
|
||||
.env("NGIT_SYNC_STARTUP_JITTER_MS", "0") // No jitter for tests
|
||||
.env("NGIT_SYNC_DISCONNECT_CHECK_INTERVAL_SECS", "1") // Fast reconnect attempts for tests
|
||||
.env("NGIT_SYNC_BASE_BACKOFF_SECS", "1") // Fast backoff for tests (1s instead of 5s default)
|
||||
.env("RUST_LOG", "info") // Enable INFO logging for diagnostics
|
||||
.stdout(Stdio::null()) // Disable stderr for cleaner test output
|
||||
// .stdout(Stdio::inherit()) // Show stdout for diagnostics
|
||||
.stderr(Stdio::null()); // Disable stderr for cleaner test output
|
||||
.env("RUST_LOG", std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string())) // Use RUST_LOG from environment or default to info
|
||||
.stdout(Stdio::null()) // Suppress stdout for cleaner test output
|
||||
.stderr(Stdio::null()); // Suppress stderr for cleaner test output
|
||||
|
||||
// Add bootstrap relay URL if provided
|
||||
if let Some(ref bootstrap_url) = bootstrap_relay_url {
|
||||
|
||||
+155
-2
@@ -28,8 +28,9 @@
|
||||
mod common;
|
||||
|
||||
use common::{
|
||||
create_repo_announcement, create_state_event, create_test_repo_with_commit, push_to_relay,
|
||||
verify_event_not_served, wait_for_event_served, CommitVariant, TestRelay,
|
||||
check_ref_at_commit, create_repo_announcement, create_state_event,
|
||||
create_test_repo_with_commit, push_to_relay, verify_event_not_served, wait_for_event_served,
|
||||
wait_for_sync_connection, CommitVariant, TestRelay,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use std::time::Duration;
|
||||
@@ -124,3 +125,155 @@ async fn test_push_triggers_unified_processing() {
|
||||
client.disconnect().await;
|
||||
relay.stop().await;
|
||||
}
|
||||
|
||||
/// Test that a state event entering purgatory triggers remote git fetch
|
||||
/// and is released once the git data is available.
|
||||
///
|
||||
/// Scenario:
|
||||
/// 1. Start source relay with git repository containing test commit
|
||||
/// 2. Start syncing relay that syncs from source
|
||||
/// 3. Syncing relay syncs state event (goes to purgatory - no local git data)
|
||||
/// 4. Wait for sync to fetch git data from source's clone URL
|
||||
/// 5. Verify state event is released and served on syncing relay
|
||||
#[tokio::test]
|
||||
async fn test_state_event_syncs_from_remote() {
|
||||
// 1. Start source relay
|
||||
let source_relay = TestRelay::start().await;
|
||||
let keys = Keys::generate();
|
||||
let identifier = "state-sync-test-repo";
|
||||
|
||||
// Pre-allocate syncing relay port so we can include it in announcement
|
||||
let syncing_port = TestRelay::find_free_port();
|
||||
let syncing_domain = format!("127.0.0.1:{}", syncing_port);
|
||||
|
||||
// 2. Create test repository locally with deterministic commit
|
||||
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
|
||||
let commit_hash = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest)
|
||||
.expect("Failed to create test repo");
|
||||
|
||||
let npub = keys.public_key().to_bech32().expect("Failed to get npub");
|
||||
|
||||
// 3. Create and send announcement listing BOTH relays
|
||||
// This ensures the syncing relay will accept the state event when it syncs
|
||||
let announcement = create_repo_announcement(
|
||||
&keys,
|
||||
&[&source_relay.domain(), &syncing_domain],
|
||||
identifier,
|
||||
);
|
||||
|
||||
let source_client = Client::new(keys.clone());
|
||||
source_client
|
||||
.add_relay(source_relay.url())
|
||||
.await
|
||||
.expect("Failed to add source relay");
|
||||
source_client.connect().await;
|
||||
|
||||
// Wait for connection
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Send announcement to source relay
|
||||
source_client
|
||||
.send_event(&announcement)
|
||||
.await
|
||||
.expect("Failed to send announcement to source");
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
// 4. Create and send state event BEFORE pushing
|
||||
// The state event goes to purgatory on source relay, which authorizes the push
|
||||
let clone_urls = [
|
||||
format!(
|
||||
"http://{}/{}/{}.git",
|
||||
source_relay.domain(),
|
||||
npub,
|
||||
identifier
|
||||
),
|
||||
format!("http://{}/{}/{}.git", syncing_domain, npub, identifier),
|
||||
];
|
||||
let relay_urls = [
|
||||
source_relay.url().to_string(),
|
||||
format!("ws://{}", syncing_domain),
|
||||
];
|
||||
|
||||
let state_event = create_state_event(
|
||||
&keys,
|
||||
identifier,
|
||||
&[("main", &commit_hash)],
|
||||
&[],
|
||||
&[&clone_urls[0], &clone_urls[1]],
|
||||
&[&relay_urls[0], &relay_urls[1]],
|
||||
)
|
||||
.expect("Failed to create state event");
|
||||
|
||||
let state_event_id = state_event.id;
|
||||
|
||||
// Send state event to source relay (goes to purgatory - no git data yet)
|
||||
source_client
|
||||
.send_event(&state_event)
|
||||
.await
|
||||
.expect("Failed to send state event to source");
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
// 5. Push git data to source relay
|
||||
// The state event in purgatory authorizes this push
|
||||
push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier)
|
||||
.expect("Push to source should succeed");
|
||||
|
||||
// After push, state event should be released from purgatory on source relay
|
||||
// Verify source relay is serving the state event
|
||||
wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5))
|
||||
.await
|
||||
.expect("State event should be served on source relay after push");
|
||||
|
||||
// 6. Start syncing relay (syncs from source)
|
||||
let syncing_relay = TestRelay::start_on_port_with_options(
|
||||
syncing_port,
|
||||
Some(source_relay.url().to_string()),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait for sync connection to establish
|
||||
wait_for_sync_connection(syncing_relay.url(), 1, Duration::from_secs(5))
|
||||
.await
|
||||
.expect("Sync connection should establish");
|
||||
|
||||
// 7. Wait for state event to be released on syncing relay
|
||||
// The sync should:
|
||||
// a) Fetch the announcement and state event from source relay
|
||||
// b) Accept announcement (creates bare repo structure)
|
||||
// c) Put state event in purgatory (git data missing on syncing relay)
|
||||
// d) Fetch git data from source relay's clone URL
|
||||
// e) Release the state event from purgatory
|
||||
let found = wait_for_event_served(
|
||||
syncing_relay.url(),
|
||||
&state_event_id,
|
||||
Duration::from_secs(30), // Allow time for sync + git fetch
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
found.is_ok(),
|
||||
"State event should be served after sync fetches git data: {:?}",
|
||||
found.err()
|
||||
);
|
||||
|
||||
// 8. Verify refs are correct on syncing relay
|
||||
let ref_correct = check_ref_at_commit(
|
||||
&syncing_domain,
|
||||
&npub,
|
||||
identifier,
|
||||
"refs/heads/main",
|
||||
&commit_hash,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to check ref");
|
||||
|
||||
assert!(ref_correct, "main branch should point to correct commit");
|
||||
|
||||
// Cleanup
|
||||
source_client.disconnect().await;
|
||||
syncing_relay.stop().await;
|
||||
source_relay.stop().await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user