logging work - almost there
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 15m30s

This commit is contained in:
2026-01-12 16:56:54 -08:00
parent b9a0e5b82c
commit 4e06dde9e1
7 changed files with 60 additions and 20 deletions

View File

@@ -11,16 +11,19 @@
// Use this file (ecosystem.config.cjs) for production deployments.
// --- Environment Variable Validation ---
// NOTE: We only WARN about missing secrets, not exit.
// Calling process.exit(1) prevents PM2 from reading the apps array.
// The actual application will fail to start if secrets are missing,
// which PM2 will handle with its restart logic.
const requiredSecrets = ['DB_HOST', 'JWT_SECRET', 'GEMINI_API_KEY'];
const missingSecrets = requiredSecrets.filter(key => !process.env[key]);
if (missingSecrets.length > 0) {
console.warn('\n[ecosystem.config.cjs] ⚠️ WARNING: The following environment variables are MISSING in the shell:');
console.warn('\n[ecosystem.config.cjs] WARNING: The following environment variables are MISSING:');
missingSecrets.forEach(key => console.warn(` - ${key}`));
console.warn('[ecosystem.config.cjs] The application may crash if these are required for startup.\n');
process.exit(1); // Fail fast so PM2 doesn't attempt to start a broken app
console.warn('[ecosystem.config.cjs] The application may fail to start if these are required.\n');
} else {
console.log('[ecosystem.config.cjs] Critical environment variables are present.');
console.log('[ecosystem.config.cjs] Critical environment variables are present.');
}
// --- Shared Environment Variables ---

View File

@@ -11,14 +11,17 @@
// - Have distinct PM2 process names to avoid conflicts with production
// --- Environment Variable Validation ---
// NOTE: We only WARN about missing secrets, not exit.
// Calling process.exit(1) prevents PM2 from reading the apps array.
// The actual application will fail to start if secrets are missing,
// which PM2 will handle with its restart logic.
const requiredSecrets = ['DB_HOST', 'JWT_SECRET', 'GEMINI_API_KEY'];
const missingSecrets = requiredSecrets.filter(key => !process.env[key]);
if (missingSecrets.length > 0) {
console.warn('\n[ecosystem.config.test.cjs] WARNING: The following environment variables are MISSING:');
missingSecrets.forEach(key => console.warn(` - ${key}`));
console.warn('[ecosystem.config.test.cjs] The application may crash if these are required for startup.\n');
process.exit(1);
console.warn('[ecosystem.config.test.cjs] The application may fail to start if these are required.\n');
} else {
console.log('[ecosystem.config.test.cjs] Critical environment variables are present.');
}

View File

@@ -943,13 +943,21 @@ CREATE TABLE IF NOT EXISTS public.receipts (
status TEXT DEFAULT 'pending' NOT NULL CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
raw_text TEXT,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
processed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
processed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
-- Columns from migration 003_receipt_scanning_enhancements.sql
store_confidence NUMERIC(5,4) CHECK (store_confidence IS NULL OR (store_confidence >= 0 AND store_confidence <= 1)),
ocr_provider TEXT,
error_details JSONB,
retry_count INTEGER DEFAULT 0 CHECK (retry_count >= 0),
ocr_confidence NUMERIC(5,4) CHECK (ocr_confidence IS NULL OR (ocr_confidence >= 0 AND ocr_confidence <= 1)),
currency TEXT DEFAULT 'CAD'
);
-- CONSTRAINT receipts_receipt_image_url_check CHECK (receipt_image_url ~* '^https://?.*')
COMMENT ON TABLE public.receipts IS 'Stores uploaded user receipts for purchase tracking and analysis.';
CREATE INDEX IF NOT EXISTS idx_receipts_user_id ON public.receipts(user_id);
CREATE INDEX IF NOT EXISTS idx_receipts_store_id ON public.receipts(store_id);
CREATE INDEX IF NOT EXISTS idx_receipts_status_retry ON public.receipts(status, retry_count) WHERE status IN ('pending', 'failed') AND retry_count < 3;
-- 53. Store individual line items extracted from a user receipt.
CREATE TABLE IF NOT EXISTS public.receipt_items (

View File

@@ -962,13 +962,21 @@ CREATE TABLE IF NOT EXISTS public.receipts (
status TEXT DEFAULT 'pending' NOT NULL CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
raw_text TEXT,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
processed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
processed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
-- Columns from migration 003_receipt_scanning_enhancements.sql
store_confidence NUMERIC(5,4) CHECK (store_confidence IS NULL OR (store_confidence >= 0 AND store_confidence <= 1)),
ocr_provider TEXT,
error_details JSONB,
retry_count INTEGER DEFAULT 0 CHECK (retry_count >= 0),
ocr_confidence NUMERIC(5,4) CHECK (ocr_confidence IS NULL OR (ocr_confidence >= 0 AND ocr_confidence <= 1)),
currency TEXT DEFAULT 'CAD'
);
-- CONSTRAINT receipts_receipt_image_url_check CHECK (receipt_image_url ~* '^https?://.*'),
COMMENT ON TABLE public.receipts IS 'Stores uploaded user receipts for purchase tracking and analysis.';
CREATE INDEX IF NOT EXISTS idx_receipts_user_id ON public.receipts(user_id);
CREATE INDEX IF NOT EXISTS idx_receipts_store_id ON public.receipts(store_id);
CREATE INDEX IF NOT EXISTS idx_receipts_status_retry ON public.receipts(status, retry_count) WHERE status IN ('pending', 'failed') AND retry_count < 3;
-- 53. Store individual line items extracted from a user receipt.
CREATE TABLE IF NOT EXISTS public.receipt_items (

View File

@@ -28,7 +28,8 @@ interface ReceiptRow {
raw_text: string | null;
store_confidence: number | null;
ocr_provider: OcrProvider | null;
error_details: string | null;
// JSONB columns are automatically parsed by pg driver
error_details: Record<string, unknown> | null;
retry_count: number;
ocr_confidence: number | null;
currency: string;
@@ -1036,7 +1037,7 @@ export class ReceiptRepository {
raw_text: row.raw_text,
store_confidence: row.store_confidence !== null ? Number(row.store_confidence) : null,
ocr_provider: row.ocr_provider,
error_details: row.error_details ? JSON.parse(row.error_details) : null,
error_details: row.error_details ?? null,
retry_count: row.retry_count,
ocr_confidence: row.ocr_confidence !== null ? Number(row.ocr_confidence) : null,
currency: row.currency,

View File

@@ -416,7 +416,14 @@ describe('Inventory/Expiry Integration Tests (/api/inventory)', () => {
.send({ expiry_date: futureDate });
expect(response.status).toBe(200);
expect(response.body.data.expiry_date).toContain(futureDate);
// Compare date portions only - the response is in UTC, which may differ by timezone offset
// e.g., '2026-02-27' sent becomes '2026-02-26T19:00:00.000Z' in UTC (for UTC-5 timezone)
const responseDate = new Date(response.body.data.expiry_date);
const sentDate = new Date(futureDate + 'T00:00:00');
// Dates should be within 24 hours of each other (same logical day)
expect(Math.abs(responseDate.getTime() - sentDate.getTime())).toBeLessThan(
24 * 60 * 60 * 1000,
);
});
it('should reject empty update body', async () => {

View File

@@ -14,6 +14,14 @@ import { getPool } from '../../services/db/connection.db';
* @vitest-environment node
*/
// Mock Bull Board to prevent BullMQAdapter from validating queue instances
vi.mock('@bull-board/api', () => ({
createBullBoard: vi.fn(),
}));
vi.mock('@bull-board/api/bullMQAdapter', () => ({
BullMQAdapter: vi.fn(),
}));
// Mock the queues to prevent actual background processing
// IMPORTANT: Must include all queue exports that are imported by workers.server.ts
vi.mock('../../services/queues.server', () => ({
@@ -88,7 +96,7 @@ describe('Receipt Processing Integration Tests (/api/receipts)', () => {
createdReceiptIds,
]);
await pool.query(
'DELETE FROM public.receipt_processing_logs WHERE receipt_id = ANY($1::int[])',
'DELETE FROM public.receipt_processing_log WHERE receipt_id = ANY($1::int[])',
[createdReceiptIds],
);
await pool.query('DELETE FROM public.receipts WHERE receipt_id = ANY($1::int[])', [
@@ -337,8 +345,8 @@ describe('Receipt Processing Integration Tests (/api/receipts)', () => {
beforeAll(async () => {
const pool = getPool();
const result = await pool.query(
`INSERT INTO public.receipts (user_id, receipt_image_url, status, error_message)
VALUES ($1, '/uploads/receipts/failed-test.jpg', 'failed', 'OCR failed')
`INSERT INTO public.receipts (user_id, receipt_image_url, status, error_details)
VALUES ($1, '/uploads/receipts/failed-test.jpg', 'failed', '{"message": "OCR failed"}'::jsonb)
RETURNING receipt_id`,
[testUser.user.user_id],
);
@@ -551,12 +559,14 @@ describe('Receipt Processing Integration Tests (/api/receipts)', () => {
receiptWithLogsId = receiptResult.rows[0].receipt_id;
createdReceiptIds.push(receiptWithLogsId);
// Add processing logs
// Add processing logs - using correct table name and column names
// processing_step must be one of: upload, ocr_extraction, text_parsing, store_detection,
// item_extraction, item_matching, price_parsing, finalization
await pool.query(
`INSERT INTO public.receipt_processing_logs (receipt_id, step, status, message)
VALUES ($1, 'ocr', 'completed', 'OCR completed successfully'),
`INSERT INTO public.receipt_processing_log (receipt_id, processing_step, status, error_message)
VALUES ($1, 'ocr_extraction', 'completed', 'OCR completed successfully'),
($1, 'item_extraction', 'completed', 'Extracted 5 items'),
($1, 'matching', 'completed', 'Matched 3 items')`,
($1, 'item_matching', 'completed', 'Matched 3 items')`,
[receiptWithLogsId],
);
});