more baseurl work - hopefully that does it for now
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 26m5s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 26m5s
This commit is contained in:
@@ -28,6 +28,7 @@ const uploadAndProcessSchema = z.object({
|
|||||||
.length(64, 'Checksum must be 64 characters long.')
|
.length(64, 'Checksum must be 64 characters long.')
|
||||||
.regex(/^[a-f0-9]+$/, 'Checksum must be a valid hexadecimal string.'),
|
.regex(/^[a-f0-9]+$/, 'Checksum must be a valid hexadecimal string.'),
|
||||||
),
|
),
|
||||||
|
baseUrl: z.string().url().optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,6 +199,7 @@ router.post(
|
|||||||
userProfile,
|
userProfile,
|
||||||
req.ip ?? 'unknown',
|
req.ip ?? 'unknown',
|
||||||
req.log,
|
req.log,
|
||||||
|
body.baseUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Respond immediately to the client with 202 Accepted
|
// Respond immediately to the client with 202 Accepted
|
||||||
|
|||||||
@@ -1243,7 +1243,7 @@ describe('User Routes (/api/users)', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Advance time to ensure rate limits are reset between tests
|
// Advance time to ensure rate limits are reset between tests
|
||||||
vi.advanceTimersByTime(60 * 60 * 1000 + 1000);
|
vi.advanceTimersByTime(2 * 60 * 60 * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -1302,6 +1302,9 @@ describe('User Routes (/api/users)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should apply userSensitiveUpdateLimiter to DELETE /account and block after limit', async () => {
|
it('should apply userSensitiveUpdateLimiter to DELETE /account and block after limit', async () => {
|
||||||
|
// Explicitly advance time to ensure the rate limiter window has reset from previous tests
|
||||||
|
vi.advanceTimersByTime(60 * 60 * 1000 + 5000);
|
||||||
|
|
||||||
const limit = 5;
|
const limit = 5;
|
||||||
vi.mocked(userService.deleteUserAccount).mockResolvedValue(undefined);
|
vi.mocked(userService.deleteUserAccount).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
|||||||
@@ -753,6 +753,7 @@ async enqueueFlyerProcessing(
|
|||||||
userProfile: UserProfile | undefined,
|
userProfile: UserProfile | undefined,
|
||||||
submitterIp: string,
|
submitterIp: string,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
|
baseUrlOverride?: string,
|
||||||
): Promise<Job> {
|
): Promise<Job> {
|
||||||
// 1. Check for duplicate flyer
|
// 1. Check for duplicate flyer
|
||||||
const existingFlyer = await db.flyerRepo.findFlyerByChecksum(checksum, logger);
|
const existingFlyer = await db.flyerRepo.findFlyerByChecksum(checksum, logger);
|
||||||
@@ -779,7 +780,7 @@ async enqueueFlyerProcessing(
|
|||||||
.join(', ');
|
.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = getBaseUrl(logger);
|
const baseUrl = baseUrlOverride || getBaseUrl(logger);
|
||||||
// --- START DEBUGGING ---
|
// --- START DEBUGGING ---
|
||||||
// Add a fail-fast check to ensure the baseUrl is a valid URL before enqueuing.
|
// Add a fail-fast check to ensure the baseUrl is a valid URL before enqueuing.
|
||||||
// This will make the test fail at the upload step if the URL is the problem,
|
// This will make the test fail at the upload step if the URL is the problem,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { logger as mockLogger } from './logger.server';
|
|||||||
import { generateFlyerIcon } from '../utils/imageProcessor';
|
import { generateFlyerIcon } from '../utils/imageProcessor';
|
||||||
import type { AiProcessorResult } from './flyerAiProcessor.server';
|
import type { AiProcessorResult } from './flyerAiProcessor.server';
|
||||||
import type { FlyerItemInsert } from '../types';
|
import type { FlyerItemInsert } from '../types';
|
||||||
|
import { getBaseUrl } from '../utils/serverUtils';
|
||||||
|
|
||||||
// Mock the dependencies
|
// Mock the dependencies
|
||||||
vi.mock('../utils/imageProcessor', () => ({
|
vi.mock('../utils/imageProcessor', () => ({
|
||||||
@@ -15,6 +16,10 @@ vi.mock('./logger.server', () => ({
|
|||||||
logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
|
logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../utils/serverUtils', () => ({
|
||||||
|
getBaseUrl: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('FlyerDataTransformer', () => {
|
describe('FlyerDataTransformer', () => {
|
||||||
let transformer: FlyerDataTransformer;
|
let transformer: FlyerDataTransformer;
|
||||||
|
|
||||||
@@ -29,6 +34,7 @@ describe('FlyerDataTransformer', () => {
|
|||||||
|
|
||||||
// Provide a default mock implementation for generateFlyerIcon
|
// Provide a default mock implementation for generateFlyerIcon
|
||||||
vi.mocked(generateFlyerIcon).mockResolvedValue('icon-flyer-page-1.webp');
|
vi.mocked(generateFlyerIcon).mockResolvedValue('icon-flyer-page-1.webp');
|
||||||
|
vi.mocked(getBaseUrl).mockReturnValue('http://localhost:3000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transform AI data into database-ready format with a user ID', async () => {
|
it('should transform AI data into database-ready format with a user ID', async () => {
|
||||||
@@ -244,7 +250,7 @@ describe('FlyerDataTransformer', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use fallback baseUrl if none is provided and log a warning', async () => {
|
it('should use fallback baseUrl from getBaseUrl if none is provided', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const aiResult: AiProcessorResult = {
|
const aiResult: AiProcessorResult = {
|
||||||
data: {
|
data: {
|
||||||
@@ -258,9 +264,8 @@ describe('FlyerDataTransformer', () => {
|
|||||||
};
|
};
|
||||||
const baseUrl = undefined; // Explicitly pass undefined for this test
|
const baseUrl = undefined; // Explicitly pass undefined for this test
|
||||||
|
|
||||||
// The fallback logic uses process.env.PORT || 3000.
|
const expectedFallbackUrl = 'http://fallback-url.com';
|
||||||
// The beforeEach sets PORT to '', so it should fallback to 3000.
|
vi.mocked(getBaseUrl).mockReturnValue(expectedFallbackUrl);
|
||||||
const expectedFallbackUrl = 'http://localhost:3000';
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const { flyerData } = await transformer.transform(
|
const { flyerData } = await transformer.transform(
|
||||||
@@ -275,10 +280,8 @@ describe('FlyerDataTransformer', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// 1. Check that a warning was logged
|
// 1. Check that getBaseUrl was called
|
||||||
expect(mockLogger.warn).toHaveBeenCalledWith(
|
expect(getBaseUrl).toHaveBeenCalledWith(mockLogger);
|
||||||
`Base URL not provided in job data. Falling back to default local URL: ${expectedFallbackUrl}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. Check that the URLs were constructed with the fallback
|
// 2. Check that the URLs were constructed with the fallback
|
||||||
expect(flyerData.image_url).toBe(`${expectedFallbackUrl}/flyer-images/flyer-page-1.jpg`);
|
expect(flyerData.image_url).toBe(`${expectedFallbackUrl}/flyer-images/flyer-page-1.jpg`);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { AiProcessorResult } from './flyerAiProcessor.server'; // Keep this
|
|||||||
import { AiFlyerDataSchema } from '../types/ai'; // Import consolidated schema
|
import { AiFlyerDataSchema } from '../types/ai'; // Import consolidated schema
|
||||||
import { TransformationError } from './processingErrors';
|
import { TransformationError } from './processingErrors';
|
||||||
import { parsePriceToCents } from '../utils/priceParser';
|
import { parsePriceToCents } from '../utils/priceParser';
|
||||||
|
import { getBaseUrl } from '../utils/serverUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is responsible for transforming the validated data from the AI service
|
* This class is responsible for transforming the validated data from the AI service
|
||||||
@@ -62,13 +63,7 @@ export class FlyerDataTransformer {
|
|||||||
logger: Logger,
|
logger: Logger,
|
||||||
): { imageUrl: string; iconUrl: string } {
|
): { imageUrl: string; iconUrl: string } {
|
||||||
logger.debug({ imageFileName, iconFileName, baseUrl }, 'Building URLs');
|
logger.debug({ imageFileName, iconFileName, baseUrl }, 'Building URLs');
|
||||||
let finalBaseUrl = baseUrl;
|
const finalBaseUrl = baseUrl || getBaseUrl(logger);
|
||||||
if (!finalBaseUrl) {
|
|
||||||
const port = process.env.PORT || 3000;
|
|
||||||
finalBaseUrl = `http://localhost:${port}`;
|
|
||||||
logger.warn(`Base URL not provided in job data. Falling back to default local URL: ${finalBaseUrl}`);
|
|
||||||
}
|
|
||||||
finalBaseUrl = finalBaseUrl.endsWith('/') ? finalBaseUrl.slice(0, -1) : finalBaseUrl;
|
|
||||||
const imageUrl = `${finalBaseUrl}/flyer-images/${imageFileName}`;
|
const imageUrl = `${finalBaseUrl}/flyer-images/${imageFileName}`;
|
||||||
const iconUrl = `${finalBaseUrl}/flyer-images/icons/${iconFileName}`;
|
const iconUrl = `${finalBaseUrl}/flyer-images/icons/${iconFileName}`;
|
||||||
logger.debug({ imageUrl, iconUrl }, 'Constructed URLs');
|
logger.debug({ imageUrl, iconUrl }, 'Constructed URLs');
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ValidationError, NotFoundError } from './db/errors.db';
|
|||||||
import { DatabaseError } from './processingErrors';
|
import { DatabaseError } from './processingErrors';
|
||||||
import type { Job } from 'bullmq';
|
import type { Job } from 'bullmq';
|
||||||
import type { TokenCleanupJobData } from '../types/job-data';
|
import type { TokenCleanupJobData } from '../types/job-data';
|
||||||
|
import { getTestBaseUrl } from '../tests/utils/testHelpers';
|
||||||
|
|
||||||
// Un-mock the service under test to ensure we are testing the real implementation,
|
// Un-mock the service under test to ensure we are testing the real implementation,
|
||||||
// not the global mock from `tests/setup/tests-setup-unit.ts`.
|
// not the global mock from `tests/setup/tests-setup-unit.ts`.
|
||||||
@@ -240,12 +241,12 @@ describe('UserService', () => {
|
|||||||
describe('updateUserAvatar', () => {
|
describe('updateUserAvatar', () => {
|
||||||
it('should construct avatar URL and update profile', async () => {
|
it('should construct avatar URL and update profile', async () => {
|
||||||
const { logger } = await import('./logger.server');
|
const { logger } = await import('./logger.server');
|
||||||
const testBaseUrl = 'http://localhost:3001';
|
const testBaseUrl = getTestBaseUrl(3001);
|
||||||
vi.stubEnv('FRONTEND_URL', testBaseUrl);
|
vi.stubEnv('FRONTEND_URL', testBaseUrl);
|
||||||
|
|
||||||
const userId = 'user-123';
|
const userId = 'user-123';
|
||||||
const file = { filename: 'avatar.jpg' } as Express.Multer.File;
|
const file = { filename: 'avatar.jpg' } as Express.Multer.File;
|
||||||
const expectedUrl = `${testBaseUrl}/uploads/avatars/avatar.jpg`;
|
const expectedUrl = `${testBaseUrl}/uploads/avatars/${file.filename}`;
|
||||||
|
|
||||||
mocks.mockUpdateUserProfile.mockResolvedValue({} as any);
|
mocks.mockUpdateUserProfile.mockResolvedValue({} as any);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import supertest from 'supertest';
|
|||||||
import app from '../../../server';
|
import app from '../../../server';
|
||||||
import { getPool } from '../../services/db/connection.db';
|
import { getPool } from '../../services/db/connection.db';
|
||||||
import type { UserProfile } from '../../types';
|
import type { UserProfile } from '../../types';
|
||||||
import { createAndLoginUser } from '../utils/testHelpers';
|
import { createAndLoginUser, TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
|
||||||
import { cleanupDb } from '../utils/cleanup';
|
import { cleanupDb } from '../utils/cleanup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +164,7 @@ describe('Admin API Routes Integration Tests', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const flyerRes = await getPool().query(
|
const flyerRes = await getPool().query(
|
||||||
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum)
|
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum)
|
||||||
VALUES ($1, 'admin-test.jpg', 'https://example.com/flyer-images/asdmin-test.jpg', 'https://example.com/flyer-images/icons/admin-test.jpg', 1, $2) RETURNING flyer_id`,
|
VALUES ($1, 'admin-test.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/asdmin-test.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/icons/admin-test.jpg', 1, $2) RETURNING flyer_id`,
|
||||||
// The checksum must be a unique 64-character string to satisfy the DB constraint.
|
// The checksum must be a unique 64-character string to satisfy the DB constraint.
|
||||||
// We generate a dynamic string and pad it to 64 characters.
|
// We generate a dynamic string and pad it to 64 characters.
|
||||||
[testStoreId, `checksum-${Date.now()}-${Math.random()}`.padEnd(64, '0')],
|
[testStoreId, `checksum-${Date.now()}-${Math.random()}`.padEnd(64, '0')],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getPool } from '../../services/db/connection.db';
|
|||||||
import { generateFileChecksum } from '../../utils/checksum';
|
import { generateFileChecksum } from '../../utils/checksum';
|
||||||
import { logger } from '../../services/logger.server';
|
import { logger } from '../../services/logger.server';
|
||||||
import type { UserProfile, ExtractedFlyerItem } from '../../types';
|
import type { UserProfile, ExtractedFlyerItem } from '../../types';
|
||||||
import { createAndLoginUser } from '../utils/testHelpers';
|
import { createAndLoginUser, getTestBaseUrl } from '../utils/testHelpers';
|
||||||
import { cleanupDb } from '../utils/cleanup';
|
import { cleanupDb } from '../utils/cleanup';
|
||||||
import { poll } from '../utils/poll';
|
import { poll } from '../utils/poll';
|
||||||
import { cleanupFiles } from '../utils/cleanupFiles';
|
import { cleanupFiles } from '../utils/cleanupFiles';
|
||||||
@@ -130,7 +130,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
|
|||||||
.field('checksum', checksum)
|
.field('checksum', checksum)
|
||||||
// Pass the baseUrl directly in the form data to ensure the worker receives it,
|
// Pass the baseUrl directly in the form data to ensure the worker receives it,
|
||||||
// bypassing issues with vi.stubEnv in multi-threaded test environments.
|
// bypassing issues with vi.stubEnv in multi-threaded test environments.
|
||||||
.field('baseUrl', 'http://localhost:3000')
|
.field('baseUrl', getTestBaseUrl())
|
||||||
.attach('flyerFile', uniqueContent, uniqueFileName);
|
.attach('flyerFile', uniqueContent, uniqueFileName);
|
||||||
if (token) {
|
if (token) {
|
||||||
uploadReq.set('Authorization', `Bearer ${token}`);
|
uploadReq.set('Authorization', `Bearer ${token}`);
|
||||||
@@ -248,7 +248,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
|
|||||||
const uploadResponse = await request
|
const uploadResponse = await request
|
||||||
.post('/api/ai/upload-and-process')
|
.post('/api/ai/upload-and-process')
|
||||||
.set('Authorization', `Bearer ${token}`)
|
.set('Authorization', `Bearer ${token}`)
|
||||||
.field('baseUrl', 'http://localhost:3000')
|
.field('baseUrl', getTestBaseUrl())
|
||||||
.field('checksum', checksum)
|
.field('checksum', checksum)
|
||||||
.attach('flyerFile', imageWithExifBuffer, uniqueFileName);
|
.attach('flyerFile', imageWithExifBuffer, uniqueFileName);
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
|
|||||||
const uploadResponse = await request
|
const uploadResponse = await request
|
||||||
.post('/api/ai/upload-and-process')
|
.post('/api/ai/upload-and-process')
|
||||||
.set('Authorization', `Bearer ${token}`)
|
.set('Authorization', `Bearer ${token}`)
|
||||||
.field('baseUrl', 'http://localhost:3000')
|
.field('baseUrl', getTestBaseUrl())
|
||||||
.field('checksum', checksum)
|
.field('checksum', checksum)
|
||||||
.attach('flyerFile', imageWithMetadataBuffer, uniqueFileName);
|
.attach('flyerFile', imageWithMetadataBuffer, uniqueFileName);
|
||||||
|
|
||||||
@@ -399,7 +399,7 @@ it(
|
|||||||
// Act 1: Upload the file to start the background job.
|
// Act 1: Upload the file to start the background job.
|
||||||
const uploadResponse = await request
|
const uploadResponse = await request
|
||||||
.post('/api/ai/upload-and-process')
|
.post('/api/ai/upload-and-process')
|
||||||
.field('baseUrl', 'http://localhost:3000')
|
.field('baseUrl', getTestBaseUrl())
|
||||||
.field('checksum', checksum)
|
.field('checksum', checksum)
|
||||||
.attach('flyerFile', uniqueContent, uniqueFileName);
|
.attach('flyerFile', uniqueContent, uniqueFileName);
|
||||||
|
|
||||||
@@ -451,7 +451,7 @@ it(
|
|||||||
// Act 1: Upload the file to start the background job.
|
// Act 1: Upload the file to start the background job.
|
||||||
const uploadResponse = await request
|
const uploadResponse = await request
|
||||||
.post('/api/ai/upload-and-process')
|
.post('/api/ai/upload-and-process')
|
||||||
.field('baseUrl', 'http://localhost:3000')
|
.field('baseUrl', getTestBaseUrl())
|
||||||
.field('checksum', checksum)
|
.field('checksum', checksum)
|
||||||
.attach('flyerFile', uniqueContent, uniqueFileName);
|
.attach('flyerFile', uniqueContent, uniqueFileName);
|
||||||
|
|
||||||
@@ -505,7 +505,7 @@ it(
|
|||||||
// Act 1: Upload the file to start the background job.
|
// Act 1: Upload the file to start the background job.
|
||||||
const uploadResponse = await request
|
const uploadResponse = await request
|
||||||
.post('/api/ai/upload-and-process')
|
.post('/api/ai/upload-and-process')
|
||||||
.field('baseUrl', 'http://localhost:3000')
|
.field('baseUrl', getTestBaseUrl())
|
||||||
.field('checksum', checksum)
|
.field('checksum', checksum)
|
||||||
.attach('flyerFile', uniqueContent, uniqueFileName);
|
.attach('flyerFile', uniqueContent, uniqueFileName);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getPool } from '../../services/db/connection.db';
|
|||||||
import app from '../../../server';
|
import app from '../../../server';
|
||||||
import type { Flyer, FlyerItem } from '../../types';
|
import type { Flyer, FlyerItem } from '../../types';
|
||||||
import { cleanupDb } from '../utils/cleanup';
|
import { cleanupDb } from '../utils/cleanup';
|
||||||
|
import { TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @vitest-environment node
|
* @vitest-environment node
|
||||||
@@ -27,7 +28,7 @@ describe('Public Flyer API Routes Integration Tests', () => {
|
|||||||
|
|
||||||
const flyerRes = await getPool().query(
|
const flyerRes = await getPool().query(
|
||||||
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum)
|
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum)
|
||||||
VALUES ($1, 'integration-test.jpg', 'https://example.com/flyer-images/integration-test.jpg', 'https://example.com/flyer-images/icons/integration-test.jpg', 1, $2) RETURNING flyer_id`,
|
VALUES ($1, 'integration-test.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/integration-test.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/icons/integration-test.jpg', 1, $2) RETURNING flyer_id`,
|
||||||
[testStoreId, `${Date.now().toString(16)}`.padEnd(64, '0')],
|
[testStoreId, `${Date.now().toString(16)}`.padEnd(64, '0')],
|
||||||
);
|
);
|
||||||
createdFlyerId = flyerRes.rows[0].flyer_id;
|
createdFlyerId = flyerRes.rows[0].flyer_id;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import app from '../../../server';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import { getPool } from '../../services/db/connection.db';
|
import { getPool } from '../../services/db/connection.db';
|
||||||
import { createAndLoginUser } from '../utils/testHelpers';
|
import { createAndLoginUser, getTestBaseUrl } from '../utils/testHelpers';
|
||||||
import { generateFileChecksum } from '../../utils/checksum';
|
import { generateFileChecksum } from '../../utils/checksum';
|
||||||
import * as db from '../../services/db/index.db';
|
import * as db from '../../services/db/index.db';
|
||||||
import { cleanupDb } from '../utils/cleanup';
|
import { cleanupDb } from '../utils/cleanup';
|
||||||
@@ -253,7 +253,8 @@ describe('Gamification Flow Integration Test', () => {
|
|||||||
// 8. Assert that the URLs are fully qualified.
|
// 8. Assert that the URLs are fully qualified.
|
||||||
expect(savedFlyer.image_url).to.equal(newFlyer.image_url);
|
expect(savedFlyer.image_url).to.equal(newFlyer.image_url);
|
||||||
expect(savedFlyer.icon_url).to.equal(newFlyer.icon_url);
|
expect(savedFlyer.icon_url).to.equal(newFlyer.icon_url);
|
||||||
expect(newFlyer.image_url).toContain('http://localhost:3001/flyer-images/');
|
const expectedBaseUrl = getTestBaseUrl(3001);
|
||||||
|
expect(newFlyer.image_url).toContain(`${expectedBaseUrl}/flyer-images/`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -3,6 +3,7 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
import app from '../../../server';
|
import app from '../../../server';
|
||||||
import { getPool } from '../../services/db/connection.db';
|
import { getPool } from '../../services/db/connection.db';
|
||||||
|
import { TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @vitest-environment node
|
* @vitest-environment node
|
||||||
@@ -35,21 +36,21 @@ describe('Price History API Integration Test (/api/price-history)', () => {
|
|||||||
// 3. Create two flyers with different dates
|
// 3. Create two flyers with different dates
|
||||||
const flyerRes1 = await pool.query(
|
const flyerRes1 = await pool.query(
|
||||||
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum, valid_from)
|
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum, valid_from)
|
||||||
VALUES ($1, 'price-test-1.jpg', 'https://example.com/flyer-images/price-test-1.jpg', 'https://example.com/flyer-images/icons/price-test-1.jpg', 1, $2, '2025-01-01') RETURNING flyer_id`,
|
VALUES ($1, 'price-test-1.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/price-test-1.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/icons/price-test-1.jpg', 1, $2, '2025-01-01') RETURNING flyer_id`,
|
||||||
[storeId, `${Date.now().toString(16)}1`.padEnd(64, '0')],
|
[storeId, `${Date.now().toString(16)}1`.padEnd(64, '0')],
|
||||||
);
|
);
|
||||||
flyerId1 = flyerRes1.rows[0].flyer_id;
|
flyerId1 = flyerRes1.rows[0].flyer_id;
|
||||||
|
|
||||||
const flyerRes2 = await pool.query(
|
const flyerRes2 = await pool.query(
|
||||||
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum, valid_from)
|
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum, valid_from)
|
||||||
VALUES ($1, 'price-test-2.jpg', 'https://example.com/flyer-images/price-test-2.jpg', 'https://example.com/flyer-images/icons/price-test-2.jpg', 1, $2, '2025-01-08') RETURNING flyer_id`,
|
VALUES ($1, 'price-test-2.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/price-test-2.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/icons/price-test-2.jpg', 1, $2, '2025-01-08') RETURNING flyer_id`,
|
||||||
[storeId, `${Date.now().toString(16)}2`.padEnd(64, '0')],
|
[storeId, `${Date.now().toString(16)}2`.padEnd(64, '0')],
|
||||||
);
|
);
|
||||||
flyerId2 = flyerRes2.rows[0].flyer_id; // This was a duplicate, fixed.
|
flyerId2 = flyerRes2.rows[0].flyer_id; // This was a duplicate, fixed.
|
||||||
|
|
||||||
const flyerRes3 = await pool.query(
|
const flyerRes3 = await pool.query(
|
||||||
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum, valid_from)
|
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum, valid_from)
|
||||||
VALUES ($1, 'price-test-3.jpg', 'https://example.com/flyer-images/price-test-3.jpg', 'https://example.com/flyer-images/icons/price-test-3.jpg', 1, $2, '2025-01-15') RETURNING flyer_id`,
|
VALUES ($1, 'price-test-3.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/price-test-3.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/icons/price-test-3.jpg', 1, $2, '2025-01-15') RETURNING flyer_id`,
|
||||||
[storeId, `${Date.now().toString(16)}3`.padEnd(64, '0')],
|
[storeId, `${Date.now().toString(16)}3`.padEnd(64, '0')],
|
||||||
);
|
);
|
||||||
flyerId3 = flyerRes3.rows[0].flyer_id;
|
flyerId3 = flyerRes3.rows[0].flyer_id;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type {
|
|||||||
import { getPool } from '../../services/db/connection.db';
|
import { getPool } from '../../services/db/connection.db';
|
||||||
import { cleanupDb } from '../utils/cleanup';
|
import { cleanupDb } from '../utils/cleanup';
|
||||||
import { poll } from '../utils/poll';
|
import { poll } from '../utils/poll';
|
||||||
import { createAndLoginUser } from '../utils/testHelpers';
|
import { createAndLoginUser, TEST_EXAMPLE_DOMAIN } from '../utils/testHelpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @vitest-environment node
|
* @vitest-environment node
|
||||||
@@ -64,7 +64,7 @@ describe('Public API Routes Integration Tests', () => {
|
|||||||
testStoreId = storeRes.rows[0].store_id;
|
testStoreId = storeRes.rows[0].store_id;
|
||||||
const flyerRes = await pool.query(
|
const flyerRes = await pool.query(
|
||||||
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum)
|
`INSERT INTO public.flyers (store_id, file_name, image_url, icon_url, item_count, checksum)
|
||||||
VALUES ($1, 'public-routes-test.jpg', 'https://example.com/flyer-images/public-routes-test.jpg', 'https://example.com/flyer-images/icons/public-routes-test.jpg', 1, $2) RETURNING *`,
|
VALUES ($1, 'public-routes-test.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/public-routes-test.jpg', '${TEST_EXAMPLE_DOMAIN}/flyer-images/icons/public-routes-test.jpg', 1, $2) RETURNING *`,
|
||||||
[testStoreId, `${Date.now().toString(16)}`.padEnd(64, '0')],
|
[testStoreId, `${Date.now().toString(16)}`.padEnd(64, '0')],
|
||||||
);
|
);
|
||||||
testFlyer = flyerRes.rows[0];
|
testFlyer = flyerRes.rows[0];
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import type { UserProfile } from '../../types';
|
|||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
|
|
||||||
export const TEST_PASSWORD = 'a-much-stronger-password-for-testing-!@#$';
|
export const TEST_PASSWORD = 'a-much-stronger-password-for-testing-!@#$';
|
||||||
|
export const TEST_EXAMPLE_DOMAIN = 'https://example.com';
|
||||||
|
|
||||||
|
export const getTestBaseUrl = (fallbackPort = 3000): string => {
|
||||||
|
const url = process.env.FRONTEND_URL || `http://localhost:${fallbackPort}`;
|
||||||
|
return url.endsWith('/') ? url.slice(0, -1) : url;
|
||||||
|
};
|
||||||
|
|
||||||
interface CreateUserOptions {
|
interface CreateUserOptions {
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user