package health_test // This is the Phase 4 steward unwind canary. It verifies whether // steward.Manager invokes Destroy on already-initialized siblings when a // later component's Init returns an error. The lifecycle design in the // lethe-server task assumes it does; if not, an explicit unwind step has to // be added in main.go (Phase 9). // // The test is deliberately placed in package `health_test` (next to the // health package) so it can import steward without polluting the health // package and without introducing a brand-new package directory just for // the canary. See the Phase 4 plan, point 16. // // IMPORTANT: this test is allowed to fail. A failure here is the very signal // the dispatcher needs to plan an explicit unwind. Do NOT mark it Skip; do // NOT add a workaround in this phase. import ( "context" "errors" "testing" "go.bigb.es/auxilia/steward" ) // recordingService records whether Init and Destroy were called. It has no // dependencies, so it can sit anywhere in the start order. type recordingService struct { initCalled bool destroyCalled bool } func (r *recordingService) Init(_ context.Context) error { r.initCalled = true return nil } func (r *recordingService) Destroy(_ context.Context) error { r.destroyCalled = true return nil } // failingService errors out of Init. It is registered after recordingService // so that recordingService is already initialized at the point of failure. type failingService struct{} var errFailing = errors.New("failingService.Init: intentional failure") func (f *failingService) Init(_ context.Context) error { return errFailing } func TestStewardUnwindsOnInitFailure(t *testing.T) { rec := &recordingService{} fail := &failingService{} mgr := steward.NewManager() mgr.AddComponent(context.Background(), steward.MustServiceAsset(rec, steward.Root(), steward.IgnoreUnused()), steward.MustServiceAsset(fail, steward.Root(), steward.IgnoreUnused()), ) if err := mgr.Inject(context.Background()); err != nil { t.Fatalf("Inject: %v", err) } initErr := mgr.Init(context.Background()) if initErr == nil { t.Fatalf("expected Init to surface failingService error, got nil") } if !rec.initCalled { t.Fatalf("recordingService.Init was never called — registration order assumption broken") } if !rec.destroyCalled { // THE FINDING. Steward did not unwind. main.go must call Destroy on // every initialized sibling itself when Init fails. t.Fatalf("steward did NOT call recordingService.Destroy after sibling Init failed; explicit unwind required in main") } }