package host import ( "context" "encoding/base64" "encoding/json" "errors" "io/fs" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/boxid" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostfloor" "github.com/carlosframework/platform/internal/status" ) var joinNow = time.Date(2026, 10, 17, 12, 0, 0, 0, time.UTC) // joinFacts is a box, "ada-hiking", in each state the tests name. The // zero value has no host-actor.json and no invitation. type joinFacts struct { enrollment *hostfloor.Enrollment enrollmentErr error roster hostactors.Roster rosterErr error guard hostfloor.Running guardErr error invited bool invitedErr error report *status.Box reportErr error relaunchWhy string relaunchErr error relaunchAsked *bool marked bool region string } func (j joinFacts) facts() modeFacts { return modeFacts{ box: "ada-hiking", enrollment: func() (hostfloor.Enrollment, error) { if j.enrollmentErr != nil { return hostfloor.Enrollment{}, j.enrollmentErr } if j.enrollment == nil { return hostfloor.Enrollment{}, &fs.PathError{Op: "open", Path: hostActorPath, Err: fs.ErrNotExist} } return *j.enrollment, nil }, roster: func(context.Context) (hostactors.Roster, error) { return j.roster, j.rosterErr }, guard: func() (hostfloor.Running, error) { if j.guardErr != nil { return hostfloor.Running{}, j.guardErr } return j.guard, nil }, invited: func(context.Context, string) (bool, error) { return j.invited, j.invitedErr }, report: func(context.Context, string) (status.Box, error) { if j.reportErr != nil { return status.Box{}, j.reportErr } if j.report == nil { return status.Box{}, os.ErrNotExist } return *j.report, nil }, relaunch: func(context.Context, hostactors.Box) (string, error) { if j.relaunchAsked != nil { *j.relaunchAsked = true } return j.relaunchWhy, j.relaunchErr }, marked: j.marked, region: j.region, now: func() time.Time { return joinNow }, } } const joinEnrollment = "0123456789abcdef0123456789abcdef" func enrolledFacts() joinFacts { e := hostfloor.Enrollment{Box: "ada-hiking", Enrollment: joinEnrollment, InstanceID: "i-ada"} return joinFacts{ enrollment: &e, roster: hostactors.Roster{FloorSHA256: "floor", Boxes: map[string]hostactors.Box{"ada-hiking": { Enrollment: joinEnrollment, State: "active", Cloud: hostactors.Cloud{InstanceID: "i-ada"}}}}, guard: hostfloor.Running{GuardReady: true, FloorSHA256: "floor", Hash: "image"}, } } // guardInstalled is a box that guard-arm has armed but the console has not // enrolled yet. func guardInstalled() joinFacts { f := enrolledFacts() f.roster.Boxes = map[string]hostactors.Box{} return f } func freshReport(at time.Time) *status.Box { return &status.Box{GeneratedAt: at, Metrics: &status.BoxMetrics{Enrollment: joinEnrollment, FloorGuardReady: true, FloorSHA256: "floor", BinarySHA256: "image"}} } func TestSelectModeHostsOnlyWhenEnrolledAndArmed(t *testing.T) { d, err := selectMode(context.Background(), enrolledFacts().facts()) if err != nil || d.Mode != modeHosting || d.Box != "ada-hiking" { t.Fatalf("enrolled and armed: %+v %v", d, err) } held := map[string]func(*joinFacts){ "the guard is not armed": func(f *joinFacts) { f.guardErr = hostfloor.ErrFloor }, "the guard has no policy": func(f *joinFacts) { f.guardErr = &fs.PathError{Op: "open", Path: "policy.json", Err: fs.ErrNotExist} }, "the guard is for another floor": func(f *joinFacts) { f.guard.FloorSHA256 = "old" }, "the roster entry is quarantined": func(f *joinFacts) { b := f.roster.Boxes["ada-hiking"] b.State = "quarantined" f.roster.Boxes["ada-hiking"] = b }, "the roster entry is another enrollment": func(f *joinFacts) { b := f.roster.Boxes["ada-hiking"] b.Enrollment = strings.Repeat("f", 32) f.roster.Boxes["ada-hiking"] = b }, "the roster entry is another instance": func(f *joinFacts) { b := f.roster.Boxes["ada-hiking"] b.Cloud.InstanceID = "i-old" f.roster.Boxes["ada-hiking"] = b }, "host-actor.json names another box": func(f *joinFacts) { f.enrollment.Box = "grace-hiking" }, } for name, spoil := range held { t.Run(name, func(t *testing.T) { f := enrolledFacts() spoil(&f) d, err := selectMode(context.Background(), f.facts()) if err != nil || d.Mode != modeJoining || d.Stage != stageHeld || d.Why == "" { t.Fatalf("%+v %v", d, err) } }) } } // A restart at each joining state resumes joining at that state (spec 2 // ยง11): nothing but an active roster entry with an armed guard hosts. func TestSelectModeResumesEachJoiningStage(t *testing.T) { for _, c := range []struct { name string facts joinFacts want joinStage }{ {"no invitation", joinFacts{}, stageNoInvitation}, {"invited", joinFacts{invited: true}, stageInvited}, {"guard-arm cut off after host-actor.json", func() joinFacts { f := guardInstalled(); f.guardErr = hostfloor.ErrFloor; return f }(), stageInvited}, {"guard installed", guardInstalled(), stageGuardInstalled}, {"guard-ready reported", func() joinFacts { f := guardInstalled(); f.report = freshReport(joinNow.Add(-time.Minute)); return f }(), stageGuardReady}, {"a stale report", func() joinFacts { f := guardInstalled() f.report = freshReport(joinNow.Add(-16 * time.Minute)) return f }(), stageGuardInstalled}, {"a report for another image", func() joinFacts { f := guardInstalled() f.report = freshReport(joinNow) f.report.Metrics.BinarySHA256 = "bootstrap" return f }(), stageGuardInstalled}, } { t.Run(c.name, func(t *testing.T) { d, err := selectMode(context.Background(), c.facts.facts()) if err != nil || d.Mode != modeJoining || d.Stage != c.want { t.Fatalf("%+v %v, want joining at %s", d, err, c.want) } }) } } // A fact that cannot be read is unknown, never its negative: an enrolled // box whose roster read fails is not sent back to joining. func TestSelectModeIsUnknownWhenAFactCannotBeRead(t *testing.T) { outage := errors.New("connection reset") for name, f := range map[string]joinFacts{ "the invitation": {invitedErr: outage}, "host-actor.json": {enrollmentErr: hostfloor.ErrFloor}, "the roster": func() joinFacts { f := enrolledFacts(); f.rosterErr = outage; return f }(), "the guard": func() joinFacts { f := enrolledFacts(); f.guardErr = errors.New("permission denied"); return f }(), "the status report": func() joinFacts { f := guardInstalled(); f.reportErr = outage; return f }(), "a roster that does not verify": func() joinFacts { f := guardInstalled(); f.rosterErr = hostactors.ErrRoster; return f }(), } { t.Run(name, func(t *testing.T) { d, err := selectMode(context.Background(), f.facts()) if !errors.Is(err, errModeUnknown) || d.Mode == modeHosting { t.Fatalf("%+v %v", d, err) } }) } } // C3: a box its region's group launched hosts only on the launch it was // enrolled on. Stopped and started since, it is held, never hosting on its // old enrollment; a launch that cannot be read is unknown. A box an // operator enrolled (no group) is not asked. func TestSelectModeHoldsAGroupBoxStartedAgain(t *testing.T) { grouped := func() joinFacts { f := enrolledFacts() b := f.roster.Boxes["ada-hiking"] b.Group, b.Region = "routers-ie", "ie" f.roster.Boxes["ada-hiking"] = b f.relaunchAsked = new(bool) f.marked, f.region = true, "ie" return f } f := grouped() if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeHosting || !*f.relaunchAsked { t.Fatalf("same launch: %+v %v", d, err) } f = grouped() f.relaunchWhy = "this instance was stopped and started since it was enrolled" if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeJoining || d.Stage != stageHeld || d.Why != f.relaunchWhy { t.Fatalf("started again: %+v %v", d, err) } f = grouped() f.relaunchErr = errors.New("IMDS answered 500") if d, err := selectMode(context.Background(), f.facts()); !errors.Is(err, errModeUnknown) || d.Mode == modeHosting { t.Fatalf("unreadable launch: %+v %v", d, err) } f = enrolledFacts() f.relaunchWhy, f.relaunchAsked = "never asked", new(bool) if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeHosting || *f.relaunchAsked { t.Fatalf("an operator-enrolled box: %+v %v, asked %v", d, err, *f.relaunchAsked) } } func TestRelaunchedComparesTheLaunchTheRosterBinds(t *testing.T) { record := func(pending string) (boxid.Record, []byte) { doc := `{"accountId":"123","region":"eu-west-1","instanceId":"i-ada"` + pending + `}` rec := boxid.Record{V: 1, Box: "i-ada", IdentityDocument: base64.StdEncoding.EncodeToString([]byte(doc)), IdentitySignature: "c2ln"} raw, _ := json.Marshal(rec) return rec, raw } enrolledRec, enrolled := record(`,"pendingTime":"2026-10-17T09:00:00Z"`) row := hostactors.Box{EvidenceSHA256: hostfloor.Digest(enrolled), Group: "routers-ie"} if why := relaunched(row, enrolled, enrolledRec); why != "" { t.Fatalf("the same launch: %q", why) } again, _ := record(`,"pendingTime":"2026-10-17T11:30:00Z"`) if why := relaunched(row, enrolled, again); !strings.Contains(why, "stopped and started") { t.Fatalf("started again: %q", why) } none, _ := record("") if why := relaunched(row, enrolled, none); why == "" { t.Fatal("a document with no pendingTime matched") } _, other := record(`,"pendingTime":"2026-10-17T09:00:00Z","x":1`) if why := relaunched(row, other, enrolledRec); !strings.Contains(why, "not the one it was enrolled on") { t.Fatalf("another record: %q", why) } } // Condition 3 of census ruling region-ha-2b: the group marker and a group // box's CARLOS_REGION come from the parameter store, and a value that // disagrees with the signed roster holds the box instead of hosting it. func TestSelectModeHoldsAStoreValueTheRosterDisagreesWith(t *testing.T) { grouped := func() joinFacts { f := enrolledFacts() b := f.roster.Boxes["ada-hiking"] b.Group, b.Region = "routers-ie", "ie" f.roster.Boxes["ada-hiking"] = b f.marked, f.region = true, "ie" return f } if d, err := selectMode(context.Background(), grouped().facts()); err != nil || d.Mode != modeHosting { t.Fatalf("agreeing: %+v %v", d, err) } for name, spoil := range map[string]func(*joinFacts){ "marked, enrolled by hand": func(f *joinFacts) { b := f.roster.Boxes["ada-hiking"] b.Group = "" f.roster.Boxes["ada-hiking"] = b }, "enrolled from a group, not marked": func(f *joinFacts) { f.marked = false }, "another region": func(f *joinFacts) { f.region = "uk" }, "no region": func(f *joinFacts) { f.region = "" }, } { t.Run(name, func(t *testing.T) { f := grouped() spoil(&f) d, err := selectMode(context.Background(), f.facts()) if err != nil || d.Mode != modeJoining || d.Stage != stageHeld || !strings.Contains(d.Why, "does not host") { t.Fatalf("%+v %v", d, err) } }) } // A box an operator enrolled keeps its own region, unchecked. f := enrolledFacts() f.region = "anything" if d, err := selectMode(context.Background(), f.facts()); err != nil || d.Mode != modeHosting { t.Fatalf("an operator-enrolled box: %+v %v", d, err) } }