016039d1a3
---ci--- project: orca phase: 3 milestone: v0.8 status: execute ---/ci---
171 lines
5.2 KiB
Go
171 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Tests run with cwd = package dir (cmd/verify-reqs), so `testdata/...`
|
|
// paths resolve relative to the package. The real-repo tests use a
|
|
// repoRoot helper to locate `.ciagent/...`.
|
|
|
|
func repoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("getwd: %v", err)
|
|
}
|
|
return filepath.Join(wd, "..", "..")
|
|
}
|
|
|
|
// case (1): clean pair → no drift.
|
|
func TestVerify_clean(t *testing.T) {
|
|
diff, count, err := verify(
|
|
filepath.Join("testdata", "roadmap_clean.md"),
|
|
filepath.Join("testdata", "requirements_clean.md"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(diff) != 0 {
|
|
t.Fatalf("expected no drift, got:\n%s", strings.Join(diff, "\n"))
|
|
}
|
|
if count != 5 {
|
|
t.Fatalf("expected 5 consistent rows, got %d", count)
|
|
}
|
|
}
|
|
|
|
// case (2)+(3): drift pair → all drifts reported, both directions.
|
|
func TestVerify_drift(t *testing.T) {
|
|
diff, _, err := verify(
|
|
filepath.Join("testdata", "roadmap_drift.md"),
|
|
filepath.Join("testdata", "requirements_drift.md"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(diff) != 4 {
|
|
t.Fatalf("expected 4 drifts (REQ-002 forward, REQ-003 reverse, REQ-004 forward, REQ-005 forward), got %d:\n%s",
|
|
len(diff), strings.Join(diff, "\n"))
|
|
}
|
|
joined := strings.Join(diff, "\n")
|
|
for _, want := range []string{"REQ-002", "REQ-003", "REQ-004", "REQ-005"} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("diff missing %s:\n%s", want, joined)
|
|
}
|
|
}
|
|
if !strings.Contains(joined, "direction=reverse") {
|
|
t.Errorf("expected a reverse-direction drift, got:\n%s", joined)
|
|
}
|
|
if !strings.Contains(joined, "direction=forward") {
|
|
t.Errorf("expected a forward-direction drift, got:\n%s", joined)
|
|
}
|
|
}
|
|
|
|
// case (4): missing args → defaults resolve to the repo's .ciagent/ files.
|
|
// Runs the program as a subprocess from the repo root.
|
|
func TestVerify_defaultArgs(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("subprocess test skipped in -short mode")
|
|
}
|
|
root := repoRoot(t)
|
|
cmd := exec.Command("go", "run", "./cmd/verify-reqs")
|
|
cmd.Dir = root
|
|
out := &strings.Builder{}
|
|
cmd.Stdout = out
|
|
cmd.Stderr = out
|
|
if err := cmd.Run(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
t.Fatalf("verify-reqs on real repo exited %d (should be 0): %s",
|
|
exitErr.ExitCode(), out.String())
|
|
}
|
|
t.Fatalf("go run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// case (5): malformed markdown (no REQ rows) → clear error, not silent pass.
|
|
func TestVerify_malformed(t *testing.T) {
|
|
_, _, err := verify(
|
|
filepath.Join("testdata", "roadmap_clean.md"),
|
|
filepath.Join("testdata", "requirements_malformed.md"),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected error on malformed (no REQ rows) requirements, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "no REQ rows") {
|
|
t.Errorf("expected 'no REQ rows' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// case (6): missing file → clear error, not silent pass.
|
|
func TestVerify_missingFile(t *testing.T) {
|
|
_, _, err := verify(
|
|
filepath.Join("testdata", "roadmap_clean.md"),
|
|
filepath.Join("testdata", "does_not_exist.md"),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected error on missing file, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "does_not_exist.md") {
|
|
t.Errorf("expected error to mention the missing file, got: %v", err)
|
|
}
|
|
}
|
|
|
|
// GRILL #4 golden test: the v0.2-style
|
|
// `**COMPLETE (merged to main via v0.3)**` header MUST be recognized as a
|
|
// complete milestone by the substring-tolerant regex. The drift fixture's
|
|
// roadmap carries exactly this header on its v0.2 line, and the drift
|
|
// fixture's REQ-002 (v0.2 P1, Pending) would be silently skipped if the
|
|
// regex regressed to the exact `\*\*COMPLETE\*\*` form. TestVerify_drift
|
|
// already asserts REQ-002 is flagged forward-drift; this test makes the
|
|
// intent explicit and guards against a regex regression.
|
|
func TestVerify_v02SubstringTolerantHeader(t *testing.T) {
|
|
diff, _, err := verify(
|
|
filepath.Join("testdata", "roadmap_drift.md"),
|
|
filepath.Join("testdata", "requirements_drift.md"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// REQ-002 references milestone v0.2, whose header uses the
|
|
// `**COMPLETE (merged to main via v0.3)**` variant. If the regex
|
|
// regressed, v0.2 would not be marked complete and REQ-002 (Pending)
|
|
// would NOT be flagged as forward drift.
|
|
found := false
|
|
for _, d := range diff {
|
|
if strings.Contains(d, "REQ-002") && strings.Contains(d, "direction=forward") {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("REQ-002 forward drift not reported — substring-tolerant regex may have regressed; diff:\n%s",
|
|
strings.Join(diff, "\n"))
|
|
}
|
|
}
|
|
|
|
// Verify the actual repo passes (regression guard for the real docs).
|
|
func TestVerify_realRepo(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("real-repo test skipped in -short mode")
|
|
}
|
|
root := repoRoot(t)
|
|
diff, count, err := verify(
|
|
filepath.Join(root, ".ciagent", "ROADMAP.md"),
|
|
filepath.Join(root, ".ciagent", "REQUIREMENTS.md"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("verify on real repo errored: %v", err)
|
|
}
|
|
if len(diff) != 0 {
|
|
t.Fatalf("real repo has requirements drift (should be clean after SPECIFY):\n%s",
|
|
strings.Join(diff, "\n"))
|
|
}
|
|
if count <= 0 {
|
|
t.Fatalf("real repo reported %d consistent rows (expected > 0)", count)
|
|
}
|
|
}
|