| 1 | package moves |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "time" |
| 8 | ) |
| 9 | |
| 10 | // The seal's state class (settings spec rev 11 §4.3a, ruling R-SEAL-STATE): |
| 11 | // the lane's build state by exact version, at most one object, at the |
| 12 | // lane's state/ key, validated like every other class. A seal written |
| 13 | // before the class existed has no "state" field and still decodes, with |
| 14 | // none; a seal with none encodes without the field. |
| 15 | func TestSealStateClass(t *testing.T) { |
| 16 | old := sealForTest().Encode() |
| 17 | if strings.Contains(old, `"state"`) { |
| 18 | t.Fatalf("a seal with no state encodes the field: %s", old) |
| 19 | } |
| 20 | got, err := DecodeSeal(old) |
| 21 | if err != nil || len(got.State) != 0 { |
| 22 | t.Fatalf("a seal without the state field: %+v %v", got.State, err) |
| 23 | } |
| 24 | |
| 25 | s := sealForTest() |
| 26 | s.State = []SealedObject{{Key: BuildStateKey("h", "mv"), VersionID: "v6", Size: 90}} |
| 27 | got, err = DecodeSeal(s.Encode()) |
| 28 | if err != nil || len(got.State) != 1 || got.State[0] != s.State[0] { |
| 29 | t.Fatalf("round trip: %+v %v", got.State, err) |
| 30 | } |
| 31 | |
| 32 | for name, edit := range map[string]func(*Seal){ |
| 33 | "two states": func(s *Seal) { |
| 34 | s.State = append(s.State, SealedObject{Key: BuildStateKey("h", "rb"), VersionID: "v7", Size: 1}) |
| 35 | }, |
| 36 | "another key": func(s *Seal) { s.State[0].Key = "moves/h/mv/replica/build-state.json" }, |
| 37 | "no version": func(s *Seal) { s.State[0].VersionID = "" }, |
| 38 | "a null version": func(s *Seal) { s.State[0].VersionID = "null" }, |
| 39 | "negative size": func(s *Seal) { s.State[0].Size = -1 }, |
| 40 | "a key twice": func(s *Seal) { s.Timers = append(s.Timers, s.State[0]) }, |
| 41 | } { |
| 42 | t.Run(name, func(t *testing.T) { |
| 43 | s := sealForTest() |
| 44 | s.State = []SealedObject{{Key: BuildStateKey("h", "mv"), VersionID: "v6", Size: 90}} |
| 45 | edit(&s) |
| 46 | if err := s.Validate(); !errors.Is(err, ErrBadSeal) { |
| 47 | t.Fatalf("Validate = %v", err) |
| 48 | } |
| 49 | if _, err := DecodeSeal(s.Encode()); !errors.Is(err, ErrBadSeal) { |
| 50 | t.Fatalf("DecodeSeal = %v", err) |
| 51 | } |
| 52 | }) |
| 53 | } |
| 54 | |
| 55 | at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC) |
| 56 | st := []SealedObject{{Key: BuildStateKey("h", "rb"), VersionID: "v9", Size: 9}} |
| 57 | r := ReturnSeal(7, sealForTest().Replica, nil, nil, st, at, at.Add(time.Hour)) |
| 58 | if len(r.State) != 1 || r.State[0] != st[0] { |
| 59 | t.Fatalf("ReturnSeal state %+v", r.State) |
| 60 | } |
| 61 | } |
| 62 | |