|
9 | 9 | "strconv" |
10 | 10 | "strings" |
11 | 11 | "testing" |
| 12 | + "time" |
12 | 13 | ) |
13 | 14 |
|
14 | 15 | func runGit(t *testing.T, repo string, arguments ...string) string { |
@@ -648,6 +649,101 @@ func TestRequiredPRVisualEvidenceBlocksPublicationBeforeMutation(t *testing.T) { |
648 | 649 | } |
649 | 650 | } |
650 | 651 |
|
| 652 | +// savePassVisualManifest records PASS evidence for the fixture feature's |
| 653 | +// approved scenario, bound to the given source commit and product diff. |
| 654 | +func savePassVisualManifest(t *testing.T, repo, feature, sourceCommit, diffHash string) { |
| 655 | + t.Helper() |
| 656 | + pngPath := filepath.Join(t.TempDir(), "warning.png") |
| 657 | + writeTestPNG(t, pngPath) |
| 658 | + if _, err := SavePRVisualEvidence(repo, PRVisualEvidenceManifest{ |
| 659 | + Key: feature, Policy: "suggest", Relevance: "relevant", RelevanceSource: "managed-plan", |
| 660 | + Status: "PASS", SourceCommit: sourceCommit, ProductDiffSHA256: diffHash, |
| 661 | + Scenarios: []PRVisualScenario{{ID: "warning", Entry: "/onboarding", State: "picker open", Viewport: "1440x900", Expected: []string{"warning visible"}}}, |
| 662 | + Items: []PRVisualEvidenceItem{{ |
| 663 | + ScenarioID: "warning", Path: pngPath, Viewport: "1440x900", |
| 664 | + CapturedAt: time.Now().UTC().Truncate(time.Second).Format(time.RFC3339), |
| 665 | + Status: "captured", PrivacyStatus: "human-reviewed", |
| 666 | + }}, |
| 667 | + Publication: PRVisualPublication{State: "pending"}, |
| 668 | + }); err != nil { |
| 669 | + t.Fatal(err) |
| 670 | + } |
| 671 | +} |
| 672 | + |
| 673 | +func TestCommittingPreviewNeverInvalidatesPassVisualEvidence(t *testing.T) { |
| 674 | + repo := prTestRepoConfigured(t, func(config *ProjectConfig) { |
| 675 | + config.Workflow.PRVisualEvidence = "suggest" |
| 676 | + }) |
| 677 | + activateManagedFeature(t, repo, "reviewer-ready") |
| 678 | + captureCommit := runGit(t, repo, "rev-parse", "HEAD") |
| 679 | + context, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"}) |
| 680 | + if err != nil { |
| 681 | + t.Fatal(err) |
| 682 | + } |
| 683 | + savePassVisualManifest(t, repo, "reviewer-ready", captureCommit, context.ProductDiffSHA256) |
| 684 | + fresh, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"}) |
| 685 | + if err != nil { |
| 686 | + t.Fatal(err) |
| 687 | + } |
| 688 | + if fresh.PRVisualEvidenceStatus != "PASS" || fresh.PRVisualEvidenceCount != 1 { |
| 689 | + t.Fatalf("recorded PASS evidence was not trusted: %#v", fresh) |
| 690 | + } |
| 691 | + previewPath := writePreview(t, repo, fresh, "Keep evidence trusted across the preview commit", visualEvidenceBody(managedPRBody(), fresh.PRVisualEvidenceStatus)) |
| 692 | + runGit(t, repo, "add", fresh.PreviewPath) |
| 693 | + runGit(t, repo, "commit", "-m", "record exact PR preview") |
| 694 | + committed, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"}) |
| 695 | + if err != nil { |
| 696 | + t.Fatal(err) |
| 697 | + } |
| 698 | + if committed.PRVisualEvidenceStatus != "PASS" { |
| 699 | + t.Fatalf("committing the reviewed pr.md invalidated PASS evidence: %s", committed.PRVisualEvidenceStatus) |
| 700 | + } |
| 701 | + if committed.PRVisualEvidenceFingerprint != fresh.PRVisualEvidenceFingerprint { |
| 702 | + t.Fatalf("preview commit changed the visual evidence fingerprint") |
| 703 | + } |
| 704 | + if _, _, err := CheckPRPreview(repo, previewPath); err != nil { |
| 705 | + t.Fatalf("committed preview no longer checks: %v", err) |
| 706 | + } |
| 707 | + if committed.PRVisualEvidence == nil || committed.PRVisualEvidence.SourceCommit != captureCommit { |
| 708 | + t.Fatalf("evidence provenance lost its capture commit: %#v", committed.PRVisualEvidence) |
| 709 | + } |
| 710 | + if template := PRPreviewTemplate(committed); !strings.Contains(template, captureCommit) { |
| 711 | + t.Fatalf("preview template does not name the capture commit") |
| 712 | + } |
| 713 | +} |
| 714 | + |
| 715 | +func TestProductDiffChangeInvalidatesPassVisualEvidence(t *testing.T) { |
| 716 | + repo := prTestRepoConfigured(t, func(config *ProjectConfig) { |
| 717 | + config.Workflow.PRVisualEvidence = "suggest" |
| 718 | + }) |
| 719 | + activateManagedFeature(t, repo, "reviewer-ready") |
| 720 | + context, err := PreparePRContext(PRContextOptions{Repo: repo, Feature: "reviewer-ready"}) |
| 721 | + if err != nil { |
| 722 | + t.Fatal(err) |
| 723 | + } |
| 724 | + savePassVisualManifest(t, repo, "reviewer-ready", runGit(t, repo, "rev-parse", "HEAD"), context.ProductDiffSHA256) |
| 725 | + config, _, err := LoadConfig(filepath.Join(repo, ".product-loop", "project.json")) |
| 726 | + if err != nil { |
| 727 | + t.Fatal(err) |
| 728 | + } |
| 729 | + changedDiff := strings.Repeat("c", 64) |
| 730 | + _, status, _, _, _, _, _, err := resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", context.HeadBranch, changedDiff) |
| 731 | + if err != nil { |
| 732 | + t.Fatal(err) |
| 733 | + } |
| 734 | + if status != "NOT_VERIFIED" { |
| 735 | + t.Fatalf("product change did not stale the evidence: %s", status) |
| 736 | + } |
| 737 | + config.Workflow.PRVisualEvidence = "require" |
| 738 | + _, status, _, _, _, _, _, err = resolvePRVisualEvidence(repo, config, "managed", "reviewer-ready", context.HeadBranch, changedDiff) |
| 739 | + if err != nil { |
| 740 | + t.Fatal(err) |
| 741 | + } |
| 742 | + if status != "BLOCKED" { |
| 743 | + t.Fatalf("require did not coerce stale evidence to BLOCKED: %s", status) |
| 744 | + } |
| 745 | +} |
| 746 | + |
651 | 747 | func TestPublishPRRequiresExactConfirmationAndUsesBodyWithoutFrontmatter(t *testing.T) { |
652 | 748 | if runtime.GOOS == "windows" { |
653 | 749 | t.Skip("fake gh fixture uses a POSIX shell; publication behavior is covered by cross-platform pure-Go checks") |
|
0 commit comments