mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
Enhance formatTestError patterns
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.
This commit is contained in:
@@ -2,8 +2,87 @@
|
||||
* 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
|
||||
@@ -12,48 +91,62 @@
|
||||
* @returns Formatted, human-readable error string
|
||||
*/
|
||||
export function formatTestError(error: unknown): string {
|
||||
// Standard Error objects
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
let errorMessage = '';
|
||||
|
||||
// Object-like errors (PostgresError, custom error objects, etc.)
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
// 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') {
|
||||
return err.message;
|
||||
}
|
||||
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
|
||||
if (err.error) {
|
||||
else if (err.error) {
|
||||
return formatTestError(err.error);
|
||||
}
|
||||
|
||||
// Some APIs use 'msg' instead of 'message'
|
||||
if (err.msg && typeof err.msg === 'string') {
|
||||
return err.msg;
|
||||
else if (err.msg && typeof err.msg === 'string') {
|
||||
errorMessage = err.msg;
|
||||
}
|
||||
|
||||
// Supabase errors might have details
|
||||
if (err.details && typeof err.details === 'string') {
|
||||
return `${err.message || 'Error'}: ${err.details}`;
|
||||
}
|
||||
|
||||
// Last resort: stringify the entire object
|
||||
else {
|
||||
try {
|
||||
const stringified = JSON.stringify(error, null, 2);
|
||||
// If it's too long, truncate it
|
||||
return stringified.length > 500
|
||||
errorMessage = stringified.length > 500
|
||||
? stringified.substring(0, 500) + '... (truncated)'
|
||||
: stringified;
|
||||
} catch {
|
||||
// JSON.stringify can fail on circular references
|
||||
return String(error);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Primitive values (strings, numbers, etc.)
|
||||
return String(error);
|
||||
// Return original message if no patterns matched
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user