import { getInstallationOctokitByOwner, isUserCollaborator } from "../github/github-utils.js" import { githubApp } from "../github/github-app.js" import { Request, Response } from "express" import { parseAndCreateOptimizationsDict } from "../github/pr-changes-utils.js" import { posthog } from "../analytics.js" import { userNickname } from "../auth0-mgmt.js" import { logger } from "../utils/logger.js" import { missingRequiredFields, unauthorized, githubInstallationError, githubNotCollaborator, githubPrNotFound, internalServerError, } from "../exceptions/index.js" // Dependencies interface for easier testing export interface VerifyExistingOptimizationsDependencies { getInstallationOctokitByOwner: typeof getInstallationOctokitByOwner githubApp: typeof githubApp parseAndCreateOptimizationsDict: typeof parseAndCreateOptimizationsDict posthog: typeof posthog userNickname: typeof userNickname isUserCollaborator: typeof isUserCollaborator } // Default dependencies let dependencies: VerifyExistingOptimizationsDependencies = { getInstallationOctokitByOwner, githubApp, parseAndCreateOptimizationsDict, posthog, userNickname, isUserCollaborator, } // For testing - allow dependency injection export function setVerifyExistingOptimizationsDependencies( deps: Partial, ) { dependencies = { ...dependencies, ...deps } } export function resetVerifyExistingOptimizationsDependencies() { dependencies = { getInstallationOctokitByOwner, githubApp, parseAndCreateOptimizationsDict, posthog, userNickname, isUserCollaborator, } } export async function verifyExistingOptimizations(req: Request, res: Response) { try { const { repo_owner, repo_name, pr_number } = req.body const userId = (req as any).userId logger.debug("Processing verify-existing-optimizations request", { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "request_start", repo_owner, repo_name, pr_number, }) if (!repo_name || !repo_owner || !pr_number) { throw missingRequiredFields("repo_name, repo_owner, pr_number") } const nickname: string | null = await dependencies.userNickname(userId) if (nickname == null) { throw unauthorized("") } const octokit = await dependencies.getInstallationOctokitByOwner( dependencies.githubApp, repo_owner, repo_name, userId, ) if (octokit instanceof Error) { throw githubInstallationError(octokit.message) } logger.info("Got installation Octokit for repository", { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "octokit_installation", repo_owner, repo_name, }) // Check collaborator status with error handling try { const isCollaborator = await dependencies.isUserCollaborator( octokit, repo_owner, repo_name, nickname, ) if (!isCollaborator) { // Emit human-readable console message for tests (using info for console.log output) // Don't include userId in context to avoid logger appending " for user ..." logger.info(`${nickname} is not a collaborator on ${repo_owner}/${repo_name}`, { requestId: (req as any).requestId, endpoint: "/cfapi/verify-existing-optimizations", operation: "collaborator_verification", repo_owner, repo_name, nickname, }) throw githubNotCollaborator(`${repo_owner}/${repo_name}`) } } catch (error) { if (error && typeof error === "object" && "getHttpStatus" in error) { throw error } // Concatenate error in message for tests logger.error(`Error checking collaborator status: ${error}`, { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "collaborator_verification", repo_owner, repo_name, nickname, }) throw internalServerError("Failed to verify collaborator status") } // Get PR with specific 404 handling // Note: GitHub returns 404 for both non-existent PRs and PRs the installation cannot access let pr try { pr = await octokit.rest.pulls.get({ owner: repo_owner, repo: repo_name, pull_number: pr_number, }) } catch (error: any) { if (error.status === 404) { // Log additional context to help diagnose permission vs not-found issues logger.warn( `PR #${pr_number} returned 404 in ${repo_owner}/${repo_name}. This could mean the PR doesn't exist, or the GitHub App installation doesn't have access to it.`, { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "pr_not_found_or_no_access", repo_owner, repo_name, pr_number, nickname, errorMessage: error.message, errorResponse: error.response?.data, }, ) throw githubPrNotFound( `#${pr_number} in ${repo_owner}/${repo_name}. If the PR exists, ensure the GitHub App installation has access to this repository.`, ) } // Handle 403 (Forbidden) as a permissions issue if (error.status === 403) { logger.warn( `Access forbidden to PR #${pr_number} in ${repo_owner}/${repo_name}. The GitHub App installation may not have sufficient permissions.`, { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "pr_access_forbidden", repo_owner, repo_name, pr_number, nickname, errorMessage: error.message, }, ) throw githubInstallationError( `Access forbidden to PR #${pr_number} in ${repo_owner}/${repo_name}. Please ensure the GitHub App has the necessary permissions.`, ) } throw error // Re-throw to be caught by global handler } // Get PR comments with error handling let pr_messages try { pr_messages = await octokit.rest.issues.listComments({ owner: repo_owner, repo: repo_name, issue_number: pr_number, }) } catch (error) { // Pass error object for tests logger.error( "Error getting PR messages:", { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "get_pr_messages", repo_owner, repo_name, pr_number, }, undefined, error as Error, ) throw internalServerError(`Failed to retrieve PR comments for ${repo_owner}/${repo_name}`) } // Get PR reviews with error handling let pr_reviews try { pr_reviews = await octokit.rest.pulls.listReviews({ owner: repo_owner, repo: repo_name, pull_number: pr_number, }) } catch (error) { // Pass error object for tests logger.error( "Error getting PR reviews:", { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "get_pr_reviews", repo_owner, repo_name, pr_number, }, undefined, error as Error, ) throw internalServerError(`Failed to retrieve PR reviews for ${repo_owner}/${repo_name}`) } const reviewBodies: Array<{ body: string }> = [] for (const review of pr_reviews.data) { // Add the main review body if it exists if (review.body) { reviewBodies.push({ body: review.body }) } // Get review comments for this specific review try { const reviewComments = await octokit.rest.pulls.listCommentsForReview({ owner: repo_owner, repo: repo_name, pull_number: pr_number, review_id: review.id, }) // Add each review comment body for (const comment of reviewComments.data) { if (comment.body) { reviewBodies.push({ body: comment.body }) } } } catch (error) { // Emit explicit console message for tests logger.error( `Error getting review comments for review ${review.id}:`, { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "get_review_comments", repo_owner, repo_name, pr_number, reviewId: review.id, }, undefined, error as Error, ) // Continue with other reviews even if one fails } } // Also get all review comments (not tied to a specific review) try { const allReviewComments = await octokit.rest.pulls.listReviewComments({ owner: repo_owner, repo: repo_name, pull_number: pr_number, }) for (const comment of allReviewComments.data) { if (comment.body) { reviewBodies.push({ body: comment.body }) } } } catch (error) { // Emit explicit console message for tests logger.error( "Error getting all review comments:", { requestId: (req as any).requestId, userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "get_all_review_comments", repo_owner, repo_name, pr_number, }, undefined, error as Error, ) // Continue even if this fails } const prBody = pr.data.body || "" const validComments = pr_messages.data.filter( (comment: { body?: string }) => comment.body !== undefined, ) as Array<{ body: string }> const allComments = [...validComments, ...reviewBodies] const optimizations_dict = dependencies.parseAndCreateOptimizationsDict(prBody, allComments) if (Object.keys(optimizations_dict).length === 0) { return res.status(200).json({ error: "No optimizations found for this PR" }) } const response_dict: Record = {} for (const key in optimizations_dict) { response_dict[key] = Array.from(optimizations_dict[key]) } dependencies.posthog?.capture({ distinctId: userId, event: `cfapi-github-pr-optimization`, properties: { repo_owner, repo_name, pr_number, }, }) return res.status(200).json(response_dict) } catch (error) { // Re-throw AppExceptions to be handled by GlobalExceptionHandler if (error && typeof error === "object" && "getHttpStatus" in error) { throw error } // Emit specific console message expected by tests (stringified for single-arg output) // Note: Error object is still passed for Sentry, but test mode outputs stringified version // Use String(error) to match test expectation of ${error} format const errorStr = String(error) logger.errorWithSentry( `Error in /cfapi/verify-existing-optimizations: ${errorStr}`, { requestId: (req as any).requestId, userId: (req as any).userId, endpoint: "/cfapi/verify-existing-optimizations", operation: "verify_existing_optimizations", }, { repo_owner: req.body?.repo_owner, repo_name: req.body?.repo_name, pr_number: req.body?.pr_number, }, error as Error, ) if (error instanceof Error) { throw internalServerError(`Error verifying existing optimizations: ${error.message}`) } else { throw internalServerError("Error verifying existing optimizations") } } }