179 lines
7.6 KiB
Go
179 lines
7.6 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"iop/apps/edge/internal/configrefresh"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
func TestRuntimeRefreshReplacesExecutionPresetGeneration(t *testing.T) {
|
|
initialPreset := config.ExecutionPreset{
|
|
ID: "preset-alpha",
|
|
Selector: config.ExecutionModelBinding{
|
|
Model: "gpt-4o",
|
|
Options: map[string]any{
|
|
"temp": 0.7,
|
|
"nested_map": map[string]string{"key1": "val1"},
|
|
"nested_slice": []string{"opt1", "opt2"},
|
|
},
|
|
},
|
|
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", Options: map[string]any{"stage_map": map[string]int{"a": 10}}},
|
|
{Role: "review", Model: "gpt-4o", Options: map[string]any{"stage_slice": []int{1, 2}}},
|
|
},
|
|
},
|
|
},
|
|
WorkspaceTools: []config.ExecutionWorkspaceToolAlternative{
|
|
{
|
|
Name: "default",
|
|
Operations: map[string]config.ExecutionWorkspaceOperation{
|
|
"read": {
|
|
ToolName: "file_read",
|
|
SchemaMatcher: map[string]any{"sm_map": map[string]bool{"read_ok": true}},
|
|
ArgumentMap: map[string]any{"arg_slice": []string{"path"}},
|
|
ResultMatcher: map[string]any{"res_map": map[string]any{"status": 200}},
|
|
},
|
|
"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"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
cfg := newTestConfig()
|
|
cfg.ExecutionPresets = []config.ExecutionPreset{initialPreset}
|
|
|
|
rt, err := NewRuntime(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewRuntime: %v", err)
|
|
}
|
|
|
|
// Immutability test: mutate input slice and nested map after setting
|
|
cfg.ExecutionPresets[0].ID = "mutated-preset"
|
|
cfg.ExecutionPresets[0].Selector.Options["temp"] = 1.9
|
|
cfg.ExecutionPresets[0].Selector.Options["nested_map"].(map[string]string)["key1"] = "mutated_val"
|
|
cfg.ExecutionPresets[0].Selector.Options["nested_slice"].([]string)[0] = "mutated_opt"
|
|
cfg.ExecutionPresets[0].Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"] = 999
|
|
cfg.ExecutionPresets[0].WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"] = false
|
|
cfg.ExecutionPresets[0].WorkspaceTools[0].Operations["read"].ArgumentMap["arg_slice"].([]string)[0] = "mutated_path"
|
|
|
|
preRefreshSnap := rt.Input.OpenAI.ExecutionPresetsSnapshot()
|
|
if len(preRefreshSnap) != 1 || preRefreshSnap[0].ID != "preset-alpha" {
|
|
t.Fatalf("expected preRefreshSnap to retain preset-alpha, got: %+v", preRefreshSnap)
|
|
}
|
|
if preRefreshSnap[0].Selector.Options["temp"] != 0.7 {
|
|
t.Fatalf("expected preRefreshSnap options temp=0.7, got %v", preRefreshSnap[0].Selector.Options["temp"])
|
|
}
|
|
if val := preRefreshSnap[0].Selector.Options["nested_map"].(map[string]string)["key1"]; val != "val1" {
|
|
t.Fatalf("expected nested_map key1=val1, got %v", val)
|
|
}
|
|
if val := preRefreshSnap[0].Selector.Options["nested_slice"].([]string)[0]; val != "opt1" {
|
|
t.Fatalf("expected nested_slice[0]=opt1, got %v", val)
|
|
}
|
|
if val := preRefreshSnap[0].Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"]; val != 10 {
|
|
t.Fatalf("expected stage_map a=10, got %v", val)
|
|
}
|
|
if val := preRefreshSnap[0].WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"]; !val {
|
|
t.Fatalf("expected sm_map read_ok=true, got %v", val)
|
|
}
|
|
if val := preRefreshSnap[0].WorkspaceTools[0].Operations["read"].ArgumentMap["arg_slice"].([]string)[0]; val != "path" {
|
|
t.Fatalf("expected arg_slice[0]=path, got %v", val)
|
|
}
|
|
|
|
// Mutate preRefreshSnap read output and verify internal server snapshot is unchanged
|
|
preRefreshSnap[0].Selector.Options["temp"] = 99.0
|
|
preRefreshSnap[0].Selector.Options["nested_map"].(map[string]string)["key1"] = "snap_mutated"
|
|
preRefreshSnap[0].Selector.Options["nested_slice"].([]string)[0] = "snap_mutated"
|
|
preRefreshSnap[0].Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"] = 888
|
|
preRefreshSnap[0].WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"] = false
|
|
|
|
preRefreshSnap2, _ := rt.Input.OpenAI.ExecutionPreset("preset-alpha")
|
|
if preRefreshSnap2.Selector.Options["temp"] != 0.7 {
|
|
t.Fatalf("expected internal snapshot options temp=0.7, got %v", preRefreshSnap2.Selector.Options["temp"])
|
|
}
|
|
if val := preRefreshSnap2.Selector.Options["nested_map"].(map[string]string)["key1"]; val != "val1" {
|
|
t.Fatalf("expected internal snapshot nested_map key1=val1, got %v", val)
|
|
}
|
|
if val := preRefreshSnap2.Selector.Options["nested_slice"].([]string)[0]; val != "opt1" {
|
|
t.Fatalf("expected internal snapshot nested_slice[0]=opt1, got %v", val)
|
|
}
|
|
if val := preRefreshSnap2.Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"]; val != 10 {
|
|
t.Fatalf("expected internal snapshot stage_map a=10, got %v", val)
|
|
}
|
|
if val := preRefreshSnap2.WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"]; !val {
|
|
t.Fatalf("expected internal snapshot sm_map read_ok=true, got %v", val)
|
|
}
|
|
|
|
// Perform refresh to replace generation
|
|
candidatePreset1 := config.ExecutionPreset{
|
|
ID: "preset-alpha",
|
|
Selector: config.ExecutionModelBinding{Model: "gpt-4o-mini", Options: map[string]any{"temp": 0.2}},
|
|
AllowedModes: []string{config.ModeDirect},
|
|
Routes: map[string]config.ExecutionRoute{
|
|
config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
|
|
},
|
|
}
|
|
candidatePreset2 := config.ExecutionPreset{
|
|
ID: "preset-beta",
|
|
Selector: config.ExecutionModelBinding{Model: "claude-3-5-sonnet"},
|
|
AllowedModes: []string{config.ModeDirect},
|
|
Routes: map[string]config.ExecutionRoute{
|
|
config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
|
|
},
|
|
}
|
|
|
|
candidateCfg := newTestConfig()
|
|
candidateCfg.ExecutionPresets = []config.ExecutionPreset{candidatePreset1, candidatePreset2}
|
|
|
|
// Manually invoke applyMutableConfig
|
|
changes := []configrefresh.Change{
|
|
{Path: `execution_presets["preset-alpha"].selector`, Class: configrefresh.StatusApplied},
|
|
{Path: `execution_presets["preset-beta"]`, Class: configrefresh.StatusApplied},
|
|
}
|
|
|
|
_, err = rt.applyMutableConfig(context.Background(), candidateCfg, changes, "req-preset-refresh")
|
|
if err != nil {
|
|
t.Fatalf("applyMutableConfig: %v", err)
|
|
}
|
|
|
|
// Assert retained preRefreshSnap2 is still unchanged
|
|
if preRefreshSnap2.Selector.Model != "gpt-4o" {
|
|
t.Fatalf("retained pre-refresh snapshot mutated! expected gpt-4o, got %s", preRefreshSnap2.Selector.Model)
|
|
}
|
|
if val := preRefreshSnap2.Selector.Options["nested_map"].(map[string]string)["key1"]; val != "val1" {
|
|
t.Fatalf("retained pre-refresh snapshot mutated! expected key1=val1, got %v", val)
|
|
}
|
|
|
|
// Assert post-refresh read sees new generation
|
|
postRefreshSnap := rt.Input.OpenAI.ExecutionPresetsSnapshot()
|
|
if len(postRefreshSnap) != 2 {
|
|
t.Fatalf("expected 2 presets post refresh, got %d", len(postRefreshSnap))
|
|
}
|
|
|
|
pAlpha, okAlpha := rt.Input.OpenAI.ExecutionPreset("preset-alpha")
|
|
if !okAlpha || pAlpha.Selector.Model != "gpt-4o-mini" {
|
|
t.Fatalf("post-refresh preset-alpha model: got %s, want gpt-4o-mini", pAlpha.Selector.Model)
|
|
}
|
|
|
|
pBeta, okBeta := rt.Input.OpenAI.ExecutionPreset("preset-beta")
|
|
if !okBeta || pBeta.Selector.Model != "claude-3-5-sonnet" {
|
|
t.Fatalf("post-refresh preset-beta model: got %s, want claude-3-5-sonnet", pBeta.Selector.Model)
|
|
}
|
|
}
|