147 lines
5.5 KiB
Go
147 lines
5.5 KiB
Go
package configrefresh_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"iop/apps/edge/internal/configrefresh"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
func TestClassifyExecutionPresetLiveApply(t *testing.T) {
|
|
current := &config.EdgeConfig{
|
|
ExecutionPresets: []config.ExecutionPreset{
|
|
{
|
|
ID: "preset-z-remove",
|
|
Selector: config.ExecutionModelBinding{Model: "gpt-4o"},
|
|
AllowedModes: []string{config.ModeDirect},
|
|
Routes: map[string]config.ExecutionRoute{
|
|
config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
|
|
},
|
|
},
|
|
{
|
|
ID: "preset-m-mod",
|
|
Selector: config.ExecutionModelBinding{Model: "gpt-4o", Options: map[string]any{"a": 1}},
|
|
AllowedModes: []string{config.ModeDirect},
|
|
Routes: map[string]config.ExecutionRoute{
|
|
config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Candidate list has IDs out of lexical order (preset-a-add first, then preset-m-mod)
|
|
candidate := &config.EdgeConfig{
|
|
ExecutionPresets: []config.ExecutionPreset{
|
|
{
|
|
ID: "preset-a-add",
|
|
Selector: config.ExecutionModelBinding{Model: "claude-3-5-sonnet"},
|
|
AllowedModes: []string{config.ModeDirect},
|
|
Routes: map[string]config.ExecutionRoute{
|
|
config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
|
|
},
|
|
},
|
|
{
|
|
ID: "preset-m-mod",
|
|
Selector: config.ExecutionModelBinding{Model: "gpt-4o-mini", Options: map[string]any{"a": 2}},
|
|
AllowedModes: []string{config.ModeDirect, config.ModeLight},
|
|
Routes: map[string]config.ExecutionRoute{
|
|
config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
|
|
config.ModeLight: {
|
|
Stages: []config.ExecutionRouteStage{
|
|
{Role: "local", Model: "gpt-4o"},
|
|
{Role: "review", Model: "gpt-4o"},
|
|
},
|
|
},
|
|
},
|
|
WorkspaceTools: []config.ExecutionWorkspaceToolAlternative{
|
|
{
|
|
Name: "default",
|
|
Operations: map[string]config.ExecutionWorkspaceOperation{
|
|
"read": {ToolName: "file_read", SchemaMatcher: map[string]any{"sm": "r"}, ArgumentMap: map[string]any{"arg": "r"}, ResultMatcher: map[string]any{"res": "r"}},
|
|
"write": {ToolName: "file_write", CreatesParents: true, SchemaMatcher: map[string]any{"sm": "w"}, ArgumentMap: map[string]any{"arg": "w"}, ResultMatcher: map[string]any{"res": "w"}},
|
|
"delete": {ToolName: "file_delete", SchemaMatcher: map[string]any{"sm": "d"}, ArgumentMap: map[string]any{"arg": "d"}, ResultMatcher: map[string]any{"res": "d"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := configrefresh.Classify(current, candidate)
|
|
if result.Status != configrefresh.StatusApplied {
|
|
t.Fatalf("expected status=%q, got %q (changes: %+v)", configrefresh.StatusApplied, result.Status, result.Changes)
|
|
}
|
|
|
|
type expectedChange struct {
|
|
path string
|
|
class configrefresh.Status
|
|
}
|
|
|
|
want := []expectedChange{
|
|
{path: `execution_presets["preset-a-add"]`, class: configrefresh.StatusApplied},
|
|
{path: `execution_presets["preset-m-mod"].allowed_modes`, class: configrefresh.StatusApplied},
|
|
{path: `execution_presets["preset-m-mod"].routes`, class: configrefresh.StatusApplied},
|
|
{path: `execution_presets["preset-m-mod"].selector`, class: configrefresh.StatusApplied},
|
|
{path: `execution_presets["preset-m-mod"].workspace_tools`, class: configrefresh.StatusApplied},
|
|
{path: `execution_presets["preset-z-remove"]`, class: configrefresh.StatusApplied},
|
|
}
|
|
|
|
if len(result.Changes) != len(want) {
|
|
t.Fatalf("got %d changes, want %d (actual changes: %+v)", len(result.Changes), len(want), result.Changes)
|
|
}
|
|
|
|
for i, c := range result.Changes {
|
|
if c.Path != want[i].path {
|
|
t.Errorf("change[%d] path: got %q, want %q", i, c.Path, want[i].path)
|
|
}
|
|
if c.Class != want[i].class {
|
|
t.Errorf("change[%d] class for %s: got %q, want %q", i, c.Path, c.Class, want[i].class)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestClassifyModelExecutionPresetLiveApply verifies that changing a virtual
|
|
// model's execution_preset mapping is reported as a single live-applied change
|
|
// and attributes the model in ChangedModels.
|
|
func TestClassifyModelExecutionPresetLiveApply(t *testing.T) {
|
|
current := &config.EdgeConfig{
|
|
Models: []config.ModelCatalogEntry{
|
|
{ID: "virtual-model", ExecutionPreset: "preset-a"},
|
|
},
|
|
}
|
|
candidate := &config.EdgeConfig{
|
|
Models: []config.ModelCatalogEntry{
|
|
{ID: "virtual-model", ExecutionPreset: "preset-b"},
|
|
},
|
|
}
|
|
|
|
result := configrefresh.Classify(current, candidate)
|
|
|
|
if result.Status != configrefresh.StatusApplied {
|
|
t.Fatalf("expected status=%q, got %q (changes: %+v)", configrefresh.StatusApplied, result.Status, result.Changes)
|
|
}
|
|
if result.Summary != "all changes can be applied without restart" {
|
|
t.Errorf("summary = %q, want all-applied summary", result.Summary)
|
|
}
|
|
|
|
if len(result.Changes) != 1 {
|
|
t.Fatalf("got %d changes, want 1 (actual: %+v)", len(result.Changes), result.Changes)
|
|
}
|
|
change := result.Changes[0]
|
|
if change.Path != `models["virtual-model"].execution_preset` {
|
|
t.Errorf("change path: got %q, want %q", change.Path, `models["virtual-model"].execution_preset`)
|
|
}
|
|
if change.Class != configrefresh.StatusApplied {
|
|
t.Errorf("change class: got %q, want %q", change.Class, configrefresh.StatusApplied)
|
|
}
|
|
if change.Previous != "preset-a" {
|
|
t.Errorf("change previous: got %q, want %q", change.Previous, "preset-a")
|
|
}
|
|
if change.Next != "preset-b" {
|
|
t.Errorf("change next: got %q, want %q", change.Next, "preset-b")
|
|
}
|
|
|
|
if len(result.ChangedModels) != 1 || result.ChangedModels[0] != "virtual-model" {
|
|
t.Errorf("ChangedModels = %v, want [virtual-model]", result.ChangedModels)
|
|
}
|
|
}
|