From ad61d55b4ad55f44186db1036cd790d2e149cc0b Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Mon, 30 Mar 2026 20:57:21 -0500 Subject: [PATCH] fix: resolve all 610 ESLint errors across the codebase - Replace 233 `no-explicit-any` with proper types (unknown, specific interfaces) - Add meaningful JSDoc descriptions, @param, and @returns to 370+ functions - Remove 20 unused variables/imports - Fix param name mismatches, member ordering, import order, and tag-lines - All 1624 tests still pass Co-Authored-By: Claude Opus 4.6 (1M context) --- app/api/accounts/[id]/route.ts | 3 +- app/api/admins/coding/pr/route.ts | 9 +- app/api/admins/coding/slack/route.ts | 9 +- app/api/admins/content/slack/route.ts | 9 +- app/api/admins/privy/route.ts | 8 ++ app/api/coding-agent/callback/route.ts | 1 + app/api/coding-agent/github/route.ts | 1 + app/api/connectors/route.ts | 9 +- app/api/songs/analyze/presets/route.ts | 1 + app/api/transcribe/route.ts | 9 ++ lib/accounts/validateOverrideAccountId.ts | 8 +- .../getContentSlackTagsHandler.test.ts | 4 +- lib/admins/content/extractVideoLinks.ts | 7 +- lib/admins/content/fetchThreadVideoLinks.ts | 7 +- .../validateGetAdminEmailsQuery.test.ts | 6 + .../getPrMergedStatusHandler.test.ts | 6 + lib/admins/pr/getPrStatusHandler.ts | 3 + .../__tests__/getPrivyLoginsHandler.test.ts | 4 +- .../validateGetPrivyLoginsQuery.test.ts | 4 +- lib/admins/privy/countNewAccounts.ts | 4 + lib/admins/privy/fetchPrivyLogins.ts | 8 ++ lib/admins/privy/getCutoffMs.ts | 3 + lib/admins/privy/getLatestVerifiedAt.ts | 3 + lib/admins/privy/toMs.ts | 3 + .../__tests__/createSlackTagsHandler.test.ts | 4 +- .../__tests__/getSlackTagsHandler.test.ts | 4 +- .../validateGetSlackTagsQuery.test.ts | 4 +- lib/admins/slack/createSlackTagsHandler.ts | 5 +- lib/admins/slack/extractGithubPrUrls.ts | 7 +- lib/admins/slack/fetchThreadPullRequests.ts | 7 +- lib/admins/slack/getCutoffTs.ts | 3 +- .../__tests__/getGeneralAgent.test.ts | 20 +-- lib/ai/__tests__/getAvailableModels.test.ts | 4 +- lib/ai/__tests__/getModel.test.ts | 4 +- lib/ai/getAvailableModels.ts | 2 + lib/ai/getModel.ts | 1 + lib/ai/isEmbedModel.ts | 3 + .../__tests__/updateArtistSocials.test.ts | 2 - .../__tests__/createArtistPostHandler.test.ts | 7 + .../validateCreateArtistBody.test.ts | 7 + lib/artists/createArtistInDb.ts | 2 +- .../__tests__/validateAuthContext.test.ts | 6 + lib/catalog/formatCatalogSongsAsCSV.ts | 5 +- lib/catalog/getCatalogDataAsCSV.ts | 6 +- lib/catalog/getCatalogSongs.ts | 13 +- lib/catalog/getCatalogs.ts | 6 + .../__tests__/handleChatCompletion.test.ts | 21 ++- lib/chat/__tests__/handleChatGenerate.test.ts | 67 +++++---- lib/chat/__tests__/handleChatStream.test.ts | 31 ++-- .../integration/chatEndToEnd.test.ts | 124 ++++++++++------ lib/chat/__tests__/saveChatCompletion.test.ts | 4 +- lib/chat/__tests__/setupChatRequest.test.ts | 14 +- .../__tests__/validateChatRequest.test.ts | 134 +++++++++--------- .../__tests__/getExecutedToolTimeline.test.ts | 15 +- .../__tests__/getPrepareStepResult.test.ts | 21 +-- .../toolChains/__tests__/toolChains.test.ts | 5 +- .../getReleaseReportReferenceMessage.ts | 4 +- lib/chat/toolChains/getPrepareStepResult.ts | 5 +- lib/chats/__tests__/createChatHandler.test.ts | 6 + lib/chats/__tests__/generateChatTitle.test.ts | 30 ++-- lib/chats/__tests__/getChatsHandler.test.ts | 1 - .../__tests__/validateCreateChatBody.test.ts | 22 ++- .../__tests__/validateGetChatsRequest.test.ts | 1 - lib/chats/compactChatsHandler.ts | 2 +- lib/chats/processCompactChatRequest.ts | 7 +- lib/chats/validateUpdateChatBody.ts | 2 +- .../handleCodingAgentCallback.test.ts | 6 +- .../__tests__/handleGitHubWebhook.test.ts | 8 ++ lib/coding-agent/__tests__/handlers.test.ts | 4 +- .../__tests__/mergeGithubBranch.test.ts | 2 +- .../__tests__/mergeGithubPR.test.ts | 2 +- .../__tests__/onMergeAction.test.ts | 4 +- .../__tests__/onMergeTestToMainAction.test.ts | 7 +- .../__tests__/onSubscribedMessage.test.ts | 4 +- .../__tests__/resolvePRState.test.ts | 6 +- lib/coding-agent/buildMergeTestToMainCard.ts | 1 + lib/coding-agent/buildPRCard.ts | 1 + lib/coding-agent/buildTaskCard.ts | 1 + lib/coding-agent/encodeGitHubThreadId.ts | 3 + lib/coding-agent/extractPRComment.ts | 1 + lib/coding-agent/handleGitHubWebhook.ts | 1 + lib/coding-agent/handleMergeSuccess.ts | 2 + lib/coding-agent/handlePRCreated.ts | 4 +- lib/coding-agent/handlers/handleFeedback.ts | 1 + lib/coding-agent/handlers/onMergeAction.ts | 2 +- .../handlers/onMergeTestToMainAction.ts | 2 +- lib/coding-agent/handlers/onNewMention.ts | 2 +- .../handlers/onSubscribedMessage.ts | 2 +- lib/coding-agent/mergeGithubBranch.ts | 1 + lib/coding-agent/mergeGithubPR.ts | 1 + lib/coding-agent/parseMergeActionId.ts | 3 + .../parseMergeTestToMainActionId.ts | 3 + lib/coding-agent/prState/buildPRStateKey.ts | 5 +- .../prState/deleteCodingAgentPRState.ts | 4 +- .../prState/getCodingAgentPRState.ts | 5 +- .../prState/setCodingAgentPRState.ts | 6 +- lib/coding-agent/resolvePRState.ts | 2 +- lib/coding-agent/verifyGitHubWebhook.ts | 1 + .../whatsApp/isWhatsAppConfigured.ts | 2 + lib/composio/client.ts | 2 + .../connectors/isAllowedArtistConnector.ts | 3 +- lib/composio/getCallbackUrl.ts | 11 +- lib/composio/getFrontendBaseUrl.ts | 2 + .../toolRouter/createToolRouterSession.ts | 1 + .../validateCreateContentBody.test.ts | 6 + lib/content/contentTemplates.ts | 6 + lib/content/createContentHandler.ts | 3 + lib/content/getArtistContentReadiness.ts | 6 + lib/content/getArtistFileTree.ts | 4 + lib/content/getArtistRootPrefix.ts | 8 ++ lib/content/getContentEstimateHandler.ts | 3 +- lib/content/getContentTemplatesHandler.ts | 3 +- lib/content/getContentValidateHandler.ts | 10 +- lib/content/isCompletedRun.ts | 6 + lib/content/persistCreateContentRunVideo.ts | 4 + lib/content/validateCreateContentBody.ts | 4 + .../validateGetContentEstimateQuery.ts | 4 + .../validateGetContentValidateQuery.ts | 4 + lib/credits/__tests__/getCreditUsage.test.ts | 10 +- lib/credits/getCreditUsage.ts | 6 +- lib/credits/handleChatCredits.ts | 8 +- .../__tests__/validateNewEmailMemory.test.ts | 4 +- lib/emails/processAndSendEmail.ts | 3 + lib/evals/callChatFunctions.ts | 8 +- lib/evals/callChatFunctionsWithResult.ts | 7 +- lib/evals/createToolsCalledScorer.ts | 8 +- lib/evals/extractTextFromResult.ts | 7 +- lib/evals/extractTextResultFromSteps.ts | 7 +- lib/evals/getCatalogSongsCountExpected.ts | 7 + lib/evals/getSpotifyFollowersExpected.ts | 8 ++ lib/evals/scorers/CatalogAvailability.ts | 10 +- lib/evals/scorers/QuestionAnswered.ts | 9 +- lib/evals/scorers/ToolsCalled.ts | 13 +- .../getFlamingoPresetsHandler.test.ts | 5 + lib/flamingo/getFlamingoPresetsHandler.ts | 1 + lib/flamingo/postFlamingoGenerateHandler.ts | 6 +- .../createOrUpdateFileContent.test.ts | 3 +- .../__tests__/expandSubmoduleEntries.test.ts | 2 +- lib/github/expandSubmoduleEntries.ts | 12 +- lib/github/getRepoGitModules.ts | 9 +- lib/github/resolveSubmodulePath.ts | 7 +- lib/mcp/__tests__/getMcpTools.test.ts | 2 +- lib/mcp/resolveAccountId.ts | 4 +- .../registerCreateNewArtistTool.test.ts | 7 +- .../flamingo/registerAnalyzeMusicTool.ts | 2 +- .../__tests__/registerGetPulsesTool.test.ts | 5 +- .../__tests__/registerUpdatePulseTool.test.ts | 7 +- .../registerPromptSandboxTool.test.ts | 7 +- .../registerGetTaskRunStatusTool.test.ts | 7 +- .../transcribe/registerTranscribeAudioTool.ts | 6 + .../__tests__/convertToUiMessages.test.ts | 2 +- lib/messages/__tests__/getTextContent.test.ts | 4 +- .../createNotificationHandler.test.ts | 6 + .../validateCreateNotificationBody.test.ts | 7 + lib/prompts/__tests__/getSystemPrompt.test.ts | 19 +-- lib/prompts/getSystemPrompt.ts | 22 +-- .../__tests__/deleteSandboxHandler.test.ts | 2 + .../postSandboxesFilesHandler.test.ts | 2 +- .../validateDeleteSandboxBody.test.ts | 2 + lib/slack/getBotChannels.ts | 3 + lib/slack/getBotUserId.ts | 3 + lib/slack/getSlackUserInfo.ts | 4 + lib/spotify/getSpotifyFollowers.ts | 5 +- .../account_artist_ids/getAccountArtistIds.ts | 10 +- .../__tests__/selectAccountSandboxes.test.ts | 2 - .../getAccountWorkspaceIds.ts | 2 +- lib/supabase/files/createFileRecord.ts | 5 +- lib/supabase/files/selectFileByStorageKey.ts | 7 +- .../song_artists/insertSongArtists.ts | 2 + .../storage/createSignedFileUrlByKey.ts | 7 +- lib/supabase/storage/uploadFileByKey.ts | 8 +- .../enrichTaskWithTriggerInfo.test.ts | 5 +- lib/tasks/__tests__/getTaskRunHandler.test.ts | 5 + .../__tests__/validateGetTaskRunQuery.test.ts | 3 + .../__tests__/validateGetTasksQuery.test.ts | 6 + lib/transcribe/processAudioTranscription.ts | 9 ++ lib/transcribe/saveAudioToFiles.ts | 6 + lib/transcribe/saveTranscriptToFiles.ts | 6 + lib/transcribe/types.ts | 3 + lib/trigger/fetchTriggerRuns.ts | 2 +- lib/trigger/triggerCreateContent.ts | 3 + lib/workspaces/createWorkspaceInDb.ts | 2 +- 182 files changed, 926 insertions(+), 411 deletions(-) diff --git a/app/api/accounts/[id]/route.ts b/app/api/accounts/[id]/route.ts index 3ed40db1..cc35dbe4 100644 --- a/app/api/accounts/[id]/route.ts +++ b/app/api/accounts/[id]/route.ts @@ -23,7 +23,8 @@ export async function OPTIONS() { * - id (required): The unique identifier of the account (UUID) * * @param request - The request object - * @param params - Route params containing the account ID + * @param root1 - Destructured route context + * @param root1.params - Promise resolving to route params containing the account ID * @returns A NextResponse with account data */ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { diff --git a/app/api/admins/coding/pr/route.ts b/app/api/admins/coding/pr/route.ts index 33aef3f9..73439572 100644 --- a/app/api/admins/coding/pr/route.ts +++ b/app/api/admins/coding/pr/route.ts @@ -10,13 +10,18 @@ import { getPrStatusHandler } from "@/lib/admins/pr/getPrStatusHandler"; * Uses the GitHub REST API to check each PR's state. * Requires admin authentication. * - * @param request + * @param request - The incoming request with pull_requests query params + * @returns A NextResponse with the PR status for each provided URL */ export async function GET(request: NextRequest): Promise { return getPrStatusHandler(request); } -/** CORS preflight handler. */ +/** + * CORS preflight handler. + * + * @returns A NextResponse with CORS headers allowing cross-origin access + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/admins/coding/slack/route.ts b/app/api/admins/coding/slack/route.ts index ea880d30..4631dc62 100644 --- a/app/api/admins/coding/slack/route.ts +++ b/app/api/admins/coding/slack/route.ts @@ -9,12 +9,19 @@ import { getSlackTagsHandler } from "@/lib/admins/slack/getSlackTagsHandler"; * Pulls directly from the Slack API as the source of truth. * Supports period filtering: all (default), daily, weekly, monthly. * Requires admin authentication. + * + * @param request - The incoming request with optional period query param + * @returns A NextResponse with Slack tagging analytics data */ export async function GET(request: NextRequest): Promise { return getSlackTagsHandler(request); } -/** CORS preflight handler. */ +/** + * CORS preflight handler. + * + * @returns A NextResponse with CORS headers allowing cross-origin access + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/admins/content/slack/route.ts b/app/api/admins/content/slack/route.ts index 35ba38f7..2c1d7a14 100644 --- a/app/api/admins/content/slack/route.ts +++ b/app/api/admins/content/slack/route.ts @@ -10,13 +10,18 @@ import { getContentSlackTagsHandler } from "@/lib/admins/content/getContentSlack * Supports period filtering: all (default), daily, weekly, monthly. * Requires admin authentication. * - * @param request + * @param request - The incoming request with optional period query param + * @returns A NextResponse with Slack tagging analytics data */ export async function GET(request: NextRequest): Promise { return getContentSlackTagsHandler(request); } -/** CORS preflight handler. */ +/** + * CORS preflight handler. + * + * @returns A NextResponse with CORS headers allowing cross-origin access + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/admins/privy/route.ts b/app/api/admins/privy/route.ts index 073bac60..a5c00bb3 100644 --- a/app/api/admins/privy/route.ts +++ b/app/api/admins/privy/route.ts @@ -8,11 +8,19 @@ import { getPrivyLoginsHandler } from "@/lib/admins/privy/getPrivyLoginsHandler" * Returns Privy login statistics for the requested time period. * Supports daily (last 24h), weekly (last 7 days), and monthly (last 30 days) periods. * Requires admin authentication. + * + * @param request - The incoming request with optional period query param + * @returns A NextResponse with Privy login statistics */ export async function GET(request: NextRequest): Promise { return getPrivyLoginsHandler(request); } +/** + * CORS preflight handler. + * + * @returns A NextResponse with CORS headers allowing cross-origin access + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/coding-agent/callback/route.ts b/app/api/coding-agent/callback/route.ts index 2582611f..002252fa 100644 --- a/app/api/coding-agent/callback/route.ts +++ b/app/api/coding-agent/callback/route.ts @@ -9,6 +9,7 @@ import { handleCodingAgentCallback } from "@/lib/coding-agent/handleCodingAgentC * Receives task results and posts them back to the Slack thread. * * @param request - The incoming callback request + * @returns A NextResponse indicating success or failure of the callback processing */ export async function POST(request: NextRequest) { await codingAgentBot.initialize(); diff --git a/app/api/coding-agent/github/route.ts b/app/api/coding-agent/github/route.ts index f1d615a7..09b78b8d 100644 --- a/app/api/coding-agent/github/route.ts +++ b/app/api/coding-agent/github/route.ts @@ -8,6 +8,7 @@ import { handleGitHubWebhook } from "@/lib/coding-agent/handleGitHubWebhook"; * Receives issue_comment events and triggers update-pr when the bot is mentioned. * * @param request - The incoming GitHub webhook request + * @returns A NextResponse indicating whether the webhook was processed */ export async function POST(request: NextRequest) { return handleGitHubWebhook(request); diff --git a/app/api/connectors/route.ts b/app/api/connectors/route.ts index 9a20d83a..95489cd0 100644 --- a/app/api/connectors/route.ts +++ b/app/api/connectors/route.ts @@ -7,6 +7,8 @@ import { disconnectConnectorHandler } from "@/lib/composio/connectors/disconnect /** * OPTIONS handler for CORS preflight requests. + * + * @returns A NextResponse with CORS headers allowing cross-origin access */ export async function OPTIONS() { return new NextResponse(null, { @@ -25,7 +27,7 @@ export async function OPTIONS() { * * Authentication: x-api-key OR Authorization Bearer token required. * - * @param request + * @param request - The incoming request with optional account_id query param * @returns List of connectors with connection status */ export async function GET(request: NextRequest) { @@ -44,7 +46,7 @@ export async function GET(request: NextRequest) { * - callback_url: Optional custom callback URL after OAuth * - account_id: Optional account ID for account-specific connections * - * @param request + * @param request - The incoming request with connector slug and optional callback_url in the body * @returns The redirect URL for OAuth authorization */ export async function POST(request: NextRequest) { @@ -62,7 +64,8 @@ export async function POST(request: NextRequest) { * * Authentication: x-api-key OR Authorization Bearer token required. * - * @param request + * @param request - The incoming request with connected_account_id in the body + * @returns A NextResponse confirming the connector was disconnected */ export async function DELETE(request: NextRequest) { return disconnectConnectorHandler(request); diff --git a/app/api/songs/analyze/presets/route.ts b/app/api/songs/analyze/presets/route.ts index 8baccd38..06dd3d55 100644 --- a/app/api/songs/analyze/presets/route.ts +++ b/app/api/songs/analyze/presets/route.ts @@ -28,6 +28,7 @@ export async function OPTIONS() { * - status: "success" * - presets: Array of { name, label, description, requiresAudio, responseFormat } * + * @param request - The incoming request with authentication headers * @returns A NextResponse with the list of available presets */ export async function GET(request: NextRequest): Promise { diff --git a/app/api/transcribe/route.ts b/app/api/transcribe/route.ts index 28cf4261..c5c0b970 100644 --- a/app/api/transcribe/route.ts +++ b/app/api/transcribe/route.ts @@ -2,6 +2,15 @@ import { NextRequest, NextResponse } from "next/server"; import { processAudioTranscription } from "@/lib/transcribe/processAudioTranscription"; import { formatTranscriptionError } from "@/lib/transcribe/types"; +/** + * POST /api/transcribe + * + * Fetches an audio file from the provided URL, transcribes it using OpenAI Whisper, + * and saves both the audio and transcript as file records linked to the artist account. + * + * @param req - The incoming request containing audio_url, account_id, artist_account_id, title, and include_timestamps + * @returns A NextResponse with the created audio file record, transcript file record, and transcription text + */ export async function POST(req: NextRequest) { try { const body = await req.json(); diff --git a/lib/accounts/validateOverrideAccountId.ts b/lib/accounts/validateOverrideAccountId.ts index 8f52cde0..ef4373f4 100644 --- a/lib/accounts/validateOverrideAccountId.ts +++ b/lib/accounts/validateOverrideAccountId.ts @@ -18,11 +18,9 @@ export type ValidateOverrideAccountIdResult = { * Used when an org API key wants to create resources on behalf of another account. * Checks that the API key belongs to an org with access to the target account. * - * @param params.apiKey - The x-api-key header value - * @param params.targetAccountId - The accountId to override to - * @param root0 - * @param root0.apiKey - * @param root0.targetAccountId + * @param root0 - The validation parameters + * @param root0.apiKey - The x-api-key header value + * @param root0.targetAccountId - The accountId to override to * @returns The validated accountId or a NextResponse error */ export async function validateOverrideAccountId({ diff --git a/lib/admins/content/__tests__/getContentSlackTagsHandler.test.ts b/lib/admins/content/__tests__/getContentSlackTagsHandler.test.ts index 16dba693..499b609b 100644 --- a/lib/admins/content/__tests__/getContentSlackTagsHandler.test.ts +++ b/lib/admins/content/__tests__/getContentSlackTagsHandler.test.ts @@ -40,8 +40,10 @@ const mockTags = [ ]; /** + * Creates a mock NextRequest for the content Slack tags endpoint with the given period. * - * @param period + * @param period - The time period query parameter (e.g., "all", "daily", "weekly", "monthly"). + * @returns A NextRequest instance targeting the content Slack tags endpoint. */ function makeRequest(period = "all") { return new NextRequest(`https://example.com/api/admins/content/slack?period=${period}`); diff --git a/lib/admins/content/extractVideoLinks.ts b/lib/admins/content/extractVideoLinks.ts index 854f2d18..fb55a502 100644 --- a/lib/admins/content/extractVideoLinks.ts +++ b/lib/admins/content/extractVideoLinks.ts @@ -6,9 +6,10 @@ const VIDEO_URL_PATTERN = /https?:\/\/[^\s>|]+/g; * Extracts video/media URLs from a Slack bot reply message's text, attachments, and blocks. * Filters for common video hosting patterns. * - * @param text - * @param attachments - * @param blocks + * @param text - The plain text body of the Slack message to scan for URLs. + * @param attachments - Optional Slack message attachments whose action button URLs are also scanned. + * @param blocks - Optional Slack Block Kit blocks whose element URLs are also scanned. + * @returns A deduplicated array of video/media URLs found across all message surfaces. */ export function extractVideoLinks( text: string, diff --git a/lib/admins/content/fetchThreadVideoLinks.ts b/lib/admins/content/fetchThreadVideoLinks.ts index 74205e0d..11f95d4e 100644 --- a/lib/admins/content/fetchThreadVideoLinks.ts +++ b/lib/admins/content/fetchThreadVideoLinks.ts @@ -20,9 +20,10 @@ interface ConversationsRepliesResponse { * Fetches bot replies in a Slack thread and returns any video/media URLs found. * Extracts URLs from message text, attachment action buttons, and Block Kit blocks. * - * @param token - * @param channel - * @param threadTs + * @param token - Slack bot token used to authenticate the conversations.replies API call. + * @param channel - Slack channel ID containing the thread. + * @param threadTs - Timestamp of the parent message that identifies the thread. + * @returns A deduplicated array of video/media URLs posted by bots in the thread. */ export async function fetchThreadVideoLinks( token: string, diff --git a/lib/admins/emails/__tests__/validateGetAdminEmailsQuery.test.ts b/lib/admins/emails/__tests__/validateGetAdminEmailsQuery.test.ts index 90e1a3d0..4fcd69aa 100644 --- a/lib/admins/emails/__tests__/validateGetAdminEmailsQuery.test.ts +++ b/lib/admins/emails/__tests__/validateGetAdminEmailsQuery.test.ts @@ -12,6 +12,12 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({ validateAdminAuth: vi.fn(), })); +/** + * Creates a minimal mock NextRequest from a URL string for use in unit tests. + * + * @param url - The full URL string to construct the mock request from. + * @returns A mock NextRequest with url and nextUrl populated. + */ function createMockRequest(url: string): NextRequest { return { url, diff --git a/lib/admins/pr/__tests__/getPrMergedStatusHandler.test.ts b/lib/admins/pr/__tests__/getPrMergedStatusHandler.test.ts index e007e9c8..f6db6afb 100644 --- a/lib/admins/pr/__tests__/getPrMergedStatusHandler.test.ts +++ b/lib/admins/pr/__tests__/getPrMergedStatusHandler.test.ts @@ -19,6 +19,12 @@ vi.mock("@/lib/github/fetchGithubPrStatus", () => ({ const PR_URL_1 = "https://github.com/recoupable/api/pull/42"; const PR_URL_2 = "https://github.com/recoupable/chat/pull/100"; +/** + * Creates a NextRequest targeting the PR status endpoint with the given PR URLs as query params. + * + * @param urls - Array of GitHub pull request URLs to include as pull_requests query parameters. + * @returns A NextRequest with all provided URLs appended as repeated pull_requests params. + */ function makeRequest(urls: string[] = [PR_URL_1]) { const params = new URLSearchParams(); urls.forEach(url => params.append("pull_requests", url)); diff --git a/lib/admins/pr/getPrStatusHandler.ts b/lib/admins/pr/getPrStatusHandler.ts index 27081718..f3b1ffa6 100644 --- a/lib/admins/pr/getPrStatusHandler.ts +++ b/lib/admins/pr/getPrStatusHandler.ts @@ -10,6 +10,9 @@ import { fetchGithubPrStatus } from "@/lib/github/fetchGithubPrStatus"; * Uses the GitHub REST API to check each PR's state. * * Requires admin authentication. + * + * @param request - The incoming Next.js request containing pull_requests query parameters. + * @returns A NextResponse with each PR URL mapped to its current status, or an error response. */ export async function getPrStatusHandler(request: NextRequest): Promise { try { diff --git a/lib/admins/privy/__tests__/getPrivyLoginsHandler.test.ts b/lib/admins/privy/__tests__/getPrivyLoginsHandler.test.ts index 87730ae6..5df5259f 100644 --- a/lib/admins/privy/__tests__/getPrivyLoginsHandler.test.ts +++ b/lib/admins/privy/__tests__/getPrivyLoginsHandler.test.ts @@ -49,8 +49,10 @@ const mockLogins = [ ]; /** + * Creates a NextRequest targeting the Privy logins endpoint with the given period query param. * - * @param period + * @param period - The time period to filter logins by (e.g., "daily", "weekly", "monthly", "all"). + * @returns A NextRequest instance for the Privy logins admin endpoint. */ function makeRequest(period = "daily") { return new NextRequest(`https://example.com/api/admins/privy?period=${period}`); diff --git a/lib/admins/privy/__tests__/validateGetPrivyLoginsQuery.test.ts b/lib/admins/privy/__tests__/validateGetPrivyLoginsQuery.test.ts index 009aa0b9..69ea3e27 100644 --- a/lib/admins/privy/__tests__/validateGetPrivyLoginsQuery.test.ts +++ b/lib/admins/privy/__tests__/validateGetPrivyLoginsQuery.test.ts @@ -15,8 +15,10 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({ const mockAuth = { accountId: "test-account", orgId: null, authToken: "token" }; /** + * Creates a NextRequest targeting the Privy logins query endpoint, optionally with a period param. * - * @param period + * @param period - Optional time period value to include as a query parameter. + * @returns A NextRequest for the Privy logins admin endpoint with or without a period filter. */ function makeRequest(period?: string) { const url = period diff --git a/lib/admins/privy/countNewAccounts.ts b/lib/admins/privy/countNewAccounts.ts index 012ced53..9ec1d660 100644 --- a/lib/admins/privy/countNewAccounts.ts +++ b/lib/admins/privy/countNewAccounts.ts @@ -5,6 +5,10 @@ import { getCutoffMs } from "./getCutoffMs"; /** * Counts how many users in the list were created within the cutoff period. + * + * @param users - Array of Privy user objects to evaluate. + * @param period - The time window to check against (e.g., "daily", "weekly", "monthly", or "all"). + * @returns The number of users whose created_at timestamp falls within the given period. */ export function countNewAccounts(users: User[], period: PrivyLoginsPeriod): number { const cutoffMs = getCutoffMs(period); diff --git a/lib/admins/privy/fetchPrivyLogins.ts b/lib/admins/privy/fetchPrivyLogins.ts index ae4d4dd0..f43ac60b 100644 --- a/lib/admins/privy/fetchPrivyLogins.ts +++ b/lib/admins/privy/fetchPrivyLogins.ts @@ -20,6 +20,14 @@ export type FetchPrivyLoginsResult = { totalPrivyUsers: number; }; +/** + * Fetches Privy users active or created within the given period via the Privy Management API. + * Paginates through all users and collects those whose created_at or latest_verified_at + * falls within the time window determined by the period. + * + * @param period - The time window to filter users by ("daily", "weekly", "monthly", or "all"). + * @returns An object containing the matching users sorted by created_at descending and the total Privy user count. + */ export async function fetchPrivyLogins(period: PrivyLoginsPeriod): Promise { const isAll = period === "all"; const cutoffMs = getCutoffMs(period); diff --git a/lib/admins/privy/getCutoffMs.ts b/lib/admins/privy/getCutoffMs.ts index 8b80ec6a..83488a1b 100644 --- a/lib/admins/privy/getCutoffMs.ts +++ b/lib/admins/privy/getCutoffMs.ts @@ -5,6 +5,9 @@ import { PERIOD_DAYS } from "./periodDays"; * Returns the cutoff timestamp in milliseconds for a given period. * Uses midnight UTC calendar day boundaries to match Privy dashboard behavior. * Returns 0 for "all" (no cutoff). + * + * @param period - The time window ("daily", "weekly", "monthly", or "all") to compute a cutoff for. + * @returns Milliseconds timestamp representing the start of the period, or 0 for "all". */ export function getCutoffMs(period: PrivyLoginsPeriod): number { if (period === "all") return 0; diff --git a/lib/admins/privy/getLatestVerifiedAt.ts b/lib/admins/privy/getLatestVerifiedAt.ts index 465ea876..deebf0e6 100644 --- a/lib/admins/privy/getLatestVerifiedAt.ts +++ b/lib/admins/privy/getLatestVerifiedAt.ts @@ -4,6 +4,9 @@ import type { User } from "@privy-io/node"; /** * Returns the most recent latest_verified_at (in ms) across all linked_accounts for a Privy user. * Returns null if no linked account has a latest_verified_at. + * + * @param user - The Privy user object whose linked accounts are inspected. + * @returns The most recent verified-at timestamp in milliseconds, or null if none exists. */ export function getLatestVerifiedAt(user: User): number | null { const linkedAccounts = user.linked_accounts; diff --git a/lib/admins/privy/toMs.ts b/lib/admins/privy/toMs.ts index 472ff9eb..7985c349 100644 --- a/lib/admins/privy/toMs.ts +++ b/lib/admins/privy/toMs.ts @@ -1,6 +1,9 @@ /** * Normalizes a Privy timestamp to milliseconds. * Privy docs say milliseconds but examples show seconds (10 digits). + * + * @param timestamp - A Privy timestamp that may be in either seconds or milliseconds. + * @returns The timestamp normalized to milliseconds. */ export function toMs(timestamp: number): number { return timestamp > 1e12 ? timestamp : timestamp * 1000; diff --git a/lib/admins/slack/__tests__/createSlackTagsHandler.test.ts b/lib/admins/slack/__tests__/createSlackTagsHandler.test.ts index fb2fe667..8b5a5496 100644 --- a/lib/admins/slack/__tests__/createSlackTagsHandler.test.ts +++ b/lib/admins/slack/__tests__/createSlackTagsHandler.test.ts @@ -18,8 +18,10 @@ const handler = createSlackTagsHandler({ }); /** + * Creates a NextRequest targeting the coding Slack tags endpoint with the given period query param. * - * @param period + * @param period - The time period to filter tags by (e.g., "all", "daily", "weekly", "monthly"). + * @returns A NextRequest instance for the coding Slack tags admin endpoint. */ function makeRequest(period = "all") { return new NextRequest(`https://example.com/api/admins/coding/slack?period=${period}`); diff --git a/lib/admins/slack/__tests__/getSlackTagsHandler.test.ts b/lib/admins/slack/__tests__/getSlackTagsHandler.test.ts index 6b5563bd..2be0fa35 100644 --- a/lib/admins/slack/__tests__/getSlackTagsHandler.test.ts +++ b/lib/admins/slack/__tests__/getSlackTagsHandler.test.ts @@ -40,8 +40,10 @@ const mockTags = [ ]; /** + * Creates a NextRequest targeting the Slack tags endpoint with the given period query param. * - * @param period + * @param period - The time period to filter tags by (e.g., "all", "daily", "weekly", "monthly"). + * @returns A NextRequest instance for the Slack tags admin endpoint. */ function makeRequest(period = "all") { return new NextRequest(`https://example.com/api/admins/coding/slack?period=${period}`); diff --git a/lib/admins/slack/__tests__/validateGetSlackTagsQuery.test.ts b/lib/admins/slack/__tests__/validateGetSlackTagsQuery.test.ts index 56a4d7a4..6fd93c97 100644 --- a/lib/admins/slack/__tests__/validateGetSlackTagsQuery.test.ts +++ b/lib/admins/slack/__tests__/validateGetSlackTagsQuery.test.ts @@ -14,8 +14,10 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({ const mockAuth = { accountId: "test-account", orgId: null, authToken: "token" }; /** + * Creates a NextRequest targeting the Slack tags query endpoint, optionally with a period param. * - * @param period + * @param period - Optional time period value to include as a query parameter. + * @returns A NextRequest for the coding Slack tags admin endpoint with or without a period filter. */ function makeRequest(period?: string) { const url = period diff --git a/lib/admins/slack/createSlackTagsHandler.ts b/lib/admins/slack/createSlackTagsHandler.ts index dcbbb0ca..ac06401c 100644 --- a/lib/admins/slack/createSlackTagsHandler.ts +++ b/lib/admins/slack/createSlackTagsHandler.ts @@ -15,7 +15,10 @@ interface SlackTagsHandlerConfig { * Factory that creates a handler for admin slack tags endpoints. * Parameterized by validation, fetching, and response field naming. * - * @param config + * @param config - Configuration object specifying the validate function, fetchMentions function, + * and the response field names used to expose per-tag response counts and totals. + * @returns An async request handler function that validates the request, fetches mentions, + * and returns aggregated tag statistics as a JSON response. */ export function createSlackTagsHandler( config: SlackTagsHandlerConfig, diff --git a/lib/admins/slack/extractGithubPrUrls.ts b/lib/admins/slack/extractGithubPrUrls.ts index 0b534e20..959b192d 100644 --- a/lib/admins/slack/extractGithubPrUrls.ts +++ b/lib/admins/slack/extractGithubPrUrls.ts @@ -25,9 +25,10 @@ const PR_URL_EXACT = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/; * Extracts GitHub pull request URLs from a Slack message's text, attachments, and blocks. * Handles plain URLs, Slack-formatted links, action button URLs, and Block Kit element URLs. * - * @param text - * @param attachments - * @param blocks + * @param text - The plain text body of the Slack message to scan for GitHub PR URLs. + * @param attachments - Optional Slack message attachments whose action button URLs are also scanned. + * @param blocks - Optional Slack Block Kit blocks whose element URLs are also scanned. + * @returns A deduplicated array of GitHub pull request URLs found across all message surfaces. */ export function extractGithubPrUrls( text: string, diff --git a/lib/admins/slack/fetchThreadPullRequests.ts b/lib/admins/slack/fetchThreadPullRequests.ts index c49fed8b..7980d0e6 100644 --- a/lib/admins/slack/fetchThreadPullRequests.ts +++ b/lib/admins/slack/fetchThreadPullRequests.ts @@ -19,9 +19,10 @@ interface ConversationsRepliesResponse { * Fetches bot replies in a Slack thread and returns any GitHub PR URLs found. * Extracts URLs from message text, attachment action buttons, and Block Kit blocks. * - * @param token - * @param channel - * @param threadTs + * @param token - Slack bot token used to authenticate the conversations.replies API call. + * @param channel - Slack channel ID containing the thread. + * @param threadTs - Timestamp of the parent message that identifies the thread. + * @returns A deduplicated array of GitHub PR URLs posted by bots in the thread. */ export async function fetchThreadPullRequests( token: string, diff --git a/lib/admins/slack/getCutoffTs.ts b/lib/admins/slack/getCutoffTs.ts index 2aeec98d..d6be5151 100644 --- a/lib/admins/slack/getCutoffTs.ts +++ b/lib/admins/slack/getCutoffTs.ts @@ -5,7 +5,8 @@ import { PERIOD_DAYS } from "@/lib/admins/privy/periodDays"; * Returns the cutoff timestamp in seconds for a given period, or null for "all". * Used by the Slack API which expects timestamps in seconds. * - * @param period + * @param period - The time window ("daily", "weekly", "monthly", or "all") to compute a cutoff for. + * @returns Unix timestamp in seconds representing the start of the period, or null for "all". */ export function getCutoffTs(period: AdminPeriod): number | null { if (period === "all") return null; diff --git a/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts b/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts index d29ed8c5..1b1588f6 100644 --- a/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts +++ b/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { ChatRequestBody } from "@/lib/chat/validateChatRequest"; -import { ToolLoopAgent, stepCountIs } from "ai"; +import { ToolLoopAgent } from "ai"; // Import after mocks import getGeneralAgent from "../getGeneralAgent"; @@ -65,14 +65,14 @@ describe("getGeneralAgent", () => { // Set up default mock returns mockSelectAccountEmails.mockResolvedValue([ - { email: "user@example.com", account_id: "account-123" } as any, + { email: "user@example.com", account_id: "account-123" } as unknown, ]); mockSelectAccountInfo.mockResolvedValue(null); mockGetAccountWithDetails.mockResolvedValue({ id: "account-123", name: "Test User", email: "user@example.com", - } as any); + } as unknown); mockGetKnowledgeBaseText.mockResolvedValue(undefined); mockSetupToolsForRequest.mockResolvedValue(mockTools); mockGetSystemPrompt.mockReturnValue(baseSystemPrompt); @@ -165,8 +165,8 @@ describe("getGeneralAgent", () => { it("uses first email from account emails list", async () => { mockSelectAccountEmails.mockResolvedValue([ - { email: "first@example.com", account_id: "account-123" } as any, - { email: "second@example.com", account_id: "account-123" } as any, + { email: "first@example.com", account_id: "account-123" } as unknown, + { email: "second@example.com", account_id: "account-123" } as unknown, ]); const body: ChatRequestBody = { @@ -229,7 +229,7 @@ describe("getGeneralAgent", () => { mockSelectAccountInfo.mockResolvedValue({ instruction: "Always be formal with this artist", knowledges: [], - } as any); + } as unknown); const body: ChatRequestBody = { accountId: "account-123", @@ -254,7 +254,7 @@ describe("getGeneralAgent", () => { mockSelectAccountInfo.mockResolvedValue({ instruction: null, knowledges: mockKnowledges, - } as any); + } as unknown); mockGetKnowledgeBaseText.mockResolvedValue("FAQ content here"); const body: ChatRequestBody = { @@ -304,7 +304,7 @@ describe("getGeneralAgent", () => { email: "user@example.com", job_title: "Music Manager", }; - mockGetAccountWithDetails.mockResolvedValue(mockAccountDetails as any); + mockGetAccountWithDetails.mockResolvedValue(mockAccountDetails as unknown); const body: ChatRequestBody = { accountId: "account-123", @@ -476,7 +476,7 @@ describe("getGeneralAgent", () => { const result = await getGeneralAgent(body); // providerOptions should be baked into the agent constructor (stored in settings) - const settings = (result.agent as any).settings; + const settings = (result.agent as unknown as { settings: Record }).settings; expect(settings.providerOptions).toBeDefined(); expect(settings.providerOptions.anthropic).toEqual( expect.objectContaining({ @@ -509,7 +509,7 @@ describe("getGeneralAgent", () => { const result = await getGeneralAgent(body); // prepareStep should be baked into the agent constructor (stored in settings) - const settings = (result.agent as any).settings; + const settings = (result.agent as unknown as { settings: Record }).settings; expect(settings.prepareStep).toBeInstanceOf(Function); }); }); diff --git a/lib/ai/__tests__/getAvailableModels.test.ts b/lib/ai/__tests__/getAvailableModels.test.ts index 271752bc..b097db43 100644 --- a/lib/ai/__tests__/getAvailableModels.test.ts +++ b/lib/ai/__tests__/getAvailableModels.test.ts @@ -29,7 +29,7 @@ describe("getAvailableModels", () => { { id: "text-embedding-ada-002", pricing: { input: "0.0001", output: "0" } }, { id: "claude-3-opus", pricing: { input: "0.00001", output: "0.00003" } }, ], - } as any); + } as unknown); const models = await getAvailableModels(); @@ -39,7 +39,7 @@ describe("getAvailableModels", () => { }); it("returns empty array when gateway returns no models", async () => { - mockGatewayGetAvailableModels.mockResolvedValue({ models: [] } as any); + mockGatewayGetAvailableModels.mockResolvedValue({ models: [] } as unknown); const models = await getAvailableModels(); diff --git a/lib/ai/__tests__/getModel.test.ts b/lib/ai/__tests__/getModel.test.ts index 367fa40c..3921c745 100644 --- a/lib/ai/__tests__/getModel.test.ts +++ b/lib/ai/__tests__/getModel.test.ts @@ -24,7 +24,7 @@ describe("getModel", () => { { id: "gpt-4", pricing: { input: "0.00003", output: "0.00006" } }, { id: "claude-3-opus", pricing: { input: "0.00001", output: "0.00003" } }, ]; - mockGetAvailableModels.mockResolvedValue(models as any); + mockGetAvailableModels.mockResolvedValue(models as unknown[]); const model = await getModel("gpt-4"); @@ -36,7 +36,7 @@ describe("getModel", () => { it("returns undefined when model is not found", async () => { const models = [{ id: "gpt-4", pricing: { input: "0.00003", output: "0.00006" } }]; - mockGetAvailableModels.mockResolvedValue(models as any); + mockGetAvailableModels.mockResolvedValue(models as unknown[]); const model = await getModel("unknown-model"); diff --git a/lib/ai/getAvailableModels.ts b/lib/ai/getAvailableModels.ts index a46fd79e..aad604b1 100644 --- a/lib/ai/getAvailableModels.ts +++ b/lib/ai/getAvailableModels.ts @@ -4,6 +4,8 @@ import isEmbedModel from "./isEmbedModel"; /** * Returns the list of available LLMs from the Vercel AI Gateway. * Filters out embed models that are not suitable for chat. + * + * @returns Array of available language model entries, or empty array on error */ export const getAvailableModels = async (): Promise => { try { diff --git a/lib/ai/getModel.ts b/lib/ai/getModel.ts index edf4d425..99ca9c2f 100644 --- a/lib/ai/getModel.ts +++ b/lib/ai/getModel.ts @@ -3,6 +3,7 @@ import { GatewayLanguageModelEntry } from "@ai-sdk/gateway"; /** * Returns a specific model by its ID from the list of available models. + * * @param modelId - The ID of the model to find * @returns The matching model or undefined if not found */ diff --git a/lib/ai/isEmbedModel.ts b/lib/ai/isEmbedModel.ts index 7c5fbbfb..90394001 100644 --- a/lib/ai/isEmbedModel.ts +++ b/lib/ai/isEmbedModel.ts @@ -3,6 +3,9 @@ import { GatewayLanguageModelEntry } from "@ai-sdk/gateway"; /** * Determines if a model is an embedding model (not suitable for chat). * Embed models typically have 0 output pricing since they only produce embeddings. + * + * @param m - The gateway model entry to check + * @returns True if the model is an embedding model, false otherwise */ export const isEmbedModel = (m: GatewayLanguageModelEntry): boolean => { const pricing = m.pricing; diff --git a/lib/artist/__tests__/updateArtistSocials.test.ts b/lib/artist/__tests__/updateArtistSocials.test.ts index 8c5f7b1c..c3088821 100644 --- a/lib/artist/__tests__/updateArtistSocials.test.ts +++ b/lib/artist/__tests__/updateArtistSocials.test.ts @@ -183,9 +183,7 @@ describe("updateArtistSocials", () => { mockSelectSocials.mockResolvedValue([]); // Mock insertSocials to return different IDs for each call - let insertCallCount = 0; mockInsertSocials.mockImplementation(socials => { - insertCallCount++; const social = socials[0]; if (social.profile_url.includes("instagram")) { return Promise.resolve([ diff --git a/lib/artists/__tests__/createArtistPostHandler.test.ts b/lib/artists/__tests__/createArtistPostHandler.test.ts index e63d244d..c7036217 100644 --- a/lib/artists/__tests__/createArtistPostHandler.test.ts +++ b/lib/artists/__tests__/createArtistPostHandler.test.ts @@ -14,6 +14,13 @@ vi.mock("@/lib/auth/validateAuthContext", () => ({ validateAuthContext: (...args: unknown[]) => mockValidateAuthContext(...args), })); +/** + * Creates a mock POST request to the artists endpoint with JSON body and headers. + * + * @param body - The request body to serialize as JSON + * @param headers - Optional extra headers to merge with defaults + * @returns A NextRequest instance for use in tests + */ function createRequest(body: unknown, headers: Record = {}): NextRequest { const defaultHeaders: Record = { "Content-Type": "application/json", diff --git a/lib/artists/__tests__/validateCreateArtistBody.test.ts b/lib/artists/__tests__/validateCreateArtistBody.test.ts index 4de5562b..9b150290 100644 --- a/lib/artists/__tests__/validateCreateArtistBody.test.ts +++ b/lib/artists/__tests__/validateCreateArtistBody.test.ts @@ -9,6 +9,13 @@ vi.mock("@/lib/auth/validateAuthContext", () => ({ validateAuthContext: (...args: unknown[]) => mockValidateAuthContext(...args), })); +/** + * Creates a mock POST request to the artists endpoint with JSON body and headers. + * + * @param body - The request body to serialize as JSON + * @param headers - Optional extra headers to merge with defaults + * @returns A NextRequest instance for use in tests + */ function createRequest(body: unknown, headers: Record = {}): NextRequest { const defaultHeaders: Record = { "Content-Type": "application/json" }; return new NextRequest("http://localhost/api/artists", { diff --git a/lib/artists/createArtistInDb.ts b/lib/artists/createArtistInDb.ts index e1eeceb9..47a71a03 100644 --- a/lib/artists/createArtistInDb.ts +++ b/lib/artists/createArtistInDb.ts @@ -51,7 +51,7 @@ export async function createArtistInDb( ...artist, account_id: artist.id, }; - } catch (error) { + } catch { return null; } } diff --git a/lib/auth/__tests__/validateAuthContext.test.ts b/lib/auth/__tests__/validateAuthContext.test.ts index 31dda345..3e8f192c 100644 --- a/lib/auth/__tests__/validateAuthContext.test.ts +++ b/lib/auth/__tests__/validateAuthContext.test.ts @@ -33,6 +33,12 @@ const mockGetAuthenticatedAccountId = vi.mocked(getAuthenticatedAccountId); const mockValidateOrganizationAccess = vi.mocked(validateOrganizationAccess); const mockCanAccessAccount = vi.mocked(canAccessAccount); +/** + * Creates a minimal mock Request object with the given headers for testing auth validation. + * + * @param headers - Key-value pairs of HTTP headers to include on the mock request + * @returns A minimal Request-shaped object suitable for passing to validateAuthContext + */ function createMockRequest(headers: Record = {}): Request { return { headers: { diff --git a/lib/catalog/formatCatalogSongsAsCSV.ts b/lib/catalog/formatCatalogSongsAsCSV.ts index 5115eece..a665562e 100644 --- a/lib/catalog/formatCatalogSongsAsCSV.ts +++ b/lib/catalog/formatCatalogSongsAsCSV.ts @@ -1,7 +1,10 @@ import { CatalogSong } from "./getCatalogSongs"; /** - * Formats catalog songs into the CSV-like format expected by the scorer + * Formats catalog songs into the CSV-like format expected by the scorer. + * + * @param songs - Array of catalog songs to format + * @returns A newline-separated string of ISRC, name, album, and artist columns */ export function formatCatalogSongsAsCSV(songs: CatalogSong[]): string { const csvLines = songs.map(song => { diff --git a/lib/catalog/getCatalogDataAsCSV.ts b/lib/catalog/getCatalogDataAsCSV.ts index ea529c37..8292270a 100644 --- a/lib/catalog/getCatalogDataAsCSV.ts +++ b/lib/catalog/getCatalogDataAsCSV.ts @@ -2,7 +2,11 @@ import { getCatalogSongs, CatalogSong } from "./getCatalogSongs"; import { formatCatalogSongsAsCSV } from "./formatCatalogSongsAsCSV"; /** - * Gets all catalog songs and formats them as CSV for the scorer + * Gets all catalog songs and formats them as CSV for the scorer. + * Paginates through all pages of results before formatting. + * + * @param catalogId - The ID of the catalog to fetch songs from + * @returns A CSV-formatted string of all songs in the catalog */ export async function getCatalogDataAsCSV(catalogId: string): Promise { const allSongs: CatalogSong[] = []; diff --git a/lib/catalog/getCatalogSongs.ts b/lib/catalog/getCatalogSongs.ts index c58c33be..773f5b84 100644 --- a/lib/catalog/getCatalogSongs.ts +++ b/lib/catalog/getCatalogSongs.ts @@ -1,7 +1,3 @@ -/** - * Fetches a single page of songs from a catalog - */ - import { Tables } from "@/types/database.types"; import { NEW_API_BASE_URL } from "../consts"; @@ -25,6 +21,15 @@ export interface CatalogSongsResponse { error?: string; } +/** + * Fetches a single page of songs from a catalog via the Recoup API. + * + * @param catalogId - The ID of the catalog to fetch songs from + * @param pageSize - Number of songs per page (default: 100) + * @param page - The page number to fetch (default: 1) + * @param artistName - Optional artist name filter to narrow results + * @returns A paginated response with songs and pagination metadata + */ export async function getCatalogSongs( catalogId: string, pageSize: number = 100, diff --git a/lib/catalog/getCatalogs.ts b/lib/catalog/getCatalogs.ts index 9533183b..04f5431f 100644 --- a/lib/catalog/getCatalogs.ts +++ b/lib/catalog/getCatalogs.ts @@ -8,6 +8,12 @@ export interface CatalogsResponse { error?: string; } +/** + * Fetches all catalogs for a given account from the Recoup API. + * + * @param accountId - The account ID whose catalogs to retrieve + * @returns A response containing the list of catalogs for the account + */ export async function getCatalogs(accountId: string): Promise { try { const response = await fetch( diff --git a/lib/chat/__tests__/handleChatCompletion.test.ts b/lib/chat/__tests__/handleChatCompletion.test.ts index aab50328..7ef4319a 100644 --- a/lib/chat/__tests__/handleChatCompletion.test.ts +++ b/lib/chat/__tests__/handleChatCompletion.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import type { UIMessage } from "ai"; +import type { Tables } from "@/types/database.types"; import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails"; import selectRoom from "@/lib/supabase/rooms/selectRoom"; @@ -56,10 +57,12 @@ const mockSendErrorNotification = vi.mocked(sendErrorNotification); // Helper to create mock UIMessage /** + * Creates a mock UIMessage for use in tests. * - * @param id - * @param role - * @param text + * @param id - The unique identifier for the message. + * @param role - The role of the message sender (user or assistant). + * @param text - The text content of the message. + * @returns A UIMessage object with the given id, role, and text. */ function createMockUIMessage(id: string, role: "user" | "assistant", text: string): UIMessage { return { @@ -72,8 +75,10 @@ function createMockUIMessage(id: string, role: "user" | "assistant", text: strin // Helper to create mock ChatRequestBody /** + * Creates a mock ChatRequestBody with default values and optional overrides. * - * @param overrides + * @param overrides - Partial ChatRequestBody fields to override the defaults. + * @returns A complete ChatRequestBody object for testing. */ function createMockBody(overrides: Partial = {}): ChatRequestBody { return { @@ -165,7 +170,9 @@ describe("handleChatCompletion", () => { it("sends notification for new conversation", async () => { mockSelectRoom.mockResolvedValue(null); mockGenerateChatTitle.mockResolvedValue("Test Topic"); - mockSelectAccountEmails.mockResolvedValue([{ email: "test@example.com" } as any]); + mockSelectAccountEmails.mockResolvedValue([ + { email: "test@example.com" } as unknown as Tables<"account_emails">, + ]); const body = createMockBody({ roomId: "new-room-123" }); const responseMessages = [createMockUIMessage("resp-1", "assistant", "Hi!")]; @@ -222,8 +229,8 @@ describe("handleChatCompletion", () => { mockSelectRoom.mockResolvedValue(null); mockGenerateChatTitle.mockResolvedValue("Topic"); mockSelectAccountEmails.mockResolvedValue([ - { email: "first@example.com" } as any, - { email: "second@example.com" } as any, + { email: "first@example.com" } as unknown as Tables<"account_emails">, + { email: "second@example.com" } as unknown as Tables<"account_emails">, ]); const body = createMockBody(); diff --git a/lib/chat/__tests__/handleChatGenerate.test.ts b/lib/chat/__tests__/handleChatGenerate.test.ts index 05e219f5..1a802bff 100644 --- a/lib/chat/__tests__/handleChatGenerate.test.ts +++ b/lib/chat/__tests__/handleChatGenerate.test.ts @@ -1,11 +1,14 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { NextResponse } from "next/server"; +import { NextRequest } from "next/server"; + import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import { validateOverrideAccountId } from "@/lib/accounts/validateOverrideAccountId"; import { setupChatRequest } from "@/lib/chat/setupChatRequest"; import { saveChatCompletion } from "@/lib/chat/saveChatCompletion"; import { setupConversation } from "@/lib/chat/setupConversation"; +import type { ChatConfig } from "@/lib/chat/types"; import { handleChatGenerate } from "../handleChatGenerate"; // Mock all dependencies before importing the module under test @@ -69,8 +72,10 @@ const mockSetupConversation = vi.mocked(setupConversation); // Helper to create a mock agent with .generate() /** + * Creates a mock agent whose generate method resolves with the given result. * - * @param generateResult + * @param generateResult - The result object that the agent's generate method will resolve with. + * @returns A mock agent object with generate and stream methods. */ function createMockAgent(generateResult: Record) { return { @@ -82,9 +87,11 @@ function createMockAgent(generateResult: Record) { // Helper to create mock NextRequest /** + * Creates a mock Request object with a JSON body and optional headers. * - * @param body - * @param headers + * @param body - The request body to be returned by json(). + * @param headers - Optional map of HTTP header names to values. + * @returns A mock Request object suitable for use in tests. */ function createMockRequest(body: unknown, headers: Record = {}): Request { return { @@ -116,7 +123,7 @@ describe("handleChatGenerate", () => { const request = createMockRequest({ roomId: "room-123" }, { "x-api-key": "test-key" }); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(400); @@ -127,7 +134,7 @@ describe("handleChatGenerate", () => { it("returns 401 error when no auth header is provided", async () => { const request = createMockRequest({ prompt: "Hello" }, {}); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(401); @@ -156,11 +163,11 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const request = createMockRequest({ prompt: "Hello, world!" }, { "x-api-key": "valid-key" }); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(mockAgent.generate).toHaveBeenCalled(); expect(result.status).toBe(200); @@ -183,12 +190,12 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const messages = [{ role: "user", content: "Hello" }]; const request = createMockRequest({ messages }, { "x-api-key": "valid-key" }); - await handleChatGenerate(request as any); + await handleChatGenerate(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalledWith( expect.objectContaining({ @@ -215,7 +222,7 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const request = createMockRequest( { @@ -228,7 +235,7 @@ describe("handleChatGenerate", () => { { "x-api-key": "valid-key" }, ); - await handleChatGenerate(request as any); + await handleChatGenerate(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalledWith( expect.objectContaining({ @@ -255,11 +262,11 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result.status).toBe(200); const json = await result.json(); @@ -275,7 +282,7 @@ describe("handleChatGenerate", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(500); @@ -295,11 +302,11 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(500); @@ -325,14 +332,14 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const request = createMockRequest( { prompt: "Hello", accountId: "target-account-456" }, { "x-api-key": "org-api-key" }, ); - await handleChatGenerate(request as any); + await handleChatGenerate(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalledWith( expect.objectContaining({ @@ -360,7 +367,7 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); mockSaveChatCompletion.mockResolvedValue(null); @@ -369,7 +376,7 @@ describe("handleChatGenerate", () => { { "x-api-key": "valid-key" }, ); - await handleChatGenerate(request as any); + await handleChatGenerate(request as unknown as NextRequest); expect(mockSaveChatCompletion).toHaveBeenCalledWith({ text: "Hello! How can I help you?", @@ -394,13 +401,13 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); mockSaveChatCompletion.mockResolvedValue(null); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - await handleChatGenerate(request as any); + await handleChatGenerate(request as unknown as NextRequest); // Since roomId is auto-created, saveChatCompletion should be called expect(mockSaveChatCompletion).toHaveBeenCalledWith({ @@ -426,7 +433,7 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); mockSaveChatCompletion.mockResolvedValue(null); @@ -435,7 +442,7 @@ describe("handleChatGenerate", () => { { "x-api-key": "valid-key" }, ); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result.status).toBe(200); const json = await result.json(); @@ -459,13 +466,13 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); mockSaveChatCompletion.mockResolvedValue(null); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result.status).toBe(200); const json = await result.json(); @@ -489,7 +496,7 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); mockSaveChatCompletion.mockResolvedValue(null); @@ -498,7 +505,7 @@ describe("handleChatGenerate", () => { { "x-api-key": "valid-key" }, ); - await handleChatGenerate(request as any); + await handleChatGenerate(request as unknown as NextRequest); expect(mockSaveChatCompletion).toHaveBeenCalledWith({ text: "This is the assistant response text", @@ -523,7 +530,7 @@ describe("handleChatGenerate", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); mockSaveChatCompletion.mockRejectedValue(new Error("Database error")); @@ -532,7 +539,7 @@ describe("handleChatGenerate", () => { { "x-api-key": "valid-key" }, ); - const result = await handleChatGenerate(request as any); + const result = await handleChatGenerate(request as unknown as NextRequest); expect(result.status).toBe(200); const json = await result.json(); diff --git a/lib/chat/__tests__/handleChatStream.test.ts b/lib/chat/__tests__/handleChatStream.test.ts index b98655e1..8bc47d93 100644 --- a/lib/chat/__tests__/handleChatStream.test.ts +++ b/lib/chat/__tests__/handleChatStream.test.ts @@ -1,10 +1,13 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { NextResponse } from "next/server"; +import { NextRequest } from "next/server"; + import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import { validateOverrideAccountId } from "@/lib/accounts/validateOverrideAccountId"; import { setupChatRequest } from "@/lib/chat/setupChatRequest"; import { setupConversation } from "@/lib/chat/setupConversation"; +import type { ChatConfig } from "@/lib/chat/types"; import { createUIMessageStream, createUIMessageStreamResponse } from "ai"; import { handleChatStream } from "../handleChatStream"; @@ -68,9 +71,11 @@ const mockCreateUIMessageStreamResponse = vi.mocked(createUIMessageStreamRespons // Helper to create mock NextRequest /** + * Creates a mock Request object with a JSON body and optional headers. * - * @param body - * @param headers + * @param body - The request body to be returned by json(). + * @param headers - Optional map of HTTP header names to values. + * @returns A mock Request object suitable for use in tests. */ function createMockRequest(body: unknown, headers: Record = {}): Request { return { @@ -103,7 +108,7 @@ describe("handleChatStream", () => { const request = createMockRequest({ roomId: "room-123" }, { "x-api-key": "test-key" }); - const result = await handleChatStream(request as any); + const result = await handleChatStream(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(400); @@ -114,7 +119,7 @@ describe("handleChatStream", () => { it("returns 401 error when no auth header is provided", async () => { const request = createMockRequest({ prompt: "Hello" }, {}); - const result = await handleChatStream(request as any); + const result = await handleChatStream(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(401); @@ -138,7 +143,7 @@ describe("handleChatStream", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const mockStream = new ReadableStream(); mockCreateUIMessageStream.mockReturnValue(mockStream); @@ -148,7 +153,7 @@ describe("handleChatStream", () => { const request = createMockRequest({ prompt: "Hello, world!" }, { "x-api-key": "valid-key" }); - const result = await handleChatStream(request as any); + const result = await handleChatStream(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalled(); expect(mockCreateUIMessageStream).toHaveBeenCalled(); @@ -178,7 +183,7 @@ describe("handleChatStream", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const mockStream = new ReadableStream(); mockCreateUIMessageStream.mockReturnValue(mockStream); @@ -187,7 +192,7 @@ describe("handleChatStream", () => { const messages = [{ role: "user", content: "Hello" }]; const request = createMockRequest({ messages }, { "x-api-key": "valid-key" }); - await handleChatStream(request as any); + await handleChatStream(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalledWith( expect.objectContaining({ @@ -211,7 +216,7 @@ describe("handleChatStream", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const mockStream = new ReadableStream(); mockCreateUIMessageStream.mockReturnValue(mockStream); @@ -228,7 +233,7 @@ describe("handleChatStream", () => { { "x-api-key": "valid-key" }, ); - await handleChatStream(request as any); + await handleChatStream(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalledWith( expect.objectContaining({ @@ -248,7 +253,7 @@ describe("handleChatStream", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await handleChatStream(request as any); + const result = await handleChatStream(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect(result.status).toBe(500); @@ -275,7 +280,7 @@ describe("handleChatStream", () => { mockSetupChatRequest.mockResolvedValue({ agent: mockAgent, messages: [], - } as any); + } as unknown as ChatConfig); const mockStream = new ReadableStream(); mockCreateUIMessageStream.mockReturnValue(mockStream); @@ -286,7 +291,7 @@ describe("handleChatStream", () => { { "x-api-key": "org-api-key" }, ); - await handleChatStream(request as any); + await handleChatStream(request as unknown as NextRequest); expect(mockSetupChatRequest).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/lib/chat/__tests__/integration/chatEndToEnd.test.ts b/lib/chat/__tests__/integration/chatEndToEnd.test.ts index 02e758be..6bde3574 100644 --- a/lib/chat/__tests__/integration/chatEndToEnd.test.ts +++ b/lib/chat/__tests__/integration/chatEndToEnd.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; +import type { UIMessage } from "ai"; import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails"; @@ -18,6 +19,8 @@ import { handleChatCompletion } from "../../handleChatCompletion"; import { handleChatCredits } from "@/lib/credits/handleChatCredits"; import { validateChatRequest } from "../../validateChatRequest"; import { setupChatRequest } from "../../setupChatRequest"; +import type { ChatRequestBody } from "../../validateChatRequest"; +import type { Tables } from "@/types/database.types"; /** * Integration tests for chat endpoints. @@ -170,6 +173,13 @@ const mockDeductCredits = vi.mocked(deductCredits); const mockGenerateChatTitle = vi.mocked(generateChatTitle); // Helper to create mock NextRequest +/** + * Creates a mock Request object with a JSON body and optional headers. + * + * @param body - The request body to be returned by json(). + * @param headers - Optional map of HTTP header names to values. + * @returns A mock Request object suitable for use in tests. + */ function createMockRequest(body: unknown, headers: Record = {}): Request { return { json: () => Promise.resolve(body), @@ -185,7 +195,9 @@ describe("Chat Integration Tests", () => { vi.clearAllMocks(); // Set up default mocks for Supabase operations - mockSelectAccountEmails.mockResolvedValue([{ email: "test@example.com" }] as any); + mockSelectAccountEmails.mockResolvedValue([ + { email: "test@example.com" } as unknown as Tables<"account_emails">, + ]); mockSelectAccountInfo.mockResolvedValue(null); mockGetAccountWithDetails.mockResolvedValue(null); mockGetKnowledgeBaseText.mockResolvedValue(""); @@ -209,12 +221,12 @@ describe("Chat Integration Tests", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); // Should not be a NextResponse error expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("account-123"); - expect((result as any).prompt).toBe("Hello"); + expect((result as unknown as ChatRequestBody).accountId).toBe("account-123"); + expect((result as unknown as ChatRequestBody).prompt).toBe("Hello"); }); it("validates and returns body for valid request with messages", async () => { @@ -227,16 +239,16 @@ describe("Chat Integration Tests", () => { { "x-api-key": "valid-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).messages).toHaveLength(1); + expect((result as unknown as ChatRequestBody).messages).toHaveLength(1); }); it("returns 401 when no auth header is provided", async () => { const request = createMockRequest({ prompt: "Hello" }, {}); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect((result as NextResponse).status).toBe(401); @@ -250,7 +262,7 @@ describe("Chat Integration Tests", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "invalid-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect((result as NextResponse).status).toBe(401); @@ -261,7 +273,7 @@ describe("Chat Integration Tests", () => { const request = createMockRequest({ roomId: "room-123" }, { "x-api-key": "valid-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect((result as NextResponse).status).toBe(400); @@ -278,7 +290,7 @@ describe("Chat Integration Tests", () => { { "x-api-key": "valid-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); expect((result as NextResponse).status).toBe(400); @@ -298,26 +310,28 @@ describe("Chat Integration Tests", () => { { "x-api-key": "valid-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).roomId).toBe("room-123"); - expect((result as any).artistId).toBe("artist-456"); - expect((result as any).model).toBe("gpt-4"); - expect((result as any).excludeTools).toEqual(["tool1", "tool2"]); + expect((result as unknown as ChatRequestBody).roomId).toBe("room-123"); + expect((result as unknown as ChatRequestBody).artistId).toBe("artist-456"); + expect((result as unknown as ChatRequestBody).model).toBe("gpt-4"); + expect((result as unknown as ChatRequestBody).excludeTools).toEqual(["tool1", "tool2"]); }); }); describe("setupChatRequest integration", () => { it("correctly retrieves account email for system prompt", async () => { - mockSelectAccountEmails.mockResolvedValue([{ email: "user@test.com" }] as any); + mockSelectAccountEmails.mockResolvedValue([ + { email: "user@test.com" } as unknown as Tables<"account_emails">, + ]); const body = { accountId: "account-123", messages: [{ id: "msg-1", role: "user", content: "Hello" }], }; - await setupChatRequest(body as any); + await setupChatRequest(body as unknown as ChatRequestBody); expect(mockSelectAccountEmails).toHaveBeenCalledWith({ accountIds: "account-123", @@ -328,7 +342,7 @@ describe("Chat Integration Tests", () => { mockSelectAccountInfo.mockResolvedValue({ instruction: "Be helpful for this artist", knowledges: [{ id: "kb-1" }], - } as any); + } as unknown as Tables<"account_info">); mockGetKnowledgeBaseText.mockResolvedValue("Artist knowledge base content"); const body = { @@ -337,7 +351,7 @@ describe("Chat Integration Tests", () => { messages: [{ id: "msg-1", role: "user", content: "Hello" }], }; - await setupChatRequest(body as any); + await setupChatRequest(body as unknown as ChatRequestBody); expect(mockSelectAccountInfo).toHaveBeenCalledWith("artist-456"); expect(mockGetKnowledgeBaseText).toHaveBeenCalled(); @@ -349,7 +363,7 @@ describe("Chat Integration Tests", () => { messages: [{ id: "msg-1", role: "user", content: "Hello" }], }; - await setupChatRequest(body as any); + await setupChatRequest(body as unknown as ChatRequestBody); expect(mockSelectAccountInfo).not.toHaveBeenCalled(); expect(mockGetKnowledgeBaseText).not.toHaveBeenCalled(); @@ -361,7 +375,7 @@ describe("Chat Integration Tests", () => { messages: [{ id: "msg-1", role: "user", content: "Hello" }], }; - const result = await setupChatRequest(body as any); + const result = await setupChatRequest(body as unknown as ChatRequestBody); expect(result).toHaveProperty("agent"); expect(result).toHaveProperty("messages"); @@ -372,14 +386,14 @@ describe("Chat Integration Tests", () => { mockGetAccountWithDetails.mockResolvedValue({ name: "Test User", professional_context: "Music producer", - } as any); + } as unknown as Awaited>); const body = { accountId: "account-123", messages: [{ id: "msg-1", role: "user", content: "Hello" }], }; - await setupChatRequest(body as any); + await setupChatRequest(body as unknown as ChatRequestBody); expect(mockGetAccountWithDetails).toHaveBeenCalledWith("account-123"); }); @@ -400,7 +414,10 @@ describe("Chat Integration Tests", () => { { id: "response-1", role: "assistant", parts: [{ type: "text", text: "Hi there!" }] }, ]; - await handleChatCompletion(body as any, responseMessages as any); + await handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ); expect(mockUpsertRoom).toHaveBeenCalledWith( expect.objectContaining({ @@ -413,7 +430,7 @@ describe("Chat Integration Tests", () => { }); it("skips room creation for existing rooms", async () => { - mockSelectRoom.mockResolvedValue({ id: "existing-room" } as any); + mockSelectRoom.mockResolvedValue({ id: "existing-room" } as unknown as Tables<"rooms">); const body = { messages: [{ id: "msg-1", role: "user", parts: [{ type: "text", text: "Hello" }] }], @@ -425,14 +442,17 @@ describe("Chat Integration Tests", () => { { id: "response-1", role: "assistant", parts: [{ type: "text", text: "Hi!" }] }, ]; - await handleChatCompletion(body as any, responseMessages as any); + await handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ); expect(mockUpsertRoom).not.toHaveBeenCalled(); expect(mockSendNewConversationNotification).not.toHaveBeenCalled(); }); it("stores both user and assistant messages to memories", async () => { - mockSelectRoom.mockResolvedValue({ id: "room-123" } as any); + mockSelectRoom.mockResolvedValue({ id: "room-123" } as unknown as Tables<"rooms">); const body = { messages: [{ id: "msg-1", role: "user", parts: [{ type: "text", text: "Hello" }] }], @@ -444,7 +464,10 @@ describe("Chat Integration Tests", () => { { id: "response-1", role: "assistant", parts: [{ type: "text", text: "Hi!" }] }, ]; - await handleChatCompletion(body as any, responseMessages as any); + await handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ); expect(mockUpsertMemory).toHaveBeenCalledTimes(2); expect(mockUpsertMemory).toHaveBeenCalledWith( @@ -462,7 +485,7 @@ describe("Chat Integration Tests", () => { }); it("processes email tool outputs", async () => { - mockSelectRoom.mockResolvedValue({ id: "room-123" } as any); + mockSelectRoom.mockResolvedValue({ id: "room-123" } as unknown as Tables<"rooms">); const body = { messages: [{ id: "msg-1", role: "user", parts: [{ type: "text", text: "Send an email" }] }], @@ -476,15 +499,19 @@ describe("Chat Integration Tests", () => { role: "assistant", parts: [ { - type: "tool-invocation", + type: "tool-send_email", + toolCallId: "call-1", toolName: "send_email", - result: { success: true }, + output: { type: "json", value: { success: true } }, }, ], }, ]; - await handleChatCompletion(body as any, responseMessages as any); + await handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ); expect(mockHandleSendEmailToolOutputs).toHaveBeenCalledWith(responseMessages); }); @@ -504,7 +531,10 @@ describe("Chat Integration Tests", () => { // Should not throw await expect( - handleChatCompletion(body as any, responseMessages as any), + handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ), ).resolves.toBeUndefined(); }); @@ -519,7 +549,10 @@ describe("Chat Integration Tests", () => { { id: "response-1", role: "assistant", parts: [{ type: "text", text: "Hi!" }] }, ]; - await handleChatCompletion(body as any, responseMessages as any); + await handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ); expect(mockSelectRoom).toHaveBeenCalledWith(""); }); @@ -604,10 +637,10 @@ describe("Chat Integration Tests", () => { const request = createMockRequest({ prompt: "What is 2+2?" }, { "x-api-key": "valid-key" }); - const validationResult = await validateChatRequest(request as any); + const validationResult = await validateChatRequest(request as unknown as NextRequest); expect(validationResult).not.toBeInstanceOf(NextResponse); - const chatConfig = await setupChatRequest(validationResult as any); + const chatConfig = await setupChatRequest(validationResult as unknown as ChatRequestBody); expect(chatConfig.agent).toBeDefined(); expect(chatConfig.messages).toBeDefined(); }); @@ -626,10 +659,10 @@ describe("Chat Integration Tests", () => { { "x-api-key": "valid-key" }, ); - const validationResult = await validateChatRequest(request as any); + const validationResult = await validateChatRequest(request as unknown as NextRequest); expect(validationResult).not.toBeInstanceOf(NextResponse); - const chatConfig = await setupChatRequest(validationResult as any); + const chatConfig = await setupChatRequest(validationResult as unknown as ChatRequestBody); expect(chatConfig.agent).toBeDefined(); expect(chatConfig.messages.length).toBeLessThanOrEqual(100); // MAX_MESSAGES }); @@ -648,11 +681,11 @@ describe("Chat Integration Tests", () => { { "x-api-key": "valid-key" }, ); - const body = await validateChatRequest(request as any); + const body = await validateChatRequest(request as unknown as NextRequest); expect(body).not.toBeInstanceOf(NextResponse); // 2. Setup chat request - const chatConfig = await setupChatRequest(body as any); + const chatConfig = await setupChatRequest(body as unknown as ChatRequestBody); expect(chatConfig.agent).toBeDefined(); // 3. Handle post-completion (simulating what would happen after agent response) @@ -664,7 +697,10 @@ describe("Chat Integration Tests", () => { }, ]; - await handleChatCompletion(body as any, responseMessages as any); + await handleChatCompletion( + body as unknown as ChatRequestBody, + responseMessages as unknown as UIMessage[], + ); expect(mockUpsertRoom).toHaveBeenCalled(); expect(mockUpsertMemory).toHaveBeenCalledTimes(2); @@ -672,8 +708,8 @@ describe("Chat Integration Tests", () => { // 4. Handle credits await handleChatCredits({ usage: { promptTokens: 100, completionTokens: 50 }, - model: (body as any).model || "openai/gpt-5-mini", - accountId: (body as any).accountId, + model: (body as unknown as ChatRequestBody).model || "openai/gpt-5-mini", + accountId: (body as unknown as ChatRequestBody).accountId, }); expect(mockGetCreditUsage).toHaveBeenCalled(); diff --git a/lib/chat/__tests__/saveChatCompletion.test.ts b/lib/chat/__tests__/saveChatCompletion.test.ts index ddd995f6..93528341 100644 --- a/lib/chat/__tests__/saveChatCompletion.test.ts +++ b/lib/chat/__tests__/saveChatCompletion.test.ts @@ -146,7 +146,9 @@ describe("saveChatCompletion", () => { }; mockGetMessages.mockReturnValue([mockMessage]); mockFilterMessageContentForMemories.mockReturnValue(mockFilteredContent); - mockInsertMemories.mockResolvedValue(mockInsertedMemory as any); + mockInsertMemories.mockResolvedValue( + mockInsertedMemory as unknown as Awaited>, + ); const result = await saveChatCompletion({ text: "Return test", diff --git a/lib/chat/__tests__/setupChatRequest.test.ts b/lib/chat/__tests__/setupChatRequest.test.ts index bd2ac4e1..1997d996 100644 --- a/lib/chat/__tests__/setupChatRequest.test.ts +++ b/lib/chat/__tests__/setupChatRequest.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { ChatRequestBody } from "../validateChatRequest"; +import type { RoutingDecision } from "../types"; +import type { ModelMessage } from "ai"; import { setupChatRequest } from "../setupChatRequest"; import getGeneralAgent from "@/lib/agents/generalAgent/getGeneralAgent"; @@ -25,14 +27,14 @@ const mockConvertToModelMessages = vi.mocked(convertToModelMessages); describe("setupChatRequest", () => { const mockAgent = { - model: "claude-sonnet-4-20250514", + model: "anthropic/claude-sonnet-4.5", tools: {}, instructions: "You are a helpful assistant", stopWhen: undefined, }; const mockRoutingDecision = { - model: "claude-sonnet-4-20250514", + model: "anthropic/claude-sonnet-4.5", instructions: "You are a helpful assistant", agent: mockAgent, stopWhen: undefined, @@ -40,8 +42,10 @@ describe("setupChatRequest", () => { beforeEach(() => { vi.clearAllMocks(); - mockGetGeneralAgent.mockResolvedValue(mockRoutingDecision as any); - mockConvertToModelMessages.mockImplementation(messages => messages as any); + mockGetGeneralAgent.mockResolvedValue(mockRoutingDecision as unknown as RoutingDecision); + mockConvertToModelMessages.mockImplementation( + messages => messages as unknown as ModelMessage[], + ); }); describe("basic functionality", () => { @@ -126,7 +130,7 @@ describe("setupChatRequest", () => { ...mockAgent, tools: mockTools, }, - } as any); + } as unknown as RoutingDecision); const body: ChatRequestBody = { accountId: "account-123", diff --git a/lib/chat/__tests__/validateChatRequest.test.ts b/lib/chat/__tests__/validateChatRequest.test.ts index b420c598..b49476fe 100644 --- a/lib/chat/__tests__/validateChatRequest.test.ts +++ b/lib/chat/__tests__/validateChatRequest.test.ts @@ -1,16 +1,12 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { NextResponse } from "next/server"; -import { validateChatRequest, chatRequestSchema } from "../validateChatRequest"; +import { NextRequest, NextResponse } from "next/server"; +import { validateChatRequest, chatRequestSchema, ChatRequestBody } from "../validateChatRequest"; import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import { getAuthenticatedAccountId } from "@/lib/auth/getAuthenticatedAccountId"; import { validateOverrideAccountId } from "@/lib/accounts/validateOverrideAccountId"; import { getApiKeyDetails } from "@/lib/keys/getApiKeyDetails"; import { validateOrganizationAccess } from "@/lib/organizations/validateOrganizationAccess"; -import { generateUUID } from "@/lib/uuid/generateUUID"; -import { createNewRoom } from "@/lib/chat/createNewRoom"; -import insertMemories from "@/lib/supabase/memories/insertMemories"; -import filterMessageContentForMemories from "@/lib/messages/filterMessageContentForMemories"; import { setupConversation } from "@/lib/chat/setupConversation"; // Mock dependencies @@ -63,17 +59,15 @@ const mockGetAuthenticatedAccountId = vi.mocked(getAuthenticatedAccountId); const mockValidateOverrideAccountId = vi.mocked(validateOverrideAccountId); const mockGetApiKeyDetails = vi.mocked(getApiKeyDetails); const mockValidateOrganizationAccess = vi.mocked(validateOrganizationAccess); -const mockGenerateUUID = vi.mocked(generateUUID); -const mockCreateNewRoom = vi.mocked(createNewRoom); -const mockInsertMemories = vi.mocked(insertMemories); -const mockFilterMessageContentForMemories = vi.mocked(filterMessageContentForMemories); const mockSetupConversation = vi.mocked(setupConversation); // Helper to create mock NextRequest /** + * Creates a mock Request object with a JSON body and optional headers. * - * @param body - * @param headers + * @param body - The request body to be returned by json(). + * @param headers - Optional map of HTTP header names to values. + * @returns A mock Request object suitable for use in tests. */ function createMockRequest(body: unknown, headers: Record = {}): Request { return { @@ -101,7 +95,7 @@ describe("validateChatRequest", () => { const request = createMockRequest({ roomId: "room-123" }, { "x-api-key": "test-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -120,7 +114,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -136,10 +130,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("account-123"); + expect((result as unknown as ChatRequestBody).accountId).toBe("account-123"); }); it("accepts valid prompt string", async () => { @@ -147,10 +141,10 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello, world!" }, { "x-api-key": "test-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("account-123"); + expect((result as unknown as ChatRequestBody).accountId).toBe("account-123"); }); }); @@ -158,7 +152,7 @@ describe("validateChatRequest", () => { it("rejects request without any auth header", async () => { const request = createMockRequest({ prompt: "Hello" }, {}); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -172,7 +166,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key", authorization: "Bearer test-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -187,7 +181,7 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "invalid-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); }); @@ -197,10 +191,10 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("account-abc-123"); + expect((result as unknown as ChatRequestBody).accountId).toBe("account-abc-123"); }); it("accepts valid Authorization Bearer token", async () => { @@ -211,10 +205,10 @@ describe("validateChatRequest", () => { { authorization: "Bearer valid-jwt-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("account-from-jwt-456"); + expect((result as unknown as ChatRequestBody).accountId).toBe("account-from-jwt-456"); }); it("rejects request with invalid Authorization token", async () => { @@ -230,7 +224,7 @@ describe("validateChatRequest", () => { { authorization: "Bearer invalid-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -245,10 +239,10 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "org-api-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("org-account-123"); + expect((result as unknown as ChatRequestBody).accountId).toBe("org-account-123"); }); it("returns accountId for personal API key", async () => { @@ -259,10 +253,10 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "personal-api-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("personal-account-123"); + expect((result as unknown as ChatRequestBody).accountId).toBe("personal-account-123"); }); it("returns accountId for bearer token auth", async () => { @@ -273,10 +267,10 @@ describe("validateChatRequest", () => { { authorization: "Bearer valid-jwt-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("jwt-account-456"); + expect((result as unknown as ChatRequestBody).accountId).toBe("jwt-account-456"); }); }); @@ -292,10 +286,10 @@ describe("validateChatRequest", () => { { "x-api-key": "org-api-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("target-account-456"); + expect((result as unknown as ChatRequestBody).accountId).toBe("target-account-456"); expect(mockValidateOverrideAccountId).toHaveBeenCalledWith({ apiKey: "org-api-key", targetAccountId: "target-account-456", @@ -316,7 +310,7 @@ describe("validateChatRequest", () => { { "x-api-key": "personal-api-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -331,12 +325,14 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello, world!" }, { "x-api-key": "test-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).messages).toHaveLength(1); - expect((result as any).messages[0].role).toBe("user"); - expect((result as any).messages[0].parts[0].text).toBe("Hello, world!"); + expect((result as unknown as ChatRequestBody).messages).toHaveLength(1); + expect((result as unknown as ChatRequestBody).messages[0].role).toBe("user"); + expect((result as unknown as ChatRequestBody).messages[0].parts[0].text).toBe( + "Hello, world!", + ); }); it("preserves original messages when provided", async () => { @@ -351,10 +347,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).messages).toEqual(originalMessages); + expect((result as unknown as ChatRequestBody).messages).toEqual(originalMessages); }); }); @@ -371,10 +367,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).roomId).toBe("room-xyz"); + expect((result as unknown as ChatRequestBody).roomId).toBe("room-xyz"); }); it("passes through artistId", async () => { @@ -385,10 +381,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).artistId).toBe("artist-abc"); + expect((result as unknown as ChatRequestBody).artistId).toBe("artist-abc"); }); it("passes through model selection", async () => { @@ -399,10 +395,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).model).toBe("gpt-4"); + expect((result as unknown as ChatRequestBody).model).toBe("gpt-4"); }); it("passes through excludeTools array", async () => { @@ -413,10 +409,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).excludeTools).toEqual(["tool1", "tool2"]); + expect((result as unknown as ChatRequestBody).excludeTools).toEqual(["tool1", "tool2"]); }); it("passes through topic", async () => { @@ -427,10 +423,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).topic).toBe("Pulse Feb 2"); + expect((result as unknown as ChatRequestBody).topic).toBe("Pulse Feb 2"); }); }); @@ -483,7 +479,7 @@ describe("validateChatRequest", () => { { authorization: "Bearer valid-jwt-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); expect(mockValidateOrganizationAccess).toHaveBeenCalledWith({ @@ -504,7 +500,7 @@ describe("validateChatRequest", () => { { "x-api-key": "personal-api-key" }, ); - const result = await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); expect(mockValidateOrganizationAccess).toHaveBeenCalledWith({ accountId: "api-key-account-123", organizationId: "org-789", @@ -523,7 +519,7 @@ describe("validateChatRequest", () => { { "x-api-key": "org-api-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); }); @@ -537,7 +533,7 @@ describe("validateChatRequest", () => { { authorization: "Bearer valid-jwt-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).toBeInstanceOf(NextResponse); const json = await (result as NextResponse).json(); @@ -553,7 +549,7 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "org-api-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); // Should not validate org access when no organizationId is provided @@ -568,7 +564,7 @@ describe("validateChatRequest", () => { { authorization: "Bearer valid-jwt-token" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); expect(mockValidateOrganizationAccess).not.toHaveBeenCalled(); @@ -585,10 +581,10 @@ describe("validateChatRequest", () => { const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "test-key" }); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).roomId).toBe("generated-uuid-456"); + expect((result as unknown as ChatRequestBody).roomId).toBe("generated-uuid-456"); }); it("calls setupConversation with correct params when roomId is not provided", async () => { @@ -603,7 +599,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); expect(mockSetupConversation).toHaveBeenCalledWith({ accountId: "account-123", @@ -629,7 +625,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); expect(mockSetupConversation).toHaveBeenCalledWith( expect.objectContaining({ @@ -650,7 +646,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); expect(mockSetupConversation).toHaveBeenCalledWith( expect.objectContaining({ @@ -671,10 +667,10 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).roomId).toBe("existing-room-123"); + expect((result as unknown as ChatRequestBody).roomId).toBe("existing-room-123"); }); it("passes roomId to setupConversation when provided", async () => { @@ -689,7 +685,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); expect(mockSetupConversation).toHaveBeenCalledWith( expect.objectContaining({ @@ -710,10 +706,10 @@ describe("validateChatRequest", () => { { authorization: "Bearer valid-jwt" }, ); - const result = await validateChatRequest(request as any); + const result = await validateChatRequest(request as unknown as NextRequest); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).roomId).toBe("jwt-generated-uuid"); + expect((result as unknown as ChatRequestBody).roomId).toBe("jwt-generated-uuid"); expect(mockSetupConversation).toHaveBeenCalledWith( expect.objectContaining({ accountId: "jwt-account-123", @@ -733,7 +729,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); expect(mockSetupConversation).toHaveBeenCalledWith({ accountId: "account-123", @@ -761,7 +757,7 @@ describe("validateChatRequest", () => { { "x-api-key": "test-key" }, ); - await validateChatRequest(request as any); + await validateChatRequest(request as unknown as NextRequest); // setupConversation handles both new and existing rooms expect(mockSetupConversation).toHaveBeenCalledWith({ diff --git a/lib/chat/toolChains/__tests__/getExecutedToolTimeline.test.ts b/lib/chat/toolChains/__tests__/getExecutedToolTimeline.test.ts index 4e7085c9..f0de9b33 100644 --- a/lib/chat/toolChains/__tests__/getExecutedToolTimeline.test.ts +++ b/lib/chat/toolChains/__tests__/getExecutedToolTimeline.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from "vitest"; +import type { StepResult, ToolSet } from "ai"; import getExecutedToolTimeline from "../getExecutedToolTimeline"; describe("getExecutedToolTimeline", () => { @@ -21,7 +22,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual(["get_spotify_search"]); }); @@ -43,7 +44,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual(["tool_a", "tool_b"]); }); @@ -78,7 +79,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual(["create_new_artist", "get_spotify_search", "update_account_info"]); }); }); @@ -98,7 +99,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual(["tool_a"]); }); @@ -116,7 +117,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual(["tool_a"]); }); @@ -141,7 +142,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual([ "step1_tool1", "step1_tool2", @@ -163,7 +164,7 @@ describe("getExecutedToolTimeline", () => { }, ]; - const result = getExecutedToolTimeline(steps as any); + const result = getExecutedToolTimeline(steps as unknown as StepResult[]); expect(result).toEqual(["tool_without_id"]); }); }); diff --git a/lib/chat/toolChains/__tests__/getPrepareStepResult.test.ts b/lib/chat/toolChains/__tests__/getPrepareStepResult.test.ts index df582c01..19a304d3 100644 --- a/lib/chat/toolChains/__tests__/getPrepareStepResult.test.ts +++ b/lib/chat/toolChains/__tests__/getPrepareStepResult.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { PrepareStepOptions } from "../getPrepareStepResult"; import getPrepareStepResult from "../getPrepareStepResult"; @@ -37,7 +38,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toBeUndefined(); }); @@ -59,7 +60,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toBeUndefined(); }); }); @@ -83,7 +84,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toEqual({ toolChoice: { type: "tool", toolName: "step_one" }, }); @@ -112,7 +113,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toEqual({ toolChoice: { type: "tool", toolName: "step_two" }, model: "gemini-2.5-pro", // From TOOL_MODEL_MAP @@ -152,7 +153,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toBeUndefined(); }); @@ -188,7 +189,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); // Should return step_two since step_one has been executed expect(result).toEqual({ toolChoice: { type: "tool", toolName: "step_two" }, @@ -216,7 +217,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toEqual({ toolChoice: { type: "tool", toolName: "custom_step_one" }, system: "Custom system prompt for step one", @@ -252,7 +253,7 @@ describe("getPrepareStepResult", () => { messages: existingMessages, }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result).toEqual({ toolChoice: { type: "tool", toolName: "custom_step_two" }, messages: [ @@ -287,7 +288,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result?.model).toBe("gemini-2.5-pro"); }); @@ -309,7 +310,7 @@ describe("getPrepareStepResult", () => { messages: [], }; - const result = getPrepareStepResult(options as any); + const result = getPrepareStepResult(options as unknown as PrepareStepOptions); expect(result?.model).toBeUndefined(); }); }); diff --git a/lib/chat/toolChains/__tests__/toolChains.test.ts b/lib/chat/toolChains/__tests__/toolChains.test.ts index fdd5782f..b9a82430 100644 --- a/lib/chat/toolChains/__tests__/toolChains.test.ts +++ b/lib/chat/toolChains/__tests__/toolChains.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from "vitest"; +import type { LanguageModel } from "ai"; import { TOOL_CHAINS, TOOL_MODEL_MAP, ToolChainItem, PrepareStepResult } from "../toolChains"; describe("toolChains", () => { @@ -38,7 +39,7 @@ describe("toolChains", () => { it("allows result with model override", () => { const result: PrepareStepResult = { toolChoice: { type: "tool", toolName: "test_tool" }, - model: "gemini-2.5-pro" as any, + model: "gemini-2.5-pro" as unknown as LanguageModel, }; expect(result.model).toBe("gemini-2.5-pro"); }); @@ -46,7 +47,7 @@ describe("toolChains", () => { it("allows result with all properties", () => { const result: PrepareStepResult = { toolChoice: { type: "tool", toolName: "test_tool" }, - model: "gemini-2.5-pro" as any, + model: "gemini-2.5-pro" as unknown as LanguageModel, system: "Custom prompt", messages: [{ role: "user", content: "Test" }], }; diff --git a/lib/chat/toolChains/create_release_report/getReleaseReportReferenceMessage.ts b/lib/chat/toolChains/create_release_report/getReleaseReportReferenceMessage.ts index 40507d7b..2e30dbb1 100644 --- a/lib/chat/toolChains/create_release_report/getReleaseReportReferenceMessage.ts +++ b/lib/chat/toolChains/create_release_report/getReleaseReportReferenceMessage.ts @@ -2,7 +2,9 @@ import { ModelMessage } from "ai"; import { referenceReleaseReport } from "./referenceReleaseReport"; /** - * Creates a reference message with the release report example + * Creates a reference message with the release report example. + * + * @returns A ModelMessage containing the release report template as a user message. */ const getReleaseReportReferenceMessage = (): ModelMessage => { return { diff --git a/lib/chat/toolChains/getPrepareStepResult.ts b/lib/chat/toolChains/getPrepareStepResult.ts index 02dd8e71..6758cf50 100644 --- a/lib/chat/toolChains/getPrepareStepResult.ts +++ b/lib/chat/toolChains/getPrepareStepResult.ts @@ -2,7 +2,7 @@ import { LanguageModel, ModelMessage, StepResult, ToolSet } from "ai"; import { PrepareStepResult, TOOL_CHAINS, TOOL_MODEL_MAP } from "./toolChains"; import getExecutedToolTimeline from "./getExecutedToolTimeline"; -type PrepareStepOptions = { +export type PrepareStepOptions = { steps: Array>>; stepNumber: number; model: LanguageModel; @@ -12,6 +12,9 @@ type PrepareStepOptions = { /** * Returns the next tool to run based on timeline progression through tool chains. * Uses toolCallsContent to track exact execution order and position in sequence. + * + * @param options - The current step options including steps history, step number, model, and messages. + * @returns The next PrepareStepResult with toolChoice and optional model/system/messages overrides, or undefined if no chain is active. */ const getPrepareStepResult = (options: PrepareStepOptions): PrepareStepResult | undefined => { const { steps } = options; diff --git a/lib/chats/__tests__/createChatHandler.test.ts b/lib/chats/__tests__/createChatHandler.test.ts index 6d509147..fd0ec31a 100644 --- a/lib/chats/__tests__/createChatHandler.test.ts +++ b/lib/chats/__tests__/createChatHandler.test.ts @@ -41,6 +41,12 @@ vi.mock("../generateChatTitle", () => ({ generateChatTitle: vi.fn(), })); +/** + * Creates a mock NextRequest with normalized headers for testing. + * + * @param headers - Key-value map of request headers to include + * @returns A partial NextRequest mock with a working headers.get method + */ function createMockRequest( headers: Record = { "x-api-key": "test-api-key" }, ): NextRequest { diff --git a/lib/chats/__tests__/generateChatTitle.test.ts b/lib/chats/__tests__/generateChatTitle.test.ts index e1bb8a8b..97b4ee23 100644 --- a/lib/chats/__tests__/generateChatTitle.test.ts +++ b/lib/chats/__tests__/generateChatTitle.test.ts @@ -18,7 +18,7 @@ describe("generateChatTitle", () => { it("generates a title from the input text", async () => { mockGenerateText.mockResolvedValue({ text: "Marketing Plan", - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies should I use?"); @@ -28,7 +28,7 @@ describe("generateChatTitle", () => { it("calls generateText with correct prompt structure", async () => { mockGenerateText.mockResolvedValue({ text: "Test Title", - } as any); + } as unknown); await generateChatTitle("Test input"); @@ -43,7 +43,7 @@ describe("generateChatTitle", () => { it("uses LIGHTWEIGHT_MODEL for efficiency", async () => { mockGenerateText.mockResolvedValue({ text: "Test Title", - } as any); + } as unknown); await generateChatTitle("Test input"); @@ -59,7 +59,7 @@ describe("generateChatTitle", () => { it("removes leading double quotes from generated title", async () => { mockGenerateText.mockResolvedValue({ text: '"Marketing Plan', - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies?"); @@ -69,7 +69,7 @@ describe("generateChatTitle", () => { it("removes trailing double quotes from generated title", async () => { mockGenerateText.mockResolvedValue({ text: 'Marketing Plan"', - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies?"); @@ -79,7 +79,7 @@ describe("generateChatTitle", () => { it("removes both leading and trailing double quotes", async () => { mockGenerateText.mockResolvedValue({ text: '"Marketing Plan"', - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies?"); @@ -89,7 +89,7 @@ describe("generateChatTitle", () => { it("removes leading single quotes", async () => { mockGenerateText.mockResolvedValue({ text: "'Marketing Plan", - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies?"); @@ -99,7 +99,7 @@ describe("generateChatTitle", () => { it("removes trailing single quotes", async () => { mockGenerateText.mockResolvedValue({ text: "Marketing Plan'", - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies?"); @@ -109,7 +109,7 @@ describe("generateChatTitle", () => { it("removes both leading and trailing single quotes", async () => { mockGenerateText.mockResolvedValue({ text: "'Marketing Plan'", - } as any); + } as unknown); const result = await generateChatTitle("What marketing strategies?"); @@ -119,7 +119,7 @@ describe("generateChatTitle", () => { it("does not remove quotes in the middle of the title", async () => { mockGenerateText.mockResolvedValue({ text: "User's Plan", - } as any); + } as unknown); const result = await generateChatTitle("Help me with the user's plan"); @@ -131,7 +131,7 @@ describe("generateChatTitle", () => { it("instructs model to generate title under 20 characters", async () => { mockGenerateText.mockResolvedValue({ text: "Short Title", - } as any); + } as unknown); await generateChatTitle("A very long question about many things"); @@ -145,7 +145,7 @@ describe("generateChatTitle", () => { it("instructs model to highlight segment names if present", async () => { mockGenerateText.mockResolvedValue({ text: "Active Fans", - } as any); + } as unknown); await generateChatTitle("Show me the Active Fans segment"); @@ -159,7 +159,7 @@ describe("generateChatTitle", () => { it("instructs model not to wrap title in quotes", async () => { mockGenerateText.mockResolvedValue({ text: "Clean Title", - } as any); + } as unknown); await generateChatTitle("Some question"); @@ -175,7 +175,7 @@ describe("generateChatTitle", () => { it("handles empty input gracefully", async () => { mockGenerateText.mockResolvedValue({ text: "New Chat", - } as any); + } as unknown); const result = await generateChatTitle(""); @@ -185,7 +185,7 @@ describe("generateChatTitle", () => { it("handles very long input", async () => { mockGenerateText.mockResolvedValue({ text: "Long Discussion", - } as any); + } as unknown); const longInput = "a".repeat(1000); const result = await generateChatTitle(longInput); diff --git a/lib/chats/__tests__/getChatsHandler.test.ts b/lib/chats/__tests__/getChatsHandler.test.ts index 5a85935e..45c80c76 100644 --- a/lib/chats/__tests__/getChatsHandler.test.ts +++ b/lib/chats/__tests__/getChatsHandler.test.ts @@ -4,7 +4,6 @@ import { getChatsHandler } from "../getChatsHandler"; import { validateAuthContext } from "@/lib/auth/validateAuthContext"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; import { selectRooms } from "@/lib/supabase/rooms/selectRooms"; vi.mock("@/lib/auth/validateAuthContext", () => ({ diff --git a/lib/chats/__tests__/validateCreateChatBody.test.ts b/lib/chats/__tests__/validateCreateChatBody.test.ts index 0ec4fbe2..abb08c3f 100644 --- a/lib/chats/__tests__/validateCreateChatBody.test.ts +++ b/lib/chats/__tests__/validateCreateChatBody.test.ts @@ -10,7 +10,9 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).artistId).toBe("123e4567-e89b-12d3-a456-426614174000"); + expect((result as Record).artistId).toBe( + "123e4567-e89b-12d3-a456-426614174000", + ); }); it("rejects invalid UUID for artistId", () => { @@ -35,7 +37,9 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).chatId).toBe("123e4567-e89b-12d3-a456-426614174000"); + expect((result as Record).chatId).toBe( + "123e4567-e89b-12d3-a456-426614174000", + ); }); it("rejects invalid UUID for chatId", () => { @@ -54,7 +58,9 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBe("123e4567-e89b-12d3-a456-426614174000"); + expect((result as Record).accountId).toBe( + "123e4567-e89b-12d3-a456-426614174000", + ); }); it("rejects invalid UUID for accountId", () => { @@ -71,7 +77,7 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).accountId).toBeUndefined(); + expect((result as Record).accountId).toBeUndefined(); }); }); @@ -99,7 +105,9 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).firstMessage).toBe("What marketing strategies should I use?"); + expect((result as Record).firstMessage).toBe( + "What marketing strategies should I use?", + ); }); it("accepts missing firstMessage (optional)", () => { @@ -108,7 +116,7 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).firstMessage).toBeUndefined(); + expect((result as Record).firstMessage).toBeUndefined(); }); it("accepts empty string for firstMessage", () => { @@ -118,7 +126,7 @@ describe("validateCreateChatBody", () => { }); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).firstMessage).toBe(""); + expect((result as Record).firstMessage).toBe(""); }); }); }); diff --git a/lib/chats/__tests__/validateGetChatsRequest.test.ts b/lib/chats/__tests__/validateGetChatsRequest.test.ts index ea86c5ab..b28f253c 100644 --- a/lib/chats/__tests__/validateGetChatsRequest.test.ts +++ b/lib/chats/__tests__/validateGetChatsRequest.test.ts @@ -4,7 +4,6 @@ import { validateGetChatsRequest } from "../validateGetChatsRequest"; import { validateAuthContext } from "@/lib/auth/validateAuthContext"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; // Mock dependencies vi.mock("@/lib/auth/validateAuthContext", () => ({ diff --git a/lib/chats/compactChatsHandler.ts b/lib/chats/compactChatsHandler.ts index 1bacaa56..ef5491f4 100644 --- a/lib/chats/compactChatsHandler.ts +++ b/lib/chats/compactChatsHandler.ts @@ -21,7 +21,7 @@ export async function compactChatsHandler(request: NextRequest): Promise { describe("handleCodingAgentCallback", () => { /** + * Builds a mock Request with a JSON body and the callback secret header. * - * @param body - * @param secret + * @param body - The request body to serialize as JSON + * @param secret - The value to send in the x-callback-secret header + * @returns A mock Request object suitable for passing to handleCodingAgentCallback */ function makeRequest(body: unknown, secret = "test-secret") { return { diff --git a/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts b/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts index 5e059f4e..2b6b5bc2 100644 --- a/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts +++ b/lib/coding-agent/__tests__/handleGitHubWebhook.test.ts @@ -45,6 +45,14 @@ const BASE_PAYLOAD = { }, }; +/** + * Builds a mock Request with a JSON body and GitHub webhook headers. + * + * @param body - The webhook payload to serialize as JSON + * @param event - The x-github-event header value (default "issue_comment") + * @param signature - The x-hub-signature-256 header value (default "valid") + * @returns A mock Request object suitable for passing to handleGitHubWebhook + */ function makeRequest(body: unknown, event = "issue_comment", signature = "valid") { return { text: () => Promise.resolve(JSON.stringify(body)), diff --git a/lib/coding-agent/__tests__/handlers.test.ts b/lib/coding-agent/__tests__/handlers.test.ts index b21ebfe2..0f54cdd4 100644 --- a/lib/coding-agent/__tests__/handlers.test.ts +++ b/lib/coding-agent/__tests__/handlers.test.ts @@ -30,12 +30,14 @@ beforeEach(() => { }); /** + * Creates a mock bot object with a stubbed onNewMention method for testing handler registration. * + * @returns A mock bot with a vitest spy on onNewMention */ function createMockBot() { return { onNewMention: vi.fn(), - } as any; + } as unknown as import("../bot").CodingAgentBot; } describe("registerOnNewMention", () => { diff --git a/lib/coding-agent/__tests__/mergeGithubBranch.test.ts b/lib/coding-agent/__tests__/mergeGithubBranch.test.ts index a31b44d1..0e36f91b 100644 --- a/lib/coding-agent/__tests__/mergeGithubBranch.test.ts +++ b/lib/coding-agent/__tests__/mergeGithubBranch.test.ts @@ -41,7 +41,7 @@ describe("mergeGithubBranch", () => { ok: false, status: 409, text: () => Promise.resolve(JSON.stringify({ message: "Merge conflict" })), - } as any); + } as unknown as Response); const result = await mergeGithubBranch("recoupable/api", "test", "main", "ghp_test"); diff --git a/lib/coding-agent/__tests__/mergeGithubPR.test.ts b/lib/coding-agent/__tests__/mergeGithubPR.test.ts index 889f23f6..454b768c 100644 --- a/lib/coding-agent/__tests__/mergeGithubPR.test.ts +++ b/lib/coding-agent/__tests__/mergeGithubPR.test.ts @@ -29,7 +29,7 @@ describe("mergeGithubPR", () => { ok: false, status: 405, text: () => Promise.resolve(JSON.stringify({ message: "Not allowed" })), - } as any); + } as unknown as Response); const result = await mergeGithubPR("recoupable/api", 42, "ghp_test"); diff --git a/lib/coding-agent/__tests__/onMergeAction.test.ts b/lib/coding-agent/__tests__/onMergeAction.test.ts index 56f25c32..57f161e4 100644 --- a/lib/coding-agent/__tests__/onMergeAction.test.ts +++ b/lib/coding-agent/__tests__/onMergeAction.test.ts @@ -30,12 +30,14 @@ beforeEach(() => { }); /** + * Creates a mock bot with a stubbed onAction method for testing merge action handler registration. * + * @returns A mock bot with a vitest spy on onAction */ function createMockBot() { return { onAction: vi.fn(), - } as any; + } as unknown as import("../bot").CodingAgentBot; } describe("registerOnMergeAction", () => { diff --git a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts index 8af470e1..ead0fe4e 100644 --- a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts +++ b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts @@ -12,8 +12,13 @@ beforeEach(() => { process.env.GITHUB_TOKEN = "ghp_test"; }); +/** + * Creates a mock bot with a stubbed onAction method for testing merge-test-to-main action handler registration. + * + * @returns A mock bot with a vitest spy on onAction + */ function createMockBot() { - return { onAction: vi.fn() } as any; + return { onAction: vi.fn() } as unknown as import("../bot").CodingAgentBot; } describe("registerOnMergeTestToMainAction", () => { diff --git a/lib/coding-agent/__tests__/onSubscribedMessage.test.ts b/lib/coding-agent/__tests__/onSubscribedMessage.test.ts index 78a772e9..4fb33f7f 100644 --- a/lib/coding-agent/__tests__/onSubscribedMessage.test.ts +++ b/lib/coding-agent/__tests__/onSubscribedMessage.test.ts @@ -15,12 +15,14 @@ beforeEach(() => { }); /** + * Creates a mock bot with a stubbed onSubscribedMessage method for testing subscribed message handler registration. * + * @returns A mock bot with a vitest spy on onSubscribedMessage */ function createMockBot() { return { onSubscribedMessage: vi.fn(), - } as any; + } as unknown as import("../bot").CodingAgentBot; } describe("registerOnSubscribedMessage", () => { diff --git a/lib/coding-agent/__tests__/resolvePRState.test.ts b/lib/coding-agent/__tests__/resolvePRState.test.ts index 308ef700..08860930 100644 --- a/lib/coding-agent/__tests__/resolvePRState.test.ts +++ b/lib/coding-agent/__tests__/resolvePRState.test.ts @@ -13,8 +13,10 @@ beforeEach(() => { }); /** + * Creates a mock thread with a stubbed state getter for testing resolvePRState. * - * @param state + * @param state - The state value the thread should resolve with + * @returns A mock thread object compatible with the Thread interface */ function createMockThread(state: unknown) { return { @@ -22,7 +24,7 @@ function createMockThread(state: unknown) { get state() { return Promise.resolve(state); }, - } as any; + } as unknown as import("chat").Thread; } describe("resolvePRState", () => { diff --git a/lib/coding-agent/buildMergeTestToMainCard.ts b/lib/coding-agent/buildMergeTestToMainCard.ts index 258cc431..bd292dac 100644 --- a/lib/coding-agent/buildMergeTestToMainCard.ts +++ b/lib/coding-agent/buildMergeTestToMainCard.ts @@ -4,6 +4,7 @@ import { Card, CardText, Actions, Button } from "chat"; * Builds a Card with a "Merge test to main" button for a specific repo. * * @param repo - Full repo identifier (e.g. "recoupable/chat") + * @returns A Card component with the merge button rendered as the primary action */ export function buildMergeTestToMainCard(repo: string) { return Card({ diff --git a/lib/coding-agent/buildPRCard.ts b/lib/coding-agent/buildPRCard.ts index 61ca0a87..8aaeae22 100644 --- a/lib/coding-agent/buildPRCard.ts +++ b/lib/coding-agent/buildPRCard.ts @@ -6,6 +6,7 @@ import type { CodingAgentPR } from "./types"; * * @param title - Card title (e.g. "PRs Created", "PRs Updated") * @param prs - Array of PRs to build review links for + * @returns A Card component listing each PR with a review link and merge button */ export function buildPRCard(title: string, prs: CodingAgentPR[]) { return Card({ diff --git a/lib/coding-agent/buildTaskCard.ts b/lib/coding-agent/buildTaskCard.ts index f0b16548..245144ad 100644 --- a/lib/coding-agent/buildTaskCard.ts +++ b/lib/coding-agent/buildTaskCard.ts @@ -6,6 +6,7 @@ import { Card, CardText, Actions, LinkButton } from "chat"; * @param title - Card title (e.g. "Task Started", "Updating PRs") * @param message - Body text * @param runId - Trigger.dev run ID for the View Task link + * @returns A Card component with the message text and a link to the Trigger.dev task run */ export function buildTaskCard(title: string, message: string, runId: string) { return Card({ diff --git a/lib/coding-agent/encodeGitHubThreadId.ts b/lib/coding-agent/encodeGitHubThreadId.ts index 1cfff2fe..6f9d020d 100644 --- a/lib/coding-agent/encodeGitHubThreadId.ts +++ b/lib/coding-agent/encodeGitHubThreadId.ts @@ -6,6 +6,9 @@ import type { GitHubThreadId } from "@chat-adapter/github"; * * - PR-level: `github:{owner}/{repo}:{prNumber}` * - Review comment: `github:{owner}/{repo}:{prNumber}:rc:{reviewCommentId}` + * + * @param thread - The GitHub thread ID object containing owner, repo, prNumber, and optional reviewCommentId + * @returns The encoded thread ID string in Chat SDK format */ export function encodeGitHubThreadId(thread: GitHubThreadId): string { const { owner, repo, prNumber, reviewCommentId } = thread; diff --git a/lib/coding-agent/extractPRComment.ts b/lib/coding-agent/extractPRComment.ts index d5e7d567..91a07961 100644 --- a/lib/coding-agent/extractPRComment.ts +++ b/lib/coding-agent/extractPRComment.ts @@ -15,6 +15,7 @@ export interface PRComment { * * @param event - The x-github-event header value * @param payload - The parsed webhook payload + * @returns A PRComment with thread, branch, and commentBody, or null if the event should be ignored */ export function extractPRComment( event: string, diff --git a/lib/coding-agent/handleGitHubWebhook.ts b/lib/coding-agent/handleGitHubWebhook.ts index fd6e5fc9..4def0c46 100644 --- a/lib/coding-agent/handleGitHubWebhook.ts +++ b/lib/coding-agent/handleGitHubWebhook.ts @@ -15,6 +15,7 @@ const BOT_MENTION = "@recoup-coding-agent"; * Verifies signature, extracts PR context, and triggers update-pr when the bot is mentioned. * * @param request - The incoming webhook request + * @returns A NextResponse with status 401 for invalid signatures, 200 for processed or ignored events, or 500 on trigger failure */ export async function handleGitHubWebhook(request: Request): Promise { const body = await request.text(); diff --git a/lib/coding-agent/handleMergeSuccess.ts b/lib/coding-agent/handleMergeSuccess.ts index f026f48d..ade30871 100644 --- a/lib/coding-agent/handleMergeSuccess.ts +++ b/lib/coding-agent/handleMergeSuccess.ts @@ -7,6 +7,8 @@ import type { CodingAgentThreadState } from "./types"; * Handles post-merge cleanup after all PRs merged successfully. * Deletes the shared PR state keys for all repos and persists the latest * snapshot via upsertAccountSnapshot. + * + * @param state - The current coding agent thread state containing branch, prs, and snapshotId */ export async function handleMergeSuccess(state: CodingAgentThreadState): Promise { try { diff --git a/lib/coding-agent/handlePRCreated.ts b/lib/coding-agent/handlePRCreated.ts index 52bee268..4e6bad8c 100644 --- a/lib/coding-agent/handlePRCreated.ts +++ b/lib/coding-agent/handlePRCreated.ts @@ -8,8 +8,8 @@ import type { CodingAgentThreadState } from "./types"; * Handles the pr_created callback status. * Writes to both thread state and shared PR state key. * - * @param threadId - * @param body + * @param threadId - The Chat SDK thread ID to post the PR card and update state on + * @param body - The validated callback body containing branch, snapshotId, and PR list */ export async function handlePRCreated(threadId: string, body: CodingAgentCallbackBody) { const thread = getThread(threadId); diff --git a/lib/coding-agent/handlers/handleFeedback.ts b/lib/coding-agent/handlers/handleFeedback.ts index bfecd3eb..3133fa97 100644 --- a/lib/coding-agent/handlers/handleFeedback.ts +++ b/lib/coding-agent/handlers/handleFeedback.ts @@ -11,6 +11,7 @@ import type { CodingAgentThreadState } from "../types"; * @param thread - The chat thread * @param messageText - The user's message text * @param state - The current thread state + * @returns True if the message was handled (agent was busy or feedback triggered an update), false if no state matched */ export async function handleFeedback( thread: Thread, diff --git a/lib/coding-agent/handlers/onMergeAction.ts b/lib/coding-agent/handlers/onMergeAction.ts index 887e3525..4593beb3 100644 --- a/lib/coding-agent/handlers/onMergeAction.ts +++ b/lib/coding-agent/handlers/onMergeAction.ts @@ -15,7 +15,7 @@ import { buildMergeTestToMainCard } from "../buildMergeTestToMainCard"; * * Uses a prefix pattern so a single handler covers all merge_pr:* actions. * - * @param bot + * @param bot - The coding agent bot instance to register the action handler on */ export function registerOnMergeAction(bot: CodingAgentBot) { bot.onAction(async event => { diff --git a/lib/coding-agent/handlers/onMergeTestToMainAction.ts b/lib/coding-agent/handlers/onMergeTestToMainAction.ts index 57932baa..1d2a6884 100644 --- a/lib/coding-agent/handlers/onMergeTestToMainAction.ts +++ b/lib/coding-agent/handlers/onMergeTestToMainAction.ts @@ -6,7 +6,7 @@ import { parseMergeTestToMainActionId } from "../parseMergeTestToMainActionId"; * Registers the "Merge test to main" button action handler on the bot. * Merges the test branch into main for the specified repo via the GitHub API. * - * @param bot + * @param bot - The coding agent bot instance to register the action handler on */ export function registerOnMergeTestToMainAction(bot: CodingAgentBot) { bot.onAction(async event => { diff --git a/lib/coding-agent/handlers/onNewMention.ts b/lib/coding-agent/handlers/onNewMention.ts index ad647a52..000905a2 100644 --- a/lib/coding-agent/handlers/onNewMention.ts +++ b/lib/coding-agent/handlers/onNewMention.ts @@ -13,7 +13,7 @@ import { handleFeedback } from "./handleFeedback"; * For GitHub PR comments, message.meta may contain { repo, branch } to look up * the shared PR state key when thread state is empty. * - * @param bot + * @param bot - The coding agent bot instance to register the mention handler on */ export function registerOnNewMention(bot: CodingAgentBot) { bot.onNewMention(async (thread, message) => { diff --git a/lib/coding-agent/handlers/onSubscribedMessage.ts b/lib/coding-agent/handlers/onSubscribedMessage.ts index 7b769706..e16241bd 100644 --- a/lib/coding-agent/handlers/onSubscribedMessage.ts +++ b/lib/coding-agent/handlers/onSubscribedMessage.ts @@ -5,7 +5,7 @@ import { handleFeedback } from "./handleFeedback"; * Registers the onSubscribedMessage handler on the bot. * Delegates to handleFeedback for busy/update-pr logic. * - * @param bot + * @param bot - The coding agent bot instance to register the subscribed message handler on */ export function registerOnSubscribedMessage(bot: CodingAgentBot) { bot.onSubscribedMessage(async (thread, message) => { diff --git a/lib/coding-agent/mergeGithubBranch.ts b/lib/coding-agent/mergeGithubBranch.ts index 74024a0f..7fe9a947 100644 --- a/lib/coding-agent/mergeGithubBranch.ts +++ b/lib/coding-agent/mergeGithubBranch.ts @@ -16,6 +16,7 @@ export type MergeGithubBranchResult = MergeGithubBranchSuccess | MergeGithubBran * @param head - Branch to merge from (e.g. "test") * @param base - Branch to merge into (e.g. "main") * @param token - GitHub API token + * @returns An object with ok: true on success, or ok: false with a message on failure */ export async function mergeGithubBranch( repo: string, diff --git a/lib/coding-agent/mergeGithubPR.ts b/lib/coding-agent/mergeGithubPR.ts index cfe70fce..b3ae26d2 100644 --- a/lib/coding-agent/mergeGithubPR.ts +++ b/lib/coding-agent/mergeGithubPR.ts @@ -15,6 +15,7 @@ export type MergeGithubPRResult = MergeGithubPRSuccess | MergeGithubPRFailure; * @param repo - Full repo identifier (e.g. "recoupable/api") * @param prNumber - PR number to merge * @param token - GitHub API token + * @returns An object with ok: true on success, or ok: false with a message on failure */ export async function mergeGithubPR( repo: string, diff --git a/lib/coding-agent/parseMergeActionId.ts b/lib/coding-agent/parseMergeActionId.ts index 5118249e..9e72aa63 100644 --- a/lib/coding-agent/parseMergeActionId.ts +++ b/lib/coding-agent/parseMergeActionId.ts @@ -1,6 +1,9 @@ /** * Parses a merge action ID like "merge_pr:recoupable/api#42" * into { repo, number } or null if the format doesn't match. + * + * @param actionId - The action ID string from a merge button click (e.g. "merge_pr:recoupable/api#42") + * @returns An object with repo and number fields, or null if the actionId format is invalid */ export function parseMergeActionId(actionId: string) { const match = actionId.match(/^merge_pr:(.+)#(\d+)$/); diff --git a/lib/coding-agent/parseMergeTestToMainActionId.ts b/lib/coding-agent/parseMergeTestToMainActionId.ts index 1228615f..a776e888 100644 --- a/lib/coding-agent/parseMergeTestToMainActionId.ts +++ b/lib/coding-agent/parseMergeTestToMainActionId.ts @@ -1,6 +1,9 @@ /** * Parses a merge_test_to_main action ID like "merge_test_to_main:recoupable/api" * into the repo string, or null if the format doesn't match. + * + * @param actionId - The action ID string from a merge-test-to-main button click (e.g. "merge_test_to_main:recoupable/api") + * @returns The repo string (e.g. "recoupable/api"), or null if the actionId format is invalid */ export function parseMergeTestToMainActionId(actionId: string): string | null { const prefix = "merge_test_to_main:"; diff --git a/lib/coding-agent/prState/buildPRStateKey.ts b/lib/coding-agent/prState/buildPRStateKey.ts index 570a44ab..87263be8 100644 --- a/lib/coding-agent/prState/buildPRStateKey.ts +++ b/lib/coding-agent/prState/buildPRStateKey.ts @@ -3,8 +3,9 @@ const KEY_PREFIX = "coding-agent:pr"; /** * Builds the Redis key for a given repo and branch. * - * @param repo - * @param branch + * @param repo - Full repo identifier (e.g. "recoupable/api") + * @param branch - Branch name (e.g. "agent/fix-bug") + * @returns The Redis key string used to store and retrieve PR state */ export function buildPRStateKey(repo: string, branch: string): string { return `${KEY_PREFIX}:${repo}:${branch}`; diff --git a/lib/coding-agent/prState/deleteCodingAgentPRState.ts b/lib/coding-agent/prState/deleteCodingAgentPRState.ts index 87cf0eef..5d3e3b2b 100644 --- a/lib/coding-agent/prState/deleteCodingAgentPRState.ts +++ b/lib/coding-agent/prState/deleteCodingAgentPRState.ts @@ -4,8 +4,8 @@ import { buildPRStateKey } from "./buildPRStateKey"; /** * Deletes the shared PR state for a repo/branch from Redis. * - * @param repo - * @param branch + * @param repo - Full repo identifier (e.g. "recoupable/api") + * @param branch - Branch name whose PR state should be removed (e.g. "agent/fix-bug") */ export async function deleteCodingAgentPRState(repo: string, branch: string): Promise { const key = buildPRStateKey(repo, branch); diff --git a/lib/coding-agent/prState/getCodingAgentPRState.ts b/lib/coding-agent/prState/getCodingAgentPRState.ts index 69e68753..94b1d2cc 100644 --- a/lib/coding-agent/prState/getCodingAgentPRState.ts +++ b/lib/coding-agent/prState/getCodingAgentPRState.ts @@ -5,8 +5,9 @@ import type { CodingAgentPRState } from "./types"; /** * Gets the shared PR state for a repo/branch from Redis. * - * @param repo - * @param branch + * @param repo - Full repo identifier (e.g. "recoupable/api") + * @param branch - Branch name to look up PR state for (e.g. "agent/fix-bug") + * @returns The stored CodingAgentPRState, or null if no state exists for the key */ export async function getCodingAgentPRState( repo: string, diff --git a/lib/coding-agent/prState/setCodingAgentPRState.ts b/lib/coding-agent/prState/setCodingAgentPRState.ts index f7ffaac6..187493ac 100644 --- a/lib/coding-agent/prState/setCodingAgentPRState.ts +++ b/lib/coding-agent/prState/setCodingAgentPRState.ts @@ -5,9 +5,9 @@ import type { CodingAgentPRState } from "./types"; /** * Sets the shared PR state for a repo/branch in Redis. * - * @param repo - * @param branch - * @param state + * @param repo - Full repo identifier (e.g. "recoupable/api") + * @param branch - Branch name to store PR state for (e.g. "agent/fix-bug") + * @param state - The PR state object to persist, including status, prs, snapshotId, and branch */ export async function setCodingAgentPRState( repo: string, diff --git a/lib/coding-agent/resolvePRState.ts b/lib/coding-agent/resolvePRState.ts index 78f8bb2d..6611d6b9 100644 --- a/lib/coding-agent/resolvePRState.ts +++ b/lib/coding-agent/resolvePRState.ts @@ -1,5 +1,5 @@ import type { Thread } from "chat"; -import { getCodingAgentPRState, type CodingAgentPRState } from "./prState"; +import { getCodingAgentPRState } from "./prState"; import type { CodingAgentThreadState } from "./types"; export interface PRContext { diff --git a/lib/coding-agent/verifyGitHubWebhook.ts b/lib/coding-agent/verifyGitHubWebhook.ts index 330ebfd4..bb1e9fc3 100644 --- a/lib/coding-agent/verifyGitHubWebhook.ts +++ b/lib/coding-agent/verifyGitHubWebhook.ts @@ -7,6 +7,7 @@ import { timingSafeEqual } from "crypto"; * @param body - Raw request body string * @param signature - The x-hub-signature-256 header value * @param secret - The webhook secret + * @returns True if the signature is valid, false otherwise */ export async function verifyGitHubWebhook( body: string, diff --git a/lib/coding-agent/whatsApp/isWhatsAppConfigured.ts b/lib/coding-agent/whatsApp/isWhatsAppConfigured.ts index 8d970266..c182fa44 100644 --- a/lib/coding-agent/whatsApp/isWhatsAppConfigured.ts +++ b/lib/coding-agent/whatsApp/isWhatsAppConfigured.ts @@ -7,6 +7,8 @@ export const WHATSAPP_ENV_VARS = [ /** * Returns true when all WhatsApp environment variables are configured. + * + * @returns True if all required WhatsApp env vars are set and non-empty, false otherwise */ export function isWhatsAppConfigured(): boolean { return WHATSAPP_ENV_VARS.every(name => !!process.env[name]); diff --git a/lib/composio/client.ts b/lib/composio/client.ts index 055540af..06311c58 100644 --- a/lib/composio/client.ts +++ b/lib/composio/client.ts @@ -4,6 +4,8 @@ import { getComposioApiKey } from "./getComposioApiKey"; * Lazily imports and creates a Composio client. * Uses dynamic imports to avoid bundler issues with @composio/core's * use of createRequire(import.meta.url). + * + * @returns A configured Composio client instance */ export const getComposioClient = async () => { const { Composio } = await import("@composio/core"); diff --git a/lib/composio/connectors/isAllowedArtistConnector.ts b/lib/composio/connectors/isAllowedArtistConnector.ts index 60201a47..4698d540 100644 --- a/lib/composio/connectors/isAllowedArtistConnector.ts +++ b/lib/composio/connectors/isAllowedArtistConnector.ts @@ -9,7 +9,8 @@ export type AllowedArtistConnector = (typeof ALLOWED_ARTIST_CONNECTORS)[number]; /** * Check if a connector slug is an allowed artist connector. * - * @param slug + * @param slug - The toolkit slug to check against the allowed list + * @returns True if the slug is in the allowed artist connector list */ export function isAllowedArtistConnector(slug: string): slug is AllowedArtistConnector { return (ALLOWED_ARTIST_CONNECTORS as readonly string[]).includes(slug); diff --git a/lib/composio/getCallbackUrl.ts b/lib/composio/getCallbackUrl.ts index 570c9251..02135553 100644 --- a/lib/composio/getCallbackUrl.ts +++ b/lib/composio/getCallbackUrl.ts @@ -17,18 +17,19 @@ interface CallbackOptions { /** * Build callback URL for OAuth redirects. * - * @param options.destination - Where to redirect: "chat" or "connectors" - * @param options.roomId - For chat destination, the room ID to return to + * @param root0 - The callback options + * @param root0.destination - Where to redirect: "chat" or "connectors" + * @param root0.roomId - For chat destination, the room ID to return to * @returns Full callback URL with success indicator */ -export function getCallbackUrl(options: CallbackOptions): string { +export function getCallbackUrl({ destination, roomId }: CallbackOptions): string { const baseUrl = getFrontendBaseUrl(); - if (options.destination === "connectors") { + if (destination === "connectors") { return `${baseUrl}/settings/connectors?connected=true`; } // Chat destination - const path = options.roomId ? `/chat/${options.roomId}` : "/chat"; + const path = roomId ? `/chat/${roomId}` : "/chat"; return `${baseUrl}${path}?connected=true`; } diff --git a/lib/composio/getFrontendBaseUrl.ts b/lib/composio/getFrontendBaseUrl.ts index 7a5dbe18..bf82caa4 100644 --- a/lib/composio/getFrontendBaseUrl.ts +++ b/lib/composio/getFrontendBaseUrl.ts @@ -2,6 +2,8 @@ const TEST_FRONTEND_URL = "https://test-recoup-chat.vercel.app"; /** * Get the frontend base URL based on environment. + * + * @returns The base URL for the frontend, varying by Vercel environment or local dev */ export function getFrontendBaseUrl(): string { if (process.env.VERCEL_ENV === "production") { diff --git a/lib/composio/toolRouter/createToolRouterSession.ts b/lib/composio/toolRouter/createToolRouterSession.ts index deb86406..8812c892 100644 --- a/lib/composio/toolRouter/createToolRouterSession.ts +++ b/lib/composio/toolRouter/createToolRouterSession.ts @@ -21,6 +21,7 @@ const ENABLED_TOOLKITS = ["googlesheets", "googledrive", "googledocs", "tiktok"] * @param accountId - Unique identifier for the account * @param roomId - Optional chat room ID for OAuth redirect * @param artistConnections - Optional mapping of toolkit slug to connected account ID for artist-specific connections + * @returns A Composio Tool Router session configured for the account */ export async function createToolRouterSession( accountId: string, diff --git a/lib/content/__tests__/validateCreateContentBody.test.ts b/lib/content/__tests__/validateCreateContentBody.test.ts index 1a71d5ae..0b4f97ff 100644 --- a/lib/content/__tests__/validateCreateContentBody.test.ts +++ b/lib/content/__tests__/validateCreateContentBody.test.ts @@ -20,6 +20,12 @@ vi.mock("@/lib/content/resolveArtistSlug", () => ({ resolveArtistSlug: vi.fn().mockResolvedValue("gatsby-grace"), })); +/** + * Creates a NextRequest with the given body for use in tests. + * + * @param body - The request body to serialize as JSON. + * @returns A NextRequest with the Content-Type and API key headers set. + */ function createRequest(body: unknown): NextRequest { return new NextRequest("http://localhost/api/content/create", { method: "POST", diff --git a/lib/content/contentTemplates.ts b/lib/content/contentTemplates.ts index 7d0653e3..28ac0232 100644 --- a/lib/content/contentTemplates.ts +++ b/lib/content/contentTemplates.ts @@ -25,6 +25,12 @@ export const CONTENT_TEMPLATES: ContentTemplate[] = [ /** Derived from the first entry in CONTENT_TEMPLATES to avoid string duplication. */ export const DEFAULT_CONTENT_TEMPLATE = CONTENT_TEMPLATES[0].name; +/** + * Returns true if the given template name is a recognized content template. + * + * @param template - The template name to check. + * @returns Whether the template is supported. + */ export function isSupportedContentTemplate(template: string): boolean { return CONTENT_TEMPLATES.some(item => item.name === template); } diff --git a/lib/content/createContentHandler.ts b/lib/content/createContentHandler.ts index 5ee90515..6560c68d 100644 --- a/lib/content/createContentHandler.ts +++ b/lib/content/createContentHandler.ts @@ -9,6 +9,9 @@ import { selectAccountSnapshots } from "@/lib/supabase/account_snapshots/selectA /** * Handler for POST /api/content/create. * Always returns runIds array (KISS — one response shape for single and batch). + * + * @param request - The incoming Next.js request containing the content creation payload. + * @returns A NextResponse with the triggered run IDs and status, or an error response. */ export async function createContentHandler(request: NextRequest): Promise { const validated = await validateCreateContentBody(request); diff --git a/lib/content/getArtistContentReadiness.ts b/lib/content/getArtistContentReadiness.ts index a902ce0f..914e7b24 100644 --- a/lib/content/getArtistContentReadiness.ts +++ b/lib/content/getArtistContentReadiness.ts @@ -22,6 +22,12 @@ export interface ArtistContentReadiness { /** * Checks whether an artist has the expected files for content creation. * Searches the main repo and org submodule repos. + * + * @param root0 - Options object. + * @param root0.accountId - The account ID that owns the sandbox GitHub repo. + * @param root0.artistAccountId - The artist's account ID, returned in the readiness result. + * @param root0.artistSlug - The artist's directory slug used to locate files in the repo. + * @returns Readiness result with missing required files, warnings, and the resolved GitHub repo URL. */ export async function getArtistContentReadiness({ accountId, diff --git a/lib/content/getArtistFileTree.ts b/lib/content/getArtistFileTree.ts index 908855a0..0474b1e9 100644 --- a/lib/content/getArtistFileTree.ts +++ b/lib/content/getArtistFileTree.ts @@ -4,6 +4,10 @@ import { getOrgRepoUrls } from "@/lib/github/getOrgRepoUrls"; /** * Gets the file tree that contains the artist, checking the main repo * first, then falling back to org submodule repos. + * + * @param githubRepo - The GitHub repository URL to search first. + * @param artistSlug - The artist's directory slug to locate within the repo. + * @returns The file tree entries for the repo that contains the artist, or null if not found. */ export async function getArtistFileTree( githubRepo: string, diff --git a/lib/content/getArtistRootPrefix.ts b/lib/content/getArtistRootPrefix.ts index 5a777abe..f9dceb33 100644 --- a/lib/content/getArtistRootPrefix.ts +++ b/lib/content/getArtistRootPrefix.ts @@ -1,3 +1,11 @@ +/** + * Determines the root path prefix for an artist directory within the repo file tree. + * Prefers the canonical `artists//` prefix, falling back to `/` if needed. + * + * @param paths - All blob paths in the repository file tree. + * @param artistSlug - The artist's directory slug. + * @returns The root prefix string (e.g. `"artists/my-artist/"`) to use when constructing file paths. + */ export function getArtistRootPrefix(paths: string[], artistSlug: string): string { const preferredPrefix = `artists/${artistSlug}/`; if (paths.some(path => path.startsWith(preferredPrefix))) { diff --git a/lib/content/getContentEstimateHandler.ts b/lib/content/getContentEstimateHandler.ts index 3d4d3a38..ca1e3fea 100644 --- a/lib/content/getContentEstimateHandler.ts +++ b/lib/content/getContentEstimateHandler.ts @@ -9,7 +9,8 @@ const BASE_AUDIO_TO_VIDEO_COST = 0.95; /** * Handler for GET /api/content/estimate. * - * @param request + * @param request - The incoming Next.js request with lipsync, batch, and compare query params. + * @returns A NextResponse containing per-video and total cost estimates in USD. */ export async function getContentEstimateHandler(request: NextRequest): Promise { const validated = await validateGetContentEstimateQuery(request); diff --git a/lib/content/getContentTemplatesHandler.ts b/lib/content/getContentTemplatesHandler.ts index d1a65d80..4fdd08f5 100644 --- a/lib/content/getContentTemplatesHandler.ts +++ b/lib/content/getContentTemplatesHandler.ts @@ -7,7 +7,8 @@ import { CONTENT_TEMPLATES } from "@/lib/content/contentTemplates"; /** * Handler for GET /api/content/templates. * - * @param request + * @param request - The incoming Next.js request (requires valid auth token). + * @returns A NextResponse containing the list of supported content templates. */ export async function getContentTemplatesHandler(request: NextRequest): Promise { const authResult = await validateAuthContext(request); diff --git a/lib/content/getContentValidateHandler.ts b/lib/content/getContentValidateHandler.ts index e0c758b8..2b192b4e 100644 --- a/lib/content/getContentValidateHandler.ts +++ b/lib/content/getContentValidateHandler.ts @@ -8,6 +8,9 @@ import { getArtistContentReadiness } from "@/lib/content/getArtistContentReadine * Handler for GET /api/content/validate. * NOTE: Phase 1 returns structural readiness scaffolding. Deep filesystem checks * are performed in the background task before spend-heavy steps. + * + * @param request - The incoming Next.js request with artist_account_id query param. + * @returns A NextResponse containing readiness status, missing required files, and warnings. */ export async function getContentValidateHandler(request: NextRequest): Promise { const validated = await validateGetContentValidateQuery(request); @@ -22,7 +25,12 @@ export async function getContentValidateHandler(request: NextRequest): Promise(run: T): Promise { if (run.taskIdentifier !== CREATE_CONTENT_TASK_ID || !isCompletedRun(run)) { diff --git a/lib/content/validateCreateContentBody.ts b/lib/content/validateCreateContentBody.ts index e1e8c343..94d92088 100644 --- a/lib/content/validateCreateContentBody.ts +++ b/lib/content/validateCreateContentBody.ts @@ -43,6 +43,10 @@ export type ValidatedCreateContentBody = { /** * Validates auth and request body for POST /api/content/create. + * + * @param request - The incoming Next.js request containing the JSON body. + * @returns A NextResponse with an error if validation or auth fails, + * otherwise the validated and normalized content creation payload. */ export async function validateCreateContentBody( request: NextRequest, diff --git a/lib/content/validateGetContentEstimateQuery.ts b/lib/content/validateGetContentEstimateQuery.ts index 5828e7cc..e2e844d1 100644 --- a/lib/content/validateGetContentEstimateQuery.ts +++ b/lib/content/validateGetContentEstimateQuery.ts @@ -15,6 +15,10 @@ export type ValidatedGetContentEstimateQuery = z.infer { input: "0.00003", // $0.03 per 1K tokens output: "0.00006", // $0.06 per 1K tokens }, - } as any); + } as unknown); const usage = { promptTokens: 1000, @@ -59,7 +59,7 @@ describe("getCreditUsage", () => { input: "0.00003", output: "0.00006", }, - } as any); + } as unknown); const usage = { promptTokens: undefined as unknown as number, @@ -78,7 +78,7 @@ describe("getCreditUsage", () => { input: "0.00003", output: "0.00006", }, - } as any); + } as unknown); const usage = { promptTokens: 1000, @@ -94,7 +94,7 @@ describe("getCreditUsage", () => { mockGetModel.mockResolvedValue({ id: "gpt-4", // No pricing property - } as any); + } as unknown); const usage = { promptTokens: 1000, @@ -114,7 +114,7 @@ describe("getCreditUsage", () => { input: "0.00003", output: "0.00006", }, - } as any); + } as unknown); const usage = { promptTokens: 0, diff --git a/lib/credits/getCreditUsage.ts b/lib/credits/getCreditUsage.ts index 7cd29c9f..359915fd 100644 --- a/lib/credits/getCreditUsage.ts +++ b/lib/credits/getCreditUsage.ts @@ -3,6 +3,7 @@ import { LanguageModelUsage } from "ai"; /** * Calculates the total spend in USD for a given language model usage. + * * @param usage - The language model usage data * @param modelId - The ID of the model used * @returns The total spend in USD or 0 if calculation fails @@ -20,8 +21,9 @@ export const getCreditUsage = async ( // LanguageModelUsage uses inputTokens/outputTokens (SDK v3) // or promptTokens/completionTokens (SDK v2 compatibility) - const inputTokens = (usage as any).inputTokens ?? (usage as any).promptTokens; - const outputTokens = (usage as any).outputTokens ?? (usage as any).completionTokens; + const usageRecord = usage as Record; + const inputTokens = usageRecord["inputTokens"] ?? usageRecord["promptTokens"]; + const outputTokens = usageRecord["outputTokens"] ?? usageRecord["completionTokens"]; if (!inputTokens || !outputTokens) { console.error("No tokens found in usage"); diff --git a/lib/credits/handleChatCredits.ts b/lib/credits/handleChatCredits.ts index c0462eab..1af37dbc 100644 --- a/lib/credits/handleChatCredits.ts +++ b/lib/credits/handleChatCredits.ts @@ -11,9 +11,11 @@ interface HandleChatCreditsParams { /** * Handles credit deduction after chat completion. * Calculates usage cost and deducts appropriate credits from the user's account. - * @param usage - The language model usage data - * @param model - The model ID used for the chat - * @param accountId - The account ID to deduct credits from (optional) + * + * @param root0 - The chat credits parameters + * @param root0.usage - The language model usage data + * @param root0.model - The model ID used for the chat + * @param root0.accountId - The account ID to deduct credits from (optional) */ export const handleChatCredits = async ({ usage, diff --git a/lib/emails/inbound/__tests__/validateNewEmailMemory.test.ts b/lib/emails/inbound/__tests__/validateNewEmailMemory.test.ts index 17c164d4..19b92674 100644 --- a/lib/emails/inbound/__tests__/validateNewEmailMemory.test.ts +++ b/lib/emails/inbound/__tests__/validateNewEmailMemory.test.ts @@ -56,8 +56,10 @@ const MOCK_EMAIL_ID = "email-123"; const MOCK_MESSAGE_ID = "msg-456"; /** + * Creates a mock Resend email received event with sensible defaults. * - * @param overrides + * @param overrides - Optional partial data fields to override on the event + * @returns A fully-formed mock ResendEmailReceivedEvent */ function createMockEvent( overrides?: Partial, diff --git a/lib/emails/processAndSendEmail.ts b/lib/emails/processAndSendEmail.ts index 934939ca..252fdf5e 100644 --- a/lib/emails/processAndSendEmail.ts +++ b/lib/emails/processAndSendEmail.ts @@ -34,6 +34,9 @@ export type ProcessAndSendEmailResult = ProcessAndSendEmailSuccess | ProcessAndS * * Handles room lookup, footer generation, markdown-to-HTML conversion, * and the Resend API call. + * + * @param input - The email input including recipients, subject, body, and optional room context + * @returns A result indicating success with the Resend email ID, or an error message */ export async function processAndSendEmail( input: ProcessAndSendEmailInput, diff --git a/lib/evals/callChatFunctions.ts b/lib/evals/callChatFunctions.ts index ef770a2b..947db421 100644 --- a/lib/evals/callChatFunctions.ts +++ b/lib/evals/callChatFunctions.ts @@ -2,10 +2,12 @@ import { callChatFunctionsWithResult } from "./callChatFunctionsWithResult"; import { extractTextFromResult } from "./extractTextFromResult"; /** - * Call the chat functions directly instead of making HTTP requests - * This function encapsulates the logic for calling the chat system - * and can be reused across different evaluations. + * Calls the chat functions directly and returns the plain text response. + * Encapsulates the logic for calling the chat system and can be reused + * across different evaluations. * + * @param input - The user input string to send to the chat system + * @returns The plain text response extracted from the chat result * @deprecated Use callChatFunctionsWithResult for access to tool calls */ export async function callChatFunctions(input: string): Promise { diff --git a/lib/evals/callChatFunctionsWithResult.ts b/lib/evals/callChatFunctionsWithResult.ts index a792248b..adf7d8c1 100644 --- a/lib/evals/callChatFunctionsWithResult.ts +++ b/lib/evals/callChatFunctionsWithResult.ts @@ -4,10 +4,15 @@ import { setupChatRequest } from "@/lib/chat/setupChatRequest"; import { ChatRequestBody } from "@/lib/chat/validateChatRequest"; /** - * Call the chat functions and return the full result including tool calls from ALL steps. + * Calls the chat functions and returns the full result including tool calls from ALL steps. + * Unlike callChatFunctions, this preserves the complete result object so callers can + * inspect which tools were invoked across every step of a multi-step tool chain. * * Note: result.toolCalls only contains calls from the LAST step. When using multi-step * tool chains, we need to collect toolCalls from result.steps to capture all tool usage. + * + * @param input - The user input string to send to the chat system + * @returns The full generate result merged with all tool calls collected from every step */ export async function callChatFunctionsWithResult(input: string) { const messages: UIMessage[] = [ diff --git a/lib/evals/createToolsCalledScorer.ts b/lib/evals/createToolsCalledScorer.ts index 1d838ee3..15e61d00 100644 --- a/lib/evals/createToolsCalledScorer.ts +++ b/lib/evals/createToolsCalledScorer.ts @@ -1,8 +1,12 @@ import { ToolsCalled } from "./scorers/ToolsCalled"; /** - * Creates a scorer that checks if required tools were called. - * Handles extracting output text and toolCalls from the task result. + * Creates a scorer that checks whether the required tools were called during + * a chat evaluation, and optionally penalizes calls to disallowed tools. + * + * @param requiredTools - List of tool name substrings that must be called for a full score + * @param penalizedTools - List of tool name substrings whose calls reduce the score + * @returns An async scorer function compatible with the evaluation framework */ export const createToolsCalledScorer = (requiredTools: string[], penalizedTools: string[] = []) => { return async (args: { output: unknown; expected?: string; input: string }) => { diff --git a/lib/evals/extractTextFromResult.ts b/lib/evals/extractTextFromResult.ts index fac24cf6..31b13106 100644 --- a/lib/evals/extractTextFromResult.ts +++ b/lib/evals/extractTextFromResult.ts @@ -2,7 +2,12 @@ import { generateText } from "ai"; import { extractTextResultFromSteps } from "./extractTextResultFromSteps"; /** - * Extract text from a GenerateTextResult + * Extracts plain text from a GenerateTextResult, handling both single-step and + * multi-step responses. Falls back to the top-level text or content fields when + * no steps are present. + * + * @param result - The GenerateTextResult returned by the AI SDK generateText call + * @returns The extracted text string, or a fallback message if no content was found */ export function extractTextFromResult(result: Awaited>): string { // Handle multi-step responses (when maxSteps > 1) diff --git a/lib/evals/extractTextResultFromSteps.ts b/lib/evals/extractTextResultFromSteps.ts index 44c0ae0d..38dba880 100644 --- a/lib/evals/extractTextResultFromSteps.ts +++ b/lib/evals/extractTextResultFromSteps.ts @@ -2,8 +2,11 @@ import { generateText } from "ai"; import type { TextPart } from "ai"; /** - * Extract text from multi-step GenerateTextResult - * Handles responses where maxSteps > 1 + * Extracts the final text content from the last step of a multi-step GenerateTextResult. + * Joins all TextPart content from the last step's content array into a single string. + * + * @param result - The GenerateTextResult returned by the AI SDK generateText call + * @returns The concatenated text from the last step, or null if no steps or text parts exist */ export function extractTextResultFromSteps( result: Awaited>, diff --git a/lib/evals/getCatalogSongsCountExpected.ts b/lib/evals/getCatalogSongsCountExpected.ts index 6f04e59c..25382978 100644 --- a/lib/evals/getCatalogSongsCountExpected.ts +++ b/lib/evals/getCatalogSongsCountExpected.ts @@ -2,6 +2,13 @@ import { getCatalogs } from "@/lib/catalog/getCatalogs"; import { getCatalogSongs } from "@/lib/catalog/getCatalogSongs"; import { EVAL_ACCOUNT_ID } from "@/lib/consts"; +/** + * Fetches the expected catalog song count string for the eval account's first catalog. + * Used as ground-truth data when evaluating whether the AI correctly reports catalog size. + * + * @returns An object with catalogId, catalogName, count, and the formatted expected string; + * falls back to zero-count values if the catalog data cannot be retrieved. + */ async function getCatalogSongsCountExpected() { try { const catalogsData = await getCatalogs(EVAL_ACCOUNT_ID); diff --git a/lib/evals/getSpotifyFollowersExpected.ts b/lib/evals/getSpotifyFollowersExpected.ts index ef96e248..1e3a823c 100644 --- a/lib/evals/getSpotifyFollowersExpected.ts +++ b/lib/evals/getSpotifyFollowersExpected.ts @@ -1,5 +1,13 @@ import { getSpotifyFollowers } from "@/lib/spotify/getSpotifyFollowers"; +/** + * Fetches the expected Spotify follower count string for a given artist. + * Used as ground-truth data when evaluating whether the AI correctly reports follower numbers. + * + * @param artist - The artist name to look up on Spotify + * @returns An object with followerCount and the formatted expected string; + * falls back to a zero-count message if the data cannot be retrieved. + */ async function getSpotifyFollowersExpected(artist: string) { try { const followerCount = await getSpotifyFollowers(artist); diff --git a/lib/evals/scorers/CatalogAvailability.ts b/lib/evals/scorers/CatalogAvailability.ts index f4829ea4..fc4639e8 100644 --- a/lib/evals/scorers/CatalogAvailability.ts +++ b/lib/evals/scorers/CatalogAvailability.ts @@ -4,7 +4,15 @@ import { getCatalogDataAsCSV } from "@/lib/catalog/getCatalogDataAsCSV"; import { z } from "zod"; /** - * Custom scorer that uses AI to check if recommended songs are actually in the catalog + * Custom scorer that uses AI to check if recommended songs are actually in the catalog. + * Fetches the catalog CSV and asks the model to match each recommendation against it, + * returning a 0–1 score based on how many recommended songs were found. + * + * @param root0 - The scorer arguments object + * @param root0.output - The AI response text containing song recommendations to evaluate + * @param root0.expected - The expected output (unused by this scorer but required by the interface) + * @param root0.input - The original user question that prompted the recommendations + * @returns A scorer result with name, numeric score, and metadata about matched/unmatched songs */ export const CatalogAvailability = async ({ output, diff --git a/lib/evals/scorers/QuestionAnswered.ts b/lib/evals/scorers/QuestionAnswered.ts index abe0222c..6cd4a7e5 100644 --- a/lib/evals/scorers/QuestionAnswered.ts +++ b/lib/evals/scorers/QuestionAnswered.ts @@ -4,7 +4,14 @@ import { z } from "zod"; /** * Custom scorer that checks if the AI actually answered the customer's question - * with a specific answer, or if it deflected/explained why it couldn't answer + * with a specific answer, or if it deflected/explained why it couldn't answer. + * Uses an LLM to evaluate the response and returns a 0–1 score. + * + * @param root0 - The scorer arguments object + * @param root0.output - The AI response text to evaluate + * @param root0.expected - Optional description of the expected answer type + * @param root0.input - The original customer question + * @returns A scorer result with name, numeric score, and metadata about the evaluation */ export const QuestionAnswered = async ({ output, diff --git a/lib/evals/scorers/ToolsCalled.ts b/lib/evals/scorers/ToolsCalled.ts index 2d901ec3..ef42371f 100644 --- a/lib/evals/scorers/ToolsCalled.ts +++ b/lib/evals/scorers/ToolsCalled.ts @@ -1,5 +1,16 @@ /** - * Generic scorer that checks if specific tools were called + * Generic scorer that checks if specific tools were called during an evaluation run. + * Scores 0–1 based on the fraction of required tools that appear in the tool call list, + * with optional penalties for calls to disallowed tools. + * + * @param root0 - The scorer arguments object + * @param root0.output - The AI response text (used for interface compatibility) + * @param root0.expected - The expected output string (unused, required by scorer interface) + * @param root0.input - The original user input that triggered the tool calls + * @param root0.toolCalls - Array of tool call records collected from all generation steps + * @param root0.requiredTools - Tool name substrings that must be called for a full score + * @param root0.penalizedTools - Tool name substrings whose calls reduce the score by 0.3 each + * @returns A scorer result with name, numeric score, and metadata listing called/missing tools */ export const ToolsCalled = async ({ toolCalls, diff --git a/lib/flamingo/__tests__/getFlamingoPresetsHandler.test.ts b/lib/flamingo/__tests__/getFlamingoPresetsHandler.test.ts index 19109b2d..25605d2f 100644 --- a/lib/flamingo/__tests__/getFlamingoPresetsHandler.test.ts +++ b/lib/flamingo/__tests__/getFlamingoPresetsHandler.test.ts @@ -17,6 +17,11 @@ vi.mock("../presets", () => ({ getPresetSummaries: vi.fn(), })); +/** + * Creates a minimal mock NextRequest with an x-api-key header for testing authentication. + * + * @returns A minimal NextRequest-shaped object for use in handler tests + */ function createMockRequest(): NextRequest { return { headers: new Headers({ "x-api-key": "test-key" }), diff --git a/lib/flamingo/getFlamingoPresetsHandler.ts b/lib/flamingo/getFlamingoPresetsHandler.ts index e35b5899..3ab68dfc 100644 --- a/lib/flamingo/getFlamingoPresetsHandler.ts +++ b/lib/flamingo/getFlamingoPresetsHandler.ts @@ -10,6 +10,7 @@ import { validateAuthContext } from "@/lib/auth/validateAuthContext"; * Returns a list of all available analysis presets. * Requires authentication via x-api-key header or Authorization bearer token. * + * @param request - The incoming request with authentication headers * @returns A NextResponse with the list of available presets. */ export async function getFlamingoPresetsHandler(request: NextRequest): Promise { diff --git a/lib/flamingo/postFlamingoGenerateHandler.ts b/lib/flamingo/postFlamingoGenerateHandler.ts index a971a361..1473090a 100644 --- a/lib/flamingo/postFlamingoGenerateHandler.ts +++ b/lib/flamingo/postFlamingoGenerateHandler.ts @@ -56,10 +56,10 @@ export async function postFlamingoGenerateHandler(request: NextRequest): Promise ); } - // 4. Return flat response - const { type: _, ...data } = result; + // 4. Return flat response — exclude the internal `type` discriminant from the response + const responseData = Object.fromEntries(Object.entries(result).filter(([k]) => k !== "type")); return NextResponse.json( - { status: "success", ...data }, + { status: "success", ...responseData }, { status: 200, headers: getCorsHeaders() }, ); } diff --git a/lib/github/__tests__/createOrUpdateFileContent.test.ts b/lib/github/__tests__/createOrUpdateFileContent.test.ts index 8e2a19a1..a73bd49c 100644 --- a/lib/github/__tests__/createOrUpdateFileContent.test.ts +++ b/lib/github/__tests__/createOrUpdateFileContent.test.ts @@ -1,12 +1,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { createOrUpdateFileContent } from "../createOrUpdateFileContent"; +import { parseGitHubRepoUrl } from "../parseGitHubRepoUrl"; vi.mock("../parseGitHubRepoUrl", () => ({ parseGitHubRepoUrl: vi.fn(), })); -import { parseGitHubRepoUrl } from "../parseGitHubRepoUrl"; - const mockFetch = vi.fn(); global.fetch = mockFetch; diff --git a/lib/github/__tests__/expandSubmoduleEntries.test.ts b/lib/github/__tests__/expandSubmoduleEntries.test.ts index aa7fdfb8..6703f9a9 100644 --- a/lib/github/__tests__/expandSubmoduleEntries.test.ts +++ b/lib/github/__tests__/expandSubmoduleEntries.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, vi, beforeEach } from "vitest"; import { expandSubmoduleEntries } from "../expandSubmoduleEntries"; import { getRepoGitModules } from "../getRepoGitModules"; import { getRepoFileTree } from "../getRepoFileTree"; diff --git a/lib/github/expandSubmoduleEntries.ts b/lib/github/expandSubmoduleEntries.ts index 9531bee1..f9ec41e8 100644 --- a/lib/github/expandSubmoduleEntries.ts +++ b/lib/github/expandSubmoduleEntries.ts @@ -11,10 +11,14 @@ interface SubmoduleRef { * Resolves submodule URLs from .gitmodules, fetches each submodule's tree, * and merges the results into the regular entries with correct path prefixes. * - * @param regularEntries - Non-submodule file tree entries - * @param submoduleEntries - Submodule references (type "commit" from GitHub Trees API) - * @param repo - Repository context for fetching .gitmodules - * @returns Combined file tree entries with submodules expanded as directories + * @param root0 - Options object. + * @param root0.regularEntries - Non-submodule file tree entries. + * @param root0.submoduleEntries - Submodule references (type "commit" from GitHub Trees API). + * @param root0.repo - Repository context for fetching .gitmodules. + * @param root0.repo.owner - The GitHub repository owner. + * @param root0.repo.repo - The GitHub repository name. + * @param root0.repo.branch - The branch to read .gitmodules from. + * @returns Combined file tree entries with submodules expanded as directories. */ export async function expandSubmoduleEntries({ regularEntries, diff --git a/lib/github/getRepoGitModules.ts b/lib/github/getRepoGitModules.ts index caa0304e..f54b9f84 100644 --- a/lib/github/getRepoGitModules.ts +++ b/lib/github/getRepoGitModules.ts @@ -4,10 +4,11 @@ import { parseGitModules, type SubmoduleEntry } from "./parseGitModules"; * Fetches and parses .gitmodules from a GitHub repository. * Uses the GitHub Contents API (works for both public and private repos). * - * @param owner - The GitHub repository owner - * @param repo - The GitHub repository name - * @param branch - The branch to fetch from - * @returns Array of submodule entries, or null if .gitmodules doesn't exist or fetch fails + * @param root0 - The repository identification object. + * @param root0.owner - The GitHub repository owner. + * @param root0.repo - The GitHub repository name. + * @param root0.branch - The branch to fetch from. + * @returns Array of submodule entries, or null if .gitmodules doesn't exist or fetch fails. */ export async function getRepoGitModules({ owner, diff --git a/lib/github/resolveSubmodulePath.ts b/lib/github/resolveSubmodulePath.ts index 7c3f60ed..b4800416 100644 --- a/lib/github/resolveSubmodulePath.ts +++ b/lib/github/resolveSubmodulePath.ts @@ -6,9 +6,10 @@ import { getRepoGitModules } from "./getRepoGitModules"; * If the path falls within a submodule, returns the submodule's repo URL * and the relative path within it. Otherwise returns the original values. * - * @param githubRepo - The parent GitHub repository URL - * @param path - The file path to resolve - * @returns The resolved repo URL and path + * @param root0 - Options object. + * @param root0.githubRepo - The parent GitHub repository URL. + * @param root0.path - The file path to resolve, potentially inside a submodule. + * @returns The resolved repo URL and relative path (may point to a submodule repo). */ export async function resolveSubmodulePath({ githubRepo, diff --git a/lib/mcp/__tests__/getMcpTools.test.ts b/lib/mcp/__tests__/getMcpTools.test.ts index 860c575e..5d2709b9 100644 --- a/lib/mcp/__tests__/getMcpTools.test.ts +++ b/lib/mcp/__tests__/getMcpTools.test.ts @@ -24,7 +24,7 @@ describe("getMcpTools", () => { mockCreateMCPClient.mockResolvedValue({ tools: vi.fn().mockResolvedValue(mockTools), - } as any); + } as unknown); }); it("creates MCP client with HTTP transport config", async () => { diff --git a/lib/mcp/resolveAccountId.ts b/lib/mcp/resolveAccountId.ts index 03d1d0d8..52e206f1 100644 --- a/lib/mcp/resolveAccountId.ts +++ b/lib/mcp/resolveAccountId.ts @@ -15,7 +15,9 @@ export interface ResolveAccountIdResult { * Resolves the accountId from MCP auth info or an override parameter. * Validates access when an org API key attempts to use an account_id override. * - * @param params - The auth info and optional account_id override. + * @param root0 - The resolution parameters. + * @param root0.authInfo - The MCP auth info from the request, containing the authenticated account ID. + * @param root0.accountIdOverride - An optional account ID override (used by org API keys to act on behalf of member accounts). * @returns The resolved accountId or an error message. */ export async function resolveAccountId({ diff --git a/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts b/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts index 47e912af..3f4f8fee 100644 --- a/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts +++ b/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts @@ -26,9 +26,10 @@ type ServerRequestHandlerExtra = RequestHandlerExtra key !== "type")); return getToolResultSuccess(data); }, ); diff --git a/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts b/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts index a0c80c8a..9fdc4fa9 100644 --- a/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts +++ b/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts @@ -20,8 +20,9 @@ type ServerRequestHandlerExtra = RequestHandlerExtra; +/** + * Registers the transcribe_audio MCP tool on the server. + * Transcribes audio files using OpenAI Whisper and saves the results to Supabase file storage. + * + * @param server - The MCP server instance to register the tool on. + */ export function registerTranscribeAudioTool(server: McpServer): void { server.registerTool( "transcribe_audio", diff --git a/lib/messages/__tests__/convertToUiMessages.test.ts b/lib/messages/__tests__/convertToUiMessages.test.ts index a5410430..c13e9c74 100644 --- a/lib/messages/__tests__/convertToUiMessages.test.ts +++ b/lib/messages/__tests__/convertToUiMessages.test.ts @@ -144,7 +144,7 @@ describe("convertToUiMessages", () => { }, ]; - const result = convertToUiMessages(messages as any); + const result = convertToUiMessages(messages as unknown[]); expect(result[0].parts[0].text).toBe("Hello world!"); }); diff --git a/lib/messages/__tests__/getTextContent.test.ts b/lib/messages/__tests__/getTextContent.test.ts index 1822bb4a..17f6bca8 100644 --- a/lib/messages/__tests__/getTextContent.test.ts +++ b/lib/messages/__tests__/getTextContent.test.ts @@ -31,7 +31,7 @@ describe("getTextContent", () => { { type: "text" as const, text: "Hello" }, { type: "image" as const, image: "data:image/png;base64,..." }, { type: "text" as const, text: " world" }, - ] as any; + ] as unknown[]; expect(getTextContent(content)).toBe("Hello world"); }); @@ -40,7 +40,7 @@ describe("getTextContent", () => { }); it("returns empty string when no text parts exist", () => { - const content = [{ type: "image" as const, image: "data:image/png;base64,..." }] as any; + const content = [{ type: "image" as const, image: "data:image/png;base64,..." }] as unknown[]; expect(getTextContent(content)).toBe(""); }); }); diff --git a/lib/notifications/__tests__/createNotificationHandler.test.ts b/lib/notifications/__tests__/createNotificationHandler.test.ts index ca7fb677..a1fc6f7b 100644 --- a/lib/notifications/__tests__/createNotificationHandler.test.ts +++ b/lib/notifications/__tests__/createNotificationHandler.test.ts @@ -26,6 +26,12 @@ vi.mock("@/lib/networking/safeParseJson", () => ({ safeParseJson: vi.fn(async (req: Request) => req.json()), })); +/** + * Creates a mock POST request to the notifications endpoint with JSON body and auth header. + * + * @param body - The request body to serialize as JSON + * @returns A NextRequest instance for use in tests + */ function createRequest(body: unknown): NextRequest { return new NextRequest("https://recoup-api.vercel.app/api/notifications", { method: "POST", diff --git a/lib/notifications/__tests__/validateCreateNotificationBody.test.ts b/lib/notifications/__tests__/validateCreateNotificationBody.test.ts index 10390b15..1edf12d2 100644 --- a/lib/notifications/__tests__/validateCreateNotificationBody.test.ts +++ b/lib/notifications/__tests__/validateCreateNotificationBody.test.ts @@ -16,6 +16,13 @@ vi.mock("@/lib/networking/safeParseJson", () => ({ safeParseJson: vi.fn(async (req: Request) => req.json()), })); +/** + * Creates a mock POST request to the notifications endpoint with JSON body and optional headers. + * + * @param body - The request body to serialize as JSON + * @param headers - Optional extra headers to merge with defaults + * @returns A NextRequest instance for use in tests + */ function createRequest(body: unknown, headers: Record = {}): NextRequest { const defaultHeaders: Record = { "Content-Type": "application/json" }; return new NextRequest("http://localhost/api/notifications", { diff --git a/lib/prompts/__tests__/getSystemPrompt.test.ts b/lib/prompts/__tests__/getSystemPrompt.test.ts index 942608af..757ffaf3 100644 --- a/lib/prompts/__tests__/getSystemPrompt.test.ts +++ b/lib/prompts/__tests__/getSystemPrompt.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "vitest"; import { getSystemPrompt } from "../getSystemPrompt"; +import type { AccountWithDetails } from "@/lib/supabase/accounts/getAccountWithDetails"; describe("getSystemPrompt", () => { describe("basic functionality", () => { @@ -91,7 +92,7 @@ describe("getSystemPrompt", () => { id: "acc-1", name: "John Doe", email: "john@example.com", - } as any, + } as unknown as AccountWithDetails, }); expect(result).toContain("-----CURRENT USER CONTEXT-----"); expect(result).toContain("Name: John Doe"); @@ -103,7 +104,7 @@ describe("getSystemPrompt", () => { accountId: "acc-1", accountWithDetails: { id: "acc-1", - } as any, + } as unknown as AccountWithDetails, }); expect(result).toContain("Name: Not provided"); }); @@ -117,7 +118,7 @@ describe("getSystemPrompt", () => { role_type: "Manager", company_name: "Warner Music", organization: "Warner Records", - } as any, + } as unknown as AccountWithDetails, }); expect(result).toContain("Professional Context:"); expect(result).toContain("Job Title: Music Manager"); @@ -132,7 +133,7 @@ describe("getSystemPrompt", () => { accountWithDetails: { id: "acc-1", name: "John", - } as any, + } as unknown as AccountWithDetails, }); expect(result).not.toContain("Professional Context:"); }); @@ -143,7 +144,7 @@ describe("getSystemPrompt", () => { accountWithDetails: { id: "acc-1", instruction: "Always respond in formal English", - } as any, + } as unknown as AccountWithDetails, }); expect(result).toContain("User's Custom Instructions & Preferences:"); expect(result).toContain("Always respond in formal English"); @@ -152,7 +153,7 @@ describe("getSystemPrompt", () => { it("includes end marker for user context", () => { const result = getSystemPrompt({ accountId: "acc-1", - accountWithDetails: { id: "acc-1" } as any, + accountWithDetails: { id: "acc-1" } as unknown as AccountWithDetails, }); expect(result).toContain("-----END USER CONTEXT-----"); }); @@ -164,7 +165,7 @@ describe("getSystemPrompt", () => { accountWithDetails: { id: "acc-1", email: "details@example.com", - } as any, + } as unknown as AccountWithDetails, }); expect(result).toContain("Email: details@example.com"); }); @@ -175,7 +176,7 @@ describe("getSystemPrompt", () => { email: "fallback@example.com", accountWithDetails: { id: "acc-1", - } as any, + } as unknown as AccountWithDetails, }); expect(result).toContain("Email: fallback@example.com"); }); @@ -233,7 +234,7 @@ describe("getSystemPrompt", () => { it("places user context before artist context", () => { const result = getSystemPrompt({ accountId: "acc-1", - accountWithDetails: { id: "acc-1", name: "User" } as any, + accountWithDetails: { id: "acc-1", name: "User" } as unknown as AccountWithDetails, artistInstruction: "Artist instructions", }); diff --git a/lib/prompts/getSystemPrompt.ts b/lib/prompts/getSystemPrompt.ts index 54964670..1d888997 100644 --- a/lib/prompts/getSystemPrompt.ts +++ b/lib/prompts/getSystemPrompt.ts @@ -2,18 +2,20 @@ import { SYSTEM_PROMPT } from "@/lib/chat/const"; import { AccountWithDetails } from "@/lib/supabase/accounts/getAccountWithDetails"; /** - * Generates a system prompt for the chat + * Generates a system prompt for the chat, injecting context values and optional + * user, artist, and knowledge-base sections based on the provided parameters. * * @param params - The parameters for the system prompt - * @param params.roomId - The ID of the room - * @param params.artistId - The ID of the artist - * @param params.accountId - The ID of the account - * @param params.email - The email of the account - * @param params.knowledgeBaseText - The knowledge base text - * @param params.artistInstruction - The artist instruction - * @param params.conversationName - The name of the conversation - * @param params.accountWithDetails - The account with details - * @returns The system prompt + * @param params.roomId - The ID of the active conversation room + * @param params.artistId - The account ID of the selected artist or workspace + * @param params.accountId - The authenticated account ID making the request + * @param params.orgId - The organization ID when using an org API key, or null for personal keys + * @param params.email - The email address of the active account + * @param params.knowledgeBaseText - Optional knowledge base content for the selected artist/workspace + * @param params.artistInstruction - Optional custom instructions for the selected artist/workspace + * @param params.conversationName - Display name of the current conversation + * @param params.accountWithDetails - Full account record with profile details for user context section + * @returns The assembled system prompt string */ export function getSystemPrompt({ roomId, diff --git a/lib/sandbox/__tests__/deleteSandboxHandler.test.ts b/lib/sandbox/__tests__/deleteSandboxHandler.test.ts index 4a84eebc..317881ce 100644 --- a/lib/sandbox/__tests__/deleteSandboxHandler.test.ts +++ b/lib/sandbox/__tests__/deleteSandboxHandler.test.ts @@ -28,7 +28,9 @@ vi.mock("@/lib/github/deleteAccountGithubRepos", () => ({ })); /** + * Creates a mock DELETE request to the sandboxes endpoint for testing. * + * @returns A minimal Request object with the DELETE method */ function createMockRequest(): Request { return new Request("https://example.com/api/sandboxes", { diff --git a/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts b/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts index 9c5f2fd1..f5d92259 100644 --- a/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts +++ b/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts @@ -205,7 +205,7 @@ describe("postSandboxesFilesHandler", () => { }); const result = await postSandboxesFilesHandler(mockRequest); - const body = await result.json(); + await result.json(); expect(result.status).toBe(500); // Blobs are always cleaned up to allow retries diff --git a/lib/sandbox/__tests__/validateDeleteSandboxBody.test.ts b/lib/sandbox/__tests__/validateDeleteSandboxBody.test.ts index f309950f..cf5d20e3 100644 --- a/lib/sandbox/__tests__/validateDeleteSandboxBody.test.ts +++ b/lib/sandbox/__tests__/validateDeleteSandboxBody.test.ts @@ -18,7 +18,9 @@ vi.mock("@/lib/auth/validateAuthContext", () => ({ })); /** + * Creates a mock DELETE request to the sandboxes endpoint for testing validation. * + * @returns A minimal Request object with the DELETE method */ function createMockRequest(): Request { return new Request("https://example.com/api/sandboxes", { diff --git a/lib/slack/getBotChannels.ts b/lib/slack/getBotChannels.ts index 01fb47ff..26855ec5 100644 --- a/lib/slack/getBotChannels.ts +++ b/lib/slack/getBotChannels.ts @@ -9,6 +9,9 @@ interface ConversationsListResponse { /** * Returns all channels the bot is a member of, paginating through all results. + * + * @param token - The Slack bot OAuth token used for API authentication + * @returns Array of channel objects with id and name for each joined channel */ export async function getBotChannels(token: string): Promise> { const channels: Array<{ id: string; name: string }> = []; diff --git a/lib/slack/getBotUserId.ts b/lib/slack/getBotUserId.ts index 1c3e0924..b222f7ab 100644 --- a/lib/slack/getBotUserId.ts +++ b/lib/slack/getBotUserId.ts @@ -8,6 +8,9 @@ interface AuthTestResponse { /** * Returns the authenticated bot's Slack user ID via auth.test. + * + * @param token - The Slack bot OAuth token used for API authentication + * @returns The bot's Slack user ID string */ export async function getBotUserId(token: string): Promise { const authTest = await slackGet("auth.test", token); diff --git a/lib/slack/getSlackUserInfo.ts b/lib/slack/getSlackUserInfo.ts index eb144e45..613cbffa 100644 --- a/lib/slack/getSlackUserInfo.ts +++ b/lib/slack/getSlackUserInfo.ts @@ -16,6 +16,10 @@ interface UsersInfoResponse { /** * Fetches a Slack account's display name and avatar by their Slack ID. + * + * @param token - The Slack bot OAuth token used for API authentication + * @param userId - The Slack user ID to look up + * @returns An object with the user's display name and avatar URL (or null if unavailable) */ export async function getSlackUserInfo( token: string, diff --git a/lib/spotify/getSpotifyFollowers.ts b/lib/spotify/getSpotifyFollowers.ts index 235de41e..220b8876 100644 --- a/lib/spotify/getSpotifyFollowers.ts +++ b/lib/spotify/getSpotifyFollowers.ts @@ -36,9 +36,10 @@ interface SpotifySearchResponse { } /** - * Get Spotify follower count for an artist + * Get Spotify follower count for an artist. + * * @param artistName - The name of the artist to search for - * @returns Promise - The follower count of the first matching artist + * @returns The follower count of the first matching artist */ export async function getSpotifyFollowers(artistName: string): Promise { try { diff --git a/lib/supabase/account_artist_ids/getAccountArtistIds.ts b/lib/supabase/account_artist_ids/getAccountArtistIds.ts index e4e6b809..207925f3 100644 --- a/lib/supabase/account_artist_ids/getAccountArtistIds.ts +++ b/lib/supabase/account_artist_ids/getAccountArtistIds.ts @@ -8,14 +8,18 @@ export type AccountArtistRow = ArtistQueryRow & { artist_id: string; pinned: boo * Get all artists for an array of artist IDs or account IDs, with full info. * Returns raw data - formatting should be done by caller. * - * @param params Object with artistIds or accountIds array + * @param root0 - Object with artistIds or accountIds array + * @param root0.artistIds - Optional array of artist account IDs to filter by + * @param root0.accountIds - Optional array of owner account IDs to filter by * @returns Array of raw artist rows from database */ -export async function getAccountArtistIds(params: { +export async function getAccountArtistIds({ + artistIds, + accountIds, +}: { artistIds?: string[]; accountIds?: string[]; }): Promise { - const { artistIds, accountIds } = params; if (!artistIds && !accountIds) { throw new Error("Must provide either artistIds or accountIds"); } diff --git a/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts b/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts index be02e565..0edb640f 100644 --- a/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts +++ b/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts @@ -105,12 +105,10 @@ describe("selectAccountSandboxes", () => { }); // First call is for account_sandboxes, second is for account_organization_ids - let callCount = 0; mockFrom.mockImplementation((table: string) => { if (table === "account_organization_ids") { return { select: mockOrgSelect }; } - callCount++; return { select: mockSelect }; }); diff --git a/lib/supabase/account_workspace_ids/getAccountWorkspaceIds.ts b/lib/supabase/account_workspace_ids/getAccountWorkspaceIds.ts index ae121fdd..4ca7ad8e 100644 --- a/lib/supabase/account_workspace_ids/getAccountWorkspaceIds.ts +++ b/lib/supabase/account_workspace_ids/getAccountWorkspaceIds.ts @@ -10,7 +10,7 @@ export type AccountWorkspaceRow = Omit & { * Get all workspaces for an account, with full info. * Returns raw data - formatting should be done by caller. * - * @param accountId The owner's account ID + * @param accountId - The owner's account ID * @returns Array of raw workspace rows from database */ export async function getAccountWorkspaceIds(accountId: string): Promise { diff --git a/lib/supabase/files/createFileRecord.ts b/lib/supabase/files/createFileRecord.ts index 6f836f3c..e6bc33a0 100644 --- a/lib/supabase/files/createFileRecord.ts +++ b/lib/supabase/files/createFileRecord.ts @@ -24,7 +24,10 @@ export interface CreateFileRecordParams { } /** - * Create a file record in the database + * Create a file record in the database. + * + * @param params - The file record parameters including owner, artist, storage key, and metadata + * @returns The created file record row */ export async function createFileRecord(params: CreateFileRecordParams): Promise { const { diff --git a/lib/supabase/files/selectFileByStorageKey.ts b/lib/supabase/files/selectFileByStorageKey.ts index 78598c3c..06971039 100644 --- a/lib/supabase/files/selectFileByStorageKey.ts +++ b/lib/supabase/files/selectFileByStorageKey.ts @@ -4,9 +4,10 @@ import type { FileRecord } from "@/lib/supabase/files/createFileRecord"; /** * Select a file record by storage key for an owner account. * - * @param root0 - * @param root0.ownerAccountId - * @param root0.storageKey + * @param root0 - The lookup parameters + * @param root0.ownerAccountId - The account ID that owns the file + * @param root0.storageKey - The storage key (path) of the file in Supabase storage + * @returns The matching file record, or null if not found */ export async function selectFileByStorageKey({ ownerAccountId, diff --git a/lib/supabase/song_artists/insertSongArtists.ts b/lib/supabase/song_artists/insertSongArtists.ts index b81879e3..2b76e434 100644 --- a/lib/supabase/song_artists/insertSongArtists.ts +++ b/lib/supabase/song_artists/insertSongArtists.ts @@ -5,6 +5,8 @@ export type SongArtistInsert = TablesInsert<"song_artists">; /** * Inserts song-artist relationships, skipping duplicates. + * + * @param songArtists - Array of song-artist relationship records to insert */ export async function insertSongArtists(songArtists: SongArtistInsert[]): Promise { const records = songArtists.filter( diff --git a/lib/supabase/storage/createSignedFileUrlByKey.ts b/lib/supabase/storage/createSignedFileUrlByKey.ts index 30625b7a..30967396 100644 --- a/lib/supabase/storage/createSignedFileUrlByKey.ts +++ b/lib/supabase/storage/createSignedFileUrlByKey.ts @@ -4,9 +4,10 @@ import { SUPABASE_STORAGE_BUCKET } from "@/lib/const"; /** * Creates a signed URL for a file in Supabase storage. * - * @param root0 - * @param root0.key - * @param root0.expiresInSeconds + * @param root0 - The signed URL parameters + * @param root0.key - The storage key (path) of the file to sign + * @param root0.expiresInSeconds - Number of seconds until the signed URL expires (default: 7 days) + * @returns The signed URL string for accessing the file */ export async function createSignedFileUrlByKey({ key, diff --git a/lib/supabase/storage/uploadFileByKey.ts b/lib/supabase/storage/uploadFileByKey.ts index ba146fa3..83a978fe 100644 --- a/lib/supabase/storage/uploadFileByKey.ts +++ b/lib/supabase/storage/uploadFileByKey.ts @@ -2,7 +2,13 @@ import supabase from "@/lib/supabase/serverClient"; import { SUPABASE_STORAGE_BUCKET } from "@/lib/const"; /** - * Upload file to Supabase storage by key + * Upload file to Supabase storage by key. + * + * @param key - The storage key (path) where the file will be stored + * @param file - The file or blob data to upload + * @param options - Optional upload configuration + * @param options.contentType - MIME type of the file (defaults to application/octet-stream) + * @param options.upsert - Whether to overwrite an existing file at the same key (defaults to false) */ export async function uploadFileByKey( key: string, diff --git a/lib/tasks/__tests__/enrichTaskWithTriggerInfo.test.ts b/lib/tasks/__tests__/enrichTaskWithTriggerInfo.test.ts index 60d38a96..84f844b2 100644 --- a/lib/tasks/__tests__/enrichTaskWithTriggerInfo.test.ts +++ b/lib/tasks/__tests__/enrichTaskWithTriggerInfo.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { enrichTaskWithTriggerInfo } from "../enrichTaskWithTriggerInfo"; +import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns"; +import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun"; vi.mock("@/lib/trigger/fetchTriggerRuns", () => ({ fetchTriggerRuns: vi.fn(), @@ -9,9 +11,6 @@ vi.mock("@/lib/trigger/retrieveTaskRun", () => ({ retrieveTaskRun: vi.fn(), })); -import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns"; -import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun"; - const mockTask = { id: "task-123", title: "Test Task", diff --git a/lib/tasks/__tests__/getTaskRunHandler.test.ts b/lib/tasks/__tests__/getTaskRunHandler.test.ts index 9f17fffc..6fc357f6 100644 --- a/lib/tasks/__tests__/getTaskRunHandler.test.ts +++ b/lib/tasks/__tests__/getTaskRunHandler.test.ts @@ -23,6 +23,11 @@ vi.mock("@/lib/networking/getCorsHeaders", () => ({ getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), })); +/** + * Creates a minimal mock NextRequest for the task run endpoint with an x-api-key header. + * + * @returns A minimal NextRequest-shaped object for use in handler tests + */ function createMockRequest(): NextRequest { return { url: "http://localhost:3000/api/tasks/runs", diff --git a/lib/tasks/__tests__/validateGetTaskRunQuery.test.ts b/lib/tasks/__tests__/validateGetTaskRunQuery.test.ts index f7126175..dc5bcb3f 100644 --- a/lib/tasks/__tests__/validateGetTaskRunQuery.test.ts +++ b/lib/tasks/__tests__/validateGetTaskRunQuery.test.ts @@ -24,6 +24,9 @@ vi.mock("@/lib/admins/checkIsAdmin", () => ({ /** * Creates a mock NextRequest with the given URL. + * + * @param url - The full URL string for the mock request including any query params + * @returns A minimal NextRequest-shaped object for testing */ function createMockRequest(url: string): NextRequest { return { diff --git a/lib/tasks/__tests__/validateGetTasksQuery.test.ts b/lib/tasks/__tests__/validateGetTasksQuery.test.ts index b9d0dda7..3616c2aa 100644 --- a/lib/tasks/__tests__/validateGetTasksQuery.test.ts +++ b/lib/tasks/__tests__/validateGetTasksQuery.test.ts @@ -22,6 +22,12 @@ vi.mock("@/lib/admins/checkIsAdmin", () => ({ checkIsAdmin: vi.fn(), })); +/** + * Creates a minimal mock NextRequest for the tasks endpoint with an x-api-key header. + * + * @param url - The full URL string for the mock request including any query params + * @returns A minimal NextRequest-shaped object for use in handler tests + */ function createMockRequest(url: string): NextRequest { return { url, diff --git a/lib/transcribe/processAudioTranscription.ts b/lib/transcribe/processAudioTranscription.ts index 351eee34..0355f124 100644 --- a/lib/transcribe/processAudioTranscription.ts +++ b/lib/transcribe/processAudioTranscription.ts @@ -7,6 +7,9 @@ import { ProcessTranscriptionParams, ProcessTranscriptionResult } from "./types" /** * Fetches audio from URL, transcribes it with OpenAI Whisper, and saves both * the original audio and transcript markdown to the customer's files. + * + * @param params - The transcription parameters including audio URL, account IDs, and options + * @returns File records for the saved audio and transcript, plus the raw transcription text and language */ export async function processAudioTranscription( params: ProcessTranscriptionParams, @@ -64,6 +67,12 @@ export async function processAudioTranscription( }; } +/** + * Derives a file extension from a MIME content type string. + * + * @param contentType - The MIME type string to map to an extension (e.g. "audio/wav") + * @returns The corresponding file extension string without a leading dot + */ function getExtensionFromContentType(contentType: string): string { if (contentType.includes("wav")) return "wav"; if (contentType.includes("m4a") || contentType.includes("mp4")) return "m4a"; diff --git a/lib/transcribe/saveAudioToFiles.ts b/lib/transcribe/saveAudioToFiles.ts index 12bda1ef..ddf06ead 100644 --- a/lib/transcribe/saveAudioToFiles.ts +++ b/lib/transcribe/saveAudioToFiles.ts @@ -2,6 +2,12 @@ import { uploadFileByKey } from "@/lib/supabase/storage/uploadFileByKey"; import { createFileRecord } from "@/lib/supabase/files/createFileRecord"; import { SaveAudioParams, FileRecord } from "./types"; +/** + * Uploads an audio blob to Supabase storage and creates the corresponding file record. + * + * @param params - The audio save parameters including blob, content type, file name, and account IDs + * @returns The created file record from the database + */ export async function saveAudioToFiles(params: SaveAudioParams): Promise { const { audioBlob, diff --git a/lib/transcribe/saveTranscriptToFiles.ts b/lib/transcribe/saveTranscriptToFiles.ts index 627feb6d..b9a5a9d3 100644 --- a/lib/transcribe/saveTranscriptToFiles.ts +++ b/lib/transcribe/saveTranscriptToFiles.ts @@ -2,6 +2,12 @@ import { uploadFileByKey } from "@/lib/supabase/storage/uploadFileByKey"; import { createFileRecord } from "@/lib/supabase/files/createFileRecord"; import { SaveTranscriptParams, FileRecord } from "./types"; +/** + * Converts markdown transcript text to a blob, uploads it to Supabase storage, and creates the file record. + * + * @param params - The transcript save parameters including markdown content and account IDs + * @returns The created file record from the database + */ export async function saveTranscriptToFiles(params: SaveTranscriptParams): Promise { const { markdown, ownerAccountId, artistAccountId, title = "Transcription" } = params; diff --git a/lib/transcribe/types.ts b/lib/transcribe/types.ts index 91c0ac10..cdb36e73 100644 --- a/lib/transcribe/types.ts +++ b/lib/transcribe/types.ts @@ -56,6 +56,9 @@ export interface ProcessTranscriptionResult { /** * Formats transcription errors into user-friendly messages. * Centralizes error message logic to avoid duplication. + * + * @param error - The caught error value to format + * @returns An object with a human-readable message and an appropriate HTTP status code */ export function formatTranscriptionError(error: unknown): { message: string; status: number } { const rawMessage = error instanceof Error ? error.message : "Transcription failed"; diff --git a/lib/trigger/fetchTriggerRuns.ts b/lib/trigger/fetchTriggerRuns.ts index d19d518f..2fd11e6b 100644 --- a/lib/trigger/fetchTriggerRuns.ts +++ b/lib/trigger/fetchTriggerRuns.ts @@ -1,11 +1,11 @@ export interface TriggerRun { + [key: string]: unknown; id: string; status: string; createdAt: string; startedAt: string | null; finishedAt: string | null; durationMs: number | null; - [key: string]: unknown; } export type TriggerRunFilter = { "filter[tag]": string } | { "filter[schedule]": string }; diff --git a/lib/trigger/triggerCreateContent.ts b/lib/trigger/triggerCreateContent.ts index c40bd53d..89d744a5 100644 --- a/lib/trigger/triggerCreateContent.ts +++ b/lib/trigger/triggerCreateContent.ts @@ -18,6 +18,9 @@ export interface TriggerCreateContentPayload { /** * Triggers the create-content task in Trigger.dev. + * + * @param payload - The content creation parameters including account, artist, template, and media options + * @returns The Trigger.dev task handle for tracking the triggered run */ export async function triggerCreateContent(payload: TriggerCreateContentPayload) { const handle = await tasks.trigger(CREATE_CONTENT_TASK_ID, payload); diff --git a/lib/workspaces/createWorkspaceInDb.ts b/lib/workspaces/createWorkspaceInDb.ts index d7684c5b..712db62a 100644 --- a/lib/workspaces/createWorkspaceInDb.ts +++ b/lib/workspaces/createWorkspaceInDb.ts @@ -49,7 +49,7 @@ export async function createWorkspaceInDb( account_id: workspace.id, isWorkspace: true, }; - } catch (error) { + } catch { return null; } }