터미널 세션 core와 CLI mode executor 경계를 분리해 후속 remote terminal bridge가 재사용할 내부 실행 기반을 만든다. adapter 기본값과 vLLM experimental surface도 명시해 production 실행 경로가 mock fallback이나 미구현 실행으로 숨지 않게 한다.
263 lines
7.5 KiB
Go
263 lines
7.5 KiB
Go
package adapters_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"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_EmptyPayloadRegistersNoAdapters(t *testing.T) {
|
|
reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{}, zap.NewNop())
|
|
if err != nil {
|
|
t.Fatalf("build from payload: %v", err)
|
|
}
|
|
if got := len(reg.All()); got != 0 {
|
|
t.Fatalf("expected no adapters, got %d", got)
|
|
}
|
|
if _, ok := reg.Get("mock"); ok {
|
|
t.Fatal("mock adapter must not be registered implicitly")
|
|
}
|
|
}
|
|
|
|
func TestBuildFromPayload_ExplicitMockEnabled(t *testing.T) {
|
|
reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "mock", Enabled: true},
|
|
},
|
|
}, zap.NewNop())
|
|
if err != nil {
|
|
t.Fatalf("build from payload: %v", err)
|
|
}
|
|
if _, ok := reg.Get("mock"); !ok {
|
|
t.Fatal("expected explicit mock adapter to be registered")
|
|
}
|
|
}
|
|
|
|
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("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: "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{"ollama", "cli"} {
|
|
if _, ok := reg.Get(name); !ok {
|
|
t.Fatalf("expected %s adapter to be registered", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildFromPayload_VllmEnabledRejected(t *testing.T) {
|
|
_, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "vllm", Enabled: true, Config: &iop.AdapterConfig_Vllm{Vllm: &iop.VllmAdapterConfig{Endpoint: "http://localhost:8000"}}},
|
|
},
|
|
}, zap.NewNop())
|
|
if err == nil {
|
|
t.Fatal("expected vllm disabled error")
|
|
}
|
|
if !strings.Contains(err.Error(), "vllm adapter is experimental and disabled") {
|
|
t.Fatalf("expected vllm disabled error, got %v", err)
|
|
}
|
|
}
|
|
|
|
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{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "mock", Enabled: true},
|
|
},
|
|
}, 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)
|
|
}
|
|
}
|