iop/packages/config/config_test.go
toki d764adb390 chore: agent-task 변경 사항 반영
- edge node store 및 console 업데이트
- node adapters(cli, ollama, vllm, mock) 리팩토링
- node router, run_manager, store 개선
- proto 파일(control.proto, runtime.proto) 및 생성 코드 업데이트
- config 업데이트
2026-05-10 20:41:33 +09:00

426 lines
11 KiB
Go

package config_test
import (
"os"
"path/filepath"
"strings"
"testing"
"iop/packages/config"
)
func TestLoadEdge_EdgeIdentity(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := "server:\n listen: \"0.0.0.0:9090\"\nedge:\n id: \"edge-dgx-group\"\n name: \"DGX Group\"\n"
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Edge.ID != "edge-dgx-group" {
t.Fatalf("expected edge.id=%q, got %q", "edge-dgx-group", cfg.Edge.ID)
}
if cfg.Edge.Name != "DGX Group" {
t.Fatalf("expected edge.name=%q, got %q", "DGX Group", cfg.Edge.Name)
}
}
func TestLoadEdge_EdgeIdentityEmptyByDefault(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
if err := os.WriteFile(f, []byte("server:\n listen: \"0.0.0.0:9090\"\n"), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Edge.ID != "" || cfg.Edge.Name != "" {
t.Fatalf("expected empty edge identity by default, got id=%q name=%q", cfg.Edge.ID, cfg.Edge.Name)
}
}
func TestLoadEdge_ConsoleTimeoutDefault(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
if err := os.WriteFile(f, []byte("server:\n listen: \"0.0.0.0:9090\"\n"), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Console.TimeoutSec != 120 {
t.Fatalf("expected default timeout_sec=120, got %d", cfg.Console.TimeoutSec)
}
if cfg.Console.Adapter != "cli" {
t.Fatalf("expected default console.adapter=%q, got %q", "cli", cfg.Console.Adapter)
}
if cfg.Console.ResolveAgent() != "claude" {
t.Fatalf("expected default console.agent=%q, got %q", "claude", cfg.Console.ResolveAgent())
}
}
func TestLoadEdge_ConsoleSessionDefaults(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
if err := os.WriteFile(f, []byte("server:\n listen: \"0.0.0.0:9090\"\n"), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Console.SessionID != "default" {
t.Fatalf("expected default session_id=%q, got %q", "default", cfg.Console.SessionID)
}
if cfg.Console.Background {
t.Fatal("expected default background=false")
}
}
func TestLoadEdge_ConsoleSessionOverride(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := "server:\n listen: \"0.0.0.0:9090\"\nconsole:\n session_id: \"worker-1\"\n background: true\n"
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Console.SessionID != "worker-1" {
t.Fatalf("expected session_id=%q, got %q", "worker-1", cfg.Console.SessionID)
}
if !cfg.Console.Background {
t.Fatal("expected background=true")
}
}
func TestLoadEdge_ConsoleTimeoutOverride(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := "server:\n listen: \"0.0.0.0:9090\"\nconsole:\n timeout_sec: 45\n"
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Console.TimeoutSec != 45 {
t.Fatalf("expected timeout_sec=45, got %d", cfg.Console.TimeoutSec)
}
}
func TestLoadEdge_ConsoleAgentFallbackFromLegacyModel(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := "server:\n listen: \"0.0.0.0:9090\"\nconsole:\n model: \"codex\"\n"
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if cfg.Console.ResolveAgent() != "codex" {
t.Fatalf("expected legacy console.model fallback to resolve to %q, got %q", "codex", cfg.Console.ResolveAgent())
}
}
func TestLoadEdge_CodexProfile(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "local"
adapters:
cli:
enabled: true
profiles:
codex:
command: "codex"
output_format: "codex-json"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.Nodes) == 0 {
t.Fatal("expected 1 node")
}
codex, ok := cfg.Nodes[0].Adapters.CLI.Profiles["codex"]
if !ok {
t.Fatal("expected codex profile")
}
if codex.Command != "codex" || codex.OutputFormat != "codex-json" {
t.Errorf("unexpected codex profile: %+v", codex)
}
}
func TestCLIProfileConf_CompletionMarkerUnmarshal(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "local"
adapters:
cli:
enabled: true
profiles:
marker-test:
command: "mycli"
completion_marker:
line: "<<END_OF_RESPONSE>>"
regex: "^DONE \\d+$"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.Nodes) == 0 {
t.Fatal("expected 1 node")
}
profile, ok := cfg.Nodes[0].Adapters.CLI.Profiles["marker-test"]
if !ok {
t.Fatal("expected marker-test profile")
}
if profile.CompletionMarker.Line != "<<END_OF_RESPONSE>>" {
t.Errorf("expected Line=%q, got %q", "<<END_OF_RESPONSE>>", profile.CompletionMarker.Line)
}
if profile.CompletionMarker.Regex != "^DONE \\d+$" {
t.Errorf("expected Regex=%q, got %q", "^DONE \\d+$", profile.CompletionMarker.Regex)
}
}
func TestCompletionMarkerConf_Empty(t *testing.T) {
var empty config.CompletionMarkerConf
if !empty.Empty() {
t.Fatal("expected Empty() == true for zero value")
}
lineOnly := config.CompletionMarkerConf{Line: "<<END>>"}
if lineOnly.Empty() {
t.Fatal("expected Empty() == false when Line is set")
}
regexOnly := config.CompletionMarkerConf{Regex: "^END$"}
if regexOnly.Empty() {
t.Fatal("expected Empty() == false when Regex is set")
}
bothSet := config.CompletionMarkerConf{Line: "<<END>>", Regex: "^END$"}
if bothSet.Empty() {
t.Fatal("expected Empty() == false when both are set")
}
}
func TestCompletionMarkerConf_LineOnlyUnmarshal(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "local"
adapters:
cli:
enabled: true
profiles:
line-only:
command: "mycli"
completion_marker:
line: "<<END>>"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
profile, ok := cfg.Nodes[0].Adapters.CLI.Profiles["line-only"]
if !ok {
t.Fatal("expected line-only profile")
}
if profile.CompletionMarker.Line != "<<END>>" {
t.Errorf("expected Line=%q, got %q", "<<END>>", profile.CompletionMarker.Line)
}
if profile.CompletionMarker.Regex != "" {
t.Errorf("expected empty Regex, got %q", profile.CompletionMarker.Regex)
}
if profile.CompletionMarker.Empty() {
t.Fatal("expected Empty() == false when only Line is set")
}
}
func TestCompletionMarkerConf_RegexOnlyUnmarshal(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "local"
adapters:
cli:
enabled: true
profiles:
regex-only:
command: "mycli"
completion_marker:
regex: "^DONE \\d+$"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
profile, ok := cfg.Nodes[0].Adapters.CLI.Profiles["regex-only"]
if !ok {
t.Fatal("expected regex-only profile")
}
if profile.CompletionMarker.Regex != "^DONE \\d+$" {
t.Errorf("expected Regex=%q, got %q", "^DONE \\d+$", profile.CompletionMarker.Regex)
}
if profile.CompletionMarker.Line != "" {
t.Errorf("expected empty Line, got %q", profile.CompletionMarker.Line)
}
if profile.CompletionMarker.Empty() {
t.Fatal("expected Empty() == false when only Regex is set")
}
}
func TestLoadEdge_CLIProfileMode(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "local"
adapters:
cli:
enabled: true
profiles:
codex:
command: "codex"
args:
- "exec"
- "--json"
resume_args:
- "exec"
- "resume"
- "--json"
mode: "codex-exec"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cfg.Nodes) == 0 {
t.Fatal("expected 1 node")
}
profile, ok := cfg.Nodes[0].Adapters.CLI.Profiles["codex"]
if !ok {
t.Fatal("expected codex profile")
}
if profile.Mode != "codex-exec" {
t.Errorf("expected mode=%q, got %q", "codex-exec", profile.Mode)
}
expectedResume := []string{"exec", "resume", "--json"}
if len(profile.ResumeArgs) != len(expectedResume) {
t.Fatalf("expected %d resume_args, got %d: %v", len(expectedResume), len(profile.ResumeArgs), profile.ResumeArgs)
}
for i, v := range expectedResume {
if profile.ResumeArgs[i] != v {
t.Errorf("resume_args[%d]: expected %q, got %q", i, v, profile.ResumeArgs[i])
}
}
}
func TestCLIProfileConf_CompletionMarkerEmpty(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
yaml := `
server:
listen: "0.0.0.0:9090"
nodes:
- alias: "local"
adapters:
cli:
enabled: true
profiles:
no-marker:
command: "mycli"
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
cfg, err := config.LoadEdge(f)
if err != nil {
t.Fatalf("load: %v", err)
}
profile, ok := cfg.Nodes[0].Adapters.CLI.Profiles["no-marker"]
if !ok {
t.Fatal("expected no-marker profile")
}
if !profile.CompletionMarker.Empty() {
t.Fatal("expected Empty() == true when completion_marker is not specified")
}
}
func TestCompletionMarkerConf_RegexAndLineUsage(t *testing.T) {
lineOnly := config.CompletionMarkerConf{Line: "<<END>>"}
if !strings.Contains(lineOnly.Line, "<<END>>") {
t.Errorf("expected Line to contain <<END>>, got %q", lineOnly.Line)
}
regexOnly := config.CompletionMarkerConf{Regex: "^DONE \\d+$"}
if !strings.Contains(regexOnly.Regex, "DONE") {
t.Errorf("expected Regex to contain DONE, got %q", regexOnly.Regex)
}
both := config.CompletionMarkerConf{Line: "<<END>>", Regex: "^DONE \\d+$"}
if both.Line != "<<END>>" || both.Regex != "^DONE \\d+$" {
t.Errorf("expected both fields set, got %+v", both)
}
if both.Empty() {
t.Fatal("expected Empty() == false when both are set")
}
}