Skip to content

Commit ea2e657

Browse files
committed
fix: update roadmap controller to use searchObject endpoint to retrieve file metadata from COMS
Signed-off-by: Sanjay Babu <sanjaytkbabu@gmail.com>
1 parent bedbcf0 commit ea2e657

4 files changed

Lines changed: 120 additions & 9 deletions

File tree

app/src/controllers/roadmap.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
33
import { PermitStage } from '../db/codes/enums.ts';
44
import { transactionWrapper } from '../db/utils/transactionWrapper';
55
import { generateCreateStamps, generateNullDeleteStamps, generateNullUpdateStamps } from '../db/utils/utils';
6-
import { getObject } from '../services/coms';
6+
import { getObject, searchObject } from '../services/coms';
77
import { email } from '../services/email';
88
import { createNote } from '../services/note';
99
import { createNoteHistory } from '../services/noteHistory';
@@ -44,9 +44,10 @@ export const sendRoadmapController = async (
4444
// If succesful it is converted to base64 encoding and added to the attachment list
4545
const objectPromises = req.body.selectedFileIds.map(async (id) => {
4646
const { status, headers, data } = await getObject(req.currentContext.bearerToken!, id);
47+
const searchResponse = await searchObject(req.currentContext.bearerToken!, id);
4748

48-
if (status === 200) {
49-
const filename = headers['x-amz-meta-name'] as string;
49+
if (status === 200 && searchResponse.data[0]) {
50+
const filename = searchResponse.data[0].name;
5051
if (filename) {
5152
attachments.push({
5253
content: Buffer.from(data).toString('base64'),
@@ -55,7 +56,7 @@ export const sendRoadmapController = async (
5556
filename: filename
5657
});
5758
} else {
58-
throw new Problem(status, { detail: `Unable to obtain filename for file ${id}` });
59+
throw new Problem(500, { detail: `Unable to obtain filename for file ${id}` });
5960
}
6061
}
6162
});

app/src/services/coms.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ export const getObject = async (bearerToken: string, objectId: string) => {
8484
return { status, headers, data };
8585
};
8686

87+
/**
88+
* Search for an object and obtain information about it
89+
* @param bearerToken The bearer token of the authorized user
90+
* @param objectId The id for the object to get metadata for
91+
* @returns The obtained information about the object
92+
*/
93+
export const searchObject = async (bearerToken: string, objectId: string) => {
94+
if (!uuidValidateV4(objectId)) {
95+
throw new Problem(422, { detail: 'Invalid objectId parameter' });
96+
}
97+
const { status, headers, data } = await comsAxios({
98+
headers: { Authorization: `Bearer ${bearerToken}` }
99+
}).get('/object', { params: { objectId } });
100+
return { status, headers, data };
101+
};
102+
87103
/**
88104
* Obtain the current user information in COMS
89105
* @param currentContext The current context of the Express request

app/tests/unit/controllers/roadmap.spec.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as noteService from '../../../src/services/note.ts';
88
import * as noteHistoryService from '../../../src/services/noteHistory.ts';
99
import { uuidv4Pattern } from '../../../src/utils/regexp.ts';
1010

11+
import type { InternalAxiosRequestConfig } from 'axios';
1112
import type { Request, Response } from 'express';
1213
import type { Mock } from 'vitest';
1314
import type { Email, Note, NoteHistory } from '../../../src/types';
@@ -34,6 +35,7 @@ afterEach(() => {
3435
describe('send', () => {
3536
const emailSpy = vi.spyOn(emailService, 'email');
3637
const getObjectSpy = vi.spyOn(comsService, 'getObject');
38+
const searchObjectSpy = vi.spyOn(comsService, 'searchObject');
3739
const createNoteSpy = vi.spyOn(noteService, 'createNote');
3840
const createHistorySpy = vi.spyOn(noteHistoryService, 'createNoteHistory');
3941

@@ -142,7 +144,7 @@ describe('send', () => {
142144
const req = {
143145
body: {
144146
activityId: 'ACTI1234',
145-
selectedFileIds: ['123', '456'],
147+
selectedFileIds: ['550e8400-e29b-41d4-a716-446655440001', '550e8400-e29b-41d4-a716-446655440002'],
146148
emailData: {
147149
body: 'Some message text',
148150
bodyType: 'text',
@@ -178,6 +180,14 @@ describe('send', () => {
178180
status: 200
179181
};
180182

183+
const searchObjectResponse = {
184+
data: [{ name: 'foo' }],
185+
status: 200,
186+
statusText: 'OK',
187+
headers: {},
188+
config: {} as InternalAxiosRequestConfig
189+
};
190+
181191
const createdHistory: NoteHistory = {
182192
...TEST_NOTE_HISTORY_1,
183193
type: 'Roadmap',
@@ -192,6 +202,7 @@ describe('send', () => {
192202
createHistorySpy.mockResolvedValue(createdHistory);
193203
createNoteSpy.mockResolvedValue(createdNote);
194204
getObjectSpy.mockResolvedValue(getObjectResponse);
205+
searchObjectSpy.mockResolvedValue(searchObjectResponse);
195206
emailSpy.mockResolvedValue(TEST_EMAIL_RESPONSE);
196207

197208
await sendRoadmapController(
@@ -212,7 +223,7 @@ describe('send', () => {
212223
const req = {
213224
body: {
214225
activityId: 'ACTI1234',
215-
selectedFileIds: ['123', '456'],
226+
selectedFileIds: ['550e8400-e29b-41d4-a716-446655440001', '550e8400-e29b-41d4-a716-446655440002'],
216227
emailData: {
217228
body: 'Some message text',
218229
bodyType: 'text',
@@ -270,7 +281,7 @@ describe('send', () => {
270281
const req = {
271282
body: {
272283
activityId: 'ACTI1234',
273-
selectedFileIds: ['123', '456'],
284+
selectedFileIds: ['550e8400-e29b-41d4-a716-446655440001', '550e8400-e29b-41d4-a716-446655440002'],
274285
emailData: {
275286
body: 'Some message text',
276287
bodyType: 'text',
@@ -315,8 +326,24 @@ describe('send', () => {
315326
status: 200
316327
};
317328

318-
const note1Name = `${getObjectResponse1.headers['x-amz-meta-name']}`;
319-
const note2Name = `${getObjectResponse2.headers['x-amz-meta-name']}`;
329+
const searchObjectResponse1 = {
330+
data: [{ name: 'foo' }],
331+
status: 200,
332+
statusText: 'OK',
333+
headers: {},
334+
config: {} as InternalAxiosRequestConfig
335+
};
336+
337+
const searchObjectResponse2 = {
338+
data: [{ name: 'bar' }],
339+
status: 200,
340+
statusText: 'OK',
341+
headers: {},
342+
config: {} as InternalAxiosRequestConfig
343+
};
344+
345+
const note1Name = `${searchObjectResponse1.data[0].name}`;
346+
const note2Name = `${searchObjectResponse2.data[0].name}`;
320347

321348
const createdHistory: NoteHistory = {
322349
...TEST_NOTE_HISTORY_1,
@@ -334,6 +361,8 @@ describe('send', () => {
334361
emailSpy.mockResolvedValue(TEST_EMAIL_RESPONSE);
335362
getObjectSpy.mockResolvedValueOnce(getObjectResponse1);
336363
getObjectSpy.mockResolvedValueOnce(getObjectResponse2);
364+
searchObjectSpy.mockResolvedValueOnce(searchObjectResponse1);
365+
searchObjectSpy.mockResolvedValueOnce(searchObjectResponse2);
337366

338367
await sendRoadmapController(
339368
req as unknown as Request<never, never, { activityId: string; selectedFileIds: string[]; emailData: Email }>,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import ConfirmationService from 'primevue/confirmationservice';
2+
import ToastService from 'primevue/toastservice';
3+
import { createTestingPinia } from '@pinia/testing';
4+
import { flushPromises, shallowMount } from '@vue/test-utils';
5+
6+
import ProjectRoadmapTab from '@/components/projectCommon/ProjectRoadmapTab.vue';
7+
import i18n from '@/i18n';
8+
import { roadmapService, userService } from '@/services';
9+
10+
vi.mock('@/services', () => ({
11+
roadmapService: { getRoadmapNote: vi.fn(), sendRoadmap: vi.fn() },
12+
userService: { listUsers: vi.fn() }
13+
}));
14+
15+
describe('ProjectRoadmapTab.vue', () => {
16+
it('initializes BCC with multiple emails from config and assignee', async () => {
17+
vi.mocked(roadmapService.getRoadmapNote).mockResolvedValue('Roadmap note');
18+
vi.mocked(userService.listUsers).mockResolvedValue([
19+
{
20+
userId: 'user1',
21+
email: 'navigator@bcgov.bc.ca',
22+
firstName: 'Nav',
23+
lastName: 'User',
24+
fullName: 'Nav User',
25+
sub: 'idir-123',
26+
idp: 'idir',
27+
active: true,
28+
groups: [],
29+
elevatedRights: false,
30+
bceidBusinessName: ''
31+
}
32+
]);
33+
34+
const wrapper = shallowMount(ProjectRoadmapTab, {
35+
global: {
36+
plugins: [
37+
i18n,
38+
ConfirmationService,
39+
ToastService,
40+
createTestingPinia({
41+
initialState: {
42+
app: { initiative: 'Housing' },
43+
config: { config: { ches: { roadmap: { bcc: 'config1@bcgov.bc.ca; config2@bcgov.bc.ca' } } } },
44+
project: {
45+
project: { activityId: 'activity1', assignedUserId: 'user1' },
46+
activityContacts: [{ contactId: 'c1', activityId: 'activity1', contact: { email: 'user@test.com' } }],
47+
roadmapNote: 'Roadmap note'
48+
}
49+
}
50+
})
51+
]
52+
}
53+
});
54+
55+
await flushPromises();
56+
57+
// Verify BCC field is initialized with config emails + assignee email
58+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
59+
expect((wrapper.vm as any).initialFormValues.bcc).toEqual([
60+
'config1@bcgov.bc.ca',
61+
'config2@bcgov.bc.ca',
62+
'navigator@bcgov.bc.ca'
63+
]);
64+
});
65+
});

0 commit comments

Comments
 (0)