Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MLPAB-1473: Add holding modal while reduced fee evidence documents are scanned #805

Merged
merged 36 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2db2ddd
add dialog with loading spinner
acsauk Oct 24, 2023
faf047b
trigger files uploadig on continue button click
acsauk Oct 24, 2023
16a5adc
add dummy random events endpoint
acsauk Oct 24, 2023
70b2542
update upload count and submit form when finished
acsauk Oct 25, 2023
99fbf87
show errors on infected files
acsauk Oct 26, 2023
ba65a6b
cover pay action
acsauk Oct 26, 2023
5d6dd88
lock modal focus
acsauk Oct 26, 2023
3ab6d0b
close SSE connection on esc
acsauk Oct 27, 2023
8f0ae1b
ensure only scanned documents are sent, redirect from upload on task …
acsauk Oct 27, 2023
6586032
cover virus uploads in e2e
acsauk Oct 27, 2023
f80f6f1
tidy and test fixes
acsauk Oct 27, 2023
c562a25
Merge branch 'main' into MLPAB-1473
acsauk Oct 27, 2023
df8b323
add ttl to sse
acsauk Oct 27, 2023
3642c00
add auto-increment version to LPA, add retry when handling object tag…
acsauk Oct 29, 2023
cc619e3
handle ttl expiration
acsauk Oct 29, 2023
b87d069
ensure we have a fresh lpa when sending independent witness codes
acsauk Oct 29, 2023
bf90939
Merge branch 'main' into MLPAB-1473
acsauk Oct 30, 2023
922c48f
move infected file error building to FilesInfectedError
acsauk Oct 30, 2023
c1f423d
move documents to individual rows
acsauk Oct 31, 2023
f32bea7
update mocks
acsauk Oct 31, 2023
3346ca8
cover upload_evidence_sse
acsauk Oct 31, 2023
048321c
cover upload_evidence
acsauk Oct 31, 2023
adccd28
cover payHelper
acsauk Oct 31, 2023
01057ef
cover payment_confirmation
acsauk Oct 31, 2023
27e2e39
more coverage
acsauk Oct 31, 2023
362049c
fix payments e2e
acsauk Oct 31, 2023
65abb2a
Merge branch 'main' into MLPAB-1473
acsauk Oct 31, 2023
cb31c93
drop unrequired namespace
acsauk Oct 31, 2023
6403b44
Merge branch 'main' into MLPAB-1473
acsauk Nov 1, 2023
61c4897
Merge branch 'MLPAB-1473' of github.com:ministryofjustice/opg-moderni…
acsauk Nov 1, 2023
06e83f0
provide UpdateItem permissions to lambda
acsauk Nov 1, 2023
8e2d42b
rename modal JS
acsauk Nov 1, 2023
aed4c62
bump SSE ttl
acsauk Nov 1, 2023
447c1c9
fix up
acsauk Nov 1, 2023
878a128
fix lambda sk
acsauk Nov 1, 2023
baebc5d
Merge branch 'main' into MLPAB-1473
acsauk Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ get-lpa: ##@app dumps all entries in the lpas dynamodb table that are related t
docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb \
query --table-name lpas --key-condition-expression 'PK = :pk' --expression-attribute-values '{":pk": {"S": "LPA#$(id)"}}'

get-evidence: ##@app dumps all fee evidence in the lpas dynamodb table that are related to the LPA id supplied e.g. get-evidence id=abc-123
get-documents: ##@app dumps all documents in the lpas dynamodb table that are related to the LPA id supplied e.g. get-documents lpaId=abc-123
docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb \
query --table-name lpas --key-condition-expression 'PK = :pk' --expression-attribute-values '{":pk": {"S": "LPA#$(id)"}}' --projection-expression "Evidence"
query --table-name lpas --key-condition-expression 'PK = :pk and begins_with(SK, :sk)' --expression-attribute-values '{":pk": {"S": "LPA#$(lpaId)"}, ":sk": {"S": "#DOCUMENT#"}}'

emit-evidence-received: ##@app emits an evidence-received event with the given UID e.g. emit-evidence-received uid=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"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)"}}'
Expand All @@ -104,17 +104,17 @@ emit-fee-denied: ##@app emits a fee-denied event with the given UID e.g. emit-fe
emit-more-evidence-required: ##@app emits a more-evidence-required event with the given UID e.g. emit-more-evidence-required uid=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"more-evidence-required","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(uid)"}}'

emit-object-tags-added-with-virus: ##@app emits a Object Tags Added 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
emit-object-tags-added-with-virus: ##@app 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" }]}'

curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"Object Tags Added","source":"aws.s3","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"object":{"key":"$(key)"}}}'
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"Records":[{"eventSource":"aws:s3","eventTime":"2023-10-23T15:58:33.081Z","eventName":"ObjectTagging:Put","s3":{"bucket":{"name":"uploads-opg-modernising-lpa-eu-west-1"},"object":{"key":"$(key)"}}}]}'

emit-object-tags-added-without-virus: ##@app emits a Object Tags Added 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 ok
emit-object-tags-added-without-virus: ##@app 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 ok
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": "ok" }]}'

curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"Object Tags Added","source":"aws.s3","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"object":{"key":"$(key)"}}}'
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"Records":[{"eventSource":"aws:s3","eventTime":"2023-10-23T15:58:33.081Z","eventName":"ObjectTagging:Put","s3":{"bucket":{"name":"uploads-opg-modernising-lpa-eu-west-1"},"object":{"key":"$(key)"}}}]}'

logs: ##@app tails logs for all containers running
docker compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml logs -f
35 changes: 16 additions & 19 deletions cmd/event-received/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
SendCertificateProvider(context.Context, notify.Template, page.AppData, bool, *page.Lpa) error
}

//go:generate mockery --testonly --inpackage --name DocumentStore --structname mockDocumentStore
type DocumentStore interface {
UpdateScanResults(ctx context.Context, PK, SK string, virusDetected bool) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep pk, sk lowercase

}

type Event struct {
events.S3Event
events.CloudWatchEvent
Expand Down Expand Up @@ -110,6 +115,8 @@

notifyClient, err := notify.New(notifyIsProduction, notifyBaseURL, notifyApiKey, http.DefaultClient)

documentStore := app.NewDocumentStore(dynamoClient, s3Client)

Check warning on line 119 in cmd/event-received/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/event-received/main.go#L118-L119

Added lines #L118 - L119 were not covered by tests
bundle := localize.NewBundle("./lang/en.json", "./lang/cy.json")

//TODO do this in handleFeeApproved when/if we save lang preference in LPA
Expand All @@ -119,7 +126,7 @@
now := time.Now

if event.isS3Event() {
return handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, now, s3Client)
return handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, s3Client, documentStore)

Check warning on line 129 in cmd/event-received/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/event-received/main.go#L129

Added line #L129 was not covered by tests
}

if event.isCloudWatchEvent() {
Expand Down Expand Up @@ -230,7 +237,7 @@
return nil
}

func handleObjectTagsAdded(ctx context.Context, client dynamodbClient, event events.S3Event, now func() time.Time, s3Client s3Client) error {
func handleObjectTagsAdded(ctx context.Context, dynamodbClient dynamodbClient, event events.S3Event, s3Client s3Client, documentStore DocumentStore) error {
objectKey := event.Records[0].S3.Object.Key
if objectKey == "" {
return fmt.Errorf("object key missing in event in '%s'", objectTagsAddedEventName)
Expand All @@ -256,26 +263,16 @@
return nil
}

uid := strings.Split(objectKey, "/")

lpa, err := getLpaByUID(ctx, client, uid[0], objectTagsAddedEventName)
if err != nil {
return err
}
parts := strings.Split(objectKey, "/")

document := lpa.Evidence.Get(objectKey)
if document.Key == "" {
return fmt.Errorf("LPA did not contain a document with key %s for '%s'", objectKey, objectTagsAddedEventName)
var lpaKey dynamo.Key
if err := dynamodbClient.OneByUID(ctx, parts[0], &lpaKey); err != nil {
return fmt.Errorf("failed to resolve uid for '%s': %w", objectTagsAddedEventName, err)
}

document.Scanned = now()
document.VirusDetected = hasVirus

lpa.Evidence.Put(document)
lpa.UpdatedAt = now()

if err := client.Put(ctx, lpa); err != nil {
return fmt.Errorf("failed to update LPA for '%s': %w", objectTagsAddedEventName, err)
err = documentStore.UpdateScanResults(ctx, lpaKey.PK, "#DOCUMENT#"+objectKey, hasVirus)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to push #DOCUMENT# into the store, so outside only has to deal with IDs and not how it is stored in dynamo. And the same with changing PK to be the Lpa ID (even though it is awkward)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much simpler - I was going around in circles with circular dependencies but makes more sense to hide the detail.

if err != nil {
return fmt.Errorf("failed to update scan results for '%s': %w", objectTagsAddedEventName, err)
}

return nil
Expand Down
147 changes: 63 additions & 84 deletions cmd/event-received/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,97 +334,98 @@ func TestHandleFeeDeniedWhenPutError(t *testing.T) {
}

func TestHandleObjectTagsAdded(t *testing.T) {
testCases := map[string]bool{
"ok": false,
"infected": true,
}

for scanResult, hasVirus := range testCases {
t.Run(scanResult, func(t *testing.T) {
event := Event{
S3Event: events.S3Event{Records: []events.S3EventRecord{
{S3: events.S3Entity{Object: events.S3Object{Key: "M-1111-2222-3333/evidence/a-uid"}}},
}},
}

s3Client := newMockS3Client(t)
s3Client.
On("GetObjectTags", ctx, "M-1111-2222-3333/evidence/a-uid").
Return([]types.Tag{
{Key: aws.String("virus-scan-status"), Value: aws.String(scanResult)},
}, nil)

dynamoClient := newMockDynamodbClient(t)
dynamoClient.
On("OneByUID", ctx, "M-1111-2222-3333", mock.Anything).
Return(func(ctx context.Context, uid string, v interface{}) error {
b, _ := json.Marshal(dynamo.Key{PK: "LPA#123", SK: "#DONOR#456"})
json.Unmarshal(b, v)
return nil
})

documentStore := newMockDocumentStore(t)
documentStore.
On("UpdateScanResults", ctx, "LPA#123", "#DOCUMENT#M-1111-2222-3333/evidence/a-uid", hasVirus).
Return(nil)

err := handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, s3Client, documentStore)
assert.Nil(t, err)
})
}
}

func TestHandleObjectTagsAddedWhenScannedTagMissing(t *testing.T) {
event := Event{
S3Event: events.S3Event{Records: []events.S3EventRecord{
{S3: events.S3Entity{Object: events.S3Object{Key: "M-1111-2222-3333/evidence/a-uid"}}},
}},
}

now := time.Now()

s3Client := newMockS3Client(t)
s3Client.
On("GetObjectTags", ctx, "M-1111-2222-3333/evidence/a-uid").
Return([]types.Tag{
{Key: aws.String("virus-scan-status"), Value: aws.String("ok")},
{Key: aws.String("not-virus-scan-status"), Value: aws.String("ok")},
}, nil)

dynamoClient := newMockDynamodbClient(t)
dynamoClient.
On("OneByUID", ctx, "M-1111-2222-3333", mock.Anything).
Return(func(ctx context.Context, uid string, v interface{}) error {
b, _ := json.Marshal(dynamo.Key{PK: "LPA#123", SK: "#DONOR#456"})
json.Unmarshal(b, v)
return nil
})
dynamoClient.
On("One", ctx, "LPA#123", "#DONOR#456", mock.Anything).
Return(func(ctx context.Context, pk, sk string, v interface{}) error {
b, _ := json.Marshal(page.Lpa{PK: "LPA#123", SK: "#DONOR#456", Evidence: page.Evidence{
Documents: []page.Document{{Key: "M-1111-2222-3333/evidence/a-uid"}},
}})
json.Unmarshal(b, v)
return nil
})
dynamoClient.
On("Put", ctx, page.Lpa{PK: "LPA#123", SK: "#DONOR#456", UpdatedAt: now, Evidence: page.Evidence{
Documents: []page.Document{{Key: "M-1111-2222-3333/evidence/a-uid", Scanned: now, VirusDetected: false}},
}}).
Return(nil)

err := handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, func() time.Time { return now }, s3Client)
err := handleObjectTagsAdded(ctx, nil, event.S3Event, s3Client, nil)
assert.Nil(t, err)
}

func TestHandleObjectTagsAddedWhenGetObjectTagsError(t *testing.T) {
func TestHandleObjectTagsAddedWhenObjectKeyMissing(t *testing.T) {
event := Event{
S3Event: events.S3Event{Records: []events.S3EventRecord{
{S3: events.S3Entity{Object: events.S3Object{Key: "M-1111-2222-3333/evidence/a-uid"}}},
{S3: events.S3Entity{Object: events.S3Object{}}},
}},
}

now := time.Now()

s3Client := newMockS3Client(t)
s3Client.
On("GetObjectTags", ctx, "M-1111-2222-3333/evidence/a-uid").
Return([]types.Tag{
{Key: aws.String("virus-scan-status"), Value: aws.String("ok")},
}, expectedError)

err := handleObjectTagsAdded(ctx, nil, event.S3Event, func() time.Time { return now }, s3Client)
assert.Equal(t, fmt.Errorf("failed to get tags for object in 'ObjectTagging:Put': %w", expectedError), err)
err := handleObjectTagsAdded(ctx, nil, event.S3Event, nil, nil)
assert.Equal(t, fmt.Errorf("object key missing in event in '%s'", objectTagsAddedEventName), err)
}

func TestHandleObjectTagsAddedWhenDoesNotContainVirusScanTag(t *testing.T) {
func TestHandleObjectTagsAddedWhenS3ClientGetObjectTagsError(t *testing.T) {
event := Event{
S3Event: events.S3Event{Records: []events.S3EventRecord{
{S3: events.S3Entity{Object: events.S3Object{Key: "M-1111-2222-3333/evidence/a-uid"}}},
}},
}

now := time.Now()

s3Client := newMockS3Client(t)
s3Client.
On("GetObjectTags", ctx, "M-1111-2222-3333/evidence/a-uid").
Return([]types.Tag{
{Key: aws.String("not-virus-scan-status")},
}, nil)
Return([]types.Tag{}, expectedError)

err := handleObjectTagsAdded(ctx, nil, event.S3Event, func() time.Time { return now }, s3Client)
assert.Nil(t, err)
err := handleObjectTagsAdded(ctx, nil, event.S3Event, s3Client, nil)
assert.Equal(t, fmt.Errorf("failed to get tags for object in '%s': %w", objectTagsAddedEventName, expectedError), err)
}

func TestHandleObjectTagsAddedWhenLpaEvidenceDoesNotContainDocument(t *testing.T) {
func TestHandleObjectTagsAddedWhenDynamoClientOneByUIDError(t *testing.T) {
event := Event{
S3Event: events.S3Event{Records: []events.S3EventRecord{
{S3: events.S3Entity{Object: events.S3Object{Key: "M-1111-2222-3333/evidence/a-uid"}}},
}},
}

now := time.Now()

s3Client := newMockS3Client(t)
s3Client.
On("GetObjectTags", ctx, "M-1111-2222-3333/evidence/a-uid").
Expand All @@ -438,31 +439,20 @@ func TestHandleObjectTagsAddedWhenLpaEvidenceDoesNotContainDocument(t *testing.T
Return(func(ctx context.Context, uid string, v interface{}) error {
b, _ := json.Marshal(dynamo.Key{PK: "LPA#123", SK: "#DONOR#456"})
json.Unmarshal(b, v)
return nil
})
dynamoClient.
On("One", ctx, "LPA#123", "#DONOR#456", mock.Anything).
Return(func(ctx context.Context, pk, sk string, v interface{}) error {
b, _ := json.Marshal(page.Lpa{PK: "LPA#123", SK: "#DONOR#456", Evidence: page.Evidence{
Documents: []page.Document{{Key: "M-1111-2222-3333/evidence/a-different-uid"}},
}})
json.Unmarshal(b, v)
return nil
return expectedError
})

err := handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, func() time.Time { return now }, s3Client)
assert.Equal(t, fmt.Errorf("LPA did not contain a document with key %s for 'ObjectTagging:Put'", "M-1111-2222-3333/evidence/a-uid"), err)
err := handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, s3Client, nil)
assert.Equal(t, fmt.Errorf("failed to resolve uid for '%s': %w", objectTagsAddedEventName, expectedError), err)
}

func TestHandleObjectTagsAddedWhenDynamoPutError(t *testing.T) {
func TestHandleObjectTagsAddedWhenDocumentStoreUpdateScanResultsError(t *testing.T) {
event := Event{
S3Event: events.S3Event{Records: []events.S3EventRecord{
{S3: events.S3Entity{Object: events.S3Object{Key: "M-1111-2222-3333/evidence/a-uid"}}},
}},
}

now := time.Now()

s3Client := newMockS3Client(t)
s3Client.
On("GetObjectTags", ctx, "M-1111-2222-3333/evidence/a-uid").
Expand All @@ -478,29 +468,18 @@ func TestHandleObjectTagsAddedWhenDynamoPutError(t *testing.T) {
json.Unmarshal(b, v)
return nil
})
dynamoClient.
On("One", ctx, "LPA#123", "#DONOR#456", mock.Anything).
Return(func(ctx context.Context, pk, sk string, v interface{}) error {
b, _ := json.Marshal(page.Lpa{PK: "LPA#123", SK: "#DONOR#456", Evidence: page.Evidence{
Documents: []page.Document{{Key: "M-1111-2222-3333/evidence/a-uid"}},
}})
json.Unmarshal(b, v)
return nil
})
dynamoClient.
On("Put", ctx, page.Lpa{PK: "LPA#123", SK: "#DONOR#456", UpdatedAt: now, Evidence: page.Evidence{
Documents: []page.Document{{Key: "M-1111-2222-3333/evidence/a-uid", Scanned: now, VirusDetected: false}},
}}).

documentStore := newMockDocumentStore(t)
documentStore.
On("UpdateScanResults", ctx, "LPA#123", "#DOCUMENT#M-1111-2222-3333/evidence/a-uid", false).
Return(expectedError)

err := handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, func() time.Time { return now }, s3Client)
assert.Equal(t, fmt.Errorf("failed to update LPA for 'ObjectTagging:Put': %w", expectedError), err)
err := handleObjectTagsAdded(ctx, dynamoClient, event.S3Event, s3Client, documentStore)
assert.Equal(t, fmt.Errorf("failed to update scan results for '%s': %w", objectTagsAddedEventName, expectedError), err)
}

func TestGetLpaByUID(t *testing.T) {
expectedLpa := page.Lpa{PK: "LPA#123", SK: "#DONOR#456", Evidence: page.Evidence{
Documents: []page.Document{{Key: "document/key"}},
}}
expectedLpa := page.Lpa{PK: "LPA#123", SK: "#DONOR#456"}

client := newMockDynamodbClient(t)
client.
Expand Down
43 changes: 43 additions & 0 deletions cmd/event-received/mock_DocumentStore_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading