| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/ed25519" |
| 6 | "encoding/json" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/hostops" |
| 11 | "github.com/carlosframework/platform/internal/provision" |
| 12 | "github.com/carlosframework/platform/internal/release" |
| 13 | ) |
| 14 | |
| 15 | // Exercise the actual wrapper composition. The injected checker permits |
| 16 | // these test writes; this tests encoding/context propagation, not authority. |
| 17 | func TestPlan9B8SequenceThroughConsoleEffectAndMutationStack(t *testing.T) { |
| 18 | ctx := context.Background() |
| 19 | raw := release.NewDirStore(t.TempDir()) |
| 20 | pub, _, err := ed25519.GenerateKey(nil) |
| 21 | if err != nil { |
| 22 | t.Fatal(err) |
| 23 | } |
| 24 | now := time.Now() |
| 25 | w, err := hostops.NewControlWriter(raw, pub, func() time.Time { return now }) |
| 26 | if err != nil { |
| 27 | t.Fatal(err) |
| 28 | } |
| 29 | a := hostops.AppAdmission{V: 1, AppID: "app-id", Revision: 1, Epoch: 1, State: "open", Registrations: map[string]hostops.Registration{}, Effects: map[string]hostops.Effect{}} |
| 30 | body, _ := json.Marshal(a) |
| 31 | if _, err = raw.Put(ctx, "apps/bab/site/admission.json", body, ""); err != nil { |
| 32 | t.Fatal(err) |
| 33 | } |
| 34 | owner := hostops.RegistrationOwner{Console: "console", Enrollment: "enrolled", Controller: "controller"} |
| 35 | h, err := w.RegisterStart(ctx, "bab", "site", "app-id", "registration", hostops.Registration{Kind: "create", Host: "one.example.com", Claim: hostops.SavedClaim{Absent: true}, Owner: owner, AppID: "app-id", Epoch: 1, CreatedAt: now}, now) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | records, err := hostops.NewConditionalRecordStore(raw, pub) |
| 40 | if err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | observed := 0 |
| 44 | data, err := hostops.NewObservedMutationBoundary(true, records, func(context.Context, hostops.MutationTarget) error { return nil }, func(e hostops.MutationEvent) { |
| 45 | if e.Verdict == nil { |
| 46 | observed++ |
| 47 | } |
| 48 | }) |
| 49 | if err != nil { |
| 50 | t.Fatal(err) |
| 51 | } |
| 52 | store := claimEffectStore{data: data, effects: claimStoreEffects{OperationID: "registration", Runner: h.Runner(func() time.Time { return now })}} |
| 53 | rec := provision.Record{Schema: 1, Account: "bab", App: "site", Host: "one.example.com", Region: "ie", Channel: "stable", CreatedAt: now} |
| 54 | // Seed legacy bytes outside the claim stack, then let the inner boundary |
| 55 | // introduce the sequence. The typed outer writer must return exactly one. |
| 56 | if err = provision.CreateRecord(ctx, raw, rec); err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | got, err := provision.SetRecordMove(ctx, store, rec.Account, rec.App, rec.Host, func(*provision.Record) error { return nil }) |
| 60 | if err != nil || got.RecordSeq != 1 { |
| 61 | t.Fatalf("first claim write: %+v %v", got, err) |
| 62 | } |
| 63 | got, tag, err := provision.GetRecordETag(ctx, store, rec.Account, rec.App, rec.Host) |
| 64 | if err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | got.Channel = "edge" |
| 68 | if err = provision.UpdateRecord(ctx, store, *got, tag); err != nil { |
| 69 | t.Fatal(err) |
| 70 | } |
| 71 | afterUpdate, updateReadErr := provision.GetRecord(ctx, raw, rec.Account, rec.App, rec.Host) |
| 72 | if updateReadErr != nil || afterUpdate.RecordSeq != 2 { |
| 73 | t.Fatalf("whole-record update: %+v %v", afterUpdate, updateReadErr) |
| 74 | } |
| 75 | got, err = provision.SetRecordMove(ctx, store, rec.Account, rec.App, rec.Host, func(*provision.Record) error { return nil }) |
| 76 | if err != nil || got.RecordSeq != 3 { |
| 77 | t.Fatalf("populated writes: %+v %v", got, err) |
| 78 | } |
| 79 | current, err := provision.GetRecord(ctx, raw, rec.Account, rec.App, rec.Host) |
| 80 | if err != nil || current.RecordSeq != got.RecordSeq { |
| 81 | t.Fatalf("returned vs stored: %+v %v", current, err) |
| 82 | } |
| 83 | side := provision.SidecarRecord{Schema: 1, Account: "bab", App: "site", Name: "worker", Host: provision.SidecarHost("worker", "site", "bab"), CreatedAt: now} |
| 84 | if err = provision.CreateSidecarRecord(ctx, store, side); err != nil { |
| 85 | t.Fatal(err) |
| 86 | } |
| 87 | body, _, err = raw.Get(ctx, provision.SidecarRecordKey("bab", "site", "worker")) |
| 88 | if err != nil { |
| 89 | t.Fatal(err) |
| 90 | } |
| 91 | _, seq, err := provision.RecordSequence(body) |
| 92 | if err != nil || seq != 1 || observed != 4 { |
| 93 | t.Fatalf("sidecar seq=%d observations=%d err=%v", seq, observed, err) |
| 94 | } |
| 95 | } |
| 96 | |