diff --git a/cmd/event-received/factory.go b/cmd/event-received/factory.go index 822076ec8..be5c1a223 100644 --- a/cmd/event-received/factory.go +++ b/cmd/event-received/factory.go @@ -74,7 +74,7 @@ type Factory struct { // previously constructed values appData *appcontext.Data - bundle *localize.Bundle + bundle Bundle lambdaClient LambdaClient secretsClient SecretsClient shareCodeSender ShareCodeSender @@ -82,6 +82,7 @@ type Factory struct { uidStore UidStore uidClient UidClient scheduledStore ScheduledStore + notifyClient NotifyClient } func (f *Factory) Now() func() time.Time { @@ -96,7 +97,7 @@ func (f *Factory) UuidString() func() string { return f.uuidString } -func (f *Factory) Bundle() (*localize.Bundle, error) { +func (f *Factory) Bundle() (Bundle, error) { if f.bundle == nil { bundle, err := localize.NewBundle("./lang/en.json", "./lang/cy.json") if err != nil { @@ -146,22 +147,7 @@ func (f *Factory) SecretsClient() (SecretsClient, error) { func (f *Factory) ShareCodeSender(ctx context.Context) (ShareCodeSender, error) { if f.shareCodeSender == nil { - bundle, err := f.Bundle() - if err != nil { - return nil, err - } - - secretsClient, err := f.SecretsClient() - if err != nil { - return nil, err - } - - notifyApiKey, err := secretsClient.Secret(ctx, secrets.GovUkNotify) - if err != nil { - return nil, fmt.Errorf("failed to get notify API secret: %w", err) - } - - notifyClient, err := notify.New(f.logger, f.notifyIsProduction, f.notifyBaseURL, notifyApiKey, f.httpClient, event.NewClient(f.cfg, f.eventBusName), bundle) + notifyClient, err := f.NotifyClient(ctx) if err != nil { return nil, err } @@ -221,3 +207,31 @@ func (f *Factory) ScheduledStore() ScheduledStore { return f.scheduledStore } + +func (f *Factory) NotifyClient(ctx context.Context) (NotifyClient, error) { + if f.notifyClient == nil { + bundle, err := f.Bundle() + if err != nil { + return nil, err + } + + secretsClient, err := f.SecretsClient() + if err != nil { + return nil, err + } + + notifyApiKey, err := secretsClient.Secret(ctx, secrets.GovUkNotify) + if err != nil { + return nil, fmt.Errorf("failed to get notify API secret: %w", err) + } + + notifyClient, err := notify.New(f.logger, f.notifyIsProduction, f.notifyBaseURL, notifyApiKey, f.httpClient, event.NewClient(f.cfg, f.eventBusName), bundle) + if err != nil { + return nil, err + } + + f.notifyClient = notifyClient + } + + return f.notifyClient, nil +} diff --git a/cmd/event-received/factory_test.go b/cmd/event-received/factory_test.go index 573eb0b8c..132c70dfb 100644 --- a/cmd/event-received/factory_test.go +++ b/cmd/event-received/factory_test.go @@ -11,26 +11,26 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNow(t *testing.T) { +func TestFactoryNow(t *testing.T) { factory := &Factory{now: testNowFn} assert.Equal(t, testNow, factory.Now()()) } -func TestDynamoClient(t *testing.T) { +func TestFactoryDynamoClient(t *testing.T) { dynamoClient := newMockDynamodbClient(t) factory := &Factory{dynamoClient: dynamoClient} assert.Equal(t, dynamoClient, factory.DynamoClient()) } -func TestUuidString(t *testing.T) { +func TestFactoryUuidString(t *testing.T) { factory := &Factory{uuidString: testUuidStringFn} assert.Equal(t, testUuidString, factory.UuidString()()) } -func TestAppData(t *testing.T) { +func TestFactoryAppData(t *testing.T) { factory := &Factory{} appData, err := factory.AppData() @@ -38,7 +38,7 @@ func TestAppData(t *testing.T) { assert.Equal(t, appcontext.Data{}, appData) } -func TestAppDataWhenSet(t *testing.T) { +func TestFactoryAppDataWhenSet(t *testing.T) { expected := appcontext.Data{Page: "hi"} factory := &Factory{appData: &expected} @@ -47,14 +47,14 @@ func TestAppDataWhenSet(t *testing.T) { assert.Equal(t, expected, appData) } -func TestLambdaClient(t *testing.T) { +func TestFactoryLambdaClient(t *testing.T) { factory := &Factory{} client := factory.LambdaClient() assert.NotNil(t, client) } -func TestLambdaClientWhenSet(t *testing.T) { +func TestFactoryLambdaClientWhenSet(t *testing.T) { expected := newMockLambdaClient(t) factory := &Factory{lambdaClient: expected} @@ -63,7 +63,7 @@ func TestLambdaClientWhenSet(t *testing.T) { assert.Equal(t, expected, client) } -func TestSecretsClient(t *testing.T) { +func TestFactorySecretsClient(t *testing.T) { factory := &Factory{} client, err := factory.SecretsClient() @@ -71,7 +71,7 @@ func TestSecretsClient(t *testing.T) { assert.NotNil(t, client) } -func TestSecretsClientWhenSet(t *testing.T) { +func TestFactorySecretsClientWhenSet(t *testing.T) { expected := newMockSecretsClient(t) factory := &Factory{secretsClient: expected} @@ -81,7 +81,7 @@ func TestSecretsClientWhenSet(t *testing.T) { assert.Equal(t, expected, client) } -func TestShareCodeSender(t *testing.T) { +func TestFactoryShareCodeSender(t *testing.T) { ctx := context.Background() secretsClient := newMockSecretsClient(t) @@ -96,7 +96,7 @@ func TestShareCodeSender(t *testing.T) { assert.NotNil(t, sender) } -func TestShareCodeSenderWhenSet(t *testing.T) { +func TestFactoryShareCodeSenderWhenSet(t *testing.T) { ctx := context.Background() expected := newMockShareCodeSender(t) @@ -108,7 +108,7 @@ func TestShareCodeSenderWhenSet(t *testing.T) { assert.Equal(t, expected, sender) } -func TestShareCodeSenderWhenBundleError(t *testing.T) { +func TestFactoryShareCodeSenderWhenBundleError(t *testing.T) { ctx := context.Background() factory := &Factory{} @@ -117,7 +117,7 @@ func TestShareCodeSenderWhenBundleError(t *testing.T) { assert.ErrorIs(t, err, os.ErrNotExist) } -func TestShareCodeSenderWhenSecretsClientError(t *testing.T) { +func TestFactoryShareCodeSenderWhenSecretsClientError(t *testing.T) { ctx := context.Background() secretsClient := newMockSecretsClient(t) @@ -131,7 +131,7 @@ func TestShareCodeSenderWhenSecretsClientError(t *testing.T) { assert.ErrorIs(t, err, expectedError) } -func TestShareCodeSenderWhenNotifyClientError(t *testing.T) { +func TestFactoryShareCodeSenderWhenNotifyClientError(t *testing.T) { ctx := context.Background() secretsClient := newMockSecretsClient(t) @@ -145,7 +145,7 @@ func TestShareCodeSenderWhenNotifyClientError(t *testing.T) { assert.NotNil(t, err) } -func TestLpaStoreClient(t *testing.T) { +func TestFactoryLpaStoreClient(t *testing.T) { secretsClient := newMockSecretsClient(t) factory := &Factory{secretsClient: secretsClient} @@ -155,7 +155,7 @@ func TestLpaStoreClient(t *testing.T) { assert.NotNil(t, client) } -func TestLpaStoreClientWhenSet(t *testing.T) { +func TestFactoryLpaStoreClientWhenSet(t *testing.T) { expected := newMockLpaStoreClient(t) factory := &Factory{lpaStoreClient: expected} @@ -165,7 +165,7 @@ func TestLpaStoreClientWhenSet(t *testing.T) { assert.Equal(t, expected, client) } -func TestUidStore(t *testing.T) { +func TestFactoryUidStore(t *testing.T) { factory := &Factory{} store, err := factory.UidStore() @@ -173,7 +173,7 @@ func TestUidStore(t *testing.T) { assert.NotNil(t, store) } -func TestUidStoreWhenSet(t *testing.T) { +func TestFactoryUidStoreWhenSet(t *testing.T) { expected := newMockUidStore(t) factory := &Factory{uidStore: expected} @@ -183,14 +183,14 @@ func TestUidStoreWhenSet(t *testing.T) { assert.Equal(t, expected, store) } -func TestUidClient(t *testing.T) { +func TestFactoryUidClient(t *testing.T) { factory := &Factory{} client := factory.UidClient() assert.NotNil(t, client) } -func TestUidClientWhenSet(t *testing.T) { +func TestFactoryUidClientWhenSet(t *testing.T) { expected := newMockUidClient(t) factory := &Factory{uidClient: expected} @@ -198,14 +198,14 @@ func TestUidClientWhenSet(t *testing.T) { assert.Equal(t, expected, client) } -func TestEventClient(t *testing.T) { +func TestFactoryEventClient(t *testing.T) { factory := &Factory{} client := factory.EventClient() assert.NotNil(t, client) } -func TestEventClientWhenSet(t *testing.T) { +func TestFactoryEventClientWhenSet(t *testing.T) { expected := newMockEventClient(t) factory := &Factory{eventClient: expected} @@ -213,17 +213,34 @@ func TestEventClientWhenSet(t *testing.T) { assert.Equal(t, expected, client) } -func TestScheduledStore(t *testing.T) { +func TestFactoryScheduledStore(t *testing.T) { factory := &Factory{} client := factory.ScheduledStore() assert.NotNil(t, client) } -func TestScheduledStoreWhenSet(t *testing.T) { +func TestFactoryScheduledStoreWhenSet(t *testing.T) { expected := newMockScheduledStore(t) factory := &Factory{scheduledStore: expected} client := factory.ScheduledStore() assert.Equal(t, expected, client) } + +func TestFactoryBundle(t *testing.T) { + factory := &Factory{} + + bundle, err := factory.Bundle() + assert.Nil(t, bundle) + assert.Error(t, err) +} + +func TestFactoryBundleWhenSet(t *testing.T) { + expected := newMockBundle(t) + factory := &Factory{bundle: expected} + + bundle, err := factory.Bundle() + assert.Equal(t, expected, bundle) + assert.Nil(t, err) +} diff --git a/cmd/event-received/lpastore_event_handler.go b/cmd/event-received/lpastore_event_handler.go index f02ec6604..8c8753cb7 100644 --- a/cmd/event-received/lpastore_event_handler.go +++ b/cmd/event-received/lpastore_event_handler.go @@ -11,6 +11,7 @@ import ( "github.com/ministryofjustice/opg-modernising-lpa/internal/dashboard/dashboarddata" "github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo" "github.com/ministryofjustice/opg-modernising-lpa/internal/event" + "github.com/ministryofjustice/opg-modernising-lpa/internal/notify" ) type lpastoreEventHandler struct{} @@ -28,6 +29,24 @@ func (h *lpastoreEventHandler) Handle(ctx context.Context, factory factory, clou } switch v.ChangeType { + case "CREATE": + 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 handleCreate(ctx, factory.DynamoClient(), lpaStoreClient, notifyClient, bundle, v) + case "REGISTER": lpaStoreClient, err := factory.LpaStoreClient() if err != nil { @@ -50,6 +69,39 @@ func (h *lpastoreEventHandler) Handle(ctx context.Context, factory factory, clou return fmt.Errorf("unknown lpastore event") } +func handleCreate(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 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 + } + + 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.DigitalDonorLpaSubmittedEmail{ + Greeting: notifyClient.EmailGreeting(lpa), + 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 e4945a2ce..aa152c986 100644 --- a/cmd/event-received/lpastore_event_handler_test.go +++ b/cmd/event-received/lpastore_event_handler_test.go @@ -13,7 +13,9 @@ import ( "github.com/ministryofjustice/opg-modernising-lpa/internal/donor/donordata" "github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo" "github.com/ministryofjustice/opg-modernising-lpa/internal/event" + "github.com/ministryofjustice/opg-modernising-lpa/internal/localize" "github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore/lpadata" + "github.com/ministryofjustice/opg-modernising-lpa/internal/notify" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -37,6 +39,283 @@ func TestLpaStoreEventHandlerHandleLpaUpdatedWhenChangeTypeNotExpected(t *testin assert.Nil(t, err) } +func TestLpaStoreEventHandlerHandleLpaUpdatedCreate(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{ + ContactLanguagePreference: localize.Cy, + }, + } + + 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.DigitalDonorLpaSubmittedEmail{ + Greeting: "hello", + 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 TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenFactoryErrors(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":"CREATE"}`), + } + + factory := newMockFactory(t) + setupFactory(factory) + + handler := &lpastoreEventHandler{} + + err := handler.Handle(ctx, factory, v) + assert.ErrorIs(t, err, expectedError) + }) + } +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenPaperDonor(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) + + notifyClient := newMockNotifyClient(t) + notifyClient.EXPECT(). + SendActorSMS(ctx, notify.ToLpaDonor(lpa), "M-1111-2222-3333", notify.PaperDonorLpaSubmittedSMS{ + LpaType: "personal-welfare", + }). + 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 TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenPaperDonorAndNotifyErrors(t *testing.T) { + lpa := &lpadata.Lpa{ + Type: lpadata.LpaTypePersonalWelfare, + Donor: lpadata.Donor{ + Channel: lpadata.ChannelPaper, + ContactLanguagePreference: localize.Cy, + }, + } + + 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 := handleCreate(ctx, nil, lpaStoreClient, notifyClient, bundle, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenLpaStoreErrors(t *testing.T) { + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(mock.Anything, mock.Anything). + Return(nil, expectedError) + + err := handleCreate(ctx, nil, lpaStoreClient, nil, nil, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenDonorStoreErrors(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 := handleCreate(ctx, client, lpaStoreClient, nil, bundle, lpaUpdatedEvent{}) + assert.ErrorIs(t, err, expectedError) + }) + } +} + +func TestLpaStoreEventHandlerHandleLpaUpdatedCreateWhenNotifyErrors(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 := handleCreate(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/cmd/event-received/main.go b/cmd/event-received/main.go index 22647ed6d..03f05fbae 100644 --- a/cmd/event-received/main.go +++ b/cmd/event-received/main.go @@ -22,6 +22,9 @@ import ( "github.com/ministryofjustice/opg-modernising-lpa/internal/document" "github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo" "github.com/ministryofjustice/opg-modernising-lpa/internal/event" + "github.com/ministryofjustice/opg-modernising-lpa/internal/localize" + "github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore/lpadata" + "github.com/ministryofjustice/opg-modernising-lpa/internal/notify" "github.com/ministryofjustice/opg-modernising-lpa/internal/pay" "github.com/ministryofjustice/opg-modernising-lpa/internal/random" "github.com/ministryofjustice/opg-modernising-lpa/internal/s3" @@ -59,11 +62,13 @@ var ( type factory interface { Now() func() time.Time + Bundle() (Bundle, error) DynamoClient() dynamodbClient UuidString() func() string AppData() (appcontext.Data, error) ShareCodeSender(ctx context.Context) (ShareCodeSender, error) LpaStoreClient() (LpaStoreClient, error) + NotifyClient(ctx context.Context) (NotifyClient, error) UidStore() (UidStore, error) UidClient() UidClient EventClient() EventClient @@ -125,6 +130,16 @@ type ScheduledStore interface { DeleteAllByUID(ctx context.Context, uid string) error } +type NotifyClient interface { + EmailGreeting(lpa *lpadata.Lpa) string + SendActorEmail(context context.Context, to notify.ToEmail, lpaUID string, email notify.Email) error + SendActorSMS(context context.Context, to notify.ToMobile, lpaUID string, sms notify.SMS) error +} + +type Bundle interface { + For(lang localize.Lang) *localize.Localizer +} + type Event struct { S3Event *events.S3Event SQSEvent *events.SQSEvent diff --git a/cmd/event-received/mock_Bundle_test.go b/cmd/event-received/mock_Bundle_test.go new file mode 100644 index 000000000..64c1738e3 --- /dev/null +++ b/cmd/event-received/mock_Bundle_test.go @@ -0,0 +1,83 @@ +// Code generated by mockery. DO NOT EDIT. + +package main + +import ( + localize "github.com/ministryofjustice/opg-modernising-lpa/internal/localize" + mock "github.com/stretchr/testify/mock" +) + +// mockBundle is an autogenerated mock type for the Bundle type +type mockBundle struct { + mock.Mock +} + +type mockBundle_Expecter struct { + mock *mock.Mock +} + +func (_m *mockBundle) EXPECT() *mockBundle_Expecter { + return &mockBundle_Expecter{mock: &_m.Mock} +} + +// For provides a mock function with given fields: lang +func (_m *mockBundle) For(lang localize.Lang) *localize.Localizer { + ret := _m.Called(lang) + + if len(ret) == 0 { + panic("no return value specified for For") + } + + var r0 *localize.Localizer + if rf, ok := ret.Get(0).(func(localize.Lang) *localize.Localizer); ok { + r0 = rf(lang) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*localize.Localizer) + } + } + + return r0 +} + +// mockBundle_For_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'For' +type mockBundle_For_Call struct { + *mock.Call +} + +// For is a helper method to define mock.On call +// - lang localize.Lang +func (_e *mockBundle_Expecter) For(lang interface{}) *mockBundle_For_Call { + return &mockBundle_For_Call{Call: _e.mock.On("For", lang)} +} + +func (_c *mockBundle_For_Call) Run(run func(lang localize.Lang)) *mockBundle_For_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(localize.Lang)) + }) + return _c +} + +func (_c *mockBundle_For_Call) Return(_a0 *localize.Localizer) *mockBundle_For_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockBundle_For_Call) RunAndReturn(run func(localize.Lang) *localize.Localizer) *mockBundle_For_Call { + _c.Call.Return(run) + return _c +} + +// newMockBundle creates a new instance of mockBundle. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockBundle(t interface { + mock.TestingT + Cleanup(func()) +}) *mockBundle { + mock := &mockBundle{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/event-received/mock_NotifyClient_test.go b/cmd/event-received/mock_NotifyClient_test.go new file mode 100644 index 000000000..6defd7455 --- /dev/null +++ b/cmd/event-received/mock_NotifyClient_test.go @@ -0,0 +1,183 @@ +// Code generated by mockery. DO NOT EDIT. + +package main + +import ( + context "context" + + lpadata "github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore/lpadata" + mock "github.com/stretchr/testify/mock" + + notify "github.com/ministryofjustice/opg-modernising-lpa/internal/notify" +) + +// mockNotifyClient is an autogenerated mock type for the NotifyClient type +type mockNotifyClient struct { + mock.Mock +} + +type mockNotifyClient_Expecter struct { + mock *mock.Mock +} + +func (_m *mockNotifyClient) EXPECT() *mockNotifyClient_Expecter { + return &mockNotifyClient_Expecter{mock: &_m.Mock} +} + +// EmailGreeting provides a mock function with given fields: lpa +func (_m *mockNotifyClient) EmailGreeting(lpa *lpadata.Lpa) string { + ret := _m.Called(lpa) + + if len(ret) == 0 { + panic("no return value specified for EmailGreeting") + } + + var r0 string + if rf, ok := ret.Get(0).(func(*lpadata.Lpa) string); ok { + r0 = rf(lpa) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// mockNotifyClient_EmailGreeting_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EmailGreeting' +type mockNotifyClient_EmailGreeting_Call struct { + *mock.Call +} + +// EmailGreeting is a helper method to define mock.On call +// - lpa *lpadata.Lpa +func (_e *mockNotifyClient_Expecter) EmailGreeting(lpa interface{}) *mockNotifyClient_EmailGreeting_Call { + return &mockNotifyClient_EmailGreeting_Call{Call: _e.mock.On("EmailGreeting", lpa)} +} + +func (_c *mockNotifyClient_EmailGreeting_Call) Run(run func(lpa *lpadata.Lpa)) *mockNotifyClient_EmailGreeting_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*lpadata.Lpa)) + }) + return _c +} + +func (_c *mockNotifyClient_EmailGreeting_Call) Return(_a0 string) *mockNotifyClient_EmailGreeting_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockNotifyClient_EmailGreeting_Call) RunAndReturn(run func(*lpadata.Lpa) string) *mockNotifyClient_EmailGreeting_Call { + _c.Call.Return(run) + return _c +} + +// SendActorEmail provides a mock function with given fields: _a0, to, lpaUID, email +func (_m *mockNotifyClient) SendActorEmail(_a0 context.Context, to notify.ToEmail, lpaUID string, email notify.Email) error { + ret := _m.Called(_a0, 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, notify.ToEmail, string, notify.Email) error); ok { + r0 = rf(_a0, 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 +// - _a0 context.Context +// - to notify.ToEmail +// - lpaUID string +// - email notify.Email +func (_e *mockNotifyClient_Expecter) SendActorEmail(_a0 interface{}, to interface{}, lpaUID interface{}, email interface{}) *mockNotifyClient_SendActorEmail_Call { + return &mockNotifyClient_SendActorEmail_Call{Call: _e.mock.On("SendActorEmail", _a0, to, lpaUID, email)} +} + +func (_c *mockNotifyClient_SendActorEmail_Call) Run(run func(_a0 context.Context, to notify.ToEmail, lpaUID string, email notify.Email)) *mockNotifyClient_SendActorEmail_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(notify.ToEmail), args[2].(string), args[3].(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, notify.ToEmail, string, notify.Email) error) *mockNotifyClient_SendActorEmail_Call { + _c.Call.Return(run) + return _c +} + +// SendActorSMS provides a mock function with given fields: _a0, to, lpaUID, sms +func (_m *mockNotifyClient) SendActorSMS(_a0 context.Context, to notify.ToMobile, lpaUID string, sms notify.SMS) error { + ret := _m.Called(_a0, to, lpaUID, sms) + + if len(ret) == 0 { + panic("no return value specified for SendActorSMS") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, notify.ToMobile, string, notify.SMS) error); ok { + r0 = rf(_a0, to, lpaUID, sms) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockNotifyClient_SendActorSMS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendActorSMS' +type mockNotifyClient_SendActorSMS_Call struct { + *mock.Call +} + +// SendActorSMS is a helper method to define mock.On call +// - _a0 context.Context +// - to notify.ToMobile +// - lpaUID string +// - sms notify.SMS +func (_e *mockNotifyClient_Expecter) SendActorSMS(_a0 interface{}, to interface{}, lpaUID interface{}, sms interface{}) *mockNotifyClient_SendActorSMS_Call { + return &mockNotifyClient_SendActorSMS_Call{Call: _e.mock.On("SendActorSMS", _a0, to, lpaUID, sms)} +} + +func (_c *mockNotifyClient_SendActorSMS_Call) Run(run func(_a0 context.Context, to notify.ToMobile, lpaUID string, sms notify.SMS)) *mockNotifyClient_SendActorSMS_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(notify.ToMobile), args[2].(string), args[3].(notify.SMS)) + }) + return _c +} + +func (_c *mockNotifyClient_SendActorSMS_Call) Return(_a0 error) *mockNotifyClient_SendActorSMS_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockNotifyClient_SendActorSMS_Call) RunAndReturn(run func(context.Context, notify.ToMobile, string, notify.SMS) error) *mockNotifyClient_SendActorSMS_Call { + _c.Call.Return(run) + return _c +} + +// newMockNotifyClient creates a new instance of mockNotifyClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockNotifyClient(t interface { + mock.TestingT + Cleanup(func()) +}) *mockNotifyClient { + mock := &mockNotifyClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/event-received/mock_factory_test.go b/cmd/event-received/mock_factory_test.go index 55a242cb8..01f0b2c55 100644 --- a/cmd/event-received/mock_factory_test.go +++ b/cmd/event-received/mock_factory_test.go @@ -80,6 +80,63 @@ func (_c *mockFactory_AppData_Call) RunAndReturn(run func() (appcontext.Data, er return _c } +// Bundle provides a mock function with given fields: +func (_m *mockFactory) Bundle() (Bundle, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Bundle") + } + + var r0 Bundle + var r1 error + if rf, ok := ret.Get(0).(func() (Bundle, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() Bundle); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(Bundle) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockFactory_Bundle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bundle' +type mockFactory_Bundle_Call struct { + *mock.Call +} + +// Bundle is a helper method to define mock.On call +func (_e *mockFactory_Expecter) Bundle() *mockFactory_Bundle_Call { + return &mockFactory_Bundle_Call{Call: _e.mock.On("Bundle")} +} + +func (_c *mockFactory_Bundle_Call) Run(run func()) *mockFactory_Bundle_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *mockFactory_Bundle_Call) Return(_a0 Bundle, _a1 error) *mockFactory_Bundle_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockFactory_Bundle_Call) RunAndReturn(run func() (Bundle, error)) *mockFactory_Bundle_Call { + _c.Call.Return(run) + return _c +} + // DynamoClient provides a mock function with given fields: func (_m *mockFactory) DynamoClient() dynamodbClient { ret := _m.Called() @@ -231,6 +288,64 @@ func (_c *mockFactory_LpaStoreClient_Call) RunAndReturn(run func() (LpaStoreClie return _c } +// NotifyClient provides a mock function with given fields: ctx +func (_m *mockFactory) NotifyClient(ctx context.Context) (NotifyClient, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for NotifyClient") + } + + var r0 NotifyClient + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (NotifyClient, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) NotifyClient); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(NotifyClient) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockFactory_NotifyClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NotifyClient' +type mockFactory_NotifyClient_Call struct { + *mock.Call +} + +// NotifyClient is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockFactory_Expecter) NotifyClient(ctx interface{}) *mockFactory_NotifyClient_Call { + return &mockFactory_NotifyClient_Call{Call: _e.mock.On("NotifyClient", ctx)} +} + +func (_c *mockFactory_NotifyClient_Call) Run(run func(ctx context.Context)) *mockFactory_NotifyClient_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockFactory_NotifyClient_Call) Return(_a0 NotifyClient, _a1 error) *mockFactory_NotifyClient_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockFactory_NotifyClient_Call) RunAndReturn(run func(context.Context) (NotifyClient, error)) *mockFactory_NotifyClient_Call { + _c.Call.Return(run) + return _c +} + // Now provides a mock function with given fields: func (_m *mockFactory) Now() func() time.Time { ret := _m.Called() diff --git a/internal/notify/client.go b/internal/notify/client.go index 66e469a23..c5d7e3d72 100644 --- a/internal/notify/client.go +++ b/internal/notify/client.go @@ -49,6 +49,10 @@ type EventClient interface { SendNotificationSent(ctx context.Context, event event.NotificationSent) error } +type Bundle interface { + For(lang localize.Lang) *localize.Localizer +} + type Client struct { logger Logger baseURL string @@ -58,10 +62,10 @@ type Client struct { now func() time.Time isProduction bool eventClient EventClient - bundle *localize.Bundle + bundle Bundle } -func New(logger Logger, isProduction bool, baseURL, apiKey string, httpClient Doer, eventClient EventClient, bundle *localize.Bundle) (*Client, error) { +func New(logger Logger, isProduction bool, baseURL, apiKey string, httpClient Doer, eventClient EventClient, bundle Bundle) (*Client, error) { keyParts := strings.Split(apiKey, "-") if len(keyParts) != 11 { return nil, errors.New("invalid apiKey format") diff --git a/internal/notify/email.go b/internal/notify/email.go index 96ffb05fe..1c321bf11 100644 --- a/internal/notify/email.go +++ b/internal/notify/email.go @@ -555,3 +555,16 @@ func (e AdviseAttorneyToSignOrOptOutEmail) emailID(isProduction bool, lang local return "3ddfd30a-02b6-4625-8fbf-5785f5b33864" } + +type DigitalDonorLpaSubmittedEmail struct { + Greeting string + LpaType string +} + +func (e DigitalDonorLpaSubmittedEmail) emailID(isProduction bool, _ localize.Lang) string { + if isProduction { + return "ce8a6d18-05ce-4028-8449-29c09bd1f958" + } + + return "cd02bc22-41be-4e2c-94c2-055d6cba6466" +} diff --git a/internal/notify/mock_Bundle_test.go b/internal/notify/mock_Bundle_test.go new file mode 100644 index 000000000..3ecc1241c --- /dev/null +++ b/internal/notify/mock_Bundle_test.go @@ -0,0 +1,83 @@ +// Code generated by mockery. DO NOT EDIT. + +package notify + +import ( + localize "github.com/ministryofjustice/opg-modernising-lpa/internal/localize" + mock "github.com/stretchr/testify/mock" +) + +// mockBundle is an autogenerated mock type for the Bundle type +type mockBundle struct { + mock.Mock +} + +type mockBundle_Expecter struct { + mock *mock.Mock +} + +func (_m *mockBundle) EXPECT() *mockBundle_Expecter { + return &mockBundle_Expecter{mock: &_m.Mock} +} + +// For provides a mock function with given fields: lang +func (_m *mockBundle) For(lang localize.Lang) *localize.Localizer { + ret := _m.Called(lang) + + if len(ret) == 0 { + panic("no return value specified for For") + } + + var r0 *localize.Localizer + if rf, ok := ret.Get(0).(func(localize.Lang) *localize.Localizer); ok { + r0 = rf(lang) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*localize.Localizer) + } + } + + return r0 +} + +// mockBundle_For_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'For' +type mockBundle_For_Call struct { + *mock.Call +} + +// For is a helper method to define mock.On call +// - lang localize.Lang +func (_e *mockBundle_Expecter) For(lang interface{}) *mockBundle_For_Call { + return &mockBundle_For_Call{Call: _e.mock.On("For", lang)} +} + +func (_c *mockBundle_For_Call) Run(run func(lang localize.Lang)) *mockBundle_For_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(localize.Lang)) + }) + return _c +} + +func (_c *mockBundle_For_Call) Return(_a0 *localize.Localizer) *mockBundle_For_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockBundle_For_Call) RunAndReturn(run func(localize.Lang) *localize.Localizer) *mockBundle_For_Call { + _c.Call.Return(run) + return _c +} + +// newMockBundle creates a new instance of mockBundle. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockBundle(t interface { + mock.TestingT + Cleanup(func()) +}) *mockBundle { + mock := &mockBundle{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/notify/sms.go b/internal/notify/sms.go index 4b2b0bdfd..a75e3ddf6 100644 --- a/internal/notify/sms.go +++ b/internal/notify/sms.go @@ -143,3 +143,15 @@ func (e VoucherHasConfirmedDonorIdentityOnSignedLpaSMS) smsID(isProduction bool, return "65072ed8-0d20-4e0d-9800-2f1407407d32" } + +type PaperDonorLpaSubmittedSMS struct { + LpaType string +} + +func (e PaperDonorLpaSubmittedSMS) smsID(isProduction bool, lang localize.Lang) string { + if isProduction { + return "edd5d11d-e9e8-4e80-a4e1-daaa46efbe0f" + } + + return "e7476d24-6d37-4137-b4a0-de14d3a977ed" +}