package tools import ( "context" "encoding/json" "os" "testing" "git.gobha.me/xcaliber/chat-switchboard/database" ) func TestMain(m *testing.M) { teardown := database.SetupTestDB() code := m.Run() teardown() os.Exit(code) } // ── Note Tool Execution (requires DB) ─────── func TestNoteCreateExecute(t *testing.T) { database.RequireTestDB(t) database.TruncateAll(t) userID := database.SeedTestUser(t, "tooluser", "tool@test.com") channelID := database.SeedTestChannel(t, userID, "Tool Test") ctx := context.Background() execCtx := ExecutionContext{UserID: userID, ChannelID: channelID} tool := Get("note_create") if tool == nil { t.Fatal("note_create not registered") } args := `{"title":"Test Note","content":"Created via tool","folder":"/tools","tags":["test","ci"]}` result, err := tool.Execute(ctx, execCtx, args) if err != nil { t.Fatalf("Execute: %v", err) } var resp map[string]interface{} if err := json.Unmarshal([]byte(result), &resp); err != nil { t.Fatalf("Invalid JSON result: %v\nRaw: %s", err, result) } if resp["id"] == nil || resp["id"] == "" { t.Error("Expected id in result") } if resp["title"] != "Test Note" { t.Errorf("Expected title='Test Note', got %v", resp["title"]) } } func TestNoteListExecute(t *testing.T) { database.RequireTestDB(t) database.TruncateAll(t) userID := database.SeedTestUser(t, "listuser", "list@test.com") channelID := database.SeedTestChannel(t, userID, "List Test") ctx := context.Background() execCtx := ExecutionContext{UserID: userID, ChannelID: channelID} // Create two notes first createTool := Get("note_create") createTool.Execute(ctx, execCtx, `{"title":"Note A","content":"Alpha","folder":"/a"}`) createTool.Execute(ctx, execCtx, `{"title":"Note B","content":"Beta","folder":"/b"}`) // List all listTool := Get("note_list") result, err := listTool.Execute(ctx, execCtx, `{}`) if err != nil { t.Fatalf("Execute: %v", err) } var resp map[string]interface{} json.Unmarshal([]byte(result), &resp) count := resp["count"].(float64) if count != 2 { t.Errorf("Expected count=2, got %.0f", count) } // List filtered by folder result, err = listTool.Execute(ctx, execCtx, `{"folder":"/a"}`) if err != nil { t.Fatalf("Execute with folder: %v", err) } json.Unmarshal([]byte(result), &resp) count = resp["count"].(float64) if count != 1 { t.Errorf("Expected count=1 for folder /a, got %.0f", count) } } func TestNoteSearchExecute(t *testing.T) { database.RequireTestDB(t) database.TruncateAll(t) userID := database.SeedTestUser(t, "searchuser", "search@test.com") channelID := database.SeedTestChannel(t, userID, "Search Test") ctx := context.Background() execCtx := ExecutionContext{UserID: userID, ChannelID: channelID} createTool := Get("note_create") createTool.Execute(ctx, execCtx, `{"title":"Kubernetes Guide","content":"How to deploy pods and services"}`) createTool.Execute(ctx, execCtx, `{"title":"Cooking Tips","content":"Season your cast iron pan properly"}`) searchTool := Get("note_search") result, err := searchTool.Execute(ctx, execCtx, `{"query":"kubernetes pods"}`) if err != nil { t.Fatalf("Execute: %v", err) } var resp map[string]interface{} json.Unmarshal([]byte(result), &resp) count := resp["count"].(float64) if count != 1 { t.Errorf("Expected 1 search result for 'kubernetes pods', got %.0f", count) } } func TestNoteUpdateExecute(t *testing.T) { database.RequireTestDB(t) database.TruncateAll(t) userID := database.SeedTestUser(t, "updateuser", "update@test.com") channelID := database.SeedTestChannel(t, userID, "Update Test") ctx := context.Background() execCtx := ExecutionContext{UserID: userID, ChannelID: channelID} // Create a note createTool := Get("note_create") result, _ := createTool.Execute(ctx, execCtx, `{"title":"Original","content":"Original content"}`) var created map[string]interface{} json.Unmarshal([]byte(result), &created) noteID := created["id"].(string) // Update title updateTool := Get("note_update") result, err := updateTool.Execute(ctx, execCtx, `{"note_id":"`+noteID+`","title":"Renamed"}`) if err != nil { t.Fatalf("Update title: %v", err) } var updated map[string]interface{} json.Unmarshal([]byte(result), &updated) if updated["title"] != "Renamed" { t.Errorf("Expected title='Renamed', got %v", updated["title"]) } // Append content result, err = updateTool.Execute(ctx, execCtx, `{"note_id":"`+noteID+`","content":"\nNew line","mode":"append"}`) if err != nil { t.Fatalf("Append: %v", err) } // Verify via direct DB read var row string database.DB.QueryRow("SELECT content FROM notes WHERE id = $1", noteID).Scan(&row) if row != "Original content\nNew line" { t.Errorf("Append: expected concatenated content, got %q", row) } } func TestNoteCreateMissingRequiredField(t *testing.T) { database.RequireTestDB(t) ctx := context.Background() execCtx := ExecutionContext{UserID: "test", ChannelID: "test"} tool := Get("note_create") // Missing content result, err := tool.Execute(ctx, execCtx, `{"title":"No Content"}`) if err == nil { // Tools return errors in content, not as Go errors var resp map[string]interface{} json.Unmarshal([]byte(result), &resp) if resp["error"] == nil { t.Error("Expected error for missing content") } } } func TestNoteUpdateNonexistent(t *testing.T) { database.RequireTestDB(t) database.TruncateAll(t) userID := database.SeedTestUser(t, "ghostuser", "ghost@test.com") ctx := context.Background() execCtx := ExecutionContext{UserID: userID, ChannelID: "test"} tool := Get("note_update") _, err := tool.Execute(ctx, execCtx, `{"note_id":"00000000-0000-0000-0000-000000000000","title":"Nope"}`) if err == nil { t.Log("Update of nonexistent note should return error or empty result") } }