iop/packages/go/config/model_execution_preset_config_test.go

516 lines
14 KiB
Go

package config_test
import (
"os"
"path/filepath"
"strings"
"testing"
"iop/packages/go/config"
)
// TestLoadEdgeModelExecutionPresetOneOf covers the one-of admission rule for
// ModelCatalogEntry: exactly one of providers or execution_preset must be set,
// preset ids must resolve to an execution_presets[] entry, and existing
// provider-only fixtures must keep working unchanged.
func TestLoadEdgeModelExecutionPresetOneOf(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "edge.yaml")
// ---- Happy path: provider-only (existing behavior) ----
t.Run("provider-only entry loads unchanged", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "qwen3.6:35b"
providers:
vllm-gpu: "nvidia/Qwen3.6-35B"
nodes:
- id: "node-gpu-01"
providers:
- id: "vllm-gpu"
type: "vllm"
category: "api"
models:
- "nvidia/Qwen3.6-35B"
capacity: 4
`
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.Models) != 1 {
t.Fatalf("expected 1 model, got %d", len(cfg.Models))
}
if cfg.Models[0].ExecutionPreset != "" {
t.Errorf("provider-only entry should not have execution_preset set, got %q", cfg.Models[0].ExecutionPreset)
}
if len(cfg.Models[0].Providers) != 1 {
t.Errorf("expected 1 provider, got %d", len(cfg.Models[0].Providers))
}
})
// ---- Happy path: preset-only (virtual model) ----
t.Run("preset-only entry loads as virtual model", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "virtual-model"
execution_preset: "fast-path"
execution_presets:
- id: "fast-path"
selector:
model: "virtual-model"
allowed_modes:
- "direct"
routes:
direct:
stages: []
nodes:
- id: "node-01"
providers:
- id: "prov-a"
type: "ollama"
category: "local_inference"
models: ["model-a"]
capacity: 2
`
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.Models) != 1 {
t.Fatalf("expected 1 model, got %d", len(cfg.Models))
}
m := cfg.Models[0]
if m.ID != "virtual-model" {
t.Errorf("model id = %q, want virtual-model", m.ID)
}
if m.ExecutionPreset != "fast-path" {
t.Errorf("execution_preset = %q, want fast-path", m.ExecutionPreset)
}
if len(m.Providers) != 0 {
t.Errorf("virtual model should have empty providers, got %v", m.Providers)
}
})
// ---- Error: both providers and execution_preset set ----
t.Run("both providers and execution_preset rejected", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "confused-model"
execution_preset: "fast-path"
providers:
prov-a: "model-a"
execution_presets:
- id: "fast-path"
selector:
model: "model-a"
allowed_modes:
- "direct"
routes:
direct:
stages: []
nodes:
- id: "node-01"
providers:
- id: "prov-a"
type: "ollama"
category: "local_inference"
models: ["model-a"]
capacity: 2
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for both providers and execution_preset set")
}
if !strings.Contains(err.Error(), "exactly one of providers or execution_preset must be set") {
t.Fatalf("expected one-of error, got %v", err)
}
})
// ---- Error: neither providers nor execution_preset ----
t.Run("neither providers nor execution_preset rejected", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "empty-model"
nodes:
- id: "node-01"
providers:
- id: "prov-a"
type: "ollama"
category: "local_inference"
models: ["model-a"]
capacity: 2
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for neither providers nor execution_preset")
}
if !strings.Contains(err.Error(), "providers must not be empty") {
t.Fatalf("expected providers must not be empty error, got %v", err)
}
})
// ---- Error: dangling execution_preset id ----
t.Run("dangling execution_preset id rejected", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "dangling-model"
execution_preset: "non-existent-preset"
nodes:
- id: "node-01"
providers:
- id: "prov-a"
type: "ollama"
category: "local_inference"
models: ["model-a"]
capacity: 2
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for dangling execution_preset id")
}
if !strings.Contains(err.Error(), "execution_preset") && !strings.Contains(err.Error(), "does not match any execution_presets") {
t.Fatalf("expected dangling preset error, got %v", err)
}
})
// ---- Compatibility: mixed catalog with both provider-only and preset-only ----
t.Run("mixed catalog with provider-only and preset-only entries", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "qwen3.6:35b"
providers:
vllm-gpu: "nvidia/Qwen3.6-35B"
- id: "virtual-light"
execution_preset: "review-path"
execution_presets:
- id: "review-path"
selector:
model: "qwen3.6:35b"
allowed_modes:
- "light"
routes:
light:
stages:
- role: "local"
model: "qwen3.6:35b"
- role: "review"
model: "qwen3.6:35b"
workspace_tools:
- name: "ws1"
operations:
read:
tool_name: "cat"
schema_matcher:
type: "object"
argument_map:
path: "path"
result_matcher:
status: "ok"
write:
tool_name: "tee"
creates_parents: true
schema_matcher:
type: "object"
argument_map:
path: "path"
result_matcher:
status: "ok"
delete:
tool_name: "rm"
schema_matcher:
type: "object"
argument_map:
path: "path"
result_matcher:
status: "ok"
nodes:
- id: "node-gpu-01"
providers:
- id: "vllm-gpu"
type: "vllm"
category: "api"
models:
- "nvidia/Qwen3.6-35B"
capacity: 4
`
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.Models) != 2 {
t.Fatalf("expected 2 models, got %d", len(cfg.Models))
}
byID := map[string]config.ModelCatalogEntry{}
for _, m := range cfg.Models {
byID[m.ID] = m
}
// Provider-only entry should be unchanged.
provModel := byID["qwen3.6:35b"]
if len(provModel.Providers) != 1 {
t.Errorf("provider-only model should have 1 provider, got %d", len(provModel.Providers))
}
if provModel.ExecutionPreset != "" {
t.Errorf("provider-only model should not have execution_preset, got %q", provModel.ExecutionPreset)
}
// Virtual entry should reference the preset.
virtualModel := byID["virtual-light"]
if virtualModel.ExecutionPreset != "review-path" {
t.Errorf("virtual model execution_preset = %q, want review-path", virtualModel.ExecutionPreset)
}
if len(virtualModel.Providers) != 0 {
t.Errorf("virtual model should have empty providers, got %v", virtualModel.Providers)
}
})
// ---- Compatibility: provider-only fixture with budget must still validate ----
t.Run("provider-only entry with insufficient budget still rejected", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "qwen3.6:35b"
context_window_tokens: 262144
providers:
vllm-gpu: "nvidia/Qwen3.6-35B"
nodes:
- id: "node-gpu-01"
providers:
- id: "vllm-gpu"
type: "vllm"
category: "api"
models:
- "nvidia/Qwen3.6-35B"
capacity: 4
total_context_tokens: 262144
long_context_capacity: 2
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for insufficient long-context budget on provider-only entry")
}
if !strings.Contains(err.Error(), "total_context_tokens") {
t.Fatalf("expected budget error, got %v", err)
}
})
// ---- Edge: whitespace-only execution_preset treated as unset ----
t.Run("whitespace-only execution_preset treated as unset", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "whitespace-model"
execution_preset: " "
providers:
prov-a: "model-a"
nodes:
- id: "node-01"
providers:
- id: "prov-a"
type: "ollama"
category: "local_inference"
models: ["model-a"]
capacity: 2
`
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.Models) != 1 {
t.Fatalf("expected 1 model, got %d", len(cfg.Models))
}
// Whitespace-only preset should be treated as unset, so provider-only
// path should apply.
if cfg.Models[0].ExecutionPreset != "" {
t.Errorf("whitespace preset should be treated as unset, got %q", cfg.Models[0].ExecutionPreset)
}
})
// ---- Normalization: non-empty execution_preset is stored canonically ----
t.Run("non-empty execution_preset is normalized", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "qwen3.6:35b"
providers:
vllm-gpu: "nvidia/Qwen3.6-35B"
- id: "virtual-fast"
execution_preset: " fast-path "
execution_presets:
- id: "fast-path"
selector:
model: "qwen3.6:35b"
allowed_modes:
- "direct"
routes:
direct:
stages: []
nodes:
- id: "node-gpu-01"
providers:
- id: "vllm-gpu"
type: "vllm"
category: "api"
models:
- "nvidia/Qwen3.6-35B"
capacity: 4
`
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)
}
byID := map[string]config.ModelCatalogEntry{}
for _, m := range cfg.Models {
byID[m.ID] = m
}
// The padded valid preset id must be persisted in canonical (trimmed)
// form so exact downstream lookups match the admitted value.
virtual := byID["virtual-fast"]
if virtual.ExecutionPreset != "fast-path" {
t.Errorf("execution_preset = %q, want canonical %q", virtual.ExecutionPreset, "fast-path")
}
if len(virtual.Providers) != 0 {
t.Errorf("virtual model should have empty providers, got %v", virtual.Providers)
}
// Provider-only entry stays unchanged.
prov := byID["qwen3.6:35b"]
if prov.ExecutionPreset != "" {
t.Errorf("provider-only entry should not have execution_preset set, got %q", prov.ExecutionPreset)
}
if len(prov.Providers) != 1 {
t.Errorf("provider-only entry should have 1 provider, got %d", len(prov.Providers))
}
})
// ---- Error: empty execution_preset string with no providers ----
t.Run("explicit empty execution_preset with no providers rejected", func(t *testing.T) {
yaml := `
server:
listen: "0.0.0.0:9090"
models:
- id: "empty-preset-model"
execution_preset: ""
nodes:
- id: "node-01"
providers:
- id: "prov-a"
type: "ollama"
category: "local_inference"
models: ["model-a"]
capacity: 2
`
if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
_, err := config.LoadEdge(f)
if err == nil {
t.Fatal("expected error for empty execution_preset with no providers")
}
if !strings.Contains(err.Error(), "providers must not be empty") {
t.Fatalf("expected providers must not be empty error, got %v", err)
}
})
}
// TestModelCatalogEntry_ValidateVirtualEntryUnit covers unit-level Validate
// behavior for the one-of rule without going through LoadEdge.
func TestModelCatalogEntry_ValidateVirtualEntryUnit(t *testing.T) {
providerIDs := map[string]struct{}{
"vllm-gpu": {},
}
serveModels := map[string]map[string]struct{}{
"vllm-gpu": {"model-a": {}},
}
t.Run("provider-only validates", func(t *testing.T) {
e := config.ModelCatalogEntry{
ID: "model-a",
Providers: map[string]string{"vllm-gpu": "model-a"},
}
if err := e.Validate(providerIDs, serveModels); err != nil {
t.Fatalf("expected no error, got: %v", err)
}
})
t.Run("preset-only validates (returns nil, preset resolved later)", func(t *testing.T) {
e := config.ModelCatalogEntry{
ID: "virtual-model",
ExecutionPreset: "fast-path",
}
if err := e.Validate(providerIDs, serveModels); err != nil {
t.Fatalf("expected no error for preset-only, got: %v", err)
}
})
t.Run("both providers and execution_preset rejected", func(t *testing.T) {
e := config.ModelCatalogEntry{
ID: "bad-model",
Providers: map[string]string{"vllm-gpu": "model-a"},
ExecutionPreset: "fast-path",
}
if err := e.Validate(providerIDs, serveModels); err == nil {
t.Fatal("expected error for both set")
}
})
t.Run("neither providers nor execution_preset rejected", func(t *testing.T) {
e := config.ModelCatalogEntry{
ID: "empty-model",
}
if err := e.Validate(providerIDs, serveModels); err == nil {
t.Fatal("expected error for neither set")
}
})
t.Run("whitespace execution_preset treated as unset", func(t *testing.T) {
e := config.ModelCatalogEntry{
ID: "ws-model",
Providers: map[string]string{"vllm-gpu": "model-a"},
ExecutionPreset: " ",
}
if err := e.Validate(providerIDs, serveModels); err != nil {
t.Fatalf("expected no error (whitespace preset treated as unset), got: %v", err)
}
})
}