mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 17:51:12 -05:00
Adds comprehensive error pattern matching in formatTestError to detect and format Supabase constraint violations (RLs, not-null, unique, foreign keys, check constraints, and related schema/permission issues) into actionable messages, extracting relevant details for clearer test failure guidance. Includes handling for common error shapes and preserves fallback messaging.
153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
/**
|
|
* Test Error Formatting Utility
|
|
*
|
|
* Provides robust error formatting for test results to avoid "[object Object]" messages
|
|
* Includes pattern matching for common Supabase/Postgres constraint violations
|
|
*/
|
|
|
|
/**
|
|
* Error pattern matchers for common database constraint violations
|
|
*/
|
|
const ERROR_PATTERNS = [
|
|
{
|
|
// RLS policy violations
|
|
pattern: /new row violates row-level security policy for table "(\w+)"/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`RLS Policy Violation: Cannot insert into table "${match[1]}". Check that RLS policies allow this operation and user has proper authentication.`
|
|
},
|
|
{
|
|
// NOT NULL constraint violations
|
|
pattern: /null value in column "(\w+)" of relation "(\w+)" violates not-null constraint/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`NOT NULL Constraint: Column "${match[1]}" in table "${match[2]}" cannot be null. Provide a value for this required field.`
|
|
},
|
|
{
|
|
// UNIQUE constraint violations
|
|
pattern: /duplicate key value violates unique constraint "(\w+)"/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`UNIQUE Constraint: Duplicate value violates constraint "${match[1]}". This value already exists in the database.`
|
|
},
|
|
{
|
|
// Foreign key violations
|
|
pattern: /insert or update on table "(\w+)" violates foreign key constraint "(\w+)"/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Foreign Key Violation: Table "${match[1]}" references non-existent record (constraint: "${match[2]}"). Ensure the referenced entity exists first.`
|
|
},
|
|
{
|
|
// Foreign key violations (alternative format)
|
|
pattern: /violates foreign key constraint/i,
|
|
format: () =>
|
|
`Foreign Key Violation: Referenced record does not exist. Create the parent entity before creating this dependent entity.`
|
|
},
|
|
{
|
|
// Check constraint violations
|
|
pattern: /new row for relation "(\w+)" violates check constraint "(\w+)"/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Check Constraint: Validation failed for table "${match[1]}" (constraint: "${match[2]}"). The provided value does not meet validation requirements.`
|
|
},
|
|
{
|
|
// Column does not exist
|
|
pattern: /column "(\w+)" of relation "(\w+)" does not exist/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Schema Error: Column "${match[1]}" does not exist in table "${match[2]}". Check database schema or migration status.`
|
|
},
|
|
{
|
|
// Could not find column in schema cache
|
|
pattern: /Could not find the '(\w+)' column of '(\w+)' in the schema cache/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Schema Cache Error: Column "${match[1]}" not found in table "${match[2]}". The schema may have changed - try refreshing the database connection.`
|
|
},
|
|
{
|
|
// Table does not exist
|
|
pattern: /relation "(\w+)" does not exist/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Schema Error: Table "${match[1]}" does not exist. Run migrations or check database schema.`
|
|
},
|
|
{
|
|
// Permission denied
|
|
pattern: /permission denied for (?:table|relation) "?(\w+)"?/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Permission Denied: Insufficient permissions to access table "${match[1]}". Check RLS policies and user roles.`
|
|
},
|
|
{
|
|
// Rate limit errors
|
|
pattern: /Rate limit exceeded\. Please wait (\d+) seconds?/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Rate Limited: Too many requests. Wait ${match[1]} seconds before retrying.`
|
|
},
|
|
{
|
|
// Rate limit errors (alternative format)
|
|
pattern: /Too many submissions in a short time\. Please wait (\d+) seconds?/i,
|
|
format: (match: RegExpMatchArray) =>
|
|
`Rate Limited: Submission throttled. Wait ${match[1]} seconds before submitting again.`
|
|
}
|
|
];
|
|
|
|
/**
|
|
* Format error for test result display
|
|
* Handles Error objects, PostgresError objects, and plain objects
|
|
*
|
|
* @param error - Any error value thrown in a test
|
|
* @returns Formatted, human-readable error string
|
|
*/
|
|
export function formatTestError(error: unknown): string {
|
|
let errorMessage = '';
|
|
|
|
// Extract base error message
|
|
if (error instanceof Error) {
|
|
errorMessage = error.message;
|
|
} else if (typeof error === 'object' && error !== null) {
|
|
const err = error as any;
|
|
|
|
// Try common error message properties
|
|
if (err.message && typeof err.message === 'string') {
|
|
errorMessage = err.message;
|
|
|
|
// Include additional Supabase error details if present
|
|
if (err.details && typeof err.details === 'string') {
|
|
errorMessage += ` | Details: ${err.details}`;
|
|
}
|
|
if (err.hint && typeof err.hint === 'string') {
|
|
errorMessage += ` | Hint: ${err.hint}`;
|
|
}
|
|
if (err.code && typeof err.code === 'string') {
|
|
errorMessage += ` | Code: ${err.code}`;
|
|
}
|
|
}
|
|
// Some errors nest the actual error in an 'error' property
|
|
else if (err.error) {
|
|
return formatTestError(err.error);
|
|
}
|
|
// Some APIs use 'msg' instead of 'message'
|
|
else if (err.msg && typeof err.msg === 'string') {
|
|
errorMessage = err.msg;
|
|
}
|
|
// Last resort: stringify the entire object
|
|
else {
|
|
try {
|
|
const stringified = JSON.stringify(error, null, 2);
|
|
errorMessage = stringified.length > 500
|
|
? stringified.substring(0, 500) + '... (truncated)'
|
|
: stringified;
|
|
} catch {
|
|
// JSON.stringify can fail on circular references
|
|
errorMessage = String(error);
|
|
}
|
|
}
|
|
} else {
|
|
// Primitive values (strings, numbers, etc.)
|
|
errorMessage = String(error);
|
|
}
|
|
|
|
// Apply pattern matching to format known constraint violations
|
|
for (const { pattern, format } of ERROR_PATTERNS) {
|
|
const match = errorMessage.match(pattern);
|
|
if (match) {
|
|
return format(match);
|
|
}
|
|
}
|
|
|
|
// Return original message if no patterns matched
|
|
return errorMessage;
|
|
}
|