From d4d6d66165be95cdc8c2c919cad38a55b831a91e Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Sun, 23 Nov 2025 09:51:39 -0800 Subject: [PATCH] tests cannot connect to db --- server.ts | 1 + src/components/auth.integration.test.ts | 10 ++++++++++ src/routes/ai.ts | 5 +++++ src/routes/passport.ts | 4 ++++ src/routes/user.integration.test.ts | 11 +++++++++++ src/routes/user.ts | 4 ++++ src/services/aiApiClient.ts | 6 ++++++ src/services/db/connection.ts | 18 +++++++++++++----- src/services/db/flyer.ts | 4 ++++ src/services/db/user.ts | 3 +++ 10 files changed, 61 insertions(+), 5 deletions(-) diff --git a/server.ts b/server.ts index ed145ebb..e2c4fb93 100644 --- a/server.ts +++ b/server.ts @@ -62,6 +62,7 @@ const getDurationInMilliseconds = (start: [number, number]): number => { const requestLogger = (req: Request, res: Response, next: NextFunction) => { const start = process.hrtime(); const { method, originalUrl } = req; + logger.debug(`[Request Logger] INCOMING: ${method} ${originalUrl}`); res.on('finish', () => { const user = req.user as { id?: string } | undefined; diff --git a/src/components/auth.integration.test.ts b/src/components/auth.integration.test.ts index e2d20ace..87f94989 100644 --- a/src/components/auth.integration.test.ts +++ b/src/components/auth.integration.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; import { loginUser } from '../services/apiClient'; +import { getPool } from '../services/db/connection'; /** * @vitest-environment node @@ -12,6 +13,15 @@ import { loginUser } from '../services/apiClient'; * To run only these tests: `vitest run src/tests/auth.integration.test.ts` */ describe('Authentication API Integration', () => { + // --- START DEBUG LOGGING --- + // Query the DB from within the test file to see its state. + getPool().query('SELECT u.id, u.email, p.role FROM public.users u JOIN public.profiles p ON u.id = p.id').then(res => { + console.log('\n--- [auth.integration.test.ts] Users found in DB from TEST perspective: ---'); + console.table(res.rows); + console.log('--------------------------------------------------------------------------\n'); + }).catch(err => console.error('--- [auth.integration.test.ts] DB QUERY FAILED ---', err)); + // --- END DEBUG LOGGING --- + // --- START DEBUG LOGGING --- // Log the database connection details as seen by an individual TEST FILE. console.log('\n\n--- [AUTH.INTEGRATION.TEST LOG] DATABASE CONNECTION ---'); diff --git a/src/routes/ai.ts b/src/routes/ai.ts index 572e5ddc..616b1bcd 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -27,6 +27,11 @@ const upload = multer({ storage: storage }); * both authenticated and anonymous users to upload flyers. */ router.post('/process-flyer', optionalAuth, upload.array('flyerImages'), async (req: Request, res: Response, next: NextFunction) => { + // --- AI ROUTE DEBUG LOGGING --- + logger.debug('[API /ai/process-flyer] Request received.'); + logger.debug(`[API /ai/process-flyer] Files received: ${req.files ? (req.files as Express.Multer.File[]).length : 0}`); + logger.debug(`[API /ai/process-flyer] Body masterItems (first 50 chars): ${req.body.masterItems?.substring(0, 50)}...`); + // --- END DEBUG LOGGING --- try { if (!req.files || !Array.isArray(req.files) || req.files.length === 0) { return res.status(400).json({ message: 'Flyer image files are required.' }); diff --git a/src/routes/passport.ts b/src/routes/passport.ts index 1ddb387c..684c145d 100644 --- a/src/routes/passport.ts +++ b/src/routes/passport.ts @@ -201,11 +201,15 @@ const jwtOptions = { }; passport.use(new JwtStrategy(jwtOptions, async (jwt_payload, done) => { + logger.debug('[JWT Strategy] Verifying token payload:', { jwt_payload }); try { // The jwt_payload contains the data you put into the token during login (e.g., { id: user.id, email: user.email }). // We re-fetch the user from the database here to ensure they are still active and valid. const userProfile = await db.findUserProfileById(jwt_payload.id); + // --- JWT STRATEGY DEBUG LOGGING --- + logger.debug(`[JWT Strategy] DB lookup for user ID ${jwt_payload.id} result: ${userProfile ? 'FOUND' : 'NOT FOUND'}`); + if (userProfile) { return done(null, userProfile); // User profile object will be available as req.user in protected routes } else { diff --git a/src/routes/user.integration.test.ts b/src/routes/user.integration.test.ts index 4998e864..46dd8a66 100644 --- a/src/routes/user.integration.test.ts +++ b/src/routes/user.integration.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import * as apiClient from '../services/apiClient'; +import { getPool } from '../services/db/connection'; import type { User } from '../types'; /** @@ -26,6 +27,15 @@ describe('User API Routes Integration Tests', () => { let testUser: User; let authToken: string; + // --- START DEBUG LOGGING --- + // Query the DB from within the test file to see its state. + beforeAll(async () => { + const res = await getPool().query('SELECT u.id, u.email, p.role FROM public.users u JOIN public.profiles p ON u.id = p.id'); + console.log('\n--- [user.integration.test.ts] Users found in DB from TEST perspective (beforeAll): ---'); + console.table(res.rows); + console.log('-------------------------------------------------------------------------------------\n'); + }); + // --- END DEBUG LOGGING --- // Before any tests run, create a new user and log them in. // The token will be used for all subsequent API calls in this test suite. beforeAll(async () => { @@ -38,6 +48,7 @@ describe('User API Routes Integration Tests', () => { // After all tests, clean up by deleting the created user. afterAll(async () => { if (testUser) { + logger.debug(`[user.integration.test.ts afterAll] Cleaning up user ID: ${testUser.id}`); // This requires an authenticated call to delete the account. await apiClient.deleteUserAccount(TEST_PASSWORD, authToken); } diff --git a/src/routes/user.ts b/src/routes/user.ts index f6c45066..20df6f37 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -151,6 +151,10 @@ router.delete('/users/account', async (req: Request, res: Response) => { const authenticatedUser = req.user as { id: string; email: string }; const { password } = req.body; + // --- DELETE ACCOUNT DEBUG LOGGING --- + logger.debug(`[API /users/account] Authenticated user from JWT:`, { user: req.user }); + // --- END DEBUG LOGGING --- + if (!password) { return res.status(400).json({ message: 'Password is required for account deletion.' }); } diff --git a/src/services/aiApiClient.ts b/src/services/aiApiClient.ts index 212ad23a..77534687 100644 --- a/src/services/aiApiClient.ts +++ b/src/services/aiApiClient.ts @@ -40,6 +40,12 @@ export const extractCoreDataFromImage = async (imageFiles: File[], masterItems: }); formData.append('masterItems', JSON.stringify(masterItems)); + // --- DEBUG LOGGING for flyer processing --- + logger.debug('[aiApiClient] Calling /api/ai/process-flyer with FormData.'); + logger.debug(`[aiApiClient] Number of image files: ${imageFiles.length}`); + logger.debug(`[aiApiClient] Master items count: ${masterItems.length}`); + // --- END DEBUG LOGGING --- + // This now calls the real backend endpoint. const response = await apiFetch('/api/ai/process-flyer', { method: 'POST', diff --git a/src/services/db/connection.ts b/src/services/db/connection.ts index 4cef477f..503d6049 100644 --- a/src/services/db/connection.ts +++ b/src/services/db/connection.ts @@ -1,4 +1,4 @@ -import { Pool } from 'pg'; +import { Pool, PoolConfig } from 'pg'; import { logger } from '../logger'; // This is the singleton instance. It's defined at the module level. @@ -6,7 +6,12 @@ import { logger } from '../logger'; // entire application (including in different test files and the running server) // will share this exact same `poolInstance` variable. This is the key to // making the singleton pattern work reliably for our tests. -let poolInstance: Pool | undefined; + +// Extend the Pool type to hold our custom ID for logging. +interface TrackedPool extends Pool { + poolId?: string; +} +let poolInstance: TrackedPool | undefined; const createPool = (): Pool => { // Add a unique ID to each pool instance for definitive tracking in logs. @@ -23,13 +28,16 @@ const createPool = (): Pool => { logger.info(` Pool Instance ID: ${poolId}`); logger.info('----------------------------------------------------'); - const newPool = new Pool({ + const poolConfig: PoolConfig = { user: process.env.DB_USER || 'postgres', host: process.env.DB_HOST || 'localhost', database: process.env.DB_NAME || 'flyer-crawler-test', password: process.env.DB_PASSWORD || 'fake_test_db_password', // do not replace this - use appropriate .env file port: parseInt(process.env.DB_PORT || '5432', 10), - }); + }; + + const newPool: TrackedPool = new Pool(poolConfig); + newPool.poolId = poolId; // Attach the ID to the instance. logger.info(`Database connection pool created for host: ${process.env.DB_HOST || 'localhost'}`); return newPool; }; @@ -44,7 +52,7 @@ export const getPool = (): Pool => { logger.info('--- [DB POOL ACCESSOR] No pool instance found. Creating a new one. ---'); poolInstance = createPool(); } else { - logger.info('--- [DB POOL ACCESSOR] Returning existing pool instance. ---'); + logger.info(`--- [DB POOL ACCESSOR] Returning existing pool instance. ID: ${poolInstance.poolId} ---`); } return poolInstance; }; diff --git a/src/services/db/flyer.ts b/src/services/db/flyer.ts index b7d8c07b..fce62c19 100644 --- a/src/services/db/flyer.ts +++ b/src/services/db/flyer.ts @@ -130,8 +130,10 @@ export async function createFlyerAndItems( items: Omit[] ): Promise { const client = await getPool().connect(); + logger.debug('[DB createFlyerAndItems] Starting transaction to create flyer.', { flyerData: { name: flyerData.file_name, store: flyerData.store_name }, itemCount: items.length }); try { await client.query('BEGIN'); + logger.debug('[DB createFlyerAndItems] BEGIN transaction successful.'); // Find or create the store let storeId: number; @@ -183,9 +185,11 @@ export async function createFlyerAndItems( } await client.query('COMMIT'); + logger.debug(`[DB createFlyerAndItems] COMMIT transaction successful for new flyer ID: ${newFlyer.id}.`); return newFlyer; } catch (error) { await client.query('ROLLBACK'); + logger.error('[DB createFlyerAndItems] ROLLBACK transaction due to error.'); logger.error('Database transaction error in createFlyerAndItems:', { error }); throw new Error('Failed to save flyer and its items.'); } finally { diff --git a/src/services/db/user.ts b/src/services/db/user.ts index 070b464f..c682da9e 100644 --- a/src/services/db/user.ts +++ b/src/services/db/user.ts @@ -22,11 +22,14 @@ interface DbUser { * @returns A promise that resolves to the user object or undefined if not found. */ export async function findUserByEmail(email: string): Promise { + logger.debug(`[DB findUserByEmail] Searching for user with email: ${email}`); try { const res = await getPool().query( 'SELECT id, email, password_hash, refresh_token, failed_login_attempts, last_failed_login FROM public.users WHERE email = $1', [email] ); + const userFound = res.rows[0]; + logger.debug(`[DB findUserByEmail] Query for ${email} result: ${userFound ? `FOUND user ID ${userFound.id}` : 'NOT FOUND'}`); return res.rows[0]; } catch (error) { logger.error('Database error in findUserByEmail:', { error });