package hostbox import ( "context" "errors" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/lease/leasetest" "github.com/carlosframework/platform/internal/park" "github.com/carlosframework/platform/internal/release" "strings" "testing" "time" ) type plan12Renewable struct { lost chan struct{} err error releases int } func (r *plan12Renewable) Err() error { return r.err } func (r *plan12Renewable) Lost() <-chan struct{} { return r.lost } func (r *plan12Renewable) Release(context.Context) error { r.releases++; return nil } func TestPlan12LandingResidueParkUnderItsOwnLease(t *testing.T) { for _, mode := range []string{"ok", "wrong-host", "wrong-location", "wrong-bucket", "lost", "authorization"} { t.Run(mode, func(t *testing.T) { ctx := t.Context() host := "app.example" inc := strings.Repeat("a", 64) loc := "instances/" + host + "/" + inc now := time.Date(2026, 10, 10, 12, 0, 0, 0, time.UTC) claims := release.NewDirStore(t.TempDir()) _, err := hostclaim.Update(ctx, claims, hostclaim.Observed{Host: host, Absent: true}, func(c *hostclaim.Claim) error { c.Op = hostclaim.Op{Kind: hostclaim.KindParking, ID: "op", Intent: map[string]string{"park_sweep": "v1"}} c.Obligations = []hostclaim.Obligation{{ID: "chore", Op: "op", Kind: "park-replica", Region: "ie", Owner: "region:ie", Retirement: "ret", Park: &hostclaim.ParkScope{Location: loc, Incarnation: inc, EvidenceAt: now.Add(-time.Hour), RequestLimit: RequestLimit}}} return nil }) if err != nil { t.Fatal(err) } data := leasetest.NewVersionedBucket() data.AddBucket("data") data.Put("data", loc+"/db", []byte("residue")) r := &plan12Renewable{lost: make(chan struct{})} acquires := 0 leases := RenewingLeases{Region: "eu-west-1", Open: func(context.Context, string, string, string) (Renewable, error) { acquires++ if acquires > 1 { return nil, errors.New("already held") } return r, nil }} bucket, scope := "data", loc switch mode { case "wrong-host": scope = "instances/other.example/" + inc case "wrong-location": scope = "instances/" + host case "wrong-bucket": bucket = "other" } own, err := leases.Acquire(ctx, bucket, scope) if err != nil { t.Fatal(err) } defer own.Close() if mode == "lost" { r.err = errors.New("lost") } authorized := 0 engine := park.Engine{Claims: claims, Store: data, Bucket: "data", Region: "ie", Leases: AggregateLeases{Leases: leases, Resolve: ReplicaParkLeaseSet}, Now: func() time.Time { return now }, NewID: func() string { return "next" }, View: func(_ context.Context, o hostclaim.Observed) (park.View, error) { return park.View{ClaimETag: o.ETag, Region: "ie"}, nil }, Authorize: func(context.Context, hostclaim.Observed, hostclaim.Obligation) error { authorized++ if mode == "authorization" { return park.ErrUnsafe } return nil }} err = engine.RunHeld(ctx, park.Work{Host: host, ID: "chore", Op: "op"}, own) if mode == "ok" { if err != nil && !errors.Is(err, park.ErrPending) { t.Fatal(err) } if authorized == 0 { t.Fatal("authorizer bypassed") } versions, e := data.ListVersions(ctx, "data", loc+"/") if e != nil || len(versions) != 0 { t.Fatal("residue not parked", e, versions) } } else if err == nil { t.Fatal("accepted", mode) } if acquires != 1 || r.releases != 0 { t.Fatalf("acquires=%d releases=%d", acquires, r.releases) } if mode != "lost" && own.Check(ctx) != nil { t.Fatal("caller lease lost") } }) } } func TestPlan12LeaseSetSemantics(t *testing.T) { for _, scope := range []string{"moves/app.example/op", "moves/app.example", "instances/app.example", "instances/app.example/" + strings.Repeat("a", 64)} { got, err := ReplicaParkLeaseSet(t.Context(), "data", scope) if err != nil || len(got) != 1 || got[0] != scope { t.Fatalf("%s: %v %v", scope, got, err) } } for _, scope := range []string{"control/staging-leases/ie/app.example", "s3://deployment/moves/app.example/op", "instances/app.example/not-an-incarnation", "moves/other/../op"} { if _, err := ReplicaParkLeaseSet(t.Context(), "data", scope); err == nil { t.Fatal(scope) } } }