unit test fixes + error refactor
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m45s

This commit is contained in:
2025-12-11 20:26:34 -08:00
parent 27ef5901f1
commit 5d7598eadf
6 changed files with 102 additions and 37 deletions

View File

@@ -4,9 +4,44 @@ import { DatabaseError, UniqueConstraintError, ForeignKeyConstraintError, NotFou
import crypto from 'crypto';
import { logger } from '../services/logger.server';
// --- Helper Functions for Secure Logging ---
/**
* Sanitizes an object by redacting values of sensitive keys.
* @param obj The object to sanitize.
* @returns A new object with sensitive values redacted.
*/
const sanitizeObject = (obj: Record<string, any>): Record<string, any> => {
if (!obj || typeof obj !== 'object') return {};
const sensitiveKeys = ['password', 'token', 'authorization', 'cookie', 'newPassword', 'currentPassword'];
const sanitizedObj: Record<string, any> = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (sensitiveKeys.some(sensitiveKey => key.toLowerCase().includes(sensitiveKey))) {
sanitizedObj[key] = '[REDACTED]';
} else {
sanitizedObj[key] = obj[key];
}
}
}
return sanitizedObj;
};
/**
* Extracts user information from the request object for logging.
* @param req The Express request object.
* @returns An object with user details or null if no user is authenticated.
*/
const getLoggableUser = (req: Request): { id: string; email?: string } | null => {
const user = req.user as { user_id?: string; email?: string };
if (user && user.user_id) {
return { id: user.user_id, email: user.email };
}
return null;
};
interface HttpError extends Error {
status?: number;
// You can add other custom properties like 'code' if needed
}
export const errorHandler = (err: HttpError, req: Request, res: Response, next: NextFunction) => {
@@ -15,41 +50,55 @@ export const errorHandler = (err: HttpError, req: Request, res: Response, next:
return next(err);
}
let statusCode = err.status || 500;
// --- 1. Determine Final Status Code and Message ---
let statusCode = err.status ?? 500;
let message = err.message;
// --- Handle Specific Custom Error Types ---
// All our custom errors inherit from DatabaseError and have a status property.
if (err instanceof DatabaseError) {
statusCode = err.status || 500;
}
// --- TEST ENVIRONMENT DEBUGGING ---
// In a test environment, log the full stack trace to the console for easier debugging.
if (process.env.NODE_ENV === 'test') {
console.error('--- [TEST] UNHANDLED ERROR ---', err);
}
let errorId: string | undefined;
// Refine the status code for known error types. This block should come first.
if (err instanceof DatabaseError) {
// This will handle UniqueConstraintError, ForeignKeyConstraintError, NotFoundError, etc.
statusCode = err.status;
} else if (err instanceof ForeignKeyConstraintError) {
statusCode = 400;
} else if (err instanceof UniqueConstraintError) {
statusCode = 409; // Conflict
} else if (err instanceof NotFoundError) {
statusCode = 404;
} else if (err.name === 'UnauthorizedError') {
statusCode = err.status || 401;
}
// --- 2. Log Based on Final Status Code ---
// Log the full error details for debugging, especially for server errors.
if (statusCode >= 500) {
errorId = crypto.randomBytes(4).toString('hex');
logger.error(`Unhandled API Error (ID: ${errorId}):`, {
// Log sanitized data for security
error: err.stack || err.message,
path: req.path,
method: req.method,
body: req.body,
body: sanitizeObject(req.body),
headers: sanitizeObject(req.headers),
user: getLoggableUser(req),
});
} else {
// For 4xx errors, log at a lower level (e.g., 'warn') to avoid flooding error trackers.
logger.warn(`Client Error: ${statusCode} on ${req.method} ${req.path}`, {
errorMessage: message,
user: getLoggableUser(req),
path: req.path,
method: req.method,
ip: req.ip,
});
}
// --- TEST ENVIRONMENT DEBUGGING ---
if (process.env.NODE_ENV === 'test') {
console.error('--- [TEST] UNHANDLED ERROR ---', err);
}
// --- 3. Send Response ---
// In production, send a generic message for 5xx errors.
// In dev/test, send the actual error message for easier debugging.
const responseMessage = (statusCode >= 500 && process.env.NODE_ENV === 'production')