Compare commits

...

9 Commits

Author SHA1 Message Date
Gitea Actions
43491359d9 ci: Bump version to 0.2.37 [skip ci] 2025-12-30 10:28:29 +05:00
5ed2cea7e9 /coverage
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m0s
2025-12-29 21:27:28 -08:00
Gitea Actions
cbb16a8d52 ci: Bump version to 0.2.36 [skip ci] 2025-12-30 09:27:29 +05:00
70e94a6ce0 fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m5s
2025-12-29 20:27:00 -08:00
Gitea Actions
b61a00003a ci: Bump version to 0.2.35 [skip ci] 2025-12-30 09:16:46 +05:00
52dba6f890 moar!
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
2025-12-29 20:16:02 -08:00
4242678aab fix unit tests 2025-12-29 20:08:01 -08:00
Gitea Actions
b2e086d5ba ci: Bump version to 0.2.34 [skip ci] 2025-12-30 08:44:55 +05:00
07a9787570 fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m5s
2025-12-29 19:44:25 -08:00
10 changed files with 71 additions and 58 deletions

View File

@@ -151,6 +151,9 @@ jobs:
--coverage.exclude='src/db/**' \
--coverage.exclude='src/lib/**' \
--coverage.exclude='src/types/**' \
--coverage.exclude='**/index.tsx' \
--coverage.exclude='**/vite-env.d.ts' \
--coverage.exclude='**/vitest.setup.ts' \
--reporter=verbose --includeTaskLocation --testTimeout=10000 --silent=passed-only --no-file-parallelism || true
echo "--- Running Integration Tests ---"
@@ -162,6 +165,9 @@ jobs:
--coverage.exclude='src/db/**' \
--coverage.exclude='src/lib/**' \
--coverage.exclude='src/types/**' \
--coverage.exclude='**/index.tsx' \
--coverage.exclude='**/vite-env.d.ts' \
--coverage.exclude='**/vitest.setup.ts' \
--reporter=verbose --includeTaskLocation --testTimeout=10000 --silent=passed-only || true
echo "--- Running E2E Tests ---"
@@ -175,6 +181,9 @@ jobs:
--coverage.exclude='src/db/**' \
--coverage.exclude='src/lib/**' \
--coverage.exclude='src/types/**' \
--coverage.exclude='**/index.tsx' \
--coverage.exclude='**/vite-env.d.ts' \
--coverage.exclude='**/vitest.setup.ts' \
--reporter=verbose --no-file-parallelism || true
# Re-enable secret masking for subsequent steps.
@@ -246,7 +255,10 @@ jobs:
--temp-dir "$NYC_SOURCE_DIR" \
--exclude "**/*.test.ts" \
--exclude "**/tests/**" \
--exclude "**/mocks/**"
--exclude "**/mocks/**" \
--exclude "**/index.tsx" \
--exclude "**/vite-env.d.ts" \
--exclude "**/vitest.setup.ts"
# Re-enable secret masking for subsequent steps.
echo "::secret-masking::"
@@ -259,16 +271,6 @@ jobs:
if: always() # This step runs even if the previous test or coverage steps failed.
run: echo "Skipping test artifact cleanup on runner; this is handled on the server."
- name: Deploy Coverage Report to Public URL
if: always()
run: |
TARGET_DIR="/var/www/flyer-crawler-test.projectium.com/coverage"
echo "Deploying HTML coverage report to $TARGET_DIR..."
mkdir -p "$TARGET_DIR"
rm -rf "$TARGET_DIR"/*
cp -r .coverage/* "$TARGET_DIR/"
echo "✅ Coverage report deployed to https://flyer-crawler-test.projectium.com/coverage"
- name: Archive Code Coverage Report
# This action saves the generated HTML coverage report as a downloadable artifact.
uses: actions/upload-artifact@v3
@@ -358,6 +360,17 @@ jobs:
rsync -avz dist/ "$APP_PATH"
echo "Application deployment complete."
- name: Deploy Coverage Report to Public URL
if: always()
run: |
TARGET_DIR="/var/www/flyer-crawler-test.projectium.com/coverage"
echo "Deploying HTML coverage report to $TARGET_DIR..."
mkdir -p "$TARGET_DIR"
rm -rf "$TARGET_DIR"/*
# The merged nyc report is generated in the .coverage directory. We copy its contents.
cp -r .coverage/* "$TARGET_DIR/"
echo "✅ Coverage report deployed to https://flyer-crawler-test.projectium.com/coverage"
- name: Install Backend Dependencies and Restart Test Server
env:
# --- Test Secrets Injection ---

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "flyer-crawler",
"version": "0.2.33",
"version": "0.2.37",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flyer-crawler",
"version": "0.2.33",
"version": "0.2.37",
"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.2.33",
"version": "0.2.37",
"type": "module",
"scripts": {
"dev": "concurrently \"npm:start:dev\" \"vite\"",

View File

@@ -113,13 +113,14 @@ describe('errorHandler Middleware', () => {
expect(response.body.message).toBe('A generic server error occurred.');
expect(response.body.stack).toBeDefined();
expect(response.body.errorId).toEqual(expect.any(String));
console.log('[DEBUG] errorHandler.test.ts: Received 500 error response with ID:', response.body.errorId);
expect(mockLogger.error).toHaveBeenCalledWith(
expect.objectContaining({
err: expect.any(Error),
errorId: expect.any(String),
req: expect.objectContaining({ method: 'GET', url: '/generic-error' }),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringMatching(/--- \[TEST\] UNHANDLED ERROR \(ID: \w+\) ---/),
@@ -226,7 +227,7 @@ describe('errorHandler Middleware', () => {
errorId: expect.any(String),
req: expect.objectContaining({ method: 'GET', url: '/db-error-500' }),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringMatching(/--- \[TEST\] UNHANDLED ERROR \(ID: \w+\) ---/),

View File

@@ -164,11 +164,12 @@ describe('Health Routes (/api/health)', () => {
expect(response.body.message).toBe('DB connection failed'); // This is the message from the original error
expect(response.body.stack).toBeDefined();
expect(response.body.errorId).toEqual(expect.any(String));
console.log('[DEBUG] health.routes.test.ts: Verifying logger.error for DB schema check failure');
expect(mockLogger.error).toHaveBeenCalledWith(
expect.objectContaining({
err: expect.any(Error),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
@@ -186,7 +187,7 @@ describe('Health Routes (/api/health)', () => {
expect.objectContaining({
err: expect.objectContaining({ message: 'DB connection failed' }),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
});
@@ -220,7 +221,7 @@ describe('Health Routes (/api/health)', () => {
expect.objectContaining({
err: expect.any(Error),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
@@ -239,7 +240,7 @@ describe('Health Routes (/api/health)', () => {
expect.objectContaining({
err: expect.any(Error),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
});
@@ -300,7 +301,7 @@ describe('Health Routes (/api/health)', () => {
expect.objectContaining({
err: expect.any(Error),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
@@ -321,7 +322,7 @@ describe('Health Routes (/api/health)', () => {
expect.objectContaining({
err: expect.objectContaining({ message: 'Pool is not initialized' }),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
@@ -336,11 +337,12 @@ describe('Health Routes (/api/health)', () => {
expect(response.body.message).toBe('Connection timed out');
expect(response.body.stack).toBeDefined();
expect(response.body.errorId).toEqual(expect.any(String));
console.log('[DEBUG] health.routes.test.ts: Checking if logger.error was called with the correct pattern');
expect(mockLogger.error).toHaveBeenCalledWith(
expect.objectContaining({
err: expect.any(Error),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
@@ -357,7 +359,7 @@ describe('Health Routes (/api/health)', () => {
expect.objectContaining({
err: expect.any(Error),
}),
expect.stringMatching(/Unhandled API Error \(ID: \w+\)/),
expect.stringMatching(/Unhandled API Error \(ID: [\w-]+\)/),
);
});
});

View File

@@ -1,3 +1,4 @@
// src/services/analyticsService.server.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { AnalyticsService } from './analyticsService.server';
import { logger } from './logger.server';
@@ -130,13 +131,14 @@ describe('AnalyticsService', () => {
// Get the promise from the service method.
const promise = service.processWeeklyReportJob(job);
// Capture the expectation promise BEFORE triggering the rejection.
const expectation = expect(promise).rejects.toThrow('Processing failed');
// Advance timers to trigger the part of the code that throws.
await vi.advanceTimersByTimeAsync(30000);
// Now, assert that the promise rejects as expected.
// This structure avoids an unhandled promise rejection that can occur
// when awaiting a rejecting promise inside a helper function without a try/catch.
await expect(promise).rejects.toThrow('Processing failed');
// Await the expectation to ensure assertions ran.
await expectation;
// Verify the side effect (error logging) after the rejection is confirmed.
expect(mockLoggerInstance.error).toHaveBeenCalledWith(

View File

@@ -148,11 +148,11 @@ describe('AuthService', () => {
expect(result).toEqual({
newUserProfile: mockUserProfile,
accessToken: 'access-token',
refreshToken: 'mocked-random-string',
refreshToken: 'mocked_random_id',
});
expect(userRepo.saveRefreshToken).toHaveBeenCalledWith(
'user-123',
'mocked-random-string',
'mocked_random_id',
reqLog,
);
});
@@ -178,7 +178,7 @@ describe('AuthService', () => {
);
expect(result).toEqual({
accessToken: 'access-token',
refreshToken: 'mocked-random-string',
refreshToken: 'mocked_random_id',
});
});
});
@@ -218,10 +218,10 @@ describe('AuthService', () => {
);
expect(sendPasswordResetEmail).toHaveBeenCalledWith(
'test@example.com',
expect.stringContaining('/reset-password/mocked-random-string'),
expect.stringContaining('/reset-password/mocked_random_id'),
reqLog,
);
expect(result).toBe('mocked-random-string');
expect(result).toBe('mocked_random_id');
});
it('should log warning and return undefined for non-existent user', async () => {

View File

@@ -190,7 +190,10 @@ describe('Worker Service Lifecycle', () => {
});
afterEach(() => {
processExitSpy.mockRestore();
if (processExitSpy && typeof processExitSpy.mockRestore === 'function') {
console.log('[DEBUG] queueService.server.test.ts: Restoring process.exit spy');
processExitSpy.mockRestore();
}
});
it('should close all workers, queues, the redis connection, and exit the process', async () => {

View File

@@ -29,13 +29,10 @@ describe('SystemService', () => {
describe('getPm2Status', () => {
it('should return success: true when process is online', async () => {
const stdout = `
┌────┬──────────────────────┬──────────┐
│ id │ name │ status
├────┼──────────────────────┼──────────┤
│ 0 │ flyer-crawler-api │ online │
└────┴──────────────────────┴──────────┘
`;
// This stdout mimics the output of `pm2 describe <app_name>`
const stdout = `Describing process with id 0 - name flyer-crawler-api
│ status │ online
│ name │ flyer-crawler-api │`;
mockExecAsync.mockResolvedValue({ stdout, stderr: '' });
const result = await systemService.getPm2Status();
@@ -47,13 +44,9 @@ describe('SystemService', () => {
});
it('should return success: false when process is stopped', async () => {
const stdout = `
┌────┬──────────────────────┬──────────┐
│ id │ name │ status │
├────┼──────────────────────┼──────────┤
│ 0 │ flyer-crawler-api │ stopped │
└────┴──────────────────────┴──────────┘
`;
const stdout = `Describing process with id 0 - name flyer-crawler-api
│ status │ stopped │
│ name │ flyer-crawler-api │`;
mockExecAsync.mockResolvedValue({ stdout, stderr: '' });
const result = await systemService.getPm2Status();

View File

@@ -122,7 +122,7 @@ afterEach(cleanup);
// when it's promisified. The standard util.promisify doesn't work on a simple vi.fn() mock.
vi.mock('util', async (importOriginal) => {
const actual = await importOriginal<typeof import('util')>();
return {
const mocked = {
...actual,
promisify: (fn: Function) => {
return (...args: any[]) => {
@@ -140,14 +140,9 @@ vi.mock('util', async (importOriginal) => {
};
},
};
});
// Mock 'child_process' using the robust `importOriginal` pattern.
// This is needed because some services (systemService, workers.server) import it at the top level.
vi.mock('child_process', () => {
return {
__esModule: true, // Handle CJS/ESM interop
exec: vi.fn(),
...mocked,
default: mocked,
};
});
@@ -170,9 +165,13 @@ vi.mock('bcrypt');
vi.mock('crypto', () => ({
default: {
randomBytes: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('mocked-random-string'),
toString: vi.fn().mockImplementation((encoding) => {
const id = 'mocked_random_id';
console.log(`[DEBUG] tests-setup-unit.ts: crypto.randomBytes mock returning "${id}" for encoding "${encoding}"`);
return id;
}),
}),
randomUUID: vi.fn().mockReturnValue('mocked-random-string'),
randomUUID: vi.fn().mockReturnValue('mocked_random_id'),
},
}));