Compare commits

...

11 Commits

Author SHA1 Message Date
Gitea Actions
f49f3a75fb ci: Bump version to 0.9.44 [skip ci] 2026-01-06 13:41:43 +05:00
8f14044ae6 debugging the flyer integration issue
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 26m27s
2026-01-06 00:41:03 -08:00
Gitea Actions
55e1e425f4 ci: Bump version to 0.9.43 [skip ci] 2026-01-06 12:56:47 +05:00
68b16ad2e8 fix the dang integration tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 25m2s
2026-01-05 23:53:54 -08:00
Gitea Actions
6a28934692 ci: Bump version to 0.9.42 [skip ci] 2026-01-06 12:25:08 +05:00
78c4a5fee6 fix the dang integration tests
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
2026-01-05 23:20:56 -08:00
Gitea Actions
1ce5f481a8 ci: Bump version to 0.9.41 [skip ci] 2026-01-06 11:39:28 +05:00
Gitea Actions
e0120d38fd ci: Bump version to 0.9.39 [skip ci] 2026-01-06 11:39:27 +05:00
6b2079ef2c fix the dang integration tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 32m44s
2026-01-05 22:38:21 -08:00
Gitea Actions
0478e176d5 ci: Bump version to 0.9.38 [skip ci] 2026-01-06 10:23:22 +05:00
47f7f97cd9 fuck database contraints - seems buggy
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 32m10s
2026-01-05 21:16:08 -08:00
25 changed files with 150 additions and 61 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "flyer-crawler",
"version": "0.9.37",
"version": "0.9.44",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flyer-crawler",
"version": "0.9.37",
"version": "0.9.44",
"dependencies": {
"@bull-board/api": "^6.14.2",
"@bull-board/express": "^6.14.2",

View File

@@ -1,7 +1,7 @@
{
"name": "flyer-crawler",
"private": true,
"version": "0.9.37",
"version": "0.9.44",
"type": "module",
"scripts": {
"dev": "concurrently \"npm:start:dev\" \"vite\"",

View File

@@ -523,7 +523,7 @@ CREATE TABLE IF NOT EXISTS public.recipes (
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
CONSTRAINT recipes_name_check CHECK (TRIM(name) <> '')
);
-- CONSTRAINT recipes_photo_url_check CHECK (photo_url IS NULL OR photo_url ~* '^https://?.*')
-- CONSTRAINT recipes_photo_url_check CHECK (photo_url IS NULL OR photo_url ~* '^https://?.*')
COMMENT ON TABLE public.recipes IS 'Stores recipes that can be used to generate shopping lists.';
COMMENT ON COLUMN public.recipes.servings IS 'The number of servings this recipe yields.';
COMMENT ON COLUMN public.recipes.original_recipe_id IS 'If this recipe is a variation of another, this points to the original.';

View File

@@ -63,6 +63,7 @@ export class FlyerRepository {
* @returns The newly created flyer record with its ID.
*/
async insertFlyer(flyerData: FlyerDbInsert, logger: Logger): Promise<Flyer> {
console.log('[DEBUG] FlyerRepository.insertFlyer called with:', JSON.stringify(flyerData, null, 2));
try {
const query = `
INSERT INTO flyers (

View File

@@ -62,10 +62,13 @@ export class FlyerDataTransformer {
baseUrl: string,
logger: Logger,
): { imageUrl: string; iconUrl: string } {
console.log('[DEBUG] FlyerDataTransformer._buildUrls inputs:', { imageFileName, iconFileName, baseUrl });
logger.debug({ imageFileName, iconFileName, baseUrl }, 'Building URLs');
const finalBaseUrl = baseUrl || getBaseUrl(logger);
console.log('[DEBUG] FlyerDataTransformer._buildUrls finalBaseUrl resolved to:', finalBaseUrl);
const imageUrl = `${finalBaseUrl}/flyer-images/${imageFileName}`;
const iconUrl = `${finalBaseUrl}/flyer-images/icons/${iconFileName}`;
console.log('[DEBUG] FlyerDataTransformer._buildUrls constructed:', { imageUrl, iconUrl });
logger.debug({ imageUrl, iconUrl }, 'Constructed URLs');
return { imageUrl, iconUrl };
}
@@ -90,6 +93,7 @@ export class FlyerDataTransformer {
logger: Logger,
baseUrl: string,
): Promise<{ flyerData: FlyerInsert; itemsForDb: FlyerItemInsert[] }> {
console.log('[DEBUG] FlyerDataTransformer.transform called with baseUrl:', baseUrl);
logger.info('Starting data transformation from AI output to database format.');
try {

View File

@@ -103,6 +103,8 @@ export class FlyerProcessingService {
// The main processed image path is already in `allFilePaths` via `createdImagePaths`.
allFilePaths.push(path.join(iconsDir, iconFileName));
console.log('[DEBUG] FlyerProcessingService calling transformer with:', { originalFileName: job.data.originalFileName, imageFileName, iconFileName, checksum: job.data.checksum, baseUrl: job.data.baseUrl });
const { flyerData, itemsForDb } = await this.transformer.transform(
aiResult,
job.data.originalFileName,

View File

@@ -1,7 +1,6 @@
// src/tests/integration/admin.integration.test.ts
import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { getPool } from '../../services/db/connection.db';
import type { UserProfile } from '../../types';
import { createAndLoginUser, TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
@@ -10,9 +9,9 @@ import { cleanupDb } from '../utils/cleanup';
/**
* @vitest-environment node
*/
const request = supertest(app);
describe('Admin API Routes Integration Tests', () => {
let request: ReturnType<typeof supertest>;
let adminToken: string;
let adminUser: UserProfile;
let regularUser: UserProfile;
@@ -21,6 +20,10 @@ describe('Admin API Routes Integration Tests', () => {
const createdStoreIds: number[] = [];
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Create a fresh admin user and a regular user for this test suite
// Using unique emails to prevent test pollution from other integration test files.
({ user: adminUser, token: adminToken } = await createAndLoginUser({
@@ -40,6 +43,7 @@ describe('Admin API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
await cleanupDb({
userIds: createdUserIds,
storeIds: createdStoreIds,

View File

@@ -1,7 +1,6 @@
// src/tests/integration/ai.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import fs from 'node:fs/promises';
import path from 'path';
import { createAndLoginUser } from '../utils/testHelpers';
@@ -12,8 +11,6 @@ import { cleanupFiles } from '../utils/cleanupFiles';
* @vitest-environment node
*/
const request = supertest(app);
interface TestGeolocationCoordinates {
latitude: number;
longitude: number;
@@ -26,10 +23,15 @@ interface TestGeolocationCoordinates {
}
describe('AI API Routes Integration Tests', () => {
let request: ReturnType<typeof supertest>;
let authToken: string;
let testUserId: string;
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Create and log in as a new user for authenticated tests.
const { token, user } = await createAndLoginUser({ fullName: 'AI Tester', request });
authToken = token;
@@ -37,6 +39,7 @@ describe('AI API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
// 1. Clean up database records
await cleanupDb({ userIds: [testUserId] });

View File

@@ -1,7 +1,6 @@
// src/tests/integration/auth.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { createAndLoginUser, TEST_PASSWORD } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';
import type { UserProfile } from '../../types';
@@ -10,8 +9,6 @@ import type { UserProfile } from '../../types';
* @vitest-environment node
*/
const request = supertest(app);
/**
* These are integration tests that verify the authentication flow against a running backend server.
* Make sure your Express server is running before executing these tests.
@@ -19,11 +16,16 @@ const request = supertest(app);
* To run only these tests: `vitest run src/tests/auth.integration.test.ts`
*/
describe('Authentication API Integration', () => {
let request: ReturnType<typeof supertest>;
let testUserEmail: string;
let testUser: UserProfile;
const createdUserIds: string[] = [];
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Use a unique email for this test suite to prevent collisions with other tests.
const email = `auth-integration-test-${Date.now()}@example.com`;
({ user: testUser } = await createAndLoginUser({ email, fullName: 'Auth Test User', request }));
@@ -32,6 +34,7 @@ describe('Authentication API Integration', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
await cleanupDb({ userIds: createdUserIds });
});

View File

@@ -1,7 +1,6 @@
// src/tests/integration/budget.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';
import type { UserProfile, Budget } from '../../types';
@@ -11,9 +10,8 @@ import { getPool } from '../../services/db/connection.db';
* @vitest-environment node
*/
const request = supertest(app);
describe('Budget API Routes Integration Tests', () => {
let request: ReturnType<typeof supertest>;
let testUser: UserProfile;
let authToken: string;
let testBudget: Budget;
@@ -21,6 +19,10 @@ describe('Budget API Routes Integration Tests', () => {
const createdBudgetIds: number[] = [];
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// 1. Create a user for the tests
const { user, token } = await createAndLoginUser({
email: `budget-user-${Date.now()}@example.com`,
@@ -50,6 +52,7 @@ describe('Budget API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
// Clean up all created resources
await cleanupDb({
userIds: createdUserIds,

View File

@@ -1,7 +1,6 @@
// src/tests/integration/flyer-processing.integration.test.ts
import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import fs from 'node:fs/promises';
import path from 'path';
import * as db from '../../services/db/index.db';
@@ -22,8 +21,6 @@ import sharp from 'sharp';
* @vitest-environment node
*/
const request = supertest(app);
const { mockExtractCoreData } = vi.hoisted(() => ({
mockExtractCoreData: vi.fn(),
}));
@@ -50,6 +47,7 @@ vi.mock('../../services/db/index.db', async (importOriginal) => {
});
describe('Flyer Processing Background Job Integration Test', () => {
let request: ReturnType<typeof supertest>;
const createdUserIds: string[] = [];
const createdFlyerIds: number[] = [];
const createdFilePaths: string[] = [];
@@ -57,12 +55,19 @@ describe('Flyer Processing Background Job Integration Test', () => {
beforeAll(async () => {
// FIX: Stub FRONTEND_URL to ensure valid absolute URLs (http://...) are generated
// for the database, satisfying the 'url_check' constraint.
// IMPORTANT: This must run BEFORE the app is imported so workers inherit the env var.
vi.stubEnv('FRONTEND_URL', 'https://example.com');
console.log('[TEST SETUP] FRONTEND_URL stubbed to:', process.env.FRONTEND_URL);
const appModule = await import('../../../server');
const app = appModule.default;
request = supertest(app);
});
// FIX: Reset mocks before each test to ensure isolation.
// This prevents "happy path" mocks from leaking into error handling tests and vice versa.
beforeEach(async () => {
console.log('[TEST SETUP] Resetting mocks before test execution');
// 1. Reset AI Service Mock to default success state
mockExtractCoreData.mockReset();
mockExtractCoreData.mockResolvedValue({
@@ -107,6 +112,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
* It uploads a file, polls for completion, and verifies the result in the database.
*/
const runBackgroundProcessingTest = async (user?: UserProfile, token?: string) => {
console.log(`[TEST START] runBackgroundProcessingTest. User: ${user?.user.email ?? 'ANONYMOUS'}`);
// Arrange: Load a mock flyer PDF.
const imagePath = path.resolve(__dirname, '../assets/test-flyer-image.jpg');
const imageBuffer = await fs.readFile(imagePath);
@@ -116,6 +122,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
const uniqueFileName = `test-flyer-image-${Date.now()}.jpg`;
const mockImageFile = new File([new Uint8Array(uniqueContent)], uniqueFileName, { type: 'image/jpeg' });
const checksum = await generateFileChecksum(mockImageFile);
console.log('[TEST DATA] Generated checksum for test:', checksum);
// Track created files for cleanup
const uploadDir = path.resolve(__dirname, '../../../flyer-images');
@@ -125,17 +132,22 @@ describe('Flyer Processing Background Job Integration Test', () => {
createdFilePaths.push(path.join(uploadDir, 'icons', iconFileName));
// Act 1: Upload the file to start the background job.
const testBaseUrl = getTestBaseUrl();
console.log('[TEST ACTION] Uploading file with baseUrl:', testBaseUrl);
const uploadReq = request
.post('/api/ai/upload-and-process')
.field('checksum', checksum)
// Pass the baseUrl directly in the form data to ensure the worker receives it,
// bypassing issues with vi.stubEnv in multi-threaded test environments.
.field('baseUrl', getTestBaseUrl())
.field('baseUrl', testBaseUrl)
.attach('flyerFile', uniqueContent, uniqueFileName);
if (token) {
uploadReq.set('Authorization', `Bearer ${token}`);
}
const uploadResponse = await uploadReq;
console.log('[TEST RESPONSE] Upload status:', uploadResponse.status);
console.log('[TEST RESPONSE] Upload body:', JSON.stringify(uploadResponse.body));
const { jobId } = uploadResponse.body;
// Assert 1: Check that a job ID was returned.
@@ -149,6 +161,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
statusReq.set('Authorization', `Bearer ${token}`);
}
const statusResponse = await statusReq;
console.log(`[TEST POLL] Job ${jobId} current state:`, statusResponse.body?.state);
return statusResponse.body;
},
(status) => status.state === 'completed' || status.state === 'failed',

View File

@@ -1,8 +1,7 @@
// src/tests/integration/flyer.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import { getPool } from '../../services/db/connection.db';
import app from '../../../server';
import type { Flyer, FlyerItem } from '../../types';
import { cleanupDb } from '../utils/cleanup';
import { TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
@@ -14,12 +13,16 @@ import { TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
describe('Public Flyer API Routes Integration Tests', () => {
let flyers: Flyer[] = [];
// Use a supertest instance for all requests in this file
const request = supertest(app);
let request: ReturnType<typeof supertest>;
let testStoreId: number;
let createdFlyerId: number;
// Fetch flyers once before all tests in this suite to use in subsequent tests.
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Ensure at least one flyer exists
const storeRes = await getPool().query(
`INSERT INTO public.stores (name) VALUES ('Integration Test Store') RETURNING store_id`,
@@ -45,6 +48,7 @@ describe('Public Flyer API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
// Clean up the test data created in beforeAll to prevent polluting the test database.
await cleanupDb({
flyerIds: [createdFlyerId],

View File

@@ -1,7 +1,6 @@
// src/tests/integration/gamification.integration.test.ts
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import path from 'path';
import fs from 'node:fs/promises';
import { getPool } from '../../services/db/connection.db';
@@ -26,8 +25,6 @@ import { cleanupFiles } from '../utils/cleanupFiles';
* @vitest-environment node
*/
const request = supertest(app);
const { mockExtractCoreData } = vi.hoisted(() => ({
mockExtractCoreData: vi.fn(),
}));
@@ -53,6 +50,7 @@ vi.mock('../../utils/imageProcessor', async () => {
});
describe('Gamification Flow Integration Test', () => {
let request: ReturnType<typeof supertest>;
let testUser: UserProfile;
let authToken: string;
const createdFlyerIds: number[] = [];
@@ -60,6 +58,12 @@ describe('Gamification Flow Integration Test', () => {
const createdStoreIds: number[] = [];
beforeAll(async () => {
// Stub environment variables for URL generation in the background worker.
// This needs to be in beforeAll to ensure it's set before any code that might use it is imported.
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Create a new user specifically for this test suite to ensure a clean slate.
({ user: testUser, token: authToken } = await createAndLoginUser({
email: `gamification-user-${Date.now()}@example.com`,
@@ -67,10 +71,6 @@ describe('Gamification Flow Integration Test', () => {
request,
}));
// Stub environment variables for URL generation in the background worker.
// This needs to be in beforeAll to ensure it's set before any code that might use it is imported.
vi.stubEnv('FRONTEND_URL', 'https://example.com');
// Setup default mock response for the AI service's extractCoreDataFromFlyerImage method.
mockExtractCoreData.mockResolvedValue({
store_name: 'Gamification Test Store',
@@ -90,6 +90,7 @@ describe('Gamification Flow Integration Test', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
await cleanupDb({
userIds: testUser ? [testUser.user.user_id] : [],
flyerIds: createdFlyerIds,

View File

@@ -1,7 +1,6 @@
// src/tests/integration/notification.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';
import type { UserProfile, Notification } from '../../types';
@@ -11,14 +10,17 @@ import { getPool } from '../../services/db/connection.db';
* @vitest-environment node
*/
const request = supertest(app);
describe('Notification API Routes Integration Tests', () => {
let request: ReturnType<typeof supertest>;
let testUser: UserProfile;
let authToken: string;
const createdUserIds: string[] = [];
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// 1. Create a user for the tests
const { user, token } = await createAndLoginUser({
email: `notification-user-${Date.now()}@example.com`,
@@ -46,6 +48,7 @@ describe('Notification API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
// Notifications are deleted via CASCADE when the user is deleted.
await cleanupDb({
userIds: createdUserIds,

View File

@@ -1,7 +1,6 @@
// src/tests/integration/price.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { getPool } from '../../services/db/connection.db';
import { TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
@@ -9,9 +8,8 @@ import { TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
* @vitest-environment node
*/
const request = supertest(app);
describe('Price History API Integration Test (/api/price-history)', () => {
let request: ReturnType<typeof supertest>;
let masterItemId: number;
let storeId: number;
let flyerId1: number;
@@ -19,6 +17,10 @@ describe('Price History API Integration Test (/api/price-history)', () => {
let flyerId3: number;
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
const pool = getPool();
// 1. Create a master grocery item
@@ -71,6 +73,7 @@ describe('Price History API Integration Test (/api/price-history)', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
const pool = getPool();
// The CASCADE on the tables should handle flyer_items.
// The delete on flyers cascades to flyer_items, which fires a trigger `recalculate_price_history_on_flyer_item_delete`.

View File

@@ -1,7 +1,6 @@
// src/tests/integration/public.routes.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import type {
Flyer,
FlyerItem,
@@ -20,16 +19,19 @@ import { createAndLoginUser, TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
* @vitest-environment node
*/
const request = supertest(app);
describe('Public API Routes Integration Tests', () => {
// Shared state for tests
let request: ReturnType<typeof supertest>;
let testUser: UserProfile;
let testRecipe: Recipe;
let testFlyer: Flyer;
let testStoreId: number;
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
const pool = getPool();
// Create a user to own the recipe
const userEmail = `public-routes-user-${Date.now()}@example.com`;
@@ -77,6 +79,7 @@ describe('Public API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
await cleanupDb({
userIds: testUser ? [testUser.user.user_id] : [],
recipeIds: testRecipe ? [testRecipe.recipe_id] : [],

View File

@@ -1,7 +1,6 @@
// src/tests/integration/recipe.integration.test.ts
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';
import type { UserProfile, Recipe, RecipeComment } from '../../types';
@@ -13,9 +12,8 @@ import { aiService } from '../../services/aiService.server';
* @vitest-environment node
*/
const request = supertest(app);
describe('Recipe API Routes Integration Tests', () => {
let request: ReturnType<typeof supertest>;
let testUser: UserProfile;
let authToken: string;
let testRecipe: Recipe;
@@ -23,6 +21,10 @@ describe('Recipe API Routes Integration Tests', () => {
const createdRecipeIds: number[] = [];
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Create a user to own the recipe and perform authenticated actions
const { user, token } = await createAndLoginUser({
email: `recipe-user-${Date.now()}@example.com`,
@@ -48,6 +50,7 @@ describe('Recipe API Routes Integration Tests', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
// Clean up all created resources
await cleanupDb({
userIds: createdUserIds,

View File

@@ -1,13 +1,23 @@
// src/tests/integration/server.integration.test.ts
import { describe, it, expect } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
/**
* @vitest-environment node
*/
describe('Server Initialization Smoke Test', () => {
let app: any;
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
app = (await import('../../../server')).default;
});
afterAll(() => {
vi.unstubAllEnvs();
});
it('should import the server app without crashing', () => {
// This test's primary purpose is to ensure that all top-level code in `server.ts`
// can execute without throwing an error. This catches issues like syntax errors,

View File

@@ -1,13 +1,23 @@
// src/tests/integration/system.integration.test.ts
import { describe, it, expect } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
/**
* @vitest-environment node
*/
describe('System API Routes Integration Tests', () => {
let app: any;
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
app = (await import('../../../server')).default;
});
afterAll(() => {
vi.unstubAllEnvs();
});
describe('GET /api/system/pm2-status', () => {
it('should return a status for PM2', async () => {
const request = supertest(app);

View File

@@ -1,9 +1,8 @@
// src/tests/integration/user.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import path from 'path';
import fs from 'node:fs/promises';
import app from '../../../server';
import { logger } from '../../services/logger.server';
import { getPool } from '../../services/db/connection.db';
import type { UserProfile, MasterGroceryItem, ShoppingList } from '../../types';
@@ -15,9 +14,8 @@ import { cleanupFiles } from '../utils/cleanupFiles';
* @vitest-environment node
*/
const request = supertest(app);
describe('User API Routes Integration Tests', () => {
let request: ReturnType<typeof supertest>;
let testUser: UserProfile;
let authToken: string;
const createdUserIds: string[] = [];
@@ -25,6 +23,10 @@ describe('User API Routes Integration Tests', () => {
// 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 () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
const email = `user-test-${Date.now()}@example.com`;
const { user, token } = await createAndLoginUser({ email, fullName: 'Test User', request });
testUser = user;
@@ -35,6 +37,7 @@ describe('User API Routes Integration Tests', () => {
// After all tests, clean up by deleting the created user.
// This now cleans up ALL users created by this test suite to prevent pollution.
afterAll(async () => {
vi.unstubAllEnvs();
await cleanupDb({ userIds: createdUserIds });
// Safeguard to clean up any avatar files created during tests.

View File

@@ -1,7 +1,6 @@
// src/tests/integration/user.routes.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import supertest from 'supertest';
import app from '../../../server';
import type { UserProfile } from '../../types';
import { createAndLoginUser } from '../utils/testHelpers';
import { cleanupDb } from '../utils/cleanup';
@@ -10,15 +9,18 @@ import { cleanupDb } from '../utils/cleanup';
* @vitest-environment node
*/
const request = supertest(app);
describe('User Routes Integration Tests (/api/users)', () => {
let request: ReturnType<typeof supertest>;
let authToken = '';
let testUser: UserProfile;
const createdUserIds: string[] = [];
// Authenticate once before all tests in this suite to get a JWT.
beforeAll(async () => {
vi.stubEnv('FRONTEND_URL', 'https://example.com');
const app = (await import('../../../server')).default;
request = supertest(app);
// Use the helper to create and log in a user in one step.
const { user, token } = await createAndLoginUser({
fullName: 'User Routes Test User',
@@ -30,6 +32,7 @@ describe('User Routes Integration Tests (/api/users)', () => {
});
afterAll(async () => {
vi.unstubAllEnvs();
await cleanupDb({ userIds: createdUserIds });
});

View File

@@ -22,6 +22,11 @@ const getPool = () => {
* and then rebuilds it from the master rollup script.
*/
export async function setup() {
// Ensure we are in the correct environment for these tests.
process.env.NODE_ENV = 'test';
// Set the FRONTEND_URL globally for any scripts or processes spawned here.
process.env.FRONTEND_URL = process.env.FRONTEND_URL || 'https://example.com';
// --- START DEBUG LOGGING ---
// Log the database connection details being used by the Vitest GLOBAL SETUP process.
// These variables are inherited from the CI environment.

View File

@@ -1,7 +1,6 @@
// src/tests/setup/integration-global-setup.ts
import { execSync } from 'child_process';
import type { Server } from 'http';
import app from '../../../server'; // Import the Express app
import { logger } from '../../services/logger.server';
import { getPool } from '../../services/db/connection.db';
@@ -13,6 +12,9 @@ let globalPool: ReturnType<typeof getPool> | null = null;
export async function setup() {
// Ensure we are in the correct environment for these tests.
process.env.NODE_ENV = 'test';
// Fix: Set the FRONTEND_URL globally for the test server instance
process.env.FRONTEND_URL = 'https://example.com';
console.log(`\n--- [PID:${process.pid}] Running Integration Test GLOBAL Setup ---`);
// The integration setup is now the single source of truth for preparing the test DB.
@@ -30,6 +32,10 @@ export async function setup() {
console.log(`[PID:${process.pid}] Initializing global database pool...`);
globalPool = getPool();
// Fix: Dynamic import AFTER env vars are set
const appModule = await import('../../../server');
const app = appModule.default;
// Programmatically start the server within the same process.
const port = process.env.PORT || 3001;
await new Promise<void>((resolve) => {

View File

@@ -43,6 +43,7 @@ export async function processAndSaveImage(
.toFile(outputPath);
logger.info(`Successfully processed image and saved to ${outputPath}`);
console.log('[DEBUG] processAndSaveImage returning:', outputFileName);
return outputFileName;
} catch (error) {
logger.error(
@@ -84,6 +85,7 @@ export async function generateFlyerIcon(
.toFile(outputPath);
logger.info(`Successfully generated icon: ${outputPath}`);
console.log('[DEBUG] generateFlyerIcon returning:', iconFileName);
return iconFileName;
} catch (error) {
logger.error(