diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-26 08:45:16 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-26 09:56:44 +0000 |
| commit | a6edb42dfc653b6826b59b7f296e0d0c4ee74557 (patch) | |
| tree | 4355445d8d51672cbf1dd86af0a4bd2ffcde1a7a /grasp-audit/src/client.rs | |
| parent | 30411a938d072a59d68815c975735d40366ad874 (diff) | |
fix: parsing maintainers from announcement event
Diffstat (limited to 'grasp-audit/src/client.rs')
| -rw-r--r-- | grasp-audit/src/client.rs | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index 35aaccd..b2a4e38 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs | |||
| @@ -286,6 +286,80 @@ impl AuditClient { | |||
| 286 | Ok(event) | 286 | Ok(event) |
| 287 | } | 287 | } |
| 288 | 288 | ||
| 289 | /// Create a NIP-34 repository announcement event with maintainers | ||
| 290 | /// | ||
| 291 | /// This helper creates a properly formatted NIP-34 announcement that will be | ||
| 292 | /// accepted by GRASP relays (which require events to list the relay in clone/relays tags). | ||
| 293 | /// This variant also includes a maintainers tag for push authorization testing. | ||
| 294 | /// | ||
| 295 | /// # Arguments | ||
| 296 | /// * `test_name` - Name of the test (used to create unique repo identifier) | ||
| 297 | /// * `maintainer_pubkeys` - Hex pubkeys of maintainers who can push to the repository | ||
| 298 | /// | ||
| 299 | /// # Returns | ||
| 300 | /// A built and signed Event ready to be sent to the relay | ||
| 301 | pub async fn create_repo_announcement_with_maintainers( | ||
| 302 | &self, | ||
| 303 | test_name: &str, | ||
| 304 | maintainer_pubkeys: &[String], | ||
| 305 | ) -> Result<Event> { | ||
| 306 | // Get relay URL from client | ||
| 307 | let relay_url = self | ||
| 308 | .client | ||
| 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) | ||
| 361 | } | ||
| 362 | |||
| 289 | /// Create an issue (kind 1621) that references a repository | 363 | /// Create an issue (kind 1621) that references a repository |
| 290 | /// | 364 | /// |
| 291 | /// # Arguments | 365 | /// # Arguments |
| @@ -456,4 +530,55 @@ mod tests { | |||
| 456 | "Missing custom tag value" | 530 | "Missing custom tag value" |
| 457 | ); | 531 | ); |
| 458 | } | 532 | } |
| 533 | |||
| 534 | #[tokio::test] | ||
| 535 | async fn test_create_repo_announcement_with_maintainers() { | ||
| 536 | let config = AuditConfig::ci(); | ||
| 537 | let client = AuditClient::new_test(config); | ||
| 538 | |||
| 539 | // Create test maintainer pubkeys (hex format) | ||
| 540 | let maintainer_pubkeys = vec![ | ||
| 541 | "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2".to_string(), | ||
| 542 | "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3".to_string(), | ||
| 543 | ]; | ||
| 544 | |||
| 545 | // Note: We can't test create_repo_announcement_with_maintainers directly in unit tests | ||
| 546 | // because it requires a connected relay. Instead, we test the underlying event building | ||
| 547 | // with maintainers tag to verify the tag format is correct. | ||
| 548 | |||
| 549 | // Build an event with maintainers tag directly to test the tag format | ||
| 550 | let event = client | ||
| 551 | .event_builder( | ||
| 552 | Kind::GitRepoAnnouncement, | ||
| 553 | "Test repository", | ||
| 554 | ) | ||
| 555 | .tag(Tag::identifier("test-repo")) | ||
| 556 | .tag(Tag::custom( | ||
| 557 | TagKind::custom("maintainers"), | ||
| 558 | maintainer_pubkeys.clone(), | ||
| 559 | )) | ||
| 560 | .build(client.keys()) | ||
| 561 | .unwrap(); | ||
| 562 | |||
| 563 | // Verify the maintainers tag is present and correctly formatted | ||
| 564 | let maintainers_tag = event | ||
| 565 | .tags | ||
| 566 | .iter() | ||
| 567 | .find(|t| t.kind() == TagKind::custom("maintainers")); | ||
| 568 | |||
| 569 | assert!( | ||
| 570 | maintainers_tag.is_some(), | ||
| 571 | "Missing 'maintainers' tag in event" | ||
| 572 | ); | ||
| 573 | |||
| 574 | // Verify the tag contains the maintainer pubkeys | ||
| 575 | let tag = maintainers_tag.unwrap(); | ||
| 576 | let tag_vec: Vec<String> = tag.clone().to_vec(); | ||
| 577 | |||
| 578 | // First element is "maintainers", rest are the pubkeys | ||
| 579 | assert_eq!(tag_vec[0], "maintainers"); | ||
| 580 | assert_eq!(tag_vec.len(), 3, "Expected 3 elements: tag name + 2 pubkeys"); | ||
| 581 | assert_eq!(tag_vec[1], maintainer_pubkeys[0]); | ||
| 582 | assert_eq!(tag_vec[2], maintainer_pubkeys[1]); | ||
| 583 | } | ||
| 459 | } | 584 | } |