upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/fixtures.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-13 09:24:51 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-13 09:24:51 +0000
commitf4e8e1089ae6e8e78c3576246d9747bb585fdc18 (patch)
tree37cd429b6c5468996178f1d9bbaafe6f789b3605 /grasp-audit/src/fixtures.rs
parentfaac6027deaf5f1e121c05df2d8a6336fd6eaf8d (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.rs242
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)
104pub const PR_TEST_COMMIT_HASH: &str = "5a51b30e4615b572dcd5b9e487861b58605a5c21"; 104pub 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)
115pub 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
1872impl CommitVariant { 2112impl 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}