diff --git a/cypress/e2e/donor/check-your-lpa.cy.js b/cypress/e2e/donor/check-your-lpa.cy.js index 6fc9adc6ca..5455df8025 100644 --- a/cypress/e2e/donor/check-your-lpa.cy.js +++ b/cypress/e2e/donor/check-your-lpa.cy.js @@ -66,6 +66,7 @@ describe('Check the LPA', () => { it('content is tailored for paper CPs, a details component is shown and nav redirects to payment', () => { cy.visit('/fixtures?redirect=/check-your-lpa&progress=addCorrespondent&certificateProvider=paper'); + cy.url().should('contain', '/check-your-lpa'); cy.get('label[for=f-checked-and-happy]').contains('I’ve checked this LPA and I’m happy to show it to my certificate provider, Charlie Cooper') cy.get('details').contains('What happens if I need to make changes later?') @@ -84,8 +85,18 @@ describe('Check the LPA', () => { describe('on subsequent check when LPA has not been paid for', () => { it('content is tailored for paper CPs, a warning component is shown and nav redirects to payment', () => { - cy.visit('/fixtures?redirect=/check-your-lpa&progress=checkAndSendToYourCertificateProvider&certificateProvider=paper'); + cy.visit('/fixtures?redirect=/task-list&progress=checkAndSendToYourCertificateProvider&certificateProvider=paper'); + cy.contains('li', 'Add a correspondent').should('contain', 'Completed').click(); + + cy.url().should('contain', '/add-correspondent'); + cy.contains('label', 'No').click(); + cy.contains('button', 'Save and continue').click(); + + cy.url().should('contain', '/task-list'); + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'In progress').click(); + + cy.url().should('contain', '/check-your-lpa'); cy.get('label[for=f-checked-and-happy]').contains('I’ve checked this LPA and I’m happy to show it to my certificate provider, Charlie Cooper') cy.get('.govuk-warning-text').contains('Once you select the confirm button, your certificate provider will be sent a text telling them you have changed your LPA.') @@ -104,8 +115,18 @@ describe('Check the LPA', () => { describe('on subsequent check when LPA has been paid for', () => { it('content is tailored for paper CPs, a warning component is shown and nav redirects to dashboard', () => { - cy.visit('/fixtures?redirect=/check-your-lpa&progress=payForTheLpa&certificateProvider=paper'); + cy.visit('/fixtures?redirect=/task-list&progress=payForTheLpa&certificateProvider=paper'); + + cy.contains('li', 'Add a correspondent').should('contain', 'Completed').click(); + + cy.url().should('contain', '/add-correspondent'); + cy.contains('label', 'No').click(); + cy.contains('button', 'Save and continue').click(); + + cy.url().should('contain', '/task-list'); + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'In progress').click(); + cy.url().should('contain', '/check-your-lpa'); cy.get('label[for=f-checked-and-happy]').contains('I’ve checked this LPA and I’m happy to show it to my certificate provider, Charlie Cooper') cy.get('.govuk-warning-text').contains('Once you select the confirm button, your certificate provider will be sent a text telling them you have changed your LPA.') @@ -128,6 +149,7 @@ describe('Check the LPA', () => { it('content is tailored for online CPs, a details component is shown and nav redirects to payment', () => { cy.visit('/fixtures?redirect=/check-your-lpa&progress=addCorrespondent'); + cy.url().should('contain', '/check-your-lpa'); cy.get('label[for=f-checked-and-happy]').contains('I’ve checked this LPA and I’m happy for OPG to share it with my certificate provider, Charlie Cooper') cy.get('details').contains('What happens if I need to make changes later?') @@ -146,8 +168,18 @@ describe('Check the LPA', () => { describe('on subsequent check when LPA has not been paid for', () => { it('content is tailored for online CPs, a warning component is shown and nav redirects to payment', () => { - cy.visit('/fixtures?redirect=/check-your-lpa&progress=checkAndSendToYourCertificateProvider'); + cy.visit('/fixtures?redirect=/task-list&progress=checkAndSendToYourCertificateProvider'); + + cy.contains('li', 'Add a correspondent').should('contain', 'Completed').click(); + cy.url().should('contain', '/add-correspondent'); + cy.contains('label', 'No').click(); + cy.contains('button', 'Save and continue').click(); + + cy.url().should('contain', '/task-list'); + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'In progress').click(); + + cy.url().should('contain', '/check-your-lpa'); cy.get('label[for=f-checked-and-happy]').contains('I’ve checked this LPA and I’m happy for OPG to share it with my certificate provider, Charlie Cooper') cy.get('.govuk-warning-text').contains('Once you select the confirm button, your certificate provider will be sent a text telling them you have changed your LPA.') @@ -166,8 +198,18 @@ describe('Check the LPA', () => { describe('on subsequent check when LPA has been paid for', () => { it('content is tailored for online CPs, a warning component is shown and nav redirects to dashboard', () => { - cy.visit('/fixtures?redirect=/check-your-lpa&progress=payForTheLpa'); + cy.visit('/fixtures?redirect=/task-list&progress=payForTheLpa'); + + cy.contains('li', 'Add a correspondent').should('contain', 'Completed').click(); + + cy.url().should('contain', '/add-correspondent'); + cy.contains('label', 'No').click(); + cy.contains('button', 'Save and continue').click(); + cy.url().should('contain', '/task-list'); + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'In progress').click(); + + cy.url().should('contain', '/check-your-lpa'); cy.get('label[for=f-checked-and-happy]').contains('I’ve checked this LPA and I’m happy for OPG to share it with my certificate provider, Charlie Cooper') cy.get('.govuk-warning-text').contains('Once you select the confirm button, your certificate provider will be sent a text telling them you have changed your LPA.') @@ -185,6 +227,37 @@ describe('Check the LPA', () => { }) }) + it("must check and send again when making LPA changes after certificate provider is contacted", () => { + cy.visit('/fixtures?redirect=/task-list&progress=checkAndSendToYourCertificateProvider'); + + cy.url().should('contain', '/task-list'); + + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'Completed'); + cy.contains('li', 'Pay for the LPA').should('contain', 'Not started'); + cy.contains('li', 'Add a correspondent').should('contain', 'Completed').click(); + + cy.url().should('contain', '/add-correspondent'); + cy.contains('label', 'No').click(); + cy.contains('button', 'Save and continue').click(); + + cy.url().should('contain', '/task-list'); + + cy.contains('li', 'Pay for the LPA').should('contain', 'Cannot start yet'); + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'In progress').click(); + + cy.get('#f-checked-and-happy').check({ force: true }) + cy.contains('button', 'Confirm').click(); + + cy.url().should('contain', '/lpa-details-saved'); + + cy.contains('a', 'Continue').click(); + + cy.url().should('contain', '/task-list'); + + cy.contains('li', 'Check and send to your certificate provider').should('contain', 'Completed'); + cy.contains('li', 'Pay for the LPA').should('contain', 'Not started'); + }) + it("errors when not selected", () => { cy.visit('/fixtures?redirect=/check-your-lpa&progress=addCorrespondent'); cy.contains('button', 'Confirm').click(); diff --git a/internal/actor/donor_provided.go b/internal/actor/donor_provided.go index 0d7cb7240f..42203a229d 100644 --- a/internal/actor/donor_provided.go +++ b/internal/actor/donor_provided.go @@ -176,6 +176,7 @@ func (c toCheck) HashInclude(field string, _ any) (bool, error) { // checked. switch field { case "CheckedAt", + "CreatedAt", "Tasks", "PaymentDetails", "DonorIdentityUserData", @@ -193,7 +194,10 @@ func (c toCheck) HashInclude(field string, _ any) (bool, error) { "FeeType", "EvidenceDelivery", "PreviousApplicationNumber", - "PreviousFee": + "PreviousFee", + "RegisteringWithCourtOfProtection", + "WantVoucher", + "Voucher": return false, nil } diff --git a/internal/actor/donor_provided_test.go b/internal/actor/donor_provided_test.go index 4a8601637b..43eb4deaa9 100644 --- a/internal/actor/donor_provided_test.go +++ b/internal/actor/donor_provided_test.go @@ -91,13 +91,13 @@ func TestGenerateCheckedHash(t *testing.T) { } // DO change this value to match the updates - const modified uint64 = 0x4af298e82cc36153 + const modified uint64 = 0x2a104a01bcc31f64 // DO NOT change these initial hash values. If a field has been added/removed // you will need to handle the version gracefully by modifying // toCheck.HashInclude and adding another testcase for the new version. testcases := map[uint8]uint64{ - 0: 0x49f8cef460cf416e, + 0: 0x52d72543cced8895, } for version, initial := range testcases { diff --git a/internal/app/donor_store.go b/internal/app/donor_store.go index c6a9a23e71..9109897253 100644 --- a/internal/app/donor_store.go +++ b/internal/app/donor_store.go @@ -162,7 +162,7 @@ func (s *donorStore) GetAny(ctx context.Context) (*actor.DonorProvidedDetails, e } if data.LpaID == "" { - return nil, errors.New("donorStore.Get requires LpaID") + return nil, errors.New("donorStore.GetAny requires LpaID") } var sk dynamo.SK = dynamo.DonorKey("") @@ -222,7 +222,7 @@ func (s *donorStore) Latest(ctx context.Context) (*actor.DonorProvidedDetails, e } if data.SessionID == "" { - return nil, errors.New("donorStore.Get requires SessionID") + return nil, errors.New("donorStore.Latest requires SessionID") } var donor *actor.DonorProvidedDetails @@ -264,6 +264,11 @@ func (s *donorStore) Put(ctx context.Context, donor *actor.DonorProvidedDetails) return nil } + // Enforces donor to send notifications to certificate provider when LPA data has changed + if donor.CheckedHashChanged() && donor.Tasks.CheckYourLpa.Completed() { + donor.Tasks.CheckYourLpa = actor.TaskInProgress + } + if err := donor.UpdateHash(); err != nil { return err } @@ -313,7 +318,7 @@ func (s *donorStore) Delete(ctx context.Context) error { } if data.SessionID == "" || data.LpaID == "" { - return errors.New("donorStore.Create requires SessionID and LpaID") + return errors.New("donorStore.Delete requires SessionID and LpaID") } keys, err := s.dynamoClient.AllKeysByPK(ctx, dynamo.LpaKey(data.LpaID)) diff --git a/internal/app/donor_store_test.go b/internal/app/donor_store_test.go index 2eca0d7cc5..73b48718ca 100644 --- a/internal/app/donor_store_test.go +++ b/internal/app/donor_store_test.go @@ -404,6 +404,21 @@ func TestDonorStorePutWhenNoChange(t *testing.T) { assert.Nil(t, err) } +func TestDonorStorePutWhenCheckChangeAndCheckCompleted(t *testing.T) { + saved := &actor.DonorProvidedDetails{PK: dynamo.LpaKey("5"), Hash: 5, CheckedHash: 5, SK: dynamo.LpaOwnerKey(dynamo.DonorKey("an-id")), LpaID: "5", HasSentApplicationUpdatedEvent: true, Donor: actor.Donor{FirstNames: "a", LastName: "b"}, Tasks: actor.DonorTasks{CheckYourLpa: actor.TaskInProgress}} + _ = saved.UpdateHash() + + dynamoClient := newMockDynamoClient(t) + dynamoClient.EXPECT(). + Put(ctx, saved). + Return(nil) + + donorStore := &donorStore{dynamoClient: dynamoClient, now: testNowFn} + + err := donorStore.Put(ctx, &actor.DonorProvidedDetails{PK: dynamo.LpaKey("5"), Hash: 5, CheckedHash: 5, SK: dynamo.LpaOwnerKey(dynamo.DonorKey("an-id")), LpaID: "5", HasSentApplicationUpdatedEvent: true, Donor: actor.Donor{FirstNames: "a", LastName: "b"}, Tasks: actor.DonorTasks{CheckYourLpa: actor.TaskCompleted}}) + assert.Nil(t, err) +} + func TestDonorStorePutWhenError(t *testing.T) { dynamoClient := newMockDynamoClient(t) dynamoClient.EXPECT().Put(ctx, mock.Anything).Return(expectedError) diff --git a/internal/page/donor/check_your_lpa.go b/internal/page/donor/check_your_lpa.go index 3b7214635a..706051a11e 100644 --- a/internal/page/donor/check_your_lpa.go +++ b/internal/page/donor/check_your_lpa.go @@ -17,12 +17,12 @@ import ( ) type checkYourLpaData struct { - App page.AppData - Errors validation.List - Donor *actor.DonorProvidedDetails - Form *checkYourLpaForm - Completed bool - CanContinue bool + App page.AppData + Errors validation.List + Donor *actor.DonorProvidedDetails + Form *checkYourLpaForm + CertificateProviderContacted bool + CanContinue bool } type checkYourLpaNotifier struct { @@ -113,8 +113,8 @@ func CheckYourLpa(tmpl template.Template, donorStore DonorStore, shareCodeSender Form: &checkYourLpaForm{ CheckedAndHappy: !donor.CheckedAt.IsZero(), }, - Completed: donor.Tasks.CheckYourLpa.Completed(), - CanContinue: donor.CheckedHashChanged(), + CertificateProviderContacted: !donor.CheckedAt.IsZero(), + CanContinue: donor.CheckedHashChanged(), } if r.Method == http.MethodPost && data.CanContinue { @@ -128,7 +128,7 @@ func CheckYourLpa(tmpl template.Template, donorStore DonorStore, shareCodeSender return err } - if err := notifier.Notify(r.Context(), appData, donor, data.Completed); err != nil { + if err := notifier.Notify(r.Context(), appData, donor, data.CertificateProviderContacted); err != nil { return err } @@ -136,7 +136,7 @@ func CheckYourLpa(tmpl template.Template, donorStore DonorStore, shareCodeSender return err } - if !data.Completed { + if !data.CertificateProviderContacted { return page.Paths.LpaDetailsSaved.RedirectQuery(w, r, appData, donor, url.Values{"firstCheck": {"1"}}) } diff --git a/internal/page/donor/check_your_lpa_test.go b/internal/page/donor/check_your_lpa_test.go index 797be28870..cd85ab5967 100644 --- a/internal/page/donor/check_your_lpa_test.go +++ b/internal/page/donor/check_your_lpa_test.go @@ -54,6 +54,7 @@ func TestGetCheckYourLpaFromStore(t *testing.T) { Form: &checkYourLpaForm{ CheckedAndHappy: true, }, + CertificateProviderContacted: true, }). Return(nil) @@ -89,7 +90,7 @@ func TestPostCheckYourLpaWhenNotChanged(t *testing.T) { Form: &checkYourLpaForm{ CheckedAndHappy: true, }, - Completed: true, + CertificateProviderContacted: true, }). Return(nil) diff --git a/internal/page/donor/one_login_identity_details.go b/internal/page/donor/one_login_identity_details.go index 1bc03b6e69..cc283028eb 100644 --- a/internal/page/donor/one_login_identity_details.go +++ b/internal/page/donor/one_login_identity_details.go @@ -47,6 +47,9 @@ func OneLoginIdentityDetails(tmpl template.Template, donorStore DonorStore) Hand donor.Donor.LastName = donor.DonorIdentityUserData.LastName donor.Donor.DateOfBirth = donor.DonorIdentityUserData.DateOfBirth donor.Donor.Address = donor.DonorIdentityUserData.CurrentAddress + if err := donor.UpdateCheckedHash(); err != nil { + return err + } if err := donorStore.Put(r.Context(), donor); err != nil { return err diff --git a/internal/page/donor/one_login_identity_details_test.go b/internal/page/donor/one_login_identity_details_test.go index 7677112f8a..3c80c7583f 100644 --- a/internal/page/donor/one_login_identity_details_test.go +++ b/internal/page/donor/one_login_identity_details_test.go @@ -87,12 +87,16 @@ func TestPostOneLoginIdentityDetailsWhenYes(t *testing.T) { existingDob := date.New("1", "2", "3") identityDob := date.New("4", "5", "6") + updated := &actor.DonorProvidedDetails{ + LpaID: "lpa-id", + Donor: actor.Donor{FirstNames: "b", LastName: "b", DateOfBirth: identityDob, Address: place.Address{Line1: "a"}}, + DonorIdentityUserData: identity.UserData{FirstNames: "b", LastName: "b", DateOfBirth: identityDob, CurrentAddress: place.Address{Line1: "a"}}, + } + updated.UpdateCheckedHash() + donorStore := newMockDonorStore(t) donorStore.EXPECT(). - Put(r.Context(), &actor.DonorProvidedDetails{ - LpaID: "lpa-id", - Donor: actor.Donor{FirstNames: "b", LastName: "b", DateOfBirth: identityDob, Address: place.Address{Line1: "a"}}, - DonorIdentityUserData: identity.UserData{FirstNames: "b", LastName: "b", DateOfBirth: identityDob, CurrentAddress: place.Address{Line1: "a"}}}). + Put(r.Context(), updated). Return(nil) err := OneLoginIdentityDetails(nil, donorStore)(testAppData, w, r, &actor.DonorProvidedDetails{ diff --git a/internal/page/fixtures/donor.go b/internal/page/fixtures/donor.go index f471244f2c..9e543f9755 100644 --- a/internal/page/fixtures/donor.go +++ b/internal/page/fixtures/donor.go @@ -109,7 +109,7 @@ func Donor( donorSessionID := base64.StdEncoding.EncodeToString([]byte(data.DonorSub)) - if err := sessionStore.SetLogin(r, w, &sesh.LoginSession{Sub: data.DonorSub, Email: testEmail}); err != nil { + if err := sessionStore.SetLogin(r, w, &sesh.LoginSession{Sub: data.DonorSub, Email: data.DonorEmail}); err != nil { return err } @@ -126,9 +126,16 @@ func Donor( donorCtx := page.ContextWithSessionData(r.Context(), &page.SessionData{SessionID: donorSessionID, LpaID: donorDetails.LpaID}) + if data.Progress >= slices.Index(progressValues, "checkAndSendToYourCertificateProvider") { + if err = donorDetails.UpdateCheckedHash(); err != nil { + return fmt.Errorf("problem updating checkedHash: %w", err) + } + } + if err := donorStore.Put(donorCtx, donorDetails); err != nil { return err } + if !donorDetails.SignedAt.IsZero() && donorDetails.LpaUID != "" { if err := lpaStoreClient.SendLpa(donorCtx, donorDetails); err != nil { return err diff --git a/web/template/donor/check_your_lpa.gohtml b/web/template/donor/check_your_lpa.gohtml index 3208e383f0..0a6f6a70a9 100644 --- a/web/template/donor/check_your_lpa.gohtml +++ b/web/template/donor/check_your_lpa.gohtml @@ -41,7 +41,7 @@ - {{ if .Completed }} + {{ if .CertificateProviderContacted }} {{ template "warning" (content .App "onceYouClickCertificateProviderWillBeSentText") }} {{ else }} {{ template "details" (details . "whatHappensIfIChange" "whatHappensIfIChangeDetails" false) }}