| 1 | package moves |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/release" |
| 10 | ) |
| 11 | |
| 12 | func TestFloorReadAndCompare(t *testing.T) { |
| 13 | ctx := context.Background() |
| 14 | s := release.NewDirStore(t.TempDir()) |
| 15 | |
| 16 | f, err := ReadFloor(ctx, s) |
| 17 | if err != nil || f != nil { |
| 18 | t.Fatalf("absent floor read as %+v, %v", f, err) |
| 19 | } |
| 20 | |
| 21 | body, _ := json.Marshal(Floor{Min: ProtocolVersion, MinRelease: "v0.23.0"}) |
| 22 | if _, err := s.Put(ctx, FloorKey, body, ""); err != nil { |
| 23 | t.Fatal(err) |
| 24 | } |
| 25 | f, err = ReadFloor(ctx, s) |
| 26 | if err != nil || f == nil || f.Min != ProtocolVersion || f.MinRelease != "v0.23.0" { |
| 27 | t.Fatalf("written floor read as %+v, %v", f, err) |
| 28 | } |
| 29 | |
| 30 | for _, tc := range []struct { |
| 31 | version, min string |
| 32 | want bool |
| 33 | wantErr bool |
| 34 | }{ |
| 35 | {"v0.23.0", "v0.23.0", true, false}, |
| 36 | {"v0.24.1", "v0.23.0", true, false}, |
| 37 | {"v0.22.9", "v0.23.0", false, false}, |
| 38 | {"v1.0.0", "v0.23.0", true, false}, |
| 39 | {"v0.30.0-rc.1", "v0.23.0", false, true}, // prerelease: fail closed |
| 40 | {"v0.30.0-rc.1", "v0.1.0", false, true}, |
| 41 | {"", "v0.23.0", false, true}, |
| 42 | {"junk", "v0.23.0", false, true}, |
| 43 | {"v0.24.1", "", false, true}, // malformed floor: fail closed |
| 44 | {"v0.24.1", "v0.23", false, true}, |
| 45 | {"v0.24.1", "v0.23.0-rc.1", false, true}, |
| 46 | } { |
| 47 | got, err := ReleaseAtOrAbove(tc.version, tc.min) |
| 48 | if got != tc.want || (err != nil) != tc.wantErr { |
| 49 | t.Errorf("ReleaseAtOrAbove(%q, %q) = %v, %v; want %v, err %v", tc.version, tc.min, got, err, tc.want, tc.wantErr) |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | func TestReadFloorGarbageIsAnError(t *testing.T) { |
| 55 | ctx := context.Background() |
| 56 | s := release.NewDirStore(t.TempDir()) |
| 57 | if _, err := s.Put(ctx, FloorKey, []byte("not json"), ""); err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | if f, err := ReadFloor(ctx, s); err == nil || f != nil { |
| 61 | t.Fatalf("garbage floor read as %+v, %v", f, err) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | // The console record's wire names are what Task 11's T2 check and any |
| 66 | // operator reading the bucket see; the four file names are what the host |
| 67 | // writes and the two system units hard-code (host_test.go pins the units |
| 68 | // against these same constants). |
| 69 | func TestConsoleRecordAndFloorFileNames(t *testing.T) { |
| 70 | at := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) |
| 71 | b, err := json.Marshal(ConsoleRecord{V: 1, Protocol: ProtocolVersion, SecretsKeyFP: "fp", At: at}) |
| 72 | if err != nil { |
| 73 | t.Fatal(err) |
| 74 | } |
| 75 | if want := `{"v":1,"protocol":1,"secrets_key_fp":"fp","at":"2026-10-07T12:00:00Z"}`; string(b) != want { |
| 76 | t.Fatalf("console record = %s, want %s", b, want) |
| 77 | } |
| 78 | if ConsoleKey != "control/moves-console.json" { |
| 79 | t.Fatalf("ConsoleKey = %q", ConsoleKey) |
| 80 | } |
| 81 | if FloorFile != ".moves/floor.json" || FloorMinReleaseFile != ".moves/floor.min-release" { |
| 82 | t.Fatalf("floor files = %q, %q", FloorFile, FloorMinReleaseFile) |
| 83 | } |
| 84 | if PrevVersionFile != "carlos.prev.version" || PrevAllowedFile != "carlos.prev.allowed" { |
| 85 | t.Fatalf("prev files = %q, %q", PrevVersionFile, PrevAllowedFile) |
| 86 | } |
| 87 | } |
| 88 | |