| new file mode 100644 |
| index 000000000..bab25e343 |
| --- /dev/null |
| +++ b/approved_seeded_copy_test.go |
| @@ -0,0 +1,157 @@ |
| +// approved_seeded_copy_test.go |
| +package main |
| + |
| +import ( |
| + "os" |
| + "path/filepath" |
| + "regexp" |
| + "strconv" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// The copy gate of the seeded-instances series (#260, #261). The deck is |
| +// reviewed BEFORE any code speaks a sentence (the reverse of the moves |
| +// record, where code came first), so the gate has two lists: |
| +// |
| +// - seededCopyIDs: every row the deck holds, pinned both ways; |
| +// - seededWiredIDs: the rows code already speaks. Each plan's wiring task |
| +// appends the ids it wired, and the gate checks each one's text is in |
| +// the file its row names. |
| + |
| +const seededCopyRecordPath = "docs/superpowers/plans/2026-10-08-seeded-instances-copy.md" |
| + |
| +var seededCopyIDPattern = regexp.MustCompile(`^SD-N[0-9]+$`) |
| + |
| +var seededCopyIDs = func() []string { |
| + ids := make([]string, 0, 116) |
| + for n := 1; n <= 116; n++ { |
| + ids = append(ids, "SD-N"+strconv.Itoa(n)) |
| + } |
| + return ids |
| +}() |
| + |
| +// seededWiredIDs grows as the plans wire rows. Plan 1 wires none. |
| +var seededWiredIDs = []string{} |
| + |
| +func mustParseSeededRecord(t *testing.T) []copyRow { |
| + t.Helper() |
| + rows, err := parseSeededCopyRecord(seededCopyRecordPath) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(rows) == 0 { |
| + t.Fatalf("%s has no rows", seededCopyRecordPath) |
| + } |
| + return rows |
| +} |
| + |
| +func TestSeededCopyRecordIsComplete(t *testing.T) { |
| + reportAll(t, checkMovesInventory(mustParseSeededRecord(t), seededCopyIDs)) |
| +} |
| + |
| +func TestSeededCopyHasNoEmDash(t *testing.T) { |
| + reportAll(t, checkMovesNoEmDash(mustParseSeededRecord(t))) |
| +} |
| + |
| +func TestSeededWiredCopyIsApplied(t *testing.T) { |
| + reportAll(t, checkSeededWired(mustParseSeededRecord(t), seededWiredIDs, ".")) |
| +} |
| + |
| +func TestParseSeededCopyTableKeepsPipes(t *testing.T) { |
| + src := "| id | file | text | status |\n|---|---|---|---|\n" + |
| + "| SD-N1 | a.go | one | two | pending |\n" |
| + rows, err := parseCopyTable(src, "x.md", seededCopyIDPattern, "an SD-N<n>") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(rows) != 1 || rows[0].Text != "one | two" { |
| + t.Fatalf("rows = %+v", rows) |
| + } |
| + if _, err := parseCopyTable(strings.Replace(src, "SD-N1", "MV-N1", 1), "x.md", seededCopyIDPattern, "an SD-N<n>"); err == nil || |
| + !strings.Contains(err.Error(), "is not an SD-N<n> id") { |
| + t.Fatalf("a moves id in the seeded deck: err = %v", err) |
| + } |
| +} |
| + |
| +func TestSeededCopyInventoryBitesBothWays(t *testing.T) { |
| + rows := []copyRow{{"SD-N1", "a.go", "x", "pending"}, {"SD-N3", "a.go", "y", "pending"}} |
| + got := checkMovesInventory(rows, []string{"SD-N1", "SD-N2"}) |
| + if len(got) != 2 { |
| + t.Fatalf("problems = %q, want a missing SD-N2 and an extra SD-N3", got) |
| + } |
| +} |
| + |
| +func TestSeededWiredCheckBites(t *testing.T) { |
| + dir := t.TempDir() |
| + writeFile(t, dir, "a.go", "package a\n\nvar s = \"hello there\"\n") |
| + rows := []copyRow{ |
| + {"SD-N1", "a.go", "hello there", "approved"}, |
| + {"SD-N2", "a.go", "goodbye", "approved"}, |
| + {"SD-N3", "a.go", "hello there", "pending"}, |
| + } |
| + if got := checkSeededWired(rows, []string{"SD-N1"}, dir); len(got) != 0 { |
| + t.Errorf("a wired, applied row: problems = %q", got) |
| + } |
| + if got := checkSeededWired(rows, []string{"SD-N2"}, dir); len(got) != 1 || !strings.Contains(got[0], "SD-N2") { |
| + t.Errorf("a wired row whose text is missing: problems = %q", got) |
| + } |
| + if got := checkSeededWired(rows, []string{"SD-N3"}, dir); len(got) != 1 || !strings.Contains(got[0], "pending") { |
| + t.Errorf("a wired pending row: problems = %q", got) |
| + } |
| + if got := checkSeededWired(rows, []string{"SD-N9"}, dir); len(got) != 1 || !strings.Contains(got[0], "no row") { |
| + t.Errorf("a wired id with no row: problems = %q", got) |
| + } |
| +} |
| + |
| +func parseSeededCopyRecord(path string) ([]copyRow, error) { |
| + raw, err := os.ReadFile(path) |
| + if err != nil { |
| + return nil, err |
| + } |
| + return parseCopyTable(string(raw), path, seededCopyIDPattern, "an SD-N<n>") |
| +} |
| + |
| +// checkSeededWired checks every wired id: it has a row, the row is |
| +// approved, and its text is in the file the row names (the moves gate's own |
| +// matcher, so a Go file must carry it as one literal, concatenation or |
| +// format, and any other file raw). |
| +func checkSeededWired(rows []copyRow, wired []string, root string) []string { |
| + byID := map[string]copyRow{} |
| + for _, r := range rows { |
| + byID[r.ID] = r |
| + } |
| + var out []string |
| + var applyRows []copyRow |
| + for _, id := range wired { |
| + r, ok := byID[id] |
| + switch { |
| + case !ok: |
| + out = append(out, id+": wired, but the deck has no row for it") |
| + case r.Status != "approved": |
| + out = append(out, id+": wired, but its row is "+r.Status+": only approved text may reach code") |
| + default: |
| + applyRows = append(applyRows, r) |
| + } |
| + } |
| + return append(out, checkMovesApplied(applyRows, root)...) |
| +} |
| + |
| +// checkSeededApproved fails on any row that is not approved. Plan 1 Task 2 |
| +// adds the test that calls it, once Paul's review has landed. |
| +func checkSeededApproved(rows []copyRow) []string { |
| + var out []string |
| + for _, r := range rows { |
| + if r.Status != "approved" { |
| + out = append(out, r.ID+" is "+r.Status+": the deck is reviewed before any plan wires a string") |
| + } |
| + } |
| + return out |
| +} |
| + |
| +func writeFile(t *testing.T, dir, name, body string) { |
| + t.Helper() |
| + if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil { |
| + t.Fatal(err) |
| + } |
| +} |