Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
696c547238 | ||
| 38165bdb9a |
@@ -113,7 +113,7 @@ jobs:
|
||||
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_TEST }}
|
||||
|
||||
# --- Integration test specific variables ---
|
||||
FRONTEND_URL: 'http://localhost:3000'
|
||||
FRONTEND_URL: 'http://example.com'
|
||||
VITE_API_BASE_URL: 'http://localhost:3001/api'
|
||||
GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY }}
|
||||
|
||||
@@ -389,7 +389,7 @@ jobs:
|
||||
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_TEST }}
|
||||
|
||||
# Application Secrets
|
||||
FRONTEND_URL: 'https://flyer-crawler-test.projectium.com'
|
||||
FRONTEND_URL: 'http://example.com'
|
||||
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
||||
GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY_TEST }}
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.9.34",
|
||||
"version": "0.9.35",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.9.34",
|
||||
"version": "0.9.35",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.9.34",
|
||||
"version": "0.9.35",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -1030,7 +1030,7 @@ describe('User Routes (/api/users)', () => {
|
||||
it('should upload an avatar and update the user profile', async () => {
|
||||
const mockUpdatedProfile = createMockUserProfile({
|
||||
...mockUserProfile,
|
||||
avatar_url: 'http://localhost:3001/uploads/avatars/new-avatar.png',
|
||||
avatar_url: 'http://example.com/uploads/avatars/new-avatar.png',
|
||||
});
|
||||
vi.mocked(userService.updateUserAvatar).mockResolvedValue(mockUpdatedProfile);
|
||||
|
||||
@@ -1042,7 +1042,7 @@ describe('User Routes (/api/users)', () => {
|
||||
.attach('avatar', Buffer.from('dummy-image-content'), dummyImagePath);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.avatar_url).toContain('http://localhost:3001/uploads/avatars/');
|
||||
expect(response.body.avatar_url).toContain('http://example.com/uploads/avatars/');
|
||||
expect(userService.updateUserAvatar).toHaveBeenCalledWith(
|
||||
mockUserProfile.user.user_id,
|
||||
expect.any(Object),
|
||||
@@ -1299,32 +1299,32 @@ describe('User Routes (/api/users)', () => {
|
||||
expect(response.headers).toHaveProperty('ratelimit-limit');
|
||||
expect(parseInt(response.headers['ratelimit-limit'])).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
vi.mocked(userService.deleteUserAccount).mockResolvedValue(undefined);
|
||||
const limit = 5;
|
||||
vi.mocked(userService.deleteUserAccount).mockResolvedValue(undefined);
|
||||
|
||||
// Consume the limit
|
||||
for (let i = 0; i < limit; i++) {
|
||||
// Consume the limit
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const response = await supertest(app)
|
||||
.delete('/api/users/account')
|
||||
.set('X-Test-Rate-Limit-Enable', 'true')
|
||||
.send({ password: 'correct-password' });
|
||||
expect(response.status).toBe(200);
|
||||
}
|
||||
|
||||
// Next request should be blocked
|
||||
const response = await supertest(app)
|
||||
.delete('/api/users/account')
|
||||
.set('X-Test-Rate-Limit-Enable', 'true')
|
||||
.send({ password: 'correct-password' });
|
||||
expect(response.status).toBe(200);
|
||||
}
|
||||
|
||||
// Next request should be blocked
|
||||
const response = await supertest(app)
|
||||
.delete('/api/users/account')
|
||||
.set('X-Test-Rate-Limit-Enable', 'true')
|
||||
.send({ password: 'correct-password' });
|
||||
|
||||
expect(response.status).toBe(429);
|
||||
expect(response.text).toContain('Too many sensitive requests');
|
||||
expect(response.status).toBe(429);
|
||||
expect(response.text).toContain('Too many sensitive requests');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,7 +116,7 @@ interface MockFlyer {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
const baseUrl = 'http://localhost:3001';
|
||||
const baseUrl = 'http://example.com';
|
||||
|
||||
describe('AI Service (Server)', () => {
|
||||
// Create mock dependencies that will be injected into the service
|
||||
@@ -1015,7 +1015,7 @@ describe('AI Service (Server)', () => {
|
||||
userId: 'user123',
|
||||
submitterIp: '127.0.0.1',
|
||||
userProfileAddress: '123 St, City, Country', // Partial address match based on filter(Boolean)
|
||||
baseUrl: 'http://localhost:3000',
|
||||
baseUrl: 'http://example.com',
|
||||
});
|
||||
expect(result.id).toBe('job123');
|
||||
});
|
||||
@@ -1037,7 +1037,7 @@ describe('AI Service (Server)', () => {
|
||||
expect.objectContaining({
|
||||
userId: undefined,
|
||||
userProfileAddress: undefined,
|
||||
baseUrl: 'http://localhost:3000',
|
||||
baseUrl: 'http://example.com',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('AuthService', () => {
|
||||
|
||||
// Set environment variables before any modules are imported
|
||||
vi.stubEnv('JWT_SECRET', 'test-secret');
|
||||
vi.stubEnv('FRONTEND_URL', 'http://localhost:3000');
|
||||
vi.stubEnv('FRONTEND_URL', 'http://example.com');
|
||||
|
||||
// Mock all dependencies before dynamically importing the service
|
||||
// Core modules like bcrypt, jsonwebtoken, and crypto are now mocked globally in tests-setup-unit.ts
|
||||
|
||||
@@ -132,8 +132,8 @@ describe('Flyer DB Service', () => {
|
||||
it('should execute an INSERT query and return the new flyer', async () => {
|
||||
const flyerData: FlyerDbInsert = {
|
||||
file_name: 'test.jpg',
|
||||
image_url: 'http://localhost:3001/images/test.jpg',
|
||||
icon_url: 'http://localhost:3001/images/icons/test.jpg',
|
||||
image_url: 'http://example.com/images/test.jpg',
|
||||
icon_url: 'http://example.com/images/icons/test.jpg',
|
||||
checksum: 'checksum123',
|
||||
store_id: 1,
|
||||
valid_from: '2024-01-01',
|
||||
@@ -155,8 +155,8 @@ describe('Flyer DB Service', () => {
|
||||
expect.stringContaining('INSERT INTO flyers'),
|
||||
[
|
||||
'test.jpg',
|
||||
'http://localhost:3001/images/test.jpg',
|
||||
'http://localhost:3001/images/icons/test.jpg',
|
||||
'http://example.com/images/test.jpg',
|
||||
'http://example.com/images/icons/test.jpg',
|
||||
'checksum123',
|
||||
1,
|
||||
'2024-01-01',
|
||||
|
||||
@@ -21,7 +21,7 @@ const createMockJobData = (data: Partial<FlyerJobData>): FlyerJobData => ({
|
||||
filePath: '/tmp/flyer.jpg',
|
||||
originalFileName: 'flyer.jpg',
|
||||
checksum: 'checksum-123',
|
||||
baseUrl: 'http://localhost:3000',
|
||||
baseUrl: 'http://example.com',
|
||||
...data,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ describe('FlyerDataTransformer', () => {
|
||||
transformer = new FlyerDataTransformer();
|
||||
// Stub environment variables to ensure consistency and predictability.
|
||||
// Prioritize FRONTEND_URL to match the updated service logic.
|
||||
vi.stubEnv('FRONTEND_URL', 'http://localhost:3000');
|
||||
vi.stubEnv('FRONTEND_URL', 'http://example.com');
|
||||
vi.stubEnv('BASE_URL', ''); // Ensure this is not used to confirm priority logic
|
||||
vi.stubEnv('PORT', ''); // Ensure this is not used
|
||||
|
||||
// Provide a default mock implementation for generateFlyerIcon
|
||||
vi.mocked(generateFlyerIcon).mockResolvedValue('icon-flyer-page-1.webp');
|
||||
vi.mocked(getBaseUrl).mockReturnValue('http://localhost:3000');
|
||||
vi.mocked(getBaseUrl).mockReturnValue('http://example.com');
|
||||
});
|
||||
|
||||
it('should transform AI data into database-ready format with a user ID', async () => {
|
||||
|
||||
@@ -189,7 +189,7 @@ describe('FlyerProcessingService', () => {
|
||||
filePath: '/tmp/flyer.jpg',
|
||||
originalFileName: 'flyer.jpg',
|
||||
checksum: 'checksum-123',
|
||||
baseUrl: 'http://localhost:3000',
|
||||
baseUrl: 'http://example.com',
|
||||
...data,
|
||||
},
|
||||
updateProgress: vi.fn(),
|
||||
@@ -241,7 +241,7 @@ describe('FlyerProcessingService', () => {
|
||||
'checksum-123', // checksum
|
||||
undefined, // userId
|
||||
expect.any(Object), // logger
|
||||
'http://localhost:3000', // baseUrl
|
||||
'http://example.com', // baseUrl
|
||||
);
|
||||
|
||||
// 5. DB transaction was initiated
|
||||
@@ -695,8 +695,8 @@ describe('FlyerProcessingService', () => {
|
||||
it('should derive paths from DB and delete files if job paths are empty', async () => {
|
||||
const job = createMockCleanupJob({ flyerId: 1, paths: [] }); // Empty paths
|
||||
const mockFlyer = createMockFlyer({
|
||||
image_url: 'http://localhost:3000/flyer-images/flyer-abc.jpg',
|
||||
icon_url: 'http://localhost:3000/flyer-images/icons/icon-flyer-abc.webp',
|
||||
image_url: 'http://example.com/flyer-images/flyer-abc.jpg',
|
||||
icon_url: 'http://example.com/flyer-images/icons/icon-flyer-abc.webp',
|
||||
});
|
||||
// Mock DB call to return a flyer
|
||||
vi.mocked(mockedDb.flyerRepo.getFlyerById).mockResolvedValue(mockFlyer);
|
||||
|
||||
@@ -241,7 +241,7 @@ describe('UserService', () => {
|
||||
describe('updateUserAvatar', () => {
|
||||
it('should construct avatar URL and update profile', async () => {
|
||||
const { logger } = await import('./logger.server');
|
||||
const testBaseUrl = getTestBaseUrl(3001);
|
||||
const testBaseUrl = getTestBaseUrl();
|
||||
vi.stubEnv('FRONTEND_URL', testBaseUrl);
|
||||
|
||||
const userId = 'user-123';
|
||||
|
||||
@@ -57,7 +57,7 @@ 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.
|
||||
vi.stubEnv('FRONTEND_URL', 'http://localhost:3000');
|
||||
vi.stubEnv('FRONTEND_URL', 'http://example.com');
|
||||
});
|
||||
|
||||
// FIX: Reset mocks before each test to ensure isolation.
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('Gamification Flow Integration Test', () => {
|
||||
|
||||
// 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', 'http://localhost:3001');
|
||||
vi.stubEnv('FRONTEND_URL', 'http://example.com');
|
||||
|
||||
// Setup default mock response for the AI service's extractCoreDataFromFlyerImage method.
|
||||
mockExtractCoreData.mockResolvedValue({
|
||||
@@ -253,7 +253,7 @@ describe('Gamification Flow Integration Test', () => {
|
||||
// 8. Assert that the URLs are fully qualified.
|
||||
expect(savedFlyer.image_url).to.equal(newFlyer.image_url);
|
||||
expect(savedFlyer.icon_url).to.equal(newFlyer.icon_url);
|
||||
const expectedBaseUrl = getTestBaseUrl(3001);
|
||||
const expectedBaseUrl = getTestBaseUrl();
|
||||
expect(newFlyer.image_url).toContain(`${expectedBaseUrl}/flyer-images/`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -178,7 +178,7 @@ export const createMockFlyer = (
|
||||
store_id: overrides.store_id ?? overrides.store?.store_id,
|
||||
});
|
||||
|
||||
const baseUrl = 'http://localhost:3001'; // A reasonable default for tests
|
||||
const baseUrl = 'http://example.com'; // A reasonable default for tests
|
||||
|
||||
// Determine the final file_name to generate dependent properties from.
|
||||
const fileName = overrides.file_name ?? `flyer-${flyerId}.jpg`;
|
||||
|
||||
@@ -7,8 +7,8 @@ import supertest from 'supertest';
|
||||
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}`;
|
||||
export const getTestBaseUrl = (): string => {
|
||||
const url = process.env.FRONTEND_URL || `http://example.com`;
|
||||
return url.endsWith('/') ? url.slice(0, -1) : url;
|
||||
};
|
||||
|
||||
|
||||
@@ -56,29 +56,21 @@ describe('serverUtils', () => {
|
||||
expect(mockLogger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fall back to localhost with default port 3000 if no URL is provided', () => {
|
||||
it('should fall back to example.com with default port 3000 if no URL is provided', () => {
|
||||
delete process.env.FRONTEND_URL;
|
||||
delete process.env.BASE_URL;
|
||||
delete process.env.PORT;
|
||||
const baseUrl = getBaseUrl(mockLogger);
|
||||
expect(baseUrl).toBe('http://localhost:3000');
|
||||
expect(baseUrl).toBe('http://example.com:3000');
|
||||
expect(mockLogger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fall back to localhost with the specified PORT if no URL is provided', () => {
|
||||
delete process.env.FRONTEND_URL;
|
||||
delete process.env.BASE_URL;
|
||||
process.env.PORT = '8888';
|
||||
const baseUrl = getBaseUrl(mockLogger);
|
||||
expect(baseUrl).toBe('http://localhost:8888');
|
||||
});
|
||||
|
||||
it('should log a warning and fall back if FRONTEND_URL is invalid (does not start with http)', () => {
|
||||
process.env.FRONTEND_URL = 'invalid.url.com';
|
||||
const baseUrl = getBaseUrl(mockLogger);
|
||||
expect(baseUrl).toBe('http://localhost:3000');
|
||||
expect(baseUrl).toBe('http://example.com');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith(
|
||||
"[getBaseUrl] FRONTEND_URL/BASE_URL is invalid or incomplete ('invalid.url.com'). Falling back to default local URL: http://localhost:3000",
|
||||
"[getBaseUrl] FRONTEND_URL/BASE_URL is invalid or incomplete ('invalid.url.com'). Falling back to default local URL: http://example.com",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export function getBaseUrl(logger: Logger): string {
|
||||
let baseUrl = (process.env.FRONTEND_URL || process.env.BASE_URL || '').trim();
|
||||
if (!baseUrl || !baseUrl.startsWith('http')) {
|
||||
const port = process.env.PORT || 3000;
|
||||
const fallbackUrl = `http://localhost:${port}`;
|
||||
const fallbackUrl = `http://example.com:${port}`;
|
||||
if (baseUrl) {
|
||||
logger.warn(
|
||||
`[getBaseUrl] FRONTEND_URL/BASE_URL is invalid or incomplete ('${baseUrl}'). Falling back to default local URL: ${fallbackUrl}`,
|
||||
|
||||
Reference in New Issue
Block a user