Compare commits

...

2 Commits

Author SHA1 Message Date
Gitea Actions
80a53fae94 ci: Bump version to 0.2.32 [skip ci] 2025-12-30 07:27:55 +05:00
e15d2b6c2f fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 12m4s
2025-12-29 18:27:30 -08:00
6 changed files with 81 additions and 93 deletions

4
package-lock.json generated
View File

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

View File

@@ -5,43 +5,6 @@ import { exec, type ExecException, type ExecOptions } from 'child_process';
import { geocodingService } from '../services/geocodingService.server';
import { createTestApp } from '../tests/utils/createTestApp';
// FIX: Mock util.promisify to correctly handle child_process.exec's (err, stdout, stderr) signature.
// This is required because the standard util.promisify relies on internal symbols on the real exec function,
// which are missing on our Vitest mock. Without this, promisify(mockExec) drops the stdout/stderr arguments.
vi.mock('util', async (importOriginal) => {
const actual = await importOriginal<typeof import('util')>();
return {
...actual,
promisify: (fn: Function) => {
return (...args: any[]) => {
return new Promise((resolve, reject) => {
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 });
reject(err);
} else {
resolve({ stdout, stderr });
}
});
});
};
},
};
});
// The `importOriginal` pattern is the robust way to mock built-in Node modules.
// It preserves the module's original structure, preventing "No default export" errors
// that can occur with simple factory mocks when using ESM-based test runners like Vitest.
vi.mock('child_process', async (importOriginal) => {
const actual = await importOriginal<typeof import('child_process')>();
return {
...actual,
// We provide a basic mock function that will be implemented in each test.
exec: vi.fn(),
};
});
// 2. Mock Geocoding
vi.mock('../services/geocodingService.server', () => ({
geocodingService: {

View File

@@ -1,10 +1,11 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { UserProfile } from '../types';
import type * as jsonwebtoken from 'jsonwebtoken';
describe('AuthService', () => {
let authService: typeof import('./authService').authService;
let bcrypt: typeof import('bcrypt');
let jwt: typeof import('jsonwebtoken');
let jwt: typeof jsonwebtoken & { default: typeof jsonwebtoken };
let userRepo: typeof import('./db/index.db').userRepo;
let adminRepo: typeof import('./db/index.db').adminRepo;
let logger: typeof import('./logger.server').logger;
@@ -31,18 +32,8 @@ describe('AuthService', () => {
process.env.FRONTEND_URL = 'http://localhost:3000';
// Mock all dependencies before dynamically importing the service
// Core modules like bcrypt, jsonwebtoken, and crypto are now mocked globally in tests-setup-unit.ts
vi.mock('bcrypt');
vi.mock('jsonwebtoken', () => ({
sign: vi.fn(),
verify: vi.fn(),
}));
vi.mock('crypto', () => ({
default: {
randomBytes: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('mocked-random-string'),
}),
},
}));
vi.mock('./db/index.db', () => ({
userRepo: {
createUser: vi.fn(),
@@ -72,7 +63,7 @@ describe('AuthService', () => {
// 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');
jwt = (await import('jsonwebtoken')) as typeof jwt;
const dbModule = await import('./db/index.db');
userRepo = dbModule.userRepo;
adminRepo = dbModule.adminRepo;
@@ -141,7 +132,10 @@ describe('AuthService', () => {
// Mock registerUser logic (since we can't easily spy on the same class instance method without prototype spying, we rely on the underlying calls)
vi.mocked(bcrypt.hash).mockImplementation(async () => 'hashed-password');
vi.mocked(userRepo.createUser).mockResolvedValue(mockUserProfile);
vi.mocked(jwt.sign).mockImplementation(() => 'access-token' as any);
// FIX: The global mock for jsonwebtoken provides a `default` export.
// The code under test (`authService`) uses `import jwt from 'jsonwebtoken'`, so it gets the default export.
// We must mock `jwt.default.sign` to affect the code under test.
vi.mocked(jwt.default.sign).mockImplementation(() => 'access-token');
const result = await authService.registerAndLoginUser(
'test@example.com',
@@ -166,11 +160,14 @@ describe('AuthService', () => {
describe('generateAuthTokens', () => {
it('should generate access and refresh tokens', () => {
vi.mocked(jwt.sign).mockImplementation(() => 'access-token' as any);
// FIX: The global mock for jsonwebtoken provides a `default` export.
// The code under test (`authService`) uses `import jwt from 'jsonwebtoken'`, so it gets the default export.
// We must mock `jwt.default.sign` to affect the code under test.
vi.mocked(jwt.default.sign).mockImplementation(() => 'access-token');
const result = authService.generateAuthTokens(mockUserProfile);
expect(jwt.sign).toHaveBeenCalledWith(
expect(vi.mocked(jwt.default.sign)).toHaveBeenCalledWith(
{
user_id: 'user-123',
email: 'test@example.com',
@@ -323,7 +320,10 @@ describe('AuthService', () => {
it('should return new access token if user found', async () => {
vi.mocked(userRepo.findUserByRefreshToken).mockResolvedValue({ user_id: 'user-123' } as any);
vi.mocked(userRepo.findUserProfileById).mockResolvedValue(mockUserProfile);
vi.mocked(jwt.sign).mockImplementation(() => 'new-access-token' as any);
// FIX: The global mock for jsonwebtoken provides a `default` export.
// The code under test (`authService`) uses `import jwt from 'jsonwebtoken'`, so it gets the default export.
// We must mock `jwt.default.sign` to affect the code under test.
vi.mocked(jwt.default.sign).mockImplementation(() => 'new-access-token');
const result = await authService.refreshAccessToken('valid-token', reqLog);

View File

@@ -12,42 +12,6 @@ vi.mock('./logger.server', () => ({
},
}));
// Mock util.promisify to handle child_process.exec signature
vi.mock('util', async (importOriginal) => {
const actual = await importOriginal<typeof import('util')>();
return {
...actual,
promisify: (fn: Function) => {
return (...args: any[]) => {
return new Promise((resolve, reject) => {
fn(...args, (err: Error | null, stdout: string, stderr: string) => {
if (err) {
// Attach stdout/stderr to error for the catch block in service
Object.assign(err, { stdout, stderr });
reject(err);
} else {
resolve({ stdout, stderr });
}
});
});
};
},
};
});
// Mock child_process
// Node.js built-in modules like 'child_process' are CommonJS modules.
// When mocked in an ESM context (like Vitest), they might sometimes
// be interpreted as having a default export if not explicitly handled.
// By providing `__esModule: true` and explicitly defining `exec`,
// we ensure Vitest correctly resolves the named import.
vi.mock('child_process', () => {
return {
__esModule: true, // Explicitly mark as an ES module
exec: vi.fn(),
};
});
// Import service AFTER mocks to ensure top-level promisify uses the mock
import { systemService } from './systemService';

View File

@@ -116,6 +116,67 @@ afterEach(cleanup);
// By placing mocks here, they are guaranteed to be hoisted and applied
// before any test files are executed, preventing initialization errors.
// --- Centralized Core Node/NPM Module Mocks ---
// Mock 'util' to correctly handle the (err, stdout, stderr) signature of child_process.exec
// 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 {
...actual,
promisify: (fn: Function) => {
return (...args: any[]) => {
return new Promise((resolve, reject) => {
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 });
reject(err);
} else {
resolve({ stdout, stderr });
}
});
});
};
},
};
});
// Mock 'child_process' using the robust `importOriginal` pattern.
// This preserves the module's structure and prevents "No default export" errors.
vi.mock('child_process', async (importOriginal) => {
const actual = await importOriginal<typeof import('child_process')>();
return {
...actual,
// Provide a mock function for `exec` that can be implemented per-test.
exec: vi.fn(),
};
});
// Mock 'jsonwebtoken'. The `default` key is crucial because the code under test
// uses `import jwt from 'jsonwebtoken'`, which imports the default export.
vi.mock('jsonwebtoken', () => ({
default: {
sign: vi.fn(),
verify: vi.fn(),
},
// Also mock named exports for completeness.
sign: vi.fn(),
verify: vi.fn(),
}));
// Mock 'bcrypt'. The service uses `import * as bcrypt from 'bcrypt'`.
vi.mock('bcrypt');
// Mock 'crypto'. The service uses `import crypto from 'crypto'`.
vi.mock('crypto', () => ({
default: {
randomBytes: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('mocked-random-string'),
}),
},
}));
// --- Global Mocks ---
// 1. Define the mock pool instance logic OUTSIDE the factory so it can be used