워크스페이스 기본 포트와 검증 문서를 실제 런타임 기본값에 맞추고, 브리지 경계에서 adapter config 타입 정보를 보존해야 이후 원격 smoke와 경계 안정화 작업이 같은 계약을 사용할 수 있다.
359 lines
9.5 KiB
Go
359 lines
9.5 KiB
Go
package adapters
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
func TestCLIConfFromProto_ProfileRuntimeOptions(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"claude": {
|
|
Command: "claude",
|
|
Args: []string{"-p"},
|
|
OutputFormat: "claude-json",
|
|
},
|
|
},
|
|
}
|
|
|
|
cfg := cliConfFromProto(m)
|
|
|
|
prof, ok := cfg.Profiles["claude"]
|
|
if !ok {
|
|
t.Fatal("expected claude profile")
|
|
}
|
|
if prof.Command != "claude" {
|
|
t.Fatalf("command: got %q", prof.Command)
|
|
}
|
|
if len(prof.Args) != 1 || prof.Args[0] != "-p" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if prof.OutputFormat != "claude-json" {
|
|
t.Fatalf("output_format: got %q", prof.OutputFormat)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_ClaudeHeadlessBypassProfile(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"claude": {
|
|
Command: "claude",
|
|
Args: []string{"-p", "--dangerously-skip-permissions"},
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["claude"]
|
|
if !ok {
|
|
t.Fatal("expected claude profile")
|
|
}
|
|
if len(prof.Args) != 2 || prof.Args[0] != "-p" || prof.Args[1] != "--dangerously-skip-permissions" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if prof.Persistent || prof.Terminal {
|
|
t.Fatalf("expected persistent/terminal=false")
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_AntigravityHeadlessProfile(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"antigravity": {
|
|
Command: "agy",
|
|
Args: []string{"--dangerously-skip-permissions", "--print"},
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["antigravity"]
|
|
if !ok {
|
|
t.Fatal("expected antigravity profile")
|
|
}
|
|
if len(prof.Args) != 2 || prof.Args[0] != "--dangerously-skip-permissions" || prof.Args[1] != "--print" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_ClineConfigProfile(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"cline-dgx": {
|
|
Command: "/config/.npm-global/bin/cline",
|
|
Args: []string{"-y", "--json", "--config", "/config/.cline/profiles/ollama-dgx"},
|
|
OutputFormat: "cline-json",
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["cline-dgx"]
|
|
if !ok {
|
|
t.Fatal("expected cline-dgx profile")
|
|
}
|
|
if prof.Command != "/config/.npm-global/bin/cline" {
|
|
t.Fatalf("command: got %q", prof.Command)
|
|
}
|
|
if len(prof.Args) != 4 {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if prof.OutputFormat != "cline-json" {
|
|
t.Fatalf("output_format: got %q", prof.OutputFormat)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_ProfileCompletionMarker(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"codex": {
|
|
Command: "codex",
|
|
Persistent: true,
|
|
CompletionMarker: &iop.CLICompletionMarker{
|
|
Line: "[[DONE]]",
|
|
Regex: `^\[\[DONE\]\]$`,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof := cfg.Profiles["codex"]
|
|
if prof.CompletionMarker.Line != "[[DONE]]" {
|
|
t.Fatalf("line: got %q", prof.CompletionMarker.Line)
|
|
}
|
|
if prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` {
|
|
t.Fatalf("regex: got %q", prof.CompletionMarker.Regex)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_PersistentAndIdleTimeouts(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"codex": {
|
|
Command: "codex",
|
|
Persistent: true,
|
|
ResponseIdleTimeoutMs: 1500,
|
|
StartupIdleTimeoutMs: 300,
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof := cfg.Profiles["codex"]
|
|
if !prof.Persistent {
|
|
t.Fatal("persistent")
|
|
}
|
|
if prof.ResponseIdleTimeoutMS != 1500 {
|
|
t.Fatalf("response_idle_timeout_ms: got %d", prof.ResponseIdleTimeoutMS)
|
|
}
|
|
if prof.StartupIdleTimeoutMS != 300 {
|
|
t.Fatalf("startup_idle_timeout_ms: got %d", prof.StartupIdleTimeoutMS)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_NilSettings(t *testing.T) {
|
|
cfg := cliConfFromProto(&iop.CLIAdapterConfig{})
|
|
if !cfg.Enabled {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if cfg.Profiles == nil {
|
|
t.Fatal("profiles map should be initialized")
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_OpencodeSSEMode(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"opencode": {
|
|
Command: "/usr/local/bin/opencode",
|
|
Args: []string{"--model", "m1", "--dangerously-skip-permissions"},
|
|
Mode: "opencode-sse",
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["opencode"]
|
|
if !ok {
|
|
t.Fatal("expected opencode profile")
|
|
}
|
|
if prof.Mode != "opencode-sse" {
|
|
t.Errorf("mode: got %q", prof.Mode)
|
|
}
|
|
if len(prof.Args) != 3 {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromStruct_OpencodeSSEModeLegacy(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{
|
|
"profiles": map[string]any{
|
|
"opencode": map[string]any{
|
|
"command": "/usr/local/bin/opencode",
|
|
"args": []any{"--model", "m1"},
|
|
"mode": "opencode-sse",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := cliConfFromStruct(st)
|
|
prof, ok := cfg.Profiles["opencode"]
|
|
if !ok {
|
|
t.Fatal("expected opencode profile")
|
|
}
|
|
if prof.Mode != "opencode-sse" {
|
|
t.Errorf("mode: got %q", prof.Mode)
|
|
}
|
|
}
|
|
|
|
// Legacy structpb fallback regression tests — these exercise the path taken
|
|
// when an older edge sends AdapterConfig.settings instead of the typed oneof.
|
|
|
|
func TestOllamaConfFromStruct_LegacyPayload(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{
|
|
"base_url": "http://legacy:11434",
|
|
"context_size": float64(262144),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := ollamaConfFromStruct(st)
|
|
if !cfg.Enabled {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if cfg.BaseURL != "http://legacy:11434" {
|
|
t.Fatalf("base_url: got %q", cfg.BaseURL)
|
|
}
|
|
if cfg.ContextSize != 262144 {
|
|
t.Fatalf("context_size: got %d", cfg.ContextSize)
|
|
}
|
|
}
|
|
|
|
func TestVllmConfFromStruct_LegacyPayload(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{"endpoint": "http://legacy:8000"})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := vllmConfFromStruct(st)
|
|
if !cfg.Enabled {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if cfg.Endpoint != "http://legacy:8000" {
|
|
t.Fatalf("endpoint: got %q", cfg.Endpoint)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromStruct_LegacyPayload(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{
|
|
"profiles": map[string]any{
|
|
"codex": map[string]any{
|
|
"command": "codex",
|
|
"args": []any{"--legacy"},
|
|
"env": []any{"K=V"},
|
|
"persistent": true,
|
|
"terminal": false,
|
|
"response_idle_timeout_ms": 1500,
|
|
"startup_idle_timeout_ms": 300,
|
|
"output_format": "codex-json",
|
|
"completion_marker": map[string]any{
|
|
"line": "[[DONE]]",
|
|
"regex": `^\[\[DONE\]\]$`,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := cliConfFromStruct(st)
|
|
prof, ok := cfg.Profiles["codex"]
|
|
if !ok {
|
|
t.Fatal("expected codex profile")
|
|
}
|
|
if prof.Command != "codex" {
|
|
t.Fatalf("command: got %q", prof.Command)
|
|
}
|
|
if len(prof.Args) != 1 || prof.Args[0] != "--legacy" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if len(prof.Env) != 1 || prof.Env[0] != "K=V" {
|
|
t.Fatalf("env: got %#v", prof.Env)
|
|
}
|
|
if !prof.Persistent {
|
|
t.Fatal("persistent")
|
|
}
|
|
if prof.ResponseIdleTimeoutMS != 1500 {
|
|
t.Fatalf("response_idle_timeout_ms: got %d", prof.ResponseIdleTimeoutMS)
|
|
}
|
|
if prof.StartupIdleTimeoutMS != 300 {
|
|
t.Fatalf("startup_idle_timeout_ms: got %d", prof.StartupIdleTimeoutMS)
|
|
}
|
|
if prof.OutputFormat != "codex-json" {
|
|
t.Fatalf("output_format: got %q", prof.OutputFormat)
|
|
}
|
|
if prof.CompletionMarker.Line != "[[DONE]]" || prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` {
|
|
t.Fatalf("completion_marker: got %+v", prof.CompletionMarker)
|
|
}
|
|
}
|
|
|
|
// End-to-end fallback through BuildFromPayload: oneof empty, settings populated.
|
|
func TestBuildFromPayload_LegacySettingsFallback(t *testing.T) {
|
|
ollamaSt, _ := structpb.NewStruct(map[string]any{"base_url": "http://legacy:11434"})
|
|
cliSt, _ := structpb.NewStruct(map[string]any{
|
|
"profiles": map[string]any{
|
|
"codex": map[string]any{
|
|
"command": "codex",
|
|
"args": []any{"--legacy"},
|
|
},
|
|
},
|
|
})
|
|
payload := &iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "ollama", Enabled: true, Settings: ollamaSt},
|
|
{Type: "cli", Enabled: true, Settings: cliSt},
|
|
},
|
|
}
|
|
reg, err := BuildFromPayload(payload, nil)
|
|
if err != nil {
|
|
t.Fatalf("build: %v", err)
|
|
}
|
|
for _, name := range []string{"ollama", "cli"} {
|
|
if _, ok := reg.Get(name); !ok {
|
|
t.Fatalf("expected %s adapter registered from legacy settings", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildFromPayload_LegacyVllmSettingsRejected(t *testing.T) {
|
|
vllmSt, _ := structpb.NewStruct(map[string]any{"endpoint": "http://legacy:8000"})
|
|
_, err := BuildFromPayload(&iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "vllm", Enabled: true, Settings: vllmSt},
|
|
},
|
|
}, nil)
|
|
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_TypedMockConfig(t *testing.T) {
|
|
reg, err := BuildFromPayload(&iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{
|
|
Type: "mock",
|
|
Enabled: true,
|
|
Config: &iop.AdapterConfig_Mock{Mock: &iop.MockAdapterConfig{}},
|
|
},
|
|
},
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatalf("build: %v", err)
|
|
}
|
|
if _, ok := reg.Get("mock"); !ok {
|
|
t.Fatal("expected mock adapter registered from typed MockAdapterConfig")
|
|
}
|
|
}
|