diff --git a/src/lib/integrationTests/formatTestError.ts b/src/lib/integrationTests/formatTestError.ts index 5b976edd..1cda04b5 100644 --- a/src/lib/integrationTests/formatTestError.ts +++ b/src/lib/integrationTests/formatTestError.ts @@ -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 - try { - const stringified = JSON.stringify(error, null, 2); - // If it's too long, truncate it - return stringified.length > 500 - ? stringified.substring(0, 500) + '... (truncated)' - : stringified; - } catch { - // JSON.stringify can fail on circular references - return String(error); + 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); } } - // Primitive values (strings, numbers, etc.) - return String(error); + // Return original message if no patterns matched + return errorMessage; }