Skip to content

Commit 66db7ad

Browse files
committed
Merge branch 'ty/scrum-180-backend-integration-tests' of https://github.com/UTSC-CSCC01-Software-Engineering-I/term-group-project-c01w25-project-course-matrix into ty/scrum-180-backend-integration-tests
2 parents 8ff7678 + 5167eb8 commit 66db7ad

File tree

1 file changed

+222
-57
lines changed

1 file changed

+222
-57
lines changed
Lines changed: 222 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
import {afterAll, beforeEach, describe, expect, it, jest, test,} from '@jest/globals';
1+
import {afterAll, beforeEach, describe, expect, jest, test,} from '@jest/globals';
22
import {Json} from '@pinecone-database/pinecone/dist/pinecone-generated-ts-fetch/db_control';
33
import {NextFunction, Request, Response} from 'express';
44
import request from 'supertest';
55

6+
import restrictionsController from '../../src/controllers/restrictionsController';
7+
import timetablesController from '../../src/controllers/timetablesController';
68
import {supabase} from '../../src/db/setupDb';
79
import app from '../../src/index';
810
import {server} from '../../src/index';
911
import {authHandler} from '../../src/middleware/authHandler';
1012
import getOfferings from '../../src/services/getOfferings';
1113

12-
jest.mock('../../src/db/setupDb', () => ({
13-
supabase: {
14-
schema: jest.fn(),
15-
},
16-
}));
14+
const USER1 = 'testuser01-ab9e6877-f603-4c6a-9832-864e520e4d01';
15+
const USER2 = 'testuser02-1d3f02df-f926-4c1f-9f41-58ca50816a33';
16+
const USER3 = 'testuser03-f84fd0da-d775-4424-ad88-d9675282453c';
17+
const USER4 = 'testuser04-f84fd0da-d775-4424-ad88-d9675282453c';
18+
// USER5 is saved for courseOffering query do not use for anyother test
19+
const USER5 = 'testuser04-f84fd0da-d775-4424-ad88-d9675282453c';
1720

1821
// Handle AI import from index.ts
1922
jest.mock('@ai-sdk/openai', () => ({
@@ -65,62 +68,224 @@ jest.mock(
6568
authHandler: jest.fn() as jest.MockedFunction<typeof authHandler>,
6669
}));
6770

68-
type SupabaseQueryResult = Promise<{data: any; error: any;}>;
71+
// Mock timetables dataset
72+
const mockTimetables1 = [
73+
{
74+
id: 1,
75+
name: 'Timetable 1',
76+
user_id: USER1,
77+
},
78+
{
79+
id: 2,
80+
name: 'Timetable 2',
81+
user_id: USER1,
82+
},
83+
];
6984

85+
// Mock list of offering
86+
const offering1 = [
87+
{
88+
id: 1,
89+
course_id: 101,
90+
day: 'MO',
91+
start: '10:00:00',
92+
end: '11:00:00',
93+
},
94+
{
95+
id: 2,
96+
course_id: 101,
97+
day: 'WE',
98+
start: '10:00:00',
99+
end: '11:00:00',
100+
},
101+
{
102+
id: 3,
103+
course_id: 101,
104+
day: 'FR',
105+
start: '10:00:00',
106+
end: '11:00:00',
107+
},
108+
];
70109

110+
const offering2 = [
111+
{
112+
id: 1,
113+
course_id: 102,
114+
day: 'MO',
115+
start: '10:00:00',
116+
end: '12:00:00',
117+
},
118+
];
71119

72-
describe('POST /api/timetable/generate', () => {
120+
const offering3 = [
121+
{
122+
id: 1,
123+
course_id: 103,
124+
day: 'TU',
125+
start: '15:00:00',
126+
end: '17:00:00',
127+
},
128+
{
129+
id: 2,
130+
course_id: 103,
131+
day: 'WE',
132+
start: '15:00:00',
133+
end: '17:00:00',
134+
},
135+
];
136+
137+
// Spy on the getTimetables method
138+
jest.spyOn(timetablesController, 'getTimetables')
139+
.mockImplementation(timetablesController.getTimetables);
140+
141+
// Spy on the createTimetable method
142+
jest.spyOn(timetablesController, 'createTimetable')
143+
.mockImplementation(timetablesController.createTimetable);
144+
145+
// Spy on the updateTimetable method
146+
jest.spyOn(timetablesController, 'updateTimetable')
147+
.mockImplementation(timetablesController.updateTimetable);
148+
149+
// Spy on the deleteTimetable method
150+
jest.spyOn(timetablesController, 'deleteTimetable')
151+
.mockImplementation(timetablesController.deleteTimetable);
152+
153+
// Mock data set response to qeury
154+
jest.mock(
155+
'../../src/db/setupDb',
156+
() => ({
157+
supabase: {
158+
// Mock return from schema, from and select to chain the next query
159+
// command
160+
schema: jest.fn().mockReturnThis(),
161+
from: jest.fn().mockReturnThis(),
162+
select: jest.fn().mockReturnThis(),
163+
// Mock db response to .eq query command
164+
eq: jest.fn().mockImplementation((key, value) => {
165+
// Each test case is codded by the user_id in session
166+
// DB response 1: Query user timetable return non null value
167+
if (key === 'user_id' && value === USER1) {
168+
// Return mock data when user_id matches
169+
return {data: mockTimetables1, error: null};
170+
}
171+
// DB response 2: Query user timetable return null value
172+
if (key === 'user_id' && value === USER2) {
173+
// Return null for this user_id
174+
return {data: null, error: null};
175+
}
176+
177+
// DB response 3: Combine .eq and .maybeSingle to signify that the
178+
// return value could be single: Return non null value
179+
if (key === 'user_id' && value === USER3) {
180+
return {
181+
eq: jest.fn().mockReturnThis(), // Allow further chaining of eq
182+
// if required
183+
maybeSingle: jest.fn().mockImplementation(() => {
184+
return {data: null, error: null};
185+
}),
186+
};
187+
}
188+
// DB response 4: Combine .eq and .maybeSingle to signify that the
189+
// return value could be single: Return null value
190+
if (key === 'user_id' && value === USER4) {
191+
return {
192+
eq: jest.fn().mockReturnThis(), // Allow further chaining of eq
193+
// if required
194+
neq: jest.fn().mockImplementation(
195+
() => ({
196+
maybeSingle: jest.fn().mockImplementation(
197+
() => ({data: null, error: null})),
198+
})),
199+
maybeSingle: jest.fn().mockImplementation(() => {
200+
return {data: mockTimetables1, error: null};
201+
}),
202+
};
203+
}
204+
// DB response with offering1 if courseID = 101 in request
205+
if (key === 'course_id' && value === 101) {
206+
return {
207+
eq: jest.fn().mockImplementation(() => {
208+
return {data: offering1, error: null};
209+
}),
210+
};
211+
}
212+
// DB response with offering1 if courseID = 102 in request
213+
if (key === 'course_id' && value === 102) {
214+
return {
215+
eq: jest.fn().mockImplementation(() => {
216+
return {data: offering2, error: null};
217+
}),
218+
};
219+
}
220+
// DB response with offering1 if courseID = 103 in request
221+
if (key === 'course_id' && value === 103) {
222+
return {
223+
eq: jest.fn().mockImplementation(() => {
224+
return {data: offering1, error: null};
225+
}),
226+
};
227+
}
228+
}),
229+
// Mock db response to .insert query command
230+
insert: jest.fn().mockImplementation((data: Json) => {
231+
// DB response 5: Create timetable successfully, new timetable data is
232+
// responded
233+
if (data && data[0].user_id === USER3) {
234+
return {
235+
select: jest.fn().mockImplementation(() => {
236+
// Return the input data when select is called
237+
return {
238+
data: data,
239+
error: null
240+
}; // Return the data passed to insert
241+
}),
242+
};
243+
}
244+
// DB response 6: Create timetable uncessfully, return error.message
245+
return {
246+
select: jest.fn().mockImplementation(() => {
247+
return {data: null, error: {message: 'Fail to create timetable'}};
248+
}),
249+
};
250+
}),
251+
252+
// Mock db response to .update query command
253+
update: jest.fn().mockImplementation((updatedata: Json) => {
254+
// DB response 7: Timetable updated successfully, db return updated
255+
// data in response
256+
if (updatedata && updatedata.timetable_title === 'Updated Title') {
257+
return {
258+
eq: jest.fn().mockReturnThis(),
259+
select: jest.fn().mockReturnThis(),
260+
single: jest.fn().mockImplementation((data) => {
261+
return {data: updatedata, error: null};
262+
}),
263+
};
264+
}
265+
// DB response 8: Update timetable uncessfully, return error.message
266+
return {data: null, error: {message: 'Fail to update timetable'}};
267+
}),
268+
269+
// Mock db response to .delete query command
270+
delete: jest.fn().mockImplementation(() => {
271+
// DB response 9: Delete timetable successfully
272+
return {
273+
eq: jest.fn().mockReturnThis(),
274+
data: null,
275+
error: null,
276+
};
277+
}),
278+
},
279+
}));
280+
281+
// Test block
282+
describe('Simple test case for offering', () => {
73283
beforeEach(() => {
74284
jest.clearAllMocks();
75285
});
76286

77-
it('should return a generated timetable when valid input is provided',
78-
async () => {
79-
const mockData = ([{
80-
id: 1,
81-
course_id: 123,
82-
meeting_section: 'LEC01',
83-
offering: 'Fall 2025',
84-
day: 'MON',
85-
start: '10:00:00',
86-
end: '11:00:00',
87-
location: 'Room 101',
88-
current: 30,
89-
max: 40,
90-
is_waitlisted: false,
91-
delivery_mode: 'In-Person',
92-
instructor: 'Dr. Smith',
93-
notes: '',
94-
code: 'ABC123',
95-
}]);
96-
97-
// Build the method chain mock
98-
const eqMock2 = jest.fn<() => SupabaseQueryResult>().mockResolvedValue({
99-
data: mockData,
100-
error: null,
101-
});
102-
const eqMock1 = jest.fn(() => ({eq: eqMock2}));
103-
const selectMock = jest.fn(() => ({eq: eqMock1}));
104-
const fromMock = jest.fn(() => ({select: selectMock}));
105-
const schemaMock = jest.fn(() => ({from: fromMock}));
106-
107-
// Replace supabase.schema with our chain
108-
(supabase.schema as jest.Mock).mockImplementation(schemaMock);
109-
110-
const response = await request(app)
111-
.post('/api/timetable/generate')
112-
.send({
113-
courses: [{id: 123}],
114-
semester: 'Fall 2025',
115-
restrictions: []
116-
})
117-
.expect(404); // Expect HTTP 200 status
118-
119-
120-
121-
// Check response structure
122-
// expect(response.body).toHaveProperty('amount');
123-
// expect(response.body).toHaveProperty('schedules');
124-
// expect(Array.isArray(response.body.schedules)).toBe(true);
125-
});
287+
test('should return offering1', async () => {
288+
const response = await getOfferings(101, 'Spring');
289+
expect(response).toEqual(offering1);
290+
});
126291
});

0 commit comments

Comments
 (0)