From cc195035fd3837ec78646667a9bd2a83a0c939ed Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:42:01 +0000 Subject: [PATCH 01/23] ADOP-2759: upgrade java-logging from 6.1.9 to 8.0.0 - Bump com.github.hmcts:java-logging from 6.1.9 to 8.0.0 - Remove com.github.hmcts.java-logging:logging-appinsights (module was removed in java-logging v7.0.0; AppInsights telemetry is handled by the v3 agent configured in Dockerfile via applicationinsights.json) - Remove net.logstash.logback:logstash-logback-encoder (was only a transitive requirement of the now-removed logging-appinsights module) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build.gradle | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 93d2cecd6..86992e75f 100644 --- a/build.gradle +++ b/build.gradle @@ -349,10 +349,7 @@ dependencies { } implementation group: 'com.github.hmcts', name: 'ccd-client', version: '4.9.2' implementation group: 'com.github.hmcts', name: 'idam-java-client', version: '3.0.4' - implementation group: 'com.github.hmcts', name: 'java-logging', version: '6.1.9' - implementation group: 'com.github.hmcts.java-logging', name: 'logging-appinsights', version: '6.1.9' - // required by logging-appinsights - implementation group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '8.1' + implementation group: 'com.github.hmcts', name: 'java-logging', version: '8.0.0' // send-letter-client requires upgrade but causes gradle dependency exceptions ADOP-2686 implementation group: 'com.github.hmcts', name: 'send-letter-client', version: '4.0.4' From 61631a1b7bf2e4706e6dcf1bdd987841832ac78f Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:20:54 +0000 Subject: [PATCH 02/23] Update functional test fixtures and Docmosis template reference - Update English adoption application summary Docmosis template to FL-ADO-APP-ENG-00953-R2.docx - Remove deprecated localAuthorityName, localAuthorityContactName, localAuthorityPhoneNumber and localAuthorityContactEmail fields from functional test case data (superseded by adopAgency* equivalents) - Reorder JSON fields alphabetically and normalise indentation across all functional test casedata fixtures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- bin/functionalTest/application.yaml | 3 +- ...ck-casedata-application-payment-added.json | 14 +- ...k-casedata-application-payment-failed.json | 14 +- ...asedata-application-payment-not-ready.json | 14 +- ...ck-casedata-application-payment-ready.json | 19 +- ...asedata-application-validation-failed.json | 14 +- .../ccd-callback-casedata-application.json | 15 +- ...-caseworker-add-notes-about-to-submit.json | 255 ++++++++++++++++ .../response-application-payment-added.json | 26 +- .../response-application-payment-failed.json | 26 +- ...esponse-application-payment-not-ready.json | 93 +++--- .../response-application-payment-ready.json | 28 +- ...esponse-application-validation-failed.json | 26 +- .../casedata/response-application.json | 93 +++--- ...-caseworker-add-notes-about-to-submit.json | 281 ++++++++++++++++++ 15 files changed, 785 insertions(+), 136 deletions(-) create mode 100644 bin/functionalTest/casedata/request/request-caseworker-add-notes-about-to-submit.json create mode 100644 bin/functionalTest/casedata/response/response-caseworker-add-notes-about-to-submit.json diff --git a/bin/functionalTest/application.yaml b/bin/functionalTest/application.yaml index be694f928..ded49e636 100644 --- a/bin/functionalTest/application.yaml +++ b/bin/functionalTest/application.yaml @@ -41,6 +41,7 @@ uk: LOCAL_COURT_APPLICATION_SUBMITTED: '2960d8dd-ab3e-4265-a71d-2d81d0fd84c8' CITIZEN_DRAFT_APPLICATION_EXPIRING_ALERT: '' + s2s-authorised: services: ${S2S_AUTHORISED_SERVICES:ccd_data} @@ -77,7 +78,7 @@ case_document_am: docmosis: templates: english: - ADOPTION_APPLICATION_SUMMARY: FL-ADO-APP-ENG-00953.docx + ADOPTION_APPLICATION_SUMMARY: FL-ADO-APP-ENG-00953-R2.docx welsh: ADOPTION_APPLICATION_SUMMARY: FL-ADO-APP-WEL-00955.docx diff --git a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-added.json b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-added.json index fa7c09455..2dc165907 100644 --- a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-added.json +++ b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-added.json @@ -5,8 +5,9 @@ "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -14,8 +15,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -134,7 +136,7 @@ "value": { "placementOrderId": "1646222494045", "placementOrderDate": "2021-12-12", - "placementOrderType": "some order2", + "placementOrderType": "Adoption order", "placementOrderCourt": "london court", "placementOrderNumber": "12345" } @@ -212,6 +214,8 @@ ], "solicitorName": null, "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "solicitorPhoneNumber": null, "birthFatherAddressCountry": null, "applicant2AddressTown": "NOTTINGHAM", diff --git a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-failed.json b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-failed.json index 0de5272d1..969e28422 100644 --- a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-failed.json +++ b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-failed.json @@ -5,8 +5,9 @@ "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -14,8 +15,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -134,7 +136,7 @@ "value": { "placementOrderId": "1646222494045", "placementOrderDate": "2021-12-12", - "placementOrderType": "some order2", + "placementOrderType": "Adoption order", "placementOrderCourt": "london court", "placementOrderNumber": "12345" } @@ -212,6 +214,8 @@ ], "solicitorName": null, "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "solicitorPhoneNumber": null, "birthFatherAddressCountry": null, "applicant2AddressTown": "NOTTINGHAM", diff --git a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-not-ready.json b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-not-ready.json index 03f75ae9b..2fc27754c 100644 --- a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-not-ready.json +++ b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-not-ready.json @@ -5,8 +5,9 @@ "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -14,8 +15,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -134,7 +136,7 @@ "value": { "placementOrderId": "1646222494045", "placementOrderDate": "2021-12-12", - "placementOrderType": "some order2", + "placementOrderType": "Adoption order", "placementOrderCourt": "london court", "placementOrderNumber": "12345" } @@ -212,6 +214,8 @@ ], "solicitorName": null, "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "solicitorPhoneNumber": null, "birthFatherAddressCountry": null, "applicant2AddressTown": "NOTTINGHAM", diff --git a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-ready.json b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-ready.json index 6cb5ff350..66ccd318e 100644 --- a/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-ready.json +++ b/bin/functionalTest/casedata/ccd-callback-casedata-application-payment-ready.json @@ -5,8 +5,9 @@ "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -14,8 +15,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -135,7 +137,7 @@ "value": { "placementOrderId": "1646222494045", "placementOrderDate": "2021-12-12", - "placementOrderType": "some order2", + "placementOrderType": "Adoption order", "placementOrderCourt": "london court", "placementOrderNumber": "12345" } @@ -213,6 +215,8 @@ ], "solicitorName": null, "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "solicitorPhoneNumber": null, "birthFatherAddressCountry": null, "applicant2AddressTown": "NOTTINGHAM", @@ -242,6 +246,11 @@ "Irish", "Other" ], + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "applicant2Email": null, "otherParentAddressCounty": "CITY OF WESTMINSTER", "applicant2LanguagePreference": null, diff --git a/bin/functionalTest/casedata/ccd-callback-casedata-application-validation-failed.json b/bin/functionalTest/casedata/ccd-callback-casedata-application-validation-failed.json index f11892136..51f051c84 100644 --- a/bin/functionalTest/casedata/ccd-callback-casedata-application-validation-failed.json +++ b/bin/functionalTest/casedata/ccd-callback-casedata-application-validation-failed.json @@ -5,8 +5,9 @@ "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -14,8 +15,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -134,7 +136,7 @@ "value": { "placementOrderId": "1646222494045", "placementOrderDate": "2021-12-12", - "placementOrderType": "some order2", + "placementOrderType": "Adoption order", "placementOrderCourt": "london court", "placementOrderNumber": "12345" } @@ -212,6 +214,8 @@ ], "solicitorName": null, "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "solicitorPhoneNumber": null, "birthFatherAddressCountry": null, "applicant2AddressTown": "NOTTINGHAM", diff --git a/bin/functionalTest/casedata/ccd-callback-casedata-application.json b/bin/functionalTest/casedata/ccd-callback-casedata-application.json index e8dd53148..8456c3c60 100644 --- a/bin/functionalTest/casedata/ccd-callback-casedata-application.json +++ b/bin/functionalTest/casedata/ccd-callback-casedata-application.json @@ -5,8 +5,9 @@ "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -14,8 +15,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -134,7 +136,7 @@ "value": { "placementOrderId": "1646222494045", "placementOrderDate": "2021-12-12", - "placementOrderType": "some order2", + "placementOrderType": "Adoption order", "placementOrderCourt": "london court", "placementOrderNumber": "12345" } @@ -212,6 +214,8 @@ ], "solicitorName": null, "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "solicitorPhoneNumber": null, "birthFatherAddressCountry": null, "applicant2AddressTown": "NOTTINGHAM", @@ -247,4 +251,5 @@ "applicant2LanguagePreference": null, "birthMotherOccupation": "Teacher", "applicant1AddressSameAsApplicant1": null + } diff --git a/bin/functionalTest/casedata/request/request-caseworker-add-notes-about-to-submit.json b/bin/functionalTest/casedata/request/request-caseworker-add-notes-about-to-submit.json new file mode 100644 index 000000000..805998b6b --- /dev/null +++ b/bin/functionalTest/casedata/request/request-caseworker-add-notes-about-to-submit.json @@ -0,0 +1,255 @@ +{ + "siblings": [ + { + "id": "1646222663145", + "value": { + "siblingId": "1646222663145", + "siblingRelation": "brother", + "siblingPoType": "adoptionOrder", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" + } + }, + { + "id": "1646222699254", + "value": { + "siblingId": "1646222699254", + "siblingRelation": "sister", + "siblingPoType": "other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" + } + } + ], + "otherParentAddressNotKnownReason": "", + "otherParentNationality": null, + "birthFatherAddressKnown": "Yes", + "birthFatherNotAliveReason": "", + "birthFatherOccupation": "unknown", + "hyphenatedCaseRef": "1234-5678-9012-3456", + "birthMotherFirstName": "Mother", + "birthMotherNameOnCertificate": null, + "applicant2StatementOfTruth": null, + "birthMotherAddressNotKnownReason": "Some reason", + "solicitorFirm": null, + "childrenLastName": "child", + "otherParentAddressKnown": "Yes", + "applicant1EmailAddress": "abid@gmail.co", + "applicant2AddressCountry": "NOTTINGHAM CITY", + "childrenDateOfBirth": "2015-12-12", + "applicant1Email": "citizen.automation@mailinator.com", + "applicant2EmailAddress": "someone@gmail.com", + "otherParentFirstName": "other", + "birthFatherOtherNationalities": [], + "documentsUploaded": [], + "otherParentNotAliveReason": null, + "childrenAdditionalNationalities": [ + { + "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", + "value": { + "country": "China" + } + }, + { + "id": "82b96f32-3f85-4b69-abf6-fd4321ca7501", + "value": { + "country": "India" + } + } + ], + "familyCourtEmailId": "adoptionproject@mailinator.com", + "birthFatherNameOnCertificate": "Yes", + "applicant2Address1": "2 WHITE ROSE WR2, BROAD STREET", + "birthFatherAddressNotKnownReason": "", + "applicant2Address2": "", + "documentsGenerated": [], + "birthFatherFirstName": "Father", + "dateChildMovedIn": "2021-10-12", + "applicant1AddressPostCode": "NG1 1JB", + "localAuthorityName": "laname", + "localAuthorityContactName": "contact name1", + "localAuthorityPhoneNumber": "01234567890", + "localAuthorityContactEmail": "agency1@email.co.uk", + "adopAgencyOrLaName": "agency1", + "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaPhoneNumber": "01234567890", + "adopAgencyAddressLine1": "address", + "adopAgencyTown": "town", + "adopAgencyPostcode": "aa14aa", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "applicant1ContactDetailsConsent": "Yes", + "applicant2Nationality": null, + "applicant1LanguagePreference": "ENGLISH", + "birthMotherAddressPostCode": "NG1 1JB", + "birthMotherOtherNationalities": [], + "otherParentOtherNationalities": [], + "applyingWith": "withSpouseOrCivilPartner", + "applicant1AdditionalNationalities": [], + "applicant2LastName": "sasass", + "applicant1Nationality": null, + "applicant2ContactDetailsConsent": "Yes", + "applicant1PhoneNumber": "0987654321", + "applicant2AdditionalNames": [], + "childrenLastNameAfterAdoption": "child", + "applicant1SotFullName": null, + "hasAnotherAdopAgencyOrLA": "No", + "socialWorkerEmail": "socialw@email.com", + "socialWorkerPhoneNumber": "01234567893", + "addAnotherSiblingPlacementOrder": "No", + "createdDate": null, + "childLocalAuthority": "la", + "applicant2Occupation": "tester", + "applicant2PhoneNumber": "12121212121", + "applicant1ContactDetails": null, + "findFamilyCourt": "Yes", + "applicant2SotFullName": null, + "birthFatherAddressTown": "LONDON", + "applicant1Address1": "1 TRIVETT SQUARE", + "applicant1Address2": "", + "applicant2AdditionalNationalities": [], + "dueDate": null, + "childrenOtherSexAtBirth": "something", + "solicitorEmail": null, + "otherParentAddressTown": "LONDON", + "birthMotherNotAliveReason": "", + "otherParentAddressPostCode": "SW1A 1AA", + "applicant1AddressCountry": "NOTTINGHAM CITY", + "placementOrderCourt": "xyz", + "familyCourtName": "xyz", + "birthMotherAddressTown": "NOTTINGHAM", + "birthFatherAddress3": null, + "birthFatherAddress2": "", + "birthFatherAddress1": "BUCKINGHAM PALACE", + "applicant1CannotUpload": "Yes", + "placementOrders": [ + { + "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", + "value": { + "placementOrderId": "1646222468418", + "placementOrderDate": "2021-12-12", + "placementOrderCourt": "xyz", + "placementOrderNumber": "123456" + } + }, + { + "id": "a341637f-c3f8-4a00-9d6f-c560cea7a677", + "value": { + "placementOrderId": "1646222494045", + "placementOrderDate": "2021-12-12", + "placementOrderType": "Adoption Order", + "placementOrderCourt": "london court", + "placementOrderNumber": "12345" + } + } + ], + "otherParentOccupation": null, + "birthMotherAddressCounty": "NOTTINGHAM CITY", + "applicant2ContactDetails": null, + "birthFatherStillAlive": "Yes", + "applicant2AddressPostCode": "NG1 3AL", + "applicant2FirstName": "second", + "applicant1DocumentsUploaded": [ + { + "id": "c283880e-068e-450a-b44d-e72a6191847b", + "value": { + "documentEmailContent": null, + "documentLink": { + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" + }, + "documentDateAdded": null, + "documentComment": "Uploaded by applicant", + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentType": null, + "documentFileId": null + } + } + ], + "addAnotherPlacementOrder": "No", + "selectedPlacementOrderId": "1646222468418", + "applicant1Occupation": "Software Engineer", + "adoptionDocument": {}, + "socialWorkerName": "social worker", + "applicant1FirstName": "Abid1", + "otherParentAddressCountry": null, + "birthFatherLastName": "Father", + "birthFatherNationality": [ + "British" + ], + "applicant2HasOtherNames": "No", + "applicant1AddressTown": "NOTTINGHAM", + "applicationFeeOrderSummary": {}, + "birthMotherAddress1": "2 TRIVETT SQUARE", + "applicant1HasOtherNames": "No", + "applicant1LastName": "sayyad", + "applicant1DateOfBirth": "1990-12-12", + "birthMotherAddressKnown": "Yes", + "dateSubmitted": null, + "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "applicant2DateOfBirth": "1990-12-12", + "birthFatherAddressCounty": "CITY OF WESTMINSTER", + "birthMotherAddress2": "", + "birthMotherAddress3": null, + "applicant1CannotUploadSupportingDocument": [ + "birthOrAdoptionCertificate" + ], + "birthMotherAddressCountry": null, + "otherParentLastName": "parent", + "applicant1AdditionalNames": [ + { + "id": "f736c59c-1a13-4a74-b6f4-ca202891dd94", + "value": { + "lastNames": "bbb1", + "firstNames": "aaa" + } + }, + { + "id": "81fdf807-5a47-48ca-ab64-828563dd427e", + "value": { + "lastNames": "wwww", + "firstNames": "aaa" + } + } + ], + "solicitorName": null, + "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "sister", + "selectedSiblingPoType": "adoptionOrder", + "solicitorPhoneNumber": null, + "birthFatherAddressCountry": null, + "applicant2AddressTown": "NOTTINGHAM", + "applicant2AddressSameAsApplicant1": "No", + "childrenSexAtBirth": "other", + "selectedAdoptionAgencyId": "1646222614256", + "otherApplicantRelation": "", + "applicant1StatementOfTruth": null, + "hasSiblings": "Yes", + "childrenFirstNameAfterAdoption": "child", + "birthFatherAddressPostCode": "SW1A 1AA", + "otherParentAddress1": "BUCKINGHAM PALACE", + "otherParentAddress2": "", + "otherParentNameOnCertificate": null, + "birthMotherLastName": "Mother", + "otherParentAddress3": null, + "otherParentStillAlive": "Yes", + "applicationPayments": [], + "childrenFirstName": "child", + "birthMotherNationality": [ + "British", + "Irish" + ], + "solicitorHelpingWithApplication": null, + "birthMotherStillAlive": "Yes", + "childrenNationality": [ + "British", + "Irish", + "Other" + ], + "applicant2Email": null, + "otherParentAddressCounty": "CITY OF WESTMINSTER", + "applicant2LanguagePreference": null, + "birthMotherOccupation": "Teacher", + "applicant1AddressSameAsApplicant1": null + +} diff --git a/bin/functionalTest/casedata/response-application-payment-added.json b/bin/functionalTest/casedata/response-application-payment-added.json index 8077bd88f..d6bce0cb7 100644 --- a/bin/functionalTest/casedata/response-application-payment-added.json +++ b/bin/functionalTest/casedata/response-application-payment-added.json @@ -258,21 +258,24 @@ "placementOrderDate": "2021-12-12", "placementOrderId": "1646222494045", "placementOrderNumber": "12345", - "placementOrderType": "some order2" + "placementOrderType": "Adoption order" } } ], "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -280,8 +283,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -312,10 +316,16 @@ } ] }, - "socialWorkerEmail": "socialw@email.com", - "socialWorkerName": "social worker", - "socialWorkerPhoneNumber": "01234567893", "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/bin/functionalTest/casedata/response-application-payment-failed.json b/bin/functionalTest/casedata/response-application-payment-failed.json index 4445b77cc..d6da25ca6 100644 --- a/bin/functionalTest/casedata/response-application-payment-failed.json +++ b/bin/functionalTest/casedata/response-application-payment-failed.json @@ -226,21 +226,24 @@ "placementOrderDate": "2021-12-12", "placementOrderId": "1646222494045", "placementOrderNumber": "12345", - "placementOrderType": "some order2" + "placementOrderType": "Adoption order" } } ], "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -248,8 +251,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -282,10 +286,16 @@ } ] }, - "socialWorkerEmail": "socialw@email.com", - "socialWorkerName": "social worker", - "socialWorkerPhoneNumber": "01234567893", "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/bin/functionalTest/casedata/response-application-payment-not-ready.json b/bin/functionalTest/casedata/response-application-payment-not-ready.json index 74664fd89..d765fac09 100644 --- a/bin/functionalTest/casedata/response-application-payment-not-ready.json +++ b/bin/functionalTest/casedata/response-application-payment-not-ready.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -46,28 +42,29 @@ "applicant1AddressSameAsApplicant1": null, "applicant1AddressTown": "NOTTINGHAM", "applicant1CannotUpload": "Yes", - "applicant1CannotUploadSupportingDocument": ["birthOrAdoptionCertificate"], + "applicant1CannotUploadSupportingDocument": ["birthOrAdoptionCertificate" + ], "applicant1ContactDetails": null, "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -101,6 +98,11 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, "applicationFeeOrderSummary": { "Fees": null, "PaymentReference": null, @@ -118,6 +120,8 @@ "birthFatherAddressPostCode": "SW1A 1AA", "birthFatherAddressTown": "LONDON", "birthFatherFirstName": "Father", + "birthFatherIdentityKnown": null, + "birthFatherLastAddressDate": null, "birthFatherLastName": "Father", "birthFatherNameOnCertificate": "Yes", "birthFatherNationality": ["British" @@ -136,14 +140,21 @@ "birthMotherAddressPostCode": "NG1 1JB", "birthMotherAddressTown": "NOTTINGHAM", "birthMotherFirstName": "Mother", + "birthMotherIdentityKnown": null, + "birthMotherLastAddressDate": null, "birthMotherLastName": "Mother", "birthMotherNameOnCertificate": null, - "birthMotherNationality": ["Irish", "British" + "birthMotherNationality": ["British", "Irish" ], "birthMotherNotAliveReason": "", "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": null, + "childSocialWorkerEmail": null, + "childSocialWorkerName": null, + "childSocialWorkerPhoneNumber": null, "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -163,7 +174,7 @@ "childrenFirstNameAfterAdoption": "child", "childrenLastName": "child", "childrenLastNameAfterAdoption": "child", - "childrenNationality": ["Irish", "British", "Other" + "childrenNationality": ["British", "Irish", "Other" ], "childrenOtherSexAtBirth": "something", "childrenSexAtBirth": "other", @@ -174,12 +185,16 @@ "documentsUploaded": [], "dueDate": null, "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", + "message": null, "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -191,6 +206,8 @@ "otherParentAddressPostCode": "SW1A 1AA", "otherParentAddressTown": "LONDON", "otherParentFirstName": "other", + "otherParentIdentityKnown": null, + "otherParentLastAddressDate": null, "otherParentLastName": "parent", "otherParentNameOnCertificate": null, "otherParentNationality": null, @@ -199,6 +216,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -217,21 +235,24 @@ "placementOrderDate": "2021-12-12", "placementOrderId": "1646222494045", "placementOrderNumber": "12345", - "placementOrderType": "some order2" + "placementOrderType": "Adoption order" } } ], "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -239,20 +260,20 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], - "socialWorkerEmail": "socialw@email.com", - "socialWorkerName": "social worker", - "socialWorkerPhoneNumber": "01234567893", - "childLocalAuthority": "la", + "socialWorkerDetails": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, "solicitorName": null, - "solicitorPhoneNumber": null + "solicitorPhoneNumber": null, + "status": null, + "typeOfAdoption": null }, "errors": ["Statement of truth must be accepted by the applicant1","Applicant1SotFullName cannot be empty or null","Statement of truth must be accepted by the applicant2","Applicant2SotFullName cannot be empty or null"], "state": null, diff --git a/bin/functionalTest/casedata/response-application-payment-ready.json b/bin/functionalTest/casedata/response-application-payment-ready.json index 30f6a7a0b..d663390ec 100644 --- a/bin/functionalTest/casedata/response-application-payment-ready.json +++ b/bin/functionalTest/casedata/response-application-payment-ready.json @@ -144,6 +144,16 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -217,21 +227,24 @@ "placementOrderDate": "2021-12-12", "placementOrderId": "1646222494045", "placementOrderNumber": "12345", - "placementOrderType": "some order2" + "placementOrderType": "Adoption order" } } ], "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -239,15 +252,12 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], - "socialWorkerEmail": "socialw@email.com", - "socialWorkerName": "social worker", - "socialWorkerPhoneNumber": "01234567893", - "childLocalAuthority": "la", "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/bin/functionalTest/casedata/response-application-validation-failed.json b/bin/functionalTest/casedata/response-application-validation-failed.json index 42d3836a7..c5553bda5 100644 --- a/bin/functionalTest/casedata/response-application-validation-failed.json +++ b/bin/functionalTest/casedata/response-application-validation-failed.json @@ -226,21 +226,24 @@ "placementOrderDate": "2021-12-12", "placementOrderId": "1646222494045", "placementOrderNumber": "12345", - "placementOrderType": "some order2" + "placementOrderType": "Adoption order" } } ], "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -248,8 +251,9 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], @@ -282,10 +286,16 @@ } ] }, - "socialWorkerEmail": "socialw@email.com", - "socialWorkerName": "social worker", - "socialWorkerPhoneNumber": "01234567893", "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/bin/functionalTest/casedata/response-application.json b/bin/functionalTest/casedata/response-application.json index 36187faf2..f52725386 100644 --- a/bin/functionalTest/casedata/response-application.json +++ b/bin/functionalTest/casedata/response-application.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -46,28 +42,29 @@ "applicant1AddressSameAsApplicant1": null, "applicant1AddressTown": "NOTTINGHAM", "applicant1CannotUpload": "Yes", - "applicant1CannotUploadSupportingDocument": ["birthOrAdoptionCertificate"], + "applicant1CannotUploadSupportingDocument": ["birthOrAdoptionCertificate" + ], "applicant1ContactDetails": null, "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -101,6 +98,11 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, "applicationFeeOrderSummary": { "Fees": null, "PaymentReference": null, @@ -118,6 +120,8 @@ "birthFatherAddressPostCode": "SW1A 1AA", "birthFatherAddressTown": "LONDON", "birthFatherFirstName": "Father", + "birthFatherIdentityKnown": null, + "birthFatherLastAddressDate": null, "birthFatherLastName": "Father", "birthFatherNameOnCertificate": "Yes", "birthFatherNationality": ["British" @@ -136,14 +140,21 @@ "birthMotherAddressPostCode": "NG1 1JB", "birthMotherAddressTown": "NOTTINGHAM", "birthMotherFirstName": "Mother", + "birthMotherIdentityKnown": null, + "birthMotherLastAddressDate": null, "birthMotherLastName": "Mother", "birthMotherNameOnCertificate": null, - "birthMotherNationality": ["Irish", "British" + "birthMotherNationality": ["British", "Irish" ], "birthMotherNotAliveReason": "", "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": null, + "childSocialWorkerEmail": null, + "childSocialWorkerName": null, + "childSocialWorkerPhoneNumber": null, "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -163,7 +174,7 @@ "childrenFirstNameAfterAdoption": "child", "childrenLastName": "child", "childrenLastNameAfterAdoption": "child", - "childrenNationality": ["Irish", "British", "Other" + "childrenNationality": ["British", "Irish", "Other" ], "childrenOtherSexAtBirth": "something", "childrenSexAtBirth": "other", @@ -174,12 +185,16 @@ "documentsUploaded": [], "dueDate": null, "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", + "message": null, "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -191,6 +206,8 @@ "otherParentAddressPostCode": "SW1A 1AA", "otherParentAddressTown": "LONDON", "otherParentFirstName": "other", + "otherParentIdentityKnown": null, + "otherParentLastAddressDate": null, "otherParentLastName": "parent", "otherParentNameOnCertificate": null, "otherParentNationality": null, @@ -199,6 +216,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -217,21 +235,24 @@ "placementOrderDate": "2021-12-12", "placementOrderId": "1646222494045", "placementOrderNumber": "12345", - "placementOrderType": "some order2" + "placementOrderType": "Adoption order" } } ], "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "Sister", + "selectedSiblingPoType": "Adoption order", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", "siblingRelation": "Brother", - "siblingPoType": "some order", - "siblingPoNumber": "1001" + "siblingPoType": "Adoption order", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" } }, { @@ -239,20 +260,20 @@ "value": { "siblingId": "1646222699254", "siblingRelation": "Sister", - "siblingPoType": "some other order", - "siblingPoNumber": "23456" + "siblingPoType": "Other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" } } ], - "socialWorkerEmail": "socialw@email.com", - "socialWorkerName": "social worker", - "socialWorkerPhoneNumber": "01234567893", - "childLocalAuthority": "la", + "socialWorkerDetails": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, "solicitorName": null, - "solicitorPhoneNumber": null + "solicitorPhoneNumber": null, + "status": "Draft", + "typeOfAdoption": null }, "errors": null, "state": null, diff --git a/bin/functionalTest/casedata/response/response-caseworker-add-notes-about-to-submit.json b/bin/functionalTest/casedata/response/response-caseworker-add-notes-about-to-submit.json new file mode 100644 index 000000000..bcd833a77 --- /dev/null +++ b/bin/functionalTest/casedata/response/response-caseworker-add-notes-about-to-submit.json @@ -0,0 +1,281 @@ +{ + "data": { + "addAnotherPlacementOrder": "No", + "addAnotherSiblingPlacementOrder": "No", + "localAuthorityName": "laname", + "localAuthorityContactName": "contact name1", + "localAuthorityPhoneNumber": "01234567890", + "localAuthorityContactEmail": "agency1@email.co.uk", + "adopAgencyOrLaName": "agency1", + "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaPhoneNumber": "01234567890", + "adopAgencyAddressLine1": "address", + "adopAgencyTown": "town", + "adopAgencyPostcode": "aa14aa", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adoptionDocument": { + "documentComment": null, + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": null, + "documentLink": null, + "documentType": null + }, + "applicant1AdditionalNames": [ + { + "id": "f736c59c-1a13-4a74-b6f4-ca202891dd94", + "value": { + "firstNames": "aaa", + "lastNames": "bbb1" + } + }, + { + "id": "81fdf807-5a47-48ca-ab64-828563dd427e", + "value": { + "firstNames": "aaa", + "lastNames": "wwww" + } + } + ], + "applicant1AdditionalNationalities": [], + "applicant1Address1": "1 TRIVETT SQUARE", + "applicant1Address2": "", + "applicant1AddressCountry": "NOTTINGHAM CITY", + "applicant1AddressPostCode": "NG1 1JB", + "applicant1AddressSameAsApplicant1": null, + "applicant1AddressTown": "NOTTINGHAM", + "applicant1CannotUpload": "Yes", + "applicant1CannotUploadSupportingDocument": ["birthOrAdoptionCertificate"], + "applicant1ContactDetails": null, + "applicant1ContactDetailsConsent": "Yes", + "applicant1DateOfBirth": "1990-12-12", + "applicant1DocumentsUploaded": [ + { + "id": "c283880e-068e-450a-b44d-e72a6191847b", + "value": { + "documentEmailContent": null, + "documentLink": { + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" + }, + "documentDateAdded": null, + "documentComment": "Uploaded by applicant", + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentType": null, + "documentFileId": null + } + } + ], + "applicant1Email": "citizen.automation@mailinator.com", + "applicant1EmailAddress": "abid@gmail.co", + "applicant1FirstName": "Abid1", + "applicant1HasOtherNames": "No", + "applicant1LanguagePreference": "ENGLISH", + "applicant1LastName": "sayyad", + "applicant1Nationality": null, + "applicant1Occupation": "Software Engineer", + "applicant1PhoneNumber": "0987654321", + "applicant1SotFullName": "null", + "applicant1StatementOfTruth": "Yes", + "applicant2AdditionalNames": [], + "applicant2AdditionalNationalities": [], + "applicant2Address1": "2 WHITE ROSE WR2, BROAD STREET", + "applicant2Address2": "", + "applicant2AddressCountry": "NOTTINGHAM CITY", + "applicant2AddressPostCode": "NG1 3AL", + "applicant2AddressSameAsApplicant1": "No", + "applicant2AddressTown": "NOTTINGHAM", + "applicant2ContactDetails": null, + "applicant2ContactDetailsConsent": "Yes", + "applicant2DateOfBirth": "1990-12-12", + "applicant2Email": null, + "applicant2EmailAddress": "someone@gmail.com", + "applicant2FirstName": "second", + "applicant2HasOtherNames": "No", + "applicant2LanguagePreference": null, + "applicant2LastName": "sasass", + "applicant2Nationality": null, + "applicant2Occupation": "tester", + "applicant2PhoneNumber": "12121212121", + "applicant2SotFullName": "null", + "applicant2StatementOfTruth": "Yes", + "applicationFeeOrderSummary": { + "Fees": null, + "PaymentReference": null, + "PaymentTotal": null + }, + "applicationPayments": [], + "applyingWith": "withSpouseOrCivilPartner", + "birthFatherAddress1": "BUCKINGHAM PALACE", + "birthFatherAddress2": "", + "birthFatherAddress3": null, + "birthFatherAddressCountry": null, + "birthFatherAddressCounty": "CITY OF WESTMINSTER", + "birthFatherAddressKnown": "Yes", + "birthFatherAddressNotKnownReason": "", + "birthFatherAddressPostCode": "SW1A 1AA", + "birthFatherAddressTown": "LONDON", + "birthFatherFirstName": "Father", + "birthFatherLastName": "Father", + "birthFatherNameOnCertificate": "Yes", + "birthFatherNationality": ["British" + ], + "birthFatherNotAliveReason": "", + "birthFatherOccupation": "unknown", + "birthFatherOtherNationalities": [], + "birthFatherStillAlive": "Yes", + "birthMotherAddress1": "2 TRIVETT SQUARE", + "birthMotherAddress2": "", + "birthMotherAddress3": null, + "birthMotherAddressCountry": null, + "birthMotherAddressCounty": "NOTTINGHAM CITY", + "birthMotherAddressKnown": "Yes", + "birthMotherAddressNotKnownReason": "Some reason", + "birthMotherAddressPostCode": "NG1 1JB", + "birthMotherAddressTown": "NOTTINGHAM", + "birthMotherFirstName": "Mother", + "birthMotherLastName": "Mother", + "birthMotherNameOnCertificate": null, + "birthMotherNationality": ["Irish", "British" + ], + "birthMotherNotAliveReason": "", + "birthMotherOccupation": "Teacher", + "birthMotherOtherNationalities": [], + "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", + "applicantLocalAuthority": null, + "applicantLocalAuthorityEmail": null, + "applicantSocialWorkerEmail": null, + "applicantSocialWorkerName": null, + "applicantSocialWorkerPhoneNumber": null, + "childrenAdditionalNationalities": [ + { + "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", + "value": { + "country": "China" + } + }, + { + "id": "82b96f32-3f85-4b69-abf6-fd4321ca7501", + "value": { + "country": "India" + } + } + ], + "childrenDateOfBirth": "2015-12-12", + "childrenFirstName": "child", + "childrenFirstNameAfterAdoption": "child", + "childrenLastName": "child", + "childrenLastNameAfterAdoption": "child", + "childrenNationality": ["Irish", "British", "Other" + ], + "childrenOtherSexAtBirth": "something", + "childrenSexAtBirth": "other", + "createdDate": null, + "dateChildMovedIn": "2021-10-12", + "dateSubmitted": null, + "documentsGenerated": [], + "documentsUploaded": [], + "dueDate": null, + "familyCourtEmailId": "adoptionproject@mailinator.com", + "placementOrderCourt": "xyz", + "familyCourtName": "xyz", + "findFamilyCourt": "Yes", + "hasAnotherAdopAgencyOrLA": "No", + "hasSiblings": "Yes", + "hyphenatedCaseRef": "1234-5678-9012-3456", + "otherApplicantRelation": "", + "otherParentAddress1": "BUCKINGHAM PALACE", + "otherParentAddress2": "", + "otherParentAddress3": null, + "otherParentAddressCountry": null, + "otherParentAddressCounty": "CITY OF WESTMINSTER", + "otherParentAddressKnown": "Yes", + "otherParentAddressNotKnownReason": "", + "otherParentAddressPostCode": "SW1A 1AA", + "otherParentAddressTown": "LONDON", + "otherParentFirstName": "other", + "otherParentLastName": "parent", + "otherParentNameOnCertificate": null, + "otherParentNationality": null, + "otherParentNotAliveReason": null, + "otherParentOccupation": null, + "otherParentOtherNationalities": [], + "otherParentStillAlive": "Yes", + "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrders": [ + { + "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", + "value": { + "placementOrderCourt": "xyz", + "placementOrderDate": "2021-12-12", + "placementOrderId": "1646222468418", + "placementOrderNumber": "123456", + "placementOrderType": null + } + }, + { + "id": "a341637f-c3f8-4a00-9d6f-c560cea7a677", + "value": { + "placementOrderCourt": "london court", + "placementOrderDate": "2021-12-12", + "placementOrderId": "1646222494045", + "placementOrderNumber": "12345", + "placementOrderType": "Adoption Order" + } + } + ], + "selectedAdoptionAgencyId": "1646222614256", + "selectedPlacementOrderId": "1646222468418", + "selectedSiblingId": "1646222699254", + "selectedSiblingRelation": "sister", + "selectedSiblingPoType": "adoptionOrder", + "siblings": [ + { + "id": "1646222663145", + "value": { + "siblingId": "1646222663145", + "siblingRelation": "brother", + "siblingPoType": "adoptionOrder", + "siblingPoNumber": "1001", + "siblingPlacementOtherType": "" + } + }, + { + "id": "1646222699254", + "value": { + "siblingId": "1646222699254", + "siblingRelation": "sister", + "siblingPoType": "other", + "siblingPoNumber": "23456", + "siblingPlacementOtherType": "other type" + } + } + ], + "solicitorEmail": null, + "solicitorFirm": null, + "solicitorHelpingWithApplication": null, + "solicitorName": null, + "solicitorPhoneNumber": null, + "caseNote": [ + { + "id": "1", + "value": { + "author": "User Test", + "date": "${json-unit.any-string}", + "subject": "TEST_SUBJECT", + "note":"TEST_NOTE" + } + } + ] + }, + "errors": null, + "state": "Submitted", + "warnings": null +} From 6c005c7f7c021b1d115a2aeeb691d3a1744e6d4b Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:02:59 +0000 Subject: [PATCH 03/23] Refactored json data and uncommented ApplicationPaymentFT tests --- .../response-application-payment-added.json | 164 +++++++++--------- .../response-application-payment-failed.json | 141 +++++++-------- ...esponse-application-validation-failed.json | 145 ++++++++-------- .../citizen/ApplicationPaymentFT.java | 107 +++++++----- .../response-application-payment-added.json | 164 +++++++++--------- .../response-application-payment-failed.json | 141 +++++++-------- ...esponse-application-validation-failed.json | 145 ++++++++-------- 7 files changed, 494 insertions(+), 513 deletions(-) diff --git a/bin/functionalTest/casedata/response-application-payment-added.json b/bin/functionalTest/casedata/response-application-payment-added.json index d6bce0cb7..858588c76 100644 --- a/bin/functionalTest/casedata/response-application-payment-added.json +++ b/bin/functionalTest/casedata/response-application-payment-added.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -53,23 +49,23 @@ "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -103,12 +99,38 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": "LA", + "applicantLocalAuthorityEmail": "la@email.gov.uk", + "applicantSocialWorkerEmail": "applicant.socialworker@la.gov.uk", + "applicantSocialWorkerName": "Applicant Social Worker", + "applicantSocialWorkerPhoneNumber": "01234567890", "applicationFeeOrderSummary": { - "Fees": null, - "PaymentReference": null, - "PaymentTotal": null + "Fees": [ + { + "value": { + "FeeAmount": "1234", + "FeeCode": "A58", + "FeeVersion": "1" + } + } + ], + "PaymentTotal": "183" }, - "applicationPayments": [], + "applicationPayments": [ + { + "id": "12", + "value": { + "amount": 183, + "channel": "online", + "created": "2021-11-02T02:50:12.208Z", + "feeCode": "A58", + "reference": "1234", + "status": "success", + "transactionId": "1234", + "updated": "2021-11-02T02:50:12.208Z" + } + } + ], "applyingWith": "withSpouseOrCivilPartner", "birthFatherAddress1": "BUCKINGHAM PALACE", "birthFatherAddress2": "", @@ -149,6 +171,11 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -182,45 +209,48 @@ { "id": "${json-unit.any-string}", "value": { + "documentComment": null, + "documentDateAdded": null, "documentEmailContent": null, + "documentFileId": "${json-unit.any-string}", + "documentFileName": "${json-unit.any-string}", "documentLink": { - "document_url": "${json-unit.any-string}", + "document_binary_url": "${json-unit.any-string}", "document_filename": "${json-unit.any-string}", - "document_binary_url": "${json-unit.any-string}" + "document_url": "${json-unit.any-string}" }, - "documentDateAdded": null, - "documentComment": null, - "documentFileName": "${json-unit.any-string}", - "documentType": "applicationSummaryEn", - "documentFileId": "${json-unit.any-string}" + "documentType": "applicationSummaryEn" } }, { "id": "${json-unit.any-string}", "value": { + "documentComment": null, + "documentDateAdded": null, "documentEmailContent": null, + "documentFileId": "${json-unit.any-string}", + "documentFileName": "${json-unit.any-string}", "documentLink": { - "document_url": "${json-unit.any-string}", + "document_binary_url": "${json-unit.any-string}", "document_filename": "${json-unit.any-string}", - "document_binary_url": "${json-unit.any-string}" + "document_url": "${json-unit.any-string}" }, - "documentDateAdded": null, - "documentComment": null, - "documentFileName": "${json-unit.any-string}", - "documentType": "applicationSummaryCy", - "documentFileId": "${json-unit.any-string}" + "documentType": "applicationSummaryCy" } } ], "documentsUploaded": [], "dueDate": "${json-unit.any-string}", "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -240,6 +270,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -265,67 +296,30 @@ "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", - "selectedSiblingRelation": "Sister", "selectedSiblingPoType": "Adoption order", + "selectedSiblingRelation": "Sister", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", - "siblingRelation": "Brother", - "siblingPoType": "Adoption order", + "siblingPlacementOtherType": "", "siblingPoNumber": "1001", - "siblingPlacementOtherType": "" + "siblingPoType": "Adoption order", + "siblingRelation": "Brother" } }, { "id": "1646222699254", "value": { "siblingId": "1646222699254", - "siblingRelation": "Sister", - "siblingPoType": "Other", + "siblingPlacementOtherType": "other type", "siblingPoNumber": "23456", - "siblingPlacementOtherType": "other type" - } - } - ], - "applicationPayments": [ - { - "id": "12", - "value": { - "created": "2021-11-02T02:50:12.208Z", - "updated": "2021-11-02T02:50:12.208Z", - "feeCode": "A58", - "amount": 183, - "status": "success", - "channel": "online", - "reference": "1234", - "transactionId": "1234" + "siblingPoType": "Other", + "siblingRelation": "Sister" } } ], - "applicationFeeOrderSummary": { - "PaymentTotal": "183", - "Fees": [ - { - "value": { - "FeeAmount": "1234", - "FeeCode": "A58", - "FeeVersion": "1" - } - } - ] - }, - "childLocalAuthority": "la", - "childLocalAuthorityEmail": "abc@gov.uk", - "childSocialWorkerEmail": "abc@gov.uk", - "childSocialWorkerName": "null", - "childSocialWorkerPhoneNumber": "1234567890", - "applicantLocalAuthority": null, - "applicantLocalAuthorityEmail": null, - "applicantSocialWorkerEmail": null, - "applicantSocialWorkerName": null, - "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/bin/functionalTest/casedata/response-application-payment-failed.json b/bin/functionalTest/casedata/response-application-payment-failed.json index d6da25ca6..d93f18b73 100644 --- a/bin/functionalTest/casedata/response-application-payment-failed.json +++ b/bin/functionalTest/casedata/response-application-payment-failed.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -53,23 +49,23 @@ "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -103,12 +99,40 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": "LA", + "applicantLocalAuthorityEmail": "la@email.gov.uk", + "applicantSocialWorkerEmail": "applicant.socialworker@la.gov.uk", + "applicantSocialWorkerName": "Applicant Social Worker", + "applicantSocialWorkerPhoneNumber": "01234567890", "applicationFeeOrderSummary": { - "Fees": null, + "Fees": [ + { + "value": { + "FeeAmount": "1234", + "FeeCode": "A58", + "FeeDescription": null, + "FeeVersion": "1" + } + } + ], "PaymentReference": null, - "PaymentTotal": null + "PaymentTotal": "183" }, - "applicationPayments": [], + "applicationPayments": [ + { + "id": "12", + "value": { + "amount": 183, + "channel": "online", + "created": "2021-11-02T02:50:12.208Z", + "feeCode": "A58", + "reference": "1234", + "status": "declined", + "transactionId": "1234", + "updated": "2021-11-02T02:50:12.208Z" + } + } + ], "applyingWith": "withSpouseOrCivilPartner", "birthFatherAddress1": "BUCKINGHAM PALACE", "birthFatherAddress2": "", @@ -149,6 +173,11 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -178,17 +207,19 @@ "createdDate": null, "dateChildMovedIn": "2021-10-12", "dateSubmitted": null, - "documentsGenerated": [ - ], + "documentsGenerated": [], "documentsUploaded": [], "dueDate": null, "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -208,6 +239,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -233,69 +265,30 @@ "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", - "selectedSiblingRelation": "Sister", "selectedSiblingPoType": "Adoption order", + "selectedSiblingRelation": "Sister", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", - "siblingRelation": "Brother", - "siblingPoType": "Adoption order", + "siblingPlacementOtherType": "", "siblingPoNumber": "1001", - "siblingPlacementOtherType": "" + "siblingPoType": "Adoption order", + "siblingRelation": "Brother" } }, { "id": "1646222699254", "value": { "siblingId": "1646222699254", - "siblingRelation": "Sister", - "siblingPoType": "Other", + "siblingPlacementOtherType": "other type", "siblingPoNumber": "23456", - "siblingPlacementOtherType": "other type" - } - } - ], - "applicationPayments": [ - { - "id": "12", - "value": { - "created": "2021-11-02T02:50:12.208Z", - "updated": "2021-11-02T02:50:12.208Z", - "feeCode": "A58", - "amount": 183, - "status": "declined", - "channel": "online", - "reference": "1234", - "transactionId": "1234" + "siblingPoType": "Other", + "siblingRelation": "Sister" } } ], - "applicationFeeOrderSummary": { - "PaymentTotal": "183", - "PaymentReference": null, - "Fees": [ - { - "value": { - "FeeAmount": "1234", - "FeeCode": "A58", - "FeeDescription":null, - "FeeVersion": "1" - } - } - ] - }, - "childLocalAuthority": "la", - "childLocalAuthorityEmail": "abc@gov.uk", - "childSocialWorkerEmail": "abc@gov.uk", - "childSocialWorkerName": "null", - "childSocialWorkerPhoneNumber": "1234567890", - "applicantLocalAuthority": null, - "applicantLocalAuthorityEmail": null, - "applicantSocialWorkerEmail": null, - "applicantSocialWorkerName": null, - "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/bin/functionalTest/casedata/response-application-validation-failed.json b/bin/functionalTest/casedata/response-application-validation-failed.json index c5553bda5..7e9c29368 100644 --- a/bin/functionalTest/casedata/response-application-validation-failed.json +++ b/bin/functionalTest/casedata/response-application-validation-failed.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -53,23 +49,23 @@ "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -103,12 +99,40 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": "LA", + "applicantLocalAuthorityEmail": "la@email.gov.uk", + "applicantSocialWorkerEmail": "applicant.socialworker@la.gov.uk", + "applicantSocialWorkerName": "Applicant Social Worker", + "applicantSocialWorkerPhoneNumber": "01234567890", "applicationFeeOrderSummary": { - "Fees": null, + "Fees": [ + { + "value": { + "FeeAmount": "1234", + "FeeCode": "A58", + "FeeDescription": null, + "FeeVersion": "1" + } + } + ], "PaymentReference": null, - "PaymentTotal": null + "PaymentTotal": "0" }, - "applicationPayments": [], + "applicationPayments": [ + { + "id": "12", + "value": { + "amount": 183, + "channel": "online", + "created": "2021-11-02T02:50:12.208Z", + "feeCode": "A58", + "reference": "1234", + "status": "success", + "transactionId": "1234", + "updated": "2021-11-02T02:50:12.208Z" + } + } + ], "applyingWith": "withSpouseOrCivilPartner", "birthFatherAddress1": "BUCKINGHAM PALACE", "birthFatherAddress2": "", @@ -149,6 +173,11 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -178,17 +207,19 @@ "createdDate": null, "dateChildMovedIn": "2021-10-12", "dateSubmitted": null, - "documentsGenerated": [ - ], + "documentsGenerated": [], "documentsUploaded": [], "dueDate": null, "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -208,6 +239,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -233,76 +265,39 @@ "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", - "selectedSiblingRelation": "Sister", "selectedSiblingPoType": "Adoption order", + "selectedSiblingRelation": "Sister", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", - "siblingRelation": "Brother", - "siblingPoType": "Adoption order", + "siblingPlacementOtherType": "", "siblingPoNumber": "1001", - "siblingPlacementOtherType": "" + "siblingPoType": "Adoption order", + "siblingRelation": "Brother" } }, { "id": "1646222699254", "value": { "siblingId": "1646222699254", - "siblingRelation": "Sister", - "siblingPoType": "Other", + "siblingPlacementOtherType": "other type", "siblingPoNumber": "23456", - "siblingPlacementOtherType": "other type" - } - } - ], - "applicationPayments": [ - { - "id": "12", - "value": { - "created": "2021-11-02T02:50:12.208Z", - "updated": "2021-11-02T02:50:12.208Z", - "feeCode": "A58", - "amount": 183, - "status": "success", - "channel": "online", - "reference": "1234", - "transactionId": "1234" + "siblingPoType": "Other", + "siblingRelation": "Sister" } } ], - "applicationFeeOrderSummary": { - "PaymentTotal": "0", - "PaymentReference": null, - "Fees": [ - { - "value": { - "FeeAmount": "1234", - "FeeCode": "A58", - "FeeDescription":null, - "FeeVersion": "1" - } - } - ] - }, - "childLocalAuthority": "la", - "childLocalAuthorityEmail": "abc@gov.uk", - "childSocialWorkerEmail": "abc@gov.uk", - "childSocialWorkerName": "null", - "childSocialWorkerPhoneNumber": "1234567890", - "applicantLocalAuthority": null, - "applicantLocalAuthorityEmail": null, - "applicantSocialWorkerEmail": null, - "applicantSocialWorkerName": null, - "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, "solicitorName": null, "solicitorPhoneNumber": null }, - "errors": ["Payment incomplete"], + "errors": [ + "Payment incomplete" + ], "state": null, "warnings": null } diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/adoption/citizen/ApplicationPaymentFT.java b/src/functionalTest/java/uk/gov/hmcts/reform/adoption/citizen/ApplicationPaymentFT.java index 5e92ec541..1e0164475 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/adoption/citizen/ApplicationPaymentFT.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/adoption/citizen/ApplicationPaymentFT.java @@ -1,9 +1,26 @@ package uk.gov.hmcts.reform.adoption.citizen; +import io.restassured.response.Response; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import uk.gov.hmcts.reform.adoption.testutil.FunctionalTest; +import java.io.IOException; +import java.util.Map; + +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; +import static net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER; +import static net.javacrumbs.jsonunit.core.Option.IGNORING_EXTRA_ARRAY_ITEMS; +import static net.javacrumbs.jsonunit.core.Option.IGNORING_EXTRA_FIELDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpStatus.OK; +import static uk.gov.hmcts.reform.adoption.adoptioncase.event.CitizenAddPayment.CITIZEN_ADD_PAYMENT; +import static uk.gov.hmcts.reform.adoption.testutil.TestConstants.ABOUT_TO_SUBMIT_URL; +import static uk.gov.hmcts.reform.adoption.testutil.TestDataHelper.caseData; +import static uk.gov.hmcts.reform.adoption.testutil.TestDataHelper.expectedResponse; + @TestPropertySource("classpath:application.yaml") @SpringBootTest public class ApplicationPaymentFT extends FunctionalTest { @@ -15,49 +32,49 @@ public class ApplicationPaymentFT extends FunctionalTest { private static final String RESPONSE_FAILED_VALIDATION = "classpath:casedata/response-application-validation-failed.json"; private static final String RESPONSE_FAILED_PAYMENT = "classpath:casedata/response-application-payment-failed.json"; - // @Test - // public void shouldUpdateCaseStateInCcdToSubmittedAndGenerateDocumentAndNotifications() throws IOException { - // Map request = caseData(REQUEST); - // - // Response response = triggerCallback(request, CITIZEN_ADD_PAYMENT, ABOUT_TO_SUBMIT_URL); - // - // assertThat(response.getStatusCode()).isEqualTo(OK.value()); - // - // assertThatJson(response.asString()) - // .when(IGNORING_EXTRA_FIELDS) - // .when(IGNORING_ARRAY_ORDER) - // .when(IGNORING_EXTRA_ARRAY_ITEMS) - // .isEqualTo(json(expectedResponse(RESPONSE))); - // } - - // @Test - // public void shouldUpdateCaseStateInCcdToDraftInCaseOfFailedPaymentAndNoDocumentsGeneratedAndNoNotificationsSent() - // throws IOException { - // Map request = caseData(REQUEST_FAILED_PAYMENT); - // - // Response response = triggerCallback(request, CITIZEN_ADD_PAYMENT, ABOUT_TO_SUBMIT_URL); - // - // assertThat(response.getStatusCode()).isEqualTo(OK.value()); - // - // assertThatJson(response.asString()) - // .when(IGNORING_EXTRA_FIELDS) - // .when(IGNORING_ARRAY_ORDER) - // .isEqualTo(json(expectedResponse(RESPONSE_FAILED_PAYMENT))); - // } - // - // - // @Test - // public void shouldThrowErrorIfValidationFailsAndNoDocumentsGeneratedAndNoNotificationsSent() - // throws IOException { - // Map request = caseData(REQUEST_FAILED_VALIDATION); - // - // Response response = triggerCallback(request, CITIZEN_ADD_PAYMENT, ABOUT_TO_SUBMIT_URL); - // - // assertThat(response.getStatusCode()).isEqualTo(OK.value()); - // - // assertThatJson(response.asString()) - // .when(IGNORING_EXTRA_FIELDS) - // .when(IGNORING_ARRAY_ORDER) - // .isEqualTo(json(expectedResponse(RESPONSE_FAILED_VALIDATION))); - // } + @Test + public void shouldUpdateCaseStateInCcdToSubmittedAndGenerateDocumentAndNotifications() throws IOException { + Map request = caseData(REQUEST); + + Response response = triggerCallback(request, CITIZEN_ADD_PAYMENT, ABOUT_TO_SUBMIT_URL); + + assertThat(response.getStatusCode()).isEqualTo(OK.value()); + + assertThatJson(response.asString()) + .when(IGNORING_EXTRA_FIELDS) + .when(IGNORING_ARRAY_ORDER) + .when(IGNORING_EXTRA_ARRAY_ITEMS) + .isEqualTo(json(expectedResponse(RESPONSE))); + } + + @Test + public void shouldUpdateCaseStateInCcdToDraftInCaseOfFailedPaymentAndNoDocumentsGeneratedAndNoNotificationsSent() + throws IOException { + Map request = caseData(REQUEST_FAILED_PAYMENT); + + Response response = triggerCallback(request, CITIZEN_ADD_PAYMENT, ABOUT_TO_SUBMIT_URL); + + assertThat(response.getStatusCode()).isEqualTo(OK.value()); + + assertThatJson(response.asString()) + .when(IGNORING_EXTRA_FIELDS) + .when(IGNORING_ARRAY_ORDER) + .isEqualTo(json(expectedResponse(RESPONSE_FAILED_PAYMENT))); + } + + + @Test + public void shouldThrowErrorIfValidationFailsAndNoDocumentsGeneratedAndNoNotificationsSent() + throws IOException { + Map request = caseData(REQUEST_FAILED_VALIDATION); + + Response response = triggerCallback(request, CITIZEN_ADD_PAYMENT, ABOUT_TO_SUBMIT_URL); + + assertThat(response.getStatusCode()).isEqualTo(OK.value()); + + assertThatJson(response.asString()) + .when(IGNORING_EXTRA_FIELDS) + .when(IGNORING_ARRAY_ORDER) + .isEqualTo(json(expectedResponse(RESPONSE_FAILED_VALIDATION))); + } } diff --git a/src/functionalTest/resources/casedata/response-application-payment-added.json b/src/functionalTest/resources/casedata/response-application-payment-added.json index d6bce0cb7..858588c76 100644 --- a/src/functionalTest/resources/casedata/response-application-payment-added.json +++ b/src/functionalTest/resources/casedata/response-application-payment-added.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -53,23 +49,23 @@ "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -103,12 +99,38 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": "LA", + "applicantLocalAuthorityEmail": "la@email.gov.uk", + "applicantSocialWorkerEmail": "applicant.socialworker@la.gov.uk", + "applicantSocialWorkerName": "Applicant Social Worker", + "applicantSocialWorkerPhoneNumber": "01234567890", "applicationFeeOrderSummary": { - "Fees": null, - "PaymentReference": null, - "PaymentTotal": null + "Fees": [ + { + "value": { + "FeeAmount": "1234", + "FeeCode": "A58", + "FeeVersion": "1" + } + } + ], + "PaymentTotal": "183" }, - "applicationPayments": [], + "applicationPayments": [ + { + "id": "12", + "value": { + "amount": 183, + "channel": "online", + "created": "2021-11-02T02:50:12.208Z", + "feeCode": "A58", + "reference": "1234", + "status": "success", + "transactionId": "1234", + "updated": "2021-11-02T02:50:12.208Z" + } + } + ], "applyingWith": "withSpouseOrCivilPartner", "birthFatherAddress1": "BUCKINGHAM PALACE", "birthFatherAddress2": "", @@ -149,6 +171,11 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -182,45 +209,48 @@ { "id": "${json-unit.any-string}", "value": { + "documentComment": null, + "documentDateAdded": null, "documentEmailContent": null, + "documentFileId": "${json-unit.any-string}", + "documentFileName": "${json-unit.any-string}", "documentLink": { - "document_url": "${json-unit.any-string}", + "document_binary_url": "${json-unit.any-string}", "document_filename": "${json-unit.any-string}", - "document_binary_url": "${json-unit.any-string}" + "document_url": "${json-unit.any-string}" }, - "documentDateAdded": null, - "documentComment": null, - "documentFileName": "${json-unit.any-string}", - "documentType": "applicationSummaryEn", - "documentFileId": "${json-unit.any-string}" + "documentType": "applicationSummaryEn" } }, { "id": "${json-unit.any-string}", "value": { + "documentComment": null, + "documentDateAdded": null, "documentEmailContent": null, + "documentFileId": "${json-unit.any-string}", + "documentFileName": "${json-unit.any-string}", "documentLink": { - "document_url": "${json-unit.any-string}", + "document_binary_url": "${json-unit.any-string}", "document_filename": "${json-unit.any-string}", - "document_binary_url": "${json-unit.any-string}" + "document_url": "${json-unit.any-string}" }, - "documentDateAdded": null, - "documentComment": null, - "documentFileName": "${json-unit.any-string}", - "documentType": "applicationSummaryCy", - "documentFileId": "${json-unit.any-string}" + "documentType": "applicationSummaryCy" } } ], "documentsUploaded": [], "dueDate": "${json-unit.any-string}", "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -240,6 +270,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -265,67 +296,30 @@ "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", - "selectedSiblingRelation": "Sister", "selectedSiblingPoType": "Adoption order", + "selectedSiblingRelation": "Sister", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", - "siblingRelation": "Brother", - "siblingPoType": "Adoption order", + "siblingPlacementOtherType": "", "siblingPoNumber": "1001", - "siblingPlacementOtherType": "" + "siblingPoType": "Adoption order", + "siblingRelation": "Brother" } }, { "id": "1646222699254", "value": { "siblingId": "1646222699254", - "siblingRelation": "Sister", - "siblingPoType": "Other", + "siblingPlacementOtherType": "other type", "siblingPoNumber": "23456", - "siblingPlacementOtherType": "other type" - } - } - ], - "applicationPayments": [ - { - "id": "12", - "value": { - "created": "2021-11-02T02:50:12.208Z", - "updated": "2021-11-02T02:50:12.208Z", - "feeCode": "A58", - "amount": 183, - "status": "success", - "channel": "online", - "reference": "1234", - "transactionId": "1234" + "siblingPoType": "Other", + "siblingRelation": "Sister" } } ], - "applicationFeeOrderSummary": { - "PaymentTotal": "183", - "Fees": [ - { - "value": { - "FeeAmount": "1234", - "FeeCode": "A58", - "FeeVersion": "1" - } - } - ] - }, - "childLocalAuthority": "la", - "childLocalAuthorityEmail": "abc@gov.uk", - "childSocialWorkerEmail": "abc@gov.uk", - "childSocialWorkerName": "null", - "childSocialWorkerPhoneNumber": "1234567890", - "applicantLocalAuthority": null, - "applicantLocalAuthorityEmail": null, - "applicantSocialWorkerEmail": null, - "applicantSocialWorkerName": null, - "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/src/functionalTest/resources/casedata/response-application-payment-failed.json b/src/functionalTest/resources/casedata/response-application-payment-failed.json index d6da25ca6..d93f18b73 100644 --- a/src/functionalTest/resources/casedata/response-application-payment-failed.json +++ b/src/functionalTest/resources/casedata/response-application-payment-failed.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -53,23 +49,23 @@ "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -103,12 +99,40 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": "LA", + "applicantLocalAuthorityEmail": "la@email.gov.uk", + "applicantSocialWorkerEmail": "applicant.socialworker@la.gov.uk", + "applicantSocialWorkerName": "Applicant Social Worker", + "applicantSocialWorkerPhoneNumber": "01234567890", "applicationFeeOrderSummary": { - "Fees": null, + "Fees": [ + { + "value": { + "FeeAmount": "1234", + "FeeCode": "A58", + "FeeDescription": null, + "FeeVersion": "1" + } + } + ], "PaymentReference": null, - "PaymentTotal": null + "PaymentTotal": "183" }, - "applicationPayments": [], + "applicationPayments": [ + { + "id": "12", + "value": { + "amount": 183, + "channel": "online", + "created": "2021-11-02T02:50:12.208Z", + "feeCode": "A58", + "reference": "1234", + "status": "declined", + "transactionId": "1234", + "updated": "2021-11-02T02:50:12.208Z" + } + } + ], "applyingWith": "withSpouseOrCivilPartner", "birthFatherAddress1": "BUCKINGHAM PALACE", "birthFatherAddress2": "", @@ -149,6 +173,11 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -178,17 +207,19 @@ "createdDate": null, "dateChildMovedIn": "2021-10-12", "dateSubmitted": null, - "documentsGenerated": [ - ], + "documentsGenerated": [], "documentsUploaded": [], "dueDate": null, "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -208,6 +239,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -233,69 +265,30 @@ "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", - "selectedSiblingRelation": "Sister", "selectedSiblingPoType": "Adoption order", + "selectedSiblingRelation": "Sister", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", - "siblingRelation": "Brother", - "siblingPoType": "Adoption order", + "siblingPlacementOtherType": "", "siblingPoNumber": "1001", - "siblingPlacementOtherType": "" + "siblingPoType": "Adoption order", + "siblingRelation": "Brother" } }, { "id": "1646222699254", "value": { "siblingId": "1646222699254", - "siblingRelation": "Sister", - "siblingPoType": "Other", + "siblingPlacementOtherType": "other type", "siblingPoNumber": "23456", - "siblingPlacementOtherType": "other type" - } - } - ], - "applicationPayments": [ - { - "id": "12", - "value": { - "created": "2021-11-02T02:50:12.208Z", - "updated": "2021-11-02T02:50:12.208Z", - "feeCode": "A58", - "amount": 183, - "status": "declined", - "channel": "online", - "reference": "1234", - "transactionId": "1234" + "siblingPoType": "Other", + "siblingRelation": "Sister" } } ], - "applicationFeeOrderSummary": { - "PaymentTotal": "183", - "PaymentReference": null, - "Fees": [ - { - "value": { - "FeeAmount": "1234", - "FeeCode": "A58", - "FeeDescription":null, - "FeeVersion": "1" - } - } - ] - }, - "childLocalAuthority": "la", - "childLocalAuthorityEmail": "abc@gov.uk", - "childSocialWorkerEmail": "abc@gov.uk", - "childSocialWorkerName": "null", - "childSocialWorkerPhoneNumber": "1234567890", - "applicantLocalAuthority": null, - "applicantLocalAuthorityEmail": null, - "applicantSocialWorkerEmail": null, - "applicantSocialWorkerName": null, - "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, diff --git a/src/functionalTest/resources/casedata/response-application-validation-failed.json b/src/functionalTest/resources/casedata/response-application-validation-failed.json index c5553bda5..7e9c29368 100644 --- a/src/functionalTest/resources/casedata/response-application-validation-failed.json +++ b/src/functionalTest/resources/casedata/response-application-validation-failed.json @@ -2,17 +2,13 @@ "data": { "addAnotherPlacementOrder": "No", "addAnotherSiblingPlacementOrder": "No", - "localAuthorityName": "laname", - "localAuthorityContactName": "contact name1", - "localAuthorityPhoneNumber": "01234567890", - "localAuthorityContactEmail": "agency1@email.co.uk", - "adopAgencyOrLaName": "agency1", + "adopAgencyAddressLine1": "address", + "adopAgencyOrLaContactEmail": "agency1@email.co.uk", "adopAgencyOrLaContactName": "contact name1", + "adopAgencyOrLaName": "agency1", "adopAgencyOrLaPhoneNumber": "01234567890", - "adopAgencyAddressLine1": "address", - "adopAgencyTown": "town", "adopAgencyPostcode": "aa14aa", - "adopAgencyOrLaContactEmail": "agency1@email.co.uk", + "adopAgencyTown": "town", "adoptionDocument": { "documentComment": null, "documentDateAdded": null, @@ -53,23 +49,23 @@ "applicant1ContactDetailsConsent": "Yes", "applicant1DateOfBirth": "1990-12-12", "applicant1DocumentsUploaded": [ - { + { "id": "c283880e-068e-450a-b44d-e72a6191847b", "value": { - "documentEmailContent": null, - "documentLink": { - "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1", - "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary" - }, - "documentDateAdded": null, - "documentComment": "Uploaded by applicant", - "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", - "documentType": null, - "documentFileId": null + "documentComment": "Uploaded by applicant", + "documentDateAdded": null, + "documentEmailContent": null, + "documentFileId": null, + "documentFileName": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "documentLink": { + "document_binary_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1/binary", + "document_filename": "adoption-application--1234567890123456-2022-03-29:23:55.pdf", + "document_url": "http://dm-store-aat.service.core-compute-aat.internal/documents/a54c625a-82da-44c7-a06a-b4af4c09f8b1" + }, + "documentType": null } - } -], + } + ], "applicant1Email": "citizen.automation@mailinator.com", "applicant1EmailAddress": "abid@gmail.co", "applicant1FirstName": "Abid1", @@ -103,12 +99,40 @@ "applicant2PhoneNumber": "12121212121", "applicant2SotFullName": null, "applicant2StatementOfTruth": null, + "applicantLocalAuthority": "LA", + "applicantLocalAuthorityEmail": "la@email.gov.uk", + "applicantSocialWorkerEmail": "applicant.socialworker@la.gov.uk", + "applicantSocialWorkerName": "Applicant Social Worker", + "applicantSocialWorkerPhoneNumber": "01234567890", "applicationFeeOrderSummary": { - "Fees": null, + "Fees": [ + { + "value": { + "FeeAmount": "1234", + "FeeCode": "A58", + "FeeDescription": null, + "FeeVersion": "1" + } + } + ], "PaymentReference": null, - "PaymentTotal": null + "PaymentTotal": "0" }, - "applicationPayments": [], + "applicationPayments": [ + { + "id": "12", + "value": { + "amount": 183, + "channel": "online", + "created": "2021-11-02T02:50:12.208Z", + "feeCode": "A58", + "reference": "1234", + "status": "success", + "transactionId": "1234", + "updated": "2021-11-02T02:50:12.208Z" + } + } + ], "applyingWith": "withSpouseOrCivilPartner", "birthFatherAddress1": "BUCKINGHAM PALACE", "birthFatherAddress2": "", @@ -149,6 +173,11 @@ "birthMotherOccupation": "Teacher", "birthMotherOtherNationalities": [], "birthMotherStillAlive": "Yes", + "childLocalAuthority": "la", + "childLocalAuthorityEmail": "abc@gov.uk", + "childSocialWorkerEmail": "abc@gov.uk", + "childSocialWorkerName": "null", + "childSocialWorkerPhoneNumber": "1234567890", "childrenAdditionalNationalities": [ { "id": "3b81bf8b-96ad-4001-a5cf-66f2af99a79c", @@ -178,17 +207,19 @@ "createdDate": null, "dateChildMovedIn": "2021-10-12", "dateSubmitted": null, - "documentsGenerated": [ - ], + "documentsGenerated": [], "documentsUploaded": [], "dueDate": null, "familyCourtEmailId": "adoptionproject@mailinator.com", - "placementOrderCourt": "xyz", "familyCourtName": "xyz", "findFamilyCourt": "Yes", "hasAnotherAdopAgencyOrLA": "No", "hasSiblings": "Yes", "hyphenatedCaseRef": "1234-5678-9012-3456", + "localAuthorityContactEmail": "agency1@email.co.uk", + "localAuthorityContactName": "contact name1", + "localAuthorityName": "laname", + "localAuthorityPhoneNumber": "01234567890", "otherApplicantRelation": "", "otherParentAddress1": "BUCKINGHAM PALACE", "otherParentAddress2": "", @@ -208,6 +239,7 @@ "otherParentOtherNationalities": [], "otherParentStillAlive": "Yes", "pcqId": "9c04ea3a-c614-4eac-8361-ac75fb59f9dc", + "placementOrderCourt": "xyz", "placementOrders": [ { "id": "6e59287f-a337-49ba-ae45-c1f8bfe72f25", @@ -233,76 +265,39 @@ "selectedAdoptionAgencyId": "1646222614256", "selectedPlacementOrderId": "1646222468418", "selectedSiblingId": "1646222699254", - "selectedSiblingRelation": "Sister", "selectedSiblingPoType": "Adoption order", + "selectedSiblingRelation": "Sister", "siblings": [ { "id": "1646222663145", "value": { "siblingId": "1646222663145", - "siblingRelation": "Brother", - "siblingPoType": "Adoption order", + "siblingPlacementOtherType": "", "siblingPoNumber": "1001", - "siblingPlacementOtherType": "" + "siblingPoType": "Adoption order", + "siblingRelation": "Brother" } }, { "id": "1646222699254", "value": { "siblingId": "1646222699254", - "siblingRelation": "Sister", - "siblingPoType": "Other", + "siblingPlacementOtherType": "other type", "siblingPoNumber": "23456", - "siblingPlacementOtherType": "other type" - } - } - ], - "applicationPayments": [ - { - "id": "12", - "value": { - "created": "2021-11-02T02:50:12.208Z", - "updated": "2021-11-02T02:50:12.208Z", - "feeCode": "A58", - "amount": 183, - "status": "success", - "channel": "online", - "reference": "1234", - "transactionId": "1234" + "siblingPoType": "Other", + "siblingRelation": "Sister" } } ], - "applicationFeeOrderSummary": { - "PaymentTotal": "0", - "PaymentReference": null, - "Fees": [ - { - "value": { - "FeeAmount": "1234", - "FeeCode": "A58", - "FeeDescription":null, - "FeeVersion": "1" - } - } - ] - }, - "childLocalAuthority": "la", - "childLocalAuthorityEmail": "abc@gov.uk", - "childSocialWorkerEmail": "abc@gov.uk", - "childSocialWorkerName": "null", - "childSocialWorkerPhoneNumber": "1234567890", - "applicantLocalAuthority": null, - "applicantLocalAuthorityEmail": null, - "applicantSocialWorkerEmail": null, - "applicantSocialWorkerName": null, - "applicantSocialWorkerPhoneNumber": null, "solicitorEmail": null, "solicitorFirm": null, "solicitorHelpingWithApplication": null, "solicitorName": null, "solicitorPhoneNumber": null }, - "errors": ["Payment incomplete"], + "errors": [ + "Payment incomplete" + ], "state": null, "warnings": null } From e22ce8e0547442cb6f036ce5e8084d3c23d901a1 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:55:44 +0000 Subject: [PATCH 04/23] Improve contract test readiness and pact publishing Align Pact consumer identity, gate broker publishing when unset, and update contract test docs. --- .github/copilot-instructions.md | 200 +++ ARCHITECTURE.md | 1088 +++++++++++++++++ build.gradle | 61 +- copilot-instructions.md | 633 ++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- .../adoption/CaseDocumentClientPactTest.java | 6 +- .../adoption/ContractTestConstants.java | 8 + .../reform/adoption/IdamApiConsumerTest.java | 12 +- .../reform/adoption/IdamConsumerTestBase.java | 8 +- .../reform/adoption/MockServerReadiness.java | 66 + 10 files changed, 2056 insertions(+), 28 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 ARCHITECTURE.md create mode 100644 copilot-instructions.md create mode 100644 src/contractTest/java/uk/gov/hmcts/reform/adoption/ContractTestConstants.java create mode 100644 src/contractTest/java/uk/gov/hmcts/reform/adoption/MockServerReadiness.java diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..7906e9cf7 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,200 @@ +# Copilot Instructions — adoption-cos-api + +## Build, Test, and Lint Commands + +- **Prerequisite:** JDK 21 (set `JAVA_HOME` to `/usr/lib/jvm/java-21-openjdk-`) +- **Build:** `./gradlew build` +- **Unit tests:** `./gradlew test` +- **Single test:** `./gradlew test --tests 'uk.gov.hmcts.reform.adoption.'` +- **Lint / static analysis:** `./gradlew check` (runs Checkstyle + PMD) +- **Integration tests:** `./gradlew integration` +- **Contract (Pact) tests:** `./gradlew contract` +- **Coverage report:** `./gradlew jacocoTestReport` → `build/reports/` + +> **Note:** The README references JDK 17 — this is outdated. JDK 21 is required. + +### CCD Config Generation + +```bash +./gradlew generateCCDConfig # JSON definitions → build/definitions/ +./gradlew buildCCDXlsx # Excel spreadsheet (depends on generateCCDConfig) +./gradlew generateTypeScript # TypeScript enums/constants consumed by adoption-web +``` + +Run these after changing any CCD model class, event, tab, state, or access profile. + +### Local Stack + +```bash +./gradlew bootWithCcd # Starts CCD + XUI locally on http://localhost:3000 +``` + +Requires F5 VPN and `az acr login --name hmctspublic`. AAT IDAM/services are used for auth. + +--- + +## Architecture + +This is a **Spring Boot CCD callback API** for the HMCTS Adoption service. It sits between CCD and upstream services — it does not serve a UI directly. + +``` +adoption-web / XUI + │ + ▼ + CCD ←──── adoption-cos-api (case type: A58, jurisdiction: ADOPTION) + │ + ┌───────────┼───────────────┐ + ▼ ▼ ▼ + IDAM case-document-am payment-api + (doc store) + GOV.UK Notify + (via SendGrid) +``` + +**Case type:** `A58` | **Jurisdiction:** `ADOPTION` + +All external service URLs default to their AAT (`*.aat.platform.hmcts.net`) equivalents, overridable via environment variables (see `application.yaml`). + +--- + +## Key Conventions + +### CCDConfig / Event Pattern + +Every CCD event is a `@Component` implementing `CCDConfig`. The `configure()` method registers the event with CCD, wires pages, and attaches callbacks: + +```java +@Component +public class CaseworkerFoo implements CCDConfig { + public static final String CASEWORKER_FOO = "caseworker-foo"; + + private final CcdPageConfiguration fooPage = new FooPage(); + + @Override + public void configure(ConfigBuilder configBuilder) { + var pageBuilder = new PageBuilder(configBuilder + .event(CASEWORKER_FOO) + .forStates(State.Submitted) + .name("Foo event") + .aboutToStartCallback(this::aboutToStart) + .aboutToSubmitCallback(this::aboutToSubmit) + .grant(Permissions.CREATE_READ_UPDATE, UserRole.CASE_WORKER)); + fooPage.addTo(pageBuilder); + } +} +``` + +Available callbacks: `aboutToStartCallback`, `aboutToSubmitCallback`, `submittedCallback`. + +### CcdPageConfiguration / Page Pattern + +Each CCD wizard page is a separate class in a `page/` sub-package alongside its event. Pages implement `CcdPageConfiguration`: + +```java +public class FooPage implements CcdPageConfiguration { + @Override + public void addTo(PageBuilder pageBuilder) { + pageBuilder.page("fooPage") + .pageLabel("Foo") + .complex(CaseData::getFooData) + .mandatory(FooData::getField1) + .optional(FooData::getField2) + .done(); + } +} +``` + +### CaseTask Pattern + +`CaseTask` is a `Function, CaseDetails>`. Tasks are `@Component` beans composed and run via `CaseTaskRunner`: + +```java +// Definition +@Component +public class MyTask implements CaseTask { + @Override + public CaseDetails apply(CaseDetails details) { + details.getData().setSomething("value"); + return details; + } +} + +// Usage in an event callback +public AboutToStartOrSubmitResponse aboutToSubmit(...) { + var result = CaseTaskRunner.caseTasks(taskA, taskB, taskC).run(details); + return AboutToStartOrSubmitResponse.builder() + .data(result.getData()) + .build(); +} +``` + +Tasks under `adoptioncase/task/` and `common/service/task/` handle state transitions, date setting, and document generation. Tasks under `document/task/` handle document creation/removal. + +### Notification Pattern + +Notifications use Spring's application event system. An event is published after a CCD callback completes, and a handler in `notification/handlers/` picks it up: + +```java +// Event record (in service/event/) +public record ApplicationSubmitNotificationEvent(CaseDetails caseData) {} + +// Handler +@Component +public class ApplicationSubmittedNotificationEventHandler { + @EventListener + public void sendNotification(ApplicationSubmitNotificationEvent event) { + sendNotificationService.sendNotifications(event.caseData()); + } +} +``` + +### State Machine + +States are defined as a `@CCD`-annotated enum (`adoptioncase/model/State.java`). Each state carries an access class controlling which roles can read/write it: + +| State | Description | +|---|---| +| `Draft` | Citizen is completing the form | +| `AwaitingPayment` | Application submitted, payment pending | +| `Submitted` | Awaiting Local Authority input | +| `LaSubmitted` | Local Authority has submitted | + +State permissions are granted in `Adoption.configure()`. + +### Source Sets + +| Source set | Task | Purpose | +|---|---|---| +| `src/main` | — | Production code | +| `src/test` | `./gradlew test` | Unit tests | +| `src/integrationTest` | `./gradlew integration` | Integration tests | +| `src/contractTest` | `./gradlew contract` | Pact consumer tests | +| `src/functionalTest` | `./gradlew functional` | Functional/E2E tests | +| `src/cftlib` | `bootWithCcd` | Local CCD stack demo events | + +### Test Conventions + +- JUnit 5 + Mockito (`@ExtendWith(MockitoExtension.class)`) for unit tests. +- `uk.gov.hmcts.reform.adoption.testutil.TestDataHelper` — factory methods for `CaseData`, `Applicant`, `ListValue`, etc. +- `uk.gov.hmcts.reform.adoption.testutil.TestConstants` — shared constants (emails, names, IDs). +- `BaseTest` — abstract base for tests that need the Spring context (loads `Adoption.class` + `application-contract.properties`). +- `integrationTest` hits real AAT endpoints; run locally only when connected to F5 VPN. + +### Feature Flags (LaunchDarkly) + +```java +// With a known user context +launchDarklyClient.isFeatureEnabled("my-flag", ldUser); + +// Using the default ADOPTION_COS_USER +launchDarklyClient.isFeatureEnabled("my-flag"); +``` + +The `LaunchDirectlyIntValidationController` exposes a health-check endpoint for flag connectivity. + +### Contract Tests (Pact) + +- Consumer: `adoption_cos_api` +- Providers tested: `idamApi_oidc`, `case-document-am-api` +- ⚠️ `build.gradle` has `pacticipant = 'sscs_tribunalsCaseApi'` — this is a copy-paste error and needs correcting before publishing pacts to the broker. +- Run tests: `./gradlew contract`; publish: `./gradlew runAndPublishConsumerPactTests` (requires `PACT_BROKER_FULL_URL`). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 000000000..3f6acc59d --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,1088 @@ +# Adoption COS API - Architecture & Patterns Analysis + +## Overview +The adoption-cos-api is a Case Management System API built with Spring Boot and heavily integrated with the CCD (Common Case Data) SDK. It follows a **task-based reactive pattern** combined with Spring event publishing for notification handling. + +--- + +## 1. CCD Callback Pattern + +### Structure & Annotations +All CCD events implement `CCDConfig` and are Spring `@Component`s. + +**Key CCD Callback Types:** +- `aboutToSubmitCallback()` - Validation & data transformation before submission +- `submittedCallback()` - Post-submission async operations (event publishing) + +### Example: CitizenCreateApplication.java +```java +@Component +@Slf4j +public class CitizenCreateApplication implements CCDConfig { + public static final String CITIZEN_CREATE = "citizen-create-application"; + + @Override + public void configure(final ConfigBuilder configBuilder) { + configBuilder + .event(CITIZEN_CREATE) + .initialState(Draft) + .name("Create adoption draft case") + .description("Apply for adoption") + .ttlIncrement(90) + .aboutToSubmitCallback(this::aboutToSubmit) // Pre-submission hook + .grant(CREATE_READ_UPDATE, CITIZEN) + .retries(120, 120); + } + + public AboutToStartOrSubmitResponse aboutToSubmit( + final CaseDetails details, + final CaseDetails beforeDetails) { + log.info("Citizen create adoption application about to submit callback invoked"); + CaseData data = details.getData(); + // Transform data, set defaults, format case reference + data.setHyphenatedCaseRef(String.format("%4s-%4s-%4s-%4s", ...)); + return AboutToStartOrSubmitResponse.builder() + .data(data) + .build(); + } +} +``` + +### Event Registration Pattern +Events are wired via `ConfigBuilder`: +- `.event(eventId)` - Registers event ID (e.g., "citizen-create-application") +- `.forStates(State...)` - Applies to specific states or all states +- `.forState(State)` - Single state variant +- `.grant(Permissions, UserRole...)` - Access control +- `.retries(120, 120)` - Retry configuration + +**Example with submitted callback:** +```java +// CitizenAddPayment.java +configBuilder + .event(CITIZEN_ADD_PAYMENT) + .forState(AwaitingPayment) + .name("Payment made") + .aboutToSubmitCallback(this::aboutToSubmit) + .submittedCallback(this::submitted); + +public SubmittedCallbackResponse submitted(CaseDetails details, + CaseDetails beforeDetails) { + if (EnumSet.of(Submitted).contains(details.getState())) { + eventPublisher.publishEvent( + ApplicationSubmitNotificationEvent.builder() + .caseData(details) + .build()); + } + return SubmittedCallbackResponse.builder().build(); +} +``` + +--- + +## 2. Task Pattern + +### Core Abstraction: CaseTask Interface +All tasks implement a simple functional interface: + +```java +public interface CaseTask extends Function, CaseDetails> { +} +``` + +Tasks are pure functions that transform case details. + +### Task Composition: CaseTaskRunner +Tasks are composed using a **function pipeline** pattern: + +```java +public final class CaseTaskRunner { + private final Function, CaseDetails> caseTask; + + @SafeVarargs + public static CaseTaskRunner caseTasks( + final Function, CaseDetails>... tasks) { + return new CaseTaskRunner(of(tasks).reduce(identity(), Function::andThen)); + } + + public CaseDetails run(final CaseDetails caseDetails) { + return caseTask.apply(caseDetails); + } +} +``` + +### Task Registration in Services +Tasks are registered and composed in Service classes (dependency injection): + +```java +@Service +public class SubmissionService { + @Autowired + private SetStateAfterSubmission setStateAfterSubmission; + + @Autowired + private SetDateSubmitted setDateSubmitted; + + @Autowired + private GenerateApplicationSummaryDocument generateApplicationSummaryDocument; + + public CaseDetails submitApplication( + final CaseDetails caseDetails) { + return CaseTaskRunner.caseTasks( + setStateAfterSubmission, + setDateSubmitted, + generateApplicationSummaryDocument + ).run(caseDetails); + } +} +``` + +### Task Examples + +**SetStateAfterSubmission (Conditional State Logic):** +```java +@Component +@Slf4j +public class SetStateAfterSubmission implements CaseTask { + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + final CaseData caseData = caseDetails.getData(); + final Application application = caseData.getApplication(); + + if (!application.hasBeenPaidFor()) { + caseDetails.setState(AwaitingPayment); + } else { + caseDetails.setState(Submitted); + } + caseDetails.getData().setStatus(Submitted); + log.info("State set to {}, CaseID {}", caseDetails.getState(), caseDetails.getId()); + return caseDetails; + } +} +``` + +**GenerateApplicationSummaryDocument (Async Document Generation):** +```java +@Component +@Slf4j +public class GenerateApplicationSummaryDocument implements CaseTask { + @Autowired + private CaseDataDocumentService caseDataDocumentService; + + @Override + public CaseDetails apply(CaseDetails caseDetails) { + final CaseData caseData = caseDetails.getData(); + final Long caseId = caseDetails.getId(); + + if (EnumSet.of(Submitted).contains(caseDetails.getState())) { + log.info("Generating summary document for caseId: {}", caseId); + Map templateContent = objectMapper.convertValue(caseData, Map.class); + + // Generate English or Welsh document based on language preference + final CompletableFuture appSummary = caseData.getApplicant1() + .getLanguagePreference().equals(LanguagePreference.ENGLISH) + ? generateDocument(caseData, APPLICATION_SUMMARY_EN, templateContent, ...) + : generateDocument(caseData, APPLICATION_SUMMARY_CY, templateContent, ...); + + CompletableFuture.allOf(appSummary).join(); + } + return caseDetails; + } +} +``` + +### Locations +- **adoptioncase/task/** - Core case task implementations +- **service/task/** - Service-level tasks (EventService, ScheduledTaskRunner) +- **document/task/** - Document generation tasks +- **common/service/task/** - Reusable tasks (SetState, SetDate, GenerateDocuments, SendNotifications) + +--- + +## 3. Event Handlers + +### Event Publishing Architecture +Uses Spring's **ApplicationEventPublisher**: + +```java +@Service +@Slf4j +@RequiredArgsConstructor +public class EventService { + private final ApplicationEventPublisher applicationEventPublisher; + + public void publishEvent(Object event) { + log.info("Publishing event {}", event.getClass().getSimpleName()); + applicationEventPublisher.publishEvent(event); + } +} +``` + +### Custom Event Records +Events are defined as immutable records (Java 16+): + +```java +// service/event/ApplicationSubmitNotificationEvent.java +@Builder +public record ApplicationSubmitNotificationEvent(CaseDetails caseData) { } + +// service/event/LocalAuthorityApplicationSubmitNotificationEvent.java +@Builder +public record LocalAuthorityApplicationSubmitNotificationEvent(CaseDetails caseData) { } +``` + +### Event Listeners +Handlers use `@EventListener` annotation (Spring event async processing): + +```java +@Slf4j +@Component +public class ApplicationSubmittedNotificationEventHandler { + private final SendNotificationService sendNotificationService; + + @EventListener + public void sendNotificationPostApplicationSubmission( + ApplicationSubmitNotificationEvent applicationSubmitNotificationEvent) { + Long caseId = applicationSubmitNotificationEvent.caseData().getId(); + log.info("ApplicationSubmittedNotificationEventHandler triggered for CaseID: {}", caseId); + sendNotificationService.sendNotifications( + applicationSubmitNotificationEvent.caseData()); + } +} +``` + +### CCD Event Configuration +Events are registered in `adoptioncase/event/` directory (implements CCDConfig): + +**Example: CitizenSubmitApplication** +```java +@Component +@Slf4j +public class CitizenSubmitApplication implements CCDConfig { + public static final String CITIZEN_SUBMIT = "citizen-submit-application"; + + @Override + public void configure(final ConfigBuilder configBuilder) { + configBuilder + .event(CITIZEN_SUBMIT) + .forStates(Draft, AwaitingPayment) + .name("Applicant Statement of Truth") + .aboutToSubmitCallback(this::aboutToSubmit); + } + + public AboutToStartOrSubmitResponse aboutToSubmit( + CaseDetails details, + CaseDetails beforeDetails) { + // Validation + final List validationErrors = validateReadyForPayment(data); + if (!validationErrors.isEmpty()) { + return AboutToStartOrSubmitResponse.builder() + .data(data) + .errors(validationErrors) + .state(state) + .build(); + } + // State transition + state = AwaitingPayment; + return AboutToStartOrSubmitResponse.builder() + .data(data) + .state(state) + .build(); + } +} +``` + +**Example: LocalAuthoritySubmitApplication with Event Publishing** +```java +@Component +@Slf4j +public class LocalAuthoritySubmitApplication implements CCDConfig { + @Autowired private SubmissionService submissionService; + @Autowired private EventService eventPublisher; + + @Override + public void configure(ConfigBuilder configBuilder) { + configBuilder + .event(LOCAL_AUTHORITY_SUBMIT) + .forStates(Submitted) + .aboutToSubmitCallback(this::aboutToSubmit) + .submittedCallback(this::submitted); + } + + public AboutToStartOrSubmitResponse aboutToSubmit( + final CaseDetails details, + final CaseDetails beforeDetails) { + final Long caseId = details.getId(); + log.info("Local Authority Submit about to submit callback invoked CaseID: {}", caseId); + final CaseDetails updatedCaseDetails = + submissionService.laSubmitApplication(details); + return AboutToStartOrSubmitResponse.builder() + .data(updatedCaseDetails.getData()) + .state(LaSubmitted) + .build(); + } + + public SubmittedCallbackResponse submitted(CaseDetails details, + CaseDetails beforeDetails) { + log.info("Local Authority Submit submitted callback invoked CaseID: {}", details.getId()); + eventPublisher.publishEvent( + LocalAuthorityApplicationSubmitNotificationEvent.builder() + .caseData(details) + .build()); + return SubmittedCallbackResponse.builder().build(); + } +} +``` + +--- + +## 4. Notification Pattern + +### Architecture: Notification Components +1. **Notification Classes** (`@Component`) - Template rendering & dispatch logic +2. **NotificationDispatcher** (`@Service`) - Routes notifications to recipients +3. **SendgridService** (`@Service`) - Email provider integration +4. **Event Handlers** (`@EventListener`) - Async notification triggers + +### Notification Template Pattern +```java +@Component +@Slf4j +public class ApplicationSubmittedNotification extends ApplicantNotification { + @Autowired private IdamService idamService; + @Autowired private SendgridService sendgridService; + @Autowired private CaseDocumentClient caseDocumentClient; + + public void sendToApplicants(CaseData caseData, Long caseId) + throws NotificationClientException, IOException { + + Applicant applicant1 = caseData.getApplicant1(); + Map templateVars = new HashMap<>(); + templateVars.put(APPLICANT_1_FULL_NAME, applicant1.getFirstName() + " " + applicant1.getLastName()); + templateVars.put(APPLICATION_REFERENCE, caseData.getHyphenatedCaseRef()); + templateVars.put(SUBMISSION_RESPONSE_DATE, + caseData.getApplication().getDateOfSubmissionResponse().format(DATE_TIME_FORMATTER)); + + sendgridService.sendEmail( + applicant1.getEmailAddress(), + APPLICANT_APPLICATION_SUBMITTED.template(), + templateVars + ); + } + + public void sendToLocalCourt(CaseData caseData, Long caseId) + throws NotificationClientException, IOException { + // Court notification logic + } + + public void sendToLocalAuthorityPostApplicantSubmission(CaseData caseData, Long caseId) + throws NotificationClientException, IOException { + // LA notification logic + } +} +``` + +### Notification Dispatcher (Router) +```java +@Service +@Slf4j +public class NotificationDispatcher { + public void send(final ApplicantNotification applicantNotification, + final CaseData caseData, final Long caseId) + throws NotificationClientException, IOException { + if (!caseData.getApplicant1().getEmailAddress().isEmpty()) { + applicantNotification.sendToApplicants(caseData, caseId); + try { + applicantNotification.sendToLocalCourt(caseData, caseId); + applicantNotification.sendToLocalAuthorityPostApplicantSubmission(caseData, caseId); + } catch (Exception e) { + log.error("Exception occurred in send method: {}", e); + } + } + } +} +``` + +### Event-Driven Notification Trigger +```java +@Slf4j +@Component +public class ApplicationSubmittedNotificationEventHandler { + private final SendNotificationService sendNotificationService; + + @EventListener + public void sendNotificationPostApplicationSubmission( + ApplicationSubmitNotificationEvent applicationSubmitNotificationEvent) { + Long caseId = applicationSubmitNotificationEvent.caseData().getId(); + log.info("Triggered for CaseID: {}", caseId); + sendNotificationService.sendNotifications( + applicationSubmitNotificationEvent.caseData()); + } +} +``` + +### Sendgrid Integration +```java +@Service +@Slf4j +public class SendgridService { + @Autowired private SendGridClient sendGridClient; + + public void sendEmail(String email, String templateId, Map personalisation) + throws NotificationClientException, IOException { + SendGridRequest sendGridRequest = SendGridRequest.builder() + .to(email) + .templateId(templateId) + .personalisation(personalisation) + .build(); + + sendGridClient.sendEmail(sendGridRequest); + } +} +``` + +### Notification Task (Used in Submission Pipeline) +```java +@Component +@Slf4j +public class SendSubmissionNotifications implements CaseTask { + @Autowired + private ApplicationSubmittedNotification applicationSubmittedNotification; + + @Autowired + private NotificationDispatcher notificationDispatcher; + + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + final CaseData caseData = caseDetails.getData(); + final Long caseId = caseDetails.getId(); + final State state = caseDetails.getState(); + + if (EnumSet.of(Submitted).contains(state)) { + log.info("Sending application submitted notifications for case : {}", caseId); + try { + notificationDispatcher.send(applicationSubmittedNotification, caseData, caseId); + } catch (NotificationClientException | IOException e) { + log.error("Couldn't send notifications"); + } + } + return caseDetails; + } +} +``` + +### Notification Locations +- **notification/** - Main notification classes +- **notification/handlers/** - Event listeners for async triggers +- **common/notification/** - Shared notification base classes +- **common/service/SendNotificationService** - Orchestration service + +--- + +## 5. CCD Model/Config Generation + +### CCD Model with @CCD Annotations +CaseData uses CCD SDK annotations to define field metadata: + +```java +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CaseData { + + @CCD( + label = "Applying with", + access = {DefaultAccess.class}, + typeOverride = FixedRadioList, + typeParameterOverride = "ApplyingWith" + ) + private ApplyingWith applyingWith; + + @CCD( + label = "Are the applicants represented by a solicitor?", + access = {DefaultAccess.class} + ) + private YesOrNo isApplicantRepresentedBySolicitor; + + @CCD( + label = "Date child moved in", + access = {DefaultAccess.class} + ) + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate dateChildMovedIn; + + @CCD( + label = "Applying with someone else reason", + typeOverride = TextArea, + access = {DefaultAccess.class} + ) + private String otherApplicantRelation; + + // Complex nested types with collections + @CCD(label = "Payment Method", + typeOverride = Collection, + typeParameterOverride = "Payment", + access = { CollectionAccess.class }) + private List> applicationPayments; +} +``` + +### Access Control +Access is controlled via specialized access classes: +- `DefaultAccess` - All authenticated users +- `CaseworkerAccess` - Only case workers +- `SystemUpdateAccess` - System update user only +- `CollectionAccess` - Collection-specific access +- `TtlAccess` - Time-to-live field access + +### Tabs Configuration +```java +@Component +public class CaseTypeTab implements CCDConfig { + + @Override + public void configure(final ConfigBuilder configBuilder) { + buildCfvTab(configBuilder); + buildSummaryTab(configBuilder); + buildApplicantsTab(configBuilder); + buildDocumentsTab(configBuilder); + } + + private void buildCfvTab(ConfigBuilder configBuilder) { + configBuilder.tab("cfv", "Case File View") + .forRoles(CASE_WORKER) + .field(CaseData::getCaseFileView, null, "#ARGUMENT(CaseFileView)"); + } + + public void buildApplicantsTab(ConfigBuilder configBuilder) { + configBuilder.tab("applicationDetails", "Applicants") + .showCondition(TabShowCondition.showForState(State.Submitted, State.LaSubmitted)) + .label("LabelApplicant-Heading", + "applyingWith=\"alone\"", + "# Applicant") + .label("LabelApplicants-Heading", + "applyingWith!=\"alone\"", + "# Applicants") + .field("applyingWith", "applyingWith=\"applyingWith\"") + .field("applicant1FirstName") + .field("applicant1LastName") + .field("applicant1DateOfBirth"); + } +} +``` + +### Workbasket Configuration +```java +@Component +public class WorkBasketInputFields implements CCDConfig { + + @Override + public void configure(final ConfigBuilder configBuilder) { + configBuilder + .workBasketInputFields() + .field("hyphenatedCaseRef", "Case reference number") + .field("childrenFirstName", "Child's first name") + .field("childrenLastName", "Child's last name") + .field("dateSubmitted", "Date submitted"); + } +} +``` + +### CCD Configuration Locations +- **adoptioncase/model/** - Core CaseData and domain models with @CCD annotations +- **adoptioncase/model/access/** - Access control classes +- **adoptioncase/tab/** - Tab configurations +- **adoptioncase/workbasket/** - Workbasket/search input fields +- **adoptioncase/event/** - Event configurations (implements CCDConfig) + +--- + +## 6. Test Conventions + +### Base Test Class +```java +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = Adoption.class) +@TestPropertySource(locations = "/application-contract.properties") +public abstract class BaseTest { + @MockitoBean + protected AuthTokenGenerator authTokenGenerator; +} +``` + +### Unit Tests +Use **Mockito** with `@ExtendWith(MockitoExtension.class)`: + +```java +public class ApplicationSubmittedNotificationTest { + + @Mock + private SendgridService sendgridService; + + @Mock + private CaseDocumentClient caseDocumentClient; + + @InjectMocks + private ApplicationSubmittedNotification notification; + + @Test + void testSendToApplicants() throws NotificationClientException, IOException { + // Setup test data + CaseData caseData = caseData(); + Long caseId = 1234567890123456L; + + // Stub mocks + when(sendgridService.sendEmail(...)).doNothing(); + + // Execute + notification.sendToApplicants(caseData, caseId); + + // Verify + verify(sendgridService, times(1)).sendEmail( + eq("test@example.com"), + eq(APPLICANT_APPLICATION_SUBMITTED.template()), + any(Map.class) + ); + } +} +``` + +### Functional Tests +Integration tests extending `FunctionalTest`: + +```java +@Slf4j +@TestPropertySource("classpath:application.yaml") +public abstract class FunctionalTest { + @Autowired protected IdamTokenGenerator idamTokenGenerator; + @Autowired protected CoreCaseDataApi coreCaseDataApi; + @Autowired protected ServiceAuthenticationGenerator serviceAuthenticationGenerator; + + protected CaseDetails createCaseInCcd() { + String userToken = idamTokenGenerator.generateIdamTokenForSystem(); + String s2sTokenForCosApi = serviceAuthenticationGenerator.generate("adoption_cos_api"); + // Create case via CCD API + } + + protected Response triggerCallback(Map caseData, String eventId, String url) + throws IOException { + CallbackRequest request = CallbackRequest.builder() + .eventId(eventId) + .caseDetailsBefore(...) + .caseDetails(...) + .build(); + return triggerCallback(request, url); + } +} +``` + +### Test Utilities +```java +public class TestDataHelper { + public static final LocalDate LOCAL_DATE = LocalDate.of(2021, 4, 28); + + public static ListValue documentWithType(final DocumentType documentType) { + Document ccdDocument = new Document( + "http://localhost:8080/" + UUID.randomUUID().toString(), + "test-draft-adoption-application.pdf", + "..." + ); + AdoptionDocument adoptionDocument = AdoptionDocument.builder() + .documentLink(ccdDocument) + .documentType(documentType) + .build(); + return ListValue.builder() + .value(adoptionDocument) + .build(); + } + + public static CaseData caseData() { + // Factory method for test case data + } + + public static Map getMainTemplateVars(CaseData caseData) { + // Extract template variables for notifications + } +} +``` + +### Test Structure +- **Unit tests** use `@ExtendWith(MockitoExtension.class)` with mocked dependencies +- **Contract/Integration tests** extend `BaseTest` with Spring context +- **Functional tests** extend `FunctionalTest` with CCD API integration +- **Test data** centralized in `testutil/TestDataHelper.java` + +--- + +## 7. LaunchDarkly Feature Flagging + +### LaunchDarkly Client +```java +@Configuration +@Service +@Slf4j +public class LaunchDarklyClient { + public static final LDUser ADOPTION_COS_USER = + new LDUser.Builder("adoption-cos-api") + .anonymous(true) + .build(); + + private final LDClientInterface internalClient; + + @Autowired + public LaunchDarklyClient( + LaunchDarkClientFactory ldClientFactory, + @Value("${launchdarkly.sdk-key:}") String sdkKey, + @Value("${launchdarkly.offline-mode:false}") Boolean offlineMode) { + this.internalClient = ldClientFactory.create(sdkKey, offlineMode); + Runtime.getRuntime().addShutdownHook(new Thread(this::close)); + } + + public boolean isFeatureEnabled(String feature) { + return internalClient.boolVariation(feature, ADOPTION_COS_USER, false); + } + + public boolean isFeatureEnabled(String feature, LDUser user) { + return internalClient.boolVariation(feature, user, false); + } + + private void close() { + try { + internalClient.close(); + } catch (IOException e) { + log.error(e.getMessage()); + } + } +} +``` + +### Factory Pattern +```java +@Service +public class LaunchDarkClientFactory { + public LDClientInterface create(String sdkKey, Boolean offlineMode) { + LDConfig config = new LDConfig.Builder() + .offline(offlineMode) + .build(); + return new LDClient(sdkKey, config); + } +} +``` + +### Configuration +- **config.launchdarkly** - Feature flag client factory +- Properties: `launchdarkly.sdk-key` and `launchdarkly.offline-mode` + +--- + +## 8. Key Integration Points + +### IDAM (Identity & Access Management) +```java +@Service +public class IdamService { + @Value("${idam.systemupdate.username}") + private String systemUpdateUserName; + + @Autowired + private IdamClient idamClient; + + public User retrieveUser(String authorisation) { + final String bearerToken = getBearerToken(authorisation); + final UserDetails userDetails = idamClient.getUserDetails(bearerToken); + return new User(bearerToken, userDetails); + } + + public User retrieveSystemUpdateUserDetails() { + return retrieveUser(getIdamOauth2Token(systemUpdateUserName, systemUpdatePassword)); + } +} +``` + +### CCD (Case Management Data) +- Uses `CoreCaseDataApi` client for case operations +- CCDConfig components auto-register with CCD system +- Case callbacks (aboutToSubmit, submitted) handled via CCD SDK + +### Document Management (DocAssembly/Docmosis) +```java +@Service +@Slf4j +public class DocAssemblyService { + @Autowired private DocAssemblyClient docAssemblyClient; + @Autowired private DocmosisTemplateProvider docmosisTemplateProvider; + + public DocumentInfo renderDocument( + final Map templateContent, + final Long caseId, + final String authorisation, + final String templateId, + final LanguagePreference languagePreference, + final String filename) { + + final String templateName = docmosisTemplateProvider + .templateNameFor(templateId, languagePreference); + final DocAssemblyRequest docAssemblyRequest = DocAssemblyRequest.builder() + .templateId(templateName) + .outputType(PDF) + .caseTypeId(CASE_TYPE) + .data(templateContent) + .build(); + + return docAssemblyClient.generateDocument( + docAssemblyRequest, caseId, authorisation); + } +} +``` + +### Payment Service +- **Payment Model**: Track payment status (SUCCESS, IN_PROGRESS, FAILED) +- **Application integration**: Check `application.hasBeenPaidFor()` before proceeding +- **State transitions**: AwaitingPayment → Submitted upon successful payment + +```java +public class Application { + @JsonIgnore + public PaymentStatus getLastPaymentStatus() { ... } + + @JsonIgnore + public boolean hasBeenPaidFor() { + return null != applicationFeeOrderSummary + && Integer.parseInt(applicationFeeOrderSummary.getPaymentTotal()) + == getPaymentTotal(); + } +} +``` + +### Notification Service (SendGrid) +```java +@Service +@Slf4j +public class SendgridService { + public void sendEmail(String email, String templateId, + Map personalisation) + throws NotificationClientException, IOException { + // Send via SendGrid + } +} +``` + +### CCD Search Service +```java +@Service +@Slf4j +public class CcdSearchService { + @Autowired + private CoreCaseDataApi coreCaseDataApi; + + public List searchCases( + String authorisation, String s2sToken, + String query, String caseType) { + // Elasticsearch query execution + } +} +``` + +--- + +## 9. SystemUpdate Package + +### Purpose +Handles system-initiated operations outside normal user workflows (e.g., automated tasks, scheduled jobs). + +### CaseDetailsConverter +Converts between CCD SDK model and Reform CCD model: + +```java +@Component +public class CaseDetailsConverter { + @Autowired + private ObjectMapper objectMapper; + + public uk.gov.hmcts.ccd.sdk.api.CaseDetails + convertToCaseDetailsFromReformModel(final CaseDetails caseDetails) { + return objectMapper.convertValue(caseDetails, + new TypeReference>() { + }); + } +} +``` + +### Usage +- Scheduled tasks that need to read/update cases +- System-initiated workflows (e.g., bulk operations) +- Conversion between external API models and internal SDK models + +--- + +## 10. SetState Tasks (common/service/task/) + +### SetStateAfterSubmission +Applies conditional logic to determine next state based on payment: + +```java +@Component +@Slf4j +public class SetStateAfterSubmission implements CaseTask { + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + final CaseData caseData = caseDetails.getData(); + final Application application = caseData.getApplication(); + + // Payment-driven state transition + if (!application.hasBeenPaidFor()) { + caseDetails.setState(AwaitingPayment); + } else { + caseDetails.setState(Submitted); + } + + caseDetails.getData().setStatus(Submitted); + log.info("State set to {}, CaseID {}", + caseDetails.getState(), caseDetails.getId()); + return caseDetails; + } +} +``` + +### SetStateAfterLaSubmission +Local Authority submission state transition: + +```java +@Component +@Slf4j +public class SetStateAfterLaSubmission implements CaseTask { + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + log.info("Setting state to LaSubmitted for CaseID: {}", caseDetails.getId()); + caseDetails.setState(LaSubmitted); + caseDetails.getData().setStatus(LaSubmitted); + return caseDetails; + } +} +``` + +### SetDateSubmitted +Captures submission timestamp: + +```java +@Component +@Slf4j +public class SetDateSubmitted implements CaseTask { + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + LocalDate now = LocalDate.now(clock); + caseDetails.getData().getApplication().setDateSubmitted(now); + log.info("Date submitted set for CaseID: {}", caseDetails.getId()); + return caseDetails; + } +} +``` + +### Composition Example +```java +public CaseDetails submitApplication( + final CaseDetails caseDetails) { + return CaseTaskRunner.caseTasks( + setStateAfterSubmission, // Step 1: Determine state + setDateSubmitted, // Step 2: Set timestamp + generateApplicationSummaryDocument // Step 3: Generate PDF + ).run(caseDetails); +} +``` + +--- + +## Key Architectural Principles + +### 1. **Functional Composition** +- Tasks implemented as pure functions: `Function` +- Composed via `CaseTaskRunner` using `Function.andThen()` +- No side effects within tasks themselves + +### 2. **Event-Driven Async** +- Submit callbacks trigger Spring events (`@EventListener`) +- Handlers execute asynchronously for notifications, document generation +- Decouples synchronous CCD operations from async side effects + +### 3. **Dependency Injection** +- All Spring components autowired +- Task dependencies injected into Service classes +- Enables testing via mocking and composition + +### 4. **CCD SDK Integration** +- CCDConfig implementations auto-discovered by Spring +- Annotations drive form generation and access control +- Callbacks provide extension points for business logic + +### 5. **Explicit State Management** +- Clear State enum (Draft, Submitted, LaSubmitted, AwaitingPayment, etc.) +- State transitions explicit in tasks +- All state changes logged + +### 6. **Template-Driven Document Generation** +- Docmosis for PDF rendering +- Language-aware template selection (English/Welsh) +- Async document generation via CompletableFuture + +### 7. **Multi-Channel Notifications** +- SendGrid for email +- Multiple recipient types: Applicants, Courts, Local Authorities +- Template variables centralized in CommonContent + +--- + +## Directory Structure Summary + +``` +src/main/java/uk/gov/hmcts/reform/adoption/ +├── adoptioncase/ +│ ├── event/ # CCD event configurations +│ ├── caseworker/event/ # Caseworker-specific events +│ ├── model/ # Domain models with @CCD annotations +│ ├── tab/ # Tab configurations +│ ├── workbasket/ # Search/workbasket fields +│ ├── task/ # Case-level tasks +│ ├── schedule/ # Scheduled tasks +│ └── service/ # Domain services (CcdSearchService, etc.) +├── service/ +│ ├── event/ # Event records (ApplicationSubmitNotificationEvent) +│ ├── task/ # EventService, ScheduledTaskRunner +├── common/ +│ ├── service/ +│ │ ├── task/ # Reusable tasks (SetState, SetDate, Generate, Send) +│ │ └── SubmissionService # Task composition +│ ├── config/ # EmailTemplatesConfig, DocmosisTemplatesConfig +│ └── notification/ # Base notification classes +├── notification/ +│ ├── handlers/ # @EventListener event handlers +│ ├── NotificationDispatcher +│ ├── SendgridService +│ └── [Notification classes] +├── document/ +│ ├── task/ # Document generation tasks +│ ├── DocAssemblyService +│ ├── CaseDataDocumentService +│ └── model/ +├── config/ +│ └── launchdarkly/ # LaunchDarkly client factory +├── idam/ # IDAM user service +├── systemupdate/ # System conversion utilities +└── controllers/ # REST endpoints (minimal) +``` + +--- + +## Key Takeaways for Copilot Instructions + +1. **Tasks are composable functions** that transform case details - use CaseTaskRunner for pipelines +2. **Events decouple submission from notifications** - publish in submittedCallback, listen with @EventListener +3. **All CCD config extends CCDConfig** and uses ConfigBuilder fluent API +4. **State is explicit and enumerated** - SetState tasks manage transitions +5. **Notifications are template-driven** with multi-channel dispatch (ApplicantNotification, NotificationDispatcher) +6. **Document generation is async** using CompletableFuture within tasks +7. **LaunchDarkly client is injected** as a service for feature flags +8. **External integrations** (IDAM, CCD, DocAssembly, SendGrid) wrapped in dedicated services +9. **Tests use Mockito** with @ExtendWith(MockitoExtension.class) for unit tests +10. **Functional tests extend FunctionalTest** with CCD API integration helpers diff --git a/build.gradle b/build.gradle index 4ecd3ba39..2f48dce5e 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ plugins { id 'idea' id 'pmd' id 'jacoco' + id 'io.spring.dependency-management' version '1.1.7' id 'org.owasp.dependencycheck' version '9.1.0' id 'org.sonarqube' version '5.0.0.4638' id 'org.springframework.boot' version '3.4.11' @@ -45,6 +46,16 @@ ext['elasticsearch.version'] = '7.17.3' ext['snakeyaml.version'] = '2.0' ext['jackson.version'] = '2.15.2' +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +application { + mainClass.set('uk.gov.hmcts.reform.adoption.Application') +} + sourceSets { functionalTest { java { @@ -79,8 +90,8 @@ java { idea { module { - testSourceDirs += file('src/integrationTest/java') - testSourceDirs += file('src/functionalTest/java') + testSources.from file('src/integrationTest/java') + testSources.from file('src/functionalTest/java') } } @@ -94,7 +105,7 @@ configurations { contractTestImplementation.extendsFrom testImplementation } -tasks.withType(JavaCompile) { +tasks.withType(JavaCompile).configureEach { options.compilerArgs << "-Xlint:unchecked" << "-Werror" } @@ -114,24 +125,23 @@ test { failFast = true useJUnitPlatform() } -task integration(type: Test) { + +tasks.register('integration', Test) { description = "Runs integration tests" group = "Verification" testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath failFast = true } + task yarnInstall(type: Exec) { workingDir '.' commandLine 'yarn', 'install' } -task smoke(type: Test,dependsOn: ':yarnInstall') { + +tasks.register('runSmokeTests', Exec) { description = "Runs Smoke Tests" - doLast { - exec { - commandLine 'yarn', 'test:smoke' - } - } + commandLine 'yarn', '--silent', 'test:smoke' } task functional(type: Test,dependsOn: ':yarnInstall') { @@ -142,6 +152,7 @@ task functional(type: Test,dependsOn: ':yarnInstall') { } } } +/// TODO got to here https://github.com/hmcts/fpl-ccd-configuration/pull/6180/changes task highLevelDataSetup(type: JavaExec) { mainClass.set("uk.gov.hmcts.reform.adoption.common.ccd.HighLevelDataSetupApp") classpath += sourceSets.main.runtimeClasspath @@ -571,8 +582,20 @@ generateCCDConfig.doLast { } } +def contractTestConstantsFile = file('src/contractTest/java/uk/gov/hmcts/reform/adoption/ContractTestConstants.java') +if (!contractTestConstantsFile.exists()) { + throw new GradleException("Missing ContractTestConstants.java for pacticipant alignment.") +} +def contractTestConstantsMatcher = (contractTestConstantsFile.text =~ /CONSUMER_NAME\s*=\s*"([^"]+)"/) +if (!contractTestConstantsMatcher.find()) { + throw new GradleException("Unable to read CONSUMER_NAME from ContractTestConstants.java for pacticipant alignment.") +} +def contractTestConsumerName = contractTestConstantsMatcher.group(1) +def pactBrokerUrl = System.getenv("PACT_BROKER_FULL_URL") +def pactBranchName = System.getenv("PACT_BRANCH_NAME") ?: 'Dev' + project.ext { - pacticipant = 'sscs_tribunalsCaseApi' + pacticipant = contractTestConsumerName pacticipantVersion = getCheckedOutGitCommitHash() } @@ -580,19 +603,28 @@ task runAndPublishConsumerPactTests(type: Test){ logger.lifecycle("Runs pact Tests") testClassesDirs = sourceSets.contractTest.output.classesDirs classpath = sourceSets.contractTest.runtimeClasspath - } runAndPublishConsumerPactTests.dependsOn contract runAndPublishConsumerPactTests.finalizedBy pactPublish +tasks.named('pactPublish') { + onlyIf { + if (!pactBrokerUrl) { + logger.lifecycle("PACT_BROKER_FULL_URL not set; skipping pactPublish.") + return false + } + return true + } +} + pact { broker { - pactBrokerUrl = System.getenv("PACT_BROKER_FULL_URL") ?: 'http://localhost:80' + pactBrokerUrl = pactBrokerUrl ?: 'http://localhost:80' } publish { pactDirectory = 'pacts' - tags = [System.getenv("PACT_BRANCH_NAME") ?:'Dev'] + tags = [pactBranchName] version = project.pacticipantVersion } } @@ -637,4 +669,3 @@ bootWithCCD { } authMode = uk.gov.hmcts.rse.AuthMode.AAT } - diff --git a/copilot-instructions.md b/copilot-instructions.md new file mode 100644 index 000000000..392d70d5a --- /dev/null +++ b/copilot-instructions.md @@ -0,0 +1,633 @@ +# Adoption COS API - Copilot Development Instructions + +## Quick Reference + +This document provides AI Copilot with essential patterns and conventions for developing in adoption-cos-api. Refer to [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed explanations with code examples. + +--- + +## 1. Creating a New CCD Event + +**Pattern:** Implement `CCDConfig` as `@Component` + +### Template +```java +@Component +@Slf4j +public class MyNewEvent implements CCDConfig { + + public static final String EVENT_ID = "my-new-event"; + + @Autowired + private MyService myService; // Inject dependencies + + @Override + public void configure(final ConfigBuilder configBuilder) { + configBuilder + .event(EVENT_ID) + .forState(Draft) // or .forStates(Draft, AwaitingPayment) for multiple + .name("User-visible event name") + .description("Description shown in UI") + .ttlIncrement(90) // Optional: time-to-live + .grant(CREATE_READ_UPDATE, CITIZEN) + .grant(READ, CASE_WORKER) + .retries(120, 120) + .aboutToSubmitCallback(this::aboutToSubmit); // Optional + // .submittedCallback(this::submitted); // Optional + } + + public AboutToStartOrSubmitResponse aboutToSubmit( + final CaseDetails details, + final CaseDetails beforeDetails) { + + log.info("Event {} about to submit callback invoked for CaseID: {}", + EVENT_ID, details.getId()); + + CaseData data = details.getData(); + State state = details.getState(); + + // Validation + List errors = new ArrayList<>(); + if (data.getSomeField() == null) { + errors.add("Some field is required"); + } + + if (!errors.isEmpty()) { + return AboutToStartOrSubmitResponse.builder() + .data(data) + .state(state) + .errors(errors) + .build(); + } + + // Data transformation + data.setProcessedField(processField(data)); + + return AboutToStartOrSubmitResponse.builder() + .data(data) + .state(state) // Can change state here + .build(); + } + + // Optional: Called after case is submitted + public SubmittedCallbackResponse submitted( + CaseDetails details, + CaseDetails beforeDetails) { + + log.info("Submitted callback invoked for CaseID: {}", details.getId()); + // Async operations here (e.g., publish events) + return SubmittedCallbackResponse.builder().build(); + } +} +``` + +### Key Points +- Always use `@Component` + `@Slf4j` +- Event ID convention: lowercase with hyphens (e.g., "citizen-submit-application") +- aboutToSubmit: Validation + synchronous data transformation +- submitted: Async operations, event publishing (no response data returned) +- Use ConfigBuilder fluent API for configuration + +--- + +## 2. Creating a New Task + +**Pattern:** Implement `CaseTask` (a Function interface), annotate with `@Component` + +### Single Task +```java +@Component +@Slf4j +public class MyNewTask implements CaseTask { + + @Autowired + private SomeDependency someDependency; + + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + final CaseData data = caseDetails.getData(); + final Long caseId = caseDetails.getId(); + final State state = caseDetails.getState(); + + log.info("Executing MyNewTask for CaseID: {}, State: {}", caseId, state); + + // Perform transformation + data.setNewField(calculateValue(data)); + + return caseDetails; + } +} +``` + +### Composing Tasks (Pipelines) +```java +@Service +public class MySubmissionService { + + @Autowired private Task1 task1; + @Autowired private Task2 task2; + @Autowired private Task3 task3; + + public CaseDetails processCase( + final CaseDetails caseDetails) { + + return CaseTaskRunner.caseTasks( + task1, // Executed first + task2, // Executed second, receives output from task1 + task3 // Executed third, receives output from task2 + ).run(caseDetails); + } +} +``` + +### Typical Task Locations +- **SetState tasks**: common/service/task/ (manage state transitions) +- **Document generation**: common/service/task/ or document/task/ +- **Data validation/transformation**: common/service/task/ +- **Notifications**: common/service/task/SendSubmissionNotifications + +--- + +## 3. Publishing Events & Notification Handlers + +**Pattern:** Publish Spring events in `submittedCallback`, listen with `@EventListener` + +### Define Event Record +```java +// service/event/MyCustomEvent.java +@Builder +public record MyCustomEvent(CaseDetails caseData) { } +``` + +### Publish Event (in submittedCallback) +```java +@Autowired +private EventService eventPublisher; + +public SubmittedCallbackResponse submitted( + CaseDetails details, + CaseDetails beforeDetails) { + + log.info("Publishing MyCustomEvent for CaseID: {}", details.getId()); + eventPublisher.publishEvent( + MyCustomEvent.builder() + .caseData(details) + .build()); + + return SubmittedCallbackResponse.builder().build(); +} +``` + +### Listen to Event (Async Handler) +```java +// notification/handlers/MyEventHandler.java +@Slf4j +@Component +public class MyEventHandler { + + @Autowired + private MyService myService; + + @EventListener + public void handleMyCustomEvent(MyCustomEvent event) { + Long caseId = event.caseData().getId(); + log.info("MyEventHandler triggered for CaseID: {}", caseId); + + try { + myService.doSomethingAsync(event.caseData()); + } catch (Exception e) { + log.error("Error handling event", e); + } + } +} +``` + +--- + +## 4. Creating Notifications + +**Pattern:** Extend `ApplicantNotification`, use `NotificationDispatcher` and `SendgridService` + +### Notification Class +```java +@Component +@Slf4j +public class MyNotification extends ApplicantNotification { + + @Autowired + private SendgridService sendgridService; + + @Autowired + private IdamService idamService; + + @Autowired + private CaseDocumentClient caseDocumentClient; + + public void sendToApplicants(CaseData caseData, Long caseId) + throws NotificationClientException, IOException { + + Applicant applicant1 = caseData.getApplicant1(); + Map templateVars = new HashMap<>(); + + // Add template variables + templateVars.put("applicant_name", applicant1.getFirstName()); + templateVars.put("case_reference", caseData.getHyphenatedCaseRef()); + templateVars.put("submission_date", + caseData.getApplication().getDateSubmitted()); + + // Send email + sendgridService.sendEmail( + applicant1.getEmailAddress(), + EmailTemplateName.MY_TEMPLATE.template(), + templateVars + ); + } + + public void sendToLocalCourt(CaseData caseData, Long caseId) + throws NotificationClientException, IOException { + // Court notification logic + } +} +``` + +### Trigger Notification via Task +```java +@Component +@Slf4j +public class MyNotificationTask implements CaseTask { + + @Autowired + private MyNotification myNotification; + + @Autowired + private NotificationDispatcher dispatcher; + + @Override + public CaseDetails apply(final CaseDetails caseDetails) { + final CaseData data = caseDetails.getData(); + final Long caseId = caseDetails.getId(); + + log.info("Sending notifications for CaseID: {}", caseId); + + try { + dispatcher.send(myNotification, data, caseId); + } catch (NotificationClientException | IOException e) { + log.error("Failed to send notifications", e); + } + + return caseDetails; + } +} +``` + +--- + +## 5. Defining CCD Fields with Annotations + +**Pattern:** Use `@CCD` annotation with metadata on CaseData fields + +### Basic Field +```java +@CCD( + label = "First Name", + access = {DefaultAccess.class} +) +private String firstName; +``` + +### Field with Type Override +```java +@CCD( + label = "Description", + access = {DefaultAccess.class}, + typeOverride = TextArea +) +private String description; +``` + +### Radio List Field +```java +@CCD( + label = "Applying with", + access = {DefaultAccess.class}, + typeOverride = FixedRadioList, + typeParameterOverride = "ApplyingWith" +) +private ApplyingWith applyingWith; +``` + +### Collection Field +```java +@CCD( + label = "Documents", + typeOverride = Collection, + typeParameterOverride = "AdoptionDocument", + access = {CollectionAccess.class} +) +private List> documents; +``` + +### Date Field +```java +@CCD( + label = "Date Submitted", + access = {DefaultAccess.class} +) +@JsonFormat(pattern = "yyyy-MM-dd") +private LocalDate dateSubmitted; +``` + +### Access Control Options +- `DefaultAccess` - All authenticated users +- `CaseworkerAccess` - Case workers only +- `SystemUpdateAccess` - System user only +- `CollectionAccess` - Collection-specific access + +--- + +## 6. Building Tabs + +**Pattern:** Implement `CCDConfig` as `@Component` in `adoptioncase/tab/` + +```java +@Component +public class MyTabConfiguration implements CCDConfig { + + @Override + public void configure(final ConfigBuilder configBuilder) { + configBuilder.tab("myTabId", "My Tab Label") + .forRoles(CASE_WORKER, DISTRICT_JUDGE) + .showCondition(TabShowCondition.showForState(State.Submitted, State.LaSubmitted)) + .field("field1") + .field("field2") + .field("field3", "field2 != null"); // Conditional display + } +} +``` + +**Show Conditions:** +```java +.showCondition("applyingWith=\"alone\"") // Show if condition matches +.label("LabelKey", "condition", "Display Text") // Dynamic labels +``` + +--- + +## 7. Unit Testing + +**Pattern:** Use `@ExtendWith(MockitoExtension.class)` with `@Mock` and `@InjectMocks` + +```java +public class MyComponentTest { + + @Mock + private DependencyA dependencyA; + + @Mock + private DependencyB dependencyB; + + @InjectMocks + private MyComponent myComponent; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testMyComponentLogic() throws Exception { + // Setup + when(dependencyA.getValue()).thenReturn("expected"); + CaseData caseData = TestDataHelper.caseData(); + + // Execute + MyResult result = myComponent.process(caseData); + + // Assert + assertThat(result).isNotNull(); + assertThat(result.getStatus()).isEqualTo("processed"); + + // Verify mocks were called correctly + verify(dependencyA, times(1)).getValue(); + verify(dependencyB).doSomething(any()); + } + + @Test + void testErrorScenario() { + when(dependencyA.getValue()).thenThrow(new RuntimeException("Error")); + + assertThatThrownBy(() -> myComponent.process(null)) + .isInstanceOf(RuntimeException.class); + } +} +``` + +--- + +## 8. Integration Testing + +**Pattern:** Extend `FunctionalTest` for CCD API integration tests + +```java +@Slf4j +public class MyIntegrationTest extends FunctionalTest { + + @Test + void testCreateCaseAndTriggerEvent() throws IOException { + // Create case + CaseDetails caseDetails = createCaseInCcd(); + Long caseId = caseDetails.getId(); + + // Trigger callback + Map caseData = getCaseData(caseDetails); + Response response = triggerCallback( + caseData, + "my-event", + "/callback/my-event" + ); + + // Assert + assertThat(response.getStatusCode()).isEqualTo(200); + CaseData resultData = response.body().as(CaseData.class); + assertThat(resultData.getField()).isEqualTo("expected"); + } +} +``` + +### Test Data Helpers +```java +// Use TestDataHelper for test data +CaseData caseData = TestDataHelper.caseData(); +ListValue document = TestDataHelper.documentWithType(DocumentType.APPLICATION); +Map templateVars = TestDataHelper.getMainTemplateVars(caseData); +``` + +--- + +## 9. Using LaunchDarkly Features + +**Pattern:** Inject `LaunchDarklyClient` service to check feature flags + +```java +@Service +public class MyService { + + @Autowired + private LaunchDarklyClient launchDarklyClient; + + public void myMethod(CaseData caseData) { + if (launchDarklyClient.isFeatureEnabled("my-feature-flag")) { + // Execute new feature code + executeNewFeature(caseData); + } else { + // Fallback to existing code + executeExistingFeature(caseData); + } + } +} +``` + +**Configuration:** +```properties +launchdarkly.sdk-key=your-sdk-key +launchdarkly.offline-mode=false +``` + +--- + +## 10. Document Generation + +**Pattern:** Implement task that calls `CaseDataDocumentService.renderDocumentAndUpdateCaseData()` + +```java +@Component +@Slf4j +public class MyDocumentGenerationTask implements CaseTask { + + @Autowired + private CaseDataDocumentService caseDataDocumentService; + + @Autowired + private ObjectMapper objectMapper; + + @Override + public CaseDetails apply( + final CaseDetails caseDetails) { + + final CaseData caseData = caseDetails.getData(); + final Long caseId = caseDetails.getId(); + + log.info("Generating document for CaseID: {}", caseId); + + // Prepare template content + @SuppressWarnings("unchecked") + Map templateContent = + objectMapper.convertValue(caseData, Map.class); + + // Select document type based on language preference + LanguagePreference language = caseData.getApplicant1() + .getLanguagePreference(); + DocumentType documentType = language.equals(LanguagePreference.ENGLISH) + ? DocumentType.MY_DOCUMENT_EN + : DocumentType.MY_DOCUMENT_CY; + + // Generate document (async) + CompletableFuture generation = CompletableFuture.runAsync(() -> + caseDataDocumentService.renderDocumentAndUpdateCaseData( + caseData, + documentType, + templateContent, + caseId, + "MY_DOCUMENT_TYPE", + language, + formatDocumentName(caseId, "MyDocument", LocalDateTime.now()) + ) + ); + + // Wait for completion + CompletableFuture.allOf(generation).join(); + + return caseDetails; + } +} +``` + +--- + +## Common Workflows + +### Workflow: Case Submission (Citizen) +1. Event: `CitizenSubmitApplication` (aboutToSubmit callback) + - Validate case data + - Return errors if invalid + - Return updated state +2. Task Pipeline (SubmissionService.submitApplication): + - `SetStateAfterSubmission` - Determine next state + - `SetDateSubmitted` - Record submission date + - `GenerateApplicationSummaryDocument` - Create PDF +3. Event: `CitizenAddPayment` (submittedCallback) + - Publish `ApplicationSubmitNotificationEvent` +4. Handler: `ApplicationSubmittedNotificationEventHandler` + - Send emails to applicants, court, local authority + +### Workflow: State Transitions +Draft → AwaitingPayment → Submitted → LaSubmitted → [Case Processing] + +Use SetState tasks to manage transitions: +```java +CaseTaskRunner.caseTasks( + setStateAfterSubmission, // Determines state based on payment + setDateSubmitted, + generateDocuments +).run(caseDetails); +``` + +### Workflow: Multi-Language Support +1. Check `applicant.getLanguagePreference()` +2. Select document type: `DocumentType.MY_DOCUMENT_EN` or `MY_DOCUMENT_CY` +3. Template rendering handles Welsh/English content + +--- + +## Do's & Don'ts + +### ✅ DO +- Create tasks as `@Component` implementing `CaseTask` +- Use `CaseTaskRunner.caseTasks()` to compose task pipelines +- Publish events in `submittedCallback`, not `aboutToSubmit` +- Validate in `aboutToSubmit`, perform side effects in `submitted` +- Log with case ID: `log.info("Event {} for CaseID: {}", eventId, caseId)` +- Use dependency injection (`@Autowired`) +- Catch checked exceptions from notification/document services +- Use `@Builder` for records and POJOs + +### ❌ DON'T +- Modify case data in async handlers (use tasks instead) +- Publish events in `aboutToSubmit` (must return response) +- Execute long-running operations in callbacks (use tasks/handlers) +- Hardcode case references or state strings (use State enum) +- Mix business logic in event configuration +- Forget to add `@Slf4j` to components +- Use `new` for Spring-managed dependencies + +--- + +## Common Patterns Summary + +| Pattern | Location | Key Interface | Key Method | +|---------|----------|---------------|------------| +| CCD Event | adoptioncase/event/ | CCDConfig | configure() | +| Task | */task/ | CaseTask | apply() | +| Service | */service/ | (any) | Methods using CaseTaskRunner | +| Event Handler | notification/handlers/ | (any) | @EventListener methods | +| Notification | notification/ | ApplicantNotification | send*() methods | +| Tab Config | adoptioncase/tab/ | CCDConfig | configure() | +| Scheduled Task | adoptioncase/schedule/ | Runnable | run() | + +--- + +## References +- See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed examples with full code listings +- Review existing events in `adoptioncase/event/` and `adoptioncase/caseworker/event/` +- Review existing tasks in `common/service/task/` +- Check test examples in `src/test/java/` for testing patterns diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a4..7705927e9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/contractTest/java/uk/gov/hmcts/reform/adoption/CaseDocumentClientPactTest.java b/src/contractTest/java/uk/gov/hmcts/reform/adoption/CaseDocumentClientPactTest.java index b8db029f0..451ea6e91 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/adoption/CaseDocumentClientPactTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/adoption/CaseDocumentClientPactTest.java @@ -1,6 +1,7 @@ package uk.gov.hmcts.reform.adoption; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.MockServer; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; @@ -45,7 +46,7 @@ public class CaseDocumentClientPactTest { @Autowired private CaseDocumentClient caseDocumentClient; - @Pact(provider = "case-document-am-api", consumer = "adoption_cos_api") + @Pact(provider = "case-document-am-api", consumer = ContractTestConstants.CONSUMER_NAME) public RequestResponsePact downloadBinaryPact(PactDslWithProvider builder) throws IOException { Map headers = Maps.newHashMap(); headers.put("Authorization", AUTH_TOKEN); @@ -65,7 +66,8 @@ public RequestResponsePact downloadBinaryPact(PactDslWithProvider builder) throw @Test @PactTestFor(pactMethod = "downloadBinaryPact") - public void verifyDownloadBinary() throws JSONException { + public void verifyDownloadBinary(MockServer mockServer) throws JSONException { + MockServerReadiness.awaitReady("127.0.0.1", mockServer.getPort()); when(authTokenGenerator.generate()).thenReturn(SOME_SERVICE_AUTHORIZATION_TOKEN); ResponseEntity response = caseDocumentClient.getDocumentBinary( AUTH_TOKEN, diff --git a/src/contractTest/java/uk/gov/hmcts/reform/adoption/ContractTestConstants.java b/src/contractTest/java/uk/gov/hmcts/reform/adoption/ContractTestConstants.java new file mode 100644 index 000000000..4c8308ced --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/adoption/ContractTestConstants.java @@ -0,0 +1,8 @@ +package uk.gov.hmcts.reform.adoption; + +public final class ContractTestConstants { + public static final String CONSUMER_NAME = "adoption_cos_api"; + + private ContractTestConstants() { + } +} diff --git a/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamApiConsumerTest.java b/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamApiConsumerTest.java index fe033c696..8efa94fae 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamApiConsumerTest.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamApiConsumerTest.java @@ -3,6 +3,7 @@ import au.com.dius.pact.consumer.dsl.PactDslJsonBody; import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.MockServer; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; import au.com.dius.pact.core.model.annotations.Pact; @@ -24,7 +25,7 @@ public class IdamApiConsumerTest extends IdamConsumerTestBase { - @Pact(provider = "idamApi_oidc", consumer = "adoption_cos_api") + @Pact(provider = "idamApi_oidc", consumer = ContractTestConstants.CONSUMER_NAME) public RequestResponsePact generatePactForUserInfo(PactDslWithProvider builder) throws JSONException { return builder @@ -39,7 +40,7 @@ public RequestResponsePact generatePactForUserInfo(PactDslWithProvider builder) .toPact(); } - @Pact(provider = "idamApi_oidc", consumer = "adoption_cos_api") + @Pact(provider = "idamApi_oidc", consumer = ContractTestConstants.CONSUMER_NAME) public RequestResponsePact generatePactForToken(PactDslWithProvider builder) { Map responseheaders = ImmutableMap.builder() @@ -68,7 +69,8 @@ public RequestResponsePact generatePactForToken(PactDslWithProvider builder) { @Test @PactTestFor(pactMethod = "generatePactForUserInfo") - public void verifyIdamUserDetailsRolesPactUserInfo() { + public void verifyIdamUserDetailsRolesPactUserInfo(MockServer mockServer) { + awaitMockServerReady(mockServer); UserInfo userInfo = idamApi.retrieveUserInfo(SOME_AUTHORIZATION_TOKEN); assertNotNull(userInfo.getUid()); assertNotNull(userInfo.getSub()); @@ -81,8 +83,8 @@ public void verifyIdamUserDetailsRolesPactUserInfo() { @Test @PactTestFor(pactMethod = "generatePactForToken") - public void verifyIdamUserDetailsRolesPactToken() { - + public void verifyIdamUserDetailsRolesPactToken(MockServer mockServer) { + awaitMockServerReady(mockServer); TokenResponse token = idamApi.generateOpenIdToken(buildTokenRequestMap()); assertNotNull("Token is expected", token.accessToken); } diff --git a/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamConsumerTestBase.java b/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamConsumerTestBase.java index d9ed15f18..8744b763b 100644 --- a/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamConsumerTestBase.java +++ b/src/contractTest/java/uk/gov/hmcts/reform/adoption/IdamConsumerTestBase.java @@ -5,12 +5,12 @@ import au.com.dius.pact.core.model.annotations.PactFolder; import org.apache.http.client.fluent.Executor; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; +import au.com.dius.pact.consumer.MockServer; import uk.gov.hmcts.reform.idam.client.IdamApi; @ExtendWith(PactConsumerTestExt.class) @@ -22,7 +22,6 @@ }) public abstract class IdamConsumerTestBase { - public static final int SLEEP_TIME = 2000; protected static final String SOME_AUTHORIZATION_TOKEN = "Bearer UserAuthToken"; @Autowired protected IdamApi idamApi; @@ -33,9 +32,8 @@ public abstract class IdamConsumerTestBase { @Value("${idam.client.secret}") protected String clientSecret; - @BeforeEach - public void prepareTest() throws Exception { - Thread.sleep(SLEEP_TIME); + protected void awaitMockServerReady(MockServer mockServer) { + MockServerReadiness.awaitReady("127.0.0.1", mockServer.getPort()); } @AfterEach diff --git a/src/contractTest/java/uk/gov/hmcts/reform/adoption/MockServerReadiness.java b/src/contractTest/java/uk/gov/hmcts/reform/adoption/MockServerReadiness.java new file mode 100644 index 000000000..dd73b9507 --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/adoption/MockServerReadiness.java @@ -0,0 +1,66 @@ +package uk.gov.hmcts.reform.adoption; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.time.Duration; +import java.time.Instant; + +public final class MockServerReadiness { + private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10); + private static final Duration DEFAULT_POLL_INTERVAL = Duration.ofMillis(200); + private static final Duration READY_STABILITY = Duration.ofSeconds(2); + + private MockServerReadiness() { + } + + public static void awaitReady(String host, int port) { + awaitReady(host, port, DEFAULT_TIMEOUT, DEFAULT_POLL_INTERVAL); + } + + static void awaitReady(String host, int port, Duration timeout, Duration pollInterval) { + Instant deadline = Instant.now().plus(timeout); + IOException lastException = null; + Instant firstStableConnect = null; + + while (Instant.now().isBefore(deadline)) { + boolean connected = false; + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(host, port), (int) pollInterval.toMillis()); + connected = true; + } catch (IOException ex) { + lastException = ex; + } + + if (connected) { + if (firstStableConnect == null) { + firstStableConnect = Instant.now(); + } + if (Duration.between(firstStableConnect, Instant.now()).compareTo(READY_STABILITY) >= 0) { + return; + } + } else { + firstStableConnect = null; + } + + pause(pollInterval); + } + + String message = String.format( + "Pact mock server on %s:%d did not become ready within %d ms", + host, + port, + timeout.toMillis() + ); + throw new AssertionError(message, lastException); + } + + private static void pause(Duration pollInterval) { + try { + Thread.sleep(pollInterval.toMillis()); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new AssertionError("Mock server readiness check was interrupted", ex); + } + } +} From d536c0dd8c49f9b6c6ae1321d2e40fd04133b7e2 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:24:57 +0000 Subject: [PATCH 05/23] Amended docker image due to pipeline failure --- .gitignore | 5 +++++ Dockerfile | 2 +- bin/generate-ccd-definition.sh | 2 +- charts/adoption-cos-api/Chart.yaml | 10 +++++----- charts/adoption-cos-api/values.preview.template.yaml | 10 +++++----- charts/adoption-cos-api/values.yaml | 2 +- docker-compose.yml | 2 +- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 08fc66426..c28baaf12 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,8 @@ bin/main/application.yaml ### Generated CCD Json Definition ### ccd-definitions/definitions/development + +# Copilot/architecture files +ARCHITECTURE.md +copilot-instructions.md +.github/copilot-instructions.md diff --git a/Dockerfile b/Dockerfile index c1ca6cd37..daaee6b34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG APP_INSIGHTS_AGENT_VERSION=3.5.2 # Application image -FROM hmctspublic.azurecr.io/base/java:21-distroless +FROM hmctsprod.azurecr.io/base/java:21-distroless COPY lib/applicationinsights.json /opt/app/ COPY build/libs/adoption-cos-api.jar /opt/app/ diff --git a/bin/generate-ccd-definition.sh b/bin/generate-ccd-definition.sh index 9bee9d194..5e9cb8bdf 100755 --- a/bin/generate-ccd-definition.sh +++ b/bin/generate-ccd-definition.sh @@ -21,5 +21,5 @@ echo "Additional params: ${additionalParameters}" docker run --pull always --rm --name json2xlsx \ -v ${definition_input_dir}:/tmp/ccd-definition \ -v ${definition_output_file}:/tmp/ccd-definition.xlsx \ - hmctspublic.azurecr.io/ccd/definition-processor:${definition_processor_version} \ + hmctssbox.azurecr.io/ccd/definition-processor:${definition_processor_version} \ json2xlsx -D /tmp/ccd-definition -o /tmp/ccd-definition.xlsx ${additionalParameters} diff --git a/charts/adoption-cos-api/Chart.yaml b/charts/adoption-cos-api/Chart.yaml index 26f05e488..d55f264e8 100644 --- a/charts/adoption-cos-api/Chart.yaml +++ b/charts/adoption-cos-api/Chart.yaml @@ -9,21 +9,21 @@ maintainers: dependencies: - name: java version: 5.3.0 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' - name: ccd version: 9.2.2 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' tags: - ccd-idam-pr - name: xui-webapp version: ~1.0.0 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' condition: xui-webapp.enabled - name: ccd-case-document-am-api version: 1.7.17 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' condition: ccd-case-document-am-api.enabled - name: postgresql version: 1.1.0 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' condition: postgresql.enabled diff --git a/charts/adoption-cos-api/values.preview.template.yaml b/charts/adoption-cos-api/values.preview.template.yaml index 5f0ed4d26..1b90fbbeb 100644 --- a/charts/adoption-cos-api/values.preview.template.yaml +++ b/charts/adoption-cos-api/values.preview.template.yaml @@ -142,7 +142,7 @@ ccd: ccd-admin-web: nodejs: disableKeyVaults: true - image: hmctspublic.azurecr.io/ccd/admin-web:latest + image: hmctssbox.azurecr.io/ccd/admin-web:latest ingressHost: admin-web-${SERVICE_FQDN} environment: USER_PROFILE_DB_HOST: '{{ tpl .Values.global.postgresHostname $}}' @@ -168,13 +168,13 @@ ccd: replicas: 1 logstash: - image: "hmctspublic.azurecr.io/imported/logstash/logstash" + image: "hmctssbox.azurecr.io/imported/logstash/logstash" imageTag: "7.16.1" imagePullPolicy: "IfNotPresent" logstashJavaOpts: "-Xmx1g -Xms512M" extraInitContainers: | - name: download-postgres-jdbc - image: hmctspublic.azurecr.io/curl:7.70.0 + image: hmctssbox.azurecr.io/curl:7.70.0 command: ['curl', '-L', 'https://jdbc.postgresql.org/download/postgresql-42.2.18.jar', '-o', '/logstash-lib/postgresql.jar'] volumeMounts: - name: logstash-lib @@ -344,7 +344,7 @@ xui-webapp: nodejs: imagePullPolicy: Always releaseNameOverride: ${SERVICE_NAME}-xui-webapp - image: hmctspublic.azurecr.io/xui/webapp:latest + image: hmctssbox.azurecr.io/xui/webapp:latest ingressHost: xui-${SERVICE_FQDN} environment: HEALTH_CCD_COMPONENT_API: http://${SERVICE_NAME}-ccd-api-gw/health @@ -366,7 +366,7 @@ ccd-case-document-am-api: disableKeyVaults: true releaseNameOverride: ${SERVICE_NAME}-ccd-case-document-am-api imagePullPolicy: Always - image: hmctspublic.azurecr.io/ccd/case-document-am-api:latest + image: hmctssbox.azurecr.io/ccd/case-document-am-api:latest ingressHost: ccd-case-document-am-api-${SERVICE_FQDN} environment: CASE_DOCUMENT_S2S_AUTHORISED_SERVICES: ccd_case_document_am_api,ccd_gw,xui_webapp,ccd_data,bulk_scan_processor,dg_docassembly_api,bulk_scan_orchestrator,adoption_cos_api,adoption_web diff --git a/charts/adoption-cos-api/values.yaml b/charts/adoption-cos-api/values.yaml index 408dafa9f..f78972d4d 100644 --- a/charts/adoption-cos-api/values.yaml +++ b/charts/adoption-cos-api/values.yaml @@ -12,7 +12,7 @@ ccd-case-document-am-api: java: applicationPort: 4550 - image: hmctspublic.azurecr.io/adoption/cos-api:latest + image: hmctsprod.azurecr.io/adoption/cos-api:latest ingressHost: adoption-cos-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal aadIdentityName: adoption keyVaults: diff --git a/docker-compose.yml b/docker-compose.yml index cdf222a03..9fa7ea618 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - http_proxy - https_proxy - no_proxy - image: hmctspublic.azurecr.io/adoption-cos-api + image: hmctssbox.azurecr.io/adoption-cos-api environment: # these environment variables are used by java-logging library - ROOT_APPENDER From c2de94dc99019d9f8a3a65c1a1ea3ef524f600cd Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:31:08 +0000 Subject: [PATCH 06/23] Remove architecture and copilot instruction files from tracking --- .github/copilot-instructions.md | 200 ------ ARCHITECTURE.md | 1088 ------------------------------- copilot-instructions.md | 633 ------------------ 3 files changed, 1921 deletions(-) delete mode 100644 .github/copilot-instructions.md delete mode 100644 ARCHITECTURE.md delete mode 100644 copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 7906e9cf7..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,200 +0,0 @@ -# Copilot Instructions — adoption-cos-api - -## Build, Test, and Lint Commands - -- **Prerequisite:** JDK 21 (set `JAVA_HOME` to `/usr/lib/jvm/java-21-openjdk-`) -- **Build:** `./gradlew build` -- **Unit tests:** `./gradlew test` -- **Single test:** `./gradlew test --tests 'uk.gov.hmcts.reform.adoption.'` -- **Lint / static analysis:** `./gradlew check` (runs Checkstyle + PMD) -- **Integration tests:** `./gradlew integration` -- **Contract (Pact) tests:** `./gradlew contract` -- **Coverage report:** `./gradlew jacocoTestReport` → `build/reports/` - -> **Note:** The README references JDK 17 — this is outdated. JDK 21 is required. - -### CCD Config Generation - -```bash -./gradlew generateCCDConfig # JSON definitions → build/definitions/ -./gradlew buildCCDXlsx # Excel spreadsheet (depends on generateCCDConfig) -./gradlew generateTypeScript # TypeScript enums/constants consumed by adoption-web -``` - -Run these after changing any CCD model class, event, tab, state, or access profile. - -### Local Stack - -```bash -./gradlew bootWithCcd # Starts CCD + XUI locally on http://localhost:3000 -``` - -Requires F5 VPN and `az acr login --name hmctspublic`. AAT IDAM/services are used for auth. - ---- - -## Architecture - -This is a **Spring Boot CCD callback API** for the HMCTS Adoption service. It sits between CCD and upstream services — it does not serve a UI directly. - -``` -adoption-web / XUI - │ - ▼ - CCD ←──── adoption-cos-api (case type: A58, jurisdiction: ADOPTION) - │ - ┌───────────┼───────────────┐ - ▼ ▼ ▼ - IDAM case-document-am payment-api - (doc store) - GOV.UK Notify - (via SendGrid) -``` - -**Case type:** `A58` | **Jurisdiction:** `ADOPTION` - -All external service URLs default to their AAT (`*.aat.platform.hmcts.net`) equivalents, overridable via environment variables (see `application.yaml`). - ---- - -## Key Conventions - -### CCDConfig / Event Pattern - -Every CCD event is a `@Component` implementing `CCDConfig`. The `configure()` method registers the event with CCD, wires pages, and attaches callbacks: - -```java -@Component -public class CaseworkerFoo implements CCDConfig { - public static final String CASEWORKER_FOO = "caseworker-foo"; - - private final CcdPageConfiguration fooPage = new FooPage(); - - @Override - public void configure(ConfigBuilder configBuilder) { - var pageBuilder = new PageBuilder(configBuilder - .event(CASEWORKER_FOO) - .forStates(State.Submitted) - .name("Foo event") - .aboutToStartCallback(this::aboutToStart) - .aboutToSubmitCallback(this::aboutToSubmit) - .grant(Permissions.CREATE_READ_UPDATE, UserRole.CASE_WORKER)); - fooPage.addTo(pageBuilder); - } -} -``` - -Available callbacks: `aboutToStartCallback`, `aboutToSubmitCallback`, `submittedCallback`. - -### CcdPageConfiguration / Page Pattern - -Each CCD wizard page is a separate class in a `page/` sub-package alongside its event. Pages implement `CcdPageConfiguration`: - -```java -public class FooPage implements CcdPageConfiguration { - @Override - public void addTo(PageBuilder pageBuilder) { - pageBuilder.page("fooPage") - .pageLabel("Foo") - .complex(CaseData::getFooData) - .mandatory(FooData::getField1) - .optional(FooData::getField2) - .done(); - } -} -``` - -### CaseTask Pattern - -`CaseTask` is a `Function, CaseDetails>`. Tasks are `@Component` beans composed and run via `CaseTaskRunner`: - -```java -// Definition -@Component -public class MyTask implements CaseTask { - @Override - public CaseDetails apply(CaseDetails details) { - details.getData().setSomething("value"); - return details; - } -} - -// Usage in an event callback -public AboutToStartOrSubmitResponse aboutToSubmit(...) { - var result = CaseTaskRunner.caseTasks(taskA, taskB, taskC).run(details); - return AboutToStartOrSubmitResponse.builder() - .data(result.getData()) - .build(); -} -``` - -Tasks under `adoptioncase/task/` and `common/service/task/` handle state transitions, date setting, and document generation. Tasks under `document/task/` handle document creation/removal. - -### Notification Pattern - -Notifications use Spring's application event system. An event is published after a CCD callback completes, and a handler in `notification/handlers/` picks it up: - -```java -// Event record (in service/event/) -public record ApplicationSubmitNotificationEvent(CaseDetails caseData) {} - -// Handler -@Component -public class ApplicationSubmittedNotificationEventHandler { - @EventListener - public void sendNotification(ApplicationSubmitNotificationEvent event) { - sendNotificationService.sendNotifications(event.caseData()); - } -} -``` - -### State Machine - -States are defined as a `@CCD`-annotated enum (`adoptioncase/model/State.java`). Each state carries an access class controlling which roles can read/write it: - -| State | Description | -|---|---| -| `Draft` | Citizen is completing the form | -| `AwaitingPayment` | Application submitted, payment pending | -| `Submitted` | Awaiting Local Authority input | -| `LaSubmitted` | Local Authority has submitted | - -State permissions are granted in `Adoption.configure()`. - -### Source Sets - -| Source set | Task | Purpose | -|---|---|---| -| `src/main` | — | Production code | -| `src/test` | `./gradlew test` | Unit tests | -| `src/integrationTest` | `./gradlew integration` | Integration tests | -| `src/contractTest` | `./gradlew contract` | Pact consumer tests | -| `src/functionalTest` | `./gradlew functional` | Functional/E2E tests | -| `src/cftlib` | `bootWithCcd` | Local CCD stack demo events | - -### Test Conventions - -- JUnit 5 + Mockito (`@ExtendWith(MockitoExtension.class)`) for unit tests. -- `uk.gov.hmcts.reform.adoption.testutil.TestDataHelper` — factory methods for `CaseData`, `Applicant`, `ListValue`, etc. -- `uk.gov.hmcts.reform.adoption.testutil.TestConstants` — shared constants (emails, names, IDs). -- `BaseTest` — abstract base for tests that need the Spring context (loads `Adoption.class` + `application-contract.properties`). -- `integrationTest` hits real AAT endpoints; run locally only when connected to F5 VPN. - -### Feature Flags (LaunchDarkly) - -```java -// With a known user context -launchDarklyClient.isFeatureEnabled("my-flag", ldUser); - -// Using the default ADOPTION_COS_USER -launchDarklyClient.isFeatureEnabled("my-flag"); -``` - -The `LaunchDirectlyIntValidationController` exposes a health-check endpoint for flag connectivity. - -### Contract Tests (Pact) - -- Consumer: `adoption_cos_api` -- Providers tested: `idamApi_oidc`, `case-document-am-api` -- ⚠️ `build.gradle` has `pacticipant = 'sscs_tribunalsCaseApi'` — this is a copy-paste error and needs correcting before publishing pacts to the broker. -- Run tests: `./gradlew contract`; publish: `./gradlew runAndPublishConsumerPactTests` (requires `PACT_BROKER_FULL_URL`). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 3f6acc59d..000000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,1088 +0,0 @@ -# Adoption COS API - Architecture & Patterns Analysis - -## Overview -The adoption-cos-api is a Case Management System API built with Spring Boot and heavily integrated with the CCD (Common Case Data) SDK. It follows a **task-based reactive pattern** combined with Spring event publishing for notification handling. - ---- - -## 1. CCD Callback Pattern - -### Structure & Annotations -All CCD events implement `CCDConfig` and are Spring `@Component`s. - -**Key CCD Callback Types:** -- `aboutToSubmitCallback()` - Validation & data transformation before submission -- `submittedCallback()` - Post-submission async operations (event publishing) - -### Example: CitizenCreateApplication.java -```java -@Component -@Slf4j -public class CitizenCreateApplication implements CCDConfig { - public static final String CITIZEN_CREATE = "citizen-create-application"; - - @Override - public void configure(final ConfigBuilder configBuilder) { - configBuilder - .event(CITIZEN_CREATE) - .initialState(Draft) - .name("Create adoption draft case") - .description("Apply for adoption") - .ttlIncrement(90) - .aboutToSubmitCallback(this::aboutToSubmit) // Pre-submission hook - .grant(CREATE_READ_UPDATE, CITIZEN) - .retries(120, 120); - } - - public AboutToStartOrSubmitResponse aboutToSubmit( - final CaseDetails details, - final CaseDetails beforeDetails) { - log.info("Citizen create adoption application about to submit callback invoked"); - CaseData data = details.getData(); - // Transform data, set defaults, format case reference - data.setHyphenatedCaseRef(String.format("%4s-%4s-%4s-%4s", ...)); - return AboutToStartOrSubmitResponse.builder() - .data(data) - .build(); - } -} -``` - -### Event Registration Pattern -Events are wired via `ConfigBuilder`: -- `.event(eventId)` - Registers event ID (e.g., "citizen-create-application") -- `.forStates(State...)` - Applies to specific states or all states -- `.forState(State)` - Single state variant -- `.grant(Permissions, UserRole...)` - Access control -- `.retries(120, 120)` - Retry configuration - -**Example with submitted callback:** -```java -// CitizenAddPayment.java -configBuilder - .event(CITIZEN_ADD_PAYMENT) - .forState(AwaitingPayment) - .name("Payment made") - .aboutToSubmitCallback(this::aboutToSubmit) - .submittedCallback(this::submitted); - -public SubmittedCallbackResponse submitted(CaseDetails details, - CaseDetails beforeDetails) { - if (EnumSet.of(Submitted).contains(details.getState())) { - eventPublisher.publishEvent( - ApplicationSubmitNotificationEvent.builder() - .caseData(details) - .build()); - } - return SubmittedCallbackResponse.builder().build(); -} -``` - ---- - -## 2. Task Pattern - -### Core Abstraction: CaseTask Interface -All tasks implement a simple functional interface: - -```java -public interface CaseTask extends Function, CaseDetails> { -} -``` - -Tasks are pure functions that transform case details. - -### Task Composition: CaseTaskRunner -Tasks are composed using a **function pipeline** pattern: - -```java -public final class CaseTaskRunner { - private final Function, CaseDetails> caseTask; - - @SafeVarargs - public static CaseTaskRunner caseTasks( - final Function, CaseDetails>... tasks) { - return new CaseTaskRunner(of(tasks).reduce(identity(), Function::andThen)); - } - - public CaseDetails run(final CaseDetails caseDetails) { - return caseTask.apply(caseDetails); - } -} -``` - -### Task Registration in Services -Tasks are registered and composed in Service classes (dependency injection): - -```java -@Service -public class SubmissionService { - @Autowired - private SetStateAfterSubmission setStateAfterSubmission; - - @Autowired - private SetDateSubmitted setDateSubmitted; - - @Autowired - private GenerateApplicationSummaryDocument generateApplicationSummaryDocument; - - public CaseDetails submitApplication( - final CaseDetails caseDetails) { - return CaseTaskRunner.caseTasks( - setStateAfterSubmission, - setDateSubmitted, - generateApplicationSummaryDocument - ).run(caseDetails); - } -} -``` - -### Task Examples - -**SetStateAfterSubmission (Conditional State Logic):** -```java -@Component -@Slf4j -public class SetStateAfterSubmission implements CaseTask { - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - final CaseData caseData = caseDetails.getData(); - final Application application = caseData.getApplication(); - - if (!application.hasBeenPaidFor()) { - caseDetails.setState(AwaitingPayment); - } else { - caseDetails.setState(Submitted); - } - caseDetails.getData().setStatus(Submitted); - log.info("State set to {}, CaseID {}", caseDetails.getState(), caseDetails.getId()); - return caseDetails; - } -} -``` - -**GenerateApplicationSummaryDocument (Async Document Generation):** -```java -@Component -@Slf4j -public class GenerateApplicationSummaryDocument implements CaseTask { - @Autowired - private CaseDataDocumentService caseDataDocumentService; - - @Override - public CaseDetails apply(CaseDetails caseDetails) { - final CaseData caseData = caseDetails.getData(); - final Long caseId = caseDetails.getId(); - - if (EnumSet.of(Submitted).contains(caseDetails.getState())) { - log.info("Generating summary document for caseId: {}", caseId); - Map templateContent = objectMapper.convertValue(caseData, Map.class); - - // Generate English or Welsh document based on language preference - final CompletableFuture appSummary = caseData.getApplicant1() - .getLanguagePreference().equals(LanguagePreference.ENGLISH) - ? generateDocument(caseData, APPLICATION_SUMMARY_EN, templateContent, ...) - : generateDocument(caseData, APPLICATION_SUMMARY_CY, templateContent, ...); - - CompletableFuture.allOf(appSummary).join(); - } - return caseDetails; - } -} -``` - -### Locations -- **adoptioncase/task/** - Core case task implementations -- **service/task/** - Service-level tasks (EventService, ScheduledTaskRunner) -- **document/task/** - Document generation tasks -- **common/service/task/** - Reusable tasks (SetState, SetDate, GenerateDocuments, SendNotifications) - ---- - -## 3. Event Handlers - -### Event Publishing Architecture -Uses Spring's **ApplicationEventPublisher**: - -```java -@Service -@Slf4j -@RequiredArgsConstructor -public class EventService { - private final ApplicationEventPublisher applicationEventPublisher; - - public void publishEvent(Object event) { - log.info("Publishing event {}", event.getClass().getSimpleName()); - applicationEventPublisher.publishEvent(event); - } -} -``` - -### Custom Event Records -Events are defined as immutable records (Java 16+): - -```java -// service/event/ApplicationSubmitNotificationEvent.java -@Builder -public record ApplicationSubmitNotificationEvent(CaseDetails caseData) { } - -// service/event/LocalAuthorityApplicationSubmitNotificationEvent.java -@Builder -public record LocalAuthorityApplicationSubmitNotificationEvent(CaseDetails caseData) { } -``` - -### Event Listeners -Handlers use `@EventListener` annotation (Spring event async processing): - -```java -@Slf4j -@Component -public class ApplicationSubmittedNotificationEventHandler { - private final SendNotificationService sendNotificationService; - - @EventListener - public void sendNotificationPostApplicationSubmission( - ApplicationSubmitNotificationEvent applicationSubmitNotificationEvent) { - Long caseId = applicationSubmitNotificationEvent.caseData().getId(); - log.info("ApplicationSubmittedNotificationEventHandler triggered for CaseID: {}", caseId); - sendNotificationService.sendNotifications( - applicationSubmitNotificationEvent.caseData()); - } -} -``` - -### CCD Event Configuration -Events are registered in `adoptioncase/event/` directory (implements CCDConfig): - -**Example: CitizenSubmitApplication** -```java -@Component -@Slf4j -public class CitizenSubmitApplication implements CCDConfig { - public static final String CITIZEN_SUBMIT = "citizen-submit-application"; - - @Override - public void configure(final ConfigBuilder configBuilder) { - configBuilder - .event(CITIZEN_SUBMIT) - .forStates(Draft, AwaitingPayment) - .name("Applicant Statement of Truth") - .aboutToSubmitCallback(this::aboutToSubmit); - } - - public AboutToStartOrSubmitResponse aboutToSubmit( - CaseDetails details, - CaseDetails beforeDetails) { - // Validation - final List validationErrors = validateReadyForPayment(data); - if (!validationErrors.isEmpty()) { - return AboutToStartOrSubmitResponse.builder() - .data(data) - .errors(validationErrors) - .state(state) - .build(); - } - // State transition - state = AwaitingPayment; - return AboutToStartOrSubmitResponse.builder() - .data(data) - .state(state) - .build(); - } -} -``` - -**Example: LocalAuthoritySubmitApplication with Event Publishing** -```java -@Component -@Slf4j -public class LocalAuthoritySubmitApplication implements CCDConfig { - @Autowired private SubmissionService submissionService; - @Autowired private EventService eventPublisher; - - @Override - public void configure(ConfigBuilder configBuilder) { - configBuilder - .event(LOCAL_AUTHORITY_SUBMIT) - .forStates(Submitted) - .aboutToSubmitCallback(this::aboutToSubmit) - .submittedCallback(this::submitted); - } - - public AboutToStartOrSubmitResponse aboutToSubmit( - final CaseDetails details, - final CaseDetails beforeDetails) { - final Long caseId = details.getId(); - log.info("Local Authority Submit about to submit callback invoked CaseID: {}", caseId); - final CaseDetails updatedCaseDetails = - submissionService.laSubmitApplication(details); - return AboutToStartOrSubmitResponse.builder() - .data(updatedCaseDetails.getData()) - .state(LaSubmitted) - .build(); - } - - public SubmittedCallbackResponse submitted(CaseDetails details, - CaseDetails beforeDetails) { - log.info("Local Authority Submit submitted callback invoked CaseID: {}", details.getId()); - eventPublisher.publishEvent( - LocalAuthorityApplicationSubmitNotificationEvent.builder() - .caseData(details) - .build()); - return SubmittedCallbackResponse.builder().build(); - } -} -``` - ---- - -## 4. Notification Pattern - -### Architecture: Notification Components -1. **Notification Classes** (`@Component`) - Template rendering & dispatch logic -2. **NotificationDispatcher** (`@Service`) - Routes notifications to recipients -3. **SendgridService** (`@Service`) - Email provider integration -4. **Event Handlers** (`@EventListener`) - Async notification triggers - -### Notification Template Pattern -```java -@Component -@Slf4j -public class ApplicationSubmittedNotification extends ApplicantNotification { - @Autowired private IdamService idamService; - @Autowired private SendgridService sendgridService; - @Autowired private CaseDocumentClient caseDocumentClient; - - public void sendToApplicants(CaseData caseData, Long caseId) - throws NotificationClientException, IOException { - - Applicant applicant1 = caseData.getApplicant1(); - Map templateVars = new HashMap<>(); - templateVars.put(APPLICANT_1_FULL_NAME, applicant1.getFirstName() + " " + applicant1.getLastName()); - templateVars.put(APPLICATION_REFERENCE, caseData.getHyphenatedCaseRef()); - templateVars.put(SUBMISSION_RESPONSE_DATE, - caseData.getApplication().getDateOfSubmissionResponse().format(DATE_TIME_FORMATTER)); - - sendgridService.sendEmail( - applicant1.getEmailAddress(), - APPLICANT_APPLICATION_SUBMITTED.template(), - templateVars - ); - } - - public void sendToLocalCourt(CaseData caseData, Long caseId) - throws NotificationClientException, IOException { - // Court notification logic - } - - public void sendToLocalAuthorityPostApplicantSubmission(CaseData caseData, Long caseId) - throws NotificationClientException, IOException { - // LA notification logic - } -} -``` - -### Notification Dispatcher (Router) -```java -@Service -@Slf4j -public class NotificationDispatcher { - public void send(final ApplicantNotification applicantNotification, - final CaseData caseData, final Long caseId) - throws NotificationClientException, IOException { - if (!caseData.getApplicant1().getEmailAddress().isEmpty()) { - applicantNotification.sendToApplicants(caseData, caseId); - try { - applicantNotification.sendToLocalCourt(caseData, caseId); - applicantNotification.sendToLocalAuthorityPostApplicantSubmission(caseData, caseId); - } catch (Exception e) { - log.error("Exception occurred in send method: {}", e); - } - } - } -} -``` - -### Event-Driven Notification Trigger -```java -@Slf4j -@Component -public class ApplicationSubmittedNotificationEventHandler { - private final SendNotificationService sendNotificationService; - - @EventListener - public void sendNotificationPostApplicationSubmission( - ApplicationSubmitNotificationEvent applicationSubmitNotificationEvent) { - Long caseId = applicationSubmitNotificationEvent.caseData().getId(); - log.info("Triggered for CaseID: {}", caseId); - sendNotificationService.sendNotifications( - applicationSubmitNotificationEvent.caseData()); - } -} -``` - -### Sendgrid Integration -```java -@Service -@Slf4j -public class SendgridService { - @Autowired private SendGridClient sendGridClient; - - public void sendEmail(String email, String templateId, Map personalisation) - throws NotificationClientException, IOException { - SendGridRequest sendGridRequest = SendGridRequest.builder() - .to(email) - .templateId(templateId) - .personalisation(personalisation) - .build(); - - sendGridClient.sendEmail(sendGridRequest); - } -} -``` - -### Notification Task (Used in Submission Pipeline) -```java -@Component -@Slf4j -public class SendSubmissionNotifications implements CaseTask { - @Autowired - private ApplicationSubmittedNotification applicationSubmittedNotification; - - @Autowired - private NotificationDispatcher notificationDispatcher; - - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - final CaseData caseData = caseDetails.getData(); - final Long caseId = caseDetails.getId(); - final State state = caseDetails.getState(); - - if (EnumSet.of(Submitted).contains(state)) { - log.info("Sending application submitted notifications for case : {}", caseId); - try { - notificationDispatcher.send(applicationSubmittedNotification, caseData, caseId); - } catch (NotificationClientException | IOException e) { - log.error("Couldn't send notifications"); - } - } - return caseDetails; - } -} -``` - -### Notification Locations -- **notification/** - Main notification classes -- **notification/handlers/** - Event listeners for async triggers -- **common/notification/** - Shared notification base classes -- **common/service/SendNotificationService** - Orchestration service - ---- - -## 5. CCD Model/Config Generation - -### CCD Model with @CCD Annotations -CaseData uses CCD SDK annotations to define field metadata: - -```java -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder(toBuilder = true) -@JsonIgnoreProperties(ignoreUnknown = true) -public class CaseData { - - @CCD( - label = "Applying with", - access = {DefaultAccess.class}, - typeOverride = FixedRadioList, - typeParameterOverride = "ApplyingWith" - ) - private ApplyingWith applyingWith; - - @CCD( - label = "Are the applicants represented by a solicitor?", - access = {DefaultAccess.class} - ) - private YesOrNo isApplicantRepresentedBySolicitor; - - @CCD( - label = "Date child moved in", - access = {DefaultAccess.class} - ) - @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate dateChildMovedIn; - - @CCD( - label = "Applying with someone else reason", - typeOverride = TextArea, - access = {DefaultAccess.class} - ) - private String otherApplicantRelation; - - // Complex nested types with collections - @CCD(label = "Payment Method", - typeOverride = Collection, - typeParameterOverride = "Payment", - access = { CollectionAccess.class }) - private List> applicationPayments; -} -``` - -### Access Control -Access is controlled via specialized access classes: -- `DefaultAccess` - All authenticated users -- `CaseworkerAccess` - Only case workers -- `SystemUpdateAccess` - System update user only -- `CollectionAccess` - Collection-specific access -- `TtlAccess` - Time-to-live field access - -### Tabs Configuration -```java -@Component -public class CaseTypeTab implements CCDConfig { - - @Override - public void configure(final ConfigBuilder configBuilder) { - buildCfvTab(configBuilder); - buildSummaryTab(configBuilder); - buildApplicantsTab(configBuilder); - buildDocumentsTab(configBuilder); - } - - private void buildCfvTab(ConfigBuilder configBuilder) { - configBuilder.tab("cfv", "Case File View") - .forRoles(CASE_WORKER) - .field(CaseData::getCaseFileView, null, "#ARGUMENT(CaseFileView)"); - } - - public void buildApplicantsTab(ConfigBuilder configBuilder) { - configBuilder.tab("applicationDetails", "Applicants") - .showCondition(TabShowCondition.showForState(State.Submitted, State.LaSubmitted)) - .label("LabelApplicant-Heading", - "applyingWith=\"alone\"", - "# Applicant") - .label("LabelApplicants-Heading", - "applyingWith!=\"alone\"", - "# Applicants") - .field("applyingWith", "applyingWith=\"applyingWith\"") - .field("applicant1FirstName") - .field("applicant1LastName") - .field("applicant1DateOfBirth"); - } -} -``` - -### Workbasket Configuration -```java -@Component -public class WorkBasketInputFields implements CCDConfig { - - @Override - public void configure(final ConfigBuilder configBuilder) { - configBuilder - .workBasketInputFields() - .field("hyphenatedCaseRef", "Case reference number") - .field("childrenFirstName", "Child's first name") - .field("childrenLastName", "Child's last name") - .field("dateSubmitted", "Date submitted"); - } -} -``` - -### CCD Configuration Locations -- **adoptioncase/model/** - Core CaseData and domain models with @CCD annotations -- **adoptioncase/model/access/** - Access control classes -- **adoptioncase/tab/** - Tab configurations -- **adoptioncase/workbasket/** - Workbasket/search input fields -- **adoptioncase/event/** - Event configurations (implements CCDConfig) - ---- - -## 6. Test Conventions - -### Base Test Class -```java -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = Adoption.class) -@TestPropertySource(locations = "/application-contract.properties") -public abstract class BaseTest { - @MockitoBean - protected AuthTokenGenerator authTokenGenerator; -} -``` - -### Unit Tests -Use **Mockito** with `@ExtendWith(MockitoExtension.class)`: - -```java -public class ApplicationSubmittedNotificationTest { - - @Mock - private SendgridService sendgridService; - - @Mock - private CaseDocumentClient caseDocumentClient; - - @InjectMocks - private ApplicationSubmittedNotification notification; - - @Test - void testSendToApplicants() throws NotificationClientException, IOException { - // Setup test data - CaseData caseData = caseData(); - Long caseId = 1234567890123456L; - - // Stub mocks - when(sendgridService.sendEmail(...)).doNothing(); - - // Execute - notification.sendToApplicants(caseData, caseId); - - // Verify - verify(sendgridService, times(1)).sendEmail( - eq("test@example.com"), - eq(APPLICANT_APPLICATION_SUBMITTED.template()), - any(Map.class) - ); - } -} -``` - -### Functional Tests -Integration tests extending `FunctionalTest`: - -```java -@Slf4j -@TestPropertySource("classpath:application.yaml") -public abstract class FunctionalTest { - @Autowired protected IdamTokenGenerator idamTokenGenerator; - @Autowired protected CoreCaseDataApi coreCaseDataApi; - @Autowired protected ServiceAuthenticationGenerator serviceAuthenticationGenerator; - - protected CaseDetails createCaseInCcd() { - String userToken = idamTokenGenerator.generateIdamTokenForSystem(); - String s2sTokenForCosApi = serviceAuthenticationGenerator.generate("adoption_cos_api"); - // Create case via CCD API - } - - protected Response triggerCallback(Map caseData, String eventId, String url) - throws IOException { - CallbackRequest request = CallbackRequest.builder() - .eventId(eventId) - .caseDetailsBefore(...) - .caseDetails(...) - .build(); - return triggerCallback(request, url); - } -} -``` - -### Test Utilities -```java -public class TestDataHelper { - public static final LocalDate LOCAL_DATE = LocalDate.of(2021, 4, 28); - - public static ListValue documentWithType(final DocumentType documentType) { - Document ccdDocument = new Document( - "http://localhost:8080/" + UUID.randomUUID().toString(), - "test-draft-adoption-application.pdf", - "..." - ); - AdoptionDocument adoptionDocument = AdoptionDocument.builder() - .documentLink(ccdDocument) - .documentType(documentType) - .build(); - return ListValue.builder() - .value(adoptionDocument) - .build(); - } - - public static CaseData caseData() { - // Factory method for test case data - } - - public static Map getMainTemplateVars(CaseData caseData) { - // Extract template variables for notifications - } -} -``` - -### Test Structure -- **Unit tests** use `@ExtendWith(MockitoExtension.class)` with mocked dependencies -- **Contract/Integration tests** extend `BaseTest` with Spring context -- **Functional tests** extend `FunctionalTest` with CCD API integration -- **Test data** centralized in `testutil/TestDataHelper.java` - ---- - -## 7. LaunchDarkly Feature Flagging - -### LaunchDarkly Client -```java -@Configuration -@Service -@Slf4j -public class LaunchDarklyClient { - public static final LDUser ADOPTION_COS_USER = - new LDUser.Builder("adoption-cos-api") - .anonymous(true) - .build(); - - private final LDClientInterface internalClient; - - @Autowired - public LaunchDarklyClient( - LaunchDarkClientFactory ldClientFactory, - @Value("${launchdarkly.sdk-key:}") String sdkKey, - @Value("${launchdarkly.offline-mode:false}") Boolean offlineMode) { - this.internalClient = ldClientFactory.create(sdkKey, offlineMode); - Runtime.getRuntime().addShutdownHook(new Thread(this::close)); - } - - public boolean isFeatureEnabled(String feature) { - return internalClient.boolVariation(feature, ADOPTION_COS_USER, false); - } - - public boolean isFeatureEnabled(String feature, LDUser user) { - return internalClient.boolVariation(feature, user, false); - } - - private void close() { - try { - internalClient.close(); - } catch (IOException e) { - log.error(e.getMessage()); - } - } -} -``` - -### Factory Pattern -```java -@Service -public class LaunchDarkClientFactory { - public LDClientInterface create(String sdkKey, Boolean offlineMode) { - LDConfig config = new LDConfig.Builder() - .offline(offlineMode) - .build(); - return new LDClient(sdkKey, config); - } -} -``` - -### Configuration -- **config.launchdarkly** - Feature flag client factory -- Properties: `launchdarkly.sdk-key` and `launchdarkly.offline-mode` - ---- - -## 8. Key Integration Points - -### IDAM (Identity & Access Management) -```java -@Service -public class IdamService { - @Value("${idam.systemupdate.username}") - private String systemUpdateUserName; - - @Autowired - private IdamClient idamClient; - - public User retrieveUser(String authorisation) { - final String bearerToken = getBearerToken(authorisation); - final UserDetails userDetails = idamClient.getUserDetails(bearerToken); - return new User(bearerToken, userDetails); - } - - public User retrieveSystemUpdateUserDetails() { - return retrieveUser(getIdamOauth2Token(systemUpdateUserName, systemUpdatePassword)); - } -} -``` - -### CCD (Case Management Data) -- Uses `CoreCaseDataApi` client for case operations -- CCDConfig components auto-register with CCD system -- Case callbacks (aboutToSubmit, submitted) handled via CCD SDK - -### Document Management (DocAssembly/Docmosis) -```java -@Service -@Slf4j -public class DocAssemblyService { - @Autowired private DocAssemblyClient docAssemblyClient; - @Autowired private DocmosisTemplateProvider docmosisTemplateProvider; - - public DocumentInfo renderDocument( - final Map templateContent, - final Long caseId, - final String authorisation, - final String templateId, - final LanguagePreference languagePreference, - final String filename) { - - final String templateName = docmosisTemplateProvider - .templateNameFor(templateId, languagePreference); - final DocAssemblyRequest docAssemblyRequest = DocAssemblyRequest.builder() - .templateId(templateName) - .outputType(PDF) - .caseTypeId(CASE_TYPE) - .data(templateContent) - .build(); - - return docAssemblyClient.generateDocument( - docAssemblyRequest, caseId, authorisation); - } -} -``` - -### Payment Service -- **Payment Model**: Track payment status (SUCCESS, IN_PROGRESS, FAILED) -- **Application integration**: Check `application.hasBeenPaidFor()` before proceeding -- **State transitions**: AwaitingPayment → Submitted upon successful payment - -```java -public class Application { - @JsonIgnore - public PaymentStatus getLastPaymentStatus() { ... } - - @JsonIgnore - public boolean hasBeenPaidFor() { - return null != applicationFeeOrderSummary - && Integer.parseInt(applicationFeeOrderSummary.getPaymentTotal()) - == getPaymentTotal(); - } -} -``` - -### Notification Service (SendGrid) -```java -@Service -@Slf4j -public class SendgridService { - public void sendEmail(String email, String templateId, - Map personalisation) - throws NotificationClientException, IOException { - // Send via SendGrid - } -} -``` - -### CCD Search Service -```java -@Service -@Slf4j -public class CcdSearchService { - @Autowired - private CoreCaseDataApi coreCaseDataApi; - - public List searchCases( - String authorisation, String s2sToken, - String query, String caseType) { - // Elasticsearch query execution - } -} -``` - ---- - -## 9. SystemUpdate Package - -### Purpose -Handles system-initiated operations outside normal user workflows (e.g., automated tasks, scheduled jobs). - -### CaseDetailsConverter -Converts between CCD SDK model and Reform CCD model: - -```java -@Component -public class CaseDetailsConverter { - @Autowired - private ObjectMapper objectMapper; - - public uk.gov.hmcts.ccd.sdk.api.CaseDetails - convertToCaseDetailsFromReformModel(final CaseDetails caseDetails) { - return objectMapper.convertValue(caseDetails, - new TypeReference>() { - }); - } -} -``` - -### Usage -- Scheduled tasks that need to read/update cases -- System-initiated workflows (e.g., bulk operations) -- Conversion between external API models and internal SDK models - ---- - -## 10. SetState Tasks (common/service/task/) - -### SetStateAfterSubmission -Applies conditional logic to determine next state based on payment: - -```java -@Component -@Slf4j -public class SetStateAfterSubmission implements CaseTask { - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - final CaseData caseData = caseDetails.getData(); - final Application application = caseData.getApplication(); - - // Payment-driven state transition - if (!application.hasBeenPaidFor()) { - caseDetails.setState(AwaitingPayment); - } else { - caseDetails.setState(Submitted); - } - - caseDetails.getData().setStatus(Submitted); - log.info("State set to {}, CaseID {}", - caseDetails.getState(), caseDetails.getId()); - return caseDetails; - } -} -``` - -### SetStateAfterLaSubmission -Local Authority submission state transition: - -```java -@Component -@Slf4j -public class SetStateAfterLaSubmission implements CaseTask { - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - log.info("Setting state to LaSubmitted for CaseID: {}", caseDetails.getId()); - caseDetails.setState(LaSubmitted); - caseDetails.getData().setStatus(LaSubmitted); - return caseDetails; - } -} -``` - -### SetDateSubmitted -Captures submission timestamp: - -```java -@Component -@Slf4j -public class SetDateSubmitted implements CaseTask { - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - LocalDate now = LocalDate.now(clock); - caseDetails.getData().getApplication().setDateSubmitted(now); - log.info("Date submitted set for CaseID: {}", caseDetails.getId()); - return caseDetails; - } -} -``` - -### Composition Example -```java -public CaseDetails submitApplication( - final CaseDetails caseDetails) { - return CaseTaskRunner.caseTasks( - setStateAfterSubmission, // Step 1: Determine state - setDateSubmitted, // Step 2: Set timestamp - generateApplicationSummaryDocument // Step 3: Generate PDF - ).run(caseDetails); -} -``` - ---- - -## Key Architectural Principles - -### 1. **Functional Composition** -- Tasks implemented as pure functions: `Function` -- Composed via `CaseTaskRunner` using `Function.andThen()` -- No side effects within tasks themselves - -### 2. **Event-Driven Async** -- Submit callbacks trigger Spring events (`@EventListener`) -- Handlers execute asynchronously for notifications, document generation -- Decouples synchronous CCD operations from async side effects - -### 3. **Dependency Injection** -- All Spring components autowired -- Task dependencies injected into Service classes -- Enables testing via mocking and composition - -### 4. **CCD SDK Integration** -- CCDConfig implementations auto-discovered by Spring -- Annotations drive form generation and access control -- Callbacks provide extension points for business logic - -### 5. **Explicit State Management** -- Clear State enum (Draft, Submitted, LaSubmitted, AwaitingPayment, etc.) -- State transitions explicit in tasks -- All state changes logged - -### 6. **Template-Driven Document Generation** -- Docmosis for PDF rendering -- Language-aware template selection (English/Welsh) -- Async document generation via CompletableFuture - -### 7. **Multi-Channel Notifications** -- SendGrid for email -- Multiple recipient types: Applicants, Courts, Local Authorities -- Template variables centralized in CommonContent - ---- - -## Directory Structure Summary - -``` -src/main/java/uk/gov/hmcts/reform/adoption/ -├── adoptioncase/ -│ ├── event/ # CCD event configurations -│ ├── caseworker/event/ # Caseworker-specific events -│ ├── model/ # Domain models with @CCD annotations -│ ├── tab/ # Tab configurations -│ ├── workbasket/ # Search/workbasket fields -│ ├── task/ # Case-level tasks -│ ├── schedule/ # Scheduled tasks -│ └── service/ # Domain services (CcdSearchService, etc.) -├── service/ -│ ├── event/ # Event records (ApplicationSubmitNotificationEvent) -│ ├── task/ # EventService, ScheduledTaskRunner -├── common/ -│ ├── service/ -│ │ ├── task/ # Reusable tasks (SetState, SetDate, Generate, Send) -│ │ └── SubmissionService # Task composition -│ ├── config/ # EmailTemplatesConfig, DocmosisTemplatesConfig -│ └── notification/ # Base notification classes -├── notification/ -│ ├── handlers/ # @EventListener event handlers -│ ├── NotificationDispatcher -│ ├── SendgridService -│ └── [Notification classes] -├── document/ -│ ├── task/ # Document generation tasks -│ ├── DocAssemblyService -│ ├── CaseDataDocumentService -│ └── model/ -├── config/ -│ └── launchdarkly/ # LaunchDarkly client factory -├── idam/ # IDAM user service -├── systemupdate/ # System conversion utilities -└── controllers/ # REST endpoints (minimal) -``` - ---- - -## Key Takeaways for Copilot Instructions - -1. **Tasks are composable functions** that transform case details - use CaseTaskRunner for pipelines -2. **Events decouple submission from notifications** - publish in submittedCallback, listen with @EventListener -3. **All CCD config extends CCDConfig** and uses ConfigBuilder fluent API -4. **State is explicit and enumerated** - SetState tasks manage transitions -5. **Notifications are template-driven** with multi-channel dispatch (ApplicantNotification, NotificationDispatcher) -6. **Document generation is async** using CompletableFuture within tasks -7. **LaunchDarkly client is injected** as a service for feature flags -8. **External integrations** (IDAM, CCD, DocAssembly, SendGrid) wrapped in dedicated services -9. **Tests use Mockito** with @ExtendWith(MockitoExtension.class) for unit tests -10. **Functional tests extend FunctionalTest** with CCD API integration helpers diff --git a/copilot-instructions.md b/copilot-instructions.md deleted file mode 100644 index 392d70d5a..000000000 --- a/copilot-instructions.md +++ /dev/null @@ -1,633 +0,0 @@ -# Adoption COS API - Copilot Development Instructions - -## Quick Reference - -This document provides AI Copilot with essential patterns and conventions for developing in adoption-cos-api. Refer to [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed explanations with code examples. - ---- - -## 1. Creating a New CCD Event - -**Pattern:** Implement `CCDConfig` as `@Component` - -### Template -```java -@Component -@Slf4j -public class MyNewEvent implements CCDConfig { - - public static final String EVENT_ID = "my-new-event"; - - @Autowired - private MyService myService; // Inject dependencies - - @Override - public void configure(final ConfigBuilder configBuilder) { - configBuilder - .event(EVENT_ID) - .forState(Draft) // or .forStates(Draft, AwaitingPayment) for multiple - .name("User-visible event name") - .description("Description shown in UI") - .ttlIncrement(90) // Optional: time-to-live - .grant(CREATE_READ_UPDATE, CITIZEN) - .grant(READ, CASE_WORKER) - .retries(120, 120) - .aboutToSubmitCallback(this::aboutToSubmit); // Optional - // .submittedCallback(this::submitted); // Optional - } - - public AboutToStartOrSubmitResponse aboutToSubmit( - final CaseDetails details, - final CaseDetails beforeDetails) { - - log.info("Event {} about to submit callback invoked for CaseID: {}", - EVENT_ID, details.getId()); - - CaseData data = details.getData(); - State state = details.getState(); - - // Validation - List errors = new ArrayList<>(); - if (data.getSomeField() == null) { - errors.add("Some field is required"); - } - - if (!errors.isEmpty()) { - return AboutToStartOrSubmitResponse.builder() - .data(data) - .state(state) - .errors(errors) - .build(); - } - - // Data transformation - data.setProcessedField(processField(data)); - - return AboutToStartOrSubmitResponse.builder() - .data(data) - .state(state) // Can change state here - .build(); - } - - // Optional: Called after case is submitted - public SubmittedCallbackResponse submitted( - CaseDetails details, - CaseDetails beforeDetails) { - - log.info("Submitted callback invoked for CaseID: {}", details.getId()); - // Async operations here (e.g., publish events) - return SubmittedCallbackResponse.builder().build(); - } -} -``` - -### Key Points -- Always use `@Component` + `@Slf4j` -- Event ID convention: lowercase with hyphens (e.g., "citizen-submit-application") -- aboutToSubmit: Validation + synchronous data transformation -- submitted: Async operations, event publishing (no response data returned) -- Use ConfigBuilder fluent API for configuration - ---- - -## 2. Creating a New Task - -**Pattern:** Implement `CaseTask` (a Function interface), annotate with `@Component` - -### Single Task -```java -@Component -@Slf4j -public class MyNewTask implements CaseTask { - - @Autowired - private SomeDependency someDependency; - - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - final CaseData data = caseDetails.getData(); - final Long caseId = caseDetails.getId(); - final State state = caseDetails.getState(); - - log.info("Executing MyNewTask for CaseID: {}, State: {}", caseId, state); - - // Perform transformation - data.setNewField(calculateValue(data)); - - return caseDetails; - } -} -``` - -### Composing Tasks (Pipelines) -```java -@Service -public class MySubmissionService { - - @Autowired private Task1 task1; - @Autowired private Task2 task2; - @Autowired private Task3 task3; - - public CaseDetails processCase( - final CaseDetails caseDetails) { - - return CaseTaskRunner.caseTasks( - task1, // Executed first - task2, // Executed second, receives output from task1 - task3 // Executed third, receives output from task2 - ).run(caseDetails); - } -} -``` - -### Typical Task Locations -- **SetState tasks**: common/service/task/ (manage state transitions) -- **Document generation**: common/service/task/ or document/task/ -- **Data validation/transformation**: common/service/task/ -- **Notifications**: common/service/task/SendSubmissionNotifications - ---- - -## 3. Publishing Events & Notification Handlers - -**Pattern:** Publish Spring events in `submittedCallback`, listen with `@EventListener` - -### Define Event Record -```java -// service/event/MyCustomEvent.java -@Builder -public record MyCustomEvent(CaseDetails caseData) { } -``` - -### Publish Event (in submittedCallback) -```java -@Autowired -private EventService eventPublisher; - -public SubmittedCallbackResponse submitted( - CaseDetails details, - CaseDetails beforeDetails) { - - log.info("Publishing MyCustomEvent for CaseID: {}", details.getId()); - eventPublisher.publishEvent( - MyCustomEvent.builder() - .caseData(details) - .build()); - - return SubmittedCallbackResponse.builder().build(); -} -``` - -### Listen to Event (Async Handler) -```java -// notification/handlers/MyEventHandler.java -@Slf4j -@Component -public class MyEventHandler { - - @Autowired - private MyService myService; - - @EventListener - public void handleMyCustomEvent(MyCustomEvent event) { - Long caseId = event.caseData().getId(); - log.info("MyEventHandler triggered for CaseID: {}", caseId); - - try { - myService.doSomethingAsync(event.caseData()); - } catch (Exception e) { - log.error("Error handling event", e); - } - } -} -``` - ---- - -## 4. Creating Notifications - -**Pattern:** Extend `ApplicantNotification`, use `NotificationDispatcher` and `SendgridService` - -### Notification Class -```java -@Component -@Slf4j -public class MyNotification extends ApplicantNotification { - - @Autowired - private SendgridService sendgridService; - - @Autowired - private IdamService idamService; - - @Autowired - private CaseDocumentClient caseDocumentClient; - - public void sendToApplicants(CaseData caseData, Long caseId) - throws NotificationClientException, IOException { - - Applicant applicant1 = caseData.getApplicant1(); - Map templateVars = new HashMap<>(); - - // Add template variables - templateVars.put("applicant_name", applicant1.getFirstName()); - templateVars.put("case_reference", caseData.getHyphenatedCaseRef()); - templateVars.put("submission_date", - caseData.getApplication().getDateSubmitted()); - - // Send email - sendgridService.sendEmail( - applicant1.getEmailAddress(), - EmailTemplateName.MY_TEMPLATE.template(), - templateVars - ); - } - - public void sendToLocalCourt(CaseData caseData, Long caseId) - throws NotificationClientException, IOException { - // Court notification logic - } -} -``` - -### Trigger Notification via Task -```java -@Component -@Slf4j -public class MyNotificationTask implements CaseTask { - - @Autowired - private MyNotification myNotification; - - @Autowired - private NotificationDispatcher dispatcher; - - @Override - public CaseDetails apply(final CaseDetails caseDetails) { - final CaseData data = caseDetails.getData(); - final Long caseId = caseDetails.getId(); - - log.info("Sending notifications for CaseID: {}", caseId); - - try { - dispatcher.send(myNotification, data, caseId); - } catch (NotificationClientException | IOException e) { - log.error("Failed to send notifications", e); - } - - return caseDetails; - } -} -``` - ---- - -## 5. Defining CCD Fields with Annotations - -**Pattern:** Use `@CCD` annotation with metadata on CaseData fields - -### Basic Field -```java -@CCD( - label = "First Name", - access = {DefaultAccess.class} -) -private String firstName; -``` - -### Field with Type Override -```java -@CCD( - label = "Description", - access = {DefaultAccess.class}, - typeOverride = TextArea -) -private String description; -``` - -### Radio List Field -```java -@CCD( - label = "Applying with", - access = {DefaultAccess.class}, - typeOverride = FixedRadioList, - typeParameterOverride = "ApplyingWith" -) -private ApplyingWith applyingWith; -``` - -### Collection Field -```java -@CCD( - label = "Documents", - typeOverride = Collection, - typeParameterOverride = "AdoptionDocument", - access = {CollectionAccess.class} -) -private List> documents; -``` - -### Date Field -```java -@CCD( - label = "Date Submitted", - access = {DefaultAccess.class} -) -@JsonFormat(pattern = "yyyy-MM-dd") -private LocalDate dateSubmitted; -``` - -### Access Control Options -- `DefaultAccess` - All authenticated users -- `CaseworkerAccess` - Case workers only -- `SystemUpdateAccess` - System user only -- `CollectionAccess` - Collection-specific access - ---- - -## 6. Building Tabs - -**Pattern:** Implement `CCDConfig` as `@Component` in `adoptioncase/tab/` - -```java -@Component -public class MyTabConfiguration implements CCDConfig { - - @Override - public void configure(final ConfigBuilder configBuilder) { - configBuilder.tab("myTabId", "My Tab Label") - .forRoles(CASE_WORKER, DISTRICT_JUDGE) - .showCondition(TabShowCondition.showForState(State.Submitted, State.LaSubmitted)) - .field("field1") - .field("field2") - .field("field3", "field2 != null"); // Conditional display - } -} -``` - -**Show Conditions:** -```java -.showCondition("applyingWith=\"alone\"") // Show if condition matches -.label("LabelKey", "condition", "Display Text") // Dynamic labels -``` - ---- - -## 7. Unit Testing - -**Pattern:** Use `@ExtendWith(MockitoExtension.class)` with `@Mock` and `@InjectMocks` - -```java -public class MyComponentTest { - - @Mock - private DependencyA dependencyA; - - @Mock - private DependencyB dependencyB; - - @InjectMocks - private MyComponent myComponent; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - @Test - void testMyComponentLogic() throws Exception { - // Setup - when(dependencyA.getValue()).thenReturn("expected"); - CaseData caseData = TestDataHelper.caseData(); - - // Execute - MyResult result = myComponent.process(caseData); - - // Assert - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo("processed"); - - // Verify mocks were called correctly - verify(dependencyA, times(1)).getValue(); - verify(dependencyB).doSomething(any()); - } - - @Test - void testErrorScenario() { - when(dependencyA.getValue()).thenThrow(new RuntimeException("Error")); - - assertThatThrownBy(() -> myComponent.process(null)) - .isInstanceOf(RuntimeException.class); - } -} -``` - ---- - -## 8. Integration Testing - -**Pattern:** Extend `FunctionalTest` for CCD API integration tests - -```java -@Slf4j -public class MyIntegrationTest extends FunctionalTest { - - @Test - void testCreateCaseAndTriggerEvent() throws IOException { - // Create case - CaseDetails caseDetails = createCaseInCcd(); - Long caseId = caseDetails.getId(); - - // Trigger callback - Map caseData = getCaseData(caseDetails); - Response response = triggerCallback( - caseData, - "my-event", - "/callback/my-event" - ); - - // Assert - assertThat(response.getStatusCode()).isEqualTo(200); - CaseData resultData = response.body().as(CaseData.class); - assertThat(resultData.getField()).isEqualTo("expected"); - } -} -``` - -### Test Data Helpers -```java -// Use TestDataHelper for test data -CaseData caseData = TestDataHelper.caseData(); -ListValue document = TestDataHelper.documentWithType(DocumentType.APPLICATION); -Map templateVars = TestDataHelper.getMainTemplateVars(caseData); -``` - ---- - -## 9. Using LaunchDarkly Features - -**Pattern:** Inject `LaunchDarklyClient` service to check feature flags - -```java -@Service -public class MyService { - - @Autowired - private LaunchDarklyClient launchDarklyClient; - - public void myMethod(CaseData caseData) { - if (launchDarklyClient.isFeatureEnabled("my-feature-flag")) { - // Execute new feature code - executeNewFeature(caseData); - } else { - // Fallback to existing code - executeExistingFeature(caseData); - } - } -} -``` - -**Configuration:** -```properties -launchdarkly.sdk-key=your-sdk-key -launchdarkly.offline-mode=false -``` - ---- - -## 10. Document Generation - -**Pattern:** Implement task that calls `CaseDataDocumentService.renderDocumentAndUpdateCaseData()` - -```java -@Component -@Slf4j -public class MyDocumentGenerationTask implements CaseTask { - - @Autowired - private CaseDataDocumentService caseDataDocumentService; - - @Autowired - private ObjectMapper objectMapper; - - @Override - public CaseDetails apply( - final CaseDetails caseDetails) { - - final CaseData caseData = caseDetails.getData(); - final Long caseId = caseDetails.getId(); - - log.info("Generating document for CaseID: {}", caseId); - - // Prepare template content - @SuppressWarnings("unchecked") - Map templateContent = - objectMapper.convertValue(caseData, Map.class); - - // Select document type based on language preference - LanguagePreference language = caseData.getApplicant1() - .getLanguagePreference(); - DocumentType documentType = language.equals(LanguagePreference.ENGLISH) - ? DocumentType.MY_DOCUMENT_EN - : DocumentType.MY_DOCUMENT_CY; - - // Generate document (async) - CompletableFuture generation = CompletableFuture.runAsync(() -> - caseDataDocumentService.renderDocumentAndUpdateCaseData( - caseData, - documentType, - templateContent, - caseId, - "MY_DOCUMENT_TYPE", - language, - formatDocumentName(caseId, "MyDocument", LocalDateTime.now()) - ) - ); - - // Wait for completion - CompletableFuture.allOf(generation).join(); - - return caseDetails; - } -} -``` - ---- - -## Common Workflows - -### Workflow: Case Submission (Citizen) -1. Event: `CitizenSubmitApplication` (aboutToSubmit callback) - - Validate case data - - Return errors if invalid - - Return updated state -2. Task Pipeline (SubmissionService.submitApplication): - - `SetStateAfterSubmission` - Determine next state - - `SetDateSubmitted` - Record submission date - - `GenerateApplicationSummaryDocument` - Create PDF -3. Event: `CitizenAddPayment` (submittedCallback) - - Publish `ApplicationSubmitNotificationEvent` -4. Handler: `ApplicationSubmittedNotificationEventHandler` - - Send emails to applicants, court, local authority - -### Workflow: State Transitions -Draft → AwaitingPayment → Submitted → LaSubmitted → [Case Processing] - -Use SetState tasks to manage transitions: -```java -CaseTaskRunner.caseTasks( - setStateAfterSubmission, // Determines state based on payment - setDateSubmitted, - generateDocuments -).run(caseDetails); -``` - -### Workflow: Multi-Language Support -1. Check `applicant.getLanguagePreference()` -2. Select document type: `DocumentType.MY_DOCUMENT_EN` or `MY_DOCUMENT_CY` -3. Template rendering handles Welsh/English content - ---- - -## Do's & Don'ts - -### ✅ DO -- Create tasks as `@Component` implementing `CaseTask` -- Use `CaseTaskRunner.caseTasks()` to compose task pipelines -- Publish events in `submittedCallback`, not `aboutToSubmit` -- Validate in `aboutToSubmit`, perform side effects in `submitted` -- Log with case ID: `log.info("Event {} for CaseID: {}", eventId, caseId)` -- Use dependency injection (`@Autowired`) -- Catch checked exceptions from notification/document services -- Use `@Builder` for records and POJOs - -### ❌ DON'T -- Modify case data in async handlers (use tasks instead) -- Publish events in `aboutToSubmit` (must return response) -- Execute long-running operations in callbacks (use tasks/handlers) -- Hardcode case references or state strings (use State enum) -- Mix business logic in event configuration -- Forget to add `@Slf4j` to components -- Use `new` for Spring-managed dependencies - ---- - -## Common Patterns Summary - -| Pattern | Location | Key Interface | Key Method | -|---------|----------|---------------|------------| -| CCD Event | adoptioncase/event/ | CCDConfig | configure() | -| Task | */task/ | CaseTask | apply() | -| Service | */service/ | (any) | Methods using CaseTaskRunner | -| Event Handler | notification/handlers/ | (any) | @EventListener methods | -| Notification | notification/ | ApplicantNotification | send*() methods | -| Tab Config | adoptioncase/tab/ | CCDConfig | configure() | -| Scheduled Task | adoptioncase/schedule/ | Runnable | run() | - ---- - -## References -- See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed examples with full code listings -- Review existing events in `adoptioncase/event/` and `adoptioncase/caseworker/event/` -- Review existing tasks in `common/service/task/` -- Check test examples in `src/test/java/` for testing patterns From f19c05a8d47267b8fb096741ac0ce0a041720505 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:41:20 +0000 Subject: [PATCH 07/23] Reverted generate-ccd-definition.sh back to hmctspublic.azurecr.io. --- .gitignore | 4 ++-- bin/generate-ccd-definition.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c28baaf12..b0f959994 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ bin/main/application.yaml ### Generated CCD Json Definition ### ccd-definitions/definitions/development -# Copilot/architecture files +### Miscellaneous ### ARCHITECTURE.md copilot-instructions.md -.github/copilot-instructions.md +.github/copilot-instructions.md \ No newline at end of file diff --git a/bin/generate-ccd-definition.sh b/bin/generate-ccd-definition.sh index 5e9cb8bdf..9bee9d194 100755 --- a/bin/generate-ccd-definition.sh +++ b/bin/generate-ccd-definition.sh @@ -21,5 +21,5 @@ echo "Additional params: ${additionalParameters}" docker run --pull always --rm --name json2xlsx \ -v ${definition_input_dir}:/tmp/ccd-definition \ -v ${definition_output_file}:/tmp/ccd-definition.xlsx \ - hmctssbox.azurecr.io/ccd/definition-processor:${definition_processor_version} \ + hmctspublic.azurecr.io/ccd/definition-processor:${definition_processor_version} \ json2xlsx -D /tmp/ccd-definition -o /tmp/ccd-definition.xlsx ${additionalParameters} From 90352e8c58cf43fa405b8f2a68e5c28632b82c19 Mon Sep 17 00:00:00 2001 From: hmcts-jenkins-a-to-c <62422075+hmcts-jenkins-a-to-c[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:43:51 +0000 Subject: [PATCH 08/23] Bumping chart version/ fixing aliases --- charts/adoption-cos-api/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/adoption-cos-api/Chart.yaml b/charts/adoption-cos-api/Chart.yaml index d55f264e8..31669f0e1 100644 --- a/charts/adoption-cos-api/Chart.yaml +++ b/charts/adoption-cos-api/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "1.0" description: A Helm chart for adoption-cos-api App name: adoption-cos-api home: https://github.com/hmcts/adoption-cos-api -version: 0.0.57 +version: 0.0.58 maintainers: - name: HMCTS Adoption team dependencies: From 5663ced3c767f9661c2a45c9b40e929f0c2e3d6a Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:55:28 +0000 Subject: [PATCH 09/23] Added tomcat-embed-core and tomcat-embed-websocket to suppressions --- config/owasp/suppressions.xml | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index 433536d77..aa4922403 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -50,6 +50,48 @@ ^pkg:maven/org\.apache\.logging\.log4j/log4j\-core@.*$ CVE-2025-68161 + + + ^pkg:maven/org\.apache\.tomcat\.embed/tomcat\-embed\-core@.*$ + CVE-2025-66614 + + + + ^pkg:maven/org\.apache\.tomcat\.embed/tomcat\-embed\-core@.*$ + CVE-2026-24733 + + + + ^pkg:maven/org\.apache\.tomcat\.embed/tomcat\-embed\-core@.*$ + CVE-2026-24734 + + + + ^pkg:maven/org\.apache\.tomcat\.embed/tomcat\-embed\-websocket@.*$ + CVE-2025-66614 + + + + ^pkg:maven/org\.apache\.tomcat\.embed/tomcat\-embed\-websocket@.*$ + CVE-2026-24733 + + + + ^pkg:maven/org\.apache\.tomcat\.embed/tomcat\-embed\-websocket@.*$ + CVE-2026-24734 + Date: Tue, 17 Mar 2026 14:24:07 +0000 Subject: [PATCH 10/23] PACT_BROKER_FULL_URL fixes --- build.gradle | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 2f48dce5e..a5a18c93f 100644 --- a/build.gradle +++ b/build.gradle @@ -592,7 +592,11 @@ if (!contractTestConstantsMatcher.find()) { } def contractTestConsumerName = contractTestConstantsMatcher.group(1) def pactBrokerUrl = System.getenv("PACT_BROKER_FULL_URL") + ?: project.findProperty('pact.broker.url')?.toString() + ?: project.findProperty('pactbroker.url')?.toString() def pactBranchName = System.getenv("PACT_BRANCH_NAME") ?: 'Dev' +// Use a different variable name to avoid Groovy DELEGATE_FIRST shadowing inside pact {} closures +def resolvedBrokerUrl = pactBrokerUrl project.ext { pacticipant = contractTestConsumerName @@ -610,8 +614,8 @@ runAndPublishConsumerPactTests.finalizedBy pactPublish tasks.named('pactPublish') { onlyIf { - if (!pactBrokerUrl) { - logger.lifecycle("PACT_BROKER_FULL_URL not set; skipping pactPublish.") + if (!resolvedBrokerUrl) { + logger.lifecycle("No pact broker URL configured; skipping pactPublish.") return false } return true @@ -620,10 +624,11 @@ tasks.named('pactPublish') { pact { broker { - pactBrokerUrl = pactBrokerUrl ?: 'http://localhost:80' + pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' } publish { pactDirectory = 'pacts' + pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' tags = [pactBranchName] version = project.pacticipantVersion } From 745aefe82f475868c63d4cf8c303eb488406f593 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:08:58 +0000 Subject: [PATCH 11/23] adding logging on build.gradle to capture more detail on PACT build.gradle --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 619fa9bdb..d9f1aaceb 100644 --- a/build.gradle +++ b/build.gradle @@ -613,6 +613,11 @@ runAndPublishConsumerPactTests.dependsOn contract runAndPublishConsumerPactTests.finalizedBy pactPublish tasks.named('pactPublish') { + doFirst { + logger.lifecycle("pactPublish doFirst: resolvedBrokerUrl='${resolvedBrokerUrl}'") + logger.lifecycle("pactPublish doFirst: pact.publish.pactBrokerUrl='${project.pact?.publish?.pactBrokerUrl}'") + logger.lifecycle("pactPublish doFirst: pact.broker.pactBrokerUrl='${project.pact?.broker?.pactBrokerUrl}'") + } onlyIf { if (!resolvedBrokerUrl) { logger.lifecycle("No pact broker URL configured; skipping pactPublish.") From 18528b20eccf660b668169d7b8eee90b376aac86 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:48:47 +0000 Subject: [PATCH 12/23] added logging --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d9f1aaceb..eefebdb5f 100644 --- a/build.gradle +++ b/build.gradle @@ -152,7 +152,7 @@ task functional(type: Test,dependsOn: ':yarnInstall') { } } } -/// TODO got to here https://github.com/hmcts/fpl-ccd-configuration/pull/6180/changes + task highLevelDataSetup(type: JavaExec) { mainClass.set("uk.gov.hmcts.reform.adoption.common.ccd.HighLevelDataSetupApp") classpath += sourceSets.main.runtimeClasspath From c4687c55359bca84056747f1b6347603d4bd04f3 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:17:49 +0000 Subject: [PATCH 13/23] change to pactbroker url --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index eefebdb5f..a92f9f254 100644 --- a/build.gradle +++ b/build.gradle @@ -629,13 +629,13 @@ tasks.named('pactPublish') { pact { broker { - pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' + it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' } publish { - pactDirectory = 'pacts' - pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' - tags = [pactBranchName] - version = project.pacticipantVersion + it.pactDirectory = 'pacts' + it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' + it.tags = [pactBranchName] + it.consumerVersion = project.pacticipantVersion } } From fafac667471740f2d7e213ce823136e64ea37d1b Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Fri, 20 Mar 2026 13:20:09 +0000 Subject: [PATCH 14/23] Reverted changes to helm referencess --- build.gradle | 10 +++++----- charts/adoption-cos-api/Chart.yaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index a92f9f254..eefebdb5f 100644 --- a/build.gradle +++ b/build.gradle @@ -629,13 +629,13 @@ tasks.named('pactPublish') { pact { broker { - it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' + pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' } publish { - it.pactDirectory = 'pacts' - it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' - it.tags = [pactBranchName] - it.consumerVersion = project.pacticipantVersion + pactDirectory = 'pacts' + pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' + tags = [pactBranchName] + version = project.pacticipantVersion } } diff --git a/charts/adoption-cos-api/Chart.yaml b/charts/adoption-cos-api/Chart.yaml index 31669f0e1..1c1f24f4a 100644 --- a/charts/adoption-cos-api/Chart.yaml +++ b/charts/adoption-cos-api/Chart.yaml @@ -9,21 +9,21 @@ maintainers: dependencies: - name: java version: 5.3.0 - repository: 'oci://hmctsprod.azurecr.io/helm' + repository: 'oci://hmctspublic.azurecr.io/helm' - name: ccd version: 9.2.2 - repository: 'oci://hmctsprod.azurecr.io/helm' + repository: 'oci://hmctspublic.azurecr.io/helm' tags: - ccd-idam-pr - name: xui-webapp version: ~1.0.0 - repository: 'oci://hmctsprod.azurecr.io/helm' + repository: 'oci://hmctspublic.azurecr.io/helm' condition: xui-webapp.enabled - name: ccd-case-document-am-api version: 1.7.17 - repository: 'oci://hmctsprod.azurecr.io/helm' + repository: 'oci://hmctspublic.azurecr.io/helm' condition: ccd-case-document-am-api.enabled - name: postgresql version: 1.1.0 - repository: 'oci://hmctsprod.azurecr.io/helm' + repository: 'oci://hmctspublic.azurecr.io/helm' condition: postgresql.enabled From 12365b5931459993093983e1f8bf6f048bbdf9cf Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:54:49 +0000 Subject: [PATCH 15/23] Fix Pact broker URL assignment in pact DSL closures Restore it.pactBrokerUrl prefix to ensure property is set on the Pact plugin delegate, not the outer def pactBrokerUrl variable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index eefebdb5f..cebb616db 100644 --- a/build.gradle +++ b/build.gradle @@ -629,13 +629,13 @@ tasks.named('pactPublish') { pact { broker { - pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' + it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' } publish { - pactDirectory = 'pacts' - pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' - tags = [pactBranchName] - version = project.pacticipantVersion + it.pactDirectory = 'pacts' + it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' + it.tags = [pactBranchName] + it.version = project.pacticipantVersion } } From 3fc5281833cb04d1c96332ea4030b214e274dc77 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:29:42 +0000 Subject: [PATCH 16/23] Trigger pipeline re-run Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 2332d6720635feeb4c3bb0ff776c613c053746fc Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:03:48 +0000 Subject: [PATCH 17/23] Fix pact publish version property Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cebb616db..a92f9f254 100644 --- a/build.gradle +++ b/build.gradle @@ -635,7 +635,7 @@ pact { it.pactDirectory = 'pacts' it.pactBrokerUrl = resolvedBrokerUrl ?: 'http://localhost:80' it.tags = [pactBranchName] - it.version = project.pacticipantVersion + it.consumerVersion = project.pacticipantVersion } } From 9c29fd2da23b424fec9388ac3dbf8ad617ff35d2 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:59:47 +0000 Subject: [PATCH 18/23] fix: resolve Java 21 AKS deploy failures in preview environment - Add JAVA_OPTS --add-opens=java.base/java.lang=ALL-UNNAMED to am-role-assignment-service in values.preview.template.yaml to fix Drools/KIE rules compilation crash on Java 21 startup - Add pipelineJavaVersion workaround to build.gradle to guarantee cnp-jenkins-library detects Java 21 toolchain --- build.gradle | 6 ++++++ charts/adoption-cos-api/values.preview.template.yaml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index a92f9f254..76e59f6d1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,9 @@ +// This is cheating workaround to how Platops Jenkins pipeline detecting Java version +// For more details on how the pipeline detect and setup Java version, +// Ref: cnp-jenkins-library src/uk/gov/hmcts/contino/GradleBuilder.groovy function setupToolVersion() +// We will need to move our nested Gradle build script to this root script in the near future +var pipelineJavaVersion = "JavaLanguageVersion.of(21)" + buildscript { repositories { mavenLocal() diff --git a/charts/adoption-cos-api/values.preview.template.yaml b/charts/adoption-cos-api/values.preview.template.yaml index 1b90fbbeb..10285db36 100644 --- a/charts/adoption-cos-api/values.preview.template.yaml +++ b/charts/adoption-cos-api/values.preview.template.yaml @@ -118,6 +118,8 @@ ccd: am: secrets: - role-assignment-service-LD-SDK-KEY + environment: + JAVA_OPTS: "-XX:MaxRAMPercentage=65.0 --add-opens=java.base/java.lang=ALL-UNNAMED" ccd-definition-store-api: java: From 311fac0f112f5927cf70a1ffb210380467afd6f2 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:06:13 +0000 Subject: [PATCH 19/23] fix: move pipelineJavaVersion declaration after plugins block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gradle does not allow statements before plugins {} (only buildscript {} and pluginManagement {} are exempt). Move the cnp-jenkins-library Java version detection workaround to immediately after the plugins {} closing brace — the library scans the file as text so position is irrelevant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 76e59f6d1..ca7cdf065 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,3 @@ -// This is cheating workaround to how Platops Jenkins pipeline detecting Java version -// For more details on how the pipeline detect and setup Java version, -// Ref: cnp-jenkins-library src/uk/gov/hmcts/contino/GradleBuilder.groovy function setupToolVersion() -// We will need to move our nested Gradle build script to this root script in the near future -var pipelineJavaVersion = "JavaLanguageVersion.of(21)" - buildscript { repositories { mavenLocal() @@ -37,6 +31,12 @@ plugins { id 'au.com.dius.pact' version '4.3.8' } +// This is cheating workaround to how Platops Jenkins pipeline detecting Java version +// For more details on how the pipeline detect and setup Java version, +// Ref: cnp-jenkins-library src/uk/gov/hmcts/contino/GradleBuilder.groovy function setupToolVersion() +// We will need to move our nested Gradle build script to this root script in the near future +var pipelineJavaVersion = "JavaLanguageVersion.of(21)" + apply plugin: 'cz.habarta.typescript-generator' apply plugin: 'com.github.ben-manes.versions' apply plugin: 'io.spring.dependency-management' From 4a7ee0372ad58a6e1c242e8886f346bc7926dba0 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:12:25 +0000 Subject: [PATCH 20/23] fix: update Helm dependency repositories to new ACR registry Replace deprecated oci://hmctspublic.azurecr.io/helm with oci://hmctsprod.azurecr.io/helm across all Chart.yaml dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- charts/adoption-cos-api/Chart.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/charts/adoption-cos-api/Chart.yaml b/charts/adoption-cos-api/Chart.yaml index 1c1f24f4a..31669f0e1 100644 --- a/charts/adoption-cos-api/Chart.yaml +++ b/charts/adoption-cos-api/Chart.yaml @@ -9,21 +9,21 @@ maintainers: dependencies: - name: java version: 5.3.0 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' - name: ccd version: 9.2.2 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' tags: - ccd-idam-pr - name: xui-webapp version: ~1.0.0 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' condition: xui-webapp.enabled - name: ccd-case-document-am-api version: 1.7.17 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' condition: ccd-case-document-am-api.enabled - name: postgresql version: 1.1.0 - repository: 'oci://hmctspublic.azurecr.io/helm' + repository: 'oci://hmctsprod.azurecr.io/helm' condition: postgresql.enabled From 18f64228e1db519f6957bb911eda10e6b2311ced Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:57:32 +0000 Subject: [PATCH 21/23] change to hmctsprod on charts and docker --- charts/adoption-cos-api/values.preview.template.yaml | 10 +++++----- docker-compose.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/adoption-cos-api/values.preview.template.yaml b/charts/adoption-cos-api/values.preview.template.yaml index 10285db36..9c7369549 100644 --- a/charts/adoption-cos-api/values.preview.template.yaml +++ b/charts/adoption-cos-api/values.preview.template.yaml @@ -144,7 +144,7 @@ ccd: ccd-admin-web: nodejs: disableKeyVaults: true - image: hmctssbox.azurecr.io/ccd/admin-web:latest + image: hmctsprod.azurecr.io/ccd/admin-web:latest ingressHost: admin-web-${SERVICE_FQDN} environment: USER_PROFILE_DB_HOST: '{{ tpl .Values.global.postgresHostname $}}' @@ -170,13 +170,13 @@ ccd: replicas: 1 logstash: - image: "hmctssbox.azurecr.io/imported/logstash/logstash" + image: "hmctsprod.azurecr.io/imported/logstash/logstash" imageTag: "7.16.1" imagePullPolicy: "IfNotPresent" logstashJavaOpts: "-Xmx1g -Xms512M" extraInitContainers: | - name: download-postgres-jdbc - image: hmctssbox.azurecr.io/curl:7.70.0 + image: hmctsprod.azurecr.io/curl:7.70.0 command: ['curl', '-L', 'https://jdbc.postgresql.org/download/postgresql-42.2.18.jar', '-o', '/logstash-lib/postgresql.jar'] volumeMounts: - name: logstash-lib @@ -346,7 +346,7 @@ xui-webapp: nodejs: imagePullPolicy: Always releaseNameOverride: ${SERVICE_NAME}-xui-webapp - image: hmctssbox.azurecr.io/xui/webapp:latest + image: hmctsprod.azurecr.io/xui/webapp:latest ingressHost: xui-${SERVICE_FQDN} environment: HEALTH_CCD_COMPONENT_API: http://${SERVICE_NAME}-ccd-api-gw/health @@ -368,7 +368,7 @@ ccd-case-document-am-api: disableKeyVaults: true releaseNameOverride: ${SERVICE_NAME}-ccd-case-document-am-api imagePullPolicy: Always - image: hmctssbox.azurecr.io/ccd/case-document-am-api:latest + image: hmctsprod.azurecr.io/ccd/case-document-am-api:latest ingressHost: ccd-case-document-am-api-${SERVICE_FQDN} environment: CASE_DOCUMENT_S2S_AUTHORISED_SERVICES: ccd_case_document_am_api,ccd_gw,xui_webapp,ccd_data,bulk_scan_processor,dg_docassembly_api,bulk_scan_orchestrator,adoption_cos_api,adoption_web diff --git a/docker-compose.yml b/docker-compose.yml index 9fa7ea618..2d7f58c74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - http_proxy - https_proxy - no_proxy - image: hmctssbox.azurecr.io/adoption-cos-api + image: hmctsprod.azurecr.io/adoption-cos-api environment: # these environment variables are used by java-logging library - ROOT_APPENDER From 6b593b2bc4ce78a83687170d9142f3c6a8a4b529 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:20:27 +0000 Subject: [PATCH 22/23] renamed smoke test in build.gradel --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ca7cdf065..d06872b33 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ task yarnInstall(type: Exec) { commandLine 'yarn', 'install' } -tasks.register('runSmokeTests', Exec) { +tasks.register('smoke', Exec) { description = "Runs Smoke Tests" commandLine 'yarn', '--silent', 'test:smoke' } From eb2744c53df06933d1d0154cb8fdb813e7f11218 Mon Sep 17 00:00:00 2001 From: Steve Newman <115482991+NewmanJustice@users.noreply.github.com> Date: Tue, 31 Mar 2026 07:52:45 +0000 Subject: [PATCH 23/23] reverted change to the smoke test --- build.gradle | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d06872b33..a05462abe 100644 --- a/build.gradle +++ b/build.gradle @@ -145,9 +145,13 @@ task yarnInstall(type: Exec) { commandLine 'yarn', 'install' } -tasks.register('smoke', Exec) { +task smoke(type: Test,dependsOn: ':yarnInstall') { description = "Runs Smoke Tests" - commandLine 'yarn', '--silent', 'test:smoke' + doLast { + exec { + commandLine 'yarn', 'test:smoke' + } + } } task functional(type: Test,dependsOn: ':yarnInstall') {