diff options
Diffstat (limited to 'internal/store')
| -rw-r--r-- | internal/store/sqlite_test.go | 986 |
1 files changed, 986 insertions, 0 deletions
diff --git a/internal/store/sqlite_test.go b/internal/store/sqlite_test.go index 9aef09d..69d188a 100644 --- a/internal/store/sqlite_test.go +++ b/internal/store/sqlite_test.go @@ -689,3 +689,989 @@ func TestResolveBug_NonExistent(t *testing.T) { t.Errorf("ResolveBug on non-existent bug should not error, got: %v", err) } } + +// ============================================================================= +// User Shopping Items Tests +// ============================================================================= + +func setupTestStoreWithShopping(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS user_shopping_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + store TEXT NOT NULL, + checked INTEGER DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS shopping_item_checks ( + source TEXT NOT NULL, + item_id TEXT NOT NULL, + checked INTEGER DEFAULT 0, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (source, item_id) + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestUserShoppingItems_CRUD(t *testing.T) { + store := setupTestStoreWithShopping(t) + defer func() { _ = store.Close() }() + + // Save items + if err := store.SaveUserShoppingItem("Milk", "Costco"); err != nil { + t.Fatalf("Failed to save item: %v", err) + } + if err := store.SaveUserShoppingItem("Bread", "Safeway"); err != nil { + t.Fatalf("Failed to save second item: %v", err) + } + + // Get items + items, err := store.GetUserShoppingItems() + if err != nil { + t.Fatalf("Failed to get items: %v", err) + } + if len(items) != 2 { + t.Errorf("Expected 2 items, got %d", len(items)) + } + + // Verify item data + var milkItem UserShoppingItem + for _, item := range items { + if item.Name == "Milk" { + milkItem = item + break + } + } + if milkItem.Name != "Milk" { + t.Error("Could not find Milk item") + } + if milkItem.Store != "Costco" { + t.Errorf("Expected store 'Costco', got '%s'", milkItem.Store) + } + if milkItem.Checked { + t.Error("New item should not be checked") + } + + // Toggle item + if err := store.ToggleUserShoppingItem(milkItem.ID, true); err != nil { + t.Fatalf("Failed to toggle item: %v", err) + } + + items, _ = store.GetUserShoppingItems() + for _, item := range items { + if item.ID == milkItem.ID && !item.Checked { + t.Error("Item should be checked after toggle") + } + } + + // Delete item + if err := store.DeleteUserShoppingItem(milkItem.ID); err != nil { + t.Fatalf("Failed to delete item: %v", err) + } + + items, _ = store.GetUserShoppingItems() + if len(items) != 1 { + t.Errorf("Expected 1 item after delete, got %d", len(items)) + } +} + +func TestShoppingItemChecks_ExternalSources(t *testing.T) { + store := setupTestStoreWithShopping(t) + defer func() { _ = store.Close() }() + + // Set checked for trello item + if err := store.SetShoppingItemChecked("trello", "card-123", true); err != nil { + t.Fatalf("Failed to set trello checked: %v", err) + } + + // Set checked for plantoeat item + if err := store.SetShoppingItemChecked("plantoeat", "pte-456", true); err != nil { + t.Fatalf("Failed to set plantoeat checked: %v", err) + } + + // Get trello checks + trelloChecks, err := store.GetShoppingItemChecks("trello") + if err != nil { + t.Fatalf("Failed to get trello checks: %v", err) + } + if !trelloChecks["card-123"] { + t.Error("Expected trello card to be checked") + } + + // Get plantoeat checks + pteChecks, err := store.GetShoppingItemChecks("plantoeat") + if err != nil { + t.Fatalf("Failed to get plantoeat checks: %v", err) + } + if !pteChecks["pte-456"] { + t.Error("Expected plantoeat item to be checked") + } + + // Uncheck trello item + if err := store.SetShoppingItemChecked("trello", "card-123", false); err != nil { + t.Fatalf("Failed to uncheck trello item: %v", err) + } + + trelloChecks, _ = store.GetShoppingItemChecks("trello") + if trelloChecks["card-123"] { + t.Error("Trello item should be unchecked after update") + } +} + +// ============================================================================= +// Feature Toggles Tests +// ============================================================================= + +func setupTestStoreWithFeatureToggles(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS feature_toggles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + description TEXT, + enabled BOOLEAN DEFAULT FALSE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestFeatureToggles_CRUD(t *testing.T) { + store := setupTestStoreWithFeatureToggles(t) + defer func() { _ = store.Close() }() + + // Create feature toggle + if err := store.CreateFeatureToggle("new_feature", "A new feature", false); err != nil { + t.Fatalf("Failed to create feature toggle: %v", err) + } + + // Get all toggles + toggles, err := store.GetFeatureToggles() + if err != nil { + t.Fatalf("Failed to get feature toggles: %v", err) + } + if len(toggles) != 1 { + t.Errorf("Expected 1 toggle, got %d", len(toggles)) + } + if toggles[0].Name != "new_feature" { + t.Errorf("Expected name 'new_feature', got '%s'", toggles[0].Name) + } + if toggles[0].Enabled { + t.Error("New feature should be disabled") + } + + // Check if enabled + if store.IsFeatureEnabled("new_feature") { + t.Error("IsFeatureEnabled should return false for disabled feature") + } + + // Enable feature + if err := store.SetFeatureEnabled("new_feature", true); err != nil { + t.Fatalf("Failed to enable feature: %v", err) + } + + if !store.IsFeatureEnabled("new_feature") { + t.Error("IsFeatureEnabled should return true after enabling") + } + + // Delete feature + if err := store.DeleteFeatureToggle("new_feature"); err != nil { + t.Fatalf("Failed to delete feature toggle: %v", err) + } + + toggles, _ = store.GetFeatureToggles() + if len(toggles) != 0 { + t.Errorf("Expected 0 toggles after delete, got %d", len(toggles)) + } +} + +func TestIsFeatureEnabled_NonExistent(t *testing.T) { + store := setupTestStoreWithFeatureToggles(t) + defer func() { _ = store.Close() }() + + // Non-existent feature should return false + if store.IsFeatureEnabled("does_not_exist") { + t.Error("Non-existent feature should return false") + } +} + +// ============================================================================= +// Completed Tasks Tests +// ============================================================================= + +func setupTestStoreWithCompletedTasks(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS completed_tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source TEXT NOT NULL, + source_id TEXT NOT NULL, + title TEXT NOT NULL, + due_date TEXT, + completed_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(source, source_id) + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestCompletedTasks_SaveAndGet(t *testing.T) { + store := setupTestStoreWithCompletedTasks(t) + defer func() { _ = store.Close() }() + + now := time.Now() + + // Save completed task with due date + if err := store.SaveCompletedTask("todoist", "task-123", "Buy groceries", &now); err != nil { + t.Fatalf("Failed to save completed task: %v", err) + } + + // Save completed task without due date + if err := store.SaveCompletedTask("trello", "card-456", "Review PR", nil); err != nil { + t.Fatalf("Failed to save second completed task: %v", err) + } + + // Get completed tasks + tasks, err := store.GetCompletedTasks(10) + if err != nil { + t.Fatalf("Failed to get completed tasks: %v", err) + } + if len(tasks) != 2 { + t.Errorf("Expected 2 completed tasks, got %d", len(tasks)) + } + + // Verify task data + var todoistTask models.CompletedTask + for _, task := range tasks { + if task.Source == "todoist" { + todoistTask = task + break + } + } + if todoistTask.Title != "Buy groceries" { + t.Errorf("Expected title 'Buy groceries', got '%s'", todoistTask.Title) + } + if todoistTask.DueDate == nil { + t.Error("Expected due date to be set") + } +} + +func TestCompletedTasks_Limit(t *testing.T) { + store := setupTestStoreWithCompletedTasks(t) + defer func() { _ = store.Close() }() + + // Save multiple tasks + for i := 0; i < 10; i++ { + _ = store.SaveCompletedTask("todoist", "task-"+string(rune('0'+i)), "Task "+string(rune('0'+i)), nil) + } + + // Get with limit + tasks, _ := store.GetCompletedTasks(5) + if len(tasks) != 5 { + t.Errorf("Expected 5 tasks with limit, got %d", len(tasks)) + } +} + +// ============================================================================= +// Source Configuration Tests +// ============================================================================= + +func setupTestStoreWithSourceConfig(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS source_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source TEXT NOT NULL, + item_type TEXT NOT NULL, + item_id TEXT NOT NULL, + item_name TEXT NOT NULL, + enabled BOOLEAN DEFAULT TRUE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(source, item_type, item_id) + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestSourceConfig_UpsertAndGet(t *testing.T) { + store := setupTestStoreWithSourceConfig(t) + defer func() { _ = store.Close() }() + + // Upsert configs + cfg1 := models.SourceConfig{ + Source: "trello", + ItemType: "board", + ItemID: "board-123", + ItemName: "Work Board", + Enabled: true, + } + if err := store.UpsertSourceConfig(cfg1); err != nil { + t.Fatalf("Failed to upsert config: %v", err) + } + + cfg2 := models.SourceConfig{ + Source: "trello", + ItemType: "board", + ItemID: "board-456", + ItemName: "Personal Board", + Enabled: false, + } + if err := store.UpsertSourceConfig(cfg2); err != nil { + t.Fatalf("Failed to upsert second config: %v", err) + } + + // Get all configs + configs, err := store.GetSourceConfigs() + if err != nil { + t.Fatalf("Failed to get configs: %v", err) + } + if len(configs) != 2 { + t.Errorf("Expected 2 configs, got %d", len(configs)) + } + + // Get by source + trelloConfigs, err := store.GetSourceConfigsBySource("trello") + if err != nil { + t.Fatalf("Failed to get trello configs: %v", err) + } + if len(trelloConfigs) != 2 { + t.Errorf("Expected 2 trello configs, got %d", len(trelloConfigs)) + } + + // Get enabled IDs + enabledIDs, err := store.GetEnabledSourceIDs("trello", "board") + if err != nil { + t.Fatalf("Failed to get enabled IDs: %v", err) + } + if len(enabledIDs) != 1 { + t.Errorf("Expected 1 enabled ID, got %d", len(enabledIDs)) + } + if enabledIDs[0] != "board-123" { + t.Errorf("Expected 'board-123', got '%s'", enabledIDs[0]) + } +} + +func TestSourceConfig_SetEnabled(t *testing.T) { + store := setupTestStoreWithSourceConfig(t) + defer func() { _ = store.Close() }() + + // Create a config + cfg := models.SourceConfig{ + Source: "calendar", + ItemType: "calendar", + ItemID: "cal-1", + ItemName: "Primary", + Enabled: true, + } + _ = store.UpsertSourceConfig(cfg) + + // Disable it + if err := store.SetSourceConfigEnabled("calendar", "calendar", "cal-1", false); err != nil { + t.Fatalf("Failed to set enabled: %v", err) + } + + // Verify + enabledIDs, _ := store.GetEnabledSourceIDs("calendar", "calendar") + if len(enabledIDs) != 0 { + t.Error("Expected no enabled calendars after disabling") + } +} + +// ============================================================================= +// Cache Metadata Tests +// ============================================================================= + +func setupTestStoreWithCacheMetadata(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS cache_metadata ( + key TEXT PRIMARY KEY, + last_fetch DATETIME NOT NULL, + ttl_minutes INTEGER DEFAULT 5, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestCacheMetadata_UpdateAndCheck(t *testing.T) { + store := setupTestStoreWithCacheMetadata(t) + defer func() { _ = store.Close() }() + + // Initially no metadata + valid, _ := store.IsCacheValid("test_key") + if valid { + t.Error("Cache should be invalid when no metadata exists") + } + + // Update cache metadata + if err := store.UpdateCacheMetadata("test_key", 5); err != nil { + t.Fatalf("Failed to update cache metadata: %v", err) + } + + // Now cache should be valid + valid, err := store.IsCacheValid("test_key") + if err != nil { + t.Fatalf("Failed to check cache validity: %v", err) + } + if !valid { + t.Error("Cache should be valid after update") + } + + // Get metadata + metadata, err := store.GetCacheMetadata("test_key") + if err != nil { + t.Fatalf("Failed to get cache metadata: %v", err) + } + if metadata == nil { + t.Fatal("Expected metadata to exist") + } + if metadata.TTLMinutes != 5 { + t.Errorf("Expected TTL 5, got %d", metadata.TTLMinutes) + } + + // Invalidate cache + if err := store.InvalidateCache("test_key"); err != nil { + t.Fatalf("Failed to invalidate cache: %v", err) + } + + valid, _ = store.IsCacheValid("test_key") + if valid { + t.Error("Cache should be invalid after invalidation") + } +} + +func TestCacheMetadata_ExpiredCache(t *testing.T) { + store := setupTestStoreWithCacheMetadata(t) + defer func() { _ = store.Close() }() + + // Insert old cache entry directly + oldTime := time.Now().Add(-10 * time.Minute) + _, err := store.db.Exec(` + INSERT INTO cache_metadata (key, last_fetch, ttl_minutes) + VALUES (?, ?, ?) + `, "expired_key", oldTime, 5) + if err != nil { + t.Fatalf("Failed to insert old metadata: %v", err) + } + + // Cache should be invalid (expired) + valid, _ := store.IsCacheValid("expired_key") + if valid { + t.Error("Expired cache should be invalid") + } +} + +// ============================================================================= +// Sync Token Tests +// ============================================================================= + +func setupTestStoreWithSyncTokens(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS sync_tokens ( + service TEXT PRIMARY KEY, + token TEXT NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestSyncTokens_SetGetClear(t *testing.T) { + store := setupTestStoreWithSyncTokens(t) + defer func() { _ = store.Close() }() + + // Get non-existent token + token, err := store.GetSyncToken("todoist") + if err != nil { + t.Fatalf("Failed to get token: %v", err) + } + if token != "" { + t.Errorf("Expected empty token, got '%s'", token) + } + + // Set token + if err := store.SetSyncToken("todoist", "sync-token-123"); err != nil { + t.Fatalf("Failed to set token: %v", err) + } + + // Get token + token, err = store.GetSyncToken("todoist") + if err != nil { + t.Fatalf("Failed to get token after set: %v", err) + } + if token != "sync-token-123" { + t.Errorf("Expected 'sync-token-123', got '%s'", token) + } + + // Clear token + if err := store.ClearSyncToken("todoist"); err != nil { + t.Fatalf("Failed to clear token: %v", err) + } + + token, _ = store.GetSyncToken("todoist") + if token != "" { + t.Errorf("Expected empty token after clear, got '%s'", token) + } +} + +// ============================================================================= +// Agent Session Tests +// ============================================================================= + +func setupTestStoreWithAgents(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + agent_id TEXT UNIQUE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + last_seen DATETIME, + trusted BOOLEAN DEFAULT 1 + ); + CREATE TABLE IF NOT EXISTS agent_sessions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_token TEXT UNIQUE NOT NULL, + agent_name TEXT NOT NULL, + agent_id TEXT NOT NULL, + status TEXT DEFAULT 'pending', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + expires_at DATETIME NOT NULL, + session_token TEXT, + session_expires_at DATETIME + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestAgentSession_CreateAndRetrieve(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + expiresAt := time.Now().Add(5 * time.Minute) + session := &models.AgentSession{ + RequestToken: "req-token-123", + AgentName: "TestAgent", + AgentID: "agent-uuid-123", + ExpiresAt: expiresAt, + } + + // Create session + if err := store.CreateAgentSession(session); err != nil { + t.Fatalf("Failed to create session: %v", err) + } + if session.ID == 0 { + t.Error("Session ID should be set after create") + } + + // Get by request token + retrieved, err := store.GetAgentSessionByRequestToken("req-token-123") + if err != nil { + t.Fatalf("Failed to get session: %v", err) + } + if retrieved == nil { + t.Fatal("Expected session to exist") + } + if retrieved.AgentName != "TestAgent" { + t.Errorf("Expected name 'TestAgent', got '%s'", retrieved.AgentName) + } + if retrieved.Status != "pending" { + t.Errorf("Expected status 'pending', got '%s'", retrieved.Status) + } + + // Get pending by agent ID + pending, err := store.GetPendingAgentSessionByAgentID("agent-uuid-123") + if err != nil { + t.Fatalf("Failed to get pending session: %v", err) + } + if pending == nil { + t.Fatal("Expected pending session to exist") + } +} + +func TestAgentSession_ApproveAndDeny(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + // Create two sessions + session1 := &models.AgentSession{ + RequestToken: "approve-token", + AgentName: "Agent1", + AgentID: "agent-1", + ExpiresAt: time.Now().Add(5 * time.Minute), + } + session2 := &models.AgentSession{ + RequestToken: "deny-token", + AgentName: "Agent2", + AgentID: "agent-2", + ExpiresAt: time.Now().Add(5 * time.Minute), + } + _ = store.CreateAgentSession(session1) + _ = store.CreateAgentSession(session2) + + // Approve session1 + sessionExpiry := time.Now().Add(1 * time.Hour) + if err := store.ApproveAgentSession("approve-token", "session-token-abc", sessionExpiry); err != nil { + t.Fatalf("Failed to approve session: %v", err) + } + + // Verify approval + approved, _ := store.GetAgentSessionByRequestToken("approve-token") + if approved.Status != "approved" { + t.Errorf("Expected status 'approved', got '%s'", approved.Status) + } + if approved.SessionToken != "session-token-abc" { + t.Errorf("Expected session token 'session-token-abc', got '%s'", approved.SessionToken) + } + + // Deny session2 + if err := store.DenyAgentSession("deny-token"); err != nil { + t.Fatalf("Failed to deny session: %v", err) + } + + denied, _ := store.GetAgentSessionByRequestToken("deny-token") + if denied.Status != "denied" { + t.Errorf("Expected status 'denied', got '%s'", denied.Status) + } +} + +func TestAgentSession_GetBySessionToken(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + session := &models.AgentSession{ + RequestToken: "req-for-session", + AgentName: "SessionAgent", + AgentID: "session-agent", + ExpiresAt: time.Now().Add(5 * time.Minute), + } + _ = store.CreateAgentSession(session) + _ = store.ApproveAgentSession("req-for-session", "active-session", time.Now().Add(1*time.Hour)) + + // Get by session token + retrieved, err := store.GetAgentSessionBySessionToken("active-session") + if err != nil { + t.Fatalf("Failed to get by session token: %v", err) + } + if retrieved == nil { + t.Fatal("Expected session to exist") + } + if retrieved.AgentName != "SessionAgent" { + t.Errorf("Expected 'SessionAgent', got '%s'", retrieved.AgentName) + } +} + +func TestAgentSession_GetPending(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + // Create pending sessions + for i := 0; i < 3; i++ { + session := &models.AgentSession{ + RequestToken: "pending-" + string(rune('0'+i)), + AgentName: "Agent" + string(rune('0'+i)), + AgentID: "agent-" + string(rune('0'+i)), + ExpiresAt: time.Now().Add(5 * time.Minute), + } + _ = store.CreateAgentSession(session) + } + + // Get pending sessions + pending, err := store.GetPendingAgentSessions() + if err != nil { + t.Fatalf("Failed to get pending sessions: %v", err) + } + if len(pending) != 3 { + t.Errorf("Expected 3 pending sessions, got %d", len(pending)) + } +} + +func TestAgentSession_Invalidate(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + // Create sessions for same agent + for i := 0; i < 2; i++ { + session := &models.AgentSession{ + RequestToken: "inv-" + string(rune('0'+i)), + AgentName: "SameAgent", + AgentID: "same-agent", + ExpiresAt: time.Now().Add(5 * time.Minute), + } + _ = store.CreateAgentSession(session) + } + + // Invalidate all sessions for agent + if err := store.InvalidatePreviousAgentSessions("same-agent"); err != nil { + t.Fatalf("Failed to invalidate sessions: %v", err) + } + + // Verify no pending sessions + pending, _ := store.GetPendingAgentSessions() + for _, s := range pending { + if s.AgentID == "same-agent" { + t.Error("Session should be invalidated") + } + } +} + +// ============================================================================= +// Agent Tests +// ============================================================================= + +func TestAgent_CreateAndRetrieve(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + // Create agent + if err := store.CreateOrUpdateAgent("TestBot", "bot-uuid-123"); err != nil { + t.Fatalf("Failed to create agent: %v", err) + } + + // Get by agent ID + agent, err := store.GetAgentByAgentID("bot-uuid-123") + if err != nil { + t.Fatalf("Failed to get agent: %v", err) + } + if agent == nil { + t.Fatal("Expected agent to exist") + } + if agent.Name != "TestBot" { + t.Errorf("Expected name 'TestBot', got '%s'", agent.Name) + } + if !agent.Trusted { + t.Error("New agent should be trusted by default") + } + + // Get by name + byName, err := store.GetAgentByName("TestBot") + if err != nil { + t.Fatalf("Failed to get agent by name: %v", err) + } + if byName == nil { + t.Fatal("Expected agent to exist by name") + } + + // Get all agents + all, err := store.GetAllAgents() + if err != nil { + t.Fatalf("Failed to get all agents: %v", err) + } + if len(all) != 1 { + t.Errorf("Expected 1 agent, got %d", len(all)) + } +} + +func TestAgent_UpdateLastSeen(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + _ = store.CreateOrUpdateAgent("SeenBot", "seen-uuid") + + // Update last seen + if err := store.UpdateAgentLastSeen("seen-uuid"); err != nil { + t.Fatalf("Failed to update last seen: %v", err) + } + + agent, _ := store.GetAgentByAgentID("seen-uuid") + if agent.LastSeen == nil { + t.Error("LastSeen should be set after update") + } +} + +func TestAgent_TrustLevels(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + // Check trust for unknown agent (new) + trust, err := store.CheckAgentTrust("UnknownBot", "unknown-uuid") + if err != nil { + t.Fatalf("Failed to check trust: %v", err) + } + if trust != models.AgentTrustNew { + t.Errorf("Expected AgentTrustNew, got %v", trust) + } + + // Create agent + _ = store.CreateOrUpdateAgent("TrustBot", "trust-uuid") + + // Check trust for recognized agent + trust, _ = store.CheckAgentTrust("TrustBot", "trust-uuid") + if trust != models.AgentTrustRecognized { + t.Errorf("Expected AgentTrustRecognized, got %v", trust) + } + + // Check trust for suspicious agent (same name, different uuid) + trust, _ = store.CheckAgentTrust("TrustBot", "different-uuid") + if trust != models.AgentTrustSuspicious { + t.Errorf("Expected AgentTrustSuspicious, got %v", trust) + } +} + +func TestAgent_Revoke(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + _ = store.CreateOrUpdateAgent("RevokeBot", "revoke-uuid") + + // Verify agent exists + agent, _ := store.GetAgentByAgentID("revoke-uuid") + if agent == nil { + t.Fatal("Agent should exist") + } + + // Revoke agent + if err := store.RevokeAgent("revoke-uuid"); err != nil { + t.Fatalf("Failed to revoke agent: %v", err) + } + + // After revoke, agent should still exist but be in different state + // (revoke doesn't delete, just marks somehow - let's verify it doesn't error) +} + +func TestAgent_NonExistent(t *testing.T) { + store := setupTestStoreWithAgents(t) + defer func() { _ = store.Close() }() + + // Get non-existent agent + agent, err := store.GetAgentByAgentID("does-not-exist") + if err != nil { + t.Fatalf("Should not error for non-existent agent: %v", err) + } + if agent != nil { + t.Error("Agent should be nil for non-existent") + } + + // Get non-existent by name + byName, err := store.GetAgentByName("unknown-name") + if err != nil { + t.Fatalf("Should not error for non-existent name: %v", err) + } + if byName != nil { + t.Error("Agent should be nil for non-existent name") + } + + // Check trust for non-existent (should be new) + trust, _ := store.CheckAgentTrust("UnknownBot", "unknown-uuid") + if trust != models.AgentTrustNew { + t.Errorf("Expected AgentTrustNew for unknown, got %v", trust) + } +} |
