upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-09 19:58:41 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-09 19:58:41 +0000
commitb28a356cb41077ccee12a9c52f4ef2054e76cac6 (patch)
tree2a0867f1ab0216e86efa062aef90b2b8077e6fb9 /tests
parent6dd9fcd5392891b0ddb7894e2c5cb40450eae00e (diff)
chore: cargo fmt
Diffstat (limited to 'tests')
-rw-r--r--tests/common/sync_helpers.rs2
-rw-r--r--tests/state_authorization.rs242
-rw-r--r--tests/sync/maintainer_reprocessing.rs311
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::*;
13async fn test_reject_state_without_announcement() { 13async 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() {
68async fn test_reject_state_from_unauthorized_author() { 69async 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() {
136async fn test_accept_state_from_announcement_author() { 137async 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() {
209async fn test_accept_state_from_maintainer() { 206async 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
146async fn test_maintainer_announcement_cold_index_prevents_refetch() { 143async 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]
366async fn test_invalid_maintainer_pubkey_handled_gracefully() { 350async 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)"