diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-07 15:39:48 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-07 15:39:48 +0000 |
| commit | 1db877d53c4ff45971c69fecc5165c352ec316c9 (patch) | |
| tree | 4e63366e49430320117ac7d207b9c2f034f8f7b5 /tests | |
| parent | 7dcbc84806e7b3000835eb9132dfc4e9003e382a (diff) | |
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
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/common/relay.rs | 7 | ||||
| -rw-r--r-- | tests/purgatory_sync.rs | 157 |
2 files changed, 158 insertions, 6 deletions
diff --git a/tests/common/relay.rs b/tests/common/relay.rs index 55cc18e..8d20da6 100644 --- a/tests/common/relay.rs +++ b/tests/common/relay.rs | |||
| @@ -144,10 +144,9 @@ impl TestRelay { | |||
| 144 | .env("NGIT_SYNC_STARTUP_JITTER_MS", "0") // No jitter for tests | 144 | .env("NGIT_SYNC_STARTUP_JITTER_MS", "0") // No jitter for tests |
| 145 | .env("NGIT_SYNC_DISCONNECT_CHECK_INTERVAL_SECS", "1") // Fast reconnect attempts for tests | 145 | .env("NGIT_SYNC_DISCONNECT_CHECK_INTERVAL_SECS", "1") // Fast reconnect attempts for tests |
| 146 | .env("NGIT_SYNC_BASE_BACKOFF_SECS", "1") // Fast backoff for tests (1s instead of 5s default) | 146 | .env("NGIT_SYNC_BASE_BACKOFF_SECS", "1") // Fast backoff for tests (1s instead of 5s default) |
| 147 | .env("RUST_LOG", "info") // Enable INFO logging for diagnostics | 147 | .env("RUST_LOG", std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string())) // Use RUST_LOG from environment or default to info |
| 148 | .stdout(Stdio::null()) // Disable stderr for cleaner test output | 148 | .stdout(Stdio::null()) // Suppress stdout for cleaner test output |
| 149 | // .stdout(Stdio::inherit()) // Show stdout for diagnostics | 149 | .stderr(Stdio::null()); // Suppress stderr for cleaner test output |
| 150 | .stderr(Stdio::null()); // Disable stderr for cleaner test output | ||
| 151 | 150 | ||
| 152 | // Add bootstrap relay URL if provided | 151 | // Add bootstrap relay URL if provided |
| 153 | if let Some(ref bootstrap_url) = bootstrap_relay_url { | 152 | if let Some(ref bootstrap_url) = bootstrap_relay_url { |
diff --git a/tests/purgatory_sync.rs b/tests/purgatory_sync.rs index 1065c9d..3da086c 100644 --- a/tests/purgatory_sync.rs +++ b/tests/purgatory_sync.rs | |||
| @@ -28,8 +28,9 @@ | |||
| 28 | mod common; | 28 | mod common; |
| 29 | 29 | ||
| 30 | use common::{ | 30 | use common::{ |
| 31 | create_repo_announcement, create_state_event, create_test_repo_with_commit, push_to_relay, | 31 | check_ref_at_commit, create_repo_announcement, create_state_event, |
| 32 | verify_event_not_served, wait_for_event_served, CommitVariant, TestRelay, | 32 | create_test_repo_with_commit, push_to_relay, verify_event_not_served, wait_for_event_served, |
| 33 | wait_for_sync_connection, CommitVariant, TestRelay, | ||
| 33 | }; | 34 | }; |
| 34 | use nostr_sdk::prelude::*; | 35 | use nostr_sdk::prelude::*; |
| 35 | use std::time::Duration; | 36 | use std::time::Duration; |
| @@ -124,3 +125,155 @@ async fn test_push_triggers_unified_processing() { | |||
| 124 | client.disconnect().await; | 125 | client.disconnect().await; |
| 125 | relay.stop().await; | 126 | relay.stop().await; |
| 126 | } | 127 | } |
| 128 | |||
| 129 | /// Test that a state event entering purgatory triggers remote git fetch | ||
| 130 | /// and is released once the git data is available. | ||
| 131 | /// | ||
| 132 | /// Scenario: | ||
| 133 | /// 1. Start source relay with git repository containing test commit | ||
| 134 | /// 2. Start syncing relay that syncs from source | ||
| 135 | /// 3. Syncing relay syncs state event (goes to purgatory - no local git data) | ||
| 136 | /// 4. Wait for sync to fetch git data from source's clone URL | ||
| 137 | /// 5. Verify state event is released and served on syncing relay | ||
| 138 | #[tokio::test] | ||
| 139 | async fn test_state_event_syncs_from_remote() { | ||
| 140 | // 1. Start source relay | ||
| 141 | let source_relay = TestRelay::start().await; | ||
| 142 | let keys = Keys::generate(); | ||
| 143 | let identifier = "state-sync-test-repo"; | ||
| 144 | |||
| 145 | // Pre-allocate syncing relay port so we can include it in announcement | ||
| 146 | let syncing_port = TestRelay::find_free_port(); | ||
| 147 | let syncing_domain = format!("127.0.0.1:{}", syncing_port); | ||
| 148 | |||
| 149 | // 2. Create test repository locally with deterministic commit | ||
| 150 | let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); | ||
| 151 | let commit_hash = create_test_repo_with_commit(temp_dir.path(), CommitVariant::StateTest) | ||
| 152 | .expect("Failed to create test repo"); | ||
| 153 | |||
| 154 | let npub = keys.public_key().to_bech32().expect("Failed to get npub"); | ||
| 155 | |||
| 156 | // 3. Create and send announcement listing BOTH relays | ||
| 157 | // This ensures the syncing relay will accept the state event when it syncs | ||
| 158 | let announcement = create_repo_announcement( | ||
| 159 | &keys, | ||
| 160 | &[&source_relay.domain(), &syncing_domain], | ||
| 161 | identifier, | ||
| 162 | ); | ||
| 163 | |||
| 164 | let source_client = Client::new(keys.clone()); | ||
| 165 | source_client | ||
| 166 | .add_relay(source_relay.url()) | ||
| 167 | .await | ||
| 168 | .expect("Failed to add source relay"); | ||
| 169 | source_client.connect().await; | ||
| 170 | |||
| 171 | // Wait for connection | ||
| 172 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 173 | |||
| 174 | // Send announcement to source relay | ||
| 175 | source_client | ||
| 176 | .send_event(&announcement) | ||
| 177 | .await | ||
| 178 | .expect("Failed to send announcement to source"); | ||
| 179 | |||
| 180 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 181 | |||
| 182 | // 4. Create and send state event BEFORE pushing | ||
| 183 | // The state event goes to purgatory on source relay, which authorizes the push | ||
| 184 | let clone_urls = [ | ||
| 185 | format!( | ||
| 186 | "http://{}/{}/{}.git", | ||
| 187 | source_relay.domain(), | ||
| 188 | npub, | ||
| 189 | identifier | ||
| 190 | ), | ||
| 191 | format!("http://{}/{}/{}.git", syncing_domain, npub, identifier), | ||
| 192 | ]; | ||
| 193 | let relay_urls = [ | ||
| 194 | source_relay.url().to_string(), | ||
| 195 | format!("ws://{}", syncing_domain), | ||
| 196 | ]; | ||
| 197 | |||
| 198 | let state_event = create_state_event( | ||
| 199 | &keys, | ||
| 200 | identifier, | ||
| 201 | &[("main", &commit_hash)], | ||
| 202 | &[], | ||
| 203 | &[&clone_urls[0], &clone_urls[1]], | ||
| 204 | &[&relay_urls[0], &relay_urls[1]], | ||
| 205 | ) | ||
| 206 | .expect("Failed to create state event"); | ||
| 207 | |||
| 208 | let state_event_id = state_event.id; | ||
| 209 | |||
| 210 | // Send state event to source relay (goes to purgatory - no git data yet) | ||
| 211 | source_client | ||
| 212 | .send_event(&state_event) | ||
| 213 | .await | ||
| 214 | .expect("Failed to send state event to source"); | ||
| 215 | |||
| 216 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 217 | |||
| 218 | // 5. Push git data to source relay | ||
| 219 | // The state event in purgatory authorizes this push | ||
| 220 | push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier) | ||
| 221 | .expect("Push to source should succeed"); | ||
| 222 | |||
| 223 | // After push, state event should be released from purgatory on source relay | ||
| 224 | // Verify source relay is serving the state event | ||
| 225 | wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5)) | ||
| 226 | .await | ||
| 227 | .expect("State event should be served on source relay after push"); | ||
| 228 | |||
| 229 | // 6. Start syncing relay (syncs from source) | ||
| 230 | let syncing_relay = TestRelay::start_on_port_with_options( | ||
| 231 | syncing_port, | ||
| 232 | Some(source_relay.url().to_string()), | ||
| 233 | false, | ||
| 234 | ) | ||
| 235 | .await; | ||
| 236 | |||
| 237 | // Wait for sync connection to establish | ||
| 238 | wait_for_sync_connection(syncing_relay.url(), 1, Duration::from_secs(5)) | ||
| 239 | .await | ||
| 240 | .expect("Sync connection should establish"); | ||
| 241 | |||
| 242 | // 7. Wait for state event to be released on syncing relay | ||
| 243 | // The sync should: | ||
| 244 | // a) Fetch the announcement and state event from source relay | ||
| 245 | // b) Accept announcement (creates bare repo structure) | ||
| 246 | // c) Put state event in purgatory (git data missing on syncing relay) | ||
| 247 | // d) Fetch git data from source relay's clone URL | ||
| 248 | // e) Release the state event from purgatory | ||
| 249 | let found = wait_for_event_served( | ||
| 250 | syncing_relay.url(), | ||
| 251 | &state_event_id, | ||
| 252 | Duration::from_secs(30), // Allow time for sync + git fetch | ||
| 253 | ) | ||
| 254 | .await; | ||
| 255 | |||
| 256 | assert!( | ||
| 257 | found.is_ok(), | ||
| 258 | "State event should be served after sync fetches git data: {:?}", | ||
| 259 | found.err() | ||
| 260 | ); | ||
| 261 | |||
| 262 | // 8. Verify refs are correct on syncing relay | ||
| 263 | let ref_correct = check_ref_at_commit( | ||
| 264 | &syncing_domain, | ||
| 265 | &npub, | ||
| 266 | identifier, | ||
| 267 | "refs/heads/main", | ||
| 268 | &commit_hash, | ||
| 269 | ) | ||
| 270 | .await | ||
| 271 | .expect("Failed to check ref"); | ||
| 272 | |||
| 273 | assert!(ref_correct, "main branch should point to correct commit"); | ||
| 274 | |||
| 275 | // Cleanup | ||
| 276 | source_client.disconnect().await; | ||
| 277 | syncing_relay.stop().await; | ||
| 278 | source_relay.stop().await; | ||
| 279 | } | ||