mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
test(sync): reuse repository identity across relay fixtures
Paired setup calls constructed separate announcements, state events and Git commits for the same owner/identifier. Discovery could exchange those replaceable events while setup waited for an exact ID, making fixture identity depend on timestamp and event-ID ordering. Prepare one RepositoryFixture and install its unchanged signed events and Git data on each relay. Migrate all sixteen paired scenarios in descendant, discovery, live-sync, metrics and tag-variation tests. Historic tests retain the target listener reservation before seeding the source, so the original announcement can list both endpoints without starting target sync early. Keep exact-ID visibility checks, their deadlines and failure diagnostics. Add a wire-level regression checking both event IDs and remote Git refs after repeated installation. Production replacement policy, dependencies, intentional revision tests and concurrency remain unchanged. Validation: rustfmt and whitespace checks pass; paired setup callers audited. The new regression and migrated scenarios await CI execution, per the request to keep Cargo tests on CI/host. The preceding diagnostic CI revision stopped at a GitHub archive HTTP 504 before Rust tests and gave no further evidence about the original setup failure. Assisted-by: Codex (GPT-6)
This commit is contained in:
@@ -27,6 +27,24 @@ Connection tasks belong to their fixture's accept loop. Stopping the fixture
|
|||||||
cancels and joins them; dropping it cancels the owner. Git fixture subprocesses
|
cancels and joins them; dropping it cancels the owner. Git fixture subprocesses
|
||||||
are cancelled with their request, and all three pipes are driven concurrently.
|
are cancelled with their request, and all three pipes are driven concurrently.
|
||||||
|
|
||||||
|
## Shared repository identity
|
||||||
|
|
||||||
|
`setup_announcement_on_relay` returns an announcement and a `RepositoryFixture`.
|
||||||
|
Prepare it once for each logical repository, then call `fixture.install(&relay)`
|
||||||
|
for every other relay. It reuses the signed announcement, signed state event,
|
||||||
|
and Git commit. Repeating setup independently can create competing replaceable
|
||||||
|
events at the same owner/identifier and different Git histories.
|
||||||
|
|
||||||
|
List all participating domains when preparing the fixture. If historic tests
|
||||||
|
must seed a source before starting the syncing relay, reserve the target's
|
||||||
|
listener first and transfer it into the target after seeding. Do not use sleeps
|
||||||
|
or fabricated future timestamps to order copies of the same fixture. Tests of
|
||||||
|
actual repository revisions should construct those revisions explicitly.
|
||||||
|
|
||||||
|
The sync regression `reinstalling_shared_repository_preserves_event_ids_and_git_refs`
|
||||||
|
checks exact announcement/state IDs and remote Git refs across installation and
|
||||||
|
reinstallation on two relays.
|
||||||
|
|
||||||
## Time and streaming assertions
|
## Time and streaming assertions
|
||||||
|
|
||||||
Age private cache timestamps explicitly and bracket wall-clock timestamps
|
Age private cache timestamps explicitly and bracket wall-clock timestamps
|
||||||
|
|||||||
+109
-88
@@ -1351,100 +1351,121 @@ pub async fn push_unique_git_data_to_relay(
|
|||||||
git_temp_dir
|
git_temp_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set up a repository announcement on a relay with git data so it passes purgatory.
|
/// One repository's immutable Git data and signed events for all test relays.
|
||||||
///
|
///
|
||||||
/// With the announcement purgatory feature, announcements (kind 30617) require git
|
/// Preparing separate copies can mint competing replaceable events for the same
|
||||||
/// data before they are promoted to the relay's main DB. This helper:
|
/// owner/identifier and different Git commits. Prepare once, list every endpoint
|
||||||
///
|
/// (reserve its listener first if necessary), and install this fixture on each
|
||||||
/// 1. Creates a local git repo with a commit
|
/// relay. Keep it alive until the test finishes.
|
||||||
/// 2. Builds an announcement and state event (kind 30618) pointing to the relay
|
pub struct RepositoryFixture {
|
||||||
/// 3. Sends both to the relay (they go to purgatory)
|
git_dir: tempfile::TempDir,
|
||||||
/// 4. Git pushes to the relay → releases both from purgatory immediately
|
announcement: Event,
|
||||||
/// 5. Returns the announcement event and temp dir (keep alive for test duration)
|
state_event: Event,
|
||||||
///
|
keys: Keys,
|
||||||
/// # Arguments
|
identifier: String,
|
||||||
/// * `relay` - The relay to set up the announcement on
|
}
|
||||||
/// * `keys` - Keys to sign the announcement with (repo owner)
|
|
||||||
/// * `domains` - All domains that should be listed in the announcement (including relay.domain())
|
impl RepositoryFixture {
|
||||||
/// * `identifier` - Repository identifier (d-tag)
|
pub fn new(keys: &Keys, domains: &[&str], identifier: &str) -> Self {
|
||||||
///
|
use super::purgatory_helpers::{
|
||||||
/// # Returns
|
create_state_event, create_test_repo_with_commit, CommitVariant,
|
||||||
/// `(Event, tempfile::TempDir)` - The announcement event and temp dir.
|
};
|
||||||
/// The temp dir MUST be kept alive for the duration of the test.
|
|
||||||
|
let npub = keys
|
||||||
|
.public_key()
|
||||||
|
.to_bech32()
|
||||||
|
.expect("Failed to convert public key to npub");
|
||||||
|
|
||||||
|
// Create local git repo with a commit
|
||||||
|
let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
|
||||||
|
let commit_hash =
|
||||||
|
create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
|
||||||
|
.expect("Failed to create test git repo");
|
||||||
|
|
||||||
|
// Build clone URLs and relay URLs from domains
|
||||||
|
let clone_urls: Vec<String> = domains
|
||||||
|
.iter()
|
||||||
|
.map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
|
||||||
|
.collect();
|
||||||
|
let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
|
||||||
|
|
||||||
|
// Build announcement event (lists ALL domains for relay discovery)
|
||||||
|
let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
|
||||||
|
.tags(vec![
|
||||||
|
Tag::identifier(identifier),
|
||||||
|
Tag::custom("clone", clone_urls.clone()),
|
||||||
|
Tag::custom("relays", relay_urls.clone()),
|
||||||
|
])
|
||||||
|
.finalize(keys)
|
||||||
|
.expect("Failed to sign repo announcement");
|
||||||
|
|
||||||
|
// Build state event with all domains' clone URLs
|
||||||
|
let state_event = create_state_event(
|
||||||
|
keys,
|
||||||
|
identifier,
|
||||||
|
&[("main", &commit_hash)],
|
||||||
|
&[],
|
||||||
|
&clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
|
||||||
|
&relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.expect("Failed to create state event");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
git_dir: git_temp_dir,
|
||||||
|
announcement,
|
||||||
|
state_event,
|
||||||
|
keys: keys.clone(),
|
||||||
|
identifier: identifier.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &std::path::Path {
|
||||||
|
self.git_dir.path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publish the same events and push the same commit to another relay.
|
||||||
|
pub async fn install(&self, relay: &TestRelay) {
|
||||||
|
use super::purgatory_helpers::push_to_relay;
|
||||||
|
|
||||||
|
send_to_relay(relay, &self.announcement)
|
||||||
|
.await
|
||||||
|
.expect("Failed to send announcement");
|
||||||
|
send_to_relay(relay, &self.state_event)
|
||||||
|
.await
|
||||||
|
.expect("Failed to send state event");
|
||||||
|
let npub = self.keys.public_key().to_bech32().expect("owner npub");
|
||||||
|
push_to_relay(self.path(), &relay.domain(), &npub, &self.identifier)
|
||||||
|
.expect("Failed to push git data to relay");
|
||||||
|
|
||||||
|
for (label, event) in [
|
||||||
|
("state", &self.state_event),
|
||||||
|
("announcement", &self.announcement),
|
||||||
|
] {
|
||||||
|
let visible = wait_for_event_on_relay(
|
||||||
|
relay.url(),
|
||||||
|
Filter::new().id(event.id),
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if !visible {
|
||||||
|
report_setup_visibility_failure(relay, &self.keys, &self.identifier, event).await;
|
||||||
|
}
|
||||||
|
assert!(visible, "pushed {label} must leave purgatory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare and install a repository on its first relay. For another relay,
|
||||||
|
/// call `install` on the returned fixture instead of preparing a new copy.
|
||||||
pub async fn setup_announcement_on_relay(
|
pub async fn setup_announcement_on_relay(
|
||||||
relay: &TestRelay,
|
relay: &TestRelay,
|
||||||
keys: &Keys,
|
keys: &Keys,
|
||||||
domains: &[&str],
|
domains: &[&str],
|
||||||
identifier: &str,
|
identifier: &str,
|
||||||
) -> (Event, tempfile::TempDir) {
|
) -> (Event, RepositoryFixture) {
|
||||||
use super::purgatory_helpers::{
|
let fixture = RepositoryFixture::new(keys, domains, identifier);
|
||||||
create_state_event, create_test_repo_with_commit, push_to_relay, CommitVariant,
|
fixture.install(relay).await;
|
||||||
};
|
(fixture.announcement.clone(), fixture)
|
||||||
|
|
||||||
let npub = keys
|
|
||||||
.public_key()
|
|
||||||
.to_bech32()
|
|
||||||
.expect("Failed to convert public key to npub");
|
|
||||||
|
|
||||||
// Create local git repo with a commit
|
|
||||||
let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
|
|
||||||
let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
|
|
||||||
.expect("Failed to create test git repo");
|
|
||||||
|
|
||||||
// Build clone URLs and relay URLs from domains
|
|
||||||
let clone_urls: Vec<String> = domains
|
|
||||||
.iter()
|
|
||||||
.map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
|
|
||||||
.collect();
|
|
||||||
let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
|
|
||||||
|
|
||||||
// Build announcement event (lists ALL domains for relay discovery)
|
|
||||||
let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
|
|
||||||
.tags(vec![
|
|
||||||
Tag::identifier(identifier),
|
|
||||||
Tag::custom("clone", clone_urls.clone()),
|
|
||||||
Tag::custom("relays", relay_urls.clone()),
|
|
||||||
])
|
|
||||||
.finalize(keys)
|
|
||||||
.expect("Failed to sign repo announcement");
|
|
||||||
|
|
||||||
// Build state event with all domains' clone URLs
|
|
||||||
let state_event = create_state_event(
|
|
||||||
keys,
|
|
||||||
identifier,
|
|
||||||
&[("main", &commit_hash)],
|
|
||||||
&[],
|
|
||||||
&clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
|
|
||||||
&relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.expect("Failed to create state event");
|
|
||||||
|
|
||||||
// Send announcement and state event to relay (both go to purgatory)
|
|
||||||
send_to_relay(relay, &announcement)
|
|
||||||
.await
|
|
||||||
.expect("Failed to send announcement");
|
|
||||||
send_to_relay(relay, &state_event)
|
|
||||||
.await
|
|
||||||
.expect("Failed to send state event");
|
|
||||||
|
|
||||||
// Git push to relay → releases both from purgatory
|
|
||||||
push_to_relay(git_temp_dir.path(), &relay.domain(), &npub, identifier)
|
|
||||||
.expect("Failed to push git data to relay");
|
|
||||||
|
|
||||||
for (label, event) in [("state", &state_event), ("announcement", &announcement)] {
|
|
||||||
let visible = wait_for_event_on_relay(
|
|
||||||
relay.url(),
|
|
||||||
Filter::new().id(event.id),
|
|
||||||
Duration::from_secs(10),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
if !visible {
|
|
||||||
report_setup_visibility_failure(relay, keys, identifier, event).await;
|
|
||||||
}
|
|
||||||
assert!(visible, "pushed {label} must leave purgatory");
|
|
||||||
}
|
|
||||||
|
|
||||||
(announcement, git_temp_dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Diagnose a failed exact-ID check without extending its success deadline.
|
/// Diagnose a failed exact-ID check without extending its success deadline.
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ mod sync {
|
|||||||
pub mod proactive_sync_plus;
|
pub mod proactive_sync_plus;
|
||||||
pub mod purgatory_fetch;
|
pub mod purgatory_fetch;
|
||||||
pub mod reconnect_backoff;
|
pub mod reconnect_backoff;
|
||||||
|
pub mod repository_fixture;
|
||||||
pub mod req_concurrency;
|
pub mod req_concurrency;
|
||||||
pub mod stale_connect_result;
|
pub mod stale_connect_result;
|
||||||
pub mod tag_variations;
|
pub mod tag_variations;
|
||||||
|
|||||||
@@ -31,12 +31,14 @@ async fn historic_recursive_cap_is_enforced_and_reconstructed_after_restart() {
|
|||||||
let source = TestRelay::start_with_relay_max_subscriptions(4).await;
|
let source = TestRelay::start_with_relay_max_subscriptions(4).await;
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let repo_id = "restart-safe-recursive-cap";
|
let repo_id = "restart-safe-recursive-cap";
|
||||||
let source_domains = [source.domain()];
|
let syncing_reservation = reserve_port();
|
||||||
|
let syncing_domain = format!("127.0.0.1:{}", syncing_reservation.port());
|
||||||
|
let source_domains = [source.domain(), syncing_domain];
|
||||||
let source_refs = source_domains
|
let source_refs = source_domains
|
||||||
.iter()
|
.iter()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let (_announcement, _source_git) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
||||||
let source_client = TestClient::new(source.url(), keys.clone())
|
let source_client = TestClient::new(source.url(), keys.clone())
|
||||||
.await
|
.await
|
||||||
@@ -64,7 +66,7 @@ async fn historic_recursive_cap_is_enforced_and_reconstructed_after_restart() {
|
|||||||
let syncing_git_dir = tempfile::tempdir().expect("create syncing git dir");
|
let syncing_git_dir = tempfile::tempdir().expect("create syncing git dir");
|
||||||
let syncing_data_dir = tempfile::tempdir().expect("create syncing relay data dir");
|
let syncing_data_dir = tempfile::tempdir().expect("create syncing relay data dir");
|
||||||
let syncing = TestRelay::start_on_reservation_persistent_sync_with_recursive_limit(
|
let syncing = TestRelay::start_on_reservation_persistent_sync_with_recursive_limit(
|
||||||
reserve_port(),
|
syncing_reservation,
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
syncing_git_dir.path().to_path_buf(),
|
syncing_git_dir.path().to_path_buf(),
|
||||||
@@ -72,10 +74,7 @@ async fn historic_recursive_cap_is_enforced_and_reconstructed_after_restart() {
|
|||||||
LIMIT,
|
LIMIT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let domains = [source.domain(), syncing.domain()];
|
repository.install(&syncing).await;
|
||||||
let domain_refs = domains.iter().map(String::as_str).collect::<Vec<_>>();
|
|
||||||
let (_target_announcement, _target_git) =
|
|
||||||
setup_announcement_on_relay(&syncing, &keys, &domain_refs, repo_id).await;
|
|
||||||
|
|
||||||
for descendant in [&first, &second] {
|
for descendant in [&first, &second] {
|
||||||
assert!(
|
assert!(
|
||||||
@@ -154,12 +153,14 @@ async fn historic_sync_recovers_recursive_parent_only_descendants() {
|
|||||||
|
|
||||||
// Seed the source before the syncing relay exists so this exercises the
|
// Seed the source before the syncing relay exists so this exercises the
|
||||||
// complete historic rotation, not ordinary live coverage.
|
// complete historic rotation, not ordinary live coverage.
|
||||||
let source_domains = [source.domain()];
|
let syncing_reservation = reserve_port();
|
||||||
|
let syncing_domain = format!("127.0.0.1:{}", syncing_reservation.port());
|
||||||
|
let source_domains = [source.domain(), syncing_domain];
|
||||||
let source_refs = source_domains
|
let source_refs = source_domains
|
||||||
.iter()
|
.iter()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let (_announcement, _source_git) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
||||||
let source_client = TestClient::new(source.url(), keys.clone())
|
let source_client = TestClient::new(source.url(), keys.clone())
|
||||||
.await
|
.await
|
||||||
@@ -188,11 +189,9 @@ async fn historic_sync_recovers_recursive_parent_only_descendants() {
|
|||||||
.expect("seed source event");
|
.expect("seed source event");
|
||||||
}
|
}
|
||||||
|
|
||||||
let syncing = TestRelay::start_with_sync(None).await;
|
let syncing =
|
||||||
let domains = [source.domain(), syncing.domain()];
|
TestRelay::start_on_reservation_with_options(syncing_reservation, None, false).await;
|
||||||
let domain_refs = domains.iter().map(String::as_str).collect::<Vec<_>>();
|
repository.install(&syncing).await;
|
||||||
let (_target_announcement, _target_git) =
|
|
||||||
setup_announcement_on_relay(&syncing, &keys, &domain_refs, repo_id).await;
|
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
wait_for_event_on_relay(
|
wait_for_event_on_relay(
|
||||||
@@ -235,12 +234,14 @@ async fn descendant_live_coverage_is_preferred_when_capacity_remains() {
|
|||||||
let source = TestRelay::start().await;
|
let source = TestRelay::start().await;
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let repo_id = "live-descendant-coverage";
|
let repo_id = "live-descendant-coverage";
|
||||||
let source_domains = [source.domain()];
|
let syncing_reservation = reserve_port();
|
||||||
|
let syncing_domain = format!("127.0.0.1:{}", syncing_reservation.port());
|
||||||
|
let source_domains = [source.domain(), syncing_domain];
|
||||||
let source_refs = source_domains
|
let source_refs = source_domains
|
||||||
.iter()
|
.iter()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let (_announcement, _source_git) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
||||||
let source_client = TestClient::new(source.url(), keys.clone())
|
let source_client = TestClient::new(source.url(), keys.clone())
|
||||||
.await
|
.await
|
||||||
@@ -252,11 +253,9 @@ async fn descendant_live_coverage_is_preferred_when_capacity_remains() {
|
|||||||
source_client.send_event(&issue).await.unwrap();
|
source_client.send_event(&issue).await.unwrap();
|
||||||
source_client.send_event(&direct_reply).await.unwrap();
|
source_client.send_event(&direct_reply).await.unwrap();
|
||||||
|
|
||||||
let syncing = TestRelay::start_with_sync(None).await;
|
let syncing =
|
||||||
let domains = [source.domain(), syncing.domain()];
|
TestRelay::start_on_reservation_with_options(syncing_reservation, None, false).await;
|
||||||
let domain_refs = domains.iter().map(String::as_str).collect::<Vec<_>>();
|
repository.install(&syncing).await;
|
||||||
let (_target_announcement, _target_git) =
|
|
||||||
setup_announcement_on_relay(&syncing, &keys, &domain_refs, repo_id).await;
|
|
||||||
assert!(
|
assert!(
|
||||||
wait_for_log(
|
wait_for_log(
|
||||||
&syncing.log_path(),
|
&syncing.log_path(),
|
||||||
@@ -295,12 +294,14 @@ async fn historic_sync_recovers_address_tag_descendants() {
|
|||||||
let source = TestRelay::start_with_relay_max_subscriptions(4).await;
|
let source = TestRelay::start_with_relay_max_subscriptions(4).await;
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let repo_id = "addressable-descendant-history";
|
let repo_id = "addressable-descendant-history";
|
||||||
let source_domains = [source.domain()];
|
let syncing_reservation = reserve_port();
|
||||||
|
let syncing_domain = format!("127.0.0.1:{}", syncing_reservation.port());
|
||||||
|
let source_domains = [source.domain(), syncing_domain];
|
||||||
let source_refs = source_domains
|
let source_refs = source_domains
|
||||||
.iter()
|
.iter()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let (_announcement, _source_git) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
setup_announcement_on_relay(&source, &keys, &source_refs, repo_id).await;
|
||||||
let source_client = TestClient::new(source.url(), keys.clone())
|
let source_client = TestClient::new(source.url(), keys.clone())
|
||||||
.await
|
.await
|
||||||
@@ -334,11 +335,9 @@ async fn historic_sync_recovers_address_tag_descendants() {
|
|||||||
source_client.send_event(event).await.unwrap();
|
source_client.send_event(event).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let syncing = TestRelay::start_with_sync(None).await;
|
let syncing =
|
||||||
let domains = [source.domain(), syncing.domain()];
|
TestRelay::start_on_reservation_with_options(syncing_reservation, None, false).await;
|
||||||
let domain_refs = domains.iter().map(String::as_str).collect::<Vec<_>>();
|
repository.install(&syncing).await;
|
||||||
let (_target_announcement, _target_git) =
|
|
||||||
setup_announcement_on_relay(&syncing, &keys, &domain_refs, repo_id).await;
|
|
||||||
|
|
||||||
for event in &address_only_children {
|
for event in &address_only_children {
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ async fn test_discovers_layer3_via_layer2() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (announcement, _git_dir_a) =
|
let (announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
let announcement_id = announcement.id;
|
let announcement_id = announcement.id;
|
||||||
println!(
|
println!(
|
||||||
@@ -102,8 +102,7 @@ async fn test_discovers_layer3_via_layer2() {
|
|||||||
client_a.disconnect().await;
|
client_a.disconnect().await;
|
||||||
|
|
||||||
// 8. Set up announcement on relay_b (triggers discovery of relay_a)
|
// 8. Set up announcement on relay_b (triggers discovery of relay_a)
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b (should trigger discovery of relay_a)");
|
println!("Announcement set up on relay_b (should trigger discovery of relay_a)");
|
||||||
|
|
||||||
// 9/10. Verify the patch syncs to relay_b. The bounded poll on the synced
|
// 9/10. Verify the patch syncs to relay_b. The bounded poll on the synced
|
||||||
@@ -176,7 +175,7 @@ async fn test_relay_discovery_via_announcements_with_historic_sync() {
|
|||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
let repo_id = "test-repo-chain";
|
let repo_id = "test-repo-chain";
|
||||||
|
|
||||||
let (announcement, _git_dir_a) =
|
let (announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
let announcement_id = announcement.id;
|
let announcement_id = announcement.id;
|
||||||
println!(
|
println!(
|
||||||
@@ -207,8 +206,7 @@ async fn test_relay_discovery_via_announcements_with_historic_sync() {
|
|||||||
client_a.disconnect().await;
|
client_a.disconnect().await;
|
||||||
|
|
||||||
// 6. Set up announcement on relay_b (triggers discovery of relay_a)
|
// 6. Set up announcement on relay_b (triggers discovery of relay_a)
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b (should trigger discovery of relay_a)");
|
println!("Announcement set up on relay_b (should trigger discovery of relay_a)");
|
||||||
|
|
||||||
// 7/8. Verify the Layer 2 event syncs to relay_b. The bounded poll on the
|
// 7/8. Verify the Layer 2 event syncs to relay_b. The bounded poll on the
|
||||||
|
|||||||
@@ -299,12 +299,11 @@ async fn test_live_sync_layer2_events() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 5. Wait for discovery: events published before the syncing relay has
|
// 5. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -397,12 +396,11 @@ async fn test_live_sync_layer3_events() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -573,10 +571,9 @@ async fn test_live_sync_event_ordering() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcements set up on both relays with git data");
|
println!("Announcements set up on both relays with git data");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ async fn test_startup_sync_event_count() {
|
|||||||
let domains = [source_relay.domain(), syncing_relay.domain()];
|
let domains = [source_relay.domain(), syncing_relay.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (announcement, _git_dir_source) =
|
let (announcement, repository) =
|
||||||
setup_announcement_on_relay(&source_relay, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&source_relay, &keys, &domain_refs, repo_id).await;
|
||||||
println!(
|
println!(
|
||||||
"Announcement {} set up on source relay with git data",
|
"Announcement {} set up on source relay with git data",
|
||||||
@@ -286,8 +286,7 @@ async fn test_startup_sync_event_count() {
|
|||||||
source_client.disconnect().await;
|
source_client.disconnect().await;
|
||||||
|
|
||||||
// 8. Set up announcement on SYNCING relay (triggers discovery of source relay)
|
// 8. Set up announcement on SYNCING relay (triggers discovery of source relay)
|
||||||
let (_announcement_syncing, _git_dir_syncing) =
|
repository.install(&syncing_relay).await;
|
||||||
setup_announcement_on_relay(&syncing_relay, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on syncing relay (triggers discovery of source)");
|
println!("Announcement set up on syncing relay (triggers discovery of source)");
|
||||||
|
|
||||||
// 9. Wait for discovery + sync on the observable condition the test
|
// 9. Wait for discovery + sync on the observable condition the test
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
//! Reusing a repository fixture must not mint replaceable event revisions.
|
||||||
|
use std::{collections::BTreeSet, time::Duration};
|
||||||
|
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
use crate::common::{reserve_port, setup_announcement_on_relay, TestRelay};
|
||||||
|
|
||||||
|
async fn repository_event_ids(
|
||||||
|
relay: &TestRelay,
|
||||||
|
keys: &Keys,
|
||||||
|
identifier: &str,
|
||||||
|
) -> BTreeSet<EventId> {
|
||||||
|
let client = Client::builder()
|
||||||
|
.authenticator(SignerAuthenticator::new(keys.clone()))
|
||||||
|
.build();
|
||||||
|
client.add_relay(relay.url()).await.unwrap();
|
||||||
|
client
|
||||||
|
.try_connect_relay(relay.url(), Duration::from_secs(5))
|
||||||
|
.await
|
||||||
|
.expect("connect to installed repository");
|
||||||
|
let events = client
|
||||||
|
.fetch_events(
|
||||||
|
Filter::new()
|
||||||
|
.author(keys.public_key())
|
||||||
|
.identifier(identifier)
|
||||||
|
.kinds([Kind::GitRepoAnnouncement, Kind::RepoState]),
|
||||||
|
)
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.await
|
||||||
|
.expect("fetch installed repository events");
|
||||||
|
client.disconnect().await;
|
||||||
|
events.iter().map(|event| event.id).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remote_head(relay: &TestRelay, keys: &Keys, identifier: &str) -> String {
|
||||||
|
let url = format!(
|
||||||
|
"http://{}/{}/{}.git",
|
||||||
|
relay.domain(),
|
||||||
|
keys.public_key().to_bech32().unwrap(),
|
||||||
|
identifier
|
||||||
|
);
|
||||||
|
let output = tokio::process::Command::from(grasp_audit::git_command())
|
||||||
|
.args(["ls-remote", &url, "refs/heads/main"])
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.expect("query repository ref");
|
||||||
|
assert!(output.status.success());
|
||||||
|
String::from_utf8(output.stdout)
|
||||||
|
.unwrap()
|
||||||
|
.split_whitespace()
|
||||||
|
.next()
|
||||||
|
.expect("main ref exists")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reinstalling_shared_repository_preserves_event_ids_and_git_refs() {
|
||||||
|
tokio::time::timeout(Duration::from_secs(120), async {
|
||||||
|
let source = TestRelay::start().await;
|
||||||
|
let target_reservation = reserve_port();
|
||||||
|
let target_domain = format!("127.0.0.1:{}", target_reservation.port());
|
||||||
|
let domains = [source.domain(), target_domain];
|
||||||
|
let refs = domains.iter().map(String::as_str).collect::<Vec<_>>();
|
||||||
|
let keys = Keys::generate();
|
||||||
|
let identifier = "shared-fixture-identity";
|
||||||
|
let (announcement, repository) =
|
||||||
|
setup_announcement_on_relay(&source, &keys, &refs, identifier).await;
|
||||||
|
let original_ids = repository_event_ids(&source, &keys, identifier).await;
|
||||||
|
assert_eq!(
|
||||||
|
original_ids.len(),
|
||||||
|
2,
|
||||||
|
"announcement and state must both exist"
|
||||||
|
);
|
||||||
|
assert!(original_ids.contains(&announcement.id));
|
||||||
|
let original_head = remote_head(&source, &keys, identifier).await;
|
||||||
|
|
||||||
|
// Start the peer only after seeding the source, matching historic sync.
|
||||||
|
let target =
|
||||||
|
TestRelay::start_on_reservation_with_options(target_reservation, None, false).await;
|
||||||
|
repository.install(&target).await;
|
||||||
|
assert_eq!(
|
||||||
|
repository_event_ids(&target, &keys, identifier).await,
|
||||||
|
original_ids
|
||||||
|
);
|
||||||
|
assert_eq!(remote_head(&target, &keys, identifier).await, original_head);
|
||||||
|
|
||||||
|
// Reinstall after sync has seen the same address. There must be no new
|
||||||
|
// event IDs, timestamps or competing Git state in either direction.
|
||||||
|
repository.install(&source).await;
|
||||||
|
repository.install(&target).await;
|
||||||
|
for relay in [&source, &target] {
|
||||||
|
assert_eq!(
|
||||||
|
repository_event_ids(relay, &keys, identifier).await,
|
||||||
|
original_ids
|
||||||
|
);
|
||||||
|
assert_eq!(remote_head(relay, &keys, identifier).await, original_head);
|
||||||
|
}
|
||||||
|
target.stop().await;
|
||||||
|
source.stop().await;
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("shared repository setup and reinstalls must complete");
|
||||||
|
}
|
||||||
@@ -61,12 +61,11 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -152,12 +151,11 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -245,12 +243,11 @@ async fn test_layer2_sync_with_q_tag() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -344,12 +341,11 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -460,12 +456,11 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
@@ -580,12 +575,11 @@ async fn test_layer3_sync_with_q_tag() {
|
|||||||
let domains = [relay_a.domain(), relay_b.domain()];
|
let domains = [relay_a.domain(), relay_b.domain()];
|
||||||
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let (_announcement, _git_dir_a) =
|
let (_announcement, repository) =
|
||||||
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
|
||||||
println!("Announcement set up on relay_a with git data");
|
println!("Announcement set up on relay_a with git data");
|
||||||
|
|
||||||
let (_announcement_b, _git_dir_b) =
|
repository.install(&relay_b).await;
|
||||||
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
|
|
||||||
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
println!("Announcement set up on relay_b with git data (triggers discovery)");
|
||||||
|
|
||||||
// 3. Wait for discovery: events published before the syncing relay has
|
// 3. Wait for discovery: events published before the syncing relay has
|
||||||
|
|||||||
Reference in New Issue
Block a user