package adapters_test import ( "context" "fmt" "testing" "go.uber.org/zap" "iop/apps/node/internal/adapters" noderuntime "iop/apps/node/internal/runtime" iop "iop/proto/gen/iop" ) // lifecycleAdapter records Start/Stop calls. type lifecycleAdapter struct { name string started []string stopped []string log *[]string } func (a *lifecycleAdapter) Name() string { return a.name } func (a *lifecycleAdapter) Capabilities(_ context.Context) (noderuntime.Capabilities, error) { return noderuntime.Capabilities{AdapterName: a.name}, nil } func (a *lifecycleAdapter) Execute(_ context.Context, _ noderuntime.ExecutionSpec, _ noderuntime.EventSink) error { return nil } func (a *lifecycleAdapter) Start(_ context.Context) error { *a.log = append(*a.log, "start:"+a.name) return nil } func (a *lifecycleAdapter) Stop(_ context.Context) error { *a.log = append(*a.log, "stop:"+a.name) return nil } func TestRegistryLifecycle_StartStopOrder(t *testing.T) { log := []string{} reg := adapters.NewRegistry() reg.Register(&lifecycleAdapter{name: "first", log: &log}) reg.Register(&lifecycleAdapter{name: "second", log: &log}) ctx := context.Background() if err := reg.Start(ctx); err != nil { t.Fatalf("Start: %v", err) } if err := reg.Stop(ctx); err != nil { t.Fatalf("Stop: %v", err) } want := []string{"start:first", "start:second", "stop:second", "stop:first"} if len(log) != len(want) { t.Fatalf("expected %v, got %v", want, log) } for i, got := range log { if got != want[i] { t.Fatalf("log[%d]: want %q, got %q", i, want[i], got) } } } // failingLifecycleAdapter starts successfully only once then always errors. type failingLifecycleAdapter struct { name string log *[]string } func (a *failingLifecycleAdapter) Name() string { return a.name } func (a *failingLifecycleAdapter) Capabilities(_ context.Context) (noderuntime.Capabilities, error) { return noderuntime.Capabilities{AdapterName: a.name}, nil } func (a *failingLifecycleAdapter) Execute(_ context.Context, _ noderuntime.ExecutionSpec, _ noderuntime.EventSink) error { return nil } func (a *failingLifecycleAdapter) Start(_ context.Context) error { *a.log = append(*a.log, "start:"+a.name) return fmt.Errorf("start failed: %s", a.name) } func (a *failingLifecycleAdapter) Stop(_ context.Context) error { *a.log = append(*a.log, "stop:"+a.name) return nil } func TestRegistryLifecycle_StartFailureStopsStartedAdapters(t *testing.T) { log := []string{} reg := adapters.NewRegistry() reg.Register(&lifecycleAdapter{name: "first", log: &log}) reg.Register(&failingLifecycleAdapter{name: "second", log: &log}) ctx := context.Background() if err := reg.Start(ctx); err == nil { t.Fatal("expected Start to return error") } want := []string{"start:first", "start:second", "stop:first"} if len(log) != len(want) { t.Fatalf("expected log %v, got %v", want, log) } for i, got := range log { if got != want[i] { t.Fatalf("log[%d]: want %q, got %q", i, want[i], got) } } } // failingStopAdapter stops successfully once then always errors. type failingStopAdapter struct { name string log *[]string stopErr error } func (a *failingStopAdapter) Name() string { return a.name } func (a *failingStopAdapter) Capabilities(_ context.Context) (noderuntime.Capabilities, error) { return noderuntime.Capabilities{AdapterName: a.name}, nil } func (a *failingStopAdapter) Execute(_ context.Context, _ noderuntime.ExecutionSpec, _ noderuntime.EventSink) error { return nil } func (a *failingStopAdapter) Start(_ context.Context) error { *a.log = append(*a.log, "start:"+a.name) return nil } func (a *failingStopAdapter) Stop(_ context.Context) error { *a.log = append(*a.log, "stop:"+a.name) return a.stopErr } func TestRegistryLifecycle_StopContinuesOnFailingAdapter(t *testing.T) { log := []string{} reg := adapters.NewRegistry() // first stops successfully, second fails reg.Register(&lifecycleAdapter{name: "first", log: &log}) reg.Register(&failingStopAdapter{name: "second", log: &log, stopErr: fmt.Errorf("stop failed: second")}) ctx := context.Background() if err := reg.Start(ctx); err != nil { t.Fatalf("Start: %v", err) } err := reg.Stop(ctx) // Should return error because second adapter fails if err == nil { t.Fatal("expected non-nil error from Stop") } // Both adapters should have been stopped regardless of error want := []string{"start:first", "start:second", "stop:second", "stop:first"} if len(log) != len(want) { t.Fatalf("expected log %v, got %v", want, log) } for i, got := range log { if got != want[i] { t.Fatalf("log[%d]: want %q, got %q", i, want[i], got) } } } func TestRegistryLifecycle_NonLifecycleAdapterSkipped(t *testing.T) { reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{}, zap.NewNop()) if err != nil { t.Fatalf("build: %v", err) } ctx := context.Background() // mock adapter has no lifecycle — should not panic or error if err := reg.Start(ctx); err != nil { t.Fatalf("Start: %v", err) } if err := reg.Stop(ctx); err != nil { t.Fatalf("Stop: %v", err) } }