carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3// claim_invariant_moves_test.go covers what the Phase 1 moves switch
4// (CARLOS_MOVES_PHASE1, Config.MovesEnabled) adds to the console, on a console
5// built with the switch on:
6//
7// - one route, the operator's resolve door, registered only with the switch
8// (registerMovesPhase1Routes). It is intentionally unavailable: with the
9// switch on its body is not built, so a valid operator request for a real
10// instance is answered the exact sentence below and writes nothing. That is
11// a refused fixture, not a probe: only an exact status and message from
12// claimUnsupported may stand for a route that does not work yet.
13// - one worker, PublishMovesConsole (control/moves-console.json), registered
14// in every console and a no-op with the switch off. Its probe is in
15// claim_invariant_probes_worker_test.go, with the switch turned on.
16
17import (
18 "net/http"
19 "os"
20 "slices"
21 "strings"
22 "testing"
23
24 "github.com/carlosframework/platform/internal/moves"
25)
26
27// claimMoveResolveUnavailableMsg is what the resolve door answers an operator
28// until Plan 4 Task 10 and Plan 6 Task 2 give it its body.
29const claimMoveResolveUnavailableMsg = "nothing to resolve yet"
30
31const claimMoveResolveKey = "route POST /api/cli/apps/{account}/{app}/instances/{host}/move/resolve"
32
33// claimMovesOn builds the console the way cmd/console builds it with the
34// switch on (applyMovesSwitch runs before NewServer).
35func claimMovesOn(c *Config) { c.MovesEnabled = true }
36
37// claimMovesFixtures are the entries that exist only on a moves-on console.
38var claimMovesFixtures = map[string]claimFixture{
39 claimMoveResolveKey: {refused: &claimRefusalFixture{
40 apps: claimRefs("bab/titogo"),
41 setup: func(t *testing.T, cx *claimCase) { cx.movableInstance(t) },
42 do: claimRefusedAPI(http.MethodPost, "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move/resolve", `{}`),
43 status: http.StatusConflict,
44 msg: claimMoveResolveUnavailableMsg,
45 }},
46}
47
48// claimMovesMerged is the fixture table a moves-on console must match exactly.
49func claimMovesMerged() map[string]claimFixture {
50 merged := map[string]claimFixture{}
51 for k, v := range claimFixtures {
52 merged[k] = v
53 }
54 for k, v := range claimMovesFixtures {
55 merged[k] = v
56 }
57 return merged
58}
59
60// TestClaimMovesConsoleRouteCensus: a moves-on console serves exactly what an
61// enabled console serves plus the entries in testdata/claim-routes-moves.txt,
62// and every one has a fixture.
63func TestClaimMovesConsoleRouteCensus(t *testing.T) {
64 base := claimInventoryKeys(newClaimCase(t).srv.claimEntries.snapshot())
65 if slices.Contains(base, claimMoveResolveKey) {
66 t.Fatalf("%s is registered with the moves switch off", claimMoveResolveKey)
67 }
68 cx := newClaimCaseWith(t, claimMovesOn)
69 if !cx.srv.cfg.MovesEnabled || !cx.srv.cfg.HostClaim {
70 t.Fatal("the moves console is not built with both switches on")
71 }
72 b, err := os.ReadFile("testdata/claim-routes-moves.txt")
73 if err != nil {
74 t.Fatal(err)
75 }
76 want := append(slices.Clone(base), strings.Split(strings.TrimSpace(string(b)), "\n")...)
77 slices.Sort(want)
78 entries := cx.srv.claimEntries.snapshot()
79 got := claimInventoryKeys(entries)
80 if !slices.Equal(got, want) {
81 for _, k := range got {
82 if !slices.Contains(want, k) {
83 t.Errorf("new with the moves switch: %s", k)
84 }
85 }
86 for _, k := range want {
87 if !slices.Contains(got, k) {
88 t.Errorf("missing with the moves switch: %s", k)
89 }
90 }
91 t.FailNow()
92 }
93 for _, p := range claimFixtureCensus(entries, claimMovesMerged()) {
94 t.Error(p)
95 }
96 for key, f := range claimMovesFixtures {
97 if f.door == nil && f.refused == nil {
98 t.Errorf("%s: a moves-only entry is a door or an exact refusal; a probe table is the base console's", key)
99 }
100 }
101}
102
103// TestClaimMovesEntriesAreCovered drives every moves-only entry on a moves-on
104// console.
105func TestClaimMovesEntriesAreCovered(t *testing.T) {
106 for key, f := range claimMovesFixtures {
107 t.Run(key, func(t *testing.T) {
108 if f.refused == nil {
109 t.Skip("a door is driven by TestClaimConsoleMutationInvariant")
110 }
111 for _, p := range runClaimRefusal(t, f.refused, claimMovesOn) {
112 t.Error(p)
113 }
114 })
115 }
116}
117
118// With the switch off the worker publishes nothing: the entry is registered in
119// every console, and only the switch makes it write.
120func TestClaimPublishMovesConsoleIsInertWithTheSwitchOff(t *testing.T) {
121 cx := newClaimCase(t)
122 if cx.srv.cfg.MovesEnabled {
123 t.Fatal("the base console has the moves switch on")
124 }
125 m := cx.rec.mark()
126 claimWorkerReply("PublishMovesConsole")(t, cx)
127 cx.quiesce()
128 if cx.bucketHas(moves.ConsoleKey) {
129 t.Fatalf("%s published with the moves switch off", moves.ConsoleKey)
130 }
131 if events := cx.rec.since(m); len(events) != 0 {
132 t.Fatalf("the worker wrote with the moves switch off: %v", events)
133 }
134}
135
136// TestClaimMovesConsoleBindsTheMovePass (moves Plan 5 Task 7): a moves-on
137// claim console binds its move pass as claim Plan 10's Recovery.Moves hook,
138// with the lost target's stamp; it adds no route and no worker (the pass runs
139// inside StartClaimRecoveryPass). A moves-off console binds neither, so the
140// recovery pass skips move claims as main does.
141func TestClaimMovesConsoleBindsTheMovePass(t *testing.T) {
142 off := newClaimCase(t).srv
143 if off.cfg.ClaimMoveRecovery != nil || off.cfg.HostClaimRecovery == nil || off.cfg.HostClaimRecovery.Moves != nil {
144 t.Fatal("a moves-off console bound the move pass")
145 }
146 on := newClaimCaseWith(t, claimMovesOn).srv
147 if on.cfg.ClaimMoveRecovery == nil || on.cfg.HostClaimRecovery == nil || on.cfg.HostClaimRecovery.Moves == nil {
148 t.Fatal("a moves-on console did not bind the move pass")
149 }
150 if on.cfg.HostClaimRecovery.MoveFenceAuthority == nil {
151 t.Fatal("the move pass has no fence authority for its conversions")
152 }
153}
154