| 1 | package provision |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/carlosframework/platform/internal/release" |
| 13 | ) |
| 14 | |
| 15 | // TestOfflineHelperRefusalsMatchTheCopyRecord pins the five transition |
| 16 | // refusals that live in fmt.Sprintf helpers (MV-O36, MV-O38, MV-O40, MV-O41, |
| 17 | // MV-O43) to the approved text in the copy record, byte for byte, with each |
| 18 | // placeholder filled with the value the transition supplies. Each refusal is |
| 19 | // driven through its real transition and still wraps ErrMoveConflict. |
| 20 | // (census-ruling-moves-offline, condition 2.) |
| 21 | func TestOfflineHelperRefusalsMatchTheCopyRecord(t *testing.T) { |
| 22 | approved := offlineApprovedCopy(t) |
| 23 | now := time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC) |
| 24 | steps := offlineSteps(now) |
| 25 | ctx := context.Background() |
| 26 | |
| 27 | for _, c := range []struct { |
| 28 | id string |
| 29 | walked int |
| 30 | mut func(*Record) error |
| 31 | fills []string // in the order the placeholders appear |
| 32 | }{ |
| 33 | {"MV-O36", 1, OfflineFlip(offID), []string{OfflineStopping, OfflineSealed}}, |
| 34 | {"MV-O38", 2, OfflineSealedBy(offID, "box-b", MoveSource{}), []string{offSealer}}, |
| 35 | {"MV-O40", 6, OfflineAbort(offID), []string{OfflineOpen}}, |
| 36 | {"MV-O41", 1, OfflineFinish(offID, now), []string{OfflineStopping}}, |
| 37 | {"MV-O43", 0, OfflineStart(offID, "us", "uk", "ops@example.com", now), []string{"home", "us"}}, |
| 38 | } { |
| 39 | t.Run(c.id, func(t *testing.T) { |
| 40 | text, ok := approved[c.id] |
| 41 | if !ok { |
| 42 | t.Fatalf("%s is not an approved row of the copy record", c.id) |
| 43 | } |
| 44 | want := text |
| 45 | for _, v := range c.fills { |
| 46 | i := strings.Index(want, "<") |
| 47 | j := strings.Index(want, ">") |
| 48 | if i < 0 || j < i { |
| 49 | t.Fatalf("%s: more fills than placeholders in %q", c.id, text) |
| 50 | } |
| 51 | want = want[:i] + v + want[j+1:] |
| 52 | } |
| 53 | if strings.ContainsAny(want, "<>") { |
| 54 | t.Fatalf("%s: an unfilled placeholder in %q", c.id, want) |
| 55 | } |
| 56 | |
| 57 | s := release.NewDirStore(t.TempDir()) |
| 58 | rec := offlineRecord(t, s) |
| 59 | offlineWalk(t, s, rec, steps[:c.walked]) |
| 60 | _, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, c.mut) |
| 61 | if !errors.Is(err, ErrMoveConflict) { |
| 62 | t.Fatalf("err %v, want ErrMoveConflict", err) |
| 63 | } |
| 64 | if got, full := err.Error(), ErrMoveConflict.Error()+": "+want; got != full { |
| 65 | t.Fatalf("refusal\n got %q\nwant %q", got, full) |
| 66 | } |
| 67 | }) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | // offlineApprovedCopy reads the MV-O rows of the copy record whose status is |
| 72 | // approved, keyed by id. |
| 73 | func offlineApprovedCopy(t *testing.T) map[string]string { |
| 74 | t.Helper() |
| 75 | raw, err := os.ReadFile(filepath.Join(censusRoot(t), "docs/superpowers/plans/2026-10-06-moves-phase1-copy.md")) |
| 76 | if err != nil { |
| 77 | t.Fatal(err) |
| 78 | } |
| 79 | rows := map[string]string{} |
| 80 | for _, line := range strings.Split(string(raw), "\n") { |
| 81 | if !strings.HasPrefix(line, "| MV-O") { |
| 82 | continue |
| 83 | } |
| 84 | cells := strings.Split(strings.Trim(line, "|"), " | ") |
| 85 | if len(cells) != 4 || strings.TrimSpace(cells[3]) != "approved" { |
| 86 | continue |
| 87 | } |
| 88 | rows[strings.TrimSpace(cells[0])] = cells[2] |
| 89 | } |
| 90 | return rows |
| 91 | } |
| 92 | |