UNPKG

@vizzly-testing/cli

Version:

Visual review platform for UI developers and designers

227 lines (216 loc) 7.64 kB
/** * Baseline Router * Handles baseline management (accept, accept-all, reset) */ import * as output from '../../utils/output.js'; import { parseJsonBody } from '../middleware/json-parser.js'; import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js'; /** * Create baseline router * @param {Object} context - Router context * @param {Object} context.screenshotHandler - Screenshot handler * @param {Object} context.tddService - TDD service for baseline downloads * @param {Object} context.authService - Auth service for OAuth requests * @returns {Function} Route handler */ export function createBaselineRouter({ screenshotHandler, tddService, authService }) { return async function handleBaselineRoute(req, res, pathname) { // Accept a single screenshot as baseline if (req.method === 'POST' && pathname === '/api/baseline/accept') { if (!screenshotHandler?.acceptBaseline) { sendError(res, 400, 'Baseline management not available'); return true; } try { const { id } = await parseJsonBody(req); if (!id) { sendError(res, 400, 'Comparison ID required'); return true; } await screenshotHandler.acceptBaseline(id); sendSuccess(res, { success: true, message: `Baseline accepted for comparison ${id}` }); return true; } catch (error) { output.error('Error accepting baseline:', error); sendError(res, 500, error.message); return true; } } // Accept all screenshots as baseline if (req.method === 'POST' && pathname === '/api/baseline/accept-all') { if (!screenshotHandler?.acceptAllBaselines) { sendError(res, 400, 'Baseline management not available'); return true; } try { const result = await screenshotHandler.acceptAllBaselines(); sendSuccess(res, { success: true, message: `Accepted ${result.count} baselines`, count: result.count }); return true; } catch (error) { output.error('Error accepting all baselines:', error); sendError(res, 500, error.message); return true; } } // Reject a single comparison (keep current baseline, discard changes) if (req.method === 'POST' && pathname === '/api/baseline/reject') { if (!screenshotHandler?.rejectBaseline) { sendError(res, 400, 'Baseline management not available'); return true; } try { const { id } = await parseJsonBody(req); if (!id) { sendError(res, 400, 'Comparison ID required'); return true; } await screenshotHandler.rejectBaseline(id); sendSuccess(res, { success: true, message: `Changes rejected for comparison ${id}` }); return true; } catch (error) { output.error('Error rejecting baseline:', error); sendError(res, 500, error.message); return true; } } // Delete a comparison entirely (removes from report and deletes files) if (req.method === 'POST' && pathname === '/api/baseline/delete') { if (!screenshotHandler?.deleteComparison) { sendError(res, 400, 'Baseline management not available'); return true; } try { const { id } = await parseJsonBody(req); if (!id) { sendError(res, 400, 'Comparison ID required'); return true; } await screenshotHandler.deleteComparison(id); sendSuccess(res, { success: true, message: `Comparison ${id} deleted` }); return true; } catch (error) { if (error.code === 'NOT_FOUND') { sendError(res, 404, error.message); } else { output.error('Error deleting comparison:', error); sendError(res, 500, error.message); } return true; } } // Reset baselines to previous state if (req.method === 'POST' && pathname === '/api/baseline/reset') { if (!screenshotHandler?.resetBaselines) { sendError(res, 400, 'Baseline management not available'); return true; } try { await screenshotHandler.resetBaselines(); sendSuccess(res, { success: true, message: 'Baselines reset to previous state' }); return true; } catch (error) { output.error('Error resetting baselines:', error); sendError(res, 500, error.message); return true; } } // Download baselines from a remote build if (req.method === 'POST' && pathname === '/api/baselines/download') { if (!tddService) { sendServiceUnavailable(res, 'TDD service not available (only available in TDD mode)'); return true; } try { let body = await parseJsonBody(req); let { buildId, organizationSlug, projectSlug } = body; if (!buildId) { sendError(res, 400, 'buildId is required'); return true; } // Try OAuth authentication first (allows access to any project user has membership to) // This is the preferred method when user is logged in via the dashboard if (authService && organizationSlug && projectSlug) { try { output.info(`Downloading baselines from build ${buildId}...`); output.debug('baseline', `Using OAuth for ${organizationSlug}/${projectSlug}`); // Use the CLI endpoint which accepts OAuth and checks project membership let apiResponse = await authService.authenticatedRequest(`/api/cli/${projectSlug}/builds/${buildId}/tdd-baselines`, { method: 'GET', headers: { 'X-Organization': organizationSlug } }); if (!apiResponse) { throw new Error(`Build ${buildId} not found or API returned null`); } // Process the baselines through tddService let result = await tddService.processDownloadedBaselines(apiResponse, buildId); sendSuccess(res, { success: true, message: `Baselines downloaded from build ${buildId}`, ...result }); return true; } catch (oauthError) { // If OAuth fails with auth error, fall through to other methods if (!oauthError.message?.includes('Not authenticated') && !oauthError.message?.includes('401')) { throw oauthError; } output.debug('baseline', `OAuth failed, trying other auth methods: ${oauthError.message}`); } } // Fall back to using tddService directly (requires VIZZLY_TOKEN env var) output.info(`Downloading baselines from build ${buildId}...`); let result = await tddService.downloadBaselines('test', // environment null, // branch (not needed when buildId is specified) buildId, // specific build to download from null // comparisonId (not needed) ); sendSuccess(res, { success: true, message: `Baselines downloaded from build ${buildId}`, ...result }); return true; } catch (error) { output.error('Error downloading baselines:', error); sendError(res, 500, error.message); return true; } } return false; }; }