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 | |
| 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')
| -rw-r--r-- | grasp-audit/src/fixtures.rs | 242 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/purgatory.rs | 144 |
2 files changed, 336 insertions, 50 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 | } |
diff --git a/grasp-audit/src/specs/grasp01/purgatory.rs b/grasp-audit/src/specs/grasp01/purgatory.rs index 60b6096..27ab97b 100644 --- a/grasp-audit/src/specs/grasp01/purgatory.rs +++ b/grasp-audit/src/specs/grasp01/purgatory.rs | |||
| @@ -49,9 +49,11 @@ impl PurgatoryTests { | |||
| 49 | results.add(Self::test_state_event_not_served_before_git_data(client).await); | 49 | results.add(Self::test_state_event_not_served_before_git_data(client).await); |
| 50 | results.add(Self::test_state_event_served_after_git_push(client).await); | 50 | results.add(Self::test_state_event_served_after_git_push(client).await); |
| 51 | 51 | ||
| 52 | // PR purgatory tests (feature not yet implemented) | 52 | // PR purgatory tests |
| 53 | results.add(Self::test_pr_event_not_served_before_git_data(client).await); | 53 | results.add(Self::test_pr_event_before_git_data_accepted_into_purgatory(client).await); |
| 54 | results.add(Self::test_pr_event_served_after_correct_push(client).await); | 54 | results.add(Self::test_pr_event_remains_in_purgatory_until_git_data(client).await); |
| 55 | results.add(Self::test_pr_event_git_push_accepted(client).await); | ||
| 56 | results.add(Self::test_pr_event_served_after_git_push(client).await); | ||
| 55 | 57 | ||
| 56 | results | 58 | results |
| 57 | } | 59 | } |
| @@ -515,37 +517,37 @@ impl PurgatoryTests { | |||
| 515 | /// 1. Send PR event for a repo | 517 | /// 1. Send PR event for a repo |
| 516 | /// 2. PR event is NOT queryable (in purgatory) | 518 | /// 2. PR event is NOT queryable (in purgatory) |
| 517 | /// 3. No git data exists at refs/nostr/<pr-event-id> | 519 | /// 3. No git data exists at refs/nostr/<pr-event-id> |
| 518 | pub async fn test_pr_event_not_served_before_git_data(client: &AuditClient) -> TestResult { | 520 | pub async fn test_pr_event_before_git_data_accepted_into_purgatory( |
| 521 | client: &AuditClient, | ||
| 522 | ) -> TestResult { | ||
| 519 | TestResult::new( | 523 | TestResult::new( |
| 520 | "pr_event_not_served_before_git_data", | 524 | "pr_event_before_git_data_accepted_into_purgatory", |
| 521 | SpecRef::PurgatoryAcceptUntilGitData, | 525 | SpecRef::PurgatoryAcceptUntilGitData, |
| 522 | "PR events SHOULD be accepted but not served until git data arrives", | 526 | "PR event SHOULD be accepted into purgatory when git data doesn't exist", |
| 523 | ) | 527 | ) |
| 524 | .run(|| async { | 528 | .run(|| async { |
| 525 | let ctx = TestContext::new(client); | 529 | let ctx = TestContext::new(client); |
| 526 | 530 | ||
| 527 | // Get a repo announcement | ||
| 528 | let _repo = ctx | ||
| 529 | .get_fixture(FixtureKind::ValidRepoSent) | ||
| 530 | .await | ||
| 531 | .map_err(|e| format!("Failed to create repo: {}", e))?; | ||
| 532 | |||
| 533 | // Build PR event (not sent yet) | ||
| 534 | let pr_event = ctx | 531 | let pr_event = ctx |
| 535 | .build_fixture_only(FixtureKind::PREvent) | 532 | .get_fixture(FixtureKind::PREvent2Sent) |
| 536 | .await | 533 | .await |
| 537 | .map_err(|e| format!("Failed to build PR event: {}", e))?; | 534 | .map_err(|e| format!("Failed to send PR event: {}", e))?; |
| 538 | 535 | ||
| 539 | // Send PR event | 536 | let filter = Filter::new() |
| 540 | let (_, in_purgatory) = client | 537 | .kind(Kind::GitPullRequest) |
| 541 | .send_event_and_note_purgatory(pr_event.clone()) | 538 | .author(client.pr_author_keys().public_key()) |
| 539 | .id(pr_event.id); | ||
| 540 | |||
| 541 | tokio::time::sleep(Duration::from_millis(300)).await; | ||
| 542 | |||
| 543 | let events = client | ||
| 544 | .query(filter) | ||
| 542 | .await | 545 | .await |
| 543 | .map_err(|e| format!("Failed to send PR event: {}", e))?; | 546 | .map_err(|e| format!("Failed to query PR events: {}", e))?; |
| 544 | 547 | ||
| 545 | if !in_purgatory { | 548 | if !events.is_empty() { |
| 546 | return Err(format!( | 549 | return Err(format!( |
| 547 | "PR event was served immediately - purgatory not implemented. \ | 550 | "PR event was served immediately - should be in purgatory. Event ID: {}", |
| 548 | Event ID: {} should NOT be queryable until git data arrives", | ||
| 549 | pr_event.id | 551 | pr_event.id |
| 550 | )); | 552 | )); |
| 551 | } | 553 | } |
| @@ -555,46 +557,89 @@ impl PurgatoryTests { | |||
| 555 | .await | 557 | .await |
| 556 | } | 558 | } |
| 557 | 559 | ||
| 558 | /// Test: PR event served after correct push | 560 | /// Test: PR event remains in purgatory until git data arrives |
| 559 | /// | ||
| 560 | /// Spec: GRASP-01 Line 22 | ||
| 561 | /// "...kept in purgatory (not served) until the related git data arrives" | ||
| 562 | /// | 561 | /// |
| 563 | /// This test verifies: | 562 | /// Verifies the event stays in purgatory until matching git data is pushed. |
| 564 | /// 1. Send PR event (enters purgatory) | 563 | pub async fn test_pr_event_remains_in_purgatory_until_git_data( |
| 565 | /// 2. Push git data to refs/nostr/<pr-event-id> with correct commit | 564 | client: &AuditClient, |
| 566 | /// 3. PR event is now served | 565 | ) -> TestResult { |
| 567 | pub async fn test_pr_event_served_after_correct_push(client: &AuditClient) -> TestResult { | ||
| 568 | TestResult::new( | 566 | TestResult::new( |
| 569 | "pr_event_served_after_correct_push", | 567 | "pr_event_remains_in_purgatory_until_git_data", |
| 570 | SpecRef::PurgatoryAcceptUntilGitData, | 568 | SpecRef::PurgatoryAcceptUntilGitData, |
| 571 | "PR events SHOULD be served after matching git data arrives", | 569 | "PR event SHOULD remain in purgatory until git data arrives", |
| 572 | ) | 570 | ) |
| 573 | .run(|| async { | 571 | .run(|| async { |
| 574 | let ctx = TestContext::new(client); | 572 | let ctx = TestContext::new(client); |
| 575 | 573 | ||
| 576 | // Get a repo with git data | 574 | let pr_event = ctx |
| 577 | let _existing_state = ctx | 575 | .get_fixture(FixtureKind::PREvent2Sent) |
| 578 | .get_fixture(FixtureKind::OwnerStateDataPushed) | ||
| 579 | .await | 576 | .await |
| 580 | .map_err(|e| format!("Failed to get existing repo: {}", e))?; | 577 | .map_err(|e| format!("Failed to get PR event: {}", e))?; |
| 581 | 578 | ||
| 582 | // Build PR event | 579 | tokio::time::sleep(Duration::from_millis(500)).await; |
| 583 | let pr_event = ctx | 580 | |
| 584 | .build_fixture_only(FixtureKind::PREvent) | 581 | let filter = Filter::new() |
| 582 | .kind(Kind::GitPullRequest) | ||
| 583 | .author(client.pr_author_keys().public_key()) | ||
| 584 | .id(pr_event.id); | ||
| 585 | |||
| 586 | let events = client | ||
| 587 | .query(filter) | ||
| 585 | .await | 588 | .await |
| 586 | .map_err(|e| format!("Failed to build PR event: {}", e))?; | 589 | .map_err(|e| format!("Failed to query PR events: {}", e))?; |
| 590 | |||
| 591 | if !events.is_empty() { | ||
| 592 | return Err(format!( | ||
| 593 | "PR event was served without git data - purgatory not working. Event ID: {}", | ||
| 594 | pr_event.id | ||
| 595 | )); | ||
| 596 | } | ||
| 597 | |||
| 598 | Ok(()) | ||
| 599 | }) | ||
| 600 | .await | ||
| 601 | } | ||
| 587 | 602 | ||
| 588 | // Send PR event (should enter purgatory) | 603 | /// Test: Git push accepted for PR event in purgatory |
| 589 | let (_, _in_purgatory) = client | 604 | /// |
| 590 | .send_event_and_note_purgatory(pr_event.clone()) | 605 | /// Verifies that pushing the correct commit to refs/nostr/<pr-event-id> |
| 606 | /// is accepted. | ||
| 607 | pub async fn test_pr_event_git_push_accepted(client: &AuditClient) -> TestResult { | ||
| 608 | TestResult::new( | ||
| 609 | "pr_event_git_push_accepted", | ||
| 610 | SpecRef::PurgatoryAcceptUntilGitData, | ||
| 611 | "Git push for PR event SHOULD be accepted", | ||
| 612 | ) | ||
| 613 | .run(|| async { | ||
| 614 | let ctx = TestContext::new(client); | ||
| 615 | |||
| 616 | let _pr_event = ctx | ||
| 617 | .get_fixture(FixtureKind::PREvent2GitDataPushed) | ||
| 591 | .await | 618 | .await |
| 592 | .map_err(|e| format!("Failed to send PR event: {}", e))?; | 619 | .map_err(|e| format!("Failed to push git data for PR event: {}", e))?; |
| 593 | 620 | ||
| 594 | // TODO: Push git data to refs/nostr/<pr-event-id> | 621 | Ok(()) |
| 595 | // This requires git operations similar to OwnerStateDataPushed | 622 | }) |
| 623 | .await | ||
| 624 | } | ||
| 625 | |||
| 626 | /// Test: PR event served after git push | ||
| 627 | /// | ||
| 628 | /// Verifies the full purgatory release mechanism. | ||
| 629 | pub async fn test_pr_event_served_after_git_push(client: &AuditClient) -> TestResult { | ||
| 630 | TestResult::new( | ||
| 631 | "pr_event_served_after_git_push", | ||
| 632 | SpecRef::PurgatoryAcceptUntilGitData, | ||
| 633 | "PR event SHOULD be served after matching git data arrives", | ||
| 634 | ) | ||
| 635 | .run(|| async { | ||
| 636 | let ctx = TestContext::new(client); | ||
| 637 | |||
| 638 | let pr_event = ctx | ||
| 639 | .get_fixture(FixtureKind::PREvent2Served) | ||
| 640 | .await | ||
| 641 | .map_err(|e| format!("Failed to complete purgatory release: {}", e))?; | ||
| 596 | 642 | ||
| 597 | // For now, verify the PR event exists | ||
| 598 | let filter = Filter::new() | 643 | let filter = Filter::new() |
| 599 | .kind(Kind::GitPullRequest) | 644 | .kind(Kind::GitPullRequest) |
| 600 | .author(client.pr_author_keys().public_key()) | 645 | .author(client.pr_author_keys().public_key()) |
| @@ -607,8 +652,7 @@ impl PurgatoryTests { | |||
| 607 | 652 | ||
| 608 | if events.is_empty() { | 653 | if events.is_empty() { |
| 609 | return Err(format!( | 654 | return Err(format!( |
| 610 | "PR event not served after git push - purgatory release not implemented. \ | 655 | "PR event not served after git push. Event ID: {} should be queryable", |
| 611 | Event ID: {} should be queryable after git data arrives", | ||
| 612 | pr_event.id | 656 | pr_event.id |
| 613 | )); | 657 | )); |
| 614 | } | 658 | } |