upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/client.rs
diff options
context:
space:
mode:
Diffstat (limited to 'grasp-audit/src/client.rs')
-rw-r--r--grasp-audit/src/client.rs147
1 files changed, 85 insertions, 62 deletions
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs
index b2a4e38..8b96f4f 100644
--- a/grasp-audit/src/client.rs
+++ b/grasp-audit/src/client.rs
@@ -10,6 +10,10 @@ pub struct AuditClient {
10 client: Client, 10 client: Client,
11 pub config: AuditConfig, 11 pub config: AuditConfig,
12 keys: Keys, 12 keys: Keys,
13 /// Maintainer keys for testing push authorization scenarios
14 maintainer_keys: Keys,
15 /// Recursive maintainer keys for testing recursive authorization scenarios
16 recursive_maintainer_keys: Keys,
13} 17}
14 18
15impl AuditClient { 19impl AuditClient {
@@ -17,17 +21,23 @@ impl AuditClient {
17 #[cfg(test)] 21 #[cfg(test)]
18 pub fn new_test(config: AuditConfig) -> Self { 22 pub fn new_test(config: AuditConfig) -> Self {
19 let keys = Keys::generate(); 23 let keys = Keys::generate();
24 let maintainer_keys = Keys::generate();
25 let recursive_maintainer_keys = Keys::generate();
20 let client = Client::new(keys.clone()); 26 let client = Client::new(keys.clone());
21 Self { 27 Self {
22 client, 28 client,
23 config, 29 config,
24 keys, 30 keys,
31 maintainer_keys,
32 recursive_maintainer_keys,
25 } 33 }
26 } 34 }
27 35
28 /// Create a new audit client 36 /// Create a new audit client
29 pub async fn new(relay_url: &str, config: AuditConfig) -> Result<Self> { 37 pub async fn new(relay_url: &str, config: AuditConfig) -> Result<Self> {
30 let keys = Keys::generate(); 38 let keys = Keys::generate();
39 let maintainer_keys = Keys::generate();
40 let recursive_maintainer_keys = Keys::generate();
31 let client = Client::new(keys.clone()); 41 let client = Client::new(keys.clone());
32 42
33 // Add relay and connect 43 // Add relay and connect
@@ -76,6 +86,8 @@ impl AuditClient {
76 client, 86 client,
77 config, 87 config,
78 keys, 88 keys,
89 maintainer_keys,
90 recursive_maintainer_keys,
79 }) 91 })
80 } 92 }
81 93
@@ -222,17 +234,45 @@ impl AuditClient {
222 &self.keys 234 &self.keys
223 } 235 }
224 236
225 /// Create a NIP-34 repository announcement event 237 /// Get the maintainer keys (for push authorization testing)
238 pub fn maintainer_keys(&self) -> &Keys {
239 &self.maintainer_keys
240 }
241
242 /// Get the maintainer public key as a hex string
243 pub fn maintainer_pubkey_hex(&self) -> String {
244 self.maintainer_keys.public_key().to_hex()
245 }
246
247 /// Get the recursive maintainer keys (for recursive authorization testing)
248 pub fn recursive_maintainer_keys(&self) -> &Keys {
249 &self.recursive_maintainer_keys
250 }
251
252 /// Get the recursive maintainer public key as a hex string
253 pub fn recursive_maintainer_pubkey_hex(&self) -> String {
254 self.recursive_maintainer_keys.public_key().to_hex()
255 }
256
257 /// Create a NIP-34 repository announcement event with full customization
226 /// 258 ///
227 /// This helper creates a properly formatted NIP-34 announcement that will be 259 /// This is the core method for creating repository announcements. It allows
228 /// accepted by GRASP relays (which require events to list the relay in clone/relays tags). 260 /// specifying the signing keys and maintainers, making it suitable for all
261 /// repo creation scenarios including maintainer and recursive maintainer testing.
229 /// 262 ///
230 /// # Arguments 263 /// # Arguments
231 /// * `test_name` - Name of the test (used to create unique repo identifier) 264 /// * `test_name` - Name of the test (used to create unique repo identifier)
265 /// * `signing_keys` - The keys to sign the event with (also used for clone URL)
266 /// * `maintainer_pubkeys` - Hex pubkeys of maintainers who can push to the repository
232 /// 267 ///
233 /// # Returns 268 /// # Returns
234 /// A built and signed Event ready to be sent to the relay 269 /// A tuple of (Event, repo_id) - the built event and the repository identifier
235 pub async fn create_repo_announcement(&self, test_name: &str) -> Result<Event> { 270 pub async fn create_repo_announcement_custom(
271 &self,
272 test_name: &str,
273 signing_keys: &Keys,
274 maintainer_pubkeys: &[String],
275 ) -> Result<(Event, String)> {
236 // Get relay URL from client 276 // Get relay URL from client
237 let relay_url = self 277 let relay_url = self
238 .client 278 .client
@@ -251,8 +291,8 @@ impl AuditClient {
251 // Create unique repository identifier using UUID for consistency 291 // Create unique repository identifier using UUID for consistency
252 let repo_id = format!("{}-{}", test_name, &uuid::Uuid::new_v4().to_string()[..8]); 292 let repo_id = format!("{}-{}", test_name, &uuid::Uuid::new_v4().to_string()[..8]);
253 293
254 // Get npub for clone URL 294 // Get npub for clone URL from signing keys
255 let npub = self 295 let npub = signing_keys
256 .public_key() 296 .public_key()
257 .to_bech32() 297 .to_bech32()
258 .map_err(|e| anyhow!("Failed to convert public key to bech32 npub format: {}", e))?; 298 .map_err(|e| anyhow!("Failed to convert public key to bech32 npub format: {}", e))?;
@@ -280,9 +320,35 @@ impl AuditClient {
280 TagKind::custom("relays"), 320 TagKind::custom("relays"),
281 vec![relay_url.clone()], 321 vec![relay_url.clone()],
282 )) 322 ))
283 .build(self.keys()) 323 .tag(Tag::custom(
324 TagKind::custom("maintainers"),
325 maintainer_pubkeys.to_vec(),
326 ))
327 .build(signing_keys)
284 .map_err(|e| anyhow!("Failed to build repository announcement event: {}", e))?; 328 .map_err(|e| anyhow!("Failed to build repository announcement event: {}", e))?;
285 329
330 Ok((event, repo_id))
331 }
332
333 /// Create a NIP-34 repository announcement event with the client's maintainer
334 ///
335 /// This helper creates a properly formatted NIP-34 announcement that will be
336 /// accepted by GRASP relays (which require events to list the relay in clone/relays tags).
337 /// The client's maintainer key is automatically added to the maintainers tag.
338 ///
339 /// # Arguments
340 /// * `test_name` - Name of the test (used to create unique repo identifier)
341 ///
342 /// # Returns
343 /// A built and signed Event ready to be sent to the relay
344 pub async fn create_repo_announcement(&self, test_name: &str) -> Result<Event> {
345 let (event, _repo_id) = self
346 .create_repo_announcement_custom(
347 test_name,
348 self.keys(),
349 &[self.maintainer_pubkey_hex()],
350 )
351 .await?;
286 Ok(event) 352 Ok(event)
287 } 353 }
288 354
@@ -303,60 +369,9 @@ impl AuditClient {
303 test_name: &str, 369 test_name: &str,
304 maintainer_pubkeys: &[String], 370 maintainer_pubkeys: &[String],
305 ) -> Result<Event> { 371 ) -> Result<Event> {
306 // Get relay URL from client 372 let (event, _repo_id) = self
307 let relay_url = self 373 .create_repo_announcement_custom(test_name, self.keys(), maintainer_pubkeys)
308 .client 374 .await?;
309 .relays()
310 .await
311 .keys()
312 .next()
313 .ok_or_else(|| anyhow!("No relay connected"))?
314 .to_string();
315
316 // Convert WebSocket URL to HTTP URL for clone tag
317 let http_url = relay_url
318 .replace("ws://", "http://")
319 .replace("wss://", "https://");
320
321 // Create unique repository identifier using UUID for consistency
322 let repo_id = format!("{}-{}", test_name, &uuid::Uuid::new_v4().to_string()[..8]);
323
324 // Get npub for clone URL
325 let npub = self
326 .public_key()
327 .to_bech32()
328 .map_err(|e| anyhow!("Failed to convert public key to bech32 npub format: {}", e))?;
329
330 // Build kind 30617 repository announcement with maintainers tag
331 let event = self
332 .event_builder(
333 Kind::GitRepoAnnouncement,
334 format!("Test repository for {}", test_name),
335 )
336 .tag(Tag::identifier(&repo_id))
337 .tag(Tag::custom(
338 TagKind::custom("name"),
339 vec![format!("{} Test Repository", test_name)],
340 ))
341 .tag(Tag::custom(
342 TagKind::custom("description"),
343 vec![format!("Repository for {} testing", test_name)],
344 ))
345 .tag(Tag::custom(
346 TagKind::custom("clone"),
347 vec![format!("{}/{}/{}.git", http_url, npub, repo_id)],
348 ))
349 .tag(Tag::custom(
350 TagKind::custom("relays"),
351 vec![relay_url.clone()],
352 ))
353 .tag(Tag::custom(
354 TagKind::custom("maintainers"),
355 maintainer_pubkeys.to_vec(),
356 ))
357 .build(self.keys())
358 .map_err(|e| anyhow!("Failed to build repository announcement event: {}", e))?;
359
360 Ok(event) 375 Ok(event)
361 } 376 }
362 377
@@ -465,10 +480,14 @@ mod tests {
465 fn test_event_builder() { 480 fn test_event_builder() {
466 let config = AuditConfig::ci(); 481 let config = AuditConfig::ci();
467 let keys = Keys::generate(); 482 let keys = Keys::generate();
483 let maintainer_keys = Keys::generate();
484 let recursive_maintainer_keys = Keys::generate();
468 let client = AuditClient { 485 let client = AuditClient {
469 client: Client::new(keys.clone()), 486 client: Client::new(keys.clone()),
470 config: config.clone(), 487 config: config.clone(),
471 keys: keys.clone(), 488 keys: keys.clone(),
489 maintainer_keys,
490 recursive_maintainer_keys,
472 }; 491 };
473 492
474 let _builder = client.event_builder(Kind::TextNote, "test content"); 493 let _builder = client.event_builder(Kind::TextNote, "test content");
@@ -481,10 +500,14 @@ mod tests {
481 fn test_audit_tags_automatically_added() { 500 fn test_audit_tags_automatically_added() {
482 let config = AuditConfig::ci(); 501 let config = AuditConfig::ci();
483 let keys = Keys::generate(); 502 let keys = Keys::generate();
503 let maintainer_keys = Keys::generate();
504 let recursive_maintainer_keys = Keys::generate();
484 let client = AuditClient { 505 let client = AuditClient {
485 client: Client::new(keys.clone()), 506 client: Client::new(keys.clone()),
486 config: config.clone(), 507 config: config.clone(),
487 keys: keys.clone(), 508 keys: keys.clone(),
509 maintainer_keys,
510 recursive_maintainer_keys,
488 }; 511 };
489 512
490 // Create an event with a custom tag 513 // Create an event with a custom tag