259 lines
6.9 KiB
Go
259 lines
6.9 KiB
Go
package configrefresh_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"iop/apps/edge/internal/configrefresh"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
// TestClassifyNodeRuntimeConcurrencyApplied verifies S15: node runtime
|
|
// concurrency is applied live.
|
|
func TestClassifyNodeRuntimeConcurrencyApplied(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
currentYAML := `
|
|
server:
|
|
listen: "0.0.0.0:9090"
|
|
nodes:
|
|
- id: "node-1"
|
|
alias: "n1"
|
|
token: "tok-1"
|
|
adapters:
|
|
cli:
|
|
enabled: true
|
|
runtime:
|
|
concurrency: 2
|
|
`
|
|
currentPath := writeYAML(t, dir, "current.yaml", currentYAML)
|
|
|
|
concurrencyOnlyYAML := `
|
|
server:
|
|
listen: "0.0.0.0:9090"
|
|
nodes:
|
|
- id: "node-1"
|
|
alias: "n1"
|
|
token: "tok-1"
|
|
adapters:
|
|
cli:
|
|
enabled: true
|
|
runtime:
|
|
concurrency: 4
|
|
`
|
|
concurrencyOnlyPath := writeYAML(t, dir, "concurrency.yaml", concurrencyOnlyYAML)
|
|
|
|
current := buildNormalizedCurrent(t, currentPath)
|
|
ctx := context.Background()
|
|
|
|
// concurrency-only change → applied.
|
|
result, _, err := configrefresh.Evaluate(ctx, current, configrefresh.Request{
|
|
Mode: configrefresh.ModeDryRun,
|
|
ConfigPath: concurrencyOnlyPath,
|
|
RequestID: "test-s15-conc",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Evaluate concurrency: %v", err)
|
|
}
|
|
if result.Status != configrefresh.StatusApplied {
|
|
t.Fatalf("expected concurrency change status=applied, got %q (summary: %s)", result.Status, result.Summary)
|
|
}
|
|
foundConc := false
|
|
for _, c := range result.Changes {
|
|
if c.Path == `nodes["node-1"].runtime.concurrency` {
|
|
foundConc = true
|
|
if c.Class != configrefresh.StatusApplied {
|
|
t.Errorf("expected concurrency class=applied, got %q", c.Class)
|
|
}
|
|
}
|
|
if c.Class == configrefresh.StatusRestartRequired {
|
|
t.Errorf("unexpected restart_required change for concurrency-only diff: %s", c.Path)
|
|
}
|
|
}
|
|
if !foundConc {
|
|
t.Errorf("concurrency change path not found in: %+v", result.Changes)
|
|
}
|
|
}
|
|
|
|
func TestClassifyModelTokenCounterApplied(t *testing.T) {
|
|
current := &config.EdgeConfig{Models: []config.ModelCatalogEntry{{
|
|
ID: "model-a", TokenCounter: &config.TokenCounterConf{Mode: config.TokenCounterDeterministic},
|
|
}}}
|
|
candidate := &config.EdgeConfig{Models: []config.ModelCatalogEntry{{
|
|
ID: "model-a", TokenCounter: &config.TokenCounterConf{Mode: config.TokenCounterEstimate, Per1kInput: 250},
|
|
}}}
|
|
result := configrefresh.Classify(current, candidate)
|
|
if result.Status != configrefresh.StatusApplied {
|
|
t.Fatalf("status = %q, want applied; changes=%+v", result.Status, result.Changes)
|
|
}
|
|
wantPath := `models["model-a"].token_counter`
|
|
for _, change := range result.Changes {
|
|
if change.Path == wantPath && change.Class == configrefresh.StatusApplied {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("token_counter applied path missing: %+v", result.Changes)
|
|
}
|
|
|
|
func TestClassifyModelCatalogProviderMappingApplied(t *testing.T) {
|
|
dir := t.TempDir()
|
|
currentYAML := `
|
|
server:
|
|
listen: "0.0.0.0:9090"
|
|
nodes:
|
|
- id: "node-1"
|
|
alias: "n1"
|
|
token: "tok-1"
|
|
adapters:
|
|
cli:
|
|
enabled: true
|
|
providers:
|
|
- id: "prov-a"
|
|
type: "ollama"
|
|
category: "local_inference"
|
|
adapter: "cli"
|
|
models: ["llama3.1"]
|
|
capacity: 2
|
|
- id: "prov-b"
|
|
type: "ollama"
|
|
category: "local_inference"
|
|
adapter: "cli"
|
|
models: ["llama3.1"]
|
|
capacity: 2
|
|
models:
|
|
- id: "qwen3.6:35b"
|
|
display_name: "Qwen"
|
|
context_window_tokens: 131072
|
|
default_max_tokens: 8192
|
|
min_max_tokens: 8192
|
|
providers:
|
|
prov-a: "llama3.1"
|
|
`
|
|
candidateYAML := `
|
|
server:
|
|
listen: "0.0.0.0:9090"
|
|
nodes:
|
|
- id: "node-1"
|
|
alias: "n1"
|
|
token: "tok-1"
|
|
adapters:
|
|
cli:
|
|
enabled: true
|
|
providers:
|
|
- id: "prov-a"
|
|
type: "ollama"
|
|
category: "local_inference"
|
|
adapter: "cli"
|
|
models: ["llama3.1"]
|
|
capacity: 2
|
|
- id: "prov-b"
|
|
type: "ollama"
|
|
category: "local_inference"
|
|
adapter: "cli"
|
|
models: ["llama3.1"]
|
|
capacity: 2
|
|
models:
|
|
- id: "qwen3.6:35b"
|
|
display_name: "Qwen"
|
|
context_window_tokens: 262144
|
|
default_max_tokens: 32768
|
|
min_max_tokens: 32768
|
|
default_thinking_token_budget: 8192
|
|
providers:
|
|
prov-b: "llama3.1"
|
|
`
|
|
currentPath := writeYAML(t, dir, "current.yaml", currentYAML)
|
|
candidatePath := writeYAML(t, dir, "candidate.yaml", candidateYAML)
|
|
|
|
current := buildNormalizedCurrent(t, currentPath)
|
|
|
|
result, _, err := configrefresh.Evaluate(context.Background(), current, configrefresh.Request{
|
|
Mode: configrefresh.ModeDryRun,
|
|
ConfigPath: candidatePath,
|
|
RequestID: "test-model-catalog",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Evaluate: %v", err)
|
|
}
|
|
if result.Status != configrefresh.StatusApplied {
|
|
t.Fatalf("expected status=%q, got %q changes=%+v", configrefresh.StatusApplied, result.Status, result.Changes)
|
|
}
|
|
found := map[string]bool{}
|
|
for _, change := range result.Changes {
|
|
if change.Class == configrefresh.StatusApplied {
|
|
found[change.Path] = true
|
|
}
|
|
}
|
|
for _, path := range []string{
|
|
`models["qwen3.6:35b"].context_window_tokens`,
|
|
`models["qwen3.6:35b"].default_max_tokens`,
|
|
`models["qwen3.6:35b"].min_max_tokens`,
|
|
`models["qwen3.6:35b"].default_thinking_token_budget`,
|
|
`models["qwen3.6:35b"].providers`,
|
|
} {
|
|
if !found[path] {
|
|
t.Fatalf("model catalog change %s not found in %+v", path, result.Changes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClassifyLongContextThresholdApplied(t *testing.T) {
|
|
dir := t.TempDir()
|
|
currentYAML := `
|
|
server:
|
|
listen: "0.0.0.0:9090"
|
|
long_context_threshold_tokens: 100000
|
|
nodes:
|
|
- id: "node-1"
|
|
alias: "n1"
|
|
token: "tok-1"
|
|
adapters:
|
|
cli:
|
|
enabled: true
|
|
`
|
|
candidateYAML := `
|
|
server:
|
|
listen: "0.0.0.0:9090"
|
|
long_context_threshold_tokens: 120000
|
|
nodes:
|
|
- id: "node-1"
|
|
alias: "n1"
|
|
token: "tok-1"
|
|
adapters:
|
|
cli:
|
|
enabled: true
|
|
`
|
|
currentPath := writeYAML(t, dir, "current.yaml", currentYAML)
|
|
candidatePath := writeYAML(t, dir, "candidate.yaml", candidateYAML)
|
|
current := buildNormalizedCurrent(t, currentPath)
|
|
|
|
result, _, err := configrefresh.Evaluate(context.Background(), current, configrefresh.Request{
|
|
Mode: configrefresh.ModeDryRun,
|
|
ConfigPath: candidatePath,
|
|
RequestID: "test-long-threshold",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Evaluate: %v", err)
|
|
}
|
|
if result.Status != configrefresh.StatusApplied {
|
|
t.Fatalf("expected status=%q, got %q changes=%+v", configrefresh.StatusApplied, result.Status, result.Changes)
|
|
}
|
|
found := false
|
|
for _, change := range result.Changes {
|
|
if change.Path == "long_context_threshold_tokens" {
|
|
found = true
|
|
if change.Class != configrefresh.StatusApplied {
|
|
t.Errorf("threshold class: got %q, want applied", change.Class)
|
|
}
|
|
if change.Previous != "100000" || change.Next != "120000" {
|
|
t.Errorf("threshold diff: got %s→%s", change.Previous, change.Next)
|
|
}
|
|
}
|
|
if change.Class == configrefresh.StatusRestartRequired {
|
|
t.Errorf("unexpected restart_required threshold change: %s", change.Path)
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("long_context_threshold_tokens change not found in %+v", result.Changes)
|
|
}
|
|
}
|