Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
- OAuth PKCE flow for Max subscription auth (no API key needed) - Claude API client with SSE streaming (Messages API v1) - 16 tool definitions: terminal, SFTP, RDP, session management - Tool dispatch router mapping to existing Wraith services - Conversation manager with SQLite persistence - Terminal output ring buffer for AI context - RDP screenshot encoder (RGBA → JPEG with downscaling) - Wired into Wails app as AIService Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
221 lines
5.3 KiB
Go
221 lines
5.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/vstockwell/wraith/internal/db"
|
|
)
|
|
|
|
func setupConversationManager(t *testing.T) *ConversationManager {
|
|
t.Helper()
|
|
database, err := db.Open(filepath.Join(t.TempDir(), "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("open db: %v", err)
|
|
}
|
|
if err := db.Migrate(database); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
// Create the conversations table (002 migration)
|
|
_, err = database.Exec(`CREATE TABLE IF NOT EXISTS conversations (
|
|
id TEXT PRIMARY KEY, title TEXT, model TEXT NOT NULL,
|
|
messages TEXT NOT NULL DEFAULT '[]',
|
|
tokens_in INTEGER DEFAULT 0, tokens_out INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP)`)
|
|
if err != nil {
|
|
t.Fatalf("create conversations table: %v", err)
|
|
}
|
|
t.Cleanup(func() { database.Close() })
|
|
return NewConversationManager(database)
|
|
}
|
|
|
|
func TestCreateConversation(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
conv, err := mgr.Create("claude-sonnet-4-20250514")
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
if conv.ID == "" {
|
|
t.Error("expected non-empty ID")
|
|
}
|
|
if conv.Model != "claude-sonnet-4-20250514" {
|
|
t.Errorf("expected model claude-sonnet-4-20250514, got %s", conv.Model)
|
|
}
|
|
if conv.Title != "New conversation" {
|
|
t.Errorf("expected title 'New conversation', got %s", conv.Title)
|
|
}
|
|
if conv.TokensIn != 0 || conv.TokensOut != 0 {
|
|
t.Errorf("expected zero tokens, got in=%d out=%d", conv.TokensIn, conv.TokensOut)
|
|
}
|
|
}
|
|
|
|
func TestAddAndGetMessages(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
conv, err := mgr.Create("test-model")
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
// Add a user message
|
|
userMsg := Message{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{Type: "text", Text: "What is running on port 8080?"},
|
|
},
|
|
}
|
|
if err := mgr.AddMessage(conv.ID, userMsg); err != nil {
|
|
t.Fatalf("add user message: %v", err)
|
|
}
|
|
|
|
// Add an assistant message
|
|
assistantMsg := Message{
|
|
Role: "assistant",
|
|
Content: []ContentBlock{
|
|
{Type: "text", Text: "Let me check that for you."},
|
|
},
|
|
}
|
|
if err := mgr.AddMessage(conv.ID, assistantMsg); err != nil {
|
|
t.Fatalf("add assistant message: %v", err)
|
|
}
|
|
|
|
// Get messages
|
|
messages, err := mgr.GetMessages(conv.ID)
|
|
if err != nil {
|
|
t.Fatalf("get messages: %v", err)
|
|
}
|
|
if len(messages) != 2 {
|
|
t.Fatalf("expected 2 messages, got %d", len(messages))
|
|
}
|
|
if messages[0].Role != "user" {
|
|
t.Errorf("expected first message role 'user', got %s", messages[0].Role)
|
|
}
|
|
if messages[1].Role != "assistant" {
|
|
t.Errorf("expected second message role 'assistant', got %s", messages[1].Role)
|
|
}
|
|
if messages[0].Content[0].Text != "What is running on port 8080?" {
|
|
t.Errorf("unexpected message text: %s", messages[0].Content[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestListConversations(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
// Create multiple conversations
|
|
_, err := mgr.Create("model-a")
|
|
if err != nil {
|
|
t.Fatalf("create 1: %v", err)
|
|
}
|
|
_, err = mgr.Create("model-b")
|
|
if err != nil {
|
|
t.Fatalf("create 2: %v", err)
|
|
}
|
|
|
|
list, err := mgr.List()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(list) != 2 {
|
|
t.Errorf("expected 2 conversations, got %d", len(list))
|
|
}
|
|
}
|
|
|
|
func TestDeleteConversation(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
conv, err := mgr.Create("test-model")
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
if err := mgr.Delete(conv.ID); err != nil {
|
|
t.Fatalf("delete: %v", err)
|
|
}
|
|
|
|
// Verify it's gone
|
|
list, err := mgr.List()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(list) != 0 {
|
|
t.Errorf("expected 0 conversations after delete, got %d", len(list))
|
|
}
|
|
|
|
// Delete non-existent should error
|
|
if err := mgr.Delete("nonexistent"); err == nil {
|
|
t.Error("expected error deleting non-existent conversation")
|
|
}
|
|
}
|
|
|
|
func TestTokenUsageTracking(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
conv, err := mgr.Create("test-model")
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
// Update token usage multiple times
|
|
if err := mgr.UpdateTokenUsage(conv.ID, 100, 50); err != nil {
|
|
t.Fatalf("update tokens 1: %v", err)
|
|
}
|
|
if err := mgr.UpdateTokenUsage(conv.ID, 200, 100); err != nil {
|
|
t.Fatalf("update tokens 2: %v", err)
|
|
}
|
|
|
|
// Verify totals
|
|
list, err := mgr.List()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(list) != 1 {
|
|
t.Fatalf("expected 1 conversation, got %d", len(list))
|
|
}
|
|
if list[0].TokensIn != 300 {
|
|
t.Errorf("expected 300 tokens in, got %d", list[0].TokensIn)
|
|
}
|
|
if list[0].TokensOut != 150 {
|
|
t.Errorf("expected 150 tokens out, got %d", list[0].TokensOut)
|
|
}
|
|
}
|
|
|
|
func TestGetMessagesNonExistent(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
_, err := mgr.GetMessages("nonexistent-id")
|
|
if err == nil {
|
|
t.Error("expected error for non-existent conversation")
|
|
}
|
|
}
|
|
|
|
func TestAutoTitle(t *testing.T) {
|
|
mgr := setupConversationManager(t)
|
|
|
|
conv, err := mgr.Create("test-model")
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
msg := Message{
|
|
Role: "user",
|
|
Content: []ContentBlock{
|
|
{Type: "text", Text: "Check disk usage on server-01"},
|
|
},
|
|
}
|
|
if err := mgr.AddMessage(conv.ID, msg); err != nil {
|
|
t.Fatalf("add message: %v", err)
|
|
}
|
|
|
|
// Verify the title was auto-set
|
|
list, err := mgr.List()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if list[0].Title != "Check disk usage on server-01" {
|
|
t.Errorf("expected auto-title, got %q", list[0].Title)
|
|
}
|
|
}
|