Skip to content

Commit fe45c29

Browse files
CCM-9247: Changes in response to review comments
1 parent 901a27d commit fe45c29

File tree

6 files changed

+124
-66
lines changed

6 files changed

+124
-66
lines changed

lambdas/backend-api/src/__tests__/templates/api/process-proof.test.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,29 @@ import { TemplateRepository } from '../../../templates/infra';
44
import { LetterFileRepository } from '@backend-api/templates/infra/letter-file-repository';
55

66
test.each([
7-
['NO_THREATS_FOUND', 'PASSED'],
8-
['THREATS_FOUND', 'FAILED'],
7+
[
8+
'NO_THREATS_FOUND',
9+
'PASSED',
10+
(letterFileRepository: LetterFileRepository) =>
11+
expect(
12+
letterFileRepository.copyFromQuarantineToInternal
13+
).toHaveBeenCalledWith(
14+
'proofs/template-id/proof.pdf',
15+
'version-id',
16+
'proofs/template-owner/template-id/proof.pdf'
17+
),
18+
],
19+
[
20+
'THREATS_FOUND',
21+
'FAILED',
22+
(letterFileRepository) =>
23+
expect(
24+
letterFileRepository.copyFromQuarantineToInternal
25+
).not.toHaveBeenCalled(),
26+
],
927
])(
1028
'calls dependencies as expected for a %s virus scan',
11-
async (scanResultStatus, virusScanStatus) => {
29+
async (scanResultStatus, virusScanStatus, s3Expectation) => {
1230
const templateRepository = mockDeep<TemplateRepository>({
1331
getOwner: jest.fn().mockReturnValue('template-owner'),
1432
});
@@ -31,13 +49,7 @@ test.each([
3149

3250
expect(templateRepository.getOwner).toHaveBeenCalledWith('template-id');
3351

34-
expect(
35-
letterFileRepository.copyFromQuarantineToInternal
36-
).toHaveBeenCalledWith(
37-
'proofs/template-id/proof.pdf',
38-
'version-id',
39-
'proofs/template-owner/template-id/proof.pdf'
40-
);
52+
s3Expectation(letterFileRepository);
4153

4254
expect(
4355
templateRepository.setLetterFileVirusScanStatusForProof

lambdas/backend-api/src/__tests__/templates/infra/template-repository.test.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,11 @@ describe('templateRepository', () => {
841841
describe('setLetterFileVirusScanStatusForProof', () => {
842842
it('adds the virus scan status of the proof to the database record and updates the template status if scan status is passed', async () => {
843843
const { templateRepository, mocks } = setup();
844+
mocks.ddbDocClient.on(UpdateCommand).resolves({
845+
Attributes: {
846+
templateStatus: 'WAITING_FOR_PROOF',
847+
},
848+
});
844849

845850
await templateRepository.setLetterFileVirusScanStatusForProof(
846851
'template-owner',
@@ -887,6 +892,11 @@ describe('templateRepository', () => {
887892

888893
it('adds the virus scan status of the proof to the database record and does not update the template status if scan status is failed', async () => {
889894
const { templateRepository, mocks } = setup();
895+
mocks.ddbDocClient.on(UpdateCommand).resolves({
896+
Attributes: {
897+
templateStatus: 'WAITING_FOR_PROOF',
898+
},
899+
});
890900

891901
await templateRepository.setLetterFileVirusScanStatusForProof(
892902
'template-owner',
@@ -945,7 +955,11 @@ describe('templateRepository', () => {
945955

946956
mocks.ddbDocClient
947957
.on(UpdateCommand)
948-
.resolvesOnce({})
958+
.resolvesOnce({
959+
Attributes: {
960+
templateStatus: 'WAITING_FOR_PROOF',
961+
},
962+
})
949963
.rejects(
950964
new ConditionalCheckFailedException({
951965
$metadata: {},
@@ -983,10 +997,13 @@ describe('templateRepository', () => {
983997

984998
it('raises other exceptions from the database for the second update', async () => {
985999
const { templateRepository, mocks } = setup();
986-
9871000
mocks.ddbDocClient
9881001
.on(UpdateCommand)
989-
.resolvesOnce({})
1002+
.resolvesOnce({
1003+
Attributes: {
1004+
templateStatus: 'WAITING_FOR_PROOF',
1005+
},
1006+
})
9901007
.rejects(new Error('Something went wrong'));
9911008

9921009
await expect(

lambdas/backend-api/src/templates/api/process-proof.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ export const createHandler =
2929
templateId,
3030
fileName
3131
);
32+
const virusScanResult =
33+
scanResultStatus === 'NO_THREATS_FOUND' ? 'PASSED' : 'FAILED';
3234

33-
await letterFileRepository.copyFromQuarantineToInternal(
34-
objectKey,
35-
versionId,
36-
internalKey
37-
);
35+
if (virusScanResult === 'PASSED') {
36+
await letterFileRepository.copyFromQuarantineToInternal(
37+
objectKey,
38+
versionId,
39+
internalKey
40+
);
41+
}
3842

3943
// we will copy to the download bucket here as well
4044

41-
const virusScanResult =
42-
scanResultStatus === 'NO_THREATS_FOUND' ? 'PASSED' : 'FAILED';
4345
await templateRepository.setLetterFileVirusScanStatusForProof(
4446
owner,
4547
templateId,

lambdas/backend-api/src/templates/infra/letter-proof-repository.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ export class LetterProofRepository extends LetterFileRepository {
1111
const [fileType, templateId, fileNameWithExtension] = keyParts;
1212
const [fileName, extension] = fileNameWithExtension.split('.');
1313

14-
if (keyParts.length !== 3 || fileType !== 'proofs' || extension !== 'pdf') {
14+
if (
15+
keyParts.length !== 3 ||
16+
fileType !== 'proofs' ||
17+
extension.toLowerCase() !== 'pdf'
18+
) {
1519
throw new Error(`Unexpected object key "${key}"`);
1620
}
1721

lambdas/backend-api/src/templates/infra/letter-upload-repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class LetterUploadRepository extends LetterFileRepository {
131131
const expectedExtension =
132132
parsed['file-type'] === 'test-data' ? 'csv' : 'pdf';
133133

134-
if (extension !== expectedExtension) {
134+
if (extension.toLowerCase() !== expectedExtension) {
135135
throw new Error(`Unexpected object key "${key}"`);
136136
}
137137

lambdas/backend-api/src/templates/infra/template-repository.ts

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,58 @@ export class TemplateRepository {
385385
}
386386
}
387387

388+
private async appendFileToProofs(
389+
templateKey: TemplateKey,
390+
fileName: string,
391+
fullFilePath: string,
392+
virusScanStatus: Extract<VirusScanStatus, 'PASSED' | 'FAILED'>
393+
) {
394+
const dynamoResponse = await this.client.send(
395+
new UpdateCommand({
396+
TableName: this.templatesTableName,
397+
Key: templateKey,
398+
UpdateExpression:
399+
'SET files.proofs.#fileName = :virusScanResult, updatedAt = :updatedAt',
400+
ExpressionAttributeNames: {
401+
'#fileName': fileName,
402+
},
403+
ExpressionAttributeValues: {
404+
':templateStatusDeleted': 'DELETED' satisfies TemplateStatus,
405+
':templateStatusSubmitted': 'SUBMITTED' satisfies TemplateStatus,
406+
':updatedAt': new Date().toISOString(),
407+
':virusScanResult': {
408+
fileName: fullFilePath,
409+
virusScanStatus,
410+
} satisfies FileDetails,
411+
},
412+
ConditionExpression:
413+
'attribute_not_exists(files.proofs.#fileName) and not templateStatus in (:templateStatusDeleted, :templateStatusSubmitted)',
414+
ReturnValues: 'ALL_NEW',
415+
})
416+
);
417+
418+
return dynamoResponse.Attributes;
419+
}
420+
421+
private async updateStatusToProofAvailable(templateKey: TemplateKey) {
422+
await this.client.send(
423+
new UpdateCommand({
424+
TableName: this.templatesTableName,
425+
Key: templateKey,
426+
UpdateExpression:
427+
'SET templateStatus = :templateStatusProofAvailable, updatedAt = :updatedAt',
428+
ExpressionAttributeValues: {
429+
':templateStatusWaitingForProof':
430+
'WAITING_FOR_PROOF' satisfies TemplateStatus,
431+
':templateStatusProofAvailable':
432+
'PROOF_AVAILABLE' satisfies TemplateStatus,
433+
':updatedAt': new Date().toISOString(),
434+
},
435+
ConditionExpression: 'templateStatus = :templateStatusWaitingForProof',
436+
})
437+
);
438+
}
439+
388440
async setLetterFileVirusScanStatusForProof(
389441
owner: string,
390442
templateId: string,
@@ -395,28 +447,20 @@ export class TemplateRepository {
395447
const templateKey = { owner, id: templateId };
396448

397449
try {
398-
await this.client.send(
399-
new UpdateCommand({
400-
TableName: this.templatesTableName,
401-
Key: templateKey,
402-
UpdateExpression:
403-
'SET files.proofs.#fileName = :virusScanResult, updatedAt = :updatedAt',
404-
ExpressionAttributeNames: {
405-
'#fileName': fileName,
406-
},
407-
ExpressionAttributeValues: {
408-
':templateStatusDeleted': 'DELETED' satisfies TemplateStatus,
409-
':templateStatusSubmitted': 'SUBMITTED' satisfies TemplateStatus,
410-
':updatedAt': new Date().toISOString(),
411-
':virusScanResult': {
412-
fileName: fullFilePath,
413-
virusScanStatus,
414-
} satisfies FileDetails,
415-
},
416-
ConditionExpression:
417-
'attribute_not_exists(files.proofs.#fileName) and not templateStatus in (:templateStatusDeleted, :templateStatusSubmitted)',
418-
})
450+
const updatedItem = await this.appendFileToProofs(
451+
templateKey,
452+
fileName,
453+
fullFilePath,
454+
virusScanStatus
419455
);
456+
457+
// we do not want to try and update the status to PROOF_AVAILABLE if the scan has not passed or if the status has already been changed by another process
458+
if (
459+
virusScanStatus === 'FAILED' ||
460+
updatedItem?.templateStatus !== 'WAITING_FOR_PROOF'
461+
) {
462+
return;
463+
}
420464
} catch (error) {
421465
if (error instanceof ConditionalCheckFailedException) {
422466
logger.error(
@@ -427,34 +471,13 @@ export class TemplateRepository {
427471

428472
// the second update has a stronger condition than the first, so if this one fails no need to try the second
429473
return;
430-
} else {
431-
throw error;
432474
}
433-
}
434475

435-
// we do not want to try and update the status to PROOF_AVAILABLE if the scan has not passed
436-
if (virusScanStatus === 'FAILED') {
437-
return;
476+
throw error;
438477
}
439478

440479
try {
441-
await this.client.send(
442-
new UpdateCommand({
443-
TableName: this.templatesTableName,
444-
Key: templateKey,
445-
UpdateExpression:
446-
'SET templateStatus = :templateStatusProofAvailable, updatedAt = :updatedAt',
447-
ExpressionAttributeValues: {
448-
':templateStatusWaitingForProof':
449-
'WAITING_FOR_PROOF' satisfies TemplateStatus,
450-
':templateStatusProofAvailable':
451-
'PROOF_AVAILABLE' satisfies TemplateStatus,
452-
':updatedAt': new Date().toISOString(),
453-
},
454-
ConditionExpression:
455-
'templateStatus = :templateStatusWaitingForProof',
456-
})
457-
);
480+
await this.updateStatusToProofAvailable(templateKey);
458481
} catch (error) {
459482
if (error instanceof ConditionalCheckFailedException) {
460483
logger.error('Conditional check setting template status', error, {

0 commit comments

Comments
 (0)