Files
2026-07-13 13:00:08 +08:00

85 lines
2.9 KiB
Go

package memorycompiler
import (
"context"
"strings"
"testing"
)
// #1: a turn that finishes without error and without tool calls is a success
// (a plain-text answer), not partial_success, and must not be learned as a
// strategy failure. The earlier behavior demoted every no-tool turn and, via
// updateStrategy, recorded it as a failure — poisoning scores with Memory v5 on.
func TestNoToolSuccessfulTurnIsSuccess(t *testing.T) {
if got := outcomeFor(nil, nil); got != "success" {
t.Fatalf("outcomeFor(no tools, no err) = %q, want success", got)
}
dir := t.TempDir()
rt := New(dir)
for i := 0; i < 5; i++ {
_, turn := rt.StartTurn(context.Background(), "explain how the auth module works", nil)
if turn == nil {
t.Fatalf("turn %d nil", i)
}
turn.Finish(nil) // no RecordToolResults => zero tool records, err == nil
}
st := rt.loadState()
for _, s := range st.Strategies {
if s.Failures > 0 {
t.Errorf("strategy %q got %d failures from no-tool successful turns", s.ID, s.Failures)
}
}
}
func TestPlanModeBlockedToolsDoNotPoisonStrategy(t *testing.T) {
records := []ToolRecord{
{Name: "bash", Error: planModeBlockedToolError, Blocked: true},
{Name: "edit", Error: planModeBlockedToolError, Blocked: true},
}
if got := outcomeFor(records, nil); got != "success" {
t.Fatalf("outcomeFor(plan-mode blocked tools, no err) = %q, want success", got)
}
dir := t.TempDir()
rt := New(dir)
_, turn := rt.StartTurn(context.Background(), "draft a plan for the fix", nil)
if turn == nil {
t.Fatal("nil turn")
}
turn.RecordToolResults(records)
turn.Finish(nil)
st := rt.loadState()
for _, s := range st.Strategies {
if s.Failures > 0 {
t.Errorf("strategy %q got %d failures from plan-mode policy blocks", s.ID, s.Failures)
}
}
}
// #2: goal classification must strip the injected "Referenced context:" preamble
// and file blocks, while SourceEvent keeps the full input (the model's only view
// of the referenced files once the contract replaces the user turn).
func TestStripReferencedContextForGoalOnly(t *testing.T) {
raw := "Referenced context:\n\n<file path=\"a.go\">package main\nfunc secret() {}\n</file>\n\nfix the flaky login test"
if got := stripReferencedContext(raw); got != "fix the flaky login test" {
t.Fatalf("stripReferencedContext = %q, want the user's actual text", got)
}
if got := stripReferencedContext("just a normal goal"); got != "just a normal goal" {
t.Fatalf("plain input changed: %q", got)
}
dir := t.TempDir()
rt := New(dir)
_, turn := rt.StartTurn(context.Background(), raw, nil)
if turn == nil {
t.Fatal("nil turn")
}
if strings.Contains(turn.ir.Goal, "secret") || strings.Contains(turn.ir.Goal, "package main") {
t.Fatalf("goal leaked file contents: %q", turn.ir.Goal)
}
if !strings.Contains(turn.ir.SourceEvent, "secret") {
t.Fatalf("source_event dropped the referenced file the model needs: %q", turn.ir.SourceEvent)
}
}