package host import ( "context" "errors" "os" "path/filepath" "reflect" "testing" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) type isolationInputs struct { recoveryInputs roles []string } func (in isolationInputs) Select(ctx context.Context, rt registry.Route, o hostclaim.Observed, policy activator.StartPolicy) (ClaimLaunchInputs, error) { selected, err := in.recoveryInputs.Select(ctx, rt, o, policy) selected.Roles = in.roles return selected, err } func TestClaimWakeRefusesEveryUnisolatedShapeBeforeAdmission(t *testing.T) { for _, shape := range []string{"nested", "flat", "foreign", "systemd", "seed", "standalone-sidecar", "co-spawn", "no-roles"} { t.Run(shape, func(t *testing.T) { f := newRecoveryFixture(t) c := f.controller("boot-1", "controller-1", 4101, "41") // Install the concrete production preflight, not a fixture override. if shape != "no-roles" { c.wake.Roles = &ClaimRoleAssembly{Manager: c.wake.Manager, Journal: f.journal} } roles := []string{"main"} route := registry.Route{Host: recoveryHost, Kind: "instance", Hibernate: true} switch shape { case "nested": route.DBPath = provision.NestedDBPath(f.guard.DataDir, route.Host) route.Socket = filepath.Join(provision.SocketDir, "instances", provision.InstanceID(route.Host), "app.sock") case "flat": route.DBPath = provision.DefaultDBPath(f.guard.DataDir, route.Host) route.Socket = filepath.Join(provision.SocketDir, route.Host+".sock") case "foreign": route.DBPath, route.Socket = "/foreign/data.db", "/foreign/app.sock" case "systemd": route.Unit = "tenant.service" case "seed": roles = []string{"seed"} case "standalone-sidecar": route.Kind = "sidecar" case "co-spawn": roles = []string{"main", "sidecar"} } c.wake.Inputs = isolationInputs{recoveryInputs{f}, roles} before := f.read() guardBefore, err := os.ReadDir(filepath.Join(f.guard.DataDir, "hostguard")) if err != nil { t.Fatal(err) } err = c.runtime.Prerequisites(t.Context(), route, before, recoveryPolicy) if !errors.Is(err, hostbox.ErrUnsupported) { t.Fatalf("shape %s: %v", shape, err) } if after := f.read(); after.ETag != before.ETag || after.Claim.Holder.WakeRetry != nil { t.Fatalf("refusal changed claim or spent retry: %+v", after) } guardAfter, err := os.ReadDir(filepath.Join(f.guard.DataDir, "hostguard")) if err != nil || !reflect.DeepEqual(guardBefore, guardAfter) || len(c.wake.pending) != 0 || len(f.requests) != 0 { t.Fatalf("refusal reached guard/stop/lease: err=%v pending=%d requests=%d", err, len(c.wake.pending), len(f.requests)) } if _, err := f.journal.Read(t.Context(), recoveryTuple(before, c.local)); err == nil { t.Fatal("refusal journaled a launch") } }) } }