All files / web/src/app/api/coverage-results route.ts

0% Statements 0/106
0% Branches 0/1
0% Functions 0/1
0% Lines 0/106

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107                                                                                                                                                                                                                     
/**
 * Coverage results endpoint
 *
 * POST /api/coverage-results
 *
 * Receives unit test coverage summary from CI and stores it in the database.
 * Protected by Bearer token (COVERAGE_API_TOKEN env var).
 *
 * Request body:
 * {
 *   commitSha?: string,
 *   lines: number,      // percentage 0-100
 *   branches: number,   // percentage 0-100
 *   functions: number,  // percentage 0-100
 *   statements: number, // percentage 0-100
 * }
 */

import { NextResponse } from 'next/server'
import { desc } from 'drizzle-orm'
import { db } from '@/db'
import { coverageResults } from '@/db/schema'
import { updateCoverageMetrics } from '@/lib/metrics'
import { withAuth } from '@/lib/auth/withAuth'

export const dynamic = 'force-dynamic'

interface CoverageRequest {
  commitSha?: string
  lines: number
  branches: number
  functions: number
  statements: number
}

export const POST = withAuth(async (request) => {
  // Verify authorization
  const authHeader = request.headers.get('authorization')
  const expectedToken = process.env.COVERAGE_API_TOKEN
  if (!expectedToken || authHeader !== `Bearer ${expectedToken}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  try {
    const body = (await request.json()) as CoverageRequest

    // Validate required fields
    if (
      typeof body.lines !== 'number' ||
      typeof body.branches !== 'number' ||
      typeof body.functions !== 'number' ||
      typeof body.statements !== 'number'
    ) {
      return NextResponse.json(
        { error: 'Missing required fields: lines, branches, functions, statements (numbers)' },
        { status: 400 }
      )
    }

    const now = new Date()

    await db.insert(coverageResults).values({
      timestamp: now,
      commitSha: body.commitSha ?? null,
      linesPct: body.lines,
      branchesPct: body.branches,
      functionsPct: body.functions,
      statementsPct: body.statements,
    })

    // Update Prometheus gauges
    updateCoverageMetrics({
      linesPct: body.lines,
      branchesPct: body.branches,
      functionsPct: body.functions,
      statementsPct: body.statements,
      timestamp: now,
    })

    // Clean up old results (keep last 100)
    const allResults = await db
      .select({ id: coverageResults.id })
      .from(coverageResults)
      .orderBy(desc(coverageResults.timestamp))

    if (allResults.length > 100) {
      const idsToKeep = new Set(allResults.slice(0, 100).map((r) => r.id))
      const { inArray } = await import('drizzle-orm')
      const idsToDelete = allResults.filter((r) => !idsToKeep.has(r.id)).map((r) => r.id)
      if (idsToDelete.length > 0) {
        await db.delete(coverageResults).where(inArray(coverageResults.id, idsToDelete))
      }
    }

    return NextResponse.json({
      success: true,
      message: 'Coverage results recorded',
    })
  } catch (error) {
    console.error('Error storing coverage results:', error)
    return NextResponse.json(
      { error: error instanceof Error ? error.message : 'Unknown error' },
      { status: 500 }
    )
  }
})