Skip to content

Commit ec9e043

Browse files
Merge pull request #62 from AdityaDRathore/dev
Dev
2 parents e46412c + c0bcd44 commit ec9e043

17 files changed

Lines changed: 752 additions & 88 deletions

backend/jest.config.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@ module.exports = {
33
testEnvironment: 'node',
44
roots: ['<rootDir>/src'],
55
transform: {
6-
'^.+\\.tsx?$': 'ts-jest',
6+
'^.+\\.tsx?$': ['ts-jest', {
7+
tsconfig: 'tsconfig.json',
8+
}],
79
},
810
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
911
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
1012
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
11-
globals: {
12-
'ts-jest': {
13-
tsconfig: 'tsconfig.json',
14-
},
15-
},
1613
};

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"eslint-config-prettier": "^9.0.0",
5252
"eslint-plugin-prettier": "^5.1.3",
5353
"jest": "^29.7.0",
54+
"jest-mock-extended": "^4.0.0-beta1",
5455
"prettier": "^3.2.5",
5556
"prisma": "^6.8.2",
5657
"ts-jest": "^29.1.4",

backend/src/config/environment.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@ dotenv.config();
1010
const envSchema = z.object({
1111
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
1212
PORT: z.string().default('4000'),
13-
DATABASE_URL: z.string(),
14-
JWT_SECRET: z.string(),
13+
DATABASE_URL:
14+
process.env.NODE_ENV === 'test'
15+
? z
16+
.string()
17+
.optional()
18+
.default('postgresql://postgres:password@localhost:5432/test_db_schema_default')
19+
: z.string(),
20+
JWT_SECRET:
21+
process.env.NODE_ENV === 'test'
22+
? z.string().optional().default('test_jwt_secret_schema_default')
23+
: z.string(),
1524
JWT_EXPIRES_IN: z.string().default('15m'),
16-
REFRESH_TOKEN_SECRET: z.string(),
25+
REFRESH_TOKEN_SECRET:
26+
process.env.NODE_ENV === 'test'
27+
? z.string().optional().default('test_refresh_secret_schema_default')
28+
: z.string(),
1729
REFRESH_TOKEN_EXPIRES_IN: z.string().default('7d'),
1830
REDIS_URL: z.string().optional(),
1931
CORS_ORIGIN: z.string().default('*'),
@@ -24,8 +36,28 @@ const envSchema = z.object({
2436
const env = envSchema.safeParse(process.env);
2537

2638
if (!env.success) {
27-
console.error('❌ Invalid environment variables:', JSON.stringify(env.error.format(), null, 4));
28-
process.exit(1);
39+
if (process.env.NODE_ENV === 'test') {
40+
console.warn('⚠️ Running with incomplete environment in test mode, using fallbacks.');
41+
} else {
42+
console.error('❌ Invalid environment variables:', JSON.stringify(env.error.format(), null, 4));
43+
process.exit(1);
44+
}
2945
}
3046

31-
export const config = env.data;
47+
// Export validated config or fallbacks for test environment
48+
export const config = env.success
49+
? env.data
50+
: {
51+
NODE_ENV: process.env.NODE_ENV ?? 'test',
52+
PORT: process.env.PORT ?? '4000',
53+
LOG_LEVEL: (process.env.LOG_LEVEL as z.infer<typeof envSchema.shape.LOG_LEVEL>) ?? 'error',
54+
DATABASE_URL:
55+
process.env.DATABASE_URL ??
56+
'postgresql://postgres:password@localhost:5432/test_db_fallback',
57+
JWT_SECRET: process.env.JWT_SECRET ?? 'test_jwt_secret_fallback',
58+
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN ?? '15m',
59+
REFRESH_TOKEN_SECRET: process.env.REFRESH_TOKEN_SECRET ?? 'test_refresh_secret_fallback',
60+
REFRESH_TOKEN_EXPIRES_IN: process.env.REFRESH_TOKEN_EXPIRES_IN ?? '7d',
61+
REDIS_URL: process.env.REDIS_URL ?? undefined, // Ensure REDIS_URL is in the fallback
62+
CORS_ORIGIN: process.env.CORS_ORIGIN ?? '*',
63+
};
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Prisma } from '@prisma/client';
2+
import logger from '../../utils/logger';
3+
import { createMockPrismaClient } from '../prisma-mock';
4+
import { mockDeep } from 'jest-mock-extended';
5+
6+
// Replace jest-fail-fast which can't be found
7+
const fail = (message: string): never => {
8+
throw new Error(message);
9+
};
10+
11+
describe('Database Connection', () => {
12+
let prisma: ReturnType<typeof createMockPrismaClient>;
13+
14+
beforeEach(() => {
15+
prisma = createMockPrismaClient();
16+
});
17+
18+
it('should connect to the database successfully', async () => {
19+
// Mock the query response
20+
prisma.$queryRaw.mockResolvedValue([{ result: 1 }]);
21+
22+
const result = await prisma.$queryRaw`SELECT 1 as result`;
23+
expect(result).toBeDefined();
24+
expect(result).toEqual([{ result: 1 }]);
25+
});
26+
27+
it('should handle connection pool correctly', async () => {
28+
// Mock multiple parallel queries
29+
for (let i = 0; i < 5; i++) {
30+
prisma.$queryRaw.mockResolvedValueOnce([{ value: Math.random() }]);
31+
}
32+
33+
const promises = Array(5)
34+
.fill(0)
35+
.map(() => prisma.$queryRaw`SELECT random() as value`);
36+
37+
const results = await Promise.all(promises);
38+
expect(results.length).toBe(5);
39+
expect(results.every(r => Array.isArray(r) && r.length === 1)).toBe(true);
40+
});
41+
42+
it('should handle transaction rollback correctly', async () => {
43+
// Mock successful transaction
44+
const mockTx = mockDeep<Prisma.TransactionClient>();
45+
46+
// First mock a successful query
47+
mockTx.$executeRaw.mockResolvedValueOnce(1);
48+
49+
// Then mock a failing query
50+
const duplicateKeyError = new Error('Duplicate key value');
51+
mockTx.$executeRaw.mockRejectedValueOnce(duplicateKeyError);
52+
53+
// Fix the callback type to match Prisma's $transaction overloads
54+
// Define transaction related types
55+
type TransactionCallback<T> = (tx: Prisma.TransactionClient) => Promise<T>;
56+
type TransactionQueries<T> = Array<Promise<T>>;
57+
58+
prisma.$transaction.mockImplementation(
59+
<T>(
60+
callbackOrQueries: TransactionCallback<T> | TransactionQueries<T>,
61+
): Promise<T | Array<T>> => {
62+
if (typeof callbackOrQueries === 'function') {
63+
return callbackOrQueries(mockTx).catch(error => {
64+
throw error instanceof Error ? error : new Error(String(error));
65+
});
66+
}
67+
// Handle the array of queries case
68+
return Promise.all(callbackOrQueries);
69+
},
70+
);
71+
72+
try {
73+
await prisma.$transaction(async tx => {
74+
// This should succeed
75+
await tx.$executeRaw`CREATE TEMPORARY TABLE test_table (id SERIAL PRIMARY KEY)`;
76+
// This will deliberately fail
77+
await tx.$executeRaw`INSERT INTO test_table (id) VALUES (1), (1)`;
78+
});
79+
fail('Transaction should have failed');
80+
} catch (error: unknown) {
81+
expect(error).toBeDefined();
82+
expect(error).toEqual(duplicateKeyError);
83+
}
84+
});
85+
86+
it('should handle connection errors gracefully', async () => {
87+
// Instead of creating a real bad client, we'll mock a connection error
88+
// Fix constructor arguments to match expected signature
89+
const mockError = new Prisma.PrismaClientInitializationError(
90+
"Can't reach database server",
91+
'4.5.0',
92+
);
93+
94+
// No need to set clientVersion property as it's now provided in the constructor
95+
96+
prisma.$queryRaw.mockRejectedValueOnce(mockError);
97+
98+
try {
99+
await prisma.$queryRaw`SELECT 1`;
100+
fail('Query should have failed with database connection error');
101+
} catch (error: unknown) {
102+
// Add proper error handling logic to satisfy SonarQube
103+
expect(error).toBeDefined();
104+
expect(error).toBe(mockError);
105+
106+
if (error instanceof Prisma.PrismaClientInitializationError) {
107+
expect(error.message).toContain("Can't reach database server");
108+
logger.error(`Database connection error: ${error.message}`);
109+
} else {
110+
fail('Error should be a PrismaClientInitializationError');
111+
}
112+
}
113+
});
114+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Prisma } from '@prisma/client';
2+
import { withErrorHandling, handlePrismaError } from '../../utils/db-errors';
3+
import { AppError } from '../../utils/errors';
4+
import { createMockPrismaClient } from '../prisma-mock';
5+
6+
describe('Database Error Handling', () => {
7+
let prisma: ReturnType<typeof createMockPrismaClient>;
8+
9+
beforeEach(() => {
10+
prisma = createMockPrismaClient();
11+
});
12+
13+
it('should handle record not found errors', async () => {
14+
// Mock a record not found error
15+
const notFoundError = new Prisma.PrismaClientKnownRequestError('Record not found', {
16+
code: 'P2001',
17+
clientVersion: '4.5.0',
18+
});
19+
20+
prisma.user.findUniqueOrThrow.mockRejectedValueOnce(notFoundError);
21+
22+
try {
23+
await withErrorHandling(async () => {
24+
return await prisma.user.findUniqueOrThrow({
25+
where: { id: 'non-existent-id' },
26+
});
27+
});
28+
fail('Should have thrown an error');
29+
} catch (error) {
30+
expect(error).toBeInstanceOf(AppError);
31+
expect((error as AppError).statusCode).toBe(404);
32+
}
33+
});
34+
35+
it('should handle unique constraint errors', async () => {
36+
// Create a proper PrismaClientKnownRequestError for unique constraint
37+
const uniqueError = new Prisma.PrismaClientKnownRequestError(
38+
'Unique constraint failed on the fields: (`user_email`)',
39+
{
40+
code: 'P2002',
41+
clientVersion: '4.5.0',
42+
meta: { target: ['user_email'] },
43+
},
44+
);
45+
46+
const error = handlePrismaError(uniqueError);
47+
expect(error.statusCode).toBe(409); // Conflict
48+
expect(error.message).toContain('already exists');
49+
});
50+
51+
it('should pass through successful operations', async () => {
52+
const result = await withErrorHandling(async () => {
53+
return 'success';
54+
});
55+
56+
expect(result).toBe('success');
57+
});
58+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { createPerformanceTest, DatabasePerformance } from '../../utils/db-performance';
2+
import fs from 'fs';
3+
import { createMockPrismaClient } from '../prisma-mock';
4+
5+
// Mock logger to avoid file system operations
6+
jest.mock('../../utils/logger', () => ({
7+
info: jest.fn(),
8+
error: jest.fn(),
9+
warn: jest.fn(),
10+
debug: jest.fn(),
11+
}));
12+
13+
// More complete fs mock including stat function needed by Winston
14+
jest.mock('fs', () => ({
15+
existsSync: jest.fn().mockReturnValue(true),
16+
mkdirSync: jest.fn(),
17+
writeFileSync: jest.fn(),
18+
stat: jest.fn().mockImplementation((path, callback) => {
19+
callback(null, { isDirectory: () => true });
20+
}),
21+
createWriteStream: jest.fn().mockReturnValue({
22+
on: jest.fn(),
23+
write: jest.fn(),
24+
end: jest.fn(),
25+
once: jest.fn(),
26+
}),
27+
}));
28+
29+
describe('Database Performance Baseline', () => {
30+
let prisma: ReturnType<typeof createMockPrismaClient>;
31+
let performanceMonitor: DatabasePerformance;
32+
33+
beforeEach(() => {
34+
prisma = createMockPrismaClient();
35+
performanceMonitor = new DatabasePerformance(prisma, 'test');
36+
});
37+
38+
it('should establish performance baseline for key queries', async () => {
39+
// Mock necessary Prisma methods
40+
prisma.user.findMany.mockResolvedValue([]);
41+
prisma.lab.findFirst.mockResolvedValue(null);
42+
43+
// Mock fs methods
44+
(fs.existsSync as jest.Mock).mockReturnValue(false);
45+
46+
const report = await createPerformanceTest(prisma);
47+
48+
// Basic assertions to ensure the test ran
49+
expect(report.queries.length).toBeGreaterThan(0);
50+
expect(report.summary.totalQueries).toBeGreaterThan(0);
51+
});
52+
53+
// Add tests for specific high-impact queries
54+
it('should measure lab booking query performance', async () => {
55+
prisma.lab.findMany.mockResolvedValue([]);
56+
57+
const result = await performanceMonitor.measureQuery('Find available labs', async () => {
58+
return prisma.lab.findMany({
59+
where: { status: 'ACTIVE' },
60+
include: {
61+
timeSlots: {
62+
where: {
63+
bookings: {
64+
some: {
65+
booking_status: 'CONFIRMED',
66+
},
67+
},
68+
},
69+
},
70+
},
71+
});
72+
});
73+
74+
expect(result).toBeDefined();
75+
});
76+
});
File renamed without changes.

backend/src/tests/db-connection.test.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

backend/src/tests/prisma-mock.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { PrismaClient } from '@prisma/client';
2+
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
3+
4+
// Create a mock instance of the Prisma client
5+
export const prisma = mockDeep<PrismaClient>();
6+
7+
// Factory for creating a fresh mock for each test
8+
export const createMockPrismaClient = (): DeepMockProxy<PrismaClient> => {
9+
mockReset(prisma);
10+
return prisma;
11+
};

0 commit comments

Comments
 (0)