Skip to content

Commit 4a75fa6

Browse files
committed
fixed test cases
1 parent 094671b commit 4a75fa6

File tree

5 files changed

+137
-78
lines changed

5 files changed

+137
-78
lines changed

question-service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"start": "node dist/index.js",
88
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
99
"build": "tsc",
10-
"test": "jest --runInBand --verbose",
10+
"test": "dotenv -e .env.test -- jest --runInBand --verbose",
1111
"test:watch": "jest --watch",
1212
"test:coverage": "jest --coverage"
1313
},

question-service/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express = require('express');
22
import { Request, Response, NextFunction } from 'express';
3-
import * as cors from 'cors';
3+
import cors = require('cors');
44
import helmet from 'helmet';
55
import questionRoutes from './routes/questionRoutes';
66
import { errorHandler, notFoundHandler } from './middleware/errorHandler';
Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,117 @@
1-
import * as request from 'supertest';
1+
import request from 'supertest';
22
import app from '../index';
3-
import { pool, closePool } from '../config/database'; // Import pool for direct checks if needed
4-
5-
// Close the database pool after all integration tests have run
6-
afterAll(async () => {
7-
await closePool();
8-
});
3+
import { closePool } from '../config/database';
94

105
describe('Question Service API (Integration)', () => {
11-
12-
// Test the health check endpoint
13-
it('health check should return status and timestamp', async () => {
14-
const res = await request(app).get('/health');
15-
expect([200, 503]).toContain(res.statusCode);
16-
expect(res.body).toHaveProperty('status');
17-
expect(res.body).toHaveProperty('database');
18-
expect(res.body).toHaveProperty('timestamp');
6+
afterAll(async () => {
7+
await closePool();
198
});
209

21-
// Test the GET /api/topics endpoint
22-
it('GET /api/topics should return an array or an error status', async () => {
23-
const res = await request(app).get('/api/topics');
24-
expect([200, 500]).toContain(res.statusCode); // Expect 200 or 500 (if DB not ready)
25-
if (res.statusCode === 200) {
26-
expect(Array.isArray(res.body)).toBe(true);
27-
}
10+
describe('Health Check', () => {
11+
it('should return status and timestamp', async () => {
12+
const res = await request(app).get('/health');
13+
expect([200, 503]).toContain(res.statusCode);
14+
expect(res.body).toHaveProperty('status');
15+
expect(res.body).toHaveProperty('database');
16+
expect(res.body).toHaveProperty('timestamp');
17+
});
2818
});
2919

30-
// Test the main POST /api/questions/select endpoint
31-
it('POST /api/questions/select should accept criteria and excludedIds', async () => {
32-
const payload = {
33-
criteria: { topic: 'Array', difficulty: 'Easy' }, // Make sure 'Array' exists in your test DB
34-
excludedIds: ['00000000-0000-0000-0000-000000000000'] // A fake UUID
35-
};
20+
describe('Questions API', () => {
21+
describe('GET /api/questions', () => {
22+
it('should return all questions', async () => {
23+
const res = await request(app).get('/api/questions');
24+
expect(res.statusCode).toBe(200);
25+
expect(Array.isArray(res.body)).toBe(true);
26+
});
27+
});
3628

37-
const res = await request(app)
38-
.post('/api/questions/select')
39-
.send(payload)
40-
.set('Accept', 'application/json');
29+
describe('GET /api/questions/:id', () => {
30+
it('should return question by id if exists', async () => {
31+
// First get all questions to get a valid ID
32+
const allQuestionsRes = await request(app).get('/api/questions');
33+
if (allQuestionsRes.body.length > 0) {
34+
const questionId = allQuestionsRes.body[0].question_id;
35+
const res = await request(app).get(`/api/questions/${questionId}`);
36+
expect(res.statusCode).toBe(200);
37+
expect(res.body).toHaveProperty('question_id', questionId);
38+
}
39+
});
4140

42-
// Depending on seeded data, this may return 200 with a question or 404 if none found.
43-
// 400 is also possible if criteria are bad. 500 if the DB connection fails.
44-
expect([200, 400, 404, 500]).toContain(res.statusCode);
41+
it('should return 404 for non-existent question', async () => {
42+
const res = await request(app).get('/api/questions/652c60dc-f518-4a7c-9c3c-bab6bf4b6cc0');
43+
expect(res.statusCode).toBe(404);
44+
});
45+
});
4546

46-
if (res.statusCode === 200) {
47-
expect(res.body).toHaveProperty('question_id');
48-
expect(res.body).toHaveProperty('title');
49-
expect(res.body).toHaveProperty('difficulty');
50-
// We know it's not the one we excluded
51-
expect(res.body.question_id).not.toBe('00000000-0000-0000-0000-000000000000');
52-
}
47+
describe('GET /api/topics', () => {
48+
it('should return array of topics', async () => {
49+
const res = await request(app).get('/api/topics');
50+
expect(res.statusCode).toBe(200);
51+
expect(Array.isArray(res.body)).toBe(true);
52+
});
53+
});
5354

54-
if (res.statusCode === 404) {
55-
expect(res.body).toHaveProperty('message', 'No suitable question found for the given criteria.');
56-
}
57-
});
55+
describe('POST /api/questions/select', () => {
56+
it('should return a question matching criteria', async () => {
57+
const payload = {
58+
criteria: { topic: 'Array', difficulty: 'Easy' },
59+
excludedIds: []
60+
};
5861

59-
it('POST /api/questions/select validates body', async () => {
60-
const res = await request(app)
61-
.post('/api/questions/select')
62-
.send({ topic: 'Array' }); // Missing criteria.difficulty and excludedIds
63-
64-
// This expects your controller's validation logic to catch the bad body
65-
expect(res.statusCode).toBe(400);
66-
expect(res.body).toHaveProperty('message');
67-
});
62+
const res = await request(app)
63+
.post('/api/questions/select')
64+
.send(payload);
65+
66+
if (res.statusCode === 200) {
67+
expect(res.body).toMatchObject({
68+
question_id: expect.any(String),
69+
title: expect.any(String),
70+
difficulty: expect.stringMatching(/^(Easy|Medium|Hard)$/)
71+
});
72+
} else {
73+
expect([404]).toContain(res.statusCode); // No matching questions found
74+
}
75+
});
76+
77+
it('should validate request payload', async () => {
78+
const invalidPayload = {
79+
criteria: { topic: '', difficulty: 'Invalid' }
80+
};
6881

69-
});
82+
const res = await request(app)
83+
.post('/api/questions/select')
84+
.send(invalidPayload);
85+
86+
expect(res.statusCode).toBe(400);
87+
expect(res.body).toHaveProperty('message');
88+
});
89+
90+
it('should handle excludedIds correctly', async () => {
91+
// First get all questions
92+
const allQuestionsRes = await request(app).get('/api/questions');
93+
if (allQuestionsRes.body.length > 0) {
94+
const firstQuestion = allQuestionsRes.body[0];
95+
96+
const payload = {
97+
criteria: {
98+
topic: firstQuestion.topic,
99+
difficulty: firstQuestion.difficulty
100+
},
101+
excludedIds: [firstQuestion.question_id]
102+
};
103+
104+
const res = await request(app)
105+
.post('/api/questions/select')
106+
.send(payload);
107+
108+
if (res.statusCode === 200) {
109+
expect(res.body.question_id).not.toBe(firstQuestion.question_id);
110+
} else {
111+
expect([404]).toContain(res.statusCode);
112+
}
113+
}
114+
});
115+
});
116+
});
117+
});

question-service/src/test/question.service.unit.test.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
1-
import { Pool, QueryResult } from 'pg'; // Import QueryResult
2-
import { pool } from '../config/database'; // This is the *mocked* pool
3-
import {
4-
selectQuestion,
5-
Difficulty,
6-
Question,
7-
} from '../services/questionService'; // Import ONLY the service
8-
9-
// Mock the database module
1+
// --- FIX ---
2+
// jest.mock MUST be the first statement in the file.
3+
// This tells Jest to replace the module *before* any other code imports it.
104
jest.mock('../config/database', () => {
115
const actual = jest.requireActual('../config/database');
126

13-
// Explicitly type the mock function to expect SQL query arguments
14-
// and return a Promise that resolves to a QueryResult.
7+
// Explicitly type the mock function
158
const mockQuery = jest.fn<Promise<QueryResult<any>>, [string, any[]]>();
169
// Set a default implementation
17-
mockQuery.mockResolvedValue({ rows: [] } as unknown as QueryResult<any>); // FIX: Add 'as unknown'
10+
mockQuery.mockResolvedValue({ rows: [] } as unknown as QueryResult<any>);
1811

1912
const mockEnd = jest.fn().mockResolvedValue(undefined);
2013
const mockPool = { query: mockQuery, end: mockEnd };
2114

2215
return {
2316
...actual,
24-
// Explicitly cast the exported pool to satisfy the module's type
2517
pool: mockPool as unknown as Pool
2618
};
2719
});
20+
// --- END FIX ---
21+
22+
23+
import { Pool, QueryResult } from 'pg'; // Import QueryResult
24+
import {
25+
selectQuestion,
26+
Difficulty,
27+
Question,
28+
} from '../services/questionService'; // Import ONLY the service
29+
import { pool } from '../config/database'; // This is now the *mocked* pool
2830

2931

32+
// We must cast pool.query once, outside the mock, to the correct Jest mock type.
33+
// This tells TypeScript what 'pool.query' is, so we can access .mock.calls etc.
3034
const mockedQuery = pool.query as unknown as jest.Mock<Promise<QueryResult<any>>, [string, any[]]>;
3135

3236
describe('questionService (Unit)', () => {
@@ -36,7 +40,7 @@ describe('questionService (Unit)', () => {
3640
// Use the correctly typed mock
3741
mockedQuery.mockClear();
3842
// Reset to default implementation
39-
mockedQuery.mockResolvedValue({ rows: [] } as unknown as QueryResult<any>); // FIX: Add 'as unknown'
43+
mockedQuery.mockResolvedValue({ rows: [] } as unknown as QueryResult<any>);
4044
});
4145

4246
// Test 1: Check the logic WITH excluded IDs
@@ -53,7 +57,7 @@ describe('questionService (Unit)', () => {
5357
};
5458

5559
// Use the correctly typed mock
56-
mockedQuery.mockResolvedValue({ rows: [mockQuestion] } as unknown as QueryResult<any>); // FIX: Add 'as unknown'
60+
mockedQuery.mockResolvedValue({ rows: [mockQuestion] } as unknown as QueryResult<any>);
5761

5862
// Call the function directly (no server, no HTTP)
5963
const result = await selectQuestion(criteria, excludedIds);
@@ -65,7 +69,8 @@ describe('questionService (Unit)', () => {
6569

6670
// --- This is the "white box" part ---
6771
// Check the *exact* SQL string and parameters it tried to run
68-
const queryArgs = mockedQuery.mock.calls[0];
72+
// FIX: Add '!' to tell TypeScript 'mock.calls[0]' is not undefined
73+
const queryArgs = mockedQuery.mock.calls[0]!;
6974
const queryString = queryArgs[0] as string;
7075
const queryParams = queryArgs[1] as any[];
7176

@@ -94,7 +99,8 @@ describe('questionService (Unit)', () => {
9499
expect(mockedQuery).toHaveBeenCalledTimes(1);
95100

96101
// --- Check the logic again ---
97-
const queryArgs = mockedQuery.mock.calls[0];
102+
// FIX: Add '!' to tell TypeScript 'mock.calls[0]' is not undefined
103+
const queryArgs = mockedQuery.mock.calls[0]!;
98104
const queryString = queryArgs[0] as string;
99105
const queryParams = queryArgs[1] as any[];
100106

question-service/tsconfig.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
"module": "commonjs",
66
"target": "esnext",
77
"lib": ["esnext"],
8-
"types": ["node"],
8+
"types": ["node", "jest"],
99
"sourceMap": true,
1010
"declaration": true,
1111
"declarationMap": true,
12+
13+
"esModuleInterop": true,
14+
"allowSyntheticDefaultImports": true,
15+
1216
"noUncheckedIndexedAccess": true,
1317
"exactOptionalPropertyTypes": true,
1418
"noImplicitReturns": true,
@@ -23,5 +27,6 @@
2327
"skipLibCheck": true
2428
},
2529
"include": ["src/**/*"],
26-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
27-
}
30+
"exclude": ["node_modules", "dist"]
31+
}
32+

0 commit comments

Comments
 (0)