From 43a7dcc32c38d79612582910fb86d52113f013ed Mon Sep 17 00:00:00 2001 From: Joshua Hawxwell Date: Wed, 27 Nov 2024 13:55:37 +0000 Subject: [PATCH] Send email to voucher when donor removes them --- ...are_you_sure_you_no_longer_need_voucher.go | 12 +++- ...ou_sure_you_no_longer_need_voucher_test.go | 58 +++++++++++++++---- .../donor/donorpage/mock_NotifyClient_test.go | 50 ++++++++++++++++ internal/donor/donorpage/register.go | 3 +- internal/notify/email.go | 21 +++++++ lang/cy.json | 1 + lang/en.json | 1 + 7 files changed, 134 insertions(+), 12 deletions(-) diff --git a/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher.go b/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher.go index 919654c22c..c1f093be5c 100644 --- a/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher.go +++ b/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher.go @@ -1,6 +1,7 @@ package donorpage import ( + "fmt" "net/http" "net/url" @@ -9,6 +10,8 @@ import ( "github.com/ministryofjustice/opg-modernising-lpa/internal/donor" "github.com/ministryofjustice/opg-modernising-lpa/internal/donor/donordata" "github.com/ministryofjustice/opg-modernising-lpa/internal/form" + "github.com/ministryofjustice/opg-modernising-lpa/internal/localize" + "github.com/ministryofjustice/opg-modernising-lpa/internal/notify" "github.com/ministryofjustice/opg-modernising-lpa/internal/validation" ) @@ -18,7 +21,7 @@ type areYouSureYouNoLongerNeedVoucherData struct { Donor *donordata.Provided } -func AreYouSureYouNoLongerNeedVoucher(tmpl template.Template, donorStore DonorStore) Handler { +func AreYouSureYouNoLongerNeedVoucher(tmpl template.Template, donorStore DonorStore, notifyClient NotifyClient) Handler { return func(appData appcontext.Data, w http.ResponseWriter, r *http.Request, provided *donordata.Provided) error { data := &areYouSureYouNoLongerNeedVoucherData{ App: appData, @@ -36,6 +39,13 @@ func AreYouSureYouNoLongerNeedVoucher(tmpl template.Template, donorStore DonorSt provided.WantVoucher = form.YesNoUnknown nextPage := handleDoNext(doNext, provided).Format(provided.LpaID) + if err := notifyClient.SendActorEmail(r.Context(), localize.En, provided.Voucher.Email, provided.LpaUID, notify.VoucherInformedTheyAreNoLongerNeededToVouchEmail{ + VoucherFullName: provided.Voucher.FullName(), + DonorFullName: provided.Donor.FullName(), + }); err != nil { + return fmt.Errorf("failed to send email: %w", err) + } + if err := donorStore.DeleteVoucher(r.Context(), provided); err != nil { return err } diff --git a/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher_test.go b/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher_test.go index 98c75c54de..0dcaa88702 100644 --- a/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher_test.go +++ b/internal/donor/donorpage/are_you_sure_you_no_longer_need_voucher_test.go @@ -10,6 +10,8 @@ import ( "github.com/ministryofjustice/opg-modernising-lpa/internal/donor/donordata" "github.com/ministryofjustice/opg-modernising-lpa/internal/form" "github.com/ministryofjustice/opg-modernising-lpa/internal/identity" + "github.com/ministryofjustice/opg-modernising-lpa/internal/localize" + "github.com/ministryofjustice/opg-modernising-lpa/internal/notify" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -26,7 +28,7 @@ func TestGetAreYouSureYouNoLongerNeedVoucher(t *testing.T) { }). Return(nil) - err := AreYouSureYouNoLongerNeedVoucher(template.Execute, nil)(testAppData, w, r, &donordata.Provided{}) + err := AreYouSureYouNoLongerNeedVoucher(template.Execute, nil, nil)(testAppData, w, r, &donordata.Provided{}) resp := w.Result() assert.Nil(t, err) @@ -42,7 +44,7 @@ func TestGetAreYouSureYouNoLongerNeedVoucherWhenTemplateErrors(t *testing.T) { Execute(w, mock.Anything). Return(expectedError) - err := AreYouSureYouNoLongerNeedVoucher(template.Execute, nil)(testAppData, w, r, &donordata.Provided{}) + err := AreYouSureYouNoLongerNeedVoucher(template.Execute, nil, nil)(testAppData, w, r, &donordata.Provided{}) resp := w.Result() assert.Equal(t, expectedError, err) @@ -58,15 +60,19 @@ func TestPostAreYouSureYouNoLongerNeedVoucher(t *testing.T) { redirect: donor.PathConfirmYourIdentity, provided: &donordata.Provided{ LpaID: "lpa-id", - Voucher: donordata.Voucher{FirstNames: "a", LastName: "b"}, + LpaUID: "lpa-uid", + Donor: donordata.Donor{FirstNames: "d", LastName: "e"}, + Voucher: donordata.Voucher{FirstNames: "a", LastName: "b", Email: "voucher@example.com"}, }, }, donordata.SelectNewVoucher: { redirect: donor.PathEnterVoucher, provided: &donordata.Provided{ LpaID: "lpa-id", + LpaUID: "lpa-uid", + Donor: donordata.Donor{FirstNames: "d", LastName: "e"}, WantVoucher: form.Yes, - Voucher: donordata.Voucher{FirstNames: "a", LastName: "b"}, + Voucher: donordata.Voucher{FirstNames: "a", LastName: "b", Email: "voucher@example.com"}, IdentityUserData: identity.UserData{Status: identity.StatusConfirmed}, }, }, @@ -74,7 +80,9 @@ func TestPostAreYouSureYouNoLongerNeedVoucher(t *testing.T) { redirect: donor.PathWithdrawThisLpa, provided: &donordata.Provided{ LpaID: "lpa-id", - Voucher: donordata.Voucher{FirstNames: "a", LastName: "b"}, + LpaUID: "lpa-uid", + Donor: donordata.Donor{FirstNames: "d", LastName: "e"}, + Voucher: donordata.Voucher{FirstNames: "a", LastName: "b", Email: "voucher@example.com"}, IdentityUserData: identity.UserData{Status: identity.StatusConfirmed}, }, }, @@ -82,7 +90,9 @@ func TestPostAreYouSureYouNoLongerNeedVoucher(t *testing.T) { redirect: donor.PathWhatHappensNextRegisteringWithCourtOfProtection, provided: &donordata.Provided{ LpaID: "lpa-id", - Voucher: donordata.Voucher{FirstNames: "a", LastName: "b"}, + LpaUID: "lpa-uid", + Donor: donordata.Donor{FirstNames: "d", LastName: "e"}, + Voucher: donordata.Voucher{FirstNames: "a", LastName: "b", Email: "voucher@example.com"}, IdentityUserData: identity.UserData{Status: identity.StatusConfirmed}, RegisteringWithCourtOfProtection: true, }, @@ -99,10 +109,20 @@ func TestPostAreYouSureYouNoLongerNeedVoucher(t *testing.T) { DeleteVoucher(r.Context(), tc.provided). Return(nil) - err := AreYouSureYouNoLongerNeedVoucher(nil, donorStore)(testAppData, w, r, &donordata.Provided{ + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + SendActorEmail(r.Context(), localize.En, "voucher@example.com", "lpa-uid", notify.VoucherInformedTheyAreNoLongerNeededToVouchEmail{ + DonorFullName: "d e", + VoucherFullName: "a b", + }). + Return(nil) + + err := AreYouSureYouNoLongerNeedVoucher(nil, donorStore, notifyClient)(testAppData, w, r, &donordata.Provided{ LpaID: "lpa-id", + LpaUID: "lpa-uid", + Donor: donordata.Donor{FirstNames: "d", LastName: "e"}, WantVoucher: form.Yes, - Voucher: donordata.Voucher{FirstNames: "a", LastName: "b"}, + Voucher: donordata.Voucher{FirstNames: "a", LastName: "b", Email: "voucher@example.com"}, IdentityUserData: identity.UserData{Status: identity.StatusConfirmed}, }) resp := w.Result() @@ -118,6 +138,19 @@ func TestPostAreYouSureYouNoLongerNeedVoucher(t *testing.T) { } } +func TestPostAreYouSureYouNoLongerNeedVoucherWhenNotifyErrors(t *testing.T) { + w := httptest.NewRecorder() + r, _ := http.NewRequest(http.MethodPost, "/?choice="+donordata.ProveOwnIdentity.String(), nil) + + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + SendActorEmail(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + + err := AreYouSureYouNoLongerNeedVoucher(nil, nil, notifyClient)(testAppData, w, r, &donordata.Provided{}) + assert.ErrorIs(t, err, expectedError) +} + func TestPostAreYouSureYouNoLongerNeedVoucherWhenStoreErrors(t *testing.T) { w := httptest.NewRecorder() r, _ := http.NewRequest(http.MethodPost, "/?choice="+donordata.ProveOwnIdentity.String(), nil) @@ -127,7 +160,12 @@ func TestPostAreYouSureYouNoLongerNeedVoucherWhenStoreErrors(t *testing.T) { DeleteVoucher(r.Context(), mock.Anything). Return(expectedError) - err := AreYouSureYouNoLongerNeedVoucher(nil, donorStore)(testAppData, w, r, &donordata.Provided{}) + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + SendActorEmail(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) + + err := AreYouSureYouNoLongerNeedVoucher(nil, donorStore, notifyClient)(testAppData, w, r, &donordata.Provided{}) assert.Equal(t, expectedError, err) } @@ -136,6 +174,6 @@ func TestPostAreYouSureYouNoLongerNeedVoucherWhenInvalidChoice(t *testing.T) { w := httptest.NewRecorder() r, _ := http.NewRequest(http.MethodPost, "/?choice=what", nil) - err := AreYouSureYouNoLongerNeedVoucher(nil, nil)(testAppData, w, r, &donordata.Provided{}) + err := AreYouSureYouNoLongerNeedVoucher(nil, nil, nil)(testAppData, w, r, &donordata.Provided{}) assert.Error(t, err) } diff --git a/internal/donor/donorpage/mock_NotifyClient_test.go b/internal/donor/donorpage/mock_NotifyClient_test.go index 87b72cdb79..49a24a06a5 100644 --- a/internal/donor/donorpage/mock_NotifyClient_test.go +++ b/internal/donor/donorpage/mock_NotifyClient_test.go @@ -24,6 +24,56 @@ func (_m *mockNotifyClient) EXPECT() *mockNotifyClient_Expecter { return &mockNotifyClient_Expecter{mock: &_m.Mock} } +// SendActorEmail provides a mock function with given fields: ctx, lang, to, lpaUID, email +func (_m *mockNotifyClient) SendActorEmail(ctx context.Context, lang localize.Lang, to string, lpaUID string, email notify.Email) error { + ret := _m.Called(ctx, lang, to, lpaUID, email) + + if len(ret) == 0 { + panic("no return value specified for SendActorEmail") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, localize.Lang, string, string, notify.Email) error); ok { + r0 = rf(ctx, lang, to, lpaUID, email) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockNotifyClient_SendActorEmail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendActorEmail' +type mockNotifyClient_SendActorEmail_Call struct { + *mock.Call +} + +// SendActorEmail is a helper method to define mock.On call +// - ctx context.Context +// - lang localize.Lang +// - to string +// - lpaUID string +// - email notify.Email +func (_e *mockNotifyClient_Expecter) SendActorEmail(ctx interface{}, lang interface{}, to interface{}, lpaUID interface{}, email interface{}) *mockNotifyClient_SendActorEmail_Call { + return &mockNotifyClient_SendActorEmail_Call{Call: _e.mock.On("SendActorEmail", ctx, lang, to, lpaUID, email)} +} + +func (_c *mockNotifyClient_SendActorEmail_Call) Run(run func(ctx context.Context, lang localize.Lang, to string, lpaUID string, email notify.Email)) *mockNotifyClient_SendActorEmail_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(localize.Lang), args[2].(string), args[3].(string), args[4].(notify.Email)) + }) + return _c +} + +func (_c *mockNotifyClient_SendActorEmail_Call) Return(_a0 error) *mockNotifyClient_SendActorEmail_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockNotifyClient_SendActorEmail_Call) RunAndReturn(run func(context.Context, localize.Lang, string, string, notify.Email) error) *mockNotifyClient_SendActorEmail_Call { + _c.Call.Return(run) + return _c +} + // SendActorSMS provides a mock function with given fields: ctx, lang, to, lpaUID, sms func (_m *mockNotifyClient) SendActorSMS(ctx context.Context, lang localize.Lang, to string, lpaUID string, sms notify.SMS) error { ret := _m.Called(ctx, lang, to, lpaUID, sms) diff --git a/internal/donor/donorpage/register.go b/internal/donor/donorpage/register.go index ec85cec63c..6ef856584d 100644 --- a/internal/donor/donorpage/register.go +++ b/internal/donor/donorpage/register.go @@ -100,6 +100,7 @@ type OneLoginClient interface { type NotifyClient interface { SendActorSMS(ctx context.Context, lang localize.Lang, to, lpaUID string, sms notify.SMS) error SendEmail(ctx context.Context, lang localize.Lang, to string, email notify.Email) error + SendActorEmail(ctx context.Context, lang localize.Lang, to, lpaUID string, email notify.Email) error } type SessionStore interface { @@ -427,7 +428,7 @@ func Register( handleWithDonor(donor.PathWhatHappensNextRegisteringWithCourtOfProtection, page.None, Guidance(tmpls.Get("what_happens_next_registering_with_court_of_protection.gohtml"))) handleWithDonor(donor.PathAreYouSureYouNoLongerNeedVoucher, page.CanGoBack, - AreYouSureYouNoLongerNeedVoucher(tmpls.Get("are_you_sure_you_no_longer_need_voucher.gohtml"), donorStore)) + AreYouSureYouNoLongerNeedVoucher(tmpls.Get("are_you_sure_you_no_longer_need_voucher.gohtml"), donorStore, notifyClient)) handleWithDonor(donor.PathWeHaveInformedVoucherNoLongerNeeded, page.None, Guidance(tmpls.Get("we_have_informed_voucher_no_longer_needed.gohtml"))) diff --git a/internal/notify/email.go b/internal/notify/email.go index e243b034c8..318f7db5ed 100644 --- a/internal/notify/email.go +++ b/internal/notify/email.go @@ -383,3 +383,24 @@ func (e VoucherHasConfirmedDonorIdentityOnSignedLpaEmail) emailID(isProduction b return "efa0ef78-9e65-4edf-88c8-70d3da7a4b0e" } + +type VoucherInformedTheyAreNoLongerNeededToVouchEmail struct { + VoucherFullName string + DonorFullName string +} + +func (e VoucherInformedTheyAreNoLongerNeededToVouchEmail) emailID(isProduction bool, lang localize.Lang) string { + if isProduction { + if lang.IsCy() { + return "TODO" + } + + return "ca7c6a15-bdf3-47fe-ba01-d811ccdbc30d" + } + + if lang.IsCy() { + return "TODO" + } + + return "00ad14c6-f6df-4d7f-ae44-d7e27f6a9187" +} diff --git a/lang/cy.json b/lang/cy.json index c66f7cf888..200a6d4f78 100644 --- a/lang/cy.json +++ b/lang/cy.json @@ -1486,6 +1486,7 @@ "weWillInformVoucherTheyNoLongerNeedTo": "Welsh {{.VoucherFullName}}", "voucherNoLongerNeeded": "Welsh {{.VoucherFullName}}", "weHaveInformedVoucherTheyNoLongerNeedTo": "Welsh {{.VoucherFullName}}", + "weHaveInformedThePersonYouWantedToVouchForYouTheyAreNoLongerNeeded": "Welsh", "youHaveChosen:prove-own-identity": "Welsh", "youHaveChosen:select-new-voucher": "Welsh", "youHaveChosen:withdraw-lpa": "Welsh", diff --git a/lang/en.json b/lang/en.json index 50f5e27897..d1ce2e34c9 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1378,6 +1378,7 @@ "weWillInformVoucherTheyNoLongerNeedTo": "We will inform {{.VoucherFullName}} they no longer need to confirm your identity. The one-time code allowing them to log into the vouching service will be cancelled.", "voucherNoLongerNeeded": "{{.VoucherFullName}} no longer needed", "weHaveInformedVoucherTheyNoLongerNeedTo": "We have informed {{.VoucherFullName}} they no longer need to confirm your identity.", + "weHaveInformedThePersonYouWantedToVouchForYouTheyAreNoLongerNeeded": "We have informed the person you wanted to vouch for you they are no longer needed", "youHaveChosen:prove-own-identity": "You have chosen to find, replace or get new ID documents and confirm your identity using GOV.UK One Login.", "youHaveChosen:select-new-voucher": "You have chosen to ask someone else to confirm your identity by vouching for you.", "youHaveChosen:withdraw-lpa": "You have told us you no longer want to make this LPA.",