| 1 | package hostops |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/ed25519" |
| 6 | "encoding/json" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/hostclaim" |
| 11 | "github.com/carlosframework/platform/internal/provision" |
| 12 | "github.com/carlosframework/platform/internal/release" |
| 13 | ) |
| 14 | |
| 15 | func TestProductionSidecarWakeRegistersAndSettlesBeforeDelete(t *testing.T) { |
| 16 | ctx := context.Background() |
| 17 | store := release.NewDirStore(t.TempDir()) |
| 18 | at := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC) |
| 19 | host := provision.SidecarHost("bot", "app", "acct") |
| 20 | record := provision.SidecarRecord{Schema: 1, Name: "bot", Host: host, Account: "acct", App: "app", CreatedAt: at} |
| 21 | raw, _ := json.Marshal(record) |
| 22 | key := provision.SidecarRecordKey("acct", "app", "bot") |
| 23 | if _, err := store.Put(ctx, key, raw, ""); err != nil { |
| 24 | t.Fatal(err) |
| 25 | } |
| 26 | nh := hostclaim.NewHolder{Family: "sidecar", Key: key, Account: "acct", App: "app", Name: "bot", Host: host, CreatedAt: at, Region: "ie"} |
| 27 | if _, err := hostclaim.Update(ctx, store, hostclaim.Observed{Host: host, Absent: true}, hostclaim.Adopt(nh, "box", nil, nil, "adopt", func() string { return "unused" })); err != nil { |
| 28 | t.Fatal(err) |
| 29 | } |
| 30 | admission := AppAdmission{V: 1, AppID: "app-id", Revision: 1, Epoch: 1, State: "open", Registrations: map[string]Registration{}, Effects: map[string]Effect{}} |
| 31 | raw, _ = json.Marshal(admission) |
| 32 | if _, err := store.Put(ctx, "apps/acct/app/admission.json", raw, ""); err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | pub, _, err := ed25519.GenerateKey(nil) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | w, err := NewControlWriter(store, pub, func() time.Time { return at }) |
| 40 | if err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | authority := ProductionAuthority{Store: store, Control: w, ConsoleID: "console", Enrollment: "enrollment", Now: func() time.Time { return at }} |
| 44 | if err := authority.TouchSidecarWake(ctx, "acct", "app", "bot", "app-id", "operator", "wake"); err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | raw, etag, err := store.Get(ctx, release.SidecarWakeKey("acct", "app", "bot")) |
| 48 | if err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | var signal release.SidecarWake |
| 52 | if err = json.Unmarshal(raw, &signal); err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | observed, err := ReadAdmission(ctx, store, "acct", "app", "app-id") |
| 56 | if err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | reg, ok := observed.Value.Registrations[signal.RequestID] |
| 60 | effect, settled := observed.Value.Effects[signal.RequestID] |
| 61 | if !ok || reg.Kind != "wake" || reg.Incarnation != signal.Incarnation || signal.AppID != "app-id" || !settled || effect.State != "complete" || effect.ResultReference != signal.RequestID { |
| 62 | t.Fatalf("missing admission/effect: %+v %+v %+v", signal, reg, effect) |
| 63 | } |
| 64 | if effect.Inputs["admission_epoch"] != "1" || effect.Inputs["created_at"] != at.Format(time.RFC3339Nano) || effect.Inputs["key"] != release.SidecarWakeKey("acct", "app", "bot") { |
| 65 | t.Fatalf("wake effect lost durable identity: %+v", effect) |
| 66 | } |
| 67 | if _, err = w.BeginExclusive(ctx, "acct", "app", "app-id", AppOperation{ID: "delete", Kind: OpDeleting, AppID: "app-id"}, RegistrationOwner{Console: "console", Enrollment: "enrollment", Controller: "controller"}); err != nil { |
| 68 | t.Fatal(err) |
| 69 | } |
| 70 | if err = authority.TouchSidecarWake(ctx, "acct", "app", "bot", "app-id", "operator", "late"); err == nil { |
| 71 | t.Fatal("wake crossed closed app gate") |
| 72 | } |
| 73 | _, after, err := store.Get(ctx, release.SidecarWakeKey("acct", "app", "bot")) |
| 74 | if err != nil || after != etag { |
| 75 | t.Fatalf("rejected wake mutated signal: %s %v", after, err) |
| 76 | } |
| 77 | } |
| 78 | |