Skip to content

Commit 0f0c4b7

Browse files
committed
test: realign service tests
1 parent 17da03f commit 0f0c4b7

25 files changed

Lines changed: 472 additions & 157 deletions

frontend/src/services/comsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
* Routes should be referenced through this object rather than
1616
* constructing endpoint paths directly within service methods.
1717
*/
18-
const comsRoute = createRouteBuilder();
18+
const comsRoute = createRouteBuilder('object');
1919

2020
const comsRoutes = {
2121
root: () => comsRoute(),

frontend/src/services/routeBuilder.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ import { useAppStore } from '@/store';
1515
* @returns A function that builds URL paths from additional segments
1616
*/
1717
export function createRouteBuilder(resource?: string) {
18-
return (...segments: (string | number)[]) =>
19-
[resource, ...segments.map((segment) => encodeURIComponent(String(segment)))]
20-
.filter((v) => v !== undefined && v !== '')
18+
return (...segments: (string | number | null | undefined)[]) =>
19+
[resource]
20+
.filter((v): v is string => v !== undefined && v !== null && v !== '')
21+
.concat(
22+
segments
23+
.filter((v): v is string | number => v !== undefined && v !== null && v !== '')
24+
.map((s) => encodeURIComponent(String(s)))
25+
)
2126
.join('/');
2227
}
2328

@@ -39,9 +44,15 @@ export function createRouteBuilder(resource?: string) {
3944
* @returns A function that builds initiative-scoped URL paths
4045
*/
4146
export function createInitiativeRouteBuilder(resource: string) {
42-
return (...segments: (string | number)[]) => {
43-
const initiative = encodeURIComponent(useAppStore().getInitiative.toLowerCase());
47+
return (...segments: (string | number | null | undefined)[]) => {
48+
const initiative = useAppStore().getInitiative.toLowerCase();
4449

45-
return [initiative, resource, ...segments.map((segment) => encodeURIComponent(String(segment)))].join('/');
50+
return [
51+
initiative,
52+
resource,
53+
...segments
54+
.filter((v): v is string | number => v !== undefined && v !== null && v !== '')
55+
.map((segment) => encodeURIComponent(String(segment)))
56+
].join('/');
4657
};
4758
}

frontend/tests/unit/components/projectCommon/ProjectTeamTab.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,6 @@ describe('ProjectTeamTab.vue', () => {
467467

468468
describe('Revoke User Interactions (@project-team-table:revoke-user)', () => {
469469
it('triggers confirm dialog and executes accept callback successfully', async () => {
470-
vi.mocked(activityContactService.deleteActivityContact).mockResolvedValue(mockActivityContact);
471-
472470
const wrapper = shallowMount(ProjectTeamTab, wrapperSettings());
473471

474472
const table = wrapper.findComponent({ name: 'ProjectTeamTable' });

frontend/tests/unit/service/accessRequestService.spec.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('accessRequestService', () => {
5151

5252
const result = await createAccessRequest(request as never);
5353

54-
expect(mockPost).toHaveBeenCalledWith('housing/accessRequest', request);
54+
expect(mockPost).toHaveBeenCalledWith('housing/accessRequest', request, undefined);
5555

5656
expect(result).toEqual(accessRequest);
5757
});
@@ -84,10 +84,14 @@ describe('accessRequestService', () => {
8484

8585
const result = await processAccessRequest(request as never);
8686

87-
expect(mockPost).toHaveBeenCalledWith('housing/accessRequest/access-request-1', {
88-
status: 'Approved',
89-
comments: 'Approved by admin'
90-
});
87+
expect(mockPost).toHaveBeenCalledWith(
88+
'housing/accessRequest/access-request-1',
89+
{
90+
status: 'Approved',
91+
comments: 'Approved by admin'
92+
},
93+
undefined
94+
);
9195

9296
expect(result).toEqual(updatedAccessRequest);
9397
});
@@ -102,9 +106,13 @@ describe('accessRequestService', () => {
102106
status: 'Denied'
103107
} as never);
104108

105-
expect(mockPost).toHaveBeenCalledWith('housing/accessRequest/access-request-1', {
106-
status: 'Denied'
107-
});
109+
expect(mockPost).toHaveBeenCalledWith(
110+
'housing/accessRequest/access-request-1',
111+
{
112+
status: 'Denied'
113+
},
114+
undefined
115+
);
108116
});
109117

110118
it('propagates errors', async () => {
@@ -137,7 +145,7 @@ describe('accessRequestService', () => {
137145

138146
const result = await listAccessRequests();
139147

140-
expect(mockGet).toHaveBeenCalledWith('housing/accessRequest');
148+
expect(mockGet).toHaveBeenCalledWith('housing/accessRequest', undefined);
141149

142150
expect(result).toEqual(accessRequests);
143151
});

frontend/tests/unit/service/activityContactService.spec.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('activity contact service', () => {
4646
role: 'PRIMARY'
4747
} as never);
4848

49-
expect(mockPost).toHaveBeenCalledWith('activity/activity-1/contact/contact-1', { role: 'PRIMARY' });
49+
expect(mockPost).toHaveBeenCalledWith('activity/activity-1/contact/contact-1', { role: 'PRIMARY' }, undefined);
5050

5151
expect(result).toEqual(response);
5252
});
@@ -82,7 +82,7 @@ describe('activity contact service', () => {
8282
contactId: 'contact-1'
8383
} as never);
8484

85-
expect(mockDelete).toHaveBeenCalledWith('activity/activity-1/contact/contact-1');
85+
expect(mockDelete).toHaveBeenCalledWith('activity/activity-1/contact/contact-1', undefined);
8686

8787
expect(result).toEqual(response);
8888
});
@@ -116,7 +116,7 @@ describe('activity contact service', () => {
116116
activityId: 'activity-1'
117117
} as never);
118118

119-
expect(mockGet).toHaveBeenCalledWith('activity/activity-1/contact');
119+
expect(mockGet).toHaveBeenCalledWith('activity/activity-1/contact', undefined);
120120

121121
expect(result).toEqual(response);
122122
});
@@ -163,7 +163,11 @@ describe('activity contact service', () => {
163163
role: 'PRIMARY_UPDATED'
164164
} as never);
165165

166-
expect(mockPut).toHaveBeenCalledWith('activity/activity-1/contact/contact-1', { role: 'PRIMARY_UPDATED' });
166+
expect(mockPut).toHaveBeenCalledWith(
167+
'activity/activity-1/contact/contact-1',
168+
{ role: 'PRIMARY_UPDATED' },
169+
undefined
170+
);
167171

168172
expect(result).toEqual(response);
169173
});
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { api, apiRaw } from '@/services/apiClient';
2+
import { appAxios } from '@/services/interceptors';
3+
4+
import type { Mock } from 'vitest';
5+
6+
vi.mock('@/services/interceptors', () => ({
7+
appAxios: vi.fn()
8+
}));
9+
10+
const mockAxios = {
11+
get: vi.fn(),
12+
post: vi.fn(),
13+
put: vi.fn(),
14+
patch: vi.fn(),
15+
delete: vi.fn()
16+
};
17+
18+
describe('api wrapper', () => {
19+
beforeEach(() => {
20+
vi.clearAllMocks();
21+
(appAxios as unknown as Mock).mockReturnValue(mockAxios);
22+
});
23+
24+
describe('api (unwrapped .data)', () => {
25+
it('get unwraps response data', async () => {
26+
mockAxios.get.mockResolvedValue({ data: 'result' });
27+
28+
const res = await api.get<string>('/test');
29+
30+
expect(mockAxios.get).toHaveBeenCalledWith('/test', undefined);
31+
expect(res).toBe('result');
32+
});
33+
34+
it('get forwards config', async () => {
35+
mockAxios.get.mockResolvedValue({ data: 'ok' });
36+
37+
const config = { params: { a: 1 } };
38+
39+
await api.get('/test', config);
40+
41+
expect(mockAxios.get).toHaveBeenCalledWith('/test', config);
42+
});
43+
44+
it('post unwraps response data', async () => {
45+
mockAxios.post.mockResolvedValue({ data: { id: 1 } });
46+
47+
const res = await api.post<{ id: number }>('/test', { foo: 'bar' });
48+
49+
expect(mockAxios.post).toHaveBeenCalledWith('/test', { foo: 'bar' }, undefined);
50+
expect(res).toEqual({ id: 1 });
51+
});
52+
53+
it('put unwraps response data', async () => {
54+
mockAxios.put.mockResolvedValue({ data: true });
55+
56+
const res = await api.put<boolean>('/test', { x: 1 });
57+
58+
expect(res).toBe(true);
59+
});
60+
61+
it('patch unwraps response data', async () => {
62+
mockAxios.patch.mockResolvedValue({ data: 'patched' });
63+
64+
const res = await api.patch<string>('/test', { x: 1 });
65+
66+
expect(res).toBe('patched');
67+
});
68+
69+
it('delete unwraps response data', async () => {
70+
mockAxios.delete.mockResolvedValue({ data: 'deleted' });
71+
72+
const res = await api.delete<string>('/test');
73+
74+
expect(mockAxios.delete).toHaveBeenCalledWith('/test', undefined);
75+
expect(res).toBe('deleted');
76+
});
77+
});
78+
79+
describe('apiRaw (no transformation)', () => {
80+
it('get returns raw axios response', async () => {
81+
const axiosResponse = { data: 'raw', status: 200 };
82+
83+
mockAxios.get.mockResolvedValue(axiosResponse);
84+
85+
const res = await apiRaw.get('/test');
86+
87+
expect(res).toBe(axiosResponse);
88+
});
89+
90+
it('post returns raw axios response', async () => {
91+
const axiosResponse = { data: 123 };
92+
93+
mockAxios.post.mockResolvedValue(axiosResponse);
94+
95+
const res = await apiRaw.post('/test', { a: 1 });
96+
97+
expect(res).toBe(axiosResponse);
98+
});
99+
100+
it('put returns raw axios response', async () => {
101+
const axiosResponse = { data: true };
102+
103+
mockAxios.put.mockResolvedValue(axiosResponse);
104+
105+
const res = await apiRaw.put('/test', { a: 1 });
106+
107+
expect(res).toBe(axiosResponse);
108+
});
109+
110+
it('patch returns raw axios response', async () => {
111+
const axiosResponse = { data: 'ok' };
112+
113+
mockAxios.patch.mockResolvedValue(axiosResponse);
114+
115+
const res = await apiRaw.patch('/test', { a: 1 });
116+
117+
expect(res).toBe(axiosResponse);
118+
});
119+
120+
it('delete returns raw axios response', async () => {
121+
const axiosResponse = { data: 'done' };
122+
123+
mockAxios.delete.mockResolvedValue(axiosResponse);
124+
125+
const res = await apiRaw.delete('/test');
126+
127+
expect(res).toBe(axiosResponse);
128+
});
129+
});
130+
131+
describe('edge cases', () => {
132+
it('handles undefined body in post', async () => {
133+
mockAxios.post.mockResolvedValue({ data: 'ok' });
134+
135+
await api.post('/test');
136+
137+
expect(mockAxios.post).toHaveBeenCalledWith('/test', undefined, undefined);
138+
});
139+
140+
it('handles config passed to delete', async () => {
141+
mockAxios.delete.mockResolvedValue({ data: 'ok' });
142+
143+
const config = { params: { force: true } };
144+
145+
await api.delete('/test', config);
146+
147+
expect(mockAxios.delete).toHaveBeenCalledWith('/test', config);
148+
});
149+
});
150+
});

frontend/tests/unit/service/atsService.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ describe('ats service', () => {
137137

138138
const result = await createAtsClient(request);
139139

140-
expect(mockPost).toHaveBeenCalledWith('ats/client', request);
140+
expect(mockPost).toHaveBeenCalledWith('ats/client', request, undefined);
141141

142142
expect(result).toEqual({
143143
status: 201,
@@ -177,7 +177,7 @@ describe('ats service', () => {
177177

178178
const result = await createAtsEnquiry(request);
179179

180-
expect(mockPost).toHaveBeenCalledWith('ats/enquiry', request);
180+
expect(mockPost).toHaveBeenCalledWith('ats/enquiry', request, undefined);
181181

182182
expect(result).toEqual({
183183
status: 201,

frontend/tests/unit/service/codeService.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('code service', () => {
4444

4545
const result = await getCodeTables();
4646

47-
expect(mockGet).toHaveBeenCalledWith('code');
47+
expect(mockGet).toHaveBeenCalledWith('code', undefined);
4848
expect(result).toEqual(response);
4949
});
5050

frontend/tests/unit/service/contactService.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('contactService', () => {
9595

9696
const result = await getCurrentUserContact();
9797

98-
expect(mockGet).toHaveBeenCalledWith('contact');
98+
expect(mockGet).toHaveBeenCalledWith('contact', undefined);
9999

100100
expect(result).toEqual(contact);
101101
});
@@ -117,7 +117,7 @@ describe('contactService', () => {
117117
contactId: 'contact-1'
118118
} as never);
119119

120-
expect(mockDelete).toHaveBeenCalledWith('contact/contact-1');
120+
expect(mockDelete).toHaveBeenCalledWith('contact/contact-1', undefined);
121121
});
122122

123123
it('propagates errors', async () => {
@@ -147,7 +147,7 @@ describe('contactService', () => {
147147

148148
const result = await matchContacts(request as never);
149149

150-
expect(mockPost).toHaveBeenCalledWith('contact/match', request);
150+
expect(mockPost).toHaveBeenCalledWith('contact/match', request, undefined);
151151

152152
expect(result).toEqual(contacts);
153153
});
@@ -175,7 +175,7 @@ describe('contactService', () => {
175175

176176
const result = await searchContacts(request as never);
177177

178-
expect(mockPost).toHaveBeenCalledWith('contact/search', request);
178+
expect(mockPost).toHaveBeenCalledWith('contact/search', request, undefined);
179179

180180
expect(result).toEqual(contacts);
181181
});
@@ -207,7 +207,7 @@ describe('contactService', () => {
207207

208208
const result = await putContact(request as never);
209209

210-
expect(mockPut).toHaveBeenCalledWith('contact', request);
210+
expect(mockPut).toHaveBeenCalledWith('contact', request, undefined);
211211

212212
expect(result).toEqual(contact);
213213
});

0 commit comments

Comments
 (0)