package host import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) func seedHostRootHistory(t *testing.T, rt registry.Route) *release.RootHistory { t.Helper() b := release.HistoryBinding{Host: rt.Host, Account: rt.Account, App: rt.App} store := &rollbackMemory{files: map[string][]byte{}} h := &release.RootHistory{Store: store} id := sha256.Sum256([]byte(b.Account + "\x00" + b.App + "\x00" + b.Host)) rawBinding, _ := json.Marshal(b) raw := fmt.Sprintf(`{"schema":1,"binding":%s,"revision":1,"programs":{"revision":1,"complete":true,"values":null},"refusals":{"revision":1,"complete":true,"values":null}}`, rawBinding) if err := store.Write(hex.EncodeToString(id[:])+".json", []byte(raw)); err != nil { t.Fatal(err) } at := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339Nano) raw = fmt.Sprintf(`{"schema":1,"revision":1,"complete":{%q:1},"observed":%q,"clock":{"jitter":0.1,"channels":{}}}`, b.Account+"|"+b.App, at) if err := store.Write("bake.json", []byte(raw)); err != nil { t.Fatal(err) } id = sha256.Sum256([]byte(b.Account + "\x00" + b.App)) raw = fmt.Sprintf(`{"schema":1,"account":%q,"app":%q,"revision":1,"complete":true,"hosts":[%q]}`, b.Account, b.App, b.Host) if err := store.Write(hex.EncodeToString(id[:])+".app", []byte(raw)); err != nil { t.Fatal(err) } return h } func TestPlan14RootHistoryProductionPass(t *testing.T) { t.Parallel() for _, attack := range []string{"positive", "legacy-deleted", "legacy-recovered", "failed-ready", "record-change"} { t.Run(attack, func(t *testing.T) { e, rt, u, c, pub := plan14AdoptionFixture(t) h := seedHostRootHistory(t, rt) b := release.HistoryBinding{Host: rt.Host, Account: rt.Account, App: rt.App} if attack == "legacy-deleted" || attack == "legacy-recovered" { ptr, err := release.Channel(context.Background(), e.store, pub, rt.Account, rt.App, rt.Channel) if err != nil { t.Fatal(err) } ref := release.Refusal{Host: rt.Host, App: rt.App, Version: ptr.Version, PointerPromotedAt: ptr.PromotedAt, RefusedAt: time.Now(), Reason: "failed"} if err := h.AddRefusal(b, ref); err != nil { t.Fatal(err) } if attack == "legacy-recovered" { ref.Recovered = true ref.Version = "v1" if err := release.WriteRefusal(e.bake, ref); err != nil { t.Fatal(err) } } } if attack == "failed-ready" { c.ready = func(registry.Route) func() error { return func() error { return os.ErrPermission } } } if attack == "record-change" { rt.Account = "bap" if err := e.root.reg.Upsert(rt); err != nil { t.Fatal(err) } } deps := boundRootHistory(h, c.resolve, e.root.obs) forward := rootRestartRouteWith(e.root.reg, u, u.opt, func(string) string { return "5s" }, e.root.obs.recordSettled(e.bake), c) err := release.Pass(context.Background(), e.root.reg, e.store, pub, u.opt, forward, release.BakeDir(e.bake), release.WithRootHistory(deps), release.WithRollbackRestart(rollbackUnitRestart(e.root.reg, u, u.opt, func(string) string { return "5s" }, c))) if attack == "positive" { if err != nil || len(u.effects) != 1 || u.exe != release.VersionPath(u.opt, "bab", "v2", "console") { t.Fatalf("root positive: %v, %+v", err, u.effects) } } else if attack == "failed-ready" { if err == nil { t.Fatal("failed readiness accepted") } if _, err := h.Refusals(b); err == nil { t.Fatal("failed update lost its pending obligation") } } else if len(u.effects) != 0 { t.Fatalf("%s restarted: %v", attack, u.effects) } }) } } func TestPlan14RootHistoryMintCensus(t *testing.T) { t.Parallel() for _, state := range []string{"complete", "refused", "unknown", "census-error", "missing-probe", "no-census-complete"} { t.Run(state, func(t *testing.T) { e, rt, _, _ := plan14ProofFixture(t) h := seedHostRootHistory(t, rt) e.root.refusals = h.MintRefusals e.root.mintCensus = func() (bool, error) { return true, nil } switch state { case "refused": if err := h.AddRefusal(release.HistoryBinding{Host: rt.Host, Account: rt.Account, App: rt.App}, release.Refusal{Host: rt.Host, App: rt.App, Version: "v1", SHA256: e.sha, RefusedAt: time.Now(), Recovered: true}); err != nil { t.Fatal(err) } case "unknown": h.Store = &rollbackMemory{files: map[string][]byte{}} case "census-error": e.root.mintCensus = func() (bool, error) { return true, os.ErrPermission } case "missing-probe": e.root.mintCensus = nil case "no-census-complete": e.root.mintCensus = func() (bool, error) { return false, nil } } e.root.run(context.Background()) if (e.proof(t) != nil) != (state == "complete") { t.Fatalf("mint %s: %v", state, e.logs) } }) } } func TestPlan14ProgramFailureStillRecordsHealth(t *testing.T) { t.Parallel() for _, census := range []bool{false, true} { t.Run(fmt.Sprint(census), func(t *testing.T) { e, _, _, _ := plan14ProofFixture(t) e.root.mintCensus = func() (bool, error) { return census, nil } e.root.recordProgram = func(liveBuild) error { return os.ErrPermission } e.root.run(context.Background()) healthy := release.ReadHealthy(e.root.historyDir(), "bab", "console") if len(healthy) != 1 || healthy[0].SHA256 != e.sha { t.Fatalf("program error suppressed healthy record: %+v", healthy) } if e.proof(t) != nil { t.Fatal("mint ignored program error") } if !strings.Contains(strings.Join(e.logs, "\n"), "recording the installed program") { t.Fatalf("missing diagnostic: %v", e.logs) } }) } } func TestPlan14RootHistoryCanonicalBinding(t *testing.T) { t.Parallel() for _, field := range []string{"unchanged", "account", "app", "unit", "upstream", "readiness"} { t.Run(field, func(t *testing.T) { e, rt, _, source := plan14ProofFixture(t) deps := boundRootHistory(&release.RootHistory{}, source.resolve, e.root.obs) candidate := rt switch field { case "account": candidate.Account = "bap" case "app": candidate.App = "other" case "unit": candidate.Unit = "other.service" case "upstream": candidate.Addr = "" case "readiness": candidate.Health = "" } b, revision, err := deps.Resolve(context.Background(), candidate) if field == "unchanged" { if err != nil || revision == "" || b.Account != "bab" || b.App != "console" || b.Host != rt.Host { t.Fatalf("canonical binding: %+v %q %v", b, revision, err) } } else if err == nil { t.Fatalf("forged %s accepted", field) } }) } } func TestPlan14RootHistoryInstalledRequiresObservation(t *testing.T) { t.Parallel() for _, attack := range []string{"unchanged", "not-ready", "wrong-version", "changed-record"} { t.Run(attack, func(t *testing.T) { e, rt, _, source := plan14ProofFixture(t) version := "v1" if attack == "wrong-version" { version = "v2" } if attack == "not-ready" { e.root.obs.ready = func(registry.Route) func() error { return nil } } deps := boundRootHistory(&release.RootHistory{}, source.resolve, e.root.obs) if attack == "changed-record" { rt.Account = "bap" } _, err := deps.Installed(context.Background(), rt, version) if attack == "unchanged" && err != nil { t.Fatal(err) } if attack != "unchanged" && err == nil { t.Fatalf("%s granted installed history", attack) } }) } }