mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
refactor: move archive_read_only test to archive_grasp_services and remove redundant test
This commit is contained in:
@@ -29,7 +29,11 @@
|
||||
|
||||
mod common;
|
||||
|
||||
use common::TestRelay;
|
||||
use common::{
|
||||
check_ref_at_commit, create_repo_announcement, create_state_event,
|
||||
create_test_repo_with_commit, push_to_relay, wait_for_event_served, wait_for_sync_connection,
|
||||
CommitVariant, TestRelay,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
@@ -376,3 +380,222 @@ async fn test_archive_multiple_grasp_services() {
|
||||
let _ = process.kill();
|
||||
let _ = process.wait();
|
||||
}
|
||||
|
||||
/// Test that archive_read_only mode creates bare git repositories and syncs data
|
||||
/// via relay-to-relay sync (purgatory sync infrastructure).
|
||||
///
|
||||
/// Scenario:
|
||||
/// 1. Start source relay with full repository (announcement + state + git data)
|
||||
/// 2. Start archive relay with archive_all=true, archive_read_only=true, syncing from source
|
||||
/// 3. Archive relay syncs announcement and state events from source
|
||||
/// 4. State events trigger purgatory sync which fetches git data from source's clone URL
|
||||
/// 5. Verify bare repository is created and git data is synced
|
||||
/// 6. Verify git pushes are rejected (read-only mode)
|
||||
#[tokio::test]
|
||||
async fn test_archive_read_only_creates_bare_repo() {
|
||||
// 1. Start source relay
|
||||
let source_relay = TestRelay::start().await;
|
||||
let keys = Keys::generate();
|
||||
let identifier = "archive-test-repo";
|
||||
|
||||
// Pre-allocate archive relay port so we can include it in announcement
|
||||
let archive_port = TestRelay::find_free_port();
|
||||
let archive_domain = format!("127.0.0.1:{}", archive_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 archive relay will accept the state event when it syncs
|
||||
let announcement = create_repo_announcement(
|
||||
&keys,
|
||||
&[&source_relay.domain(), &archive_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
|
||||
let clone_urls = [
|
||||
format!(
|
||||
"http://{}/{}/{}.git",
|
||||
source_relay.domain(),
|
||||
npub,
|
||||
identifier
|
||||
),
|
||||
format!("http://{}/{}/{}.git", archive_domain, npub, identifier),
|
||||
];
|
||||
let relay_urls = [
|
||||
source_relay.url().to_string(),
|
||||
format!("ws://{}", archive_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
|
||||
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 archive relay with archive_all=true, archive_read_only=true, syncing from source
|
||||
let archive_relay = TestRelay::start_with_archive_and_sync(
|
||||
archive_port,
|
||||
Some(source_relay.url().to_string()),
|
||||
false, // negentropy enabled
|
||||
true, // archive_all
|
||||
true, // archive_read_only
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait for sync connection to establish
|
||||
wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5))
|
||||
.await
|
||||
.expect("Sync connection should establish");
|
||||
|
||||
// 7. Wait for state event to be released on archive relay
|
||||
// The sync should:
|
||||
// a) Fetch the announcement and state event from source relay
|
||||
// b) Accept announcement (creates bare repo structure) - via archive mode
|
||||
// c) Put state event in purgatory (git data missing on archive relay)
|
||||
// d) Fetch git data from source relay's clone URL
|
||||
// e) Release the state event from purgatory
|
||||
|
||||
let found = wait_for_event_served(
|
||||
archive_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 bare repository was created
|
||||
let repo_path = archive_relay
|
||||
.git_data_path()
|
||||
.join(format!("{}/{}.git", npub, identifier));
|
||||
|
||||
assert!(
|
||||
repo_path.exists(),
|
||||
"Bare repository should be created at {:?} for archive announcement",
|
||||
repo_path
|
||||
);
|
||||
|
||||
// 9. Verify it's a bare repository (check for config file with bare = true)
|
||||
let config_path = repo_path.join("config");
|
||||
assert!(
|
||||
config_path.exists(),
|
||||
"Git config should exist at {:?}",
|
||||
config_path
|
||||
);
|
||||
|
||||
let config_content = tokio::fs::read_to_string(&config_path)
|
||||
.await
|
||||
.expect("Should read git config");
|
||||
assert!(
|
||||
config_content.contains("bare = true"),
|
||||
"Repository at {:?} should be bare (config should contain 'bare = true')",
|
||||
repo_path
|
||||
);
|
||||
|
||||
// 10. Verify refs are correct on archive relay
|
||||
let ref_correct = check_ref_at_commit(
|
||||
&archive_domain,
|
||||
&npub,
|
||||
identifier,
|
||||
"refs/heads/main",
|
||||
&commit_hash,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to check ref");
|
||||
|
||||
assert!(ref_correct, "main branch should point to correct commit");
|
||||
|
||||
// 11. Verify git pushes are rejected (read-only mode)
|
||||
// Create a new commit in the source repo
|
||||
tokio::fs::write(temp_dir.path().join("new_file.txt"), "new content")
|
||||
.await
|
||||
.expect("Failed to write new file");
|
||||
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(temp_dir.path())
|
||||
.output()
|
||||
.await
|
||||
.expect("Failed to git add");
|
||||
assert!(output.status.success());
|
||||
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["commit", "-m", "New commit for push test"])
|
||||
.current_dir(temp_dir.path())
|
||||
.output()
|
||||
.await
|
||||
.expect("Failed to git commit");
|
||||
assert!(output.status.success());
|
||||
|
||||
// Try to push to archive relay (should fail in read-only mode)
|
||||
let push_url = format!("http://{}/{}/{}.git", archive_domain, npub, identifier);
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["push", &push_url, "main"])
|
||||
.current_dir(temp_dir.path())
|
||||
.output()
|
||||
.await
|
||||
.expect("Failed to run git push");
|
||||
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"Git push should be rejected in archive_read_only mode. stderr: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
source_client.disconnect().await;
|
||||
archive_relay.stop().await;
|
||||
source_relay.stop().await;
|
||||
}
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
//! Archive Read-Only Mode Integration Tests
|
||||
//!
|
||||
//! Tests that verify archive_read_only mode behavior:
|
||||
//! - Bare git repositories are created for announcements
|
||||
//! - Git data is synced via relay-to-relay sync (purgatory sync)
|
||||
//! - Git pushes are rejected (read-only mode)
|
||||
//!
|
||||
//! # Test Strategy
|
||||
//!
|
||||
//! These tests verify the GRASP-05 archive mode with read_only flag:
|
||||
//! 1. Source relay has full repository (announcement + state events + git data)
|
||||
//! 2. Archive relay syncs from source relay (relay-to-relay sync)
|
||||
//! 3. State events trigger purgatory sync which fetches git data
|
||||
//! 4. Git data is validated against Nostr state events
|
||||
//! 5. Git pushes are rejected (read-only enforcement)
|
||||
//!
|
||||
//! # Security Model
|
||||
//!
|
||||
//! Archive mode uses the existing purgatory sync infrastructure to ensure:
|
||||
//! - Git data is validated against Nostr state events
|
||||
//! - "Naughty git servers" can't provide incorrect state
|
||||
//! - Same security guarantees as normal relay operation
|
||||
//!
|
||||
//! # Running Tests
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Run all archive read-only tests
|
||||
//! cargo test --test archive_read_only
|
||||
//!
|
||||
//! # Run specific test
|
||||
//! cargo test --test archive_read_only test_archive_read_only_creates_bare_repo
|
||||
//!
|
||||
//! # With output for debugging
|
||||
//! cargo test --test archive_read_only -- --nocapture
|
||||
//! ```
|
||||
|
||||
mod common;
|
||||
|
||||
use common::{
|
||||
check_ref_at_commit, create_repo_announcement, create_state_event,
|
||||
create_test_repo_with_commit, push_to_relay, wait_for_event_served, wait_for_sync_connection,
|
||||
CommitVariant, TestRelay,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Test that archive_read_only mode creates bare git repositories and syncs data
|
||||
/// via relay-to-relay sync (purgatory sync infrastructure).
|
||||
///
|
||||
/// Scenario:
|
||||
/// 1. Start source relay with full repository (announcement + state + git data)
|
||||
/// 2. Start archive relay with archive_all=true, archive_read_only=true, syncing from source
|
||||
/// 3. Archive relay syncs announcement and state events from source
|
||||
/// 4. State events trigger purgatory sync which fetches git data from source's clone URL
|
||||
/// 5. Verify bare repository is created and git data is synced
|
||||
/// 6. Verify git pushes are rejected (read-only mode)
|
||||
#[tokio::test]
|
||||
async fn test_archive_read_only_creates_bare_repo() {
|
||||
// 1. Start source relay
|
||||
let source_relay = TestRelay::start().await;
|
||||
let keys = Keys::generate();
|
||||
let identifier = "archive-test-repo";
|
||||
|
||||
// Pre-allocate archive relay port so we can include it in announcement
|
||||
let archive_port = TestRelay::find_free_port();
|
||||
let archive_domain = format!("127.0.0.1:{}", archive_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 archive relay will accept the state event when it syncs
|
||||
let announcement = create_repo_announcement(
|
||||
&keys,
|
||||
&[&source_relay.domain(), &archive_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
|
||||
let clone_urls = [
|
||||
format!(
|
||||
"http://{}/{}/{}.git",
|
||||
source_relay.domain(),
|
||||
npub,
|
||||
identifier
|
||||
),
|
||||
format!("http://{}/{}/{}.git", archive_domain, npub, identifier),
|
||||
];
|
||||
let relay_urls = [
|
||||
source_relay.url().to_string(),
|
||||
format!("ws://{}", archive_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
|
||||
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 archive relay with archive_all=true, archive_read_only=true, syncing from source
|
||||
let archive_relay = TestRelay::start_with_archive_and_sync(
|
||||
archive_port,
|
||||
Some(source_relay.url().to_string()),
|
||||
false, // negentropy enabled
|
||||
true, // archive_all
|
||||
true, // archive_read_only
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait for sync connection to establish
|
||||
wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5))
|
||||
.await
|
||||
.expect("Sync connection should establish");
|
||||
|
||||
// 7. Wait for state event to be released on archive relay
|
||||
// The sync should:
|
||||
// a) Fetch the announcement and state event from source relay
|
||||
// b) Accept announcement (creates bare repo structure) - via archive mode
|
||||
// c) Put state event in purgatory (git data missing on archive relay)
|
||||
// d) Fetch git data from source relay's clone URL
|
||||
// e) Release the state event from purgatory
|
||||
|
||||
let found = wait_for_event_served(
|
||||
archive_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 bare repository was created
|
||||
let repo_path = archive_relay
|
||||
.git_data_path()
|
||||
.join(format!("{}/{}.git", npub, identifier));
|
||||
|
||||
assert!(
|
||||
repo_path.exists(),
|
||||
"Bare repository should be created at {:?} for archive announcement",
|
||||
repo_path
|
||||
);
|
||||
|
||||
// 9. Verify it's a bare repository (check for config file with bare = true)
|
||||
let config_path = repo_path.join("config");
|
||||
assert!(
|
||||
config_path.exists(),
|
||||
"Git config should exist at {:?}",
|
||||
config_path
|
||||
);
|
||||
|
||||
let config_content = tokio::fs::read_to_string(&config_path)
|
||||
.await
|
||||
.expect("Should read git config");
|
||||
assert!(
|
||||
config_content.contains("bare = true"),
|
||||
"Repository at {:?} should be bare (config should contain 'bare = true')",
|
||||
repo_path
|
||||
);
|
||||
|
||||
// 10. Verify refs are correct on archive relay
|
||||
let ref_correct = check_ref_at_commit(
|
||||
&archive_domain,
|
||||
&npub,
|
||||
identifier,
|
||||
"refs/heads/main",
|
||||
&commit_hash,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to check ref");
|
||||
|
||||
assert!(ref_correct, "main branch should point to correct commit");
|
||||
|
||||
// 11. Verify git pushes are rejected (read-only mode)
|
||||
// Create a new commit in the source repo
|
||||
tokio::fs::write(temp_dir.path().join("new_file.txt"), "new content")
|
||||
.await
|
||||
.expect("Failed to write new file");
|
||||
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["add", "."])
|
||||
.current_dir(temp_dir.path())
|
||||
.output()
|
||||
.await
|
||||
.expect("Failed to git add");
|
||||
assert!(output.status.success());
|
||||
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["commit", "-m", "New commit for push test"])
|
||||
.current_dir(temp_dir.path())
|
||||
.output()
|
||||
.await
|
||||
.expect("Failed to git commit");
|
||||
assert!(output.status.success());
|
||||
|
||||
// Try to push to archive relay (should fail in read-only mode)
|
||||
let push_url = format!("http://{}/{}/{}.git", archive_domain, npub, identifier);
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["push", &push_url, "main"])
|
||||
.current_dir(temp_dir.path())
|
||||
.output()
|
||||
.await
|
||||
.expect("Failed to run git push");
|
||||
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"Git push should be rejected in archive_read_only mode. stderr: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
source_client.disconnect().await;
|
||||
archive_relay.stop().await;
|
||||
source_relay.stop().await;
|
||||
}
|
||||
|
||||
/// Test that archive mode proactively syncs state events and git data
|
||||
/// when the source relay has state events available.
|
||||
///
|
||||
/// With StateOnly sync now implemented, purgatory announcements subscribe
|
||||
/// to state events from the relays listed in the announcement. This means
|
||||
/// the archive relay will:
|
||||
/// 1. Sync the announcement → purgatory → register as StateOnly in repo_sync_index
|
||||
/// 2. Subscribe to state events (kind 30618) on source relay
|
||||
/// 3. Receive the state event → purgatory sync triggered
|
||||
/// 4. Fetch git data from source relay's clone URL
|
||||
///
|
||||
/// This test verifies the full sync chain works end-to-end for archive mode.
|
||||
#[tokio::test]
|
||||
async fn test_archive_syncs_state_events_and_git_data_via_state_only_subscription() {
|
||||
// 1. Start source relay
|
||||
let source_relay = TestRelay::start().await;
|
||||
let keys = Keys::generate();
|
||||
let identifier = "archive-state-only-sync-repo";
|
||||
|
||||
// Pre-allocate archive relay port
|
||||
let archive_port = TestRelay::find_free_port();
|
||||
let archive_domain = format!("127.0.0.1:{}", archive_port);
|
||||
|
||||
// 2. Create test repository locally
|
||||
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
|
||||
// The archive relay will subscribe to state events on BOTH listed relays
|
||||
let announcement = create_repo_announcement(
|
||||
&keys,
|
||||
&[&source_relay.domain(), &archive_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;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Send announcement to source relay (goes to purgatory)
|
||||
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 to source relay (goes to purgatory)
|
||||
let clone_url = format!(
|
||||
"http://{}/{}/{}.git",
|
||||
source_relay.domain(),
|
||||
npub,
|
||||
identifier
|
||||
);
|
||||
let relay_url = source_relay.url().to_string();
|
||||
|
||||
let state_event = create_state_event(
|
||||
&keys,
|
||||
identifier,
|
||||
&[("main", &commit_hash)],
|
||||
&[],
|
||||
&[&clone_url],
|
||||
&[&relay_url],
|
||||
)
|
||||
.expect("Failed to create state event");
|
||||
|
||||
let state_event_id = state_event.id;
|
||||
|
||||
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 (promotes announcement and state event)
|
||||
push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier)
|
||||
.expect("Push to source should succeed");
|
||||
|
||||
// Wait for state event to be promoted on source relay
|
||||
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 archive relay - StateOnly subscription will proactively fetch state events
|
||||
let archive_relay = TestRelay::start_with_archive_and_sync(
|
||||
archive_port,
|
||||
Some(source_relay.url().to_string()),
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait for sync connection
|
||||
wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5))
|
||||
.await
|
||||
.expect("Sync connection should establish");
|
||||
|
||||
// 7. Wait for state event to be served on archive relay
|
||||
// The StateOnly subscription fetches the state event from source relay,
|
||||
// which then triggers purgatory sync and git data fetch.
|
||||
let found = wait_for_event_served(
|
||||
archive_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 on archive after StateOnly subscription fetches it: {:?}",
|
||||
found.err()
|
||||
);
|
||||
|
||||
// 8. Verify bare repository was created
|
||||
let repo_path = archive_relay
|
||||
.git_data_path()
|
||||
.join(format!("{}/{}.git", npub, identifier));
|
||||
|
||||
assert!(
|
||||
repo_path.exists(),
|
||||
"Bare repository should be created for archive announcement"
|
||||
);
|
||||
|
||||
// 9. Verify git data was synced via the state event chain
|
||||
let output = tokio::process::Command::new("git")
|
||||
.args(["cat-file", "-t", &commit_hash])
|
||||
.current_dir(&repo_path)
|
||||
.output()
|
||||
.await;
|
||||
|
||||
let commit_exists = output.map(|o| o.status.success()).unwrap_or(false);
|
||||
|
||||
assert!(
|
||||
commit_exists,
|
||||
"Git data should be synced via StateOnly subscription → state event → git fetch chain"
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
source_client.disconnect().await;
|
||||
archive_relay.stop().await;
|
||||
source_relay.stop().await;
|
||||
}
|
||||
Reference in New Issue
Block a user