Compare commits

...

4 Commits

Author SHA1 Message Date
Gitea Actions
4a4f349805 ci: Bump version to 0.2.30 [skip ci] 2025-12-30 06:19:25 +05:00
517a268307 fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m5s
2025-12-29 17:18:52 -08:00
Gitea Actions
a94b2a97b1 ci: Bump version to 0.2.29 [skip ci] 2025-12-30 05:41:58 +05:00
542cdfbb82 fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m18s
2025-12-29 16:41:32 -08:00
7 changed files with 89 additions and 80 deletions

4
package-lock.json generated
View File

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

View File

@@ -12,11 +12,10 @@ vi.mock('util', async (importOriginal) => {
const actual = await importOriginal<typeof import('util')>();
return {
...actual,
default: actual,
promisify: (fn: Function) => {
return (...args: any[]) => {
return new Promise((resolve, reject) => {
fn(...args, (err: Error | null, stdout: unknown, stderr: unknown) => {
fn(...args, (err: Error | null, stdout: string, stderr: string) => {
if (err) {
// Attach stdout/stderr to the error object to mimic child_process.exec behavior
Object.assign(err, { stdout, stderr });
@@ -41,7 +40,6 @@ vi.mock('child_process', () => {
});
return {
default: { exec: mockExec },
exec: mockExec,
};
});

View File

@@ -127,12 +127,21 @@ describe('AnalyticsService', () => {
throw new Error('Processing failed');
}); // "Successfully generated..."
const promise = service.processWeeklyReportJob(job);
// Wrap the async operation that is expected to reject in a function.
// This prevents an "unhandled rejection" error by ensuring the `expect.rejects`
// is actively waiting for the promise to reject when the timers are advanced.
const testFunction = async () => {
const promise = service.processWeeklyReportJob(job);
// Advance timers to trigger the part of the code that throws.
await vi.advanceTimersByTimeAsync(30000);
// Await the promise to allow the rejection to be caught by `expect.rejects`.
await promise;
};
await vi.advanceTimersByTimeAsync(30000);
await expect(promise).rejects.toThrow('Processing failed');
// Now, assert that the entire operation rejects as expected.
await expect(testFunction()).rejects.toThrow('Processing failed');
// Verify the side effect (error logging) after the rejection is confirmed.
expect(mockLoggerInstance.error).toHaveBeenCalledWith(
expect.objectContaining({
err: expect.any(Error),

View File

@@ -1,70 +1,16 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Set environment variables before importing the service
process.env.JWT_SECRET = 'test-secret';
process.env.FRONTEND_URL = 'http://localhost:3000';
import { authService } from './authService';
import * as bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { userRepo, adminRepo } from './db/index.db';
import { UniqueConstraintError } from './db/errors.db';
import { logger } from './logger.server';
import { sendPasswordResetEmail } from './emailService.server';
import type { UserProfile } from '../types';
// Mock dependencies
vi.mock('bcrypt');
vi.mock('jsonwebtoken');
vi.mock('crypto', () => ({
default: {
randomBytes: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('mocked-random-string'),
}),
},
}));
vi.mock('./db/index.db', () => ({
userRepo: {
createUser: vi.fn(),
saveRefreshToken: vi.fn(),
findUserByEmail: vi.fn(),
createPasswordResetToken: vi.fn(),
getValidResetTokens: vi.fn(),
updateUserPassword: vi.fn(),
deleteResetToken: vi.fn(),
findUserByRefreshToken: vi.fn(),
findUserProfileById: vi.fn(),
deleteRefreshToken: vi.fn(),
},
adminRepo: {
logActivity: vi.fn(),
},
}));
vi.mock('./logger.server', () => ({
logger: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
},
}));
vi.mock('./emailService.server', () => ({
sendPasswordResetEmail: vi.fn(),
}));
vi.mock('./db/connection.db', () => ({
getPool: vi.fn(),
}));
vi.mock('../utils/authUtils', () => ({
validatePasswordStrength: vi.fn(),
}));
describe('AuthService', () => {
let authService: typeof import('./authService').authService;
let bcrypt: typeof import('bcrypt');
let jwt: typeof import('jsonwebtoken');
let userRepo: typeof import('./db/index.db').userRepo;
let adminRepo: typeof import('./db/index.db').adminRepo;
let logger: typeof import('./logger.server').logger;
let sendPasswordResetEmail: typeof import('./emailService.server').sendPasswordResetEmail;
const reqLog = {}; // Mock request logger object
const mockUser = {
user_id: 'user-123',
@@ -76,8 +22,59 @@ describe('AuthService', () => {
role: 'user',
} as unknown as UserProfile;
beforeEach(() => {
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules();
// Set environment variables before any modules are imported
process.env.JWT_SECRET = 'test-secret';
process.env.FRONTEND_URL = 'http://localhost:3000';
// Mock all dependencies before dynamically importing the service
vi.mock('bcrypt');
vi.mock('jsonwebtoken');
vi.mock('crypto', () => ({
default: {
randomBytes: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('mocked-random-string'),
}),
},
}));
vi.mock('./db/index.db', () => ({
userRepo: {
createUser: vi.fn(),
saveRefreshToken: vi.fn(),
findUserByEmail: vi.fn(),
createPasswordResetToken: vi.fn(),
getValidResetTokens: vi.fn(),
updateUserPassword: vi.fn(),
deleteResetToken: vi.fn(),
findUserByRefreshToken: vi.fn(),
findUserProfileById: vi.fn(),
deleteRefreshToken: vi.fn(),
},
adminRepo: {
logActivity: vi.fn(),
},
}));
vi.mock('./logger.server', () => ({
logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));
vi.mock('./emailService.server', () => ({
sendPasswordResetEmail: vi.fn(),
}));
vi.mock('./db/connection.db', () => ({ getPool: vi.fn() }));
vi.mock('../utils/authUtils', () => ({ validatePasswordStrength: vi.fn() }));
// Dynamically import modules to get the mocked versions and the service instance
authService = (await import('./authService')).authService;
bcrypt = await import('bcrypt');
jwt = await import('jsonwebtoken');
const dbModule = await import('./db/index.db');
userRepo = dbModule.userRepo;
adminRepo = dbModule.adminRepo;
logger = (await import('./logger.server')).logger;
sendPasswordResetEmail = (await import('./emailService.server')).sendPasswordResetEmail;
});
describe('registerUser', () => {
@@ -227,7 +224,7 @@ describe('AuthService', () => {
});
it('should log warning and return undefined for non-existent user', async () => {
vi.mocked(userRepo.findUserByEmail).mockResolvedValue(null);
vi.mocked(userRepo.findUserByEmail).mockResolvedValue(undefined);
const result = await authService.resetPassword('unknown@example.com', reqLog);
@@ -295,7 +292,7 @@ describe('AuthService', () => {
});
it('should return null if token not found', async () => {
vi.mocked(userRepo.findUserByRefreshToken).mockResolvedValue(null);
vi.mocked(userRepo.findUserByRefreshToken).mockResolvedValue(undefined);
const result = await authService.getUserByRefreshToken('invalid-token', reqLog);
@@ -330,7 +327,7 @@ describe('AuthService', () => {
});
it('should return null if user not found', async () => {
vi.mocked(userRepo.findUserByRefreshToken).mockResolvedValue(null);
vi.mocked(userRepo.findUserByRefreshToken).mockResolvedValue(undefined);
const result = await authService.refreshAccessToken('invalid-token', reqLog);
expect(result).toBeNull();
});

View File

@@ -242,7 +242,10 @@ export class FlyerProcessingService {
// Mark subsequent critical stages as skipped
for (let i = errorStageIndex + 1; i < stagesToReport.length; i++) {
if (stagesToReport[i].critical) {
stagesToReport[i] = { ...stagesToReport[i], status: 'skipped' };
// When a stage is skipped, we don't need its previous 'detail' property.
// This creates a clean 'skipped' state object by removing `detail` and keeping the rest.
const { detail, ...restOfStage } = stagesToReport[i];
stagesToReport[i] = { ...restOfStage, status: 'skipped' };
}
}
}

View File

@@ -1,3 +1,4 @@
// src/services/systemService.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { exec, type ExecException } from 'child_process';
import { logger } from './logger.server';
@@ -35,10 +36,11 @@ vi.mock('util', async (importOriginal) => {
});
// Mock child_process
vi.mock('child_process', () => {
const mockExec = vi.fn();
vi.mock('child_process', async (importOriginal) => {
const actual = await importOriginal<typeof import('child_process')>();
return {
exec: mockExec,
...actual,
exec: vi.fn(),
};
});