Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewpearce-digital authored Jan 31, 2025
2 parents 0b3ae63 + 86341f0 commit 0e3862b
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 28 deletions.
51 changes: 30 additions & 21 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ ECR_LOGIN ?= @aws-vault exec management -- aws ecr get-login-password --region e
# A category can be added with @category
# This was made possible by https://gist.github.com/prwhite/8168133#gistcomment-1727513
HELP_FUN = \
%help; \
while(<>) { push @{$$help{$$2 // 'options'}}, [$$1, $$3] if /^([a-zA-Z0-9\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
print "usage: make [target]\n\n"; \
for (sort keys %help) { \
print "${WHITE}$$_:${RESET}\n"; \
for (@{$$help{$$_}}) { \
$$sep = " " x (32 - length $$_->[0]); \
print " ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
}; \
print "\n"; }
%help; \
while(<>) { push @{$$help{$$2 // 'options'}}, [$$1, $$3] if /^([a-zA-Z0-9\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
print "usage: make [target]\n\n"; \
for (sort keys %help) { \
print "${WHITE}$$_:${RESET}\n"; \
for (@{$$help{$$_}}) { \
$$sep = " " x (32 - length $$_->[0]); \
print " ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
}; \
print "\n"; }

help: ##@other Show this help.
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
Expand Down Expand Up @@ -119,12 +119,12 @@ delete-all-items: ##@dynamodb deletes and recreates lpas dynamodb table
delete-table --table-name lpas

docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb create-table \
--region eu-west-1 \
--table-name lpas \
--attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S AttributeName=LpaUID,AttributeType=S AttributeName=UpdatedAt,AttributeType=S \
--key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
--provisioned-throughput ReadCapacityUnits=1000,WriteCapacityUnits=1000 \
--global-secondary-indexes file://dynamodb-lpa-gsi-schema.json
--region eu-west-1 \
--table-name lpas \
--attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S AttributeName=LpaUID,AttributeType=S AttributeName=UpdatedAt,AttributeType=S \
--key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
--provisioned-throughput ReadCapacityUnits=1000,WriteCapacityUnits=1000 \
--global-secondary-indexes file://dynamodb-lpa-gsi-schema.json

emit-evidence-received: ##@events emits an evidence-received event with the given LpaUID e.g. emit-evidence-received uid=abc-123
$(eval BODY := $(shell echo '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"evidence-received","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(uid)"}}' | sed 's/"/\\"/g'))
Expand Down Expand Up @@ -181,6 +181,15 @@ emit-lpa-updated-event: ##@events emits an lpa-updated event with the given chan
--function-name event-received text \
--payload '{"Records": [{"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", "body": "$(BODY)"}]}'

emit-priority-correspondence-sent: ##@events emits a priority-correspondence-sent event with the given LpaUID e.g. emit-priority-correspondence-sent uid=abc-123
$(eval BODY := $(shell echo '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"priority-correspondence-sent","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"uid":"$(uid)","sentDate":"2024-01-02T12:13:14.000006Z"}}' | sed 's/"/\\"/g'))

docker compose -f docker/docker-compose.yml exec localstack awslocal lambda invoke \
--endpoint-url=http://localhost:4566 \
--region eu-west-1 \
--function-name event-received text \
--payload '{"Records": [{"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", "body": "$(BODY)"}]}'

emit-object-tags-added-with-virus: ##@events emits a ObjectTagging:Put event with the given S3 key e.g. emit-object-tags-added-with-virus key=doc/key. Also ensures a tag with virus-scan-status exists on an existing object set to infected
docker compose -f docker/docker-compose.yml exec localstack awslocal s3api \
put-object-tagging --bucket evidence --key $(key) --tagging '{"TagSet": [{ "Key": "virus-scan-status", "Value": "infected" }]}'
Expand All @@ -205,12 +214,12 @@ emit-object-tags-added-without-virus: ##@events emits a ObjectTagging:Put event
set-uploads-clean: ##@events calls emit-object-tags-added-without-virus for all documents on a given lpa e.g. set-uploads-clean lpaId=abc
for k in $$(docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb --region eu-west-1 query --table-name lpas --key-condition-expression 'PK = :pk and begins_with(SK, :sk)' --expression-attribute-values '{":pk": {"S": "LPA#$(lpaId)"}, ":sk": {"S": "DOCUMENT#"}}' | jq -c -r '.Items[] | .Key[]'); do \
key=$$k $(MAKE) emit-object-tags-added-without-virus ; \
done
done

set-uploads-infected: ##@events calls emit-object-tags-added-with-virus for all documents on a given lpa e.g. set-uploads-clean lpaId=abc
for k in $$(docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb --region eu-west-1 query --table-name lpas --key-condition-expression 'PK = :pk and begins_with(SK, :sk)' --expression-attribute-values '{":pk": {"S": "LPA#$(lpaId)"}, ":sk": {"S": "DOCUMENT#"}}' | jq -c -r '.Items[] | .Key[]'); do \
key=$$k $(MAKE) emit-object-tags-added-with-virus ; \
done
done

tail-logs: ##@app tails logs for app mock-notify, mock-onelogin, mock-lpa-store, mock-uid and mock-pay
docker compose --ansi=always -f docker/docker-compose.yml -f docker/docker-compose.dev.yml logs app mock-notify mock-onelogin mock-lpa-store mock-uid mock-pay -f
Expand Down Expand Up @@ -246,9 +255,9 @@ endif

run-schedule-runner: ##@scheduler invokes the schedule-runner lambda
docker compose -f docker/docker-compose.yml exec localstack awslocal lambda invoke \
--endpoint-url=http://localhost:4566 \
--region eu-west-1 \
--function-name schedule-runner text
--endpoint-url=http://localhost:4566 \
--region eu-west-1 \
--function-name schedule-runner text

test-schedule-runner: add-scheduled-tasks run-schedule-runner ##@scheduler seeds scheduled tasks and runs the schedule-runner (defaults to 10 seeded tasks) e.g. test-schedule-runner count=100
docker compose -f docker/docker-compose.yml exec localstack awslocal cloudwatch get-metric-data \
Expand Down
28 changes: 28 additions & 0 deletions cmd/event-received/sirius_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ func (h *siriusEventHandler) Handle(ctx context.Context, factory factory, cloudW
case "certificate-provider-submission-completed":
return handleCertificateProviderSubmissionCompleted(ctx, cloudWatchEvent, factory)

case "priority-correspondence-sent":
return handlePriorityCorrespondenceSent(ctx, factory.DynamoClient(), cloudWatchEvent, factory.Now())

default:
return fmt.Errorf("unknown sirius event")
}
Expand Down Expand Up @@ -316,3 +319,28 @@ func handleCertificateProviderSubmissionCompleted(ctx context.Context, event *ev

return nil
}

type priorityCorrespondenceSentEvent struct {
UID string `json:"uid"`
SentDate time.Time `json:"sentDate"`
}

func handlePriorityCorrespondenceSent(ctx context.Context, client dynamodbClient, event *events.CloudWatchEvent, now func() time.Time) error {
var v priorityCorrespondenceSentEvent
if err := json.Unmarshal(event.Detail, &v); err != nil {
return fmt.Errorf("failed to unmarshal detail: %w", err)
}

donor, err := getDonorByLpaUID(ctx, client, v.UID)
if err != nil {
return fmt.Errorf("failed to get lpa: %w", err)
}

donor.PriorityCorrespondenceSentAt = v.SentDate

if err := putDonor(ctx, donor, now, client); err != nil {
return fmt.Errorf("failed to update lpa: %w", err)
}

return nil
}
74 changes: 74 additions & 0 deletions cmd/event-received/sirius_event_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1332,3 +1332,77 @@ func TestHandleCertificateProviderSubmissionCompletedWhenAppDataFactoryErrors(t
err := handler.Handle(ctx, factory, certificateProviderSubmissionCompletedEvent)
assert.Equal(t, expectedError, err)
}

func TestHandlePriorityCorrespondenceSent(t *testing.T) {
event := &events.CloudWatchEvent{
DetailType: "priority-correspondence-sent",
Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","sentAt":"2024-01-18T00:00:00.000Z"}`),
}

updated := &donordata.Provided{PK: dynamo.LpaKey("123"), SK: dynamo.LpaOwnerKey(dynamo.DonorKey("456")), UpdatedAt: testNow}
updated.UpdateHash()

client := newMockDynamodbClient(t)
client.EXPECT().
OneByUID(ctx, "M-1111-2222-3333", mock.Anything).
Return(nil).
SetData(dynamo.Keys{PK: dynamo.LpaKey("123"), SK: dynamo.DonorKey("456")})
client.EXPECT().
One(ctx, dynamo.LpaKey("123"), dynamo.DonorKey("456"), mock.Anything).
Return(nil).
SetData(donordata.Provided{PK: dynamo.LpaKey("123"), SK: dynamo.LpaOwnerKey(dynamo.DonorKey("456"))})
client.EXPECT().
Put(ctx, updated).
Return(nil)

factory := newMockFactory(t)
factory.EXPECT().
DynamoClient().
Return(client)
factory.EXPECT().
Now().
Return(testNowFn)

handler := &siriusEventHandler{}
err := handler.Handle(ctx, factory, event)

assert.Nil(t, err)
}

func TestHandlePriorityCorrespondenceSentWhenGetError(t *testing.T) {
event := &events.CloudWatchEvent{
DetailType: "priority-correspondence-sent",
Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","sentAt":"2024-01-18T00:00:00.000Z"}`),
}

client := newMockDynamodbClient(t)
client.EXPECT().
OneByUID(ctx, "M-1111-2222-3333", mock.Anything).
Return(expectedError)

err := handlePriorityCorrespondenceSent(ctx, client, event, testNowFn)
assert.ErrorIs(t, err, expectedError)
}

func TestHandlePriorityCorrespondenceSentWhenPutError(t *testing.T) {
event := &events.CloudWatchEvent{
DetailType: "priority-correspondence-sent",
Detail: json.RawMessage(`{"uid":"M-1111-2222-3333","sentAt":"2024-01-18T00:00:00.000Z"}`),
}

client := newMockDynamodbClient(t)
client.EXPECT().
OneByUID(ctx, "M-1111-2222-3333", mock.Anything).
Return(nil).
SetData(dynamo.Keys{PK: dynamo.LpaKey("123"), SK: dynamo.DonorKey("456")})
client.EXPECT().
One(ctx, dynamo.LpaKey("123"), dynamo.DonorKey("456"), mock.Anything).
Return(nil).
SetData(donordata.Provided{PK: dynamo.LpaKey("123"), SK: dynamo.LpaOwnerKey(dynamo.DonorKey("456"))})
client.EXPECT().
Put(ctx, mock.Anything).
Return(expectedError)

err := handlePriorityCorrespondenceSent(ctx, client, event, testNowFn)
assert.ErrorIs(t, err, expectedError)
}
12 changes: 8 additions & 4 deletions cypress/e2e/donor/you-cannot-sign-your-lpa-yet.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ describe('You cannot sign your LPA yet', () => {
cy.visit('/fixtures?redirect=/choose-attorneys-summary&progress=peopleToNotifyAboutYourLpa');

cy.contains('.govuk-summary-card', 'Jessie Jones').contains('a', 'Change').click();
cy.get('#f-date-of-birth-year').clear().type(today.getFullYear() - 1)
cy.get('#f-date-of-birth-year').clear();
cy.get('#f-date-of-birth-year').type(today.getFullYear() - 1);
cy.contains('button', 'Save and continue').click()
cy.contains('button', 'Save and continue').click()
cy.visitLpa('/choose-replacement-attorneys-summary')

cy.contains('.govuk-summary-card', 'Blake Buckley').contains('a', 'Change').click();
cy.get('#f-date-of-birth-year').clear().type(today.getFullYear() - 1)
cy.get('#f-date-of-birth-year').clear();
cy.get('#f-date-of-birth-year').type(today.getFullYear() - 1);
cy.contains('button', 'Save and continue').click()
cy.contains('button', 'Save and continue').click()
cy.contains('a', 'Return to task list').click()
Expand All @@ -22,14 +24,16 @@ describe('You cannot sign your LPA yet', () => {
cy.contains('.govuk-summary-list__row', 'Jessie Jones').contains('a', 'Change').click();

cy.url().should('contain', '/choose-attorneys')
cy.get('#f-date-of-birth-year').clear().type("2000")
cy.get('#f-date-of-birth-year').clear();
cy.get('#f-date-of-birth-year').type("2000");
cy.contains('button', 'Save and continue').click()
cy.url().should('contain', '/you-cannot-sign-your-lpa-yet')

cy.contains('.govuk-summary-list__row', 'Blake Buckley').contains('a', 'Change').click();

cy.url().should('contain', '/choose-replacement-attorneys')
cy.get('#f-date-of-birth-year').clear().type("2000")
cy.get('#f-date-of-birth-year').clear();
cy.get('#f-date-of-birth-year').type("2000");
cy.contains('button', 'Save and continue').click()
cy.url().should('contain', '/task-list')
});
Expand Down
7 changes: 6 additions & 1 deletion internal/donor/donordata/provided.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,14 @@ type Provided struct {
// VoucherInvitedAt records when the invite is sent to the voucher to vouch.
VoucherInvitedAt time.Time `checkhash:"-"`

// MoreEvidenceRequiredAt records when a request for further information on an exemption/remission was received.
// MoreEvidenceRequiredAt records when a request for further information on an
// exemption/remission was received.
MoreEvidenceRequiredAt time.Time `checkhash:"-"`

// PriorityCorrespondenceSentAt records when a caseworker sent a letter to the
// donor informing them of a problem.
PriorityCorrespondenceSentAt time.Time `checkhash:"-"`

// HasSeenSuccessfulVouchBanner records if the donor has seen the progress tracker successful vouch banner
HasSeenSuccessfulVouchBanner bool `checkhash:"-"`

Expand Down
4 changes: 2 additions & 2 deletions internal/donor/donordata/provided_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,14 @@ func TestGenerateHash(t *testing.T) {
}

// DO change this value to match the updates
const modified uint64 = 0xe3bd213ba882ed4f
const modified uint64 = 0x44d4ab7efe0264f8

// DO NOT change these initial hash values. If a field has been added/removed
// you will need to handle the version gracefully by modifying
// (*Provided).HashInclude and adding another testcase for the new
// version.
testcases := map[uint8]uint64{
0: 0x3730007cdf45a8cd,
0: 0x48b67658eed0ea5c,
}

for version, initial := range testcases {
Expand Down
10 changes: 10 additions & 0 deletions internal/donor/donorpage/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,16 @@ func Progress(tmpl template.Template, lpaStoreResolvingService LpaStoreResolving
})
}

if !lpa.Status.IsRegistered() && !donor.PriorityCorrespondenceSentAt.IsZero() {
data.InfoNotifications = append(data.InfoNotifications, progressNotification{
Heading: appData.Localizer.T("thereIsAProblemWithYourLpa"),
Body: appData.Localizer.Format(
"weContactedYouOnWithGuidanceAboutWhatToDoNext",
map[string]any{"ContactedDate": appData.Localizer.FormatDate(donor.PriorityCorrespondenceSentAt)},
),
})
}

return tmpl(w, data)
}
}
26 changes: 26 additions & 0 deletions internal/donor/donorpage/progress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,32 @@ func TestGetProgress(t *testing.T) {
return l
},
},
"priority correspondence sent": {
donor: &donordata.Provided{
PriorityCorrespondenceSentAt: testNow,
},
lpa: &lpadata.Lpa{},
setupCertificateProviderStore: certificateProviderStoreNotFound,
infoNotifications: []progressNotification{
{Heading: "H", Body: "B"},
},
setupLocalizer: func(t *testing.T) *mockLocalizer {
l := newMockLocalizer(t)
l.EXPECT().T("thereIsAProblemWithYourLpa").Return("H")
l.EXPECT().Format("weContactedYouOnWithGuidanceAboutWhatToDoNext", map[string]any{"ContactedDate": "translated date"}).Return("B")
l.EXPECT().FormatDate(testNow).Return("translated date")
return l
},
},
"priority correspondence sent registered": {
donor: &donordata.Provided{
PriorityCorrespondenceSentAt: testNow,
},
lpa: &lpadata.Lpa{
Status: lpadata.StatusRegistered,
},
setupCertificateProviderStore: certificateProviderStoreNotFound,
},
}

for name, tc := range testCases {
Expand Down

0 comments on commit 0e3862b

Please sign in to comment.