package hostclaim import ( "context" "errors" "fmt" "reflect" "testing" "time" "github.com/carlosframework/platform/internal/release" ) func counter() func() string { n := 0 return func() string { n++; return fmt.Sprintf("id_%d", n) } } func servingClaim(t *testing.T, ctx context.Context, s release.Store) Observed { t.Helper() now := time.Date(2026, 10, 4, 1, 0, 1, 0, time.UTC) reg, err := Read(ctx, s, testHost) if err != nil { t.Fatal(err) } o, err := Update(ctx, s, reg, BeginCreate(newHolder(), record, "op_c1", "console", now)) if err != nil { t.Fatal(err) } if o, err = Update(ctx, s, o, CompleteCreate("op_c1", "adi-knitting")); err != nil { t.Fatal(err) } return o // no fixture repair: CompleteCreate itself records adi-knitting in touched.boxes } func TestDeleteParkFree(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) now := time.Date(2026, 10, 4, 2, 0, 0, 0, time.UTC) o := servingClaim(t, ctx, s) inc, stamp := o.Claim.Holder.Incarnation, o.Claim.Holder.Stamp o, err := Update(ctx, s, o, BeginDelete("op_d1", "console", `"etag1"`, now, nil, nil, "")) if err != nil || o.Claim.Op.Kind != KindDeleting || o.Claim.Op.Intent["etag"] != `"etag1"` { t.Fatalf("begin delete = %+v, %v", o.Claim, err) } if o.Claim.Op.Exec != "adi-knitting" || o.Claim.Holder.Stamp != stamp { t.Fatalf("delete dropped execution authority: exec %q stamp %d", o.Claim.Op.Exec, o.Claim.Holder.Stamp) } o, err = Update(ctx, s, o, ToParking("op_d1", nil, nil, counter())) if err != nil || o.Claim.Op.Kind != KindParking || len(o.Claim.Obligations) != 2 { t.Fatalf("to parking = %+v, %v", o.Claim, err) } if _, err := Update(ctx, s, o, FreeAfterPark("op_d1")); !errors.Is(err, ErrWrongState) { t.Fatalf("free with chores left = %v", err) } for _, ob := range o.Claim.Obligations { if o, err = Update(ctx, s, o, CompleteObligation(ob.Op, ob.ID)); err != nil { t.Fatal(err) } } if _, err := Update(ctx, s, o, CompleteObligation("op_d1", "id_1")); !errors.Is(err, ErrWrongState) { t.Fatalf("completing a chore twice = %v", err) } o, err = Update(ctx, s, o, FreeAfterPark("op_d1")) if err != nil || !o.Claim.Free() || o.Claim.RetiredIncarnations[0] != inc { t.Fatalf("free = %+v, %v", o.Claim, err) } } func TestIncompleteTouchedParksEverywhere(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) now := time.Date(2026, 10, 4, 2, 0, 0, 0, time.UTC) o := servingClaim(t, ctx, s) o, err := Update(ctx, s, o, func(c *Claim) error { c.Touched.Complete = false; return nil }) if err != nil { t.Fatal(err) } regions, boxes := []string{"ie", "uk", "us"}, []string{"b1", "b2"} o, err = Update(ctx, s, o, BeginDelete("op_d1", "console", `"e"`, now, regions, boxes, "")) if err != nil { t.Fatal(err) } o, err = Update(ctx, s, o, ToParking("op_d1", regions, boxes, counter())) if err != nil { t.Fatal(err) } assertParkingTargets(t, o.Claim.Obligations, []Obligation{ {ParkTarget: "instances/" + testHost + "/" + o.Claim.Holder.Incarnation, Op: "op_d1", Kind: "park-replica", Region: "ie", Owner: "region:ie"}, {ParkTarget: "instances/" + testHost + "/" + o.Claim.Holder.Incarnation, Op: "op_d1", Kind: "park-replica", Region: "uk", Owner: "region:uk"}, {ParkTarget: "instances/" + testHost + "/" + o.Claim.Holder.Incarnation, Op: "op_d1", Kind: "park-replica", Region: "us", Owner: "region:us"}, {Op: "op_d1", Kind: "park-local", Box: "adi-knitting", Owner: "box:adi-knitting"}, {Op: "op_d1", Kind: "park-local", Box: "b1", Owner: "box:b1"}, {Op: "op_d1", Kind: "park-local", Box: "b2", Owner: "box:b2"}, }) } func TestToParkingRefusesAPurgeFence(t *testing.T) { fence := &Claim{V: 2, Host: testHost, Gen: 3, Op: Op{Kind: KindPurging, ID: "op_p"}} if err := ToParking("op_p", nil, nil, counter())(fence); !errors.Is(err, ErrWrongState) { t.Fatalf("purge fence = %v, want ErrWrongState", err) } } func TestRetiredCapKeepsNewest32(t *testing.T) { c := &Claim{} for i := 0; i < 33; i++ { retire(c, fmt.Sprintf("inc%02d", i)) } if len(c.RetiredIncarnations) != 32 || c.RetiredIncarnations[0] != "inc32" || c.RetiredIncarnations[31] != "inc01" { t.Fatalf("retired = %v", c.RetiredIncarnations) } } func TestAppendParkObligations(t *testing.T) { existing := Obligation{ID: "existing", Op: "op_old", Kind: "park-local", Box: "b0", Retirement: "retired", Owner: "box:b0"} c := &Claim{Obligations: []Obligation{existing}} newID := counter() appendParkObligations(c, "op_park", []string{"uk", "us"}, []string{"b1", "b2"}, newID) want := []Obligation{ existing, {ID: "id_1", Op: "op_park", Kind: "park-replica", Region: "uk", Retirement: "id_2", Owner: "region:uk"}, {ID: "id_3", Op: "op_park", Kind: "park-replica", Region: "us", Retirement: "id_4", Owner: "region:us"}, {ID: "id_5", Op: "op_park", Kind: "park-local", Box: "b1", Retirement: "id_6", Owner: "box:b1"}, {ID: "id_7", Op: "op_park", Kind: "park-local", Box: "b2", Retirement: "id_8", Owner: "box:b2"}, } if !reflect.DeepEqual(c.Obligations, want) { t.Fatalf("obligations = %+v, want %+v", c.Obligations, want) } appendParkObligations(c, "op_empty", nil, nil, newID) if !reflect.DeepEqual(c.Obligations, want) { t.Fatalf("empty append changed obligations: %+v", c.Obligations) } if got := newID(); got != "id_9" { t.Fatalf("next id = %q, want id_9", got) } } func assertParkingTargets(t *testing.T, obligations, want []Obligation) { t.Helper() got := append([]Obligation(nil), obligations...) for i := range got { got[i].ID, got[i].Retirement = "", "" } if !reflect.DeepEqual(got, want) { t.Fatalf("parking targets = %+v, want %+v", got, want) } } func TestWriteSetIncompleteUnion(t *testing.T) { c := &Claim{Touched: Touched{ Regions: []string{"ie", "ie", "uk"}, Boxes: []string{"B0", "B0", "B2"}, }} regions, boxes := WriteSet(c, []string{"us", "uk", "us", "ie"}, []string{"B3", "B2", "B3", "B0"}) if !reflect.DeepEqual(regions, []string{"ie", "uk", "us"}) || !reflect.DeepEqual(boxes, []string{"B0", "B2", "B3"}) { t.Fatalf("write set = %v, %v; want [ie uk us], [B0 B2 B3]", regions, boxes) } } func TestIncompleteAdoptionPreservesParkingTargets(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) obs, err := Read(ctx, s, testHost) if err != nil { t.Fatal(err) } newID := counter() o, err := Update(ctx, s, obs, Adopt(newHolder(), "B0", []string{"uk"}, []string{"B2"}, "op_adopt", newID)) if err != nil { t.Fatal(err) } regions := []string{"ie"} o, err = Update(ctx, s, o, BeginDelete("op_d", "console", `"e"`, time.Now(), regions, nil, "")) if err != nil { t.Fatal(err) } o, err = Update(ctx, s, o, ToParking("op_d", regions, nil, newID)) if err != nil { t.Fatal(err) } assertParkingTargets(t, o.Claim.Obligations, []Obligation{ {ParkTarget: "instances/" + testHost, Op: "op_adopt", Kind: "park-replica", Region: "uk", Owner: "region:uk"}, {Op: "op_adopt", Kind: "park-local", Box: "B2", Owner: "box:B2"}, {ParkTarget: "instances/" + testHost, Op: "op_d", Kind: "park-replica", Region: "ie", Owner: "region:ie"}, {Op: "op_d", Kind: "park-local", Box: "B0", Owner: "box:B0"}, }) for _, ob := range o.Claim.Obligations { if _, err := Update(ctx, s, o, FreeAfterPark("op_d")); !errors.Is(err, ErrWrongState) { t.Fatalf("free with %d chores remaining = %v", len(o.Claim.Obligations), err) } o, err = Update(ctx, s, o, CompleteObligation(ob.Op, ob.ID)) if err != nil { t.Fatal(err) } } o, err = Update(ctx, s, o, FreeAfterPark("op_d")) if err != nil { t.Fatal(err) } if !o.Claim.Free() { t.Fatalf("not free after all chores: %+v", o.Claim) } } func TestOrdinaryDeleteProducesPhasedParking(t *testing.T) { c := &Claim{Host: "app.example", Holder: &Holder{Incarnation: "inc", Location: "instances/app.example/inc"}, Op: Op{Kind: KindDeleting, ID: "delete"}, Touched: Touched{Complete: true, Boxes: []string{"box"}}} if err := ToParking("delete", nil, nil, func() string { return "id" })(c); err != nil { t.Fatal(err) } if c.Op.Kind != KindParking || c.Op.Phase != "park" || len(c.Obligations) != 1 || c.Obligations[0].Kind != "park-local" { t.Fatalf("ordinary parking stop authority: %+v", c) } }