diff --git a/internal/datastore/src/types.ts b/internal/datastore/src/types.ts index 53708cedf..26739c6c7 100644 --- a/internal/datastore/src/types.ts +++ b/internal/datastore/src/types.ts @@ -42,6 +42,7 @@ export const LetterSchemaBase = z.object({ groupId: z.string(), reasonCode: z.string().optional(), reasonText: z.string().optional(), + sha256Hash: z.string().optional(), }); export const LetterSchema = LetterSchemaBase.extend({ @@ -87,6 +88,7 @@ export const PendingLetterSchemaBase = z.object({ letterId: idRef(LetterSchema, "id"), specificationId: z.string(), groupId: z.string(), + sha256Hash: z.string().optional(), }); export const PendingLetterSchema = PendingLetterSchemaBase.extend({ diff --git a/internal/event-builders/src/letter-mapper.ts b/internal/event-builders/src/letter-mapper.ts index 27083b1b3..2367045c7 100644 --- a/internal/event-builders/src/letter-mapper.ts +++ b/internal/event-builders/src/letter-mapper.ts @@ -19,7 +19,6 @@ export function mapLetterToCloudEvent( dataschemaversion, source, subject: `letter-origin/letter-rendering/letter/${letter.id}`, - data: { domainId: letter.id as LetterStatusChangeEvent["data"]["domainId"], status: letter.status, diff --git a/lambdas/api-handler/src/contracts/letters.ts b/lambdas/api-handler/src/contracts/letters.ts index 0c3d14182..e53dd935f 100644 --- a/lambdas/api-handler/src/contracts/letters.ts +++ b/lambdas/api-handler/src/contracts/letters.ts @@ -52,6 +52,7 @@ export const GetLetterResponseResourceSchema = z groupId: z.string().optional(), reasonCode: z.string().optional(), reasonText: z.string().optional(), + sha256Hash: z.string().optional(), }) .strict(), }) @@ -66,6 +67,7 @@ export const GetLettersResponseResourceSchema = z status: LetterStatusSchema, specificationId: z.string(), groupId: z.string().optional(), + sha256Hash: z.string().optional(), }) .strict(), }) diff --git a/lambdas/api-handler/src/mappers/__tests__/letter-mapper.test.ts b/lambdas/api-handler/src/mappers/__tests__/letter-mapper.test.ts index d6440eee5..758e039d0 100644 --- a/lambdas/api-handler/src/mappers/__tests__/letter-mapper.test.ts +++ b/lambdas/api-handler/src/mappers/__tests__/letter-mapper.test.ts @@ -120,6 +120,43 @@ describe("letter-mapper", () => { }); }); + it("maps an internal Letter to a GetLetterResponse with sha256Hash when present", () => { + const date = new Date().toISOString(); + const letter: Letter = { + id: "abc123", + status: "PENDING", + supplierId: "supplier1", + specificationId: "spec123", + billingRef: "spec123", + groupId: "group123", + url: "https://example.com/letter/abc123", + createdAt: date, + updatedAt: date, + supplierStatus: "supplier1#PENDING", + supplierStatusSk: date, + ttl: 123, + sha256Hash: "abc123hash", + source: "/data-plane/letter-rendering/pdf", + subject: "letter-rendering/source/letter/letter-id", + specificationBillingId: "billing123", + }; + + const result: GetLetterResponse = mapToGetLetterResponse(letter); + + expect(result).toEqual({ + data: { + id: "abc123", + type: "Letter", + attributes: { + specificationId: "spec123", + status: "PENDING", + groupId: "group123", + sha256Hash: "abc123hash", + }, + }, + }); + }); + it("maps an internal Letter to a GetLetterResponse with reasonCode and reasonText when present", () => { const date = new Date().toISOString(); const letter: Letter = { @@ -209,4 +246,57 @@ describe("letter-mapper", () => { ], }); }); + it("maps an internal Letter collection to a GetLettersResponse with sha256Hash when present", () => { + const date = new Date().toISOString(); + const letter: Letter = { + id: "abc123", + status: "PENDING", + supplierId: "supplier1", + specificationId: "spec123", + billingRef: "spec123", + groupId: "group123", + url: "https://example.com/letter/abc123", + createdAt: date, + updatedAt: date, + supplierStatus: "supplier1#PENDING", + supplierStatusSk: date, + ttl: 123, + reasonCode: "R01", + reasonText: "Reason text", + sha256Hash: "abc123hash", + source: "/data-plane/letter-rendering/pdf", + subject: "letter-rendering/source/letter/letter-id", + specificationBillingId: "billing123", + }; + + const result: GetLettersResponse = mapToGetLettersResponse([ + letter, + letter, + ]); + + expect(result).toEqual({ + data: [ + { + id: "abc123", + type: "Letter", + attributes: { + specificationId: "spec123", + status: "PENDING", + groupId: "group123", + sha256Hash: "abc123hash", + }, + }, + { + id: "abc123", + type: "Letter", + attributes: { + specificationId: "spec123", + status: "PENDING", + groupId: "group123", + sha256Hash: "abc123hash", + }, + }, + ], + }); + }); }); diff --git a/lambdas/api-handler/src/mappers/letter-mapper.ts b/lambdas/api-handler/src/mappers/letter-mapper.ts index c31d61b34..81403d6dd 100644 --- a/lambdas/api-handler/src/mappers/letter-mapper.ts +++ b/lambdas/api-handler/src/mappers/letter-mapper.ts @@ -22,6 +22,7 @@ function letterToResourceResponse(letter: LetterBase) { groupId: letter.groupId, ...(letter.reasonCode != null && { reasonCode: letter.reasonCode }), ...(letter.reasonText != null && { reasonText: letter.reasonText }), + ...(letter.sha256Hash != null && { sha256Hash: letter.sha256Hash }), }, }; } @@ -34,6 +35,7 @@ function letterToGetLettersResourceResponse(letter: LetterBase) { status: letter.status, specificationId: letter.specificationId, groupId: letter.groupId, + ...(letter.sha256Hash != null && { sha256Hash: letter.sha256Hash }), }, }; } diff --git a/lambdas/api-handler/src/services/letter-operations.ts b/lambdas/api-handler/src/services/letter-operations.ts index a29017f65..322bd463e 100644 --- a/lambdas/api-handler/src/services/letter-operations.ts +++ b/lambdas/api-handler/src/services/letter-operations.ts @@ -36,6 +36,7 @@ function mapPendingLetterToLetterBase(pending: PendingLetterBase): LetterBase { status: "PENDING", specificationId: pending.specificationId, groupId: pending.groupId, + sha256Hash: pending.sha256Hash, }; } diff --git a/lambdas/upsert-letter/src/handler/upsert-handler.ts b/lambdas/upsert-letter/src/handler/upsert-handler.ts index a38bbf013..fa49adb05 100644 --- a/lambdas/upsert-letter/src/handler/upsert-handler.ts +++ b/lambdas/upsert-letter/src/handler/upsert-handler.ts @@ -132,6 +132,7 @@ function mapToInsertLetter( upsertRequest.data.templateId, ), url: upsertRequest.data.url, + sha256Hash: upsertRequest.data.sha256Hash, source: upsertRequest.source, subject: upsertRequest.subject, createdAt: now, diff --git a/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts b/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts index 22e56cd54..93e996fbf 100644 --- a/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts +++ b/scripts/utilities/letter-test-data/src/__test__/helpers/s3-helpers.test.ts @@ -64,6 +64,10 @@ describe("uploadFile", () => { Key: `${supplierId}/${targetFilename}`, Body: Buffer.from("fake-pdf-bytes"), ContentType: "application/pdf", + Metadata: { + sha256Hash: + "50af8d443ccf8b2777b72a9169cd0665ef4be5335b8f53543556fa0d320b135b", + }, }); }); diff --git a/scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts b/scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts index 3f25a5c79..33f7104e1 100644 --- a/scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts +++ b/scripts/utilities/letter-test-data/src/helpers/s3-helpers.ts @@ -1,4 +1,5 @@ import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import path from "node:path"; @@ -12,12 +13,16 @@ export default async function uploadFile( const s3 = new S3Client(); const filePath = path.join(__dirname, "..", "test-letters", sourceFilename); const fileContent = readFileSync(filePath); + const hash = createHash("sha256").update(fileContent).digest("hex"); const uploadParams = { Bucket: bucketName, Key: `${folder}/${targetFilename}`, Body: fileContent, ContentType: "application/pdf", + Metadata: { + sha256Hash: hash, + }, }; const command = new PutObjectCommand(uploadParams);