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")
}
}