diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-13 09:24:51 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-13 09:24:51 +0000 |
| commit | f4e8e1089ae6e8e78c3576246d9747bb585fdc18 (patch) | |
| tree | 37cd429b6c5468996178f1d9bbaafe6f789b3605 /grasp-audit/src/fixtures.rs | |
| parent | faac6027deaf5f1e121c05df2d8a6336fd6eaf8d (diff) | |
test: add PR purgatory tests with PREvent2 fixtures
Add new fixtures for testing PR purgatory mechanism:
- PREvent2Generated: PR event with different commit hash
- PREvent2Sent: PR event sent to relay (enters purgatory)
- PREvent2GitDataPushed: Git data pushed after event sent
- PREvent2Served: Full fixture with event served
Add PRTestCommit2 variant for second PR test commit.
Update purgatory tests to use new fixtures for proper PR purgatory testing.
Diffstat (limited to 'grasp-audit/src/fixtures.rs')
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/grasp-audit/src/fixtures.rs b/grasp-audit/src/fixtures.rs index 56d29ef..8a51d77 100644 --- a/grasp-audit/src/fixtures.rs +++ b/grasp-audit/src/fixtures.rs | |||
| @@ -103,6 +103,17 @@ pub const RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH: &str = | |||
| 103 | /// - Parent: none (root commit) | 103 | /// - Parent: none (root commit) |
| 104 | pub const PR_TEST_COMMIT_HASH: &str = "5a51b30e4615b572dcd5b9e487861b58605a5c21"; | 104 | pub const PR_TEST_COMMIT_HASH: &str = "5a51b30e4615b572dcd5b9e487861b58605a5c21"; |
| 105 | 105 | ||
| 106 | /// Deterministic commit hash for second PR test fixtures (PRTestCommit2 variant) | ||
| 107 | /// This is the hash produced by creating a commit with: | ||
| 108 | /// - Message: "PR test deterministic commit 2" | ||
| 109 | /// - File: test.txt containing "PR test deterministic commit 2\n" (with trailing newline) | ||
| 110 | /// - Author date: 2024-01-01T00:00:00Z | ||
| 111 | /// - Committer date: 2024-01-01T00:00:00Z | ||
| 112 | /// - GPG signing: disabled | ||
| 113 | /// - User: "GRASP Audit Test <test@grasp-audit.local>" | ||
| 114 | /// - Parent: none (root commit) | ||
| 115 | pub const PR_TEST_COMMIT_HASH_2: &str = "99420bc57835f5bc8ca20ab21a8d12850043920e"; | ||
| 116 | |||
| 106 | /// Types of test fixtures available | 117 | /// Types of test fixtures available |
| 107 | /// | 118 | /// |
| 108 | /// ## Fixture Dependencies | 119 | /// ## Fixture Dependencies |
| @@ -216,6 +227,50 @@ pub enum FixtureKind { | |||
| 216 | /// - Returns: the sent PR event | 227 | /// - Returns: the sent PR event |
| 217 | PREventSentAfterWrongPush, | 228 | PREventSentAfterWrongPush, |
| 218 | 229 | ||
| 230 | /// Second PR event generated (built) but NOT sent to relay | ||
| 231 | /// | ||
| 232 | /// Uses PR_TEST_COMMIT_HASH_2 (different from PR_TEST_COMMIT_HASH). | ||
| 233 | /// This allows testing purgatory mechanism with a separate PR event | ||
| 234 | /// that doesn't conflict with existing PR fixtures. | ||
| 235 | /// | ||
| 236 | /// - Requires ValidRepoServed (uses same repo_id, needs git data to exist) | ||
| 237 | /// - Signed by `client.pr_author_keys()` | ||
| 238 | /// - Kind 1618 (NIP-34 PR) | ||
| 239 | /// - Includes `c` tag pointing to PR_TEST_COMMIT_HASH_2 | ||
| 240 | /// - NOT sent to relay | ||
| 241 | PREvent2Generated, | ||
| 242 | |||
| 243 | /// Second PR event sent to relay (enters purgatory) | ||
| 244 | /// | ||
| 245 | /// After this fixture: | ||
| 246 | /// - PR event is on relay but NOT served (in purgatory) | ||
| 247 | /// - No git data at refs/nostr/<pr-event-id> | ||
| 248 | /// | ||
| 249 | /// - Requires PREvent2Generated | ||
| 250 | /// - Sends the PR event to relay | ||
| 251 | /// - Returns: the sent PR event (in purgatory) | ||
| 252 | PREvent2Sent, | ||
| 253 | |||
| 254 | /// Git data pushed for second PR event AFTER event was sent | ||
| 255 | /// | ||
| 256 | /// After this fixture: | ||
| 257 | /// - PR event was in purgatory | ||
| 258 | /// - Correct commit pushed to refs/nostr/<pr-event-id> | ||
| 259 | /// - PR event should be released from purgatory | ||
| 260 | /// | ||
| 261 | /// - Requires PREvent2Sent | ||
| 262 | /// - Pushes correct commit (PR_TEST_COMMIT_HASH_2) to refs/nostr/<pr-event-id> | ||
| 263 | /// - Returns: the PR event (should now be served) | ||
| 264 | PREvent2GitDataPushed, | ||
| 265 | |||
| 266 | /// Full fixture: second PR event sent, git pushed, event served | ||
| 267 | /// | ||
| 268 | /// Combines PREvent2Sent + PREvent2GitDataPushed for convenience. | ||
| 269 | /// | ||
| 270 | /// - Requires PREvent2GitDataPushed | ||
| 271 | /// - Returns: the served PR event | ||
| 272 | PREvent2Served, | ||
| 273 | |||
| 219 | /// Owner's state event with git data successfully pushed (full 4-stage fixture) | 274 | /// Owner's state event with git data successfully pushed (full 4-stage fixture) |
| 220 | /// | 275 | /// |
| 221 | /// This fixture represents the complete flow for testing state push authorization: | 276 | /// This fixture represents the complete flow for testing state push authorization: |
| @@ -293,6 +348,12 @@ impl FixtureKind { | |||
| 293 | Self::PRWrongCommitPushedBeforeEvent => vec![Self::PREventGenerated], | 348 | Self::PRWrongCommitPushedBeforeEvent => vec![Self::PREventGenerated], |
| 294 | Self::PREventSentAfterWrongPush => vec![Self::PRWrongCommitPushedBeforeEvent], | 349 | Self::PREventSentAfterWrongPush => vec![Self::PRWrongCommitPushedBeforeEvent], |
| 295 | 350 | ||
| 351 | // Second PR event fixtures (for purgatory testing) | ||
| 352 | Self::PREvent2Generated => vec![Self::ValidRepoServed], | ||
| 353 | Self::PREvent2Sent => vec![Self::PREvent2Generated], | ||
| 354 | Self::PREvent2GitDataPushed => vec![Self::PREvent2Sent], | ||
| 355 | Self::PREvent2Served => vec![Self::PREvent2GitDataPushed], | ||
| 356 | |||
| 296 | Self::OwnerStateDataPushed => vec![Self::ValidRepoSent], | 357 | Self::OwnerStateDataPushed => vec![Self::ValidRepoSent], |
| 297 | 358 | ||
| 298 | // Fixtures that depend on RepoWithIssue | 359 | // Fixtures that depend on RepoWithIssue |
| @@ -329,6 +390,11 @@ impl FixtureKind { | |||
| 329 | Self::PRWrongCommitPushedBeforeEvent => true, | 390 | Self::PRWrongCommitPushedBeforeEvent => true, |
| 330 | // PREventSentAfterWrongPush sends the PR event internally | 391 | // PREventSentAfterWrongPush sends the PR event internally |
| 331 | Self::PREventSentAfterWrongPush => true, | 392 | Self::PREventSentAfterWrongPush => true, |
| 393 | // Second PR event fixtures handle their own events/git data | ||
| 394 | Self::PREvent2Generated => true, | ||
| 395 | Self::PREvent2Sent => true, | ||
| 396 | Self::PREvent2GitDataPushed => true, | ||
| 397 | Self::PREvent2Served => true, | ||
| 332 | // HeadSetToDevelopBranch sends its state event internally | 398 | // HeadSetToDevelopBranch sends its state event internally |
| 333 | Self::HeadSetToDevelopBranch => true, | 399 | Self::HeadSetToDevelopBranch => true, |
| 334 | // ValidRepoServed doesn't send anything itself, just returns cached event | 400 | // ValidRepoServed doesn't send anything itself, just returns cached event |
| @@ -800,6 +866,11 @@ impl<'a> TestContext<'a> { | |||
| 800 | self.build_pr_event_sent_after_wrong_push().await | 866 | self.build_pr_event_sent_after_wrong_push().await |
| 801 | } | 867 | } |
| 802 | 868 | ||
| 869 | FixtureKind::PREvent2Generated => self.build_pr_event_2_generated().await, | ||
| 870 | FixtureKind::PREvent2Sent => self.build_pr_event_2_sent().await, | ||
| 871 | FixtureKind::PREvent2GitDataPushed => self.build_pr_event_2_git_data_pushed().await, | ||
| 872 | FixtureKind::PREvent2Served => self.build_pr_event_2_served().await, | ||
| 873 | |||
| 803 | FixtureKind::OwnerStateDataPushed => self.build_owner_state_data_pushed().await, | 874 | FixtureKind::OwnerStateDataPushed => self.build_owner_state_data_pushed().await, |
| 804 | 875 | ||
| 805 | FixtureKind::MaintainerStateDataPushed => { | 876 | FixtureKind::MaintainerStateDataPushed => { |
| @@ -1561,6 +1632,173 @@ impl<'a> TestContext<'a> { | |||
| 1561 | Ok(pr_event) | 1632 | Ok(pr_event) |
| 1562 | } | 1633 | } |
| 1563 | 1634 | ||
| 1635 | /// Build PREvent2Generated fixture | ||
| 1636 | /// | ||
| 1637 | /// Creates a PR event with `c` tag pointing to PR_TEST_COMMIT_HASH_2. | ||
| 1638 | /// The event is NOT sent to the relay. | ||
| 1639 | async fn build_pr_event_2_generated(&self) -> Result<Event> { | ||
| 1640 | use nostr_sdk::prelude::*; | ||
| 1641 | |||
| 1642 | let repo = self.get_cached_dependency(FixtureKind::ValidRepoServed)?; | ||
| 1643 | let repo_id = self.extract_repo_id(&repo)?; | ||
| 1644 | |||
| 1645 | let base_time = Timestamp::now().as_secs(); | ||
| 1646 | let pr_timestamp = Timestamp::from(base_time - 1); | ||
| 1647 | |||
| 1648 | self.client | ||
| 1649 | .event_builder(Kind::GitPullRequest, "Test PR 2 for GRASP validation") | ||
| 1650 | .tag(Tag::custom( | ||
| 1651 | TagKind::custom("a"), | ||
| 1652 | vec![format!( | ||
| 1653 | "30617:{}:{}", | ||
| 1654 | self.client.public_key().to_hex(), | ||
| 1655 | repo_id | ||
| 1656 | )], | ||
| 1657 | )) | ||
| 1658 | .tag(Tag::custom( | ||
| 1659 | TagKind::custom("c"), | ||
| 1660 | vec![PR_TEST_COMMIT_HASH_2.to_string()], | ||
| 1661 | )) | ||
| 1662 | .custom_time(pr_timestamp) | ||
| 1663 | .build(self.client.pr_author_keys()) | ||
| 1664 | .map_err(|e| anyhow::anyhow!("Failed to build PR event 2: {}", e)) | ||
| 1665 | } | ||
| 1666 | |||
| 1667 | /// Build PREvent2Sent fixture | ||
| 1668 | /// | ||
| 1669 | /// Sends the PR event to relay. Event should enter purgatory. | ||
| 1670 | async fn build_pr_event_2_sent(&self) -> Result<Event> { | ||
| 1671 | let pr_event = self.get_cached_dependency(FixtureKind::PREvent2Generated)?; | ||
| 1672 | |||
| 1673 | let (_, in_purgatory) = self | ||
| 1674 | .client | ||
| 1675 | .send_event_and_note_purgatory(pr_event.clone()) | ||
| 1676 | .await?; | ||
| 1677 | |||
| 1678 | if !in_purgatory { | ||
| 1679 | return Err(anyhow::anyhow!( | ||
| 1680 | "PR event 2 was served immediately - purgatory not implemented" | ||
| 1681 | )); | ||
| 1682 | } | ||
| 1683 | |||
| 1684 | Ok(pr_event) | ||
| 1685 | } | ||
| 1686 | |||
| 1687 | /// Build PREvent2GitDataPushed fixture | ||
| 1688 | /// | ||
| 1689 | /// Pushes correct commit to refs/nostr/<pr-event-id> after event was sent. | ||
| 1690 | async fn build_pr_event_2_git_data_pushed(&self) -> Result<Event> { | ||
| 1691 | use nostr_sdk::prelude::*; | ||
| 1692 | |||
| 1693 | let pr_event = self.get_cached_dependency(FixtureKind::PREvent2Sent)?; | ||
| 1694 | let pr_event_id = pr_event.id.to_hex(); | ||
| 1695 | |||
| 1696 | let repo = self.get_cached_dependency(FixtureKind::ValidRepoServed)?; | ||
| 1697 | let repo_id = self.extract_repo_id(&repo)?; | ||
| 1698 | |||
| 1699 | let relay_domain = self.get_relay_domain().await?; | ||
| 1700 | |||
| 1701 | let npub = repo | ||
| 1702 | .pubkey | ||
| 1703 | .to_bech32() | ||
| 1704 | .map_err(|e| anyhow::anyhow!("Failed to convert pubkey: {}", e))?; | ||
| 1705 | |||
| 1706 | let clone_path = clone_repo(&relay_domain, &npub, &repo_id) | ||
| 1707 | .map_err(|e| anyhow::anyhow!("Failed to clone repo: {}", e))?; | ||
| 1708 | |||
| 1709 | let cleanup = |path: &PathBuf| { | ||
| 1710 | let _ = fs::remove_dir_all(path); | ||
| 1711 | }; | ||
| 1712 | |||
| 1713 | // Reset to orphan state and create deterministic root commit | ||
| 1714 | // Step 1: Create orphan branch (removes all history) | ||
| 1715 | let _ = Command::new("git") | ||
| 1716 | .args(["checkout", "--orphan", "pr-branch"]) | ||
| 1717 | .current_dir(&clone_path) | ||
| 1718 | .output(); | ||
| 1719 | |||
| 1720 | // Step 2: Clear staged files (orphan keeps files staged from previous branch) | ||
| 1721 | let _ = Command::new("git") | ||
| 1722 | .args(["rm", "-rf", "--cached", "."]) | ||
| 1723 | .current_dir(&clone_path) | ||
| 1724 | .output(); | ||
| 1725 | |||
| 1726 | // Step 3: Remove all working directory files for clean state (except .git) | ||
| 1727 | for entry in | ||
| 1728 | fs::read_dir(&clone_path).map_err(|e| anyhow::anyhow!("Failed to read dir: {}", e))? | ||
| 1729 | { | ||
| 1730 | if let Ok(entry) = entry { | ||
| 1731 | let path = entry.path(); | ||
| 1732 | if path.file_name() != Some(std::ffi::OsStr::new(".git")) { | ||
| 1733 | let _ = fs::remove_file(&path).or_else(|_| fs::remove_dir_all(&path)); | ||
| 1734 | } | ||
| 1735 | } | ||
| 1736 | } | ||
| 1737 | |||
| 1738 | let commit_hash = match create_deterministic_commit_with_variant( | ||
| 1739 | &clone_path, | ||
| 1740 | CommitVariant::PRTestCommit2, | ||
| 1741 | ) { | ||
| 1742 | Ok(h) => h, | ||
| 1743 | Err(e) => { | ||
| 1744 | cleanup(&clone_path); | ||
| 1745 | return Err(anyhow::anyhow!("Failed to create PR test commit 2: {}", e)); | ||
| 1746 | } | ||
| 1747 | }; | ||
| 1748 | |||
| 1749 | if commit_hash != PR_TEST_COMMIT_HASH_2 { | ||
| 1750 | cleanup(&clone_path); | ||
| 1751 | return Err(anyhow::anyhow!( | ||
| 1752 | "PR test commit 2 hash mismatch: got {}, expected {}", | ||
| 1753 | commit_hash, | ||
| 1754 | PR_TEST_COMMIT_HASH_2 | ||
| 1755 | )); | ||
| 1756 | } | ||
| 1757 | |||
| 1758 | let push_output = Command::new("git") | ||
| 1759 | .args([ | ||
| 1760 | "push", | ||
| 1761 | "origin", | ||
| 1762 | &format!("pr-branch:refs/nostr/{}", pr_event_id), | ||
| 1763 | ]) | ||
| 1764 | .current_dir(&clone_path) | ||
| 1765 | .output() | ||
| 1766 | .map_err(|e| { | ||
| 1767 | cleanup(&clone_path); | ||
| 1768 | anyhow::anyhow!("Failed to execute git push: {}", e) | ||
| 1769 | })?; | ||
| 1770 | |||
| 1771 | cleanup(&clone_path); | ||
| 1772 | |||
| 1773 | if !push_output.status.success() { | ||
| 1774 | let stderr = String::from_utf8_lossy(&push_output.stderr); | ||
| 1775 | return Err(anyhow::anyhow!( | ||
| 1776 | "Push to refs/nostr/{} failed: {}", | ||
| 1777 | pr_event_id, | ||
| 1778 | stderr | ||
| 1779 | )); | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | tokio::time::sleep(std::time::Duration::from_millis(500)).await; | ||
| 1783 | |||
| 1784 | Ok(pr_event) | ||
| 1785 | } | ||
| 1786 | |||
| 1787 | /// Build PREvent2Served fixture | ||
| 1788 | /// | ||
| 1789 | /// Full fixture: event sent, git pushed, event now served. | ||
| 1790 | async fn build_pr_event_2_served(&self) -> Result<Event> { | ||
| 1791 | let pr_event = self.get_cached_dependency(FixtureKind::PREvent2GitDataPushed)?; | ||
| 1792 | |||
| 1793 | if !self.client.is_event_on_relay(pr_event.id).await? { | ||
| 1794 | return Err(anyhow::anyhow!( | ||
| 1795 | "PR event 2 not released from purgatory after git push" | ||
| 1796 | )); | ||
| 1797 | } | ||
| 1798 | |||
| 1799 | Ok(pr_event) | ||
| 1800 | } | ||
| 1801 | |||
| 1564 | /// Get relay domain (host:port) from the connected relay | 1802 | /// Get relay domain (host:port) from the connected relay |
| 1565 | /// | 1803 | /// |
| 1566 | /// Extracts the domain from the relay URL for git HTTP operations. | 1804 | /// Extracts the domain from the relay URL for git HTTP operations. |
| @@ -1867,6 +2105,8 @@ pub enum CommitVariant { | |||
| 1867 | RecursiveMaintainer, | 2105 | RecursiveMaintainer, |
| 1868 | /// PR test commit variant - for PR event tests | 2106 | /// PR test commit variant - for PR event tests |
| 1869 | PRTestCommit, | 2107 | PRTestCommit, |
| 2108 | /// Second PR test commit variant - for second PR event tests | ||
| 2109 | PRTestCommit2, | ||
| 1870 | } | 2110 | } |
| 1871 | 2111 | ||
| 1872 | impl CommitVariant { | 2112 | impl CommitVariant { |
| @@ -1877,6 +2117,7 @@ impl CommitVariant { | |||
| 1877 | CommitVariant::Maintainer => "Maintainer initial commit\n", | 2117 | CommitVariant::Maintainer => "Maintainer initial commit\n", |
| 1878 | CommitVariant::RecursiveMaintainer => "Recursive maintainer initial commit\n", | 2118 | CommitVariant::RecursiveMaintainer => "Recursive maintainer initial commit\n", |
| 1879 | CommitVariant::PRTestCommit => "PR test deterministic commit\n", | 2119 | CommitVariant::PRTestCommit => "PR test deterministic commit\n", |
| 2120 | CommitVariant::PRTestCommit2 => "PR test deterministic commit 2\n", | ||
| 1880 | } | 2121 | } |
| 1881 | } | 2122 | } |
| 1882 | 2123 | ||
| @@ -1887,6 +2128,7 @@ impl CommitVariant { | |||
| 1887 | CommitVariant::Maintainer => "Maintainer initial commit", | 2128 | CommitVariant::Maintainer => "Maintainer initial commit", |
| 1888 | CommitVariant::RecursiveMaintainer => "Recursive maintainer initial commit", | 2129 | CommitVariant::RecursiveMaintainer => "Recursive maintainer initial commit", |
| 1889 | CommitVariant::PRTestCommit => "PR test deterministic commit", | 2130 | CommitVariant::PRTestCommit => "PR test deterministic commit", |
| 2131 | CommitVariant::PRTestCommit2 => "PR test deterministic commit 2", | ||
| 1890 | } | 2132 | } |
| 1891 | } | 2133 | } |
| 1892 | } | 2134 | } |