diff --git a/cmd/event-received/main.go b/cmd/event-received/main.go index f1100b82a..29ff8429b 100644 --- a/cmd/event-received/main.go +++ b/cmd/event-received/main.go @@ -28,6 +28,7 @@ import ( "github.com/ministryofjustice/opg-modernising-lpa/internal/pay" "github.com/ministryofjustice/opg-modernising-lpa/internal/random" "github.com/ministryofjustice/opg-modernising-lpa/internal/s3" + "github.com/ministryofjustice/opg-modernising-lpa/internal/scheduled" "github.com/ministryofjustice/opg-modernising-lpa/internal/telemetry" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig" @@ -130,6 +131,7 @@ type EventClient interface { type ScheduledStore interface { DeleteAllByUID(ctx context.Context, uid string) error + DeleteAllActionByUID(ctx context.Context, actions []scheduled.Action, uid string) error } type NotifyClient interface { diff --git a/cmd/event-received/mock_ScheduledStore_test.go b/cmd/event-received/mock_ScheduledStore_test.go index e8f3ccb93..f8c4057d9 100644 --- a/cmd/event-received/mock_ScheduledStore_test.go +++ b/cmd/event-received/mock_ScheduledStore_test.go @@ -5,6 +5,7 @@ package main import ( context "context" + scheduled "github.com/ministryofjustice/opg-modernising-lpa/internal/scheduled" mock "github.com/stretchr/testify/mock" ) @@ -21,6 +22,54 @@ func (_m *mockScheduledStore) EXPECT() *mockScheduledStore_Expecter { return &mockScheduledStore_Expecter{mock: &_m.Mock} } +// DeleteAllActionByUID provides a mock function with given fields: ctx, actions, uid +func (_m *mockScheduledStore) DeleteAllActionByUID(ctx context.Context, actions []scheduled.Action, uid string) error { + ret := _m.Called(ctx, actions, uid) + + if len(ret) == 0 { + panic("no return value specified for DeleteAllActionByUID") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []scheduled.Action, string) error); ok { + r0 = rf(ctx, actions, uid) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockScheduledStore_DeleteAllActionByUID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAllActionByUID' +type mockScheduledStore_DeleteAllActionByUID_Call struct { + *mock.Call +} + +// DeleteAllActionByUID is a helper method to define mock.On call +// - ctx context.Context +// - actions []scheduled.Action +// - uid string +func (_e *mockScheduledStore_Expecter) DeleteAllActionByUID(ctx interface{}, actions interface{}, uid interface{}) *mockScheduledStore_DeleteAllActionByUID_Call { + return &mockScheduledStore_DeleteAllActionByUID_Call{Call: _e.mock.On("DeleteAllActionByUID", ctx, actions, uid)} +} + +func (_c *mockScheduledStore_DeleteAllActionByUID_Call) Run(run func(ctx context.Context, actions []scheduled.Action, uid string)) *mockScheduledStore_DeleteAllActionByUID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]scheduled.Action), args[2].(string)) + }) + return _c +} + +func (_c *mockScheduledStore_DeleteAllActionByUID_Call) Return(_a0 error) *mockScheduledStore_DeleteAllActionByUID_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockScheduledStore_DeleteAllActionByUID_Call) RunAndReturn(run func(context.Context, []scheduled.Action, string) error) *mockScheduledStore_DeleteAllActionByUID_Call { + _c.Call.Return(run) + return _c +} + // DeleteAllByUID provides a mock function with given fields: ctx, uid func (_m *mockScheduledStore) DeleteAllByUID(ctx context.Context, uid string) error { ret := _m.Called(ctx, uid) diff --git a/cmd/event-received/sirius_event_handler.go b/cmd/event-received/sirius_event_handler.go index b5a56c026..d90b4bcba 100644 --- a/cmd/event-received/sirius_event_handler.go +++ b/cmd/event-received/sirius_event_handler.go @@ -339,6 +339,13 @@ func handleCertificateProviderSubmissionCompleted(ctx context.Context, event *ev now := factory.Now() donor.AttorneysInvitedAt = now() + if err := factory.ScheduledStore().DeleteAllActionByUID(ctx, []scheduled.Action{ + scheduled.ActionRemindCertificateProviderToComplete, + scheduled.ActionRemindCertificateProviderToConfirmIdentity, + }, v.UID); err != nil { + return fmt.Errorf("failed to delete scheduled events: %w", err) + } + if err := shareCodeSender.SendAttorneys(ctx, appData, lpa); err != nil { return fmt.Errorf("failed to send share codes to attorneys: %w", err) } diff --git a/cmd/event-received/sirius_event_handler_test.go b/cmd/event-received/sirius_event_handler_test.go index c32b64997..924c2c6b9 100644 --- a/cmd/event-received/sirius_event_handler_test.go +++ b/cmd/event-received/sirius_event_handler_test.go @@ -1093,6 +1093,11 @@ func TestHandleCertificateProviderSubmissionCompleted(t *testing.T) { SendAttorneys(ctx, appData, lpa). Return(nil) + scheduledStore := newMockScheduledStore(t) + scheduledStore.EXPECT(). + DeleteAllActionByUID(mock.Anything, mock.Anything, mock.Anything). + Return(nil) + factory := newMockFactory(t) factory.EXPECT(). LpaStoreClient(). @@ -1109,6 +1114,9 @@ func TestHandleCertificateProviderSubmissionCompleted(t *testing.T) { factory.EXPECT(). Now(). Return(testNowFn) + factory.EXPECT(). + ScheduledStore(). + Return(scheduledStore) handler := &siriusEventHandler{} err := handler.Handle(ctx, factory, certificateProviderSubmissionCompletedEvent) @@ -1228,6 +1236,11 @@ func TestHandleCertificateProviderSubmissionCompletedWhenDonorPutErrors(t *testi SendAttorneys(ctx, mock.Anything, mock.Anything). Return(nil) + scheduledStore := newMockScheduledStore(t) + scheduledStore.EXPECT(). + DeleteAllActionByUID(mock.Anything, mock.Anything, mock.Anything). + Return(nil) + factory := newMockFactory(t) factory.EXPECT(). LpaStoreClient(). @@ -1244,12 +1257,65 @@ func TestHandleCertificateProviderSubmissionCompletedWhenDonorPutErrors(t *testi factory.EXPECT(). Now(). Return(testNowFn) + factory.EXPECT(). + ScheduledStore(). + Return(scheduledStore) handler := &siriusEventHandler{} err := handler.Handle(ctx, factory, certificateProviderSubmissionCompletedEvent) assert.ErrorIs(t, err, expectedError) } +func TestHandleCertificateProviderSubmissionCompletedWhenScheduleDeleteErrors(t *testing.T) { + lpaStoreClient := newMockLpaStoreClient(t) + lpaStoreClient.EXPECT(). + Lpa(ctx, "M-1111-2222-3333"). + Return(&lpadata.Lpa{ + CertificateProvider: lpadata.CertificateProvider{ + Channel: lpadata.ChannelPaper, + }, + }, nil) + + dynamoClient := newMockDynamodbClient(t) + dynamoClient.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"))}) + dynamoClient.EXPECT(). + One(ctx, dynamo.LpaKey("an-lpa"), dynamo.DonorKey("a-donor"), mock.Anything). + Return(nil). + SetData(&donordata.Provided{PK: dynamo.LpaKey("an-lpa")}) + + scheduledStore := newMockScheduledStore(t) + scheduledStore.EXPECT(). + DeleteAllActionByUID(mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + + factory := newMockFactory(t) + factory.EXPECT(). + LpaStoreClient(). + Return(lpaStoreClient, nil) + factory.EXPECT(). + ShareCodeSender(ctx). + Return(nil, nil) + factory.EXPECT(). + AppData(). + Return(appcontext.Data{}, nil) + factory.EXPECT(). + DynamoClient(). + Return(dynamoClient) + factory.EXPECT(). + Now(). + Return(testNowFn) + factory.EXPECT(). + ScheduledStore(). + Return(scheduledStore) + + handler := &siriusEventHandler{} + err := handler.Handle(ctx, factory, certificateProviderSubmissionCompletedEvent) + assert.Equal(t, fmt.Errorf("failed to delete scheduled events: %w", expectedError), err) +} + func TestHandleCertificateProviderSubmissionCompletedWhenShareCodeSenderErrors(t *testing.T) { lpaStoreClient := newMockLpaStoreClient(t) lpaStoreClient.EXPECT(). @@ -1275,6 +1341,11 @@ func TestHandleCertificateProviderSubmissionCompletedWhenShareCodeSenderErrors(t SendAttorneys(ctx, mock.Anything, mock.Anything). Return(expectedError) + scheduledStore := newMockScheduledStore(t) + scheduledStore.EXPECT(). + DeleteAllActionByUID(mock.Anything, mock.Anything, mock.Anything). + Return(nil) + factory := newMockFactory(t) factory.EXPECT(). LpaStoreClient(). @@ -1291,6 +1362,9 @@ func TestHandleCertificateProviderSubmissionCompletedWhenShareCodeSenderErrors(t factory.EXPECT(). Now(). Return(testNowFn) + factory.EXPECT(). + ScheduledStore(). + Return(scheduledStore) handler := &siriusEventHandler{} err := handler.Handle(ctx, factory, certificateProviderSubmissionCompletedEvent) diff --git a/internal/scheduled/store.go b/internal/scheduled/store.go index 498e4e7a6..bac6fa10f 100644 --- a/internal/scheduled/store.go +++ b/internal/scheduled/store.go @@ -3,6 +3,7 @@ package scheduled import ( "context" "fmt" + "slices" "time" "github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo" @@ -79,3 +80,20 @@ func (s *Store) DeleteAllByUID(ctx context.Context, uid string) error { return s.dynamoClient.DeleteKeys(ctx, keys) } + +func (s *Store) DeleteAllActionByUID(ctx context.Context, actions []Action, uid string) error { + var events []Event + + if err := s.dynamoClient.AllByLpaUIDAndPartialSK(ctx, uid, dynamo.PartialScheduledKey(), &events); err != nil { + return err + } + + var keys []dynamo.Keys + for _, e := range events { + if slices.Contains(actions, e.Action) { + keys = append(keys, dynamo.Keys{PK: e.PK, SK: e.SK}) + } + } + + return s.dynamoClient.DeleteKeys(ctx, keys) +} diff --git a/internal/scheduled/store_test.go b/internal/scheduled/store_test.go index c14129277..5d8ef89bd 100644 --- a/internal/scheduled/store_test.go +++ b/internal/scheduled/store_test.go @@ -174,3 +174,55 @@ func TestDeleteAllByUIDWhenDeleteKeysErrors(t *testing.T) { assert.Equal(t, expectedError, err) } + +func TestDeleteAllActionByUID(t *testing.T) { + now := time.Now() + yesterday := now.Add(-24 * time.Hour) + + dynamoClient := newMockDynamoClient(t) + dynamoClient.EXPECT(). + AllByLpaUIDAndPartialSK(ctx, "lpa-uid", dynamo.PartialScheduledKey(), mock.Anything). + Return(nil). + SetData([]Event{ + {LpaUID: "lpa-uid", Action: ActionRemindAttorneyToComplete, PK: dynamo.ScheduledDayKey(now), SK: dynamo.ScheduledKey(now, testUuidString)}, + {LpaUID: "lpa-uid", Action: ActionExpireDonorIdentity, PK: dynamo.ScheduledDayKey(yesterday), SK: dynamo.ScheduledKey(yesterday, testUuidString)}, + }) + dynamoClient.EXPECT(). + DeleteKeys(ctx, []dynamo.Keys{ + {PK: dynamo.ScheduledDayKey(yesterday), SK: dynamo.ScheduledKey(yesterday, testUuidString)}, + }). + Return(nil) + + store := &Store{dynamoClient: dynamoClient, now: testNowFn, uuidString: testUuidStringFn} + err := store.DeleteAllActionByUID(ctx, []Action{ActionExpireDonorIdentity}, "lpa-uid") + + assert.Nil(t, err) +} + +func TestDeleteAllActionByUIDWhenAllByLpaUIDAndPartialSKErrors(t *testing.T) { + dynamoClient := newMockDynamoClient(t) + dynamoClient.EXPECT(). + AllByLpaUIDAndPartialSK(ctx, mock.Anything, mock.Anything, mock.Anything). + Return(expectedError) + + store := &Store{dynamoClient: dynamoClient, now: testNowFn} + err := store.DeleteAllActionByUID(ctx, []Action{}, "lpa-uid") + + assert.Equal(t, expectedError, err) +} + +func TestDeleteAllActionByUIDWhenDeleteKeysErrors(t *testing.T) { + dynamoClient := newMockDynamoClient(t) + dynamoClient.EXPECT(). + AllByLpaUIDAndPartialSK(ctx, mock.Anything, mock.Anything, mock.Anything). + Return(nil). + SetData([]Event{{LpaUID: "lpa-uid"}}) + dynamoClient.EXPECT(). + DeleteKeys(mock.Anything, mock.Anything). + Return(expectedError) + + store := &Store{dynamoClient: dynamoClient, now: testNowFn} + err := store.DeleteAllActionByUID(ctx, []Action{}, "lpa-uid") + + assert.Equal(t, expectedError, err) +}