From 0e73251e35fab165f2d7c02c51b32c3e96b61e9d Mon Sep 17 00:00:00 2001 From: Joshua Hawxwell Date: Fri, 3 Jan 2025 10:53:10 +0000 Subject: [PATCH] Send notification to donor when certificate provider signs --- cmd/event-received/lpastore_event_handler.go | 65 +++- .../lpastore_event_handler_test.go | 367 ++++++++++++++++++ internal/notify/email.go | 14 + internal/notify/sms.go | 13 + 4 files changed, 455 insertions(+), 4 deletions(-) diff --git a/cmd/event-received/lpastore_event_handler.go b/cmd/event-received/lpastore_event_handler.go index 8c8753cb7..572022475 100644 --- a/cmd/event-received/lpastore_event_handler.go +++ b/cmd/event-received/lpastore_event_handler.go @@ -47,6 +47,24 @@ func (h *lpastoreEventHandler) Handle(ctx context.Context, factory factory, clou return handleCreate(ctx, factory.DynamoClient(), lpaStoreClient, notifyClient, bundle, v) + case "CERTIFICATE_PROVIDER_SIGN": + lpaStoreClient, err := factory.LpaStoreClient() + if err != nil { + return fmt.Errorf("could not create LpaStoreClient: %w", err) + } + + bundle, err := factory.Bundle() + if err != nil { + return fmt.Errorf("could not load Bundle: %w", err) + } + + notifyClient, err := factory.NotifyClient(ctx) + if err != nil { + return fmt.Errorf("could not create NotifyClient: %w", err) + } + + return handleCertificateProviderSign(ctx, factory.DynamoClient(), lpaStoreClient, notifyClient, bundle, v) + case "REGISTER": lpaStoreClient, err := factory.LpaStoreClient() if err != nil { @@ -78,10 +96,12 @@ func handleCreate(ctx context.Context, client dynamodbClient, lpaStoreClient Lpa localizer := bundle.For(lpa.Donor.ContactLanguagePreference) if lpa.Donor.Channel.IsPaper() { - if err := notifyClient.SendActorSMS(ctx, notify.ToLpaDonor(lpa), v.UID, notify.PaperDonorLpaSubmittedSMS{ - LpaType: localizer.T(lpa.Type.String()), - }); err != nil { - return fmt.Errorf("error sending sms: %w", err) + if lpa.Donor.Mobile != "" { + if err := notifyClient.SendActorSMS(ctx, notify.ToLpaDonor(lpa), v.UID, notify.PaperDonorLpaSubmittedSMS{ + LpaType: localizer.T(lpa.Type.String()), + }); err != nil { + return fmt.Errorf("error sending sms: %w", err) + } } return nil @@ -102,6 +122,43 @@ func handleCreate(ctx context.Context, client dynamodbClient, lpaStoreClient Lpa return nil } +func handleCertificateProviderSign(ctx context.Context, client dynamodbClient, lpaStoreClient LpaStoreClient, notifyClient NotifyClient, bundle Bundle, v lpaUpdatedEvent) error { + lpa, err := lpaStoreClient.Lpa(ctx, v.UID) + if err != nil { + return fmt.Errorf("error getting lpa: %w", err) + } + + localizer := bundle.For(lpa.Donor.ContactLanguagePreference) + + if lpa.Donor.Channel.IsPaper() { + if lpa.Donor.Mobile != "" { + if err := notifyClient.SendActorSMS(ctx, notify.ToLpaDonor(lpa), v.UID, notify.PaperDonorCertificateProvidedSMS{ + CertificateProviderFullName: lpa.CertificateProvider.FullName(), + LpaType: localizer.T(lpa.Type.String()), + }); err != nil { + return fmt.Errorf("error sending sms: %w", err) + } + } + + return nil + } + + donor, err := getDonorByLpaUID(ctx, client, v.UID) + if err != nil { + return fmt.Errorf("error getting donor: %w", err) + } + + if err := notifyClient.SendActorEmail(ctx, notify.ToDonor(donor), v.UID, notify.DigitalDonorCertificateProvidedEmail{ + Greeting: notifyClient.EmailGreeting(lpa), + CertificateProviderFullName: lpa.CertificateProvider.FullName(), + LpaType: localizer.T(lpa.Type.String()), + }); err != nil { + return fmt.Errorf("error sending email: %w", err) + } + + return nil +} + func handleRegister(ctx context.Context, client dynamodbClient, lpaStoreClient LpaStoreClient, eventClient EventClient, v lpaUpdatedEvent) error { lpa, err := lpaStoreClient.Lpa(ctx, v.UID) if err != nil { diff --git a/cmd/event-received/lpastore_event_handler_test.go b/cmd/event-received/lpastore_event_handler_test.go index aa152c986..8325fb12a 100644 --- a/cmd/event-received/lpastore_event_handler_test.go +++ b/cmd/event-received/lpastore_event_handler_test.go @@ -148,6 +148,7 @@ func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenPaperDonor(t *testing.T) Donor: lpadata.Donor{ Channel: lpadata.ChannelPaper, ContactLanguagePreference: localize.Cy, + Mobile: "07", }, } @@ -180,12 +181,49 @@ func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenPaperDonor(t *testing.T) assert.Nil(t, err) } +func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenPaperDonorWithNoMobile(t *testing.T) { + v := &events.CloudWatchEvent{ + DetailType: "lpa-updated", + Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","changeType":"CREATE"}`), + } + + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + Channel: lpadata.ChannelPaper, + ContactLanguagePreference: localize.Cy, + }, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(ctx, "M-1111-2222-3333"). + Return(lpa, nil) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(localize.Cy). + Return(&localize.Localizer{}) + + factory := newMockFactory(t) + factory.EXPECT().DynamoClient().Return(nil) + factory.EXPECT().LpaStoreClient().Return(lpaStoreClient, nil) + factory.EXPECT().NotifyClient(ctx).Return(nil, nil) + factory.EXPECT().Bundle().Return(bundle, nil) + + handler := &lpastoreEventHandler{} + + err := handler.Handle(ctx, factory, v) + assert.Nil(t, err) +} + func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenPaperDonorAndNotifyErrors(t *testing.T) { lpa := &lpadata.Lpa{ Type: lpadata.LpaTypePersonalWelfare, Donor: lpadata.Donor{ Channel: lpadata.ChannelPaper, ContactLanguagePreference: localize.Cy, + Mobile: "07", }, } @@ -316,6 +354,335 @@ func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenNotifyErrors(t *testing.T assert.ErrorIs(t, err, expectedError) } +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSign(t *testing.T) { + v := &events.CloudWatchEvent{ + DetailType: "lpa-updated", + Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","changeType":"CERTIFICATE_PROVIDER_SIGN"}`), + } + + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + ContactLanguagePreference: localize.Cy, + }, + CertificateProvider: lpadata.CertificateProvider{ + FirstNames: "a", + LastName: "b", + }, + } + + donor := &donordata.Provided{ + PK: dynamo.LpaKey("an-lpa"), + Correspondent: donordata.Correspondent{Email: "hey@example.com"}, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(ctx, "M-1111-2222-3333"). + Return(lpa, nil) + + client := newMockDynamodbClient(t) + client.EXPECT(). + OneByUID(ctx, "M-1111-2222-3333", mock.Anything). + Return(nil). + SetData(&donordata.Provided{ + PK: dynamo.LpaKey("an-lpa"), + SK: dynamo.LpaOwnerKey(dynamo.DonorKey("a-donor")), + }) + client.EXPECT(). + One(ctx, dynamo.LpaKey("an-lpa"), dynamo.DonorKey("a-donor"), mock.Anything). + Return(nil). + SetData(donor) + + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + EmailGreeting(lpa). + Return("hello") + notifyClient.EXPECT(). + SendActorEmail(ctx, notify.ToDonor(donor), "M-1111-2222-3333", notify.DigitalDonorCertificateProvidedEmail{ + Greeting: "hello", + CertificateProviderFullName: "a b", + LpaType: "personal-welfare", + }). + Return(nil) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(localize.Cy). + Return(&localize.Localizer{}) + + factory := newMockFactory(t) + factory.EXPECT().DynamoClient().Return(client) + factory.EXPECT().LpaStoreClient().Return(lpaStoreClient, nil) + factory.EXPECT().NotifyClient(ctx).Return(notifyClient, nil) + factory.EXPECT().Bundle().Return(bundle, nil) + + handler := &lpastoreEventHandler{} + + err := handler.Handle(ctx, factory, v) + assert.Nil(t, err) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenFactoryErrors(t *testing.T) { + testcases := map[string]func(*mockFactory){ + "LpaStoreClient": func(factory *mockFactory) { + factory.EXPECT().LpaStoreClient().Return(nil, expectedError) + }, + "Bundle": func(factory *mockFactory) { + factory.EXPECT().LpaStoreClient().Return(nil, nil) + factory.EXPECT().Bundle().Return(nil, expectedError) + }, + "NotifyClient": func(factory *mockFactory) { + factory.EXPECT().LpaStoreClient().Return(nil, nil) + factory.EXPECT().Bundle().Return(nil, nil) + factory.EXPECT().NotifyClient(ctx).Return(nil, expectedError) + }, + } + + for name, setupFactory := range testcases { + t.Run(name, func(t *testing.T) { + v := &events.CloudWatchEvent{ + DetailType: "lpa-updated", + Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","changeType":"CERTIFICATE_PROVIDER_SIGN"}`), + } + + factory := newMockFactory(t) + setupFactory(factory) + + handler := &lpastoreEventHandler{} + + err := handler.Handle(ctx, factory, v) + assert.ErrorIs(t, err, expectedError) + }) + } +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenPaperDonor(t *testing.T) { + v := &events.CloudWatchEvent{ + DetailType: "lpa-updated", + Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","changeType":"CERTIFICATE_PROVIDER_SIGN"}`), + } + + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + Channel: lpadata.ChannelPaper, + ContactLanguagePreference: localize.Cy, + Mobile: "07", + }, + CertificateProvider: lpadata.CertificateProvider{ + FirstNames: "a", + LastName: "b", + }, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(ctx, "M-1111-2222-3333"). + Return(lpa, nil) + + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + SendActorSMS(ctx, notify.ToLpaDonor(lpa), "M-1111-2222-3333", notify.PaperDonorCertificateProvidedSMS{ + LpaType: "personal-welfare", + CertificateProviderFullName: "a b", + }). + Return(nil) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(localize.Cy). + Return(&localize.Localizer{}) + + factory := newMockFactory(t) + factory.EXPECT().DynamoClient().Return(nil) + factory.EXPECT().LpaStoreClient().Return(lpaStoreClient, nil) + factory.EXPECT().NotifyClient(ctx).Return(notifyClient, nil) + factory.EXPECT().Bundle().Return(bundle, nil) + + handler := &lpastoreEventHandler{} + + err := handler.Handle(ctx, factory, v) + assert.Nil(t, err) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenPaperDonorWithNoMobile(t *testing.T) { + v := &events.CloudWatchEvent{ + DetailType: "lpa-updated", + Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","changeType":"CERTIFICATE_PROVIDER_SIGN"}`), + } + + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + Channel: lpadata.ChannelPaper, + ContactLanguagePreference: localize.Cy, + }, + CertificateProvider: lpadata.CertificateProvider{ + FirstNames: "a", + LastName: "b", + }, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(ctx, "M-1111-2222-3333"). + Return(lpa, nil) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(localize.Cy). + Return(&localize.Localizer{}) + + factory := newMockFactory(t) + factory.EXPECT().DynamoClient().Return(nil) + factory.EXPECT().LpaStoreClient().Return(lpaStoreClient, nil) + factory.EXPECT().NotifyClient(ctx).Return(nil, nil) + factory.EXPECT().Bundle().Return(bundle, nil) + + handler := &lpastoreEventHandler{} + + err := handler.Handle(ctx, factory, v) + assert.Nil(t, err) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenPaperDonorAndNotifyErrors(t *testing.T) { + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + Channel: lpadata.ChannelPaper, + ContactLanguagePreference: localize.Cy, + Mobile: "07", + }, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(mock.Anything, mock.Anything). + Return(lpa, nil) + + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + SendActorSMS(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(mock.Anything). + Return(&localize.Localizer{}) + + err := handleCertificateProviderSign(ctx, nil, lpaStoreClient, notifyClient, bundle, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenLpaStoreErrors(t *testing.T) { + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(mock.Anything, mock.Anything). + Return(nil, expectedError) + + err := handleCertificateProviderSign(ctx, nil, lpaStoreClient, nil, nil, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenDonorStoreErrors(t *testing.T) { + testcases := map[string]func(*mockDynamodbClient){ + "first": func(client *mockDynamodbClient) { + client.EXPECT(). + OneByUID(mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + }, + "second": func(client *mockDynamodbClient) { + client.EXPECT(). + OneByUID(mock.Anything, mock.Anything, mock.Anything). + Return(nil). + SetData(&donordata.Provided{ + PK: dynamo.LpaKey("an-lpa"), + SK: dynamo.LpaOwnerKey(dynamo.DonorKey("a-donor")), + }) + client.EXPECT(). + One(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + }, + } + + for name, setupDynamodbClient := range testcases { + t.Run(name, func(t *testing.T) { + + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + ContactLanguagePreference: localize.Cy, + }, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(mock.Anything, mock.Anything). + Return(lpa, nil) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(mock.Anything). + Return(&localize.Localizer{}) + + client := newMockDynamodbClient(t) + setupDynamodbClient(client) + + err := handleCertificateProviderSign(ctx, client, lpaStoreClient, nil, bundle, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) + }) + } +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCertificateProviderSignWhenNotifyErrors(t *testing.T) { + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + ContactLanguagePreference: localize.Cy, + }, + } + + donor := &donordata.Provided{ + PK: dynamo.LpaKey("an-lpa"), + Correspondent: donordata.Correspondent{Email: "hey@example.com"}, + } + + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(mock.Anything, mock.Anything). + Return(lpa, nil) + + client := newMockDynamodbClient(t) + client.EXPECT(). + OneByUID(mock.Anything, mock.Anything, mock.Anything). + Return(nil). + SetData(&donordata.Provided{ + PK: dynamo.LpaKey("an-lpa"), + SK: dynamo.LpaOwnerKey(dynamo.DonorKey("a-donor")), + }) + client.EXPECT(). + One(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil). + SetData(donor) + + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + EmailGreeting(mock.Anything). + Return("hello") + notifyClient.EXPECT(). + SendActorEmail(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + + bundle := newMockBundle(t) + bundle.EXPECT(). + For(mock.Anything). + Return(&localize.Localizer{}) + + err := handleCertificateProviderSign(ctx, client, lpaStoreClient, notifyClient, bundle, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) +} + func TestLpaStoreEventHandlerHandleLpaUpdatedRegister(t *testing.T) { v := &events.CloudWatchEvent{ DetailType: "lpa-updated", diff --git a/internal/notify/email.go b/internal/notify/email.go index 1c321bf11..b03e26cbb 100644 --- a/internal/notify/email.go +++ b/internal/notify/email.go @@ -568,3 +568,17 @@ func (e DigitalDonorLpaSubmittedEmail) emailID(isProduction bool, _ localize.Lan return "cd02bc22-41be-4e2c-94c2-055d6cba6466" } + +type DigitalDonorCertificateProvidedEmail struct { + Greeting string + CertificateProviderFullName string + LpaType string +} + +func (e DigitalDonorCertificateProvidedEmail) emailID(isProduction bool, _ localize.Lang) string { + if isProduction { + return "9db20b35-8728-434b-8090-52cf704bc5a9" + } + + return "9baaeb36-f1c6-44ba-840d-14914c18a455" +} diff --git a/internal/notify/sms.go b/internal/notify/sms.go index a75e3ddf6..b7d79363a 100644 --- a/internal/notify/sms.go +++ b/internal/notify/sms.go @@ -155,3 +155,16 @@ func (e PaperDonorLpaSubmittedSMS) smsID(isProduction bool, lang localize.Lang) return "e7476d24-6d37-4137-b4a0-de14d3a977ed" } + +type PaperDonorCertificateProvidedSMS struct { + CertificateProviderFullName string + LpaType string +} + +func (e PaperDonorCertificateProvidedSMS) smsID(isProduction bool, lang localize.Lang) string { + if isProduction { + return "6b3d9a6c-5103-4c16-8c09-6ebaaec58f93" + } + + return "ecdfef3e-cdc0-4393-add5-571bd9cd5c9f" +}