Skip to content

Commit d106d48

Browse files
committed
CCM-10548: disable field validation for RTL letters
1 parent 4e27592 commit d106d48

File tree

9 files changed

+292
-70
lines changed

9 files changed

+292
-70
lines changed

infrastructure/terraform/components/acct/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes |
2020
| <a name="input_initial_cli_secrets_provision_override"></a> [initial\_cli\_secrets\_provision\_override](#input\_initial\_cli\_secrets\_provision\_override) | A map of default value to intialise SSM secret values with. Only useful for initial setup of the account due to lifecycle rules. | `map(string)` | `{}` | no |
2121
| <a name="input_kms_deletion_window"></a> [kms\_deletion\_window](#input\_kms\_deletion\_window) | When a kms key is deleted, how long should it wait in the pending deletion state? | `string` | `"30"` | no |
22-
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the account (across all environments) | <pre>map(object({<br/> enable_polling = bool<br/> default_supplier = optional(bool)<br/> }))</pre> | `{}` | no |
22+
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the account (across all environments) | <pre>map(object({<br> enable_polling = bool<br> default_supplier = optional(bool)<br> }))</pre> | `{}` | no |
2323
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no |
2424
| <a name="input_oam_sink_id"></a> [oam\_sink\_id](#input\_oam\_sink\_id) | The ID of the Cloudwatch OAM sink in the appropriate observability account. | `string` | `""` | no |
2525
| <a name="input_observability_account_id"></a> [observability\_account\_id](#input\_observability\_account\_id) | The Observability Account ID that needs access | `string` | n/a | yes |
@@ -28,7 +28,7 @@
2828
| <a name="input_root_domain_name"></a> [root\_domain\_name](#input\_root\_domain\_name) | The service's root DNS root nameespace, like nonprod.nhsnotify.national.nhs.uk | `string` | `"nonprod.nhsnotify.national.nhs.uk"` | no |
2929
| <a name="input_support_sandbox_environments"></a> [support\_sandbox\_environments](#input\_support\_sandbox\_environments) | Does this account support dev sandbox environments? | `bool` | `false` | no |
3030
| <a name="input_vpc_cidr"></a> [vpc\_cidr](#input\_vpc\_cidr) | n/a | `string` | `"10.0.0.0/16"` | no |
31-
| <a name="input_vpc_subnet_cidr_bits"></a> [vpc\_subnet\_cidr\_bits](#input\_vpc\_subnet\_cidr\_bits) | Number of additional bits to use for subnetting the VPC CIDR block. The bits are evently distributed | <pre>object({<br/> public = number<br/> private = number<br/> })</pre> | <pre>{<br/> "private": 3,<br/> "public": 12<br/>}</pre> | no |
31+
| <a name="input_vpc_subnet_cidr_bits"></a> [vpc\_subnet\_cidr\_bits](#input\_vpc\_subnet\_cidr\_bits) | Number of additional bits to use for subnetting the VPC CIDR block. The bits are evently distributed | <pre>object({<br> public = number<br> private = number<br> })</pre> | <pre>{<br> "private": 3,<br> "public": 12<br>}</pre> | no |
3232
## Modules
3333

3434
| Name | Source | Version |
@@ -51,6 +51,7 @@
5151
| <a name="output_s3_buckets"></a> [s3\_buckets](#output\_s3\_buckets) | n/a |
5252
| <a name="output_vpc_nat_ips"></a> [vpc\_nat\_ips](#output\_vpc\_nat\_ips) | n/a |
5353
| <a name="output_vpc_subnets"></a> [vpc\_subnets](#output\_vpc\_subnets) | n/a |
54+
5455
<!-- vale on -->
5556
<!-- markdownlint-enable -->
5657
<!-- END_TF_DOCS -->

infrastructure/terraform/components/app/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
| <a name="input_event_delivery_logging_success_sample_percentage"></a> [event\_delivery\_logging\_success\_sample\_percentage](#input\_event\_delivery\_logging\_success\_sample\_percentage) | Enable caching of events to an S3 bucket | `number` | `0` | no |
3737
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes |
3838
| <a name="input_kms_deletion_window"></a> [kms\_deletion\_window](#input\_kms\_deletion\_window) | When a kms key is deleted, how long should it wait in the pending deletion state? | `string` | `"30"` | no |
39-
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment | <pre>map(object({<br/> enable_polling = bool<br/> default_supplier = optional(bool)<br/> }))</pre> | `{}` | no |
39+
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment | <pre>map(object({<br> enable_polling = bool<br> default_supplier = optional(bool)<br> }))</pre> | `{}` | no |
4040
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no |
4141
| <a name="input_observability_account_id"></a> [observability\_account\_id](#input\_observability\_account\_id) | The Observability Account ID that needs access | `string` | n/a | yes |
4242
| <a name="input_parent_acct_environment"></a> [parent\_acct\_environment](#input\_parent\_acct\_environment) | Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments | `string` | `"main"` | no |
@@ -62,6 +62,7 @@
6262
|------|-------------|
6363
| <a name="output_amplify"></a> [amplify](#output\_amplify) | n/a |
6464
| <a name="output_deployment"></a> [deployment](#output\_deployment) | Deployment details used for post-deployment scripts |
65+
6566
<!-- vale on -->
6667
<!-- markdownlint-enable -->
6768
<!-- END_TF_DOCS -->

infrastructure/terraform/components/branch/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
## Outputs
3030

3131
No outputs.
32+
3233
<!-- vale on -->
3334
<!-- markdownlint-enable -->
3435
<!-- END_TF_DOCS -->

infrastructure/terraform/components/sandbox/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ No requirements.
1515
| <a name="input_environment"></a> [environment](#input\_environment) | The name of the tfscaffold environment | `string` | n/a | yes |
1616
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonymous with account short-name) | `string` | n/a | yes |
1717
| <a name="input_kms_deletion_window"></a> [kms\_deletion\_window](#input\_kms\_deletion\_window) | When a kms key is deleted, how long should it wait in the pending deletion state? | `string` | `"30"` | no |
18-
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment | <pre>map(object({<br/> enable_polling = bool<br/> default_supplier = optional(bool)<br/> }))</pre> | <pre>{<br/> "WTMMOCK": {<br/> "default_supplier": true,<br/> "enable_polling": true<br/> }<br/>}</pre> | no |
18+
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment | <pre>map(object({<br> enable_polling = bool<br> default_supplier = optional(bool)<br> }))</pre> | <pre>{<br> "WTMMOCK": {<br> "default_supplier": true,<br> "enable_polling": true<br> }<br>}</pre> | no |
1919
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no |
2020
| <a name="input_parent_acct_environment"></a> [parent\_acct\_environment](#input\_parent\_acct\_environment) | Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments | `string` | `"main"` | no |
2121
| <a name="input_project"></a> [project](#input\_project) | The name of the tfscaffold project | `string` | n/a | yes |
@@ -40,6 +40,7 @@ No requirements.
4040
| <a name="output_sftp_mock_credential_path"></a> [sftp\_mock\_credential\_path](#output\_sftp\_mock\_credential\_path) | n/a |
4141
| <a name="output_sftp_poll_lambda_name"></a> [sftp\_poll\_lambda\_name](#output\_sftp\_poll\_lambda\_name) | n/a |
4242
| <a name="output_templates_table_name"></a> [templates\_table\_name](#output\_templates\_table\_name) | n/a |
43+
4344
<!-- vale on -->
4445
<!-- markdownlint-enable -->
4546
<!-- END_TF_DOCS -->

infrastructure/terraform/modules/backend-api/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ No requirements.
1111
|------|-------------|------|---------|:--------:|
1212
| <a name="input_aws_account_id"></a> [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
1313
| <a name="input_cloudfront_distribution_arn"></a> [cloudfront\_distribution\_arn](#input\_cloudfront\_distribution\_arn) | ARN of the cloudfront distribution to serve files from | `string` | `null` | no |
14-
| <a name="input_cognito_config"></a> [cognito\_config](#input\_cognito\_config) | Cognito config | <pre>object({<br/> USER_POOL_ID : string,<br/> USER_POOL_CLIENT_ID : string<br/> })</pre> | n/a | yes |
14+
| <a name="input_cognito_config"></a> [cognito\_config](#input\_cognito\_config) | Cognito config | <pre>object({<br> USER_POOL_ID : string,<br> USER_POOL_CLIENT_ID : string<br> })</pre> | n/a | yes |
1515
| <a name="input_component"></a> [component](#input\_component) | The variable encapsulating the name of this component | `string` | n/a | yes |
1616
| <a name="input_csi"></a> [csi](#input\_csi) | CSI from the parent component | `string` | n/a | yes |
1717
| <a name="input_enable_backup"></a> [enable\_backup](#input\_enable\_backup) | Enable Backups for the DynamoDB table? | `bool` | `true` | no |
@@ -20,7 +20,7 @@ No requirements.
2020
| <a name="input_function_s3_bucket"></a> [function\_s3\_bucket](#input\_function\_s3\_bucket) | Name of S3 bucket to upload lambda artefacts to | `string` | n/a | yes |
2121
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes |
2222
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | KMS Key ARN | `string` | n/a | yes |
23-
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment | <pre>map(object({<br/> enable_polling = bool<br/> default_supplier = optional(bool)<br/> }))</pre> | n/a | yes |
23+
| <a name="input_letter_suppliers"></a> [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment | <pre>map(object({<br> enable_polling = bool<br> default_supplier = optional(bool)<br> }))</pre> | n/a | yes |
2424
| <a name="input_log_destination_arn"></a> [log\_destination\_arn](#input\_log\_destination\_arn) | Destination ARN to use for the log subscription filter | `string` | `""` | no |
2525
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no |
2626
| <a name="input_log_subscription_role_arn"></a> [log\_subscription\_role\_arn](#input\_log\_subscription\_role\_arn) | The ARN of the IAM role to use for the log subscription filter | `string` | `""` | no |
@@ -68,6 +68,7 @@ No requirements.
6868
| <a name="output_sftp_mock_credential_path"></a> [sftp\_mock\_credential\_path](#output\_sftp\_mock\_credential\_path) | n/a |
6969
| <a name="output_sftp_poll_lambda_name"></a> [sftp\_poll\_lambda\_name](#output\_sftp\_poll\_lambda\_name) | n/a |
7070
| <a name="output_templates_table_name"></a> [templates\_table\_name](#output\_templates\_table\_name) | n/a |
71+
7172
<!-- vale on -->
7273
<!-- markdownlint-enable -->
7374
<!-- END_TF_DOCS -->

lambdas/backend-api/src/__tests__/templates/api/validate-letter-template-files.test.ts

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ describe('guard duty handler', () => {
7777
},
7878
},
7979
templateStatus: 'PENDING_VALIDATION',
80+
language: 'en',
8081
}),
8182
});
8283

@@ -138,6 +139,164 @@ describe('guard duty handler', () => {
138139
);
139140
});
140141

142+
test('skips personalisation field validation for RTL languages', async () => {
143+
// arrange
144+
const { handler, mocks } = setup();
145+
146+
const event = makeGuardDutyMalwareScanResultNotificationEvent({
147+
detail: {
148+
s3ObjectDetails: {
149+
bucketName: 'quarantine-bucket',
150+
objectKey: `pdf-template/${owner}/${templateId}/${versionId}.pdf`,
151+
},
152+
scanResultDetails: {
153+
scanResultStatus: 'NO_THREATS_FOUND',
154+
},
155+
},
156+
});
157+
158+
mocks.templateRepository.get.mockResolvedValueOnce({
159+
data: mock<DatabaseTemplate>({
160+
files: {
161+
pdfTemplate: {
162+
fileName: '',
163+
virusScanStatus: 'PASSED',
164+
currentVersion: versionId,
165+
},
166+
testDataCsv: {
167+
fileName: '',
168+
virusScanStatus: 'PASSED',
169+
currentVersion: versionId,
170+
},
171+
},
172+
templateStatus: 'PENDING_VALIDATION',
173+
language: 'fa',
174+
}),
175+
});
176+
177+
const pdfData = Uint8Array.from('pdf');
178+
const csvData = Uint8Array.from('csv');
179+
180+
mocks.letterUploadRepository.download
181+
.mockResolvedValueOnce(pdfData)
182+
.mockResolvedValueOnce(csvData);
183+
184+
const pdf = {
185+
personalisationParameters: [
186+
'firstName',
187+
'parameter_1',
188+
'unknown_parameter',
189+
],
190+
parse: jest.fn(),
191+
} as unknown as TemplatePdf;
192+
mocks.TemplatePdf.mockImplementation(() => pdf);
193+
194+
const csv = {
195+
parameters: ['parameter_1', 'missing_parameter'],
196+
parse: jest.fn(),
197+
} as unknown as TestDataCsv;
198+
mocks.TestDataCsv.mockImplementation(() => csv);
199+
200+
// act
201+
await handler(event);
202+
203+
// assert
204+
expect(mocks.TemplatePdf).toHaveBeenCalledWith(
205+
{ id: templateId, owner },
206+
pdfData
207+
);
208+
expect(mocks.TestDataCsv).toHaveBeenCalledWith(csvData);
209+
expect(pdf.parse).toHaveBeenCalled();
210+
expect(csv.parse).toHaveBeenCalled();
211+
expect(mocks.validateLetterTemplateFiles).not.toHaveBeenCalled();
212+
expect(
213+
mocks.templateRepository.setLetterValidationResult
214+
).toHaveBeenCalledWith(
215+
{ id: templateId, owner },
216+
versionId,
217+
true,
218+
['firstName', 'parameter_1', 'unknown_parameter'],
219+
['parameter_1', 'missing_parameter']
220+
);
221+
});
222+
223+
test('handles missing language', async () => {
224+
// arrange
225+
const { handler, mocks } = setup();
226+
227+
const event = makeGuardDutyMalwareScanResultNotificationEvent({
228+
detail: {
229+
s3ObjectDetails: {
230+
bucketName: 'quarantine-bucket',
231+
objectKey: `pdf-template/${owner}/${templateId}/${versionId}.pdf`,
232+
},
233+
scanResultDetails: {
234+
scanResultStatus: 'NO_THREATS_FOUND',
235+
},
236+
},
237+
});
238+
239+
mocks.templateRepository.get.mockResolvedValueOnce({
240+
data: mock<DatabaseTemplate>({
241+
files: {
242+
pdfTemplate: {
243+
fileName: '',
244+
virusScanStatus: 'PASSED',
245+
currentVersion: versionId,
246+
},
247+
testDataCsv: {
248+
fileName: '',
249+
virusScanStatus: 'PASSED',
250+
currentVersion: versionId,
251+
},
252+
},
253+
templateStatus: 'PENDING_VALIDATION',
254+
language: undefined,
255+
}),
256+
});
257+
258+
const pdfData = Uint8Array.from('pdf');
259+
const csvData = Uint8Array.from('csv');
260+
261+
mocks.letterUploadRepository.download
262+
.mockResolvedValueOnce(pdfData)
263+
.mockResolvedValueOnce(csvData);
264+
265+
const pdf = {
266+
personalisationParameters: [
267+
'firstName',
268+
'parameter_1',
269+
'unknown_parameter',
270+
],
271+
parse: jest.fn(),
272+
} as unknown as TemplatePdf;
273+
mocks.TemplatePdf.mockImplementation(() => pdf);
274+
275+
const csv = {
276+
parameters: ['parameter_1', 'missing_parameter'],
277+
parse: jest.fn(),
278+
} as unknown as TestDataCsv;
279+
mocks.TestDataCsv.mockImplementation(() => csv);
280+
mocks.validateLetterTemplateFiles.mockReturnValueOnce(false);
281+
282+
// act
283+
await handler(event);
284+
285+
// assert
286+
expect(pdf.parse).toHaveBeenCalled();
287+
expect(csv.parse).toHaveBeenCalled();
288+
expect(mocks.validateLetterTemplateFiles).toHaveBeenCalled();
289+
expect(
290+
mocks.templateRepository.setLetterValidationResult
291+
).toHaveBeenCalledWith(
292+
{ id: templateId, owner },
293+
versionId,
294+
false,
295+
['firstName', 'parameter_1', 'unknown_parameter'],
296+
['parameter_1', 'missing_parameter']
297+
);
298+
});
299+
141300
test('loads the template data and associated files (pdf only), validates the file contents and saves the result to the database', async () => {
142301
const { handler, mocks } = setup();
143302

@@ -164,6 +323,7 @@ describe('guard duty handler', () => {
164323
testDataCsv: undefined,
165324
},
166325
templateStatus: 'PENDING_VALIDATION',
326+
language: 'en',
167327
}),
168328
});
169329

@@ -403,6 +563,7 @@ describe('guard duty handler', () => {
403563
data: mock<DatabaseTemplate>({
404564
files: undefined,
405565
templateStatus: 'PENDING_VALIDATION',
566+
language: 'en',
406567
}),
407568
});
408569

@@ -443,6 +604,7 @@ describe('guard duty handler', () => {
443604
},
444605
},
445606
templateStatus: 'PENDING_VALIDATION',
607+
language: 'en',
446608
}),
447609
});
448610

@@ -483,6 +645,7 @@ describe('guard duty handler', () => {
483645
},
484646
},
485647
templateStatus: 'PENDING_VALIDATION',
648+
language: 'en',
486649
}),
487650
});
488651

@@ -523,6 +686,7 @@ describe('guard duty handler', () => {
523686
},
524687
},
525688
templateStatus: 'NOT_YET_SUBMITTED',
689+
language: 'en',
526690
}),
527691
});
528692

@@ -563,6 +727,7 @@ describe('guard duty handler', () => {
563727
},
564728
},
565729
templateStatus: 'VALIDATION_FAILED',
730+
language: 'en',
566731
}),
567732
});
568733

@@ -603,6 +768,7 @@ describe('guard duty handler', () => {
603768
},
604769
},
605770
templateStatus: 'PENDING_VALIDATION',
771+
language: 'en',
606772
}),
607773
});
608774

@@ -643,6 +809,7 @@ describe('guard duty handler', () => {
643809
},
644810
},
645811
templateStatus: 'PENDING_VALIDATION',
812+
language: 'en',
646813
}),
647814
});
648815

@@ -683,6 +850,7 @@ describe('guard duty handler', () => {
683850
},
684851
},
685852
templateStatus: 'PENDING_VALIDATION',
853+
language: 'en',
686854
}),
687855
});
688856

@@ -725,6 +893,7 @@ describe('guard duty handler', () => {
725893
},
726894
},
727895
templateStatus: 'PENDING_VALIDATION',
896+
language: 'en',
728897
}),
729898
});
730899

@@ -767,6 +936,7 @@ describe('guard duty handler', () => {
767936
},
768937
},
769938
templateStatus: 'PENDING_VALIDATION',
939+
language: 'en',
770940
}),
771941
});
772942

@@ -813,6 +983,7 @@ describe('guard duty handler', () => {
813983
},
814984
},
815985
templateStatus: 'PENDING_VALIDATION',
986+
language: 'en',
816987
}),
817988
});
818989

@@ -859,6 +1030,7 @@ describe('guard duty handler', () => {
8591030
},
8601031
},
8611032
templateStatus: 'PENDING_VALIDATION',
1033+
language: 'en',
8621034
}),
8631035
});
8641036

@@ -909,6 +1081,7 @@ describe('guard duty handler', () => {
9091081
},
9101082
},
9111083
templateStatus: 'PENDING_VALIDATION',
1084+
language: 'en',
9121085
}),
9131086
});
9141087

@@ -963,6 +1136,7 @@ describe('guard duty handler', () => {
9631136
},
9641137
},
9651138
templateStatus: 'PENDING_VALIDATION',
1139+
language: 'en',
9661140
}),
9671141
});
9681142

0 commit comments

Comments
 (0)