diff options
Diffstat (limited to 'tests/archive_read_only.rs')
| -rw-r--r-- | tests/archive_read_only.rs | 63 |
1 files changed, 40 insertions, 23 deletions
diff --git a/tests/archive_read_only.rs b/tests/archive_read_only.rs index e388ae5..069b3b7 100644 --- a/tests/archive_read_only.rs +++ b/tests/archive_read_only.rs | |||
| @@ -55,7 +55,6 @@ use std::time::Duration; | |||
| 55 | /// 5. Verify bare repository is created and git data is synced | 55 | /// 5. Verify bare repository is created and git data is synced |
| 56 | /// 6. Verify git pushes are rejected (read-only mode) | 56 | /// 6. Verify git pushes are rejected (read-only mode) |
| 57 | #[tokio::test] | 57 | #[tokio::test] |
| 58 | #[ignore] // Requires SyncLevel implementation (Phase 3) - purgatory announcements don't trigger per-repo sync yet | ||
| 59 | async fn test_archive_read_only_creates_bare_repo() { | 58 | async fn test_archive_read_only_creates_bare_repo() { |
| 60 | // 1. Start source relay | 59 | // 1. Start source relay |
| 61 | let source_relay = TestRelay::start().await; | 60 | let source_relay = TestRelay::start().await; |
| @@ -264,24 +263,24 @@ async fn test_archive_read_only_creates_bare_repo() { | |||
| 264 | source_relay.stop().await; | 263 | source_relay.stop().await; |
| 265 | } | 264 | } |
| 266 | 265 | ||
| 267 | /// Test that archive mode without state events does NOT sync git data. | 266 | /// Test that archive mode proactively syncs state events and git data |
| 267 | /// when the source relay has state events available. | ||
| 268 | /// | 268 | /// |
| 269 | /// This verifies the security model: archive mode only syncs git data | 269 | /// With StateOnly sync now implemented, purgatory announcements subscribe |
| 270 | /// when there are state events to validate against. | 270 | /// to state events from the relays listed in the announcement. This means |
| 271 | /// the archive relay will: | ||
| 272 | /// 1. Sync the announcement → purgatory → register as StateOnly in repo_sync_index | ||
| 273 | /// 2. Subscribe to state events (kind 30618) on source relay | ||
| 274 | /// 3. Receive the state event → purgatory sync triggered | ||
| 275 | /// 4. Fetch git data from source relay's clone URL | ||
| 271 | /// | 276 | /// |
| 272 | /// With announcement purgatory, the flow is: | 277 | /// This test verifies the full sync chain works end-to-end for archive mode. |
| 273 | /// 1. Send announcement to source relay (goes to purgatory) | ||
| 274 | /// 2. Send state event to source relay (goes to purgatory) | ||
| 275 | /// 3. Push git data to source relay (promotes announcement and state event) | ||
| 276 | /// 4. Start archive relay with sync from source | ||
| 277 | /// 5. Archive relay syncs the promoted announcement | ||
| 278 | /// 6. Verify git data is NOT synced (archive has no state event to authorize git fetch) | ||
| 279 | #[tokio::test] | 278 | #[tokio::test] |
| 280 | async fn test_archive_without_state_events_does_not_sync_git() { | 279 | async fn test_archive_syncs_state_events_and_git_data_via_state_only_subscription() { |
| 281 | // 1. Start source relay | 280 | // 1. Start source relay |
| 282 | let source_relay = TestRelay::start().await; | 281 | let source_relay = TestRelay::start().await; |
| 283 | let keys = Keys::generate(); | 282 | let keys = Keys::generate(); |
| 284 | let identifier = "archive-no-state-repo"; | 283 | let identifier = "archive-state-only-sync-repo"; |
| 285 | 284 | ||
| 286 | // Pre-allocate archive relay port | 285 | // Pre-allocate archive relay port |
| 287 | let archive_port = TestRelay::find_free_port(); | 286 | let archive_port = TestRelay::find_free_port(); |
| @@ -295,6 +294,7 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 295 | let npub = keys.public_key().to_bech32().expect("Failed to get npub"); | 294 | let npub = keys.public_key().to_bech32().expect("Failed to get npub"); |
| 296 | 295 | ||
| 297 | // 3. Create and send announcement listing BOTH relays | 296 | // 3. Create and send announcement listing BOTH relays |
| 297 | // The archive relay will subscribe to state events on BOTH listed relays | ||
| 298 | let announcement = create_repo_announcement( | 298 | let announcement = create_repo_announcement( |
| 299 | &keys, | 299 | &keys, |
| 300 | &[&source_relay.domain(), &archive_domain], | 300 | &[&source_relay.domain(), &archive_domain], |
| @@ -337,6 +337,8 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 337 | ) | 337 | ) |
| 338 | .expect("Failed to create state event"); | 338 | .expect("Failed to create state event"); |
| 339 | 339 | ||
| 340 | let state_event_id = state_event.id; | ||
| 341 | |||
| 340 | source_client | 342 | source_client |
| 341 | .send_event(&state_event) | 343 | .send_event(&state_event) |
| 342 | .await | 344 | .await |
| @@ -348,9 +350,12 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 348 | push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier) | 350 | push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier) |
| 349 | .expect("Push to source should succeed"); | 351 | .expect("Push to source should succeed"); |
| 350 | 352 | ||
| 351 | tokio::time::sleep(Duration::from_millis(500)).await; | 353 | // Wait for state event to be promoted on source relay |
| 354 | wait_for_event_served(source_relay.url(), &state_event_id, Duration::from_secs(5)) | ||
| 355 | .await | ||
| 356 | .expect("State event should be served on source relay after push"); | ||
| 352 | 357 | ||
| 353 | // 6. Start archive relay (without state event - we don't send state event to archive) | 358 | // 6. Start archive relay - StateOnly subscription will proactively fetch state events |
| 354 | let archive_relay = TestRelay::start_with_archive_and_sync( | 359 | let archive_relay = TestRelay::start_with_archive_and_sync( |
| 355 | archive_port, | 360 | archive_port, |
| 356 | Some(source_relay.url().to_string()), | 361 | Some(source_relay.url().to_string()), |
| @@ -360,15 +365,28 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 360 | ) | 365 | ) |
| 361 | .await; | 366 | .await; |
| 362 | 367 | ||
| 363 | // Wait for sync | 368 | // Wait for sync connection |
| 364 | wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5)) | 369 | wait_for_sync_connection(archive_relay.url(), 1, Duration::from_secs(5)) |
| 365 | .await | 370 | .await |
| 366 | .expect("Sync connection should establish"); | 371 | .expect("Sync connection should establish"); |
| 367 | 372 | ||
| 368 | // Give time for sync to fetch announcement | 373 | // 7. Wait for state event to be served on archive relay |
| 369 | tokio::time::sleep(Duration::from_secs(3)).await; | 374 | // The StateOnly subscription fetches the state event from source relay, |
| 375 | // which then triggers purgatory sync and git data fetch. | ||
| 376 | let found = wait_for_event_served( | ||
| 377 | archive_relay.url(), | ||
| 378 | &state_event_id, | ||
| 379 | Duration::from_secs(30), // Allow time for sync + git fetch | ||
| 380 | ) | ||
| 381 | .await; | ||
| 382 | |||
| 383 | assert!( | ||
| 384 | found.is_ok(), | ||
| 385 | "State event should be served on archive after StateOnly subscription fetches it: {:?}", | ||
| 386 | found.err() | ||
| 387 | ); | ||
| 370 | 388 | ||
| 371 | // 7. Verify bare repository was created (announcement was synced and accepted to purgatory) | 389 | // 8. Verify bare repository was created |
| 372 | let repo_path = archive_relay | 390 | let repo_path = archive_relay |
| 373 | .git_data_path() | 391 | .git_data_path() |
| 374 | .join(format!("{}/{}.git", npub, identifier)); | 392 | .join(format!("{}/{}.git", npub, identifier)); |
| @@ -378,8 +396,7 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 378 | "Bare repository should be created for archive announcement" | 396 | "Bare repository should be created for archive announcement" |
| 379 | ); | 397 | ); |
| 380 | 398 | ||
| 381 | // 8. Verify git data was NOT synced (no state events on archive to trigger git fetch) | 399 | // 9. Verify git data was synced via the state event chain |
| 382 | // Check that the commit does NOT exist in the archive relay's repo | ||
| 383 | let output = tokio::process::Command::new("git") | 400 | let output = tokio::process::Command::new("git") |
| 384 | .args(["cat-file", "-t", &commit_hash]) | 401 | .args(["cat-file", "-t", &commit_hash]) |
| 385 | .current_dir(&repo_path) | 402 | .current_dir(&repo_path) |
| @@ -389,8 +406,8 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 389 | let commit_exists = output.map(|o| o.status.success()).unwrap_or(false); | 406 | let commit_exists = output.map(|o| o.status.success()).unwrap_or(false); |
| 390 | 407 | ||
| 391 | assert!( | 408 | assert!( |
| 392 | !commit_exists, | 409 | commit_exists, |
| 393 | "Git data should NOT be synced without state events (security: validates against Nostr state)" | 410 | "Git data should be synced via StateOnly subscription → state event → git fetch chain" |
| 394 | ); | 411 | ); |
| 395 | 412 | ||
| 396 | // Cleanup | 413 | // Cleanup |