summaryrefslogtreecommitdiff
path: root/internal/store/sqlite_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-02-03 15:16:35 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-02-03 15:16:35 -1000
commit25a5b7ecf9ddd31da54e91f87988b77aea857571 (patch)
tree30654edbdd966cea316a5f54a99474aad337cf58 /internal/store/sqlite_test.go
parent9f35f7149d8fb790bbe8e4f0ee74f895aea1fc58 (diff)
Add comprehensive test coverage across packages
New test files: - api/http_test.go: HTTP client and error handling tests - config/config_test.go: Configuration loading and validation tests - middleware/security_test.go: Security middleware tests - models/atom_test.go: Atom model and conversion tests Expanded test coverage: - api/todoist_test.go: Todoist API client tests - api/trello_test.go: Trello API client tests - auth/auth_test.go: Authentication and CSRF tests - handlers/timeline_logic_test.go: Timeline building logic tests - store/sqlite_test.go: SQLite store operations tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/store/sqlite_test.go')
-rw-r--r--internal/store/sqlite_test.go986
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)
+ }
+}