package bot import ( "strings" "testing" ) // TestClampMemory pins the read-side clamp (the scratchpad and the sidecar // share the slot unsigned, so both sides re-clamp whatever they read — keep // this in step with web/static/apps/memory.js sanitizeMemory). func TestClampMemory(t *testing.T) { long := strings.Repeat("x", maxMemFactChars+50) m := memoryState{Facts: []memFact{ {ID: "m1", Text: " keeps replies short "}, {Text: "no id — repaired"}, {ID: "m3", Text: " "}, // blank — dropped {ID: "m4", Text: long}, }} clampMemory(&m) if m.V != 1 || len(m.Facts) != 3 { t.Fatalf("clamped shape: v=%d facts=%d", m.V, len(m.Facts)) } if m.Facts[0].Text != "keeps replies short" { t.Errorf("trim: %q", m.Facts[0].Text) } if m.Facts[1].ID == "" { t.Error("missing id not repaired") } if len(m.Facts[2].Text) != maxMemFactChars { t.Errorf("overlong fact not clamped: %d", len(m.Facts[2].Text)) } over := memoryState{} for i := 0; i < maxMemFacts+10; i++ { over.Facts = append(over.Facts, memFact{ID: newMemID(), Text: "fact"}) } clampMemory(&over) if len(over.Facts) != maxMemFacts { t.Fatalf("cap: %d facts survived", len(over.Facts)) } } // TestMemoryToolsDMOnly pins the gate: remember/forget exist exactly where // the memory lives — a DM — and never in groups. func TestMemoryToolsDMOnly(t *testing.T) { r := &Runtime{Session: &Session{Name: "Eleven"}} has := func(tools []anthTool, name string) bool { for _, tl := range tools { if tl.Name == name { return true } } return false } dm := r.replyTools(&ThreadCtx{Kind: "dm"}) if !has(dm, "remember") || !has(dm, "forget") { t.Error("DM palette missing the memory tools") } group := r.replyTools(&ThreadCtx{Kind: "group"}) if has(group, "remember") || has(group, "forget") { t.Error("group palette must not carry the memory tools") } }