mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-08-05 16:24:42 +00:00
Add Wave 3 package coverage: - cache/permission_cache_test.go: PermissionCache 9 methods + invalidation - sessions/key_extra_test.go: session key builders - sessions/manager_extra_test.go: SetHistory, Save, loadAll - knowledgegraph/extractor_helpers_test.go: Extract with mock provider, splitChunks, mergeResults
235 lines
6.9 KiB
Go
235 lines
6.9 KiB
Go
package sessions
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
|
)
|
|
|
|
// TestSetHistory replaces session history entirely.
|
|
func TestSetHistory(t *testing.T) {
|
|
m := NewManager("")
|
|
ctx := context.Background()
|
|
key := "agent:a1:s1"
|
|
|
|
m.GetOrCreate(ctx, key)
|
|
m.AddMessage(ctx, key, providers.Message{Role: "user", Content: "old"})
|
|
|
|
newMsgs := []providers.Message{
|
|
{Role: "user", Content: "new1"},
|
|
{Role: "assistant", Content: "new2"},
|
|
}
|
|
m.SetHistory(ctx, key, newMsgs)
|
|
|
|
history := m.GetHistory(ctx, key)
|
|
if len(history) != 2 {
|
|
t.Fatalf("expected 2 messages after SetHistory, got %d", len(history))
|
|
}
|
|
if history[0].Content != "new1" || history[1].Content != "new2" {
|
|
t.Errorf("unexpected history content: %+v", history)
|
|
}
|
|
}
|
|
|
|
// TestSetHistory_NonExistentSession is a silent no-op.
|
|
func TestSetHistory_NonExistentSession(t *testing.T) {
|
|
m := NewManager("")
|
|
// Must not panic
|
|
m.SetHistory(context.Background(), "nonexistent", []providers.Message{{Role: "user", Content: "x"}})
|
|
}
|
|
|
|
// TestGetSummary_NonExistentSession returns empty string.
|
|
func TestGetSummary_NonExistentSession(t *testing.T) {
|
|
m := NewManager("")
|
|
got := m.GetSummary(context.Background(), "nonexistent")
|
|
if got != "" {
|
|
t.Errorf("expected empty string, got %q", got)
|
|
}
|
|
}
|
|
|
|
// TestGetCompactionCount_NonExistentSession returns 0.
|
|
func TestGetCompactionCount_NonExistentSession(t *testing.T) {
|
|
m := NewManager("")
|
|
got := m.GetCompactionCount(context.Background(), "nonexistent")
|
|
if got != 0 {
|
|
t.Errorf("expected 0, got %d", got)
|
|
}
|
|
}
|
|
|
|
// TestGetContextWindow_NonExistentSession returns 0.
|
|
func TestGetContextWindow_NonExistentSession(t *testing.T) {
|
|
m := NewManager("")
|
|
got := m.GetContextWindow(context.Background(), "nonexistent")
|
|
if got != 0 {
|
|
t.Errorf("expected 0, got %d", got)
|
|
}
|
|
}
|
|
|
|
// TestGetLastPromptTokens_NonExistentSession returns (0, 0).
|
|
func TestGetLastPromptTokens_NonExistentSession(t *testing.T) {
|
|
m := NewManager("")
|
|
tokens, msgCount := m.GetLastPromptTokens(context.Background(), "nonexistent")
|
|
if tokens != 0 || msgCount != 0 {
|
|
t.Errorf("expected (0, 0), got (%d, %d)", tokens, msgCount)
|
|
}
|
|
}
|
|
|
|
// TestTruncateHistory_NonExistentSession is a silent no-op.
|
|
func TestTruncateHistory_NonExistentSession(t *testing.T) {
|
|
m := NewManager("")
|
|
// Must not panic
|
|
m.TruncateHistory(context.Background(), "nonexistent", 5)
|
|
}
|
|
|
|
// TestTruncateHistory_LessThanKeep keeps all messages when count < keepLast.
|
|
func TestTruncateHistory_LessThanKeep(t *testing.T) {
|
|
m := NewManager("")
|
|
ctx := context.Background()
|
|
key := "agent:a1:s1"
|
|
|
|
m.AddMessage(ctx, key, providers.Message{Role: "user", Content: "only"})
|
|
m.TruncateHistory(ctx, key, 10) // keepLast > actual count
|
|
|
|
history := m.GetHistory(ctx, key)
|
|
if len(history) != 1 {
|
|
t.Fatalf("expected 1 message (no truncation needed), got %d", len(history))
|
|
}
|
|
}
|
|
|
|
// TestLastUsedChannel_Empty returns ("","") for an empty manager.
|
|
func TestLastUsedChannel_Empty(t *testing.T) {
|
|
m := NewManager("")
|
|
ch, chatID := m.LastUsedChannel(context.Background(), "my-agent")
|
|
if ch != "" || chatID != "" {
|
|
t.Errorf("expected ('', ''), got (%q, %q)", ch, chatID)
|
|
}
|
|
}
|
|
|
|
// TestLastUsedChannel_FindsMostRecent returns the most recently updated channel session.
|
|
func TestLastUsedChannel_FindsMostRecent(t *testing.T) {
|
|
m := NewManager("")
|
|
ctx := context.Background()
|
|
|
|
key1 := "agent:my-agent:telegram:direct:111"
|
|
key2 := "agent:my-agent:telegram:direct:222"
|
|
|
|
s1 := m.GetOrCreate(ctx, key1)
|
|
s1.Updated = time.Now().Add(-10 * time.Second)
|
|
|
|
s2 := m.GetOrCreate(ctx, key2)
|
|
s2.Updated = time.Now()
|
|
|
|
ch, chatID := m.LastUsedChannel(ctx, "my-agent")
|
|
if ch != "telegram" {
|
|
t.Errorf("expected channel 'telegram', got %q", ch)
|
|
}
|
|
if chatID != "222" {
|
|
t.Errorf("expected chatID '222', got %q", chatID)
|
|
}
|
|
}
|
|
|
|
// TestLastUsedChannel_SkipsCronAndSubagent skips non-channel sessions.
|
|
func TestLastUsedChannel_SkipsCronAndSubagent(t *testing.T) {
|
|
m := NewManager("")
|
|
ctx := context.Background()
|
|
|
|
m.GetOrCreate(ctx, "agent:my-agent:cron:job-1")
|
|
m.GetOrCreate(ctx, "agent:my-agent:subagent:worker")
|
|
|
|
ch, chatID := m.LastUsedChannel(ctx, "my-agent")
|
|
if ch != "" || chatID != "" {
|
|
t.Errorf("expected ('', '') for only cron/subagent sessions, got (%q, %q)", ch, chatID)
|
|
}
|
|
}
|
|
|
|
// TestLastUsedChannel_AgentIsolation ensures different agents don't cross-contaminate.
|
|
func TestLastUsedChannel_AgentIsolation(t *testing.T) {
|
|
m := NewManager("")
|
|
ctx := context.Background()
|
|
|
|
m.GetOrCreate(ctx, "agent:agent-A:telegram:direct:100")
|
|
m.GetOrCreate(ctx, "agent:agent-B:telegram:direct:200")
|
|
|
|
ch, chatID := m.LastUsedChannel(ctx, "agent-A")
|
|
if ch != "telegram" || chatID != "100" {
|
|
t.Errorf("agent-A: expected (telegram, 100), got (%q, %q)", ch, chatID)
|
|
}
|
|
}
|
|
|
|
// TestSave_MissingSession_NoError returns nil for a key that doesn't exist.
|
|
func TestSave_MissingSession_NoError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m := NewManager(dir)
|
|
ctx := context.Background()
|
|
|
|
if err := m.Save(ctx, "agent:ghost:s1"); err != nil {
|
|
t.Fatalf("expected nil error for missing session, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSave_NoStorage_NoError returns nil when storage path is "".
|
|
func TestSave_NoStorage_NoError(t *testing.T) {
|
|
m := NewManager("")
|
|
ctx := context.Background()
|
|
key := "agent:a1:s1"
|
|
|
|
m.GetOrCreate(ctx, key)
|
|
if err := m.Save(ctx, key); err != nil {
|
|
t.Fatalf("expected nil error with no storage, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestDelete_NonExistentKey returns nil (idempotent).
|
|
func TestDelete_NonExistentKey(t *testing.T) {
|
|
m := NewManager("")
|
|
if err := m.Delete(context.Background(), "nonexistent"); err != nil {
|
|
t.Fatalf("expected nil error deleting non-existent session, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestDelete_WithStorage_NonExistentFile returns nil when the file was never saved.
|
|
func TestDelete_WithStorage_NonExistentFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m := NewManager(dir)
|
|
ctx := context.Background()
|
|
|
|
m.GetOrCreate(ctx, "agent:a1:s1")
|
|
// Delete without prior Save — file does not exist on disk.
|
|
if err := m.Delete(ctx, "agent:a1:s1"); err != nil {
|
|
t.Fatalf("expected nil for non-existent file, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestNewManager_LoadAll_SkipsBadFiles verifies corrupt JSON files are silently skipped.
|
|
func TestNewManager_LoadAll_SkipsBadFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
// Write a corrupt JSON file directly.
|
|
if err := os.WriteFile(dir+"/corrupt.json", []byte("not valid json"), 0644); err != nil {
|
|
t.Fatalf("setup: write corrupt file: %v", err)
|
|
}
|
|
|
|
// NewManager must not crash; corrupt file is skipped.
|
|
m := NewManager(dir)
|
|
all := m.List(context.Background(), "")
|
|
if len(all) != 0 {
|
|
t.Errorf("expected 0 sessions (corrupt file skipped), got %d", len(all))
|
|
}
|
|
}
|
|
|
|
// TestNewManager_LoadAll_SkipsNonJSON verifies non-JSON files are ignored.
|
|
func TestNewManager_LoadAll_SkipsNonJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
if err := os.WriteFile(dir+"/readme.txt", []byte("ignore me"), 0644); err != nil {
|
|
t.Fatalf("setup: %v", err)
|
|
}
|
|
|
|
m := NewManager(dir)
|
|
if len(m.List(context.Background(), "")) != 0 {
|
|
t.Error("expected 0 sessions (non-JSON skipped)")
|
|
}
|
|
}
|