diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/archive_read_only.rs | 59 | ||||
| -rw-r--r-- | tests/purgatory.rs | 4 | ||||
| -rw-r--r-- | tests/purgatory_persistence.rs | 26 |
3 files changed, 61 insertions, 28 deletions
diff --git a/tests/archive_read_only.rs b/tests/archive_read_only.rs index be6959b..e39b4b2 100644 --- a/tests/archive_read_only.rs +++ b/tests/archive_read_only.rs | |||
| @@ -165,6 +165,7 @@ async fn test_archive_read_only_creates_bare_repo() { | |||
| 165 | // c) Put state event in purgatory (git data missing on archive relay) | 165 | // c) Put state event in purgatory (git data missing on archive relay) |
| 166 | // d) Fetch git data from source relay's clone URL | 166 | // d) Fetch git data from source relay's clone URL |
| 167 | // e) Release the state event from purgatory | 167 | // e) Release the state event from purgatory |
| 168 | |||
| 168 | let found = wait_for_event_served( | 169 | let found = wait_for_event_served( |
| 169 | archive_relay.url(), | 170 | archive_relay.url(), |
| 170 | &state_event_id, | 171 | &state_event_id, |
| @@ -267,11 +268,13 @@ async fn test_archive_read_only_creates_bare_repo() { | |||
| 267 | /// This verifies the security model: archive mode only syncs git data | 268 | /// This verifies the security model: archive mode only syncs git data |
| 268 | /// when there are state events to validate against. | 269 | /// when there are state events to validate against. |
| 269 | /// | 270 | /// |
| 270 | /// Scenario: | 271 | /// With announcement purgatory, the flow is: |
| 271 | /// 1. Start source relay with announcement only (no state events) | 272 | /// 1. Send announcement to source relay (goes to purgatory) |
| 272 | /// 2. Start archive relay syncing from source | 273 | /// 2. Send state event to source relay (goes to purgatory) |
| 273 | /// 3. Archive relay syncs announcement (creates bare repo) | 274 | /// 3. Push git data to source relay (promotes announcement and state event) |
| 274 | /// 4. Verify git data is NOT synced (no state events to trigger purgatory sync) | 275 | /// 4. Start archive relay with sync from source |
| 276 | /// 5. Archive relay syncs the promoted announcement | ||
| 277 | /// 6. Verify git data is NOT synced (archive has no state event to authorize git fetch) | ||
| 275 | #[tokio::test] | 278 | #[tokio::test] |
| 276 | async fn test_archive_without_state_events_does_not_sync_git() { | 279 | async fn test_archive_without_state_events_does_not_sync_git() { |
| 277 | // 1. Start source relay | 280 | // 1. Start source relay |
| @@ -290,7 +293,7 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 290 | 293 | ||
| 291 | 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"); |
| 292 | 295 | ||
| 293 | // 3. Create and send announcement listing BOTH relays (but NO state event) | 296 | // 3. Create and send announcement listing BOTH relays |
| 294 | let announcement = create_repo_announcement( | 297 | let announcement = create_repo_announcement( |
| 295 | &keys, | 298 | &keys, |
| 296 | &[&source_relay.domain(), &archive_domain], | 299 | &[&source_relay.domain(), &archive_domain], |
| @@ -306,7 +309,7 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 306 | 309 | ||
| 307 | tokio::time::sleep(Duration::from_millis(500)).await; | 310 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 308 | 311 | ||
| 309 | // Send announcement to source relay | 312 | // Send announcement to source relay (goes to purgatory) |
| 310 | source_client | 313 | source_client |
| 311 | .send_event(&announcement) | 314 | .send_event(&announcement) |
| 312 | .await | 315 | .await |
| @@ -314,11 +317,39 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 314 | 317 | ||
| 315 | tokio::time::sleep(Duration::from_millis(200)).await; | 318 | tokio::time::sleep(Duration::from_millis(200)).await; |
| 316 | 319 | ||
| 317 | // 4. Push git data to source relay (but no state event to authorize it) | 320 | // 4. Create and send state event to source relay (goes to purgatory) |
| 318 | // This push will fail because there's no state event in purgatory | 321 | let clone_url = format!( |
| 319 | // That's expected - we're testing that archive mode doesn't blindly fetch git data | 322 | "http://{}/{}/{}.git", |
| 323 | source_relay.domain(), | ||
| 324 | npub, | ||
| 325 | identifier | ||
| 326 | ); | ||
| 327 | let relay_url = source_relay.url().to_string(); | ||
| 328 | |||
| 329 | let state_event = create_state_event( | ||
| 330 | &keys, | ||
| 331 | identifier, | ||
| 332 | &[("main", &commit_hash)], | ||
| 333 | &[], | ||
| 334 | &[&clone_url], | ||
| 335 | &[&relay_url], | ||
| 336 | ) | ||
| 337 | .expect("Failed to create state event"); | ||
| 338 | |||
| 339 | source_client | ||
| 340 | .send_event(&state_event) | ||
| 341 | .await | ||
| 342 | .expect("Failed to send state event to source"); | ||
| 343 | |||
| 344 | tokio::time::sleep(Duration::from_millis(200)).await; | ||
| 345 | |||
| 346 | // 5. Push git data to source relay (promotes announcement and state event) | ||
| 347 | push_to_relay(temp_dir.path(), &source_relay.domain(), &npub, identifier) | ||
| 348 | .expect("Push to source should succeed"); | ||
| 349 | |||
| 350 | tokio::time::sleep(Duration::from_millis(500)).await; | ||
| 320 | 351 | ||
| 321 | // 5. Start archive relay | 352 | // 6. Start archive relay (without state event - we don't send state event to archive) |
| 322 | let archive_relay = TestRelay::start_with_archive_and_sync( | 353 | let archive_relay = TestRelay::start_with_archive_and_sync( |
| 323 | archive_port, | 354 | archive_port, |
| 324 | Some(source_relay.url().to_string()), | 355 | Some(source_relay.url().to_string()), |
| @@ -333,10 +364,10 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 333 | .await | 364 | .await |
| 334 | .expect("Sync connection should establish"); | 365 | .expect("Sync connection should establish"); |
| 335 | 366 | ||
| 336 | // Give time for any potential git sync to happen | 367 | // Give time for sync to fetch announcement |
| 337 | tokio::time::sleep(Duration::from_secs(3)).await; | 368 | tokio::time::sleep(Duration::from_secs(3)).await; |
| 338 | 369 | ||
| 339 | // 6. Verify bare repository was created (announcement was accepted) | 370 | // 7. Verify bare repository was created (announcement was synced and accepted to purgatory) |
| 340 | let repo_path = archive_relay | 371 | let repo_path = archive_relay |
| 341 | .git_data_path() | 372 | .git_data_path() |
| 342 | .join(format!("{}/{}.git", npub, identifier)); | 373 | .join(format!("{}/{}.git", npub, identifier)); |
| @@ -346,7 +377,7 @@ async fn test_archive_without_state_events_does_not_sync_git() { | |||
| 346 | "Bare repository should be created for archive announcement" | 377 | "Bare repository should be created for archive announcement" |
| 347 | ); | 378 | ); |
| 348 | 379 | ||
| 349 | // 7. Verify git data was NOT synced (no state events to trigger purgatory sync) | 380 | // 8. Verify git data was NOT synced (no state events on archive to trigger git fetch) |
| 350 | // Check that the commit does NOT exist in the archive relay's repo | 381 | // Check that the commit does NOT exist in the archive relay's repo |
| 351 | let output = tokio::process::Command::new("git") | 382 | let output = tokio::process::Command::new("git") |
| 352 | .args(["cat-file", "-t", &commit_hash]) | 383 | .args(["cat-file", "-t", &commit_hash]) |
diff --git a/tests/purgatory.rs b/tests/purgatory.rs index e99540b..efc28c9 100644 --- a/tests/purgatory.rs +++ b/tests/purgatory.rs | |||
| @@ -58,10 +58,10 @@ macro_rules! isolated_purgatory_test { | |||
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | // ============================================================ | 60 | // ============================================================ |
| 61 | // Announcement Purgatory Tests (commented out - feature not yet implemented) | 61 | // Announcement Purgatory Tests |
| 62 | // ============================================================ | 62 | // ============================================================ |
| 63 | 63 | ||
| 64 | // isolated_purgatory_test!(test_announcement_not_served_before_git_data); | 64 | isolated_purgatory_test!(test_announcement_not_served_before_git_data); |
| 65 | isolated_purgatory_test!(test_announcement_served_after_git_push); | 65 | isolated_purgatory_test!(test_announcement_served_after_git_push); |
| 66 | isolated_purgatory_test!(test_bare_repo_exists_for_purgatory_announcement); | 66 | isolated_purgatory_test!(test_bare_repo_exists_for_purgatory_announcement); |
| 67 | isolated_purgatory_test!(test_state_event_accepted_for_purgatory_announcement); | 67 | isolated_purgatory_test!(test_state_event_accepted_for_purgatory_announcement); |
diff --git a/tests/purgatory_persistence.rs b/tests/purgatory_persistence.rs index fe37c33..5abbf15 100644 --- a/tests/purgatory_persistence.rs +++ b/tests/purgatory_persistence.rs | |||
| @@ -120,7 +120,8 @@ async fn test_full_purgatory_save_restore_cycle() { | |||
| 120 | // so we'll focus on testing state and PR events persistence | 120 | // so we'll focus on testing state and PR events persistence |
| 121 | 121 | ||
| 122 | // Verify initial counts | 122 | // Verify initial counts |
| 123 | let (state_count, pr_count) = purgatory.count(); | 123 | let (announcement_count, state_count, pr_count) = purgatory.count(); |
| 124 | assert_eq!(announcement_count, 0, "Should have 0 announcements"); | ||
| 124 | assert_eq!(state_count, 2, "Should have 2 state events"); | 125 | assert_eq!(state_count, 2, "Should have 2 state events"); |
| 125 | assert_eq!( | 126 | assert_eq!( |
| 126 | pr_count, 3, | 127 | pr_count, 3, |
| @@ -142,7 +143,8 @@ async fn test_full_purgatory_save_restore_cycle() { | |||
| 142 | ); | 143 | ); |
| 143 | 144 | ||
| 144 | // Verify all data was restored | 145 | // Verify all data was restored |
| 145 | let (state_count2, pr_count2) = purgatory2.count(); | 146 | let (announcement_count2, state_count2, pr_count2) = purgatory2.count(); |
| 147 | assert_eq!(announcement_count2, 0, "Should have 0 announcements after restore"); | ||
| 146 | assert_eq!(state_count2, 2, "Should have 2 state events after restore"); | 148 | assert_eq!(state_count2, 2, "Should have 2 state events after restore"); |
| 147 | assert_eq!( | 149 | assert_eq!( |
| 148 | pr_count2, 3, | 150 | pr_count2, 3, |
| @@ -275,7 +277,7 @@ async fn test_purgatory_downtime_adjustment() { | |||
| 275 | purgatory2.restore_from_disk(&state_path).unwrap(); | 277 | purgatory2.restore_from_disk(&state_path).unwrap(); |
| 276 | 278 | ||
| 277 | // Verify event is still there (downtime was accounted for) | 279 | // Verify event is still there (downtime was accounted for) |
| 278 | let (state_count, _) = purgatory2.count(); | 280 | let (_, state_count, _) = purgatory2.count(); |
| 279 | assert_eq!(state_count, 1); | 281 | assert_eq!(state_count, 1); |
| 280 | 282 | ||
| 281 | let repo1_states = purgatory2.find_state("repo1"); | 283 | let repo1_states = purgatory2.find_state("repo1"); |
| @@ -401,7 +403,7 @@ async fn test_purgatory_restore_missing_file() { | |||
| 401 | assert!(result.is_err(), "Should error on missing file"); | 403 | assert!(result.is_err(), "Should error on missing file"); |
| 402 | 404 | ||
| 403 | // Purgatory should still be usable (empty state) | 405 | // Purgatory should still be usable (empty state) |
| 404 | let (state_count, pr_count) = purgatory.count(); | 406 | let (_, state_count, pr_count) = purgatory.count(); |
| 405 | assert_eq!(state_count, 0); | 407 | assert_eq!(state_count, 0); |
| 406 | assert_eq!(pr_count, 0); | 408 | assert_eq!(pr_count, 0); |
| 407 | 409 | ||
| @@ -410,7 +412,7 @@ async fn test_purgatory_restore_missing_file() { | |||
| 410 | let event = create_test_event(&keys, "test").await; | 412 | let event = create_test_event(&keys, "test").await; |
| 411 | purgatory.add_state(event, "repo1".to_string(), keys.public_key()); | 413 | purgatory.add_state(event, "repo1".to_string(), keys.public_key()); |
| 412 | 414 | ||
| 413 | let (state_count, _) = purgatory.count(); | 415 | let (_, state_count, _) = purgatory.count(); |
| 414 | assert_eq!(state_count, 1); | 416 | assert_eq!(state_count, 1); |
| 415 | } | 417 | } |
| 416 | 418 | ||
| @@ -461,7 +463,7 @@ async fn test_purgatory_restore_corrupted_file() { | |||
| 461 | assert!(result.is_err(), "Should error on corrupted file"); | 463 | assert!(result.is_err(), "Should error on corrupted file"); |
| 462 | 464 | ||
| 463 | // Purgatory should still be usable | 465 | // Purgatory should still be usable |
| 464 | let (state_count, pr_count) = purgatory.count(); | 466 | let (_, state_count, pr_count) = purgatory.count(); |
| 465 | assert_eq!(state_count, 0); | 467 | assert_eq!(state_count, 0); |
| 466 | assert_eq!(pr_count, 0); | 468 | assert_eq!(pr_count, 0); |
| 467 | } | 469 | } |
| @@ -504,7 +506,7 @@ async fn test_empty_purgatory_save_restore() { | |||
| 504 | purgatory2.restore_from_disk(&state_path).unwrap(); | 506 | purgatory2.restore_from_disk(&state_path).unwrap(); |
| 505 | 507 | ||
| 506 | // Verify empty state | 508 | // Verify empty state |
| 507 | let (state_count, pr_count) = purgatory2.count(); | 509 | let (_, state_count, pr_count) = purgatory2.count(); |
| 508 | assert_eq!(state_count, 0); | 510 | assert_eq!(state_count, 0); |
| 509 | assert_eq!(pr_count, 0); | 511 | assert_eq!(pr_count, 0); |
| 510 | assert_eq!(purgatory2.expired_count(), 0); | 512 | assert_eq!(purgatory2.expired_count(), 0); |
| @@ -591,7 +593,7 @@ async fn test_purgatory_continues_working_after_restore() { | |||
| 591 | purgatory2.add_state(event2.clone(), "repo2".to_string(), keys.public_key()); | 593 | purgatory2.add_state(event2.clone(), "repo2".to_string(), keys.public_key()); |
| 592 | 594 | ||
| 593 | // Verify both old and new events work | 595 | // Verify both old and new events work |
| 594 | let (state_count, _) = purgatory2.count(); | 596 | let (_, state_count, _) = purgatory2.count(); |
| 595 | assert_eq!(state_count, 2); | 597 | assert_eq!(state_count, 2); |
| 596 | 598 | ||
| 597 | let repo1_states = purgatory2.find_state("repo1"); | 599 | let repo1_states = purgatory2.find_state("repo1"); |
| @@ -603,7 +605,7 @@ async fn test_purgatory_continues_working_after_restore() { | |||
| 603 | assert_eq!(repo2_states[0].event.id, event2.id); | 605 | assert_eq!(repo2_states[0].event.id, event2.id); |
| 604 | 606 | ||
| 605 | // Verify cleanup still works | 607 | // Verify cleanup still works |
| 606 | let (state_removed, pr_removed) = purgatory2.cleanup(); | 608 | let (_, state_removed, pr_removed) = purgatory2.cleanup(); |
| 607 | // Nothing should be expired yet | 609 | // Nothing should be expired yet |
| 608 | assert_eq!(state_removed, 0); | 610 | assert_eq!(state_removed, 0); |
| 609 | assert_eq!(pr_removed, 0); | 611 | assert_eq!(pr_removed, 0); |
| @@ -684,15 +686,15 @@ async fn test_purgatory_entries_expired_during_downtime() { | |||
| 684 | purgatory2.restore_from_disk(&state_path).unwrap(); | 686 | purgatory2.restore_from_disk(&state_path).unwrap(); |
| 685 | 687 | ||
| 686 | // Event should be restored | 688 | // Event should be restored |
| 687 | let (state_count, _) = purgatory2.count(); | 689 | let (_, state_count, _) = purgatory2.count(); |
| 688 | assert_eq!(state_count, 1); | 690 | assert_eq!(state_count, 1); |
| 689 | 691 | ||
| 690 | // Cleanup should work (even if nothing is expired yet) | 692 | // Cleanup should work (even if nothing is expired yet) |
| 691 | let (state_removed, _) = purgatory2.cleanup(); | 693 | let (_, state_removed, _) = purgatory2.cleanup(); |
| 692 | // Nothing expired yet since we didn't wait 30 minutes | 694 | // Nothing expired yet since we didn't wait 30 minutes |
| 693 | assert_eq!(state_removed, 0); | 695 | assert_eq!(state_removed, 0); |
| 694 | 696 | ||
| 695 | let (state_count, _) = purgatory2.count(); | 697 | let (_, state_count, _) = purgatory2.count(); |
| 696 | assert_eq!(state_count, 1); | 698 | assert_eq!(state_count, 1); |
| 697 | } | 699 | } |
| 698 | 700 | ||