diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-09 19:58:41 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-09 19:58:41 +0000 |
| commit | b28a356cb41077ccee12a9c52f4ef2054e76cac6 (patch) | |
| tree | 2a0867f1ab0216e86efa062aef90b2b8077e6fb9 /tests | |
| parent | 6dd9fcd5392891b0ddb7894e2c5cb40450eae00e (diff) | |
chore: cargo fmt
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/common/sync_helpers.rs | 2 | ||||
| -rw-r--r-- | tests/state_authorization.rs | 242 | ||||
| -rw-r--r-- | tests/sync/maintainer_reprocessing.rs | 311 |
3 files changed, 266 insertions, 289 deletions
diff --git a/tests/common/sync_helpers.rs b/tests/common/sync_helpers.rs index d6a6ee4..5fc2ad7 100644 --- a/tests/common/sync_helpers.rs +++ b/tests/common/sync_helpers.rs | |||
| @@ -708,7 +708,7 @@ impl ParsedMetrics { | |||
| 708 | /// Check if a specific relay is connected | 708 | /// Check if a specific relay is connected |
| 709 | pub fn relay_connected(&self, relay: &str) -> Option<bool> { | 709 | pub fn relay_connected(&self, relay: &str) -> Option<bool> { |
| 710 | self.gauge("ngit_sync_relay_connected", &[("relay", relay)]) | 710 | self.gauge("ngit_sync_relay_connected", &[("relay", relay)]) |
| 711 | .map(|v| v >= 2) // Syncing (2), Connected (3), or ConnectedHistoricSyncFailures (4) | 711 | .map(|v| v >= 2) // Syncing (2), Connected (3), or ConnectedHistoricSyncFailures (4) |
| 712 | } | 712 | } |
| 713 | 713 | ||
| 714 | /// Get total number of connected relays | 714 | /// Get total number of connected relays |
diff --git a/tests/state_authorization.rs b/tests/state_authorization.rs index a5dfa2d..d443005 100644 --- a/tests/state_authorization.rs +++ b/tests/state_authorization.rs | |||
| @@ -13,30 +13,27 @@ use nostr_sdk::prelude::*; | |||
| 13 | async fn test_reject_state_without_announcement() { | 13 | async fn test_reject_state_without_announcement() { |
| 14 | // Start test relay | 14 | // Start test relay |
| 15 | let relay = TestRelay::start().await; | 15 | let relay = TestRelay::start().await; |
| 16 | 16 | ||
| 17 | // Create test keypair | 17 | // Create test keypair |
| 18 | let keys = Keys::generate(); | 18 | let keys = Keys::generate(); |
| 19 | 19 | ||
| 20 | // Create a state event without any announcement | 20 | // Create a state event without any announcement |
| 21 | let state_event = EventBuilder::new( | 21 | let state_event = EventBuilder::new(Kind::RepoState, "") |
| 22 | Kind::RepoState, | 22 | .tags([ |
| 23 | "", | 23 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 24 | ) | 24 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), |
| 25 | .tags([ | 25 | ]) |
| 26 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 26 | .sign_with_keys(&keys) |
| 27 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), | 27 | .unwrap(); |
| 28 | ]) | 28 | |
| 29 | .sign_with_keys(&keys) | ||
| 30 | .unwrap(); | ||
| 31 | |||
| 32 | // Connect to relay | 29 | // Connect to relay |
| 33 | let client = Client::default(); | 30 | let client = Client::default(); |
| 34 | client.add_relay(relay.url()).await.unwrap(); | 31 | client.add_relay(relay.url()).await.unwrap(); |
| 35 | client.connect().await; | 32 | client.connect().await; |
| 36 | 33 | ||
| 37 | // Try to send state event | 34 | // Try to send state event |
| 38 | let result = client.send_event(&state_event).await; | 35 | let result = client.send_event(&state_event).await; |
| 39 | 36 | ||
| 40 | // Should be rejected | 37 | // Should be rejected |
| 41 | match result { | 38 | match result { |
| 42 | Ok(output) => { | 39 | Ok(output) => { |
| @@ -45,22 +42,26 @@ async fn test_reject_state_without_announcement() { | |||
| 45 | "Event should be processed" | 42 | "Event should be processed" |
| 46 | ); | 43 | ); |
| 47 | // Check if any relay rejected it | 44 | // Check if any relay rejected it |
| 48 | let rejected = output.failed.values().any(|err| { | 45 | let rejected = output |
| 49 | err.to_string().contains("no announcement exists") | 46 | .failed |
| 50 | }); | 47 | .values() |
| 51 | assert!(rejected, "Event should be rejected due to missing announcement"); | 48 | .any(|err| err.to_string().contains("no announcement exists")); |
| 49 | assert!( | ||
| 50 | rejected, | ||
| 51 | "Event should be rejected due to missing announcement" | ||
| 52 | ); | ||
| 52 | } | 53 | } |
| 53 | Err(e) => { | 54 | Err(e) => { |
| 54 | // Also acceptable - relay rejected the event | 55 | // Also acceptable - relay rejected the event |
| 55 | assert!( | 56 | assert!( |
| 56 | e.to_string().contains("no announcement exists") || | 57 | e.to_string().contains("no announcement exists") |
| 57 | e.to_string().contains("rejected"), | 58 | || e.to_string().contains("rejected"), |
| 58 | "Error should indicate missing announcement: {}", | 59 | "Error should indicate missing announcement: {}", |
| 59 | e | 60 | e |
| 60 | ); | 61 | ); |
| 61 | } | 62 | } |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | relay.stop().await; | 65 | relay.stop().await; |
| 65 | } | 66 | } |
| 66 | 67 | ||
| @@ -68,67 +69,67 @@ async fn test_reject_state_without_announcement() { | |||
| 68 | async fn test_reject_state_from_unauthorized_author() { | 69 | async fn test_reject_state_from_unauthorized_author() { |
| 69 | // Start test relay | 70 | // Start test relay |
| 70 | let relay = TestRelay::start().await; | 71 | let relay = TestRelay::start().await; |
| 71 | 72 | ||
| 72 | // Create two keypairs: one for announcement, one for unauthorized state | 73 | // Create two keypairs: one for announcement, one for unauthorized state |
| 73 | let announcement_keys = Keys::generate(); | 74 | let announcement_keys = Keys::generate(); |
| 74 | let unauthorized_keys = Keys::generate(); | 75 | let unauthorized_keys = Keys::generate(); |
| 75 | 76 | ||
| 76 | // Create announcement | 77 | // Create announcement |
| 77 | let announcement = EventBuilder::new( | 78 | let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "") |
| 78 | Kind::GitRepoAnnouncement, | 79 | .tags([ |
| 79 | "", | 80 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 80 | ) | 81 | Tag::custom( |
| 81 | .tags([ | 82 | TagKind::custom("clone"), |
| 82 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 83 | [format!("https://{}/test.git", relay.domain())], |
| 83 | Tag::custom(TagKind::custom("clone"), [format!("https://{}/test.git", relay.domain())]), | 84 | ), |
| 84 | Tag::custom(TagKind::custom("relays"), [relay.url()]), | 85 | Tag::custom(TagKind::custom("relays"), [relay.url()]), |
| 85 | ]) | 86 | ]) |
| 86 | .sign_with_keys(&announcement_keys) | 87 | .sign_with_keys(&announcement_keys) |
| 87 | .unwrap(); | 88 | .unwrap(); |
| 88 | 89 | ||
| 89 | // Connect to relay | 90 | // Connect to relay |
| 90 | let client = Client::default(); | 91 | let client = Client::default(); |
| 91 | client.add_relay(relay.url()).await.unwrap(); | 92 | client.add_relay(relay.url()).await.unwrap(); |
| 92 | client.connect().await; | 93 | client.connect().await; |
| 93 | 94 | ||
| 94 | // Send announcement | 95 | // Send announcement |
| 95 | client.send_event(&announcement).await.unwrap(); | 96 | client.send_event(&announcement).await.unwrap(); |
| 96 | 97 | ||
| 97 | // Wait for announcement to be processed | 98 | // Wait for announcement to be processed |
| 98 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; | 99 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; |
| 99 | 100 | ||
| 100 | // Try to send state event from unauthorized author | 101 | // Try to send state event from unauthorized author |
| 101 | let state_event = EventBuilder::new( | 102 | let state_event = EventBuilder::new(Kind::RepoState, "") |
| 102 | Kind::RepoState, | 103 | .tags([ |
| 103 | "", | 104 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 104 | ) | 105 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), |
| 105 | .tags([ | 106 | ]) |
| 106 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 107 | .sign_with_keys(&unauthorized_keys) |
| 107 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), | 108 | .unwrap(); |
| 108 | ]) | 109 | |
| 109 | .sign_with_keys(&unauthorized_keys) | ||
| 110 | .unwrap(); | ||
| 111 | |||
| 112 | let result = client.send_event(&state_event).await; | 110 | let result = client.send_event(&state_event).await; |
| 113 | 111 | ||
| 114 | // Should be rejected | 112 | // Should be rejected |
| 115 | match result { | 113 | match result { |
| 116 | Ok(output) => { | 114 | Ok(output) => { |
| 117 | let rejected = output.failed.values().any(|err| { | 115 | let rejected = output |
| 118 | err.to_string().contains("not authorized") | 116 | .failed |
| 119 | }); | 117 | .values() |
| 120 | assert!(rejected, "Event should be rejected due to unauthorized author"); | 118 | .any(|err| err.to_string().contains("not authorized")); |
| 119 | assert!( | ||
| 120 | rejected, | ||
| 121 | "Event should be rejected due to unauthorized author" | ||
| 122 | ); | ||
| 121 | } | 123 | } |
| 122 | Err(e) => { | 124 | Err(e) => { |
| 123 | assert!( | 125 | assert!( |
| 124 | e.to_string().contains("not authorized") || | 126 | e.to_string().contains("not authorized") || e.to_string().contains("rejected"), |
| 125 | e.to_string().contains("rejected"), | ||
| 126 | "Error should indicate unauthorized author: {}", | 127 | "Error should indicate unauthorized author: {}", |
| 127 | e | 128 | e |
| 128 | ); | 129 | ); |
| 129 | } | 130 | } |
| 130 | } | 131 | } |
| 131 | 132 | ||
| 132 | relay.stop().await; | 133 | relay.stop().await; |
| 133 | } | 134 | } |
| 134 | 135 | ||
| @@ -136,48 +137,45 @@ async fn test_reject_state_from_unauthorized_author() { | |||
| 136 | async fn test_accept_state_from_announcement_author() { | 137 | async fn test_accept_state_from_announcement_author() { |
| 137 | // Start test relay | 138 | // Start test relay |
| 138 | let relay = TestRelay::start().await; | 139 | let relay = TestRelay::start().await; |
| 139 | 140 | ||
| 140 | // Create keypair | 141 | // Create keypair |
| 141 | let keys = Keys::generate(); | 142 | let keys = Keys::generate(); |
| 142 | 143 | ||
| 143 | // Create announcement | 144 | // Create announcement |
| 144 | let announcement = EventBuilder::new( | 145 | let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "") |
| 145 | Kind::GitRepoAnnouncement, | 146 | .tags([ |
| 146 | "", | 147 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 147 | ) | 148 | Tag::custom( |
| 148 | .tags([ | 149 | TagKind::custom("clone"), |
| 149 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 150 | [format!("https://{}/test.git", relay.domain())], |
| 150 | Tag::custom(TagKind::custom("clone"), [format!("https://{}/test.git", relay.domain())]), | 151 | ), |
| 151 | Tag::custom(TagKind::custom("relays"), [relay.url()]), | 152 | Tag::custom(TagKind::custom("relays"), [relay.url()]), |
| 152 | ]) | 153 | ]) |
| 153 | .sign_with_keys(&keys) | 154 | .sign_with_keys(&keys) |
| 154 | .unwrap(); | 155 | .unwrap(); |
| 155 | 156 | ||
| 156 | // Connect to relay | 157 | // Connect to relay |
| 157 | let client = Client::default(); | 158 | let client = Client::default(); |
| 158 | client.add_relay(relay.url()).await.unwrap(); | 159 | client.add_relay(relay.url()).await.unwrap(); |
| 159 | client.connect().await; | 160 | client.connect().await; |
| 160 | 161 | ||
| 161 | // Send announcement | 162 | // Send announcement |
| 162 | client.send_event(&announcement).await.unwrap(); | 163 | client.send_event(&announcement).await.unwrap(); |
| 163 | 164 | ||
| 164 | // Wait for announcement to be processed | 165 | // Wait for announcement to be processed |
| 165 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; | 166 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; |
| 166 | 167 | ||
| 167 | // Send state event from same author (should be accepted or go to purgatory) | 168 | // Send state event from same author (should be accepted or go to purgatory) |
| 168 | let state_event = EventBuilder::new( | 169 | let state_event = EventBuilder::new(Kind::RepoState, "") |
| 169 | Kind::RepoState, | 170 | .tags([ |
| 170 | "", | 171 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 171 | ) | 172 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), |
| 172 | .tags([ | 173 | ]) |
| 173 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 174 | .sign_with_keys(&keys) |
| 174 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), | 175 | .unwrap(); |
| 175 | ]) | 176 | |
| 176 | .sign_with_keys(&keys) | ||
| 177 | .unwrap(); | ||
| 178 | |||
| 179 | let result = client.send_event(&state_event).await; | 177 | let result = client.send_event(&state_event).await; |
| 180 | 178 | ||
| 181 | // Should be accepted or go to purgatory (not permanently rejected) | 179 | // Should be accepted or go to purgatory (not permanently rejected) |
| 182 | match result { | 180 | match result { |
| 183 | Ok(output) => { | 181 | Ok(output) => { |
| @@ -194,14 +192,13 @@ async fn test_accept_state_from_announcement_author() { | |||
| 194 | Err(e) => { | 192 | Err(e) => { |
| 195 | // Purgatory is acceptable | 193 | // Purgatory is acceptable |
| 196 | assert!( | 194 | assert!( |
| 197 | e.to_string().contains("purgatory") || | 195 | e.to_string().contains("purgatory") || e.to_string().contains("waiting for git"), |
| 198 | e.to_string().contains("waiting for git"), | ||
| 199 | "Error should be about purgatory, not authorization: {}", | 196 | "Error should be about purgatory, not authorization: {}", |
| 200 | e | 197 | e |
| 201 | ); | 198 | ); |
| 202 | } | 199 | } |
| 203 | } | 200 | } |
| 204 | 201 | ||
| 205 | relay.stop().await; | 202 | relay.stop().await; |
| 206 | } | 203 | } |
| 207 | 204 | ||
| @@ -209,50 +206,50 @@ async fn test_accept_state_from_announcement_author() { | |||
| 209 | async fn test_accept_state_from_maintainer() { | 206 | async fn test_accept_state_from_maintainer() { |
| 210 | // Start test relay | 207 | // Start test relay |
| 211 | let relay = TestRelay::start().await; | 208 | let relay = TestRelay::start().await; |
| 212 | 209 | ||
| 213 | // Create two keypairs: owner and maintainer | 210 | // Create two keypairs: owner and maintainer |
| 214 | let owner_keys = Keys::generate(); | 211 | let owner_keys = Keys::generate(); |
| 215 | let maintainer_keys = Keys::generate(); | 212 | let maintainer_keys = Keys::generate(); |
| 216 | 213 | ||
| 217 | // Create announcement with maintainer | 214 | // Create announcement with maintainer |
| 218 | let announcement = EventBuilder::new( | 215 | let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "") |
| 219 | Kind::GitRepoAnnouncement, | 216 | .tags([ |
| 220 | "", | 217 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 221 | ) | 218 | Tag::custom( |
| 222 | .tags([ | 219 | TagKind::custom("clone"), |
| 223 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 220 | [format!("https://{}/test.git", relay.domain())], |
| 224 | Tag::custom(TagKind::custom("clone"), [format!("https://{}/test.git", relay.domain())]), | 221 | ), |
| 225 | Tag::custom(TagKind::custom("relays"), [relay.url()]), | 222 | Tag::custom(TagKind::custom("relays"), [relay.url()]), |
| 226 | Tag::custom(TagKind::custom("maintainers"), [maintainer_keys.public_key().to_hex()]), | 223 | Tag::custom( |
| 227 | ]) | 224 | TagKind::custom("maintainers"), |
| 228 | .sign_with_keys(&owner_keys) | 225 | [maintainer_keys.public_key().to_hex()], |
| 229 | .unwrap(); | 226 | ), |
| 230 | 227 | ]) | |
| 228 | .sign_with_keys(&owner_keys) | ||
| 229 | .unwrap(); | ||
| 230 | |||
| 231 | // Connect to relay | 231 | // Connect to relay |
| 232 | let client = Client::default(); | 232 | let client = Client::default(); |
| 233 | client.add_relay(relay.url()).await.unwrap(); | 233 | client.add_relay(relay.url()).await.unwrap(); |
| 234 | client.connect().await; | 234 | client.connect().await; |
| 235 | 235 | ||
| 236 | // Send announcement | 236 | // Send announcement |
| 237 | client.send_event(&announcement).await.unwrap(); | 237 | client.send_event(&announcement).await.unwrap(); |
| 238 | 238 | ||
| 239 | // Wait for announcement to be processed | 239 | // Wait for announcement to be processed |
| 240 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; | 240 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; |
| 241 | 241 | ||
| 242 | // Send state event from maintainer | 242 | // Send state event from maintainer |
| 243 | let state_event = EventBuilder::new( | 243 | let state_event = EventBuilder::new(Kind::RepoState, "") |
| 244 | Kind::RepoState, | 244 | .tags([ |
| 245 | "", | 245 | Tag::custom(TagKind::custom("d"), ["test-repo"]), |
| 246 | ) | 246 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), |
| 247 | .tags([ | 247 | ]) |
| 248 | Tag::custom(TagKind::custom("d"), ["test-repo"]), | 248 | .sign_with_keys(&maintainer_keys) |
| 249 | Tag::custom(TagKind::custom("refs/heads/main"), ["abc123"]), | 249 | .unwrap(); |
| 250 | ]) | 250 | |
| 251 | .sign_with_keys(&maintainer_keys) | ||
| 252 | .unwrap(); | ||
| 253 | |||
| 254 | let result = client.send_event(&state_event).await; | 251 | let result = client.send_event(&state_event).await; |
| 255 | 252 | ||
| 256 | // Should be accepted or go to purgatory (not permanently rejected) | 253 | // Should be accepted or go to purgatory (not permanently rejected) |
| 257 | match result { | 254 | match result { |
| 258 | Ok(output) => { | 255 | Ok(output) => { |
| @@ -268,13 +265,12 @@ async fn test_accept_state_from_maintainer() { | |||
| 268 | Err(e) => { | 265 | Err(e) => { |
| 269 | // Purgatory is acceptable | 266 | // Purgatory is acceptable |
| 270 | assert!( | 267 | assert!( |
| 271 | e.to_string().contains("purgatory") || | 268 | e.to_string().contains("purgatory") || e.to_string().contains("waiting for git"), |
| 272 | e.to_string().contains("waiting for git"), | ||
| 273 | "Error should be about purgatory, not authorization: {}", | 269 | "Error should be about purgatory, not authorization: {}", |
| 274 | e | 270 | e |
| 275 | ); | 271 | ); |
| 276 | } | 272 | } |
| 277 | } | 273 | } |
| 278 | 274 | ||
| 279 | relay.stop().await; | 275 | relay.stop().await; |
| 280 | } | 276 | } |
diff --git a/tests/sync/maintainer_reprocessing.rs b/tests/sync/maintainer_reprocessing.rs index 2b7fb0f..df1bf78 100644 --- a/tests/sync/maintainer_reprocessing.rs +++ b/tests/sync/maintainer_reprocessing.rs | |||
| @@ -42,23 +42,18 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 42 | .await | 42 | .await |
| 43 | .expect("Failed to connect to relay_a"); | 43 | .expect("Failed to connect to relay_a"); |
| 44 | 44 | ||
| 45 | let maintainer_announcement = EventBuilder::new( | 45 | let maintainer_announcement = |
| 46 | Kind::GitRepoAnnouncement, | 46 | EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") |
| 47 | "Maintainer's repository", | 47 | .tags(vec![ |
| 48 | ) | 48 | Tag::identifier(identifier), |
| 49 | .tags(vec![ | 49 | Tag::custom( |
| 50 | Tag::identifier(identifier), | 50 | TagKind::custom("clone"), |
| 51 | Tag::custom( | 51 | vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], |
| 52 | TagKind::custom("clone"), | 52 | ), |
| 53 | vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], | 53 | Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), |
| 54 | ), | 54 | ]) |
| 55 | Tag::custom( | 55 | .sign_with_keys(&maintainer_keys) |
| 56 | TagKind::custom("relays"), | 56 | .unwrap(); |
| 57 | vec![relay_a.url().to_string()], | ||
| 58 | ), | ||
| 59 | ]) | ||
| 60 | .sign_with_keys(&maintainer_keys) | ||
| 61 | .unwrap(); | ||
| 62 | 57 | ||
| 63 | client_a.send_event(&maintainer_announcement).await.unwrap(); | 58 | client_a.send_event(&maintainer_announcement).await.unwrap(); |
| 64 | println!("✓ Maintainer announcement sent to relay_a"); | 59 | println!("✓ Maintainer announcement sent to relay_a"); |
| @@ -68,27 +63,24 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 68 | .await | 63 | .await |
| 69 | .expect("Failed to connect to relay_b"); | 64 | .expect("Failed to connect to relay_b"); |
| 70 | 65 | ||
| 71 | let owner_announcement = EventBuilder::new( | 66 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 72 | Kind::GitRepoAnnouncement, | 67 | .tags(vec![ |
| 73 | "Owner's repository", | 68 | Tag::identifier(identifier), |
| 74 | ) | 69 | Tag::custom( |
| 75 | .tags(vec![ | 70 | TagKind::custom("clone"), |
| 76 | Tag::identifier(identifier), | 71 | vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], |
| 77 | Tag::custom( | 72 | ), |
| 78 | TagKind::custom("clone"), | 73 | Tag::custom( |
| 79 | vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], | 74 | TagKind::custom("relays"), |
| 80 | ), | 75 | vec![relay_a.url().to_string(), relay_b.url().to_string()], |
| 81 | Tag::custom( | 76 | ), |
| 82 | TagKind::custom("relays"), | 77 | Tag::custom( |
| 83 | vec![relay_a.url().to_string(), relay_b.url().to_string()], | 78 | TagKind::custom("maintainers"), |
| 84 | ), | 79 | vec![maintainer_keys.public_key().to_hex()], |
| 85 | Tag::custom( | 80 | ), |
| 86 | TagKind::custom("maintainers"), | 81 | ]) |
| 87 | vec![maintainer_keys.public_key().to_hex()], | 82 | .sign_with_keys(&owner_keys) |
| 88 | ), | 83 | .unwrap(); |
| 89 | ]) | ||
| 90 | .sign_with_keys(&owner_keys) | ||
| 91 | .unwrap(); | ||
| 92 | 84 | ||
| 93 | client_b.send_event(&owner_announcement).await.unwrap(); | 85 | client_b.send_event(&owner_announcement).await.unwrap(); |
| 94 | println!("✓ Owner announcement sent to relay_b"); | 86 | println!("✓ Owner announcement sent to relay_b"); |
| @@ -104,7 +96,8 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 104 | .author(owner_keys.public_key()) | 96 | .author(owner_keys.public_key()) |
| 105 | .identifier(identifier); | 97 | .identifier(identifier); |
| 106 | 98 | ||
| 107 | let owner_found = wait_for_event_on_relay(relay_b.url(), owner_filter, Duration::from_secs(2)).await; | 99 | let owner_found = |
| 100 | wait_for_event_on_relay(relay_b.url(), owner_filter, Duration::from_secs(2)).await; | ||
| 108 | assert!(owner_found, "Owner announcement should be in relay_b"); | 101 | assert!(owner_found, "Owner announcement should be in relay_b"); |
| 109 | 102 | ||
| 110 | let maintainer_filter = Filter::new() | 103 | let maintainer_filter = Filter::new() |
| @@ -112,8 +105,12 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 112 | .author(maintainer_keys.public_key()) | 105 | .author(maintainer_keys.public_key()) |
| 113 | .identifier(identifier); | 106 | .identifier(identifier); |
| 114 | 107 | ||
| 115 | let maintainer_found = wait_for_event_on_relay(relay_b.url(), maintainer_filter, Duration::from_secs(2)).await; | 108 | let maintainer_found = |
| 116 | assert!(maintainer_found, "Maintainer announcement should be re-processed and accepted in relay_b"); | 109 | wait_for_event_on_relay(relay_b.url(), maintainer_filter, Duration::from_secs(2)).await; |
| 110 | assert!( | ||
| 111 | maintainer_found, | ||
| 112 | "Maintainer announcement should be re-processed and accepted in relay_b" | ||
| 113 | ); | ||
| 117 | 114 | ||
| 118 | // Step 5: Verify it happened quickly (not 24 hours!) | 115 | // Step 5: Verify it happened quickly (not 24 hours!) |
| 119 | assert!( | 116 | assert!( |
| @@ -145,36 +142,34 @@ async fn test_maintainer_announcement_reprocessed_immediately() { | |||
| 145 | #[ignore] // Skip by default due to 2+ minute duration | 142 | #[ignore] // Skip by default due to 2+ minute duration |
| 146 | async fn test_maintainer_announcement_cold_index_prevents_refetch() { | 143 | async fn test_maintainer_announcement_cold_index_prevents_refetch() { |
| 147 | let relay = TestRelay::start().await; | 144 | let relay = TestRelay::start().await; |
| 148 | 145 | ||
| 149 | // Create keys | 146 | // Create keys |
| 150 | let owner_keys = Keys::generate(); | 147 | let owner_keys = Keys::generate(); |
| 151 | let maintainer_keys = Keys::generate(); | 148 | let maintainer_keys = Keys::generate(); |
| 152 | 149 | ||
| 153 | let identifier = "test-repo-cold"; | 150 | let identifier = "test-repo-cold"; |
| 154 | 151 | ||
| 155 | // Create client using TestClient helper | 152 | // Create client using TestClient helper |
| 156 | let client = TestClient::new(relay.url(), maintainer_keys.clone()) | 153 | let client = TestClient::new(relay.url(), maintainer_keys.clone()) |
| 157 | .await | 154 | .await |
| 158 | .expect("Failed to connect to relay"); | 155 | .expect("Failed to connect to relay"); |
| 159 | 156 | ||
| 160 | // Step 1: Send maintainer announcement (will be rejected - doesn't list our relay) | 157 | // Step 1: Send maintainer announcement (will be rejected - doesn't list our relay) |
| 161 | let maintainer_announcement = EventBuilder::new( | 158 | let maintainer_announcement = |
| 162 | Kind::GitRepoAnnouncement, | 159 | EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") |
| 163 | "Maintainer's repository", | 160 | .tags(vec![ |
| 164 | ) | 161 | Tag::identifier(identifier), |
| 165 | .tags(vec![ | 162 | Tag::custom( |
| 166 | Tag::identifier(identifier), | 163 | TagKind::custom("clone"), |
| 167 | Tag::custom( | 164 | vec![format!("https://example.com/{}.git", identifier)], |
| 168 | TagKind::custom("clone"), | 165 | ), |
| 169 | vec![format!("https://example.com/{}.git", identifier)], | 166 | Tag::custom( |
| 170 | ), | 167 | TagKind::custom("relays"), |
| 171 | Tag::custom( | 168 | vec!["wss://example.com".to_string()], |
| 172 | TagKind::custom("relays"), | 169 | ), |
| 173 | vec!["wss://example.com".to_string()], | 170 | ]) |
| 174 | ), | 171 | .sign_with_keys(&maintainer_keys) |
| 175 | ]) | 172 | .unwrap(); |
| 176 | .sign_with_keys(&maintainer_keys) | ||
| 177 | .unwrap(); | ||
| 178 | 173 | ||
| 179 | // Send maintainer announcement - expect it to be rejected | 174 | // Send maintainer announcement - expect it to be rejected |
| 180 | let _ = client.send_event(&maintainer_announcement).await; | 175 | let _ = client.send_event(&maintainer_announcement).await; |
| @@ -185,27 +180,21 @@ async fn test_maintainer_announcement_cold_index_prevents_refetch() { | |||
| 185 | tokio::time::sleep(Duration::from_secs(125)).await; | 180 | tokio::time::sleep(Duration::from_secs(125)).await; |
| 186 | 181 | ||
| 187 | // Step 3: Send owner announcement (lists maintainer) | 182 | // Step 3: Send owner announcement (lists maintainer) |
| 188 | let owner_announcement = EventBuilder::new( | 183 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 189 | Kind::GitRepoAnnouncement, | 184 | .tags(vec![ |
| 190 | "Owner's repository", | 185 | Tag::identifier(identifier), |
| 191 | ) | 186 | Tag::custom( |
| 192 | .tags(vec![ | 187 | TagKind::custom("clone"), |
| 193 | Tag::identifier(identifier), | 188 | vec![format!("https://{}/{}.git", relay.domain(), identifier)], |
| 194 | Tag::custom( | 189 | ), |
| 195 | TagKind::custom("clone"), | 190 | Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]), |
| 196 | vec![format!("https://{}/{}.git", relay.domain(), identifier)], | 191 | Tag::custom( |
| 197 | ), | 192 | TagKind::custom("maintainers"), |
| 198 | Tag::custom( | 193 | vec![maintainer_keys.public_key().to_hex()], |
| 199 | TagKind::custom("relays"), | 194 | ), |
| 200 | vec![relay.url().to_string()], | 195 | ]) |
| 201 | ), | 196 | .sign_with_keys(&owner_keys) |
| 202 | Tag::custom( | 197 | .unwrap(); |
| 203 | TagKind::custom("maintainers"), | ||
| 204 | vec![maintainer_keys.public_key().to_hex()], | ||
| 205 | ), | ||
| 206 | ]) | ||
| 207 | .sign_with_keys(&owner_keys) | ||
| 208 | .unwrap(); | ||
| 209 | 198 | ||
| 210 | client.send_event(&owner_announcement).await.unwrap(); | 199 | client.send_event(&owner_announcement).await.unwrap(); |
| 211 | tokio::time::sleep(Duration::from_millis(500)).await; | 200 | tokio::time::sleep(Duration::from_millis(500)).await; |
| @@ -215,16 +204,18 @@ async fn test_maintainer_announcement_cold_index_prevents_refetch() { | |||
| 215 | .kind(Kind::GitRepoAnnouncement) | 204 | .kind(Kind::GitRepoAnnouncement) |
| 216 | .author(owner_keys.public_key()) | 205 | .author(owner_keys.public_key()) |
| 217 | .identifier(identifier); | 206 | .identifier(identifier); |
| 218 | 207 | ||
| 219 | let owner_found = wait_for_event_on_relay(relay.url(), owner_filter, Duration::from_secs(2)).await; | 208 | let owner_found = |
| 209 | wait_for_event_on_relay(relay.url(), owner_filter, Duration::from_secs(2)).await; | ||
| 220 | assert!(owner_found, "Owner announcement should be accepted"); | 210 | assert!(owner_found, "Owner announcement should be accepted"); |
| 221 | 211 | ||
| 222 | let maintainer_filter = Filter::new() | 212 | let maintainer_filter = Filter::new() |
| 223 | .kind(Kind::GitRepoAnnouncement) | 213 | .kind(Kind::GitRepoAnnouncement) |
| 224 | .author(maintainer_keys.public_key()) | 214 | .author(maintainer_keys.public_key()) |
| 225 | .identifier(identifier); | 215 | .identifier(identifier); |
| 226 | 216 | ||
| 227 | let maintainer_found = wait_for_event_on_relay(relay.url(), maintainer_filter, Duration::from_millis(500)).await; | 217 | let maintainer_found = |
| 218 | wait_for_event_on_relay(relay.url(), maintainer_filter, Duration::from_millis(500)).await; | ||
| 228 | assert!( | 219 | assert!( |
| 229 | !maintainer_found, | 220 | !maintainer_found, |
| 230 | "Maintainer announcement should NOT be re-processed (hot cache expired)" | 221 | "Maintainer announcement should NOT be re-processed (hot cache expired)" |
| @@ -267,7 +258,10 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 267 | .await | 258 | .await |
| 268 | .expect("Failed to connect to relay_a"); | 259 | .expect("Failed to connect to relay_a"); |
| 269 | 260 | ||
| 270 | for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys].iter().enumerate() { | 261 | for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys] |
| 262 | .iter() | ||
| 263 | .enumerate() | ||
| 264 | { | ||
| 271 | let announcement = EventBuilder::new( | 265 | let announcement = EventBuilder::new( |
| 272 | Kind::GitRepoAnnouncement, | 266 | Kind::GitRepoAnnouncement, |
| 273 | format!("Maintainer {} repository", idx + 1), | 267 | format!("Maintainer {} repository", idx + 1), |
| @@ -278,10 +272,7 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 278 | TagKind::custom("clone"), | 272 | TagKind::custom("clone"), |
| 279 | vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], | 273 | vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], |
| 280 | ), | 274 | ), |
| 281 | Tag::custom( | 275 | Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), |
| 282 | TagKind::custom("relays"), | ||
| 283 | vec![relay_a.url().to_string()], | ||
| 284 | ), | ||
| 285 | ]) | 276 | ]) |
| 286 | .sign_with_keys(maintainer_keys) | 277 | .sign_with_keys(maintainer_keys) |
| 287 | .unwrap(); | 278 | .unwrap(); |
| @@ -295,31 +286,28 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 295 | .await | 286 | .await |
| 296 | .expect("Failed to connect to relay_b"); | 287 | .expect("Failed to connect to relay_b"); |
| 297 | 288 | ||
| 298 | let owner_announcement = EventBuilder::new( | 289 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 299 | Kind::GitRepoAnnouncement, | 290 | .tags(vec![ |
| 300 | "Owner's repository", | 291 | Tag::identifier(identifier), |
| 301 | ) | 292 | Tag::custom( |
| 302 | .tags(vec![ | 293 | TagKind::custom("clone"), |
| 303 | Tag::identifier(identifier), | 294 | vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], |
| 304 | Tag::custom( | 295 | ), |
| 305 | TagKind::custom("clone"), | 296 | Tag::custom( |
| 306 | vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], | 297 | TagKind::custom("relays"), |
| 307 | ), | 298 | vec![relay_a.url().to_string(), relay_b.url().to_string()], |
| 308 | Tag::custom( | 299 | ), |
| 309 | TagKind::custom("relays"), | 300 | Tag::custom( |
| 310 | vec![relay_a.url().to_string(), relay_b.url().to_string()], | 301 | TagKind::custom("maintainers"), |
| 311 | ), | 302 | vec![ |
| 312 | Tag::custom( | 303 | maintainer1_keys.public_key().to_hex(), |
| 313 | TagKind::custom("maintainers"), | 304 | maintainer2_keys.public_key().to_hex(), |
| 314 | vec![ | 305 | maintainer3_keys.public_key().to_hex(), |
| 315 | maintainer1_keys.public_key().to_hex(), | 306 | ], |
| 316 | maintainer2_keys.public_key().to_hex(), | 307 | ), |
| 317 | maintainer3_keys.public_key().to_hex(), | 308 | ]) |
| 318 | ], | 309 | .sign_with_keys(&owner_keys) |
| 319 | ), | 310 | .unwrap(); |
| 320 | ]) | ||
| 321 | .sign_with_keys(&owner_keys) | ||
| 322 | .unwrap(); | ||
| 323 | 311 | ||
| 324 | client_b.send_event(&owner_announcement).await.unwrap(); | 312 | client_b.send_event(&owner_announcement).await.unwrap(); |
| 325 | println!("✓ Owner announcement sent to relay_b"); | 313 | println!("✓ Owner announcement sent to relay_b"); |
| @@ -340,11 +328,7 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 340 | .identifier(identifier); | 328 | .identifier(identifier); |
| 341 | 329 | ||
| 342 | let found = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(2)).await; | 330 | let found = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(2)).await; |
| 343 | assert!( | 331 | assert!(found, "{} announcement should be in relay_b", name); |
| 344 | found, | ||
| 345 | "{} announcement should be in relay_b", | ||
| 346 | name | ||
| 347 | ); | ||
| 348 | } | 332 | } |
| 349 | 333 | ||
| 350 | println!("✅ All three maintainer announcements re-processed successfully"); | 334 | println!("✅ All three maintainer announcements re-processed successfully"); |
| @@ -365,63 +349,55 @@ async fn test_multiple_maintainers_all_reprocessed() { | |||
| 365 | #[tokio::test] | 349 | #[tokio::test] |
| 366 | async fn test_invalid_maintainer_pubkey_handled_gracefully() { | 350 | async fn test_invalid_maintainer_pubkey_handled_gracefully() { |
| 367 | let relay = TestRelay::start().await; | 351 | let relay = TestRelay::start().await; |
| 368 | 352 | ||
| 369 | // Create keys | 353 | // Create keys |
| 370 | let owner_keys = Keys::generate(); | 354 | let owner_keys = Keys::generate(); |
| 371 | let maintainer_keys = Keys::generate(); | 355 | let maintainer_keys = Keys::generate(); |
| 372 | 356 | ||
| 373 | let identifier = "invalid-maintainer-repo"; | 357 | let identifier = "invalid-maintainer-repo"; |
| 374 | 358 | ||
| 375 | // Create client using TestClient helper | 359 | // Create client using TestClient helper |
| 376 | let client = TestClient::new(relay.url(), owner_keys.clone()) | 360 | let client = TestClient::new(relay.url(), owner_keys.clone()) |
| 377 | .await | 361 | .await |
| 378 | .expect("Failed to connect to relay"); | 362 | .expect("Failed to connect to relay"); |
| 379 | 363 | ||
| 380 | // Step 1: Send maintainer announcement (will be rejected - doesn't list our relay) | 364 | // Step 1: Send maintainer announcement (will be rejected - doesn't list our relay) |
| 381 | let maintainer_announcement = EventBuilder::new( | 365 | let maintainer_announcement = |
| 382 | Kind::GitRepoAnnouncement, | 366 | EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") |
| 383 | "Maintainer's repository", | 367 | .tags(vec![ |
| 384 | ) | 368 | Tag::identifier(identifier), |
| 385 | .tags(vec![ | 369 | Tag::custom( |
| 386 | Tag::identifier(identifier), | 370 | TagKind::custom("clone"), |
| 387 | Tag::custom( | 371 | vec![format!("https://example.com/{}.git", identifier)], |
| 388 | TagKind::custom("clone"), | 372 | ), |
| 389 | vec![format!("https://example.com/{}.git", identifier)], | 373 | Tag::custom( |
| 390 | ), | 374 | TagKind::custom("relays"), |
| 391 | Tag::custom( | 375 | vec!["wss://example.com".to_string()], |
| 392 | TagKind::custom("relays"), | 376 | ), |
| 393 | vec!["wss://example.com".to_string()], | 377 | ]) |
| 394 | ), | 378 | .sign_with_keys(&maintainer_keys) |
| 395 | ]) | 379 | .unwrap(); |
| 396 | .sign_with_keys(&maintainer_keys) | ||
| 397 | .unwrap(); | ||
| 398 | 380 | ||
| 399 | // Send maintainer announcement - expect it to be rejected | 381 | // Send maintainer announcement - expect it to be rejected |
| 400 | let _ = client.send_event(&maintainer_announcement).await; | 382 | let _ = client.send_event(&maintainer_announcement).await; |
| 401 | tokio::time::sleep(Duration::from_millis(200)).await; | 383 | tokio::time::sleep(Duration::from_millis(200)).await; |
| 402 | 384 | ||
| 403 | // Step 2: Send owner announcement with INVALID maintainer hex | 385 | // Step 2: Send owner announcement with INVALID maintainer hex |
| 404 | let owner_announcement = EventBuilder::new( | 386 | let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") |
| 405 | Kind::GitRepoAnnouncement, | 387 | .tags(vec![ |
| 406 | "Owner's repository", | 388 | Tag::identifier(identifier), |
| 407 | ) | 389 | Tag::custom( |
| 408 | .tags(vec![ | 390 | TagKind::custom("clone"), |
| 409 | Tag::identifier(identifier), | 391 | vec![format!("https://{}/{}.git", relay.domain(), identifier)], |
| 410 | Tag::custom( | 392 | ), |
| 411 | TagKind::custom("clone"), | 393 | Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]), |
| 412 | vec![format!("https://{}/{}.git", relay.domain(), identifier)], | 394 | Tag::custom( |
| 413 | ), | 395 | TagKind::custom("maintainers"), |
| 414 | Tag::custom( | 396 | vec!["invalid-hex-not-a-pubkey".to_string()], |
| 415 | TagKind::custom("relays"), | 397 | ), |
| 416 | vec![relay.url().to_string()], | 398 | ]) |
| 417 | ), | 399 | .sign_with_keys(&owner_keys) |
| 418 | Tag::custom( | 400 | .unwrap(); |
| 419 | TagKind::custom("maintainers"), | ||
| 420 | vec!["invalid-hex-not-a-pubkey".to_string()], | ||
| 421 | ), | ||
| 422 | ]) | ||
| 423 | .sign_with_keys(&owner_keys) | ||
| 424 | .unwrap(); | ||
| 425 | 401 | ||
| 426 | client.send_event(&owner_announcement).await.unwrap(); | 402 | client.send_event(&owner_announcement).await.unwrap(); |
| 427 | tokio::time::sleep(Duration::from_millis(500)).await; | 403 | tokio::time::sleep(Duration::from_millis(500)).await; |
| @@ -431,16 +407,21 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() { | |||
| 431 | .kind(Kind::GitRepoAnnouncement) | 407 | .kind(Kind::GitRepoAnnouncement) |
| 432 | .author(owner_keys.public_key()) | 408 | .author(owner_keys.public_key()) |
| 433 | .identifier(identifier); | 409 | .identifier(identifier); |
| 434 | 410 | ||
| 435 | let owner_found = wait_for_event_on_relay(relay.url(), owner_filter, Duration::from_secs(2)).await; | 411 | let owner_found = |
| 436 | assert!(owner_found, "Owner announcement should be accepted despite invalid maintainer"); | 412 | wait_for_event_on_relay(relay.url(), owner_filter, Duration::from_secs(2)).await; |
| 413 | assert!( | ||
| 414 | owner_found, | ||
| 415 | "Owner announcement should be accepted despite invalid maintainer" | ||
| 416 | ); | ||
| 437 | 417 | ||
| 438 | let maintainer_filter = Filter::new() | 418 | let maintainer_filter = Filter::new() |
| 439 | .kind(Kind::GitRepoAnnouncement) | 419 | .kind(Kind::GitRepoAnnouncement) |
| 440 | .author(maintainer_keys.public_key()) | 420 | .author(maintainer_keys.public_key()) |
| 441 | .identifier(identifier); | 421 | .identifier(identifier); |
| 442 | 422 | ||
| 443 | let maintainer_found = wait_for_event_on_relay(relay.url(), maintainer_filter, Duration::from_millis(500)).await; | 423 | let maintainer_found = |
| 424 | wait_for_event_on_relay(relay.url(), maintainer_filter, Duration::from_millis(500)).await; | ||
| 444 | assert!( | 425 | assert!( |
| 445 | !maintainer_found, | 426 | !maintainer_found, |
| 446 | "Maintainer announcement should NOT be re-processed (invalid pubkey)" | 427 | "Maintainer announcement should NOT be re-processed (invalid pubkey)" |