diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-01 22:13:37 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-01 22:13:37 +0000 |
| commit | 11870a0f810accf0431d82a74b6fd3adec9d23df (patch) | |
| tree | 82a61995b1a18b4a618544b159e886d5c2e95015 /grasp-audit/src/fixtures.rs | |
| parent | 883774c7785c57bded21ba3be63d0fdac8a51dcf (diff) | |
better fixtures: refs/nostr tests
Diffstat (limited to 'grasp-audit/src/fixtures.rs')
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index 5e8c50a..d054e36 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -188,6 +188,50 @@ pub enum FixtureKind { | |||
| 188 | /// - Timestamp: 1 second in the past | 188 | /// - Timestamp: 1 second in the past |
| 189 | PREvent, | 189 | PREvent, |
| 190 | 190 | ||
| 191 | /// PR event generated (built) but NOT sent to relay | ||
| 192 | /// | ||
| 193 | /// This is a "Generated" stage fixture - the event is created but not published. | ||
| 194 | /// Useful for tests that need the PR event ID before the event exists on the relay. | ||
| 195 | /// | ||
| 196 | /// - Requires ValidRepo (uses same repo_id) | ||
| 197 | /// - Signed by `client.pr_author_keys()` | ||
| 198 | /// - Kind 1618 (NIP-34 PR) | ||
| 199 | /// - Includes `c` tag pointing to PR_TEST_COMMIT_HASH | ||
| 200 | /// - NOT sent to relay (use `client.send_event()` to publish when ready) | ||
| 201 | PREventGenerated, | ||
| 202 | |||
| 203 | /// Wrong commit pushed to refs/nostr/<pr-event-id> BEFORE PR event is sent | ||
| 204 | /// | ||
| 205 | /// This is a "DataPushed" stage fixture for testing pre-event ref behavior. | ||
| 206 | /// The server has refs/nostr/<pr-event-id> pointing to DETERMINISTIC_COMMIT_HASH | ||
| 207 | /// (the "wrong" commit), but no PR event exists yet on the relay. | ||
| 208 | /// | ||
| 209 | /// Server state after this fixture: | ||
| 210 | /// - ValidRepo announcement on relay | ||
| 211 | /// - refs/nostr/<pr-event-id> exists on git server with wrong commit | ||
| 212 | /// - PR event is NOT on relay (but returned for tests to publish later) | ||
| 213 | /// | ||
| 214 | /// - Requires PREventGenerated (for the event ID) | ||
| 215 | /// - Clones repo, creates wrong commit, pushes to refs/nostr/<event-id> | ||
| 216 | /// - Returns: the unsent PR event (tests can publish it later) | ||
| 217 | PRWrongCommitPushedBeforeEvent, | ||
| 218 | |||
| 219 | /// PR event sent to relay AFTER wrong commit was pushed to refs/nostr/<pr-event-id> | ||
| 220 | /// | ||
| 221 | /// This is a compound fixture testing post-event behavior. | ||
| 222 | /// The server had refs/nostr/<pr-event-id> pointing to wrong commit, | ||
| 223 | /// then the PR event was published (which may trigger cleanup). | ||
| 224 | /// | ||
| 225 | /// Server state after this fixture: | ||
| 226 | /// - ValidRepo announcement on relay | ||
| 227 | /// - PR event is on relay | ||
| 228 | /// - refs/nostr/<pr-event-id> may have been cleaned up (that's what tests verify) | ||
| 229 | /// | ||
| 230 | /// - Requires PRWrongCommitPushedBeforeEvent | ||
| 231 | /// - Sends the PR event to relay | ||
| 232 | /// - Returns: the sent PR event | ||
| 233 | PREventSentAfterWrongPush, | ||
| 234 | |||
| 191 | /// Owner's state event with git data successfully pushed (full 4-stage fixture) | 235 | /// Owner's state event with git data successfully pushed (full 4-stage fixture) |
| 192 | /// | 236 | /// |
| 193 | /// This fixture represents the complete flow for testing push authorization: | 237 | /// This fixture represents the complete flow for testing push authorization: |
| @@ -268,6 +312,9 @@ impl FixtureKind { | |||
| 268 | Self::RecursiveMaintainerState => vec![Self::ValidRepo], | 312 | Self::RecursiveMaintainerState => vec![Self::ValidRepo], |
| 269 | Self::RecursiveMaintainerRepoAndState => vec![Self::ValidRepo], | 313 | Self::RecursiveMaintainerRepoAndState => vec![Self::ValidRepo], |
| 270 | Self::PREvent => vec![Self::ValidRepo], | 314 | Self::PREvent => vec![Self::ValidRepo], |
| 315 | Self::PREventGenerated => vec![Self::ValidRepo], | ||
| 316 | Self::PRWrongCommitPushedBeforeEvent => vec![Self::PREventGenerated], | ||
| 317 | Self::PREventSentAfterWrongPush => vec![Self::PRWrongCommitPushedBeforeEvent], | ||
| 271 | Self::OwnerStateDataPushed => vec![Self::ValidRepo], | 318 | Self::OwnerStateDataPushed => vec![Self::ValidRepo], |
| 272 | 319 | ||
| 273 | // Fixtures that depend on RepoWithIssue | 320 | // Fixtures that depend on RepoWithIssue |
| @@ -296,6 +343,12 @@ impl FixtureKind { | |||
| 296 | Self::RecursiveMaintainerStateDataPushed => true, | 343 | Self::RecursiveMaintainerStateDataPushed => true, |
| 297 | // RecursiveMaintainerRepoAndState sends multiple events internally | 344 | // RecursiveMaintainerRepoAndState sends multiple events internally |
| 298 | Self::RecursiveMaintainerRepoAndState => true, | 345 | Self::RecursiveMaintainerRepoAndState => true, |
| 346 | // PREventGenerated builds but does NOT send the PR event (that's the point) | ||
| 347 | Self::PREventGenerated => true, | ||
| 348 | // PRWrongCommitPushedBeforeEvent pushes git data but doesn't send event | ||
| 349 | Self::PRWrongCommitPushedBeforeEvent => true, | ||
| 350 | // PREventSentAfterWrongPush sends the PR event internally | ||
| 351 | Self::PREventSentAfterWrongPush => true, | ||
| 299 | // All other fixtures return a single event for the caller to send | 352 | // All other fixtures return a single event for the caller to send |
| 300 | _ => false, | 353 | _ => false, |
| 301 | } | 354 | } |
| @@ -753,6 +806,57 @@ impl<'a> TestContext<'a> { | |||
| 753 | .map_err(|e| anyhow::anyhow!("Failed to build PR event: {}", e)) | 806 | .map_err(|e| anyhow::anyhow!("Failed to build PR event: {}", e)) |
| 754 | } | 807 | } |
| 755 | 808 | ||
| 809 | FixtureKind::PREventGenerated => { | ||
| 810 | // Same as PREvent but will NOT be sent to relay (caller may send it later) | ||
| 811 | // This fixture is for "Generated" stage only | ||
| 812 | use nostr_sdk::prelude::*; | ||
| 813 | |||
| 814 | // ValidRepo is ensured by ensure_fixture before this is called | ||
| 815 | let repo = self.get_cached_dependency(FixtureKind::ValidRepo)?; | ||
| 816 | |||
| 817 | let repo_id = repo | ||
| 818 | .tags | ||
| 819 | .iter() | ||
| 820 | .find(|t| t.kind() == TagKind::d()) | ||
| 821 | .and_then(|t| t.content()) | ||
| 822 | .ok_or_else(|| anyhow::anyhow!("Missing repo_id in ValidRepo fixture"))? | ||
| 823 | .to_string(); | ||
| 824 | |||
| 825 | // Create PR event 1 second in the past | ||
| 826 | let base_time = Timestamp::now().as_u64(); | ||
| 827 | let pr_timestamp = Timestamp::from(base_time - 1); | ||
| 828 | |||
| 829 | // Build NIP-34 PR event (kind 1618) | ||
| 830 | self.client | ||
| 831 | .event_builder( | ||
| 832 | Kind::Custom(1618), // NIP-34 PR kind (has 'c' tag for commit) | ||
| 833 | "Test PR for GRASP validation", | ||
| 834 | ) | ||
| 835 | .tag(Tag::custom( | ||
| 836 | TagKind::custom("a"), | ||
| 837 | vec![format!( | ||
| 838 | "30617:{}:{}", | ||
| 839 | self.client.public_key().to_hex(), // Owner pubkey | ||
| 840 | repo_id | ||
| 841 | )], | ||
| 842 | )) | ||
| 843 | .tag(Tag::custom( | ||
| 844 | TagKind::custom("c"), | ||
| 845 | vec![PR_TEST_COMMIT_HASH.to_string()], | ||
| 846 | )) | ||
| 847 | .custom_time(pr_timestamp) | ||
| 848 | .build(self.client.pr_author_keys()) | ||
| 849 | .map_err(|e| anyhow::anyhow!("Failed to build PR event: {}", e)) | ||
| 850 | } | ||
| 851 | |||
| 852 | FixtureKind::PRWrongCommitPushedBeforeEvent => { | ||
| 853 | self.build_pr_wrong_commit_pushed_before_event().await | ||
| 854 | } | ||
| 855 | |||
| 856 | FixtureKind::PREventSentAfterWrongPush => { | ||
| 857 | self.build_pr_event_sent_after_wrong_push().await | ||
| 858 | } | ||
| 859 | |||
| 756 | FixtureKind::OwnerStateDataPushed => { | 860 | FixtureKind::OwnerStateDataPushed => { |
| 757 | self.build_owner_state_data_pushed().await | 861 | self.build_owner_state_data_pushed().await |
| 758 | } | 862 | } |
| @@ -1370,6 +1474,127 @@ impl<'a> TestContext<'a> { | |||
| 1370 | } | 1474 | } |
| 1371 | } | 1475 | } |
| 1372 | 1476 | ||
| 1477 | /// Build PRWrongCommitPushedBeforeEvent fixture | ||
| 1478 | /// | ||
| 1479 | /// This fixture sets up a scenario where: | ||
| 1480 | /// 1. A repo exists on the relay | ||
| 1481 | /// 2. A PR event is generated (but NOT sent to relay) | ||
| 1482 | /// 3. A wrong commit is pushed to refs/nostr/<pr-event-id> | ||
| 1483 | /// | ||
| 1484 | /// Server state after: | ||
| 1485 | /// - ValidRepo announcement on relay | ||
| 1486 | /// - refs/nostr/<pr-event-id> on git server pointing to DETERMINISTIC_COMMIT_HASH (wrong) | ||
| 1487 | /// - NO PR event on relay | ||
| 1488 | /// | ||
| 1489 | /// Returns: the unsent PR event (tests can publish it later) | ||
| 1490 | async fn build_pr_wrong_commit_pushed_before_event(&self) -> Result<Event> { | ||
| 1491 | use nostr_sdk::prelude::*; | ||
| 1492 | |||
| 1493 | // Get the cached PREventGenerated (the unsent PR event) | ||
| 1494 | let pr_event = self.get_cached_dependency(FixtureKind::PREventGenerated)?; | ||
| 1495 | let pr_event_id = pr_event.id.to_hex(); | ||
| 1496 | |||
| 1497 | // Get the ValidRepo to extract repo info | ||
| 1498 | let repo = self.get_cached_dependency(FixtureKind::ValidRepo)?; | ||
| 1499 | let repo_id = self.extract_repo_id(&repo)?; | ||
| 1500 | |||
| 1501 | // Get relay domain for cloning | ||
| 1502 | let relay_domain = self.get_relay_domain().await?; | ||
| 1503 | |||
| 1504 | // Owner npub for clone URL | ||
| 1505 | let npub = repo | ||
| 1506 | .pubkey | ||
| 1507 | .to_bech32() | ||
| 1508 | .map_err(|e| anyhow::anyhow!("Failed to convert pubkey to bech32: {}", e))?; | ||
| 1509 | |||
| 1510 | // Clone the repository (fresh clone - local repos are never cached) | ||
| 1511 | let clone_path = clone_repo(&relay_domain, &npub, &repo_id) | ||
| 1512 | .map_err(|e| anyhow::anyhow!("Failed to clone repo: {}", e))?; | ||
| 1513 | |||
| 1514 | // Cleanup helper | ||
| 1515 | let cleanup = |path: &PathBuf| { | ||
| 1516 | let _ = fs::remove_dir_all(path); | ||
| 1517 | }; | ||
| 1518 | |||
| 1519 | // Create a WRONG commit (Owner variant, not PRTestCommit) | ||
| 1520 | // This commit hash will NOT match what's in the PR event's `c` tag | ||
| 1521 | let wrong_commit_hash = match create_deterministic_commit_with_variant( | ||
| 1522 | &clone_path, | ||
| 1523 | CommitVariant::Owner, | ||
| 1524 | ) { | ||
| 1525 | Ok(h) => h, | ||
| 1526 | Err(e) => { | ||
| 1527 | cleanup(&clone_path); | ||
| 1528 | return Err(anyhow::anyhow!("Failed to create wrong commit: {}", e)); | ||
| 1529 | } | ||
| 1530 | }; | ||
| 1531 | |||
| 1532 | // Verify it's actually different from expected PR commit | ||
| 1533 | if wrong_commit_hash == PR_TEST_COMMIT_HASH { | ||
| 1534 | cleanup(&clone_path); | ||
| 1535 | return Err(anyhow::anyhow!( | ||
| 1536 | "Test setup error: wrong_commit_hash {} equals PR_TEST_COMMIT_HASH", | ||
| 1537 | wrong_commit_hash | ||
| 1538 | )); | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | // Create master branch if needed and push to refs/nostr/<pr-event-id> | ||
| 1542 | let _ = Command::new("git") | ||
| 1543 | .args(["branch", "-M", "master"]) | ||
| 1544 | .current_dir(&clone_path) | ||
| 1545 | .output(); | ||
| 1546 | |||
| 1547 | let push_output = Command::new("git") | ||
| 1548 | .args([ | ||
| 1549 | "push", | ||
| 1550 | "origin", | ||
| 1551 | &format!("master:refs/nostr/{}", pr_event_id), | ||
| 1552 | ]) | ||
| 1553 | .current_dir(&clone_path) | ||
| 1554 | .output() | ||
| 1555 | .map_err(|e| { | ||
| 1556 | cleanup(&clone_path); | ||
| 1557 | anyhow::anyhow!("Failed to execute git push: {}", e) | ||
| 1558 | })?; | ||
| 1559 | |||
| 1560 | cleanup(&clone_path); | ||
| 1561 | |||
| 1562 | if !push_output.status.success() { | ||
| 1563 | let stderr = String::from_utf8_lossy(&push_output.stderr); | ||
| 1564 | return Err(anyhow::anyhow!( | ||
| 1565 | "Initial push to refs/nostr/{} failed (expected success before PR event exists): {}", | ||
| 1566 | pr_event_id, | ||
| 1567 | stderr | ||
| 1568 | )); | ||
| 1569 | } | ||
| 1570 | |||
| 1571 | // Return the unsent PR event (tests can publish it later) | ||
| 1572 | Ok(pr_event) | ||
| 1573 | } | ||
| 1574 | |||
| 1575 | /// Build PREventSentAfterWrongPush fixture | ||
| 1576 | /// | ||
| 1577 | /// This fixture builds on PRWrongCommitPushedBeforeEvent by sending the PR event. | ||
| 1578 | /// After this fixture, the relay has: | ||
| 1579 | /// - ValidRepo announcement | ||
| 1580 | /// - PR event | ||
| 1581 | /// - refs/nostr/<pr-event-id> may have been cleaned up (that's what tests verify) | ||
| 1582 | /// | ||
| 1583 | /// Returns: the sent PR event | ||
| 1584 | async fn build_pr_event_sent_after_wrong_push(&self) -> Result<Event> { | ||
| 1585 | // Get the PR event that was cached by PRWrongCommitPushedBeforeEvent | ||
| 1586 | let pr_event = self.get_cached_dependency(FixtureKind::PRWrongCommitPushedBeforeEvent)?; | ||
| 1587 | |||
| 1588 | // Send the PR event to relay | ||
| 1589 | self.client.send_event(pr_event.clone()).await?; | ||
| 1590 | |||
| 1591 | // Wait for relay to process | ||
| 1592 | tokio::time::sleep(std::time::Duration::from_millis(500)).await; | ||
| 1593 | |||
| 1594 | // Return the now-sent PR event | ||
| 1595 | Ok(pr_event) | ||
| 1596 | } | ||
| 1597 | |||
| 1373 | /// Get relay domain (host:port) from the connected relay | 1598 | /// Get relay domain (host:port) from the connected relay |
| 1374 | /// | 1599 | /// |
| 1375 | /// Extracts the domain from the relay URL for git HTTP operations. | 1600 | /// Extracts the domain from the relay URL for git HTTP operations. |