175 lines
9.1 KiB
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
|
|
}
|