package db import ( "database/sql" "path/filepath" "testing" ) func newTempStore(t *testing.T) *Store { t.Helper() dbFile := filepath.Join(t.TempDir(), "test.db") conn, err := sql.Open("sqlite", dbFile+"?_busy_timeout=5000&_journal=WAL") if err != nil { t.Fatalf("sql.Open returned error: %v", err) } store := &Store{db: conn} if err := store.createTables(); err != nil { t.Fatalf("createTables returned error: %v", err) } t.Cleanup(func() { _ = conn.Close() }) return store } func TestSetGroupLocationUpdatesExistingLocation(t *testing.T) { store := newTempStore(t) group, err := store.CreateGroup("Workspace") if err != nil { t.Fatalf("CreateGroup returned error: %v", err) } if err := store.SetGroupLocation(group.ID, "/tmp/one"); err != nil { t.Fatalf("SetGroupLocation returned error: %v", err) } updated, err := store.GetGroup(group.ID) if err != nil { t.Fatalf("GetGroup returned error: %v", err) } if updated.Location != "/tmp/one" { t.Fatalf("location after first update = %q, want %q", updated.Location, "/tmp/one") } if err := store.SetGroupLocation(group.ID, "/tmp/two"); err != nil { t.Fatalf("SetGroupLocation returned error: %v", err) } updated, err = store.GetGroup(group.ID) if err != nil { t.Fatalf("GetGroup returned error: %v", err) } if updated.Location != "/tmp/two" { t.Fatalf("location after second update = %q, want %q", updated.Location, "/tmp/two") } } func TestUpdateConversationGroupAttachesUngroupedConversation(t *testing.T) { store := newTempStore(t) group, err := store.CreateGroup("Workspace") if err != nil { t.Fatalf("CreateGroup returned error: %v", err) } conv, err := store.CreateConversation("Chat", nil) if err != nil { t.Fatalf("CreateConversation returned error: %v", err) } if err := store.UpdateConversationGroup(conv.ID, group.ID); err != nil { t.Fatalf("UpdateConversationGroup returned error: %v", err) } updated, err := store.GetConversation(conv.ID) if err != nil { t.Fatalf("GetConversation returned error: %v", err) } if updated.GroupID == nil { t.Fatal("conversation group ID is nil, want a group ID") } if got, want := *updated.GroupID, group.ID; got != want { t.Fatalf("conversation group ID = %q, want %q", got, want) } } func TestAddMessageWithMetaPersistsKindNameAndAgent(t *testing.T) { store := newTempStore(t) conv, err := store.CreateConversation("Chat", nil) if err != nil { t.Fatalf("CreateConversation returned error: %v", err) } if err := store.AddMessageWithMeta(conv.ID, "assistant", "Thinking", 1, "thinking", "", "Planner", 42); err != nil { t.Fatalf("AddMessageWithMeta returned error: %v", err) } messages, err := store.GetMessages(conv.ID) if err != nil { t.Fatalf("GetMessages returned error: %v", err) } if len(messages) != 1 { t.Fatalf("message count = %d, want 1", len(messages)) } if got, want := messages[0].Kind, "thinking"; got != want { t.Fatalf("message kind = %q, want %q", got, want) } if got, want := messages[0].Name, ""; got != want { t.Fatalf("message name = %q, want %q", got, want) } if got, want := messages[0].Agent, "Planner"; got != want { t.Fatalf("message agent = %q, want %q", got, want) } if got, want := messages[0].TotalTokens, 42; got != want { t.Fatalf("message total tokens = %d, want %d", got, want) } } func TestConversationAndGroupTokenTotalsAggregate(t *testing.T) { store := newTempStore(t) group, err := store.CreateGroup("Workspace") if err != nil { t.Fatalf("CreateGroup returned error: %v", err) } conv1, err := store.CreateConversation("One", &group.ID) if err != nil { t.Fatalf("CreateConversation returned error: %v", err) } conv2, err := store.CreateConversation("Two", &group.ID) if err != nil { t.Fatalf("CreateConversation returned error: %v", err) } ungrouped, err := store.CreateConversation("Three", nil) if err != nil { t.Fatalf("CreateConversation returned error: %v", err) } if err := store.AddMessageWithMeta(conv1.ID, "assistant", "A", 1, "message", "", "Planner", 11); err != nil { t.Fatalf("AddMessageWithMeta returned error: %v", err) } if err := store.AddMessageWithMeta(conv1.ID, "assistant", "B", 2, "message", "", "Planner", 7); err != nil { t.Fatalf("AddMessageWithMeta returned error: %v", err) } if err := store.AddMessageWithMeta(conv2.ID, "assistant", "C", 1, "message", "", "Planner", 5); err != nil { t.Fatalf("AddMessageWithMeta returned error: %v", err) } if err := store.AddMessageWithMeta(ungrouped.ID, "assistant", "D", 1, "message", "", "Planner", 3); err != nil { t.Fatalf("AddMessageWithMeta returned error: %v", err) } conversations, err := store.ListConversations() if err != nil { t.Fatalf("ListConversations returned error: %v", err) } totalsByID := make(map[string]int, len(conversations)) for _, conv := range conversations { totalsByID[conv.ID] = conv.TotalTokens } if got, want := totalsByID[conv1.ID], 18; got != want { t.Fatalf("conversation one total tokens = %d, want %d", got, want) } if got, want := totalsByID[conv2.ID], 5; got != want { t.Fatalf("conversation two total tokens = %d, want %d", got, want) } if got, want := totalsByID[ungrouped.ID], 3; got != want { t.Fatalf("ungrouped conversation total tokens = %d, want %d", got, want) } groups, err := store.ListGroups() if err != nil { t.Fatalf("ListGroups returned error: %v", err) } if len(groups) != 1 { t.Fatalf("group count = %d, want 1", len(groups)) } if got, want := groups[0].TotalTokens, 23; got != want { t.Fatalf("group total tokens = %d, want %d", got, want) } } func TestUpdateConversationContextSummaryPersistsSummaryAndOrder(t *testing.T) { store := newTempStore(t) conv, err := store.CreateConversation("Chat", nil) if err != nil { t.Fatalf("CreateConversation returned error: %v", err) } if err := store.UpdateConversationContextSummary(conv.ID, "compressed memory", 12); err != nil { t.Fatalf("UpdateConversationContextSummary returned error: %v", err) } updated, err := store.GetConversation(conv.ID) if err != nil { t.Fatalf("GetConversation returned error: %v", err) } if got, want := updated.ContextSummary, "compressed memory"; got != want { t.Fatalf("context summary = %q, want %q", got, want) } if got, want := updated.ContextSummaryOrder, 12; got != want { t.Fatalf("context summary order = %d, want %d", got, want) } }