Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/stage-3-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ jobs:
--targetAccountGroup "nhs-notify-supplier-api-dev" \
--terraformAction "apply" \
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-supplier-api-github-deploy"
--overrideRoleName "nhs-main-acct-supplier-api-github-deploy" \
--internalRef "feature/CCM-15148"
artefact-proxies:
name: "Build proxies"
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/terraform/components/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ No requirements.
| <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 |
| <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 |
| <a name="input_letter_table_ttl_hours"></a> [letter\_table\_ttl\_hours](#input\_letter\_table\_ttl\_hours) | Number of hours to set as TTL on letters table | `number` | `24` | no |
| <a name="input_letter_variant_map"></a> [letter\_variant\_map](#input\_letter\_variant\_map) | n/a | `map(object({ supplierId = string, specId = string }))` | <pre>{<br/> "lv1": {<br/> "specId": "spec1",<br/> "supplierId": "supplier1"<br/> },<br/> "lv2": {<br/> "specId": "spec2",<br/> "supplierId": "supplier1"<br/> },<br/> "lv3": {<br/> "specId": "spec3",<br/> "supplierId": "supplier2"<br/> }<br/>}</pre> | no |
| <a name="input_letter_variant_map"></a> [letter\_variant\_map](#input\_letter\_variant\_map) | n/a | `map(object({ supplierId = string, specId = string, billingId = string }))` | <pre>{<br/> "lv1": {<br/> "billingId": "billing1",<br/> "specId": "spec1",<br/> "supplierId": "supplier1"<br/> },<br/> "lv2": {<br/> "billingId": "billing2",<br/> "specId": "spec2",<br/> "supplierId": "supplier1"<br/> },<br/> "lv3": {<br/> "billingId": "billing3",<br/> "specId": "spec3",<br/> "supplierId": "supplier2"<br/> }<br/>}</pre> | no |
| <a name="input_log_level"></a> [log\_level](#input\_log\_level) | The log level to be used in lambda functions within the component. Any log with a lower severity than the configured value will not be logged: https://docs.python.org/3/library/logging.html#levels | `string` | `"INFO"` | no |
| <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 |
| <a name="input_manually_configure_mtls_truststore"></a> [manually\_configure\_mtls\_truststore](#input\_manually\_configure\_mtls\_truststore) | Manually manage the truststore used for API Gateway mTLS (e.g. for prod environment) | `bool` | `false` | no |
Expand Down
8 changes: 4 additions & 4 deletions infrastructure/terraform/components/api/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ variable "eventpub_control_plane_bus_arn" {
}

variable "letter_variant_map" {
type = map(object({ supplierId = string, specId = string }))
type = map(object({ supplierId = string, specId = string, billingId = string }))
default = {
"lv1" = { supplierId = "supplier1", specId = "spec1" },
"lv2" = { supplierId = "supplier1", specId = "spec2" },
"lv3" = { supplierId = "supplier2", specId = "spec3" }
"lv1" = { supplierId = "supplier1", specId = "spec1", billingId = "billing1" },
"lv2" = { supplierId = "supplier1", specId = "spec2", billingId = "billing2" },
"lv3" = { supplierId = "supplier2", specId = "spec3", billingId = "billing3" }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function createLetter(
source: "/data-plane/letter-rendering/pdf",
subject: `client/1/letter-request/${letterId}`,
billingRef: "specification1",
specificationBillingId: "billing1",
};
}

Expand Down
1 change: 1 addition & 0 deletions internal/datastore/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const LetterSchema = LetterSchemaBase.extend({
source: z.string(),
subject: z.string(),
billingRef: z.string(),
specificationBillingId: z.string(),
}).describe("Letter");

/**
Expand Down
2 changes: 1 addition & 1 deletion internal/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@
"typecheck": "tsc --noEmit"
},
"types": "dist/index.d.ts",
"version": "1.0.13"
"version": "1.0.14"
}
7 changes: 7 additions & 0 deletions internal/events/src/domain/letter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ The identifier will be included as the origin domain in the subject of any corre
examples: ["1y3q9v1zzzz"],
}),

specificationBillingId: z.string().meta({
title: "Specification Billing ID",
description:
"The billing ID from the letter specification which was used to produce a letter pack for this request.",
examples: ["1y3q9v1zzzz"],
}),

supplierId: z.string().meta({
title: "Supplier ID",
description: "Supplier ID allocated to the letter during creation.",
Expand Down
2 changes: 2 additions & 0 deletions internal/events/src/events/__tests__/letter-mapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe("letter-mapper", () => {
updatedAt: "2025-11-24T15:55:18.000Z",
source: "letter-rendering/source/test",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
} as Letter;
const source = "/data-plane/supplier-api/nhs-supplier-api-dev/main/letters";
const event = mapLetterToCloudEvent(letter, source);
Expand All @@ -35,6 +36,7 @@ describe("letter-mapper", () => {
status: "PRINTED",
specificationId: "spec1",
billingRef: "spec1",
specificationBillingId: "billing123",
supplierId: "supplier1",
groupId: "group1",
reasonCode: "R02",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe("LetterStatus event validations", () => {
billingRef: "1y3q9v1zzzz",
groupId: "client_template",
status,
specificationBillingId: "billing123",
}),
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"source": "/data-plane/letter-rendering/prod/render-pdf",
"subject": "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5"
},
"specificationBillingId": "billing123",
"specificationId": "1y3q9v1zzzz",
"status": "ACCEPTED",
"supplierId": "supplier1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"event": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"source": "/data-plane/letter-rendering/prod/render-pdf"
},
"specificationBillingId": "billing123",
"specificationId": "1y3q9v1zzzz",
"status": "ACCEPTED",
"supplierId": "supplier1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"source": "/data-plane/letter-rendering/prod/render-pdf",
"subject": "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5"
},
"specificationBillingId": "billing123",
"specificationId": "1y3q9v1zzzz",
"status": "ACCEPTED",
"supplierId": "supplier1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"reasonCode": "RNIB",
"reasonText": "RNIB",
"specificationBillingId": "billing123",
"specificationId": "1y3q9v1zzzz",
"status": "FORWARDED",
"supplierId": "supplier1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"reasonCode": "R07",
"reasonText": "No such address",
"specificationBillingId": "billing123",
"specificationId": "1y3q9v1zzzz",
"status": "RETURNED",
"supplierId": "supplier1"
Expand Down
1 change: 1 addition & 0 deletions internal/events/src/events/letter-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function mapLetterToCloudEvent(
status: letter.status,
specificationId: letter.specificationId,
billingRef: letter.billingRef,
specificationBillingId: letter.specificationBillingId,
supplierId: letter.supplierId,
groupId: letter.groupId,
reasonCode: letter.reasonCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe("letter-mapper", () => {
ttl: 123,
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: PatchLetterResponse = mapToPatchLetterResponse(letter);
Expand Down Expand Up @@ -64,6 +65,7 @@ describe("letter-mapper", () => {
reasonText: "Reason text",
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: PatchLetterResponse = mapToPatchLetterResponse(letter);
Expand Down Expand Up @@ -100,6 +102,7 @@ describe("letter-mapper", () => {
ttl: 123,
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: GetLetterResponse = mapToGetLetterResponse(letter);
Expand Down Expand Up @@ -136,6 +139,7 @@ describe("letter-mapper", () => {
reasonText: "Reason text",
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: GetLetterResponse = mapToGetLetterResponse(letter);
Expand Down Expand Up @@ -174,6 +178,7 @@ describe("letter-mapper", () => {
reasonText: "Reason text",
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};

const result: GetLettersResponse = mapToGetLettersResponse([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function makeLetter(id: string, status: Letter["status"]): Letter {
reasonText: "Reason text",
source: "/data-plane/letter-rendering/pdf",
subject: "letter-rendering/source/letter/letter-id",
specificationBillingId: "billing123",
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ function generateLetter(status: LetterStatus, id?: string): Letter {
supplierStatus: `supplier1#${status}`,
supplierStatusSk: "2025-12-10T11:12:54Z#1",
ttl: 1_234_567_890,
specificationBillingId: "billing1",
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe("createDependenciesContainer", () => {
lv1: {
supplierId: "supplier1",
specId: "spec1",
billingId: "billing1",
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ describe("lambdaEnv", () => {
process.env.VARIANT_MAP = `{
"lv1": {
"supplierId": "supplier1",
"specId": "spec1"
"specId": "spec1",
"billingId": "billing1"
}
}`;

Expand All @@ -31,6 +32,7 @@ describe("lambdaEnv", () => {
lv1: {
supplierId: "supplier1",
specId: "spec1",
billingId: "billing1",
},
},
});
Expand Down
1 change: 1 addition & 0 deletions lambdas/supplier-allocator/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const LetterVariantSchema = z.record(
z.object({
supplierId: z.string(),
specId: z.string(),
billingId: z.string(),
}),
);
export type LetterVariant = z.infer<typeof LetterVariantSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,16 @@ function makeDeps(overrides: Partial<Deps> = {}): Deps {

const env = {
VARIANT_MAP: {
"variant-1": { supplierId: "supplier-1", specId: "spec-1" },
"variant-2": { supplierId: "supplier-2", specId: "spec-2" },
"variant-1": {
supplierId: "supplier-1",
specId: "spec-1",
billingId: "billing-1",
},
"variant-2": {
supplierId: "supplier-2",
specId: "spec-2",
billingId: "billing-2",
},
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "../services/supplier-config";
import { Deps } from "../config/deps";

type SupplierSpec = { supplierId: string; specId: string };
type SupplierSpec = { supplierId: string; specId: string; billingId: string };
type PreparedEvents = LetterRequestPreparedEventV2 | LetterRequestPreparedEvent;

// small envelope that must exist in all inputs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function generateLetter(status: LetterStatus, id?: string): Letter {
source: "test-source",
subject: "test-subject",
billingRef: "billing-ref-1",
specificationBillingId: "billing1",
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ function createSupplierStatusChangeEventWithoutSupplier(
billingRef: "1y3q9v1zzzz",
status: "RETURNED",
supplierId: "",
specificationBillingId: "billing1",
},
datacontenttype: "application/json",
dataschema:
Expand Down Expand Up @@ -151,6 +152,7 @@ function createSupplierStatusChangeEvent(
billingRef: "1y3q9v1zzzz",
status: "RETURNED",
supplierId: "supplier1",
specificationBillingId: "billing1",
},
datacontenttype: "application/json",
dataschema:
Expand Down Expand Up @@ -211,11 +213,19 @@ describe("createUpsertLetterHandler", () => {
test("processes all records successfully and returns no batch failures", async () => {
const v2message = {
letterEvent: createPreparedV2Event(),
supplierSpec: { supplierId: "supplier1", specId: "spec1" },
supplierSpec: {
supplierId: "supplier1",
specId: "spec1",
billingId: "billing1",
},
};
const v1message = {
letterEvent: createPreparedV1Event(),
supplierSpec: { supplierId: "supplier1", specId: "spec1" },
supplierSpec: {
supplierId: "supplier2",
specId: "spec2",
billingId: "billing2",
},
};

const evt: SQSEvent = createSQSEvent([
Expand Down Expand Up @@ -249,17 +259,19 @@ describe("createUpsertLetterHandler", () => {
expect(insertedV2Letter.status).toBe("PENDING");
expect(insertedV2Letter.groupId).toBe("client1campaign1template1");
expect(insertedV2Letter.source).toBe("/data-plane/letter-rendering/test");
expect(insertedV2Letter.specificationBillingId).toBe("billing1");

const insertedV1Letter = (mockedDeps.letterRepo.putLetter as jest.Mock).mock
.calls[1][0];
expect(insertedV1Letter.id).toBe("letter1");
expect(insertedV1Letter.supplierId).toBe("supplier1");
expect(insertedV1Letter.specificationId).toBe("spec1");
expect(insertedV1Letter.billingRef).toBe("spec1");
expect(insertedV1Letter.supplierId).toBe("supplier2");
expect(insertedV1Letter.specificationId).toBe("spec2");
expect(insertedV1Letter.billingRef).toBe("spec2");
expect(insertedV1Letter.url).toBe("s3://letterDataBucket/letter1.pdf");
expect(insertedV1Letter.status).toBe("PENDING");
expect(insertedV1Letter.groupId).toBe("client1campaign1template1");
expect(insertedV1Letter.source).toBe("/data-plane/letter-rendering/test");
expect(insertedV1Letter.specificationBillingId).toBe("billing2");

const updatedLetter = (
mockedDeps.letterRepo.updateLetterStatus as jest.Mock
Expand All @@ -275,7 +287,12 @@ describe("createUpsertLetterHandler", () => {
});
expect(mockMetrics.putMetric).toHaveBeenCalledWith(
"MessagesProcessed",
3,
2,
"Count",
);
expect(mockMetrics.putMetric).toHaveBeenCalledWith(
"MessagesProcessed",
1,
"Count",
);
});
Expand Down Expand Up @@ -472,14 +489,22 @@ describe("createUpsertLetterHandler", () => {
id: "7b9a03ca-342a-4150-b56b-989109c45615",
domainId: "ok",
}),
supplierSpec: { supplierId: "supplier1", specId: "spec1" },
supplierSpec: {
supplierId: "supplier1",
specId: "spec1",
billingId: "billing1",
},
};
const message2 = {
letterEvent: createPreparedV2Event({
id: "7b9a03ca-342a-4150-b56b-989109c45616",
domainId: "fail",
}),
supplierSpec: { supplierId: "supplier1", specId: "spec1" },
supplierSpec: {
supplierId: "supplier1",
specId: "spec1",
billingId: "billing1",
},
};
const evt: SQSEvent = createSQSEvent([
createSqsRecord("ok-msg", JSON.stringify(message1)),
Expand Down
Loading
Loading