-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.test.js
More file actions
204 lines (170 loc) · 7.02 KB
/
api.test.js
File metadata and controls
204 lines (170 loc) · 7.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// integration testing
jest.setTimeout(30000);
const request = require('supertest');
const path = require('path');
const fs = require('fs');
const illustrationKeys = require('./__tests__/illustrations.json');
const app = require('./server');
// Mocks the Pool object from 'pg' to intercept all queries
jest.mock('pg', () => {
const mPool = {
query: jest.fn((query, params) => {
if (query.includes('SELECT * FROM sample."Questions"')) {
// Creates 20 mock questions with controlled multiplecorrectanswersallowed flag
const questions = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
text: `Question ${i + 1}`,
chapterid: i % 10 + 1,
multiplecorrectanswersallowed: i % 3 === 1, // Sets every 3rd question to allow multiple correct answers
}));
// Logs generated questions for debugging
console.log('Generated mock questions:', JSON.stringify(questions, null, 2));
return Promise.resolve({ rows: questions });
} else if (query.includes('SELECT * FROM sample."Options"')) {
// Generates options based on the multiplecorrectanswersallowed flag from the question
const questionId = params[0];
const isMultipleCorrectAllowed = questionId % 3 === 1;
// Generates correct options based on the flag
const numCorrectOptions = isMultipleCorrectAllowed ? 2 : 1;
const options = Array.from({ length: 4 }, (_, j) => ({
id: questionId * 10 + j + 1,
text: `Option ${j + 1}`,
iscorrect: j < numCorrectOptions, // Assigns correct options based on flag
questionid: questionId,
}));
// Logs generated options for debugging
console.log(`Generated options for question ${questionId}:`, JSON.stringify(options, null, 2));
return Promise.resolve({ rows: options });
}
return Promise.resolve({ rows: [] });
}),
};
return { Pool: jest.fn(() => mPool) };
});
const mockQuery = require('pg').Pool().query;
// Mocks the rate limiter middleware to bypass rate limiting during tests
jest.mock('express-rate-limit', () => {
return jest.fn(() => (req, res, next) => {
next(); // Bypasses the rate limiter for testing
});
});
// Wraps the app in an HTTP server instance and captures the dynamically assigned port
let server;
// Lifecycle hooks to start and stop the server
beforeAll((done) => {
server = app.listen(0, () => {
console.log(`Server started on port ${server.address().port}`);
done();
});
server.on('error', (error) => {
console.error('Error starting the server:', error);
done(error);
});
});
// Final cleanup after all tests are done
afterAll((done) => {
server?.close((error) => {
if (error) {
console.error('Error stopping the server:', error);
}
done(error);
});
});
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
describe('API Endpoints', () => {
beforeEach(() => {
mockQuery.mockClear(); // Clears the mock query calls before each test
});
describe('GET /api/questions', () => {
it('should fetch random questions without triggering rate limit', async () => {
// Performs the API request with the required custom header
const res = await request(server)
.get('/api/questions')
.set('X-Requested-With', 'XMLHttpRequest');
// Logs the API response body for debugging purposes
console.log('API Response (questions):', JSON.stringify(res.body, null, 2));
// Basic status and length checks
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveLength(20);
// Logs total number of questions
console.log(`Total number of questions returned: ${res.body.length}`);
// Validates the structure of the questions
res.body.forEach((question) => {
expect(question).toHaveProperty('Id');
expect(question).toHaveProperty('Text');
expect(question).toHaveProperty('ChapterId');
expect(question).toHaveProperty('MultipleCorrectAnswersAllowed');
expect(question).toHaveProperty('options');
expect(Array.isArray(question.options)).toBe(true);
// Logs the options for each question
console.log(`Question ${question.Id} options:`, question.options);
// Validates the structure of options
question.options.forEach(option => {
expect(option).toHaveProperty('Id');
expect(option).toHaveProperty('Text');
});
});
// Checks overall options distribution for logging and debugging purposes
const totalAnswers = res.body.reduce(
(acc, question) => acc + question.options.length,
0
);
console.log(`Total options across all questions: ${totalAnswers}`);
await delay(100);
});
});
// Illustration existence checks
describe('GET /api/illustration/:key', () => {
const illustrationsPath = path.join(__dirname, '../../illustrations');
// Checks that all illustrations listed in the JSON file exist in the illustrations folder
illustrationKeys.forEach((key) => {
it(`should have illustration ${key} in the illustrations folder`, async () => {
const illustrationFile = path.join(illustrationsPath, key);
const fileExists = fs.existsSync(illustrationFile);
expect(fileExists).toBe(true);
});
});
// Checks that all illustrations in the folder are listed in the JSON file
const illustrationFiles = fs.readdirSync(illustrationsPath);
illustrationFiles.forEach((file) => {
it(`should have ${file} listed in illustrations.json`, () => {
expect(illustrationKeys).toContain(file);
});
});
});
describe('POST /api/check-answers', () => {
it('should check answers correctly without triggering rate limit', async () => {
const mockAnswers = [
{ questionId: 1, answerId: '11' },
{ questionId: 2, answerId: '21' },
];
const mockCorrectAnswers = [
{ questionId: 1, correctAnswers: ['11'] },
{ questionId: 2, correctAnswers: ['21'] },
];
// Logs the mock answers for debugging
console.log('Mock answers:', JSON.stringify(mockAnswers, null, 2));
mockQuery.mockImplementation((query, params) => {
if (query.includes('SELECT id FROM sample."Options" WHERE "questionid" = $1 AND "iscorrect" = TRUE')) {
return Promise.resolve({
rows: mockCorrectAnswers
.find((a) => a.questionId === params[0])
.correctAnswers.map((id) => ({ id })),
});
}
return Promise.resolve({ rows: [] });
});
const response = await request(server)
.post('/api/check-answers')
.send({ answers: mockAnswers })
.set('X-Requested-With', 'XMLHttpRequest');
// Logs the API response body for debugging
console.log('API Response (check-answers):', JSON.stringify(response.body, null, 2));
expect(response.status).toBe(200);
expect(response.body).toEqual(mockCorrectAnswers);
await delay(100);
});
});
});