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" ) // --- BuildFromPayload tests --- func TestBuildFromPayload_MockAlwaysPresent(t *testing.T) { reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{}, zap.NewNop()) if err != nil { t.Fatalf("build from payload: %v", err) } if _, ok := reg.Get("mock"); !ok { t.Fatal("expected mock adapter to be registered") } if got := len(reg.All()); got != 1 { t.Fatalf("expected 1 adapter, got %d", got) } } func TestBuildFromPayload_OllamaEnabled(t *testing.T) { reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ Adapters: []*iop.AdapterConfig{ { Type: "ollama", Enabled: true, Config: &iop.AdapterConfig_Ollama{Ollama: &iop.OllamaAdapterConfig{BaseUrl: "http://localhost:11434"}}, }, }, }, zap.NewNop()) if err != nil { t.Fatalf("build from payload: %v", err) } if _, ok := reg.Get("mock"); !ok { t.Fatal("expected mock adapter to be registered") } if _, ok := reg.Get("ollama"); !ok { t.Fatal("expected ollama adapter to be registered") } } func TestBuildFromPayload_MultipleAdapters(t *testing.T) { reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ Adapters: []*iop.AdapterConfig{ {Type: "ollama", Enabled: true, Config: &iop.AdapterConfig_Ollama{Ollama: &iop.OllamaAdapterConfig{BaseUrl: "x"}}}, {Type: "vllm", Enabled: true, Config: &iop.AdapterConfig_Vllm{Vllm: &iop.VllmAdapterConfig{Endpoint: "y"}}}, {Type: "cli", Enabled: true, Config: &iop.AdapterConfig_Cli{Cli: &iop.CLIAdapterConfig{ Profiles: map[string]*iop.CLIProfileConfig{ "codex": {Command: "codex", Persistent: true, ResponseIdleTimeoutMs: 1500, StartupIdleTimeoutMs: 300}, }, }}}, }, }, zap.NewNop()) if err != nil { t.Fatalf("build from payload: %v", err) } for _, name := range []string{"mock", "ollama", "vllm", "cli"} { if _, ok := reg.Get(name); !ok { t.Fatalf("expected %s adapter to be registered", name) } } } func TestBuildFromPayload_UnknownType(t *testing.T) { _, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ Adapters: []*iop.AdapterConfig{ {Type: "unknown", Enabled: true}, }, }, zap.NewNop()) if err == nil { t.Fatal("expected unknown adapter type error") } } // --- Registry lifecycle tests --- type lifecycleAdapter struct { name 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 } 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 } 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_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) } } } 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) } } } func TestRegistryLifecycle_StopContinuesOnFailingAdapter(t *testing.T) { log := []string{} reg := adapters.NewRegistry() 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) } if err := reg.Stop(ctx); err == nil { t.Fatal("expected non-nil error from Stop") } 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() if err := reg.Start(ctx); err != nil { t.Fatalf("Start: %v", err) } if err := reg.Stop(ctx); err != nil { t.Fatalf("Stop: %v", err) } }