iop/apps/node/internal/router/router_test.go
toki 423264feb2 feat: openai-workspace-agent-execution-contract milestone plan & code review, node test fixes
- Update automation-runtime-bridge phase and milestone docs
- Add plan and code review for CLI CWD (G07) and session workspace isolation (G08)
- Fix node and router tests
2026-06-13 19:07:59 +09:00

208 lines
5.6 KiB
Go

package router
import (
"context"
"strings"
"testing"
"go.uber.org/zap"
"iop/apps/node/internal/adapters"
"iop/apps/node/internal/runtime"
)
type stubAdapter struct{ name string }
func (s *stubAdapter) Name() string { return s.name }
func (s *stubAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
return runtime.Capabilities{AdapterName: s.name}, nil
}
func (s *stubAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
return nil
}
func TestResolve_PreservesSessionFields(t *testing.T) {
reg := adapters.NewRegistry()
reg.Register(&stubAdapter{name: "cli"})
r := New(reg, zap.NewNop())
req := runtime.RunRequest{
RunID: "run-1",
Adapter: "cli",
Target: "codex",
SessionID: "session-a",
SessionMode: runtime.SessionModeRequireExisting,
Background: true,
Workspace: "/config/workspace/iop",
}
spec, err := r.Resolve(context.Background(), req)
if err != nil {
t.Fatalf("resolve: %v", err)
}
if spec.SessionID != req.SessionID {
t.Errorf("SessionID: got %q want %q", spec.SessionID, req.SessionID)
}
if spec.SessionMode != req.SessionMode {
t.Errorf("SessionMode: got %q want %q", spec.SessionMode, req.SessionMode)
}
if spec.Background != req.Background {
t.Errorf("Background: got %v want %v", spec.Background, req.Background)
}
if spec.Workspace != req.Workspace {
t.Errorf("Workspace: got %q want %q", spec.Workspace, req.Workspace)
}
}
func TestResolveAdapter_Found(t *testing.T) {
reg := adapters.NewRegistry()
expected := &stubAdapter{name: "cli"}
reg.Register(expected)
r := New(reg, zap.NewNop())
req := runtime.RunRequest{
RunID: "run-1",
Adapter: "cli",
Target: "codex",
}
spec, adapter, err := r.ResolveAdapter(context.Background(), req)
if err != nil {
t.Fatalf("ResolveAdapter: %v", err)
}
if spec.Adapter != "cli" {
t.Errorf("expected adapter name 'cli', got %q", spec.Adapter)
}
if adapter != expected {
t.Fatal("expected same adapter instance")
}
}
func TestResolveAdapter_NotFound(t *testing.T) {
reg := adapters.NewRegistry()
r := New(reg, zap.NewNop())
req := runtime.RunRequest{
RunID: "run-1",
Adapter: "nonexistent",
Target: "codex",
}
_, _, err := r.ResolveAdapter(context.Background(), req)
if err == nil {
t.Fatal("expected error for unregistered adapter")
}
if !strings.Contains(err.Error(), "not found") {
t.Fatalf("expected 'not found' in error, got %v", err)
}
}
func TestResolveAdapter_RequiresExplicitAdapter(t *testing.T) {
reg := adapters.NewRegistry()
reg.Register(&stubAdapter{name: "mock"})
r := New(reg, zap.NewNop())
req := runtime.RunRequest{
RunID: "run-1",
Target: "mock-echo",
}
_, _, err := r.ResolveAdapter(context.Background(), req)
if err == nil {
t.Fatal("expected error for empty adapter")
}
if !strings.Contains(err.Error(), "adapter is required") {
t.Fatalf("expected adapter required error, got %v", err)
}
}
func TestResolveAdapter_InstanceKeyLookup(t *testing.T) {
reg := adapters.NewRegistry()
local := &stubAdapter{name: "ollama@local"}
dgx := &stubAdapter{name: "ollama@dgx"}
reg.RegisterKeyed("ollama@local", "ollama", local)
reg.RegisterKeyed("ollama@dgx", "ollama", dgx)
r := New(reg, zap.NewNop())
spec, adapter, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
RunID: "run-1",
Adapter: "ollama@local",
Target: "llama3",
})
if err != nil {
t.Fatalf("ResolveAdapter: %v", err)
}
if spec.Adapter != "ollama@local" {
t.Errorf("spec.Adapter: got %q want %q", spec.Adapter, "ollama@local")
}
if adapter != local {
t.Fatal("expected ollama@local adapter instance")
}
spec2, adapter2, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
RunID: "run-2",
Adapter: "ollama@dgx",
Target: "mixtral",
})
if err != nil {
t.Fatalf("ResolveAdapter dgx: %v", err)
}
if spec2.Adapter != "ollama@dgx" {
t.Errorf("spec2.Adapter: got %q want %q", spec2.Adapter, "ollama@dgx")
}
if adapter2 != dgx {
t.Fatal("expected ollama@dgx adapter instance")
}
}
func TestResolveAdapter_AmbiguousLegacyLookup(t *testing.T) {
reg := adapters.NewRegistry()
reg.RegisterKeyed("ollama@local", "ollama", &stubAdapter{name: "ollama@local"})
reg.RegisterKeyed("ollama@dgx", "ollama", &stubAdapter{name: "ollama@dgx"})
r := New(reg, zap.NewNop())
_, _, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
RunID: "run-1",
Adapter: "ollama",
Target: "llama3",
})
if err == nil {
t.Fatal("expected error for ambiguous adapter type-name with multiple instances")
}
if !strings.Contains(err.Error(), "ambiguous") {
t.Errorf("expected 'ambiguous' in error, got %v", err)
}
}
func TestResolveAdapter_LegacyTypeName_SingleInstance(t *testing.T) {
reg := adapters.NewRegistry()
single := &stubAdapter{name: "ollama@prod"}
reg.RegisterKeyed("ollama@prod", "ollama", single)
r := New(reg, zap.NewNop())
spec, adapter, err := r.ResolveAdapter(context.Background(), runtime.RunRequest{
RunID: "run-1",
Adapter: "ollama",
Target: "llama3",
})
if err != nil {
t.Fatalf("ResolveAdapter legacy single: %v", err)
}
if spec.Adapter != "ollama" {
t.Errorf("spec.Adapter: got %q want %q", spec.Adapter, "ollama")
}
if adapter != single {
t.Fatal("expected the single ollama instance")
}
}
func TestGetAdapter_AmbiguousReturnsNotFound(t *testing.T) {
reg := adapters.NewRegistry()
reg.RegisterKeyed("ollama@a", "ollama", &stubAdapter{name: "ollama@a"})
reg.RegisterKeyed("ollama@b", "ollama", &stubAdapter{name: "ollama@b"})
r := New(reg, zap.NewNop())
_, ok := r.GetAdapter("ollama")
if ok {
t.Fatal("expected GetAdapter to return false for ambiguous type name")
}
}