iop/apps/edge/internal/openai/hot_path_selector_test.go

175 lines
9.1 KiB
Go

package openai
import (
"testing"
"iop/packages/go/config"
)
func TestHotPathSelectorDecisionMatrix(t *testing.T) {
issued := newReservedPaths("req_test_123")
preset := hotPathSelectorPreset([]string{config.ModeDirect, config.ModeLight})
directOnly := hotPathSelectorPreset([]string{config.ModeDirect})
validGate := hotPathTestGate(preset)
tests := []struct {
name string
preset config.ExecutionPreset
output normalizedStageOutput
gate hotPathSelectorGate
wantMode string
wantReason string
wantErr bool
}{
{name: "ContentTextOnly", preset: preset, output: normalizedStageOutput{Content: "Hello"}, gate: validGate, wantMode: modeDirect, wantReason: reasonDirectNoReservedControls},
{name: "HighThinkingText", preset: preset, output: normalizedStageOutput{Content: "Result", Reasoning: "Reasoning"}, gate: validGate, wantMode: modeDirect, wantReason: reasonDirectNoReservedControls},
{
name: "GeneralTools", preset: preset, gate: validGate, wantMode: modeDirect, wantReason: reasonDirectNoReservedControls,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{
{ID: "call_read", Name: "read_file", Arguments: map[string]any{"path": "src/main.go"}},
}},
},
{
name: "ExactPrepare", preset: preset, gate: validGate, wantMode: modeLight, wantReason: reasonLightExactPrepare,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{
{ID: "call_prepare", Name: "mkdir_p", Arguments: map[string]any{"path": issued.JobDir}},
}},
},
{
name: "ExactPairWithMaskedPath", preset: preset, gate: validGate, wantMode: modeLight, wantReason: reasonLightExactPair,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{
{ID: "call_plan", Name: "write_file", RawArgs: `{"path":".iop\/job\/req_test_123\/plan.md"}`},
{ID: "call_review", Name: "write_file", Arguments: map[string]any{"path": issued.ReviewPath}},
}},
},
{
name: "PartialPair", preset: preset, gate: validGate, wantReason: reasonMalformedPartialPair, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_plan", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath}}}},
},
{
name: "MixedCalls", preset: preset, gate: validGate, wantReason: reasonMalformedMixedCalls, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{
{ID: "call_prepare", Name: "mkdir_p", Arguments: map[string]any{"path": issued.JobDir}},
{ID: "call_general", Name: "read_file", Arguments: map[string]any{"path": "README.md"}},
}},
},
{
name: "DuplicateCalls", preset: preset, gate: validGate, wantReason: reasonMalformedDuplicateCalls, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{
{ID: "call_plan_1", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath}},
{ID: "call_plan_2", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath}},
{ID: "call_review", Name: "write_file", Arguments: map[string]any{"path": issued.ReviewPath}},
}},
},
{
name: "WrongIssuedPath", preset: preset, gate: validGate, wantReason: reasonMalformedWrongPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_wrong", Name: "write_file", Arguments: map[string]any{"path": ".iop/job/another/plan.md"}}}},
},
{
name: "PrefixedMappedPath", preset: preset, gate: validGate, wantReason: reasonMalformedWrongPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_prefix", Name: "write_file", Arguments: map[string]any{"path": "prefix/" + issued.PlanPath}}}},
},
{
name: "AbsoluteMappedPath", preset: preset, gate: validGate, wantReason: reasonMalformedWrongPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_absolute", Name: "write_file", Arguments: map[string]any{"path": "/" + issued.PlanPath}}}},
},
{
name: "SuffixedMappedPath", preset: preset, gate: validGate, wantReason: reasonMalformedWrongPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_suffix", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath + ".bak"}}}},
},
{
name: "SamePathExtraSource", preset: preset, gate: validGate, wantReason: reasonMalformedConflictingPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_extra", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath, "shadow": issued.PlanPath}}}},
},
{
name: "DecodedAndRawConflictingReservedPaths", preset: preset, gate: validGate, wantReason: reasonMalformedConflictingPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_raw_conflict", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath}, RawArgs: `{"path":".iop/job/req_test_123/review.md"}`}}},
},
{
name: "ArbitraryControlRole", preset: preset, gate: validGate, wantReason: reasonMalformedControlRole, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_wrong_role", Name: "shell", Arguments: map[string]any{"path": issued.PlanPath}}}},
},
{
name: "CanonicalRoleWrongReservedShape", preset: preset, gate: validGate, wantReason: reasonMalformedControlRole, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_wrong_shape", Name: "mkdir_p", Arguments: map[string]any{"path": issued.PlanPath}}}},
},
{
name: "ConflictingReservedPathSources", preset: preset, gate: validGate, wantReason: reasonMalformedConflictingPath, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_conflict", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath, "shadow": issued.ReviewPath}}}},
},
{
name: "LightDisabled", preset: directOnly, gate: hotPathTestGate(directOnly), wantReason: reasonModeDisabled, wantErr: true,
output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_prepare", Name: "mkdir_p", Arguments: map[string]any{"path": issued.JobDir}}}},
},
{name: "UnhealthyPinnedGate", preset: preset, output: normalizedStageOutput{Content: "text"}, gate: withGateHealth(validGate, false), wantReason: reasonUnhealthyRoute, wantErr: true},
{name: "MissingCapabilityEvidence", preset: preset, output: normalizedStageOutput{Content: "text"}, gate: withoutGateCapability(validGate), wantReason: reasonUnhealthyRoute, wantErr: true},
{name: "MismatchedPresetBinding", preset: preset, output: normalizedStageOutput{Content: "text"}, gate: withGateSelector(validGate, "other-model"), wantReason: reasonUnhealthyRoute, wantErr: true},
{
name: "ProseNeverSelectsMode", preset: preset, gate: validGate, wantMode: modeDirect, wantReason: reasonDirectNoReservedControls,
output: normalizedStageOutput{Content: "I would choose light and mention .iop/job/req_test_123/plan.md in prose."},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
decision, err := classifyHotPathOutput(test.preset, issued, test.output, test.gate)
if (err != nil) != test.wantErr {
t.Fatalf("classifyHotPathOutput() error = %v, wantErr %v", err, test.wantErr)
}
if decision.Reason != test.wantReason {
t.Errorf("decision.Reason = %q, want %q", decision.Reason, test.wantReason)
}
if !test.wantErr && decision.Mode != test.wantMode {
t.Errorf("decision.Mode = %q, want %q", decision.Mode, test.wantMode)
}
})
}
}
func hotPathSelectorPreset(modes []string) config.ExecutionPreset {
routes := make(map[string]config.ExecutionRoute, len(modes))
for _, mode := range modes {
switch mode {
case config.ModeDirect:
routes[mode] = config.ExecutionRoute{}
case config.ModeLight:
routes[mode] = config.ExecutionRoute{Stages: []config.ExecutionRouteStage{
{Role: "local", Model: "local-model"},
{Role: "review", Model: "review-model"},
}}
}
}
return config.ExecutionPreset{
ID: "preset-standard", Selector: config.ExecutionModelBinding{Model: "selector-model"}, AllowedModes: modes, Routes: routes,
WorkspaceTools: []config.ExecutionWorkspaceToolAlternative{{
Name: "canonical-fs",
Operations: map[string]config.ExecutionWorkspaceOperation{
"prepare": {ToolName: "mkdir_p", ArgumentMap: map[string]any{"path": "path"}},
"write": {ToolName: "write_file", ArgumentMap: map[string]any{"path": "path"}},
},
}},
}
}
func hotPathTestGate(preset config.ExecutionPreset) hotPathSelectorGate {
return hotPathSelectorGate{
PresetID: preset.ID, SelectorModel: preset.Selector.Model, ModelGroupKey: preset.Selector.Model,
ProviderID: "provider-1", RunID: "run-1", NodeID: "node-1", ExecutionPath: "provider_tunnel",
ProfileDriver: "openai_chat", ProfileCapabilities: []string{"chat"}, Healthy: true, CapabilitySatisfied: true,
}
}
func withGateHealth(gate hotPathSelectorGate, healthy bool) hotPathSelectorGate {
gate.Healthy = healthy
return gate
}
func withoutGateCapability(gate hotPathSelectorGate) hotPathSelectorGate {
gate.CapabilitySatisfied = false
return gate
}
func withGateSelector(gate hotPathSelectorGate, model string) hotPathSelectorGate {
gate.SelectorModel = model
return gate
}