diff options
Diffstat (limited to 'grasp-audit/src/specs/grasp01/push_authorization.rs')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/push_authorization.rs | 194 |
1 files changed, 14 insertions, 180 deletions
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs index cd422d2..1e28f8c 100644 --- a/grasp-audit/src/specs/grasp01/push_authorization.rs +++ b/grasp-audit/src/specs/grasp01/push_authorization.rs | |||
| @@ -587,197 +587,31 @@ impl PushAuthorizationTests { | |||
| 587 | /// GRASP-01: "MUST accept pushes via this service that match the latest | 587 | /// GRASP-01: "MUST accept pushes via this service that match the latest |
| 588 | /// repo state announcement on the relay" | 588 | /// repo state announcement on the relay" |
| 589 | /// | 589 | /// |
| 590 | /// ## Fixture-First Pattern | 590 | /// This test uses the OwnerStateDataPushed fixture which handles all 4 stages: |
| 591 | /// 1. **Generated**: Creates RepoState (repo announcement + state event) | ||
| 592 | /// 2. **Sent**: Sends events to relay | ||
| 593 | /// 3. **Verified**: Confirms events accepted by relay | ||
| 594 | /// 4. **DataPushed**: Clones repo, creates deterministic commit, pushes to relay | ||
| 591 | /// | 595 | /// |
| 592 | /// 1. **Generate**: Create TestContext and get RepoState fixture | 596 | /// The test wraps the fixture result in pass/fail using the error message. |
| 593 | /// (repo announcement + state event pointing to deterministic commit) | 597 | #[allow(unused_variables)] // relay_domain is now handled by fixture |
| 594 | /// 2. **Send**: Clone repo, create deterministic commit locally, push to relay | ||
| 595 | /// 3. **Verify**: Push should succeed because state event authorizes this commit | ||
| 596 | pub async fn test_push_authorized_by_owner_state( | 598 | pub async fn test_push_authorized_by_owner_state( |
| 597 | client: &AuditClient, | 599 | client: &AuditClient, |
| 598 | relay_domain: &str, | 600 | relay_domain: &str, |
| 599 | ) -> TestResult { | 601 | ) -> TestResult { |
| 600 | use std::process::Command; | ||
| 601 | |||
| 602 | let test_name = "test_push_authorized_by_owner_state"; | 602 | let test_name = "test_push_authorized_by_owner_state"; |
| 603 | |||
| 604 | // ============================================================ | ||
| 605 | // Step 1: GENERATE - Create TestContext and get RepoState fixture | ||
| 606 | // ============================================================ | ||
| 607 | let ctx = TestContext::new(client); | 603 | let ctx = TestContext::new(client); |
| 608 | 604 | ||
| 609 | let state_event = match ctx.get_fixture(FixtureKind::RepoState).await { | 605 | // The OwnerStateDataPushed fixture handles all stages: |
| 610 | Ok(e) => e, | 606 | // Generate → Send → Verify → DataPush |
| 611 | Err(e) => { | 607 | match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await { |
| 612 | return TestResult::new( | 608 | Ok(_state_event) => { |
| 613 | test_name, | ||
| 614 | "GRASP-01", | ||
| 615 | "Push authorized with matching state", | ||
| 616 | ) | ||
| 617 | .fail(format!("Failed to create RepoState fixture: {}", e)); | ||
| 618 | } | ||
| 619 | }; | ||
| 620 | |||
| 621 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 622 | |||
| 623 | // Extract repo_id and npub from state event | ||
| 624 | let repo_id = match state_event | ||
| 625 | .tags | ||
| 626 | .iter() | ||
| 627 | .find(|t| t.kind() == TagKind::d()) | ||
| 628 | .and_then(|t| t.content()) | ||
| 629 | { | ||
| 630 | Some(id) => id.to_string(), | ||
| 631 | None => { | ||
| 632 | return TestResult::new( | ||
| 633 | test_name, | ||
| 634 | "GRASP-01", | ||
| 635 | "Push authorized with matching state", | ||
| 636 | ) | ||
| 637 | .fail("Missing repo_id in state event"); | ||
| 638 | } | ||
| 639 | }; | ||
| 640 | |||
| 641 | let npub = match state_event.pubkey.to_bech32() { | ||
| 642 | Ok(n) => n, | ||
| 643 | Err(e) => { | ||
| 644 | return TestResult::new( | ||
| 645 | test_name, | ||
| 646 | "GRASP-01", | ||
| 647 | "Push authorized with matching state", | ||
| 648 | ) | ||
| 649 | .fail(format!("Failed to convert pubkey to bech32: {}", e)); | ||
| 650 | } | ||
| 651 | }; | ||
| 652 | |||
| 653 | // ============================================================ | ||
| 654 | // Step 2: SEND - Clone repo, create deterministic commit, push | ||
| 655 | // ============================================================ | ||
| 656 | let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { | ||
| 657 | Ok(p) => p, | ||
| 658 | Err(e) => { | ||
| 659 | return TestResult::new( | ||
| 660 | test_name, | ||
| 661 | "GRASP-01", | ||
| 662 | "Push authorized with matching state", | ||
| 663 | ) | ||
| 664 | .fail(format!("Failed to clone repo: {}", e)); | ||
| 665 | } | ||
| 666 | }; | ||
| 667 | |||
| 668 | // Cleanup helper | ||
| 669 | let cleanup = || { | ||
| 670 | let _ = fs::remove_dir_all(&clone_path); | ||
| 671 | }; | ||
| 672 | |||
| 673 | // Create deterministic commit locally | ||
| 674 | let commit_hash = match create_deterministic_commit(&clone_path, "Initial commit") { | ||
| 675 | Ok(h) => h, | ||
| 676 | Err(e) => { | ||
| 677 | cleanup(); | ||
| 678 | return TestResult::new( | ||
| 679 | test_name, | ||
| 680 | "GRASP-01", | ||
| 681 | "Push authorized with matching state", | ||
| 682 | ) | ||
| 683 | .fail(format!("Failed to create deterministic commit: {}", e)); | ||
| 684 | } | ||
| 685 | }; | ||
| 686 | |||
| 687 | // Verify commit hash matches expected | ||
| 688 | if commit_hash != DETERMINISTIC_COMMIT_HASH { | ||
| 689 | cleanup(); | ||
| 690 | return TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") | ||
| 691 | .fail(format!( | ||
| 692 | "Commit hash mismatch: got {}, expected {}", | ||
| 693 | commit_hash, DETERMINISTIC_COMMIT_HASH | ||
| 694 | )); | ||
| 695 | } | ||
| 696 | |||
| 697 | // Create main branch pointing to our deterministic commit | ||
| 698 | let branch_output = Command::new("git") | ||
| 699 | .args(["branch", "main"]) | ||
| 700 | .current_dir(&clone_path) | ||
| 701 | .output(); | ||
| 702 | |||
| 703 | match branch_output { | ||
| 704 | Err(e) => { | ||
| 705 | cleanup(); | ||
| 706 | return TestResult::new( | ||
| 707 | test_name, | ||
| 708 | "GRASP-01", | ||
| 709 | "Push authorized with matching state", | ||
| 710 | ) | ||
| 711 | .fail(format!("Failed to create main branch: {}", e)); | ||
| 712 | } | ||
| 713 | Ok(output) if !output.status.success() => { | ||
| 714 | cleanup(); | ||
| 715 | return TestResult::new( | ||
| 716 | test_name, | ||
| 717 | "GRASP-01", | ||
| 718 | "Push authorized with matching state", | ||
| 719 | ) | ||
| 720 | .fail(format!( | ||
| 721 | "Failed to create main branch: {}", | ||
| 722 | String::from_utf8_lossy(&output.stderr) | ||
| 723 | )); | ||
| 724 | } | ||
| 725 | _ => {} | ||
| 726 | } | ||
| 727 | |||
| 728 | // Checkout main branch | ||
| 729 | let checkout_output = Command::new("git") | ||
| 730 | .args(["checkout", "main"]) | ||
| 731 | .current_dir(&clone_path) | ||
| 732 | .output(); | ||
| 733 | |||
| 734 | match checkout_output { | ||
| 735 | Err(e) => { | ||
| 736 | cleanup(); | ||
| 737 | return TestResult::new( | ||
| 738 | test_name, | ||
| 739 | "GRASP-01", | ||
| 740 | "Push authorized with matching state", | ||
| 741 | ) | ||
| 742 | .fail(format!("Failed to checkout main branch: {}", e)); | ||
| 743 | } | ||
| 744 | Ok(output) if !output.status.success() => { | ||
| 745 | cleanup(); | ||
| 746 | return TestResult::new( | ||
| 747 | test_name, | ||
| 748 | "GRASP-01", | ||
| 749 | "Push authorized with matching state", | ||
| 750 | ) | ||
| 751 | .fail(format!( | ||
| 752 | "Failed to checkout main branch: {}", | ||
| 753 | String::from_utf8_lossy(&output.stderr) | ||
| 754 | )); | ||
| 755 | } | ||
| 756 | _ => {} | ||
| 757 | } | ||
| 758 | |||
| 759 | // ============================================================ | ||
| 760 | // Step 3: VERIFY - Push should succeed because state event | ||
| 761 | // authorizes this commit | ||
| 762 | // ============================================================ | ||
| 763 | let push_result = try_push(&clone_path); | ||
| 764 | cleanup(); | ||
| 765 | |||
| 766 | match push_result { | ||
| 767 | Ok(true) => { | ||
| 768 | TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass() | 609 | TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").pass() |
| 769 | } | 610 | } |
| 770 | Ok(false) => { | 611 | Err(e) => { |
| 771 | TestResult::new(test_name, "GRASP-01", "Push authorized with matching state").fail( | 612 | TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") |
| 772 | format!( | 613 | .fail(format!("{}", e)) |
| 773 | "Push was rejected but should have been accepted. \ | ||
| 774 | The state event points to commit {} which matches the pushed commit.", | ||
| 775 | DETERMINISTIC_COMMIT_HASH | ||
| 776 | ), | ||
| 777 | ) | ||
| 778 | } | 614 | } |
| 779 | Err(e) => TestResult::new(test_name, "GRASP-01", "Push authorized with matching state") | ||
| 780 | .fail(format!("Push error: {}", e)), | ||
| 781 | } | 615 | } |
| 782 | } | 616 | } |
| 783 | 617 | ||