package host import ( "context" "errors" "net" "os" "path/filepath" "strings" "testing" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) func TestParseShowReadsActiveEnterTimestamp(t *testing.T) { p := parseShow("ActiveState=active\nActiveEnterTimestamp=Mon 2026-10-06 09:30:30 UTC\n") if want := time.Date(2026, 10, 6, 9, 30, 30, 0, time.UTC); !p.ActiveEnterTimestamp.Equal(want) { t.Fatalf("ActiveEnterTimestamp = %v, want %v", p.ActiveEnterTimestamp, want) } if !parseShow("ActiveEnterTimestamp=\n").ActiveEnterTimestamp.IsZero() { t.Fatal("an empty timestamp is not zero") } if !parseShow("ActiveEnterTimestamp=n/a\n").ActiveEnterTimestamp.IsZero() { t.Fatal("n/a is not zero") } } const observedHost = "platform.example.com" // observeFixture: the route's live link names versions/v1/console, and by // default the unit runs that very file, bound, active for 20 minutes, ready. func observeFixture(t *testing.T) (proofObserver, registry.Route, string, time.Time) { t.Helper() opt := t.TempDir() liveTo(t, opt, observedHost, "v1") staged := filepath.Join(opt, "versions", "v1", "console") now := time.Date(2026, 10, 6, 10, 0, 0, 0, time.UTC) o := proofObserver{ sys: &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "i1", MainPID: 7, ExecPath: release.LiveLink(opt, observedHost), ActiveEnterTimestamp: now.Add(-20 * time.Minute)}}}, procExe: func(int) string { return staged }, optDir: opt, now: func() time.Time { return now }, ready: func(registry.Route) func() error { return func() error { return nil } }, } rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} o.resolve = proofFixtureResolver(rt) return o, rt, staged, now } func TestProofObserveAcceptsABakedBuild(t *testing.T) { o, rt, staged, now := observeFixture(t) b, err := o.observe(rt, 15*time.Minute) if err != nil { t.Fatalf("observe: %v", err) } want, _ := release.FileSHA256(staged) if b.SHA256 != want || b.Version != "v1" || !b.ActiveSince.Equal(now.Add(-20*time.Minute)) { t.Fatalf("build = %+v", b) } } // Spec ยง7: the mint conditions, each false in turn. func TestProofObserveConditionsEachFalse(t *testing.T) { for name, c := range map[string]struct { edit func(t *testing.T, o *proofObserver, now time.Time) want string }{ "binding": {func(t *testing.T, o *proofObserver, now time.Time) { o.sys.(*fakeUnits).shows[0].ExecPath = release.LiveLink(o.optDir, "console.example.com") }, "binding"}, "not active": {func(t *testing.T, o *proofObserver, now time.Time) { o.sys.(*fakeUnits).shows[0].ActiveState = "failed" }, "failed"}, "too young": {func(t *testing.T, o *proofObserver, now time.Time) { o.sys.(*fakeUnits).shows[0].ActiveEnterTimestamp = now.Add(-5 * time.Minute) }, "needs 15m"}, "restarted during the check": {func(t *testing.T, o *proofObserver, now time.Time) { f := o.sys.(*fakeUnits) second := f.shows[0] second.InvocationID = "i2" f.shows = append(f.shows, second) }, "restarted"}, "exe and live link differ": {func(t *testing.T, o *proofObserver, now time.Time) { other := filepath.Join(t.TempDir(), "console") if err := os.WriteFile(other, []byte("a different build"), 0o755); err != nil { t.Fatal(err) } o.procExe = func(int) string { return other } }, "is not the live link's build"}, "not ready": {func(t *testing.T, o *proofObserver, now time.Time) { o.ready = func(registry.Route) func() error { return func() error { return errors.New("status 503") } } }, "not ready"}, } { t.Run(name, func(t *testing.T) { o, rt, _, now := observeFixture(t) c.edit(t, &o, now) if _, err := o.observe(rt, 15*time.Minute); err == nil || !strings.Contains(err.Error(), c.want) { t.Fatalf("observe = %v, want an error containing %q", err, c.want) } }) } } // D1's settle passing is the hook that records a healthy build, hashed from // the executable that passed. func TestSettledRestartRecordsTheRunningExe(t *testing.T) { opt, bake := t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "new") staged, err := filepath.EvalSymlinks(filepath.Join(opt, "versions", "new", "console")) if err != nil { t.Fatal(err) } ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } t.Cleanup(func() { ln.Close() }) rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service", Addr: ln.Addr().String()} f := &fakeUnits{shows: []unitProps{ {ActiveState: "active", InvocationID: "old", ExecPath: filepath.Join(opt, "live", observedHost)}, {ActiveState: "active", InvocationID: "new", MainPID: 7, ExecPath: filepath.Join(opt, "live", observedHost)}, }, exe: map[int]string{7: staged}} settled := proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt) if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err != nil { t.Fatalf("restart: %v", err) } sha, _ := release.FileSHA256(staged) h := release.ReadHealthy(bake, "bab", "console") if len(h) != 1 || h[0].SHA256 != sha || h[0].How != "settle" || h[0].Version != "new" { t.Fatalf("history = %+v, want the settled build", h) } } // Ruling 8: the settled hook records only what observe would. Its own Show // must name the live link as the unit's executable, and the bytes it hashes // must be the live link's; otherwise a process restarted, or not yet exec'd, // in the moment after the settle could enter the history. func TestRecordSettledRecordsNothingForAnotherExe(t *testing.T) { for name, c := range map[string]struct { execPath func(opt string) string procExe func(t *testing.T, opt string) string }{ "Show names another executable": { execPath: func(opt string) string { return release.LiveLink(opt, "console.example.com") }, procExe: func(t *testing.T, opt string) string { return filepath.Join(opt, "versions", "new", "console") }, }, "the running bytes are not the live link's": { execPath: func(opt string) string { return release.LiveLink(opt, observedHost) }, procExe: func(t *testing.T, opt string) string { other := filepath.Join(t.TempDir(), "console") if err := os.WriteFile(other, []byte("a different build"), 0o755); err != nil { t.Fatal(err) } return other }, }, } { t.Run(name, func(t *testing.T) { opt, bake := t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "new") exe := c.procExe(t, opt) rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} f := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "new", MainPID: 7, ExecPath: c.execPath(opt)}}} proofFixtureSettled(f, func(int) string { return exe }, opt, bake, time.Now, rt)(rt) if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 { t.Fatalf("recorded %+v for a process that is not the live link's build", h) } }) } // The control: the same call with both equalities holding records. opt, bake := t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "new") rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} f := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "new", MainPID: 7, ExecPath: release.LiveLink(opt, observedHost)}}} staged := filepath.Join(opt, "versions", "new", "console") proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt)(rt) if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 1 || h[0].How != "settle" { t.Fatalf("history = %+v, want the settled build", h) } } func TestUnsettledRestartRecordsNothing(t *testing.T) { opt, bake := t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "new") rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} f := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "old", ExecPath: filepath.Join(opt, "live", "console.example.com")}}} settled := proofFixtureSettled(f, func(int) string { return "/nonexistent" }, opt, bake, time.Now, rt) if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err != nil { t.Fatalf("restart: %v", err) } if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 { t.Fatalf("a restart that was not checked recorded %+v", h) } } // F2: a unit an operator stopped during the settle is not judged, and its // build is not recorded as healthy, even though its executable is readable. func TestStoppedDuringSettleRecordsNothing(t *testing.T) { opt, bake := t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "new") staged := filepath.Join(opt, "versions", "new", "console") rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} f := &fakeUnits{shows: []unitProps{ {ActiveState: "active", SubState: "running", InvocationID: "old", MainPID: 6, ExecPath: filepath.Join(opt, "live", observedHost)}, {ActiveState: "inactive", SubState: "dead", InvocationID: "old"}, }} settled := proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt) if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err != nil { t.Fatalf("restart: %v", err) } if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 { t.Fatalf("a stopped unit recorded %+v", h) } } // A settle that fails records nothing either. func TestFailedSettleRecordsNothing(t *testing.T) { opt, bake := t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "new") staged := filepath.Join(opt, "versions", "new", "console") rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} f := &fakeUnits{shows: []unitProps{ {ActiveState: "active", SubState: "running", InvocationID: "old", MainPID: 6, ExecPath: filepath.Join(opt, "live", observedHost)}, {ActiveState: "failed", SubState: "failed", InvocationID: "new"}, }} settled := proofFixtureSettled(f, func(int) string { return staged }, opt, bake, time.Now, rt) if err := agentCheckedUnitRestart(f, opt, func(string) string { return "" }, settled)(rt); err == nil { t.Fatal("a failed unit settled") } if h := release.ReadHealthy(bake, "bab", "console"); len(h) != 0 { t.Fatalf("a failed build recorded %+v", h) } } func proofFixtureResolver(rt registry.Route) proofBindingResolver { rt.Account = release.RouteAccount(rt) return func(_ context.Context, candidate registry.Route) (proofBinding, error) { if candidate.Host != rt.Host || candidate.App != rt.App || candidate.Unit != rt.Unit { return proofBinding{}, os.ErrPermission } return proofBinding{Route: rt, Source: "test-installed-binding", Network: true}, nil } } func proofFixtureSettled(sys unitSystem, procExe func(int) string, opt, history string, now func() time.Time, rt registry.Route) func(registry.Route) error { if f, ok := sys.(*fakeUnits); ok { for i := range f.shows { if f.shows[i].ActiveEnterTimestamp.IsZero() { f.shows[i].ActiveEnterTimestamp = now().Add(-time.Minute) } } } o := proofObserver{sys: sys, procExe: procExe, optDir: opt, now: now, resolve: proofFixtureResolver(rt), ready: func(registry.Route) func() error { return func() error { return nil } }} return o.recordSettled(history) }