diff options
Diffstat (limited to 'tests/purgatory_persistence.rs')
| -rw-r--r-- | tests/purgatory_persistence.rs | 157 |
1 files changed, 143 insertions, 14 deletions
diff --git a/tests/purgatory_persistence.rs b/tests/purgatory_persistence.rs index 4dc5e94..655b0d9 100644 --- a/tests/purgatory_persistence.rs +++ b/tests/purgatory_persistence.rs | |||
| @@ -31,9 +31,11 @@ | |||
| 31 | 31 | ||
| 32 | mod common; | 32 | mod common; |
| 33 | 33 | ||
| 34 | use common::purgatory_helpers::create_announcement_event; | ||
| 34 | use ngit_grasp::purgatory::Purgatory; | 35 | use ngit_grasp::purgatory::Purgatory; |
| 35 | use ngit_grasp::sync::rejected_index::{EventType, RejectedEventsIndex, RejectionReason}; | 36 | use ngit_grasp::sync::rejected_index::{EventType, RejectedEventsIndex, RejectionReason}; |
| 36 | use nostr_sdk::prelude::*; | 37 | use nostr_sdk::prelude::*; |
| 38 | use std::collections::HashSet; | ||
| 37 | use std::time::Duration; | 39 | use std::time::Duration; |
| 38 | 40 | ||
| 39 | /// Helper to create a test event | 41 | /// Helper to create a test event |
| @@ -120,11 +122,31 @@ async fn test_full_purgatory_save_restore_cycle() { | |||
| 120 | // Add a PR placeholder (git-data-first scenario) | 122 | // Add a PR placeholder (git-data-first scenario) |
| 121 | purgatory.add_pr_placeholder("placeholder-id".to_string(), "commit-xyz".to_string()); | 123 | purgatory.add_pr_placeholder("placeholder-id".to_string(), "commit-xyz".to_string()); |
| 122 | 124 | ||
| 123 | // Note: We can't directly test expired events without accessing private fields, | 125 | // Add an announcement to purgatory (requires a real directory for the repo path) |
| 124 | // so we'll focus on testing state and PR events persistence | 126 | let repo_dir = temp_dir.path().join("repo.git"); |
| 127 | std::fs::create_dir_all(&repo_dir).unwrap(); | ||
| 128 | let ann_keys = Keys::generate(); | ||
| 129 | let ann_event = create_announcement_event( | ||
| 130 | &ann_keys, | ||
| 131 | "my-repo", | ||
| 132 | &["http://example.com/my-repo.git"], | ||
| 133 | &["wss://relay.example.com"], | ||
| 134 | ) | ||
| 135 | .unwrap(); | ||
| 136 | let ann_event_id = ann_event.id; | ||
| 137 | let mut ann_relays = HashSet::new(); | ||
| 138 | ann_relays.insert("wss://relay.example.com".to_string()); | ||
| 139 | purgatory.add_announcement( | ||
| 140 | ann_event, | ||
| 141 | "my-repo".to_string(), | ||
| 142 | ann_keys.public_key(), | ||
| 143 | repo_dir.clone(), | ||
| 144 | ann_relays, | ||
| 145 | ); | ||
| 125 | 146 | ||
| 126 | // Verify initial counts | 147 | // Verify initial counts |
| 127 | let (state_count, pr_count) = purgatory.count(); | 148 | let (announcement_count, state_count, pr_count) = purgatory.count(); |
| 149 | assert_eq!(announcement_count, 1, "Should have 1 announcement"); | ||
| 128 | assert_eq!(state_count, 2, "Should have 2 state events"); | 150 | assert_eq!(state_count, 2, "Should have 2 state events"); |
| 129 | assert_eq!( | 151 | assert_eq!( |
| 130 | pr_count, 3, | 152 | pr_count, 3, |
| @@ -146,13 +168,23 @@ async fn test_full_purgatory_save_restore_cycle() { | |||
| 146 | ); | 168 | ); |
| 147 | 169 | ||
| 148 | // Verify all data was restored | 170 | // Verify all data was restored |
| 149 | let (state_count2, pr_count2) = purgatory2.count(); | 171 | let (announcement_count2, state_count2, pr_count2) = purgatory2.count(); |
| 172 | assert_eq!(announcement_count2, 1, "Should have 1 announcement after restore"); | ||
| 150 | assert_eq!(state_count2, 2, "Should have 2 state events after restore"); | 173 | assert_eq!(state_count2, 2, "Should have 2 state events after restore"); |
| 151 | assert_eq!( | 174 | assert_eq!( |
| 152 | pr_count2, 3, | 175 | pr_count2, 3, |
| 153 | "Should have 3 PR events after restore (2 events + 1 placeholder)" | 176 | "Should have 3 PR events after restore (2 events + 1 placeholder)" |
| 154 | ); | 177 | ); |
| 155 | 178 | ||
| 179 | // Verify announcement was restored correctly | ||
| 180 | let restored_ann = purgatory2 | ||
| 181 | .find_announcement(&ann_keys.public_key(), "my-repo") | ||
| 182 | .expect("Announcement should be restored"); | ||
| 183 | assert_eq!(restored_ann.event.id, ann_event_id); | ||
| 184 | assert_eq!(restored_ann.identifier, "my-repo"); | ||
| 185 | assert_eq!(restored_ann.repo_path, repo_dir); | ||
| 186 | assert!(!restored_ann.soft_expired); | ||
| 187 | |||
| 156 | // Verify specific state events | 188 | // Verify specific state events |
| 157 | let repo1_states = purgatory2.find_state("repo1"); | 189 | let repo1_states = purgatory2.find_state("repo1"); |
| 158 | assert_eq!(repo1_states.len(), 1); | 190 | assert_eq!(repo1_states.len(), 1); |
| @@ -284,7 +316,7 @@ async fn test_purgatory_downtime_adjustment() { | |||
| 284 | purgatory2.restore_from_disk(&state_path).unwrap(); | 316 | purgatory2.restore_from_disk(&state_path).unwrap(); |
| 285 | 317 | ||
| 286 | // Verify event is still there (downtime was accounted for) | 318 | // Verify event is still there (downtime was accounted for) |
| 287 | let (state_count, _) = purgatory2.count(); | 319 | let (_, state_count, _) = purgatory2.count(); |
| 288 | assert_eq!(state_count, 1); | 320 | assert_eq!(state_count, 1); |
| 289 | 321 | ||
| 290 | let repo1_states = purgatory2.find_state("repo1"); | 322 | let repo1_states = purgatory2.find_state("repo1"); |
| @@ -410,7 +442,7 @@ async fn test_purgatory_restore_missing_file() { | |||
| 410 | assert!(result.is_err(), "Should error on missing file"); | 442 | assert!(result.is_err(), "Should error on missing file"); |
| 411 | 443 | ||
| 412 | // Purgatory should still be usable (empty state) | 444 | // Purgatory should still be usable (empty state) |
| 413 | let (state_count, pr_count) = purgatory.count(); | 445 | let (_, state_count, pr_count) = purgatory.count(); |
| 414 | assert_eq!(state_count, 0); | 446 | assert_eq!(state_count, 0); |
| 415 | assert_eq!(pr_count, 0); | 447 | assert_eq!(pr_count, 0); |
| 416 | 448 | ||
| @@ -419,7 +451,7 @@ async fn test_purgatory_restore_missing_file() { | |||
| 419 | let event = create_test_event(&keys, "test").await; | 451 | let event = create_test_event(&keys, "test").await; |
| 420 | purgatory.add_state(event, "repo1".to_string(), keys.public_key(), false); | 452 | purgatory.add_state(event, "repo1".to_string(), keys.public_key(), false); |
| 421 | 453 | ||
| 422 | let (state_count, _) = purgatory.count(); | 454 | let (_, state_count, _) = purgatory.count(); |
| 423 | assert_eq!(state_count, 1); | 455 | assert_eq!(state_count, 1); |
| 424 | } | 456 | } |
| 425 | 457 | ||
| @@ -470,7 +502,7 @@ async fn test_purgatory_restore_corrupted_file() { | |||
| 470 | assert!(result.is_err(), "Should error on corrupted file"); | 502 | assert!(result.is_err(), "Should error on corrupted file"); |
| 471 | 503 | ||
| 472 | // Purgatory should still be usable | 504 | // Purgatory should still be usable |
| 473 | let (state_count, pr_count) = purgatory.count(); | 505 | let (_, state_count, pr_count) = purgatory.count(); |
| 474 | assert_eq!(state_count, 0); | 506 | assert_eq!(state_count, 0); |
| 475 | assert_eq!(pr_count, 0); | 507 | assert_eq!(pr_count, 0); |
| 476 | } | 508 | } |
| @@ -513,7 +545,7 @@ async fn test_empty_purgatory_save_restore() { | |||
| 513 | purgatory2.restore_from_disk(&state_path).unwrap(); | 545 | purgatory2.restore_from_disk(&state_path).unwrap(); |
| 514 | 546 | ||
| 515 | // Verify empty state | 547 | // Verify empty state |
| 516 | let (state_count, pr_count) = purgatory2.count(); | 548 | let (_, state_count, pr_count) = purgatory2.count(); |
| 517 | assert_eq!(state_count, 0); | 549 | assert_eq!(state_count, 0); |
| 518 | assert_eq!(pr_count, 0); | 550 | assert_eq!(pr_count, 0); |
| 519 | assert_eq!(purgatory2.expired_count(), 0); | 551 | assert_eq!(purgatory2.expired_count(), 0); |
| @@ -620,7 +652,7 @@ async fn test_purgatory_continues_working_after_restore() { | |||
| 620 | ); | 652 | ); |
| 621 | 653 | ||
| 622 | // Verify both old and new events work | 654 | // Verify both old and new events work |
| 623 | let (state_count, _) = purgatory2.count(); | 655 | let (_, state_count, _) = purgatory2.count(); |
| 624 | assert_eq!(state_count, 2); | 656 | assert_eq!(state_count, 2); |
| 625 | 657 | ||
| 626 | let repo1_states = purgatory2.find_state("repo1"); | 658 | let repo1_states = purgatory2.find_state("repo1"); |
| @@ -632,7 +664,7 @@ async fn test_purgatory_continues_working_after_restore() { | |||
| 632 | assert_eq!(repo2_states[0].event.id, event2.id); | 664 | assert_eq!(repo2_states[0].event.id, event2.id); |
| 633 | 665 | ||
| 634 | // Verify cleanup still works | 666 | // Verify cleanup still works |
| 635 | let (state_removed, pr_removed) = purgatory2.cleanup(); | 667 | let (_, state_removed, pr_removed) = purgatory2.cleanup(); |
| 636 | // Nothing should be expired yet | 668 | // Nothing should be expired yet |
| 637 | assert_eq!(state_removed, 0); | 669 | assert_eq!(state_removed, 0); |
| 638 | assert_eq!(pr_removed, 0); | 670 | assert_eq!(pr_removed, 0); |
| @@ -713,15 +745,15 @@ async fn test_purgatory_entries_expired_during_downtime() { | |||
| 713 | purgatory2.restore_from_disk(&state_path).unwrap(); | 745 | purgatory2.restore_from_disk(&state_path).unwrap(); |
| 714 | 746 | ||
| 715 | // Event should be restored | 747 | // Event should be restored |
| 716 | let (state_count, _) = purgatory2.count(); | 748 | let (_, state_count, _) = purgatory2.count(); |
| 717 | assert_eq!(state_count, 1); | 749 | assert_eq!(state_count, 1); |
| 718 | 750 | ||
| 719 | // Cleanup should work (even if nothing is expired yet) | 751 | // Cleanup should work (even if nothing is expired yet) |
| 720 | let (state_removed, _) = purgatory2.cleanup(); | 752 | let (_, state_removed, _) = purgatory2.cleanup(); |
| 721 | // Nothing expired yet since we didn't wait 30 minutes | 753 | // Nothing expired yet since we didn't wait 30 minutes |
| 722 | assert_eq!(state_removed, 0); | 754 | assert_eq!(state_removed, 0); |
| 723 | 755 | ||
| 724 | let (state_count, _) = purgatory2.count(); | 756 | let (_, state_count, _) = purgatory2.count(); |
| 725 | assert_eq!(state_count, 1); | 757 | assert_eq!(state_count, 1); |
| 726 | } | 758 | } |
| 727 | 759 | ||
| @@ -775,3 +807,100 @@ async fn test_rejected_cache_entries_expired_during_downtime() { | |||
| 775 | assert_eq!(index2.hot_cache_len(), 0); | 807 | assert_eq!(index2.hot_cache_len(), 0); |
| 776 | assert_eq!(index2.cold_index_len(), 1); | 808 | assert_eq!(index2.cold_index_len(), 1); |
| 777 | } | 809 | } |
| 810 | |||
| 811 | /// Test 18: Announcement events are saved and restored across restarts | ||
| 812 | #[tokio::test] | ||
| 813 | async fn test_announcement_save_restore_cycle() { | ||
| 814 | let temp_dir = tempfile::tempdir().unwrap(); | ||
| 815 | let git_data_path = temp_dir.path().join("git"); | ||
| 816 | let state_path = temp_dir.path().join("purgatory.json"); | ||
| 817 | |||
| 818 | // Create a real bare repo directory (restore skips entries whose path is missing) | ||
| 819 | let repo_dir = temp_dir.path().join("owner.git"); | ||
| 820 | std::fs::create_dir_all(&repo_dir).unwrap(); | ||
| 821 | |||
| 822 | let purgatory = Purgatory::new(&git_data_path); | ||
| 823 | let keys = Keys::generate(); | ||
| 824 | |||
| 825 | let ann_event = create_announcement_event( | ||
| 826 | &keys, | ||
| 827 | "my-repo", | ||
| 828 | &["http://example.com/my-repo.git"], | ||
| 829 | &["wss://relay.example.com"], | ||
| 830 | ) | ||
| 831 | .unwrap(); | ||
| 832 | let ann_event_id = ann_event.id; | ||
| 833 | |||
| 834 | let mut relays = HashSet::new(); | ||
| 835 | relays.insert("wss://relay.example.com".to_string()); | ||
| 836 | |||
| 837 | purgatory.add_announcement( | ||
| 838 | ann_event, | ||
| 839 | "my-repo".to_string(), | ||
| 840 | keys.public_key(), | ||
| 841 | repo_dir.clone(), | ||
| 842 | relays.clone(), | ||
| 843 | ); | ||
| 844 | |||
| 845 | let (ann_count, _, _) = purgatory.count(); | ||
| 846 | assert_eq!(ann_count, 1); | ||
| 847 | |||
| 848 | // Save to disk | ||
| 849 | purgatory.save_to_disk(&state_path).unwrap(); | ||
| 850 | assert!(state_path.exists()); | ||
| 851 | |||
| 852 | // Restore into a fresh purgatory | ||
| 853 | let purgatory2 = Purgatory::new(&git_data_path); | ||
| 854 | purgatory2.restore_from_disk(&state_path).unwrap(); | ||
| 855 | |||
| 856 | assert!(!state_path.exists(), "State file should be deleted after restore"); | ||
| 857 | |||
| 858 | let (ann_count2, _, _) = purgatory2.count(); | ||
| 859 | assert_eq!(ann_count2, 1, "Announcement should be restored"); | ||
| 860 | |||
| 861 | let restored = purgatory2 | ||
| 862 | .find_announcement(&keys.public_key(), "my-repo") | ||
| 863 | .expect("Announcement should be findable after restore"); | ||
| 864 | |||
| 865 | assert_eq!(restored.event.id, ann_event_id); | ||
| 866 | assert_eq!(restored.identifier, "my-repo"); | ||
| 867 | assert_eq!(restored.owner, keys.public_key()); | ||
| 868 | assert_eq!(restored.repo_path, repo_dir); | ||
| 869 | assert_eq!(restored.relays, relays); | ||
| 870 | assert!(!restored.soft_expired); | ||
| 871 | } | ||
| 872 | |||
| 873 | /// Test 19: Announcement with missing repo path is skipped on restore | ||
| 874 | #[tokio::test] | ||
| 875 | async fn test_announcement_missing_repo_skipped_on_restore() { | ||
| 876 | let temp_dir = tempfile::tempdir().unwrap(); | ||
| 877 | let git_data_path = temp_dir.path().join("git"); | ||
| 878 | let state_path = temp_dir.path().join("purgatory.json"); | ||
| 879 | |||
| 880 | // Point to a path that does NOT exist on disk | ||
| 881 | let missing_repo = temp_dir.path().join("nonexistent.git"); | ||
| 882 | |||
| 883 | let purgatory = Purgatory::new(&git_data_path); | ||
| 884 | let keys = Keys::generate(); | ||
| 885 | |||
| 886 | let ann_event = create_announcement_event(&keys, "my-repo", &[], &[]).unwrap(); | ||
| 887 | |||
| 888 | purgatory.add_announcement( | ||
| 889 | ann_event, | ||
| 890 | "my-repo".to_string(), | ||
| 891 | keys.public_key(), | ||
| 892 | missing_repo, | ||
| 893 | HashSet::new(), | ||
| 894 | ); | ||
| 895 | |||
| 896 | purgatory.save_to_disk(&state_path).unwrap(); | ||
| 897 | |||
| 898 | let purgatory2 = Purgatory::new(&git_data_path); | ||
| 899 | purgatory2.restore_from_disk(&state_path).unwrap(); | ||
| 900 | |||
| 901 | let (ann_count, _, _) = purgatory2.count(); | ||
| 902 | assert_eq!( | ||
| 903 | ann_count, 0, | ||
| 904 | "Announcement with missing repo path must be skipped" | ||
| 905 | ); | ||
| 906 | } | ||