Skip to content

Commit

Permalink
MLPAB-1306 Delete or withdraw an LPA (#804)
Browse files Browse the repository at this point in the history
  • Loading branch information
hawx authored Oct 30, 2023
1 parent e62d126 commit d1d1726
Show file tree
Hide file tree
Showing 30 changed files with 862 additions and 65 deletions.
11 changes: 11 additions & 0 deletions cypress/e2e/donor/dashboard.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ describe('Dashboard', () => {
});
});

context('with withdrawn LPA', () => {
it('shows the no options', () => {
cy.visit('/fixtures?redirect=&progress=withdrawn');

cy.contains('Property and affairs');
cy.contains('Sam Smith');
cy.contains('strong', 'Withdrawn');
cy.contains('.app-dashboard-card a').should('not.exist');
});
});

context('with registered LPA', () => {
it('shows the correct options', () => {
cy.visit('/fixtures?redirect=&progress=registered');
Expand Down
17 changes: 17 additions & 0 deletions cypress/e2e/donor/delete-lpa.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
describe('Delete LPA', () => {
it('can be deleted', () => {
cy.visit('/fixtures?redirect=&progress=provideYourDetails');

cy.contains('Sam Smith');
cy.contains('a', 'Delete LPA').click();

cy.checkA11yApp();
cy.contains('button', 'Delete this LPA').click();

cy.checkA11yApp();
cy.contains('has been deleted');
cy.contains('a', 'Return to dashboard').click();

cy.contains('Sam Smith').should('not.exist');
});
});
17 changes: 17 additions & 0 deletions cypress/e2e/donor/withdraw-lpa.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
describe('Withdraw LPA', () => {
it('can be withdrawn', () => {
cy.visit('/fixtures?redirect=&progress=submitted');

cy.contains('Sam Smith');
cy.contains('a', 'Withdraw LPA').click();

cy.checkA11yApp();
cy.contains('button', 'Withdraw this LPA').click();

cy.checkA11yApp();
cy.contains('You have withdrawn');
cy.contains('a', 'Return to dashboard').click();

cy.contains('.app-dashboard-card', 'Sam Smith').contains('.govuk-tag', 'Withdrawn');
});
});
6 changes: 6 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ type DynamoClient interface {
LatestForActor(ctx context.Context, sk string, v interface{}) error
AllForActor(ctx context.Context, sk string, v interface{}) error
AllByKeys(ctx context.Context, pks []dynamo.Key) ([]map[string]dynamodbtypes.AttributeValue, error)
AllKeysByPk(ctx context.Context, pk string) ([]dynamo.Key, error)
Put(ctx context.Context, v interface{}) error
Create(ctx context.Context, v interface{}) error
DeleteKeys(ctx context.Context, keys []dynamo.Key) error
}

//go:generate mockery --testonly --inpackage --name S3Client --structname mockS3Client
Expand Down Expand Up @@ -128,6 +130,10 @@ func App(
page.Guidance(tmpls.Get("attorney_start.gohtml")))
handleRoot(page.Paths.Dashboard, RequireSession,
page.Dashboard(tmpls.Get("dashboard.gohtml"), donorStore, dashboardStore))
handleRoot(page.Paths.LpaDeleted, RequireSession,
page.Guidance(tmpls.Get("lpa_deleted.gohtml")))
handleRoot(page.Paths.LpaWithdrawn, RequireSession,
page.Guidance(tmpls.Get("lpa_withdrawn.gohtml")))

certificateprovider.Register(
rootMux,
Expand Down
30 changes: 30 additions & 0 deletions internal/app/donor_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,36 @@ func (s *donorStore) Put(ctx context.Context, lpa *page.Lpa) error {
return s.dynamoClient.Put(ctx, lpa)
}

func (s *donorStore) Delete(ctx context.Context) error {
data, err := page.SessionDataFromContext(ctx)
if err != nil {
return err
}

if data.SessionID == "" || data.LpaID == "" {
return errors.New("donorStore.Create requires SessionID and LpaID")
}

keys, err := s.dynamoClient.AllKeysByPk(ctx, lpaKey(data.LpaID))
if err != nil {
return err
}

canDelete := false
for _, key := range keys {
if key.PK == lpaKey(data.LpaID) && key.SK == donorKey(data.SessionID) {
canDelete = true
break
}
}

if !canDelete {
return errors.New("cannot access data of another donor")
}

return s.dynamoClient.DeleteKeys(ctx, keys)
}

func lpaKey(s string) string {
return "LPA#" + s
}
Expand Down
91 changes: 91 additions & 0 deletions internal/app/donor_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,3 +692,94 @@ func TestDonorStoreCreateWhenError(t *testing.T) {
})
}
}

func TestDonorStoreDelete(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id", LpaID: "123"})

keys := []dynamo.Key{
{PK: "LPA#123", SK: "sk1"},
{PK: "LPA#123", SK: "sk2"},
{PK: "LPA#123", SK: "#DONOR#an-id"},
}

dynamoClient := newMockDynamoClient(t)
dynamoClient.
On("AllKeysByPk", ctx, "LPA#123").
Return(keys, nil)
dynamoClient.
On("DeleteKeys", ctx, keys).
Return(nil)

donorStore := &donorStore{dynamoClient: dynamoClient}

err := donorStore.Delete(ctx)
assert.Nil(t, err)
}

func TestDonorStoreDeleteWhenOtherDonor(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id", LpaID: "123"})

keys := []dynamo.Key{
{PK: "LPA#123", SK: "sk1"},
{PK: "LPA#123", SK: "sk2"},
{PK: "LPA#123", SK: "#DONOR#another-id"},
}

dynamoClient := newMockDynamoClient(t)
dynamoClient.
On("AllKeysByPk", ctx, "LPA#123").
Return(keys, nil)

donorStore := &donorStore{dynamoClient: dynamoClient}

err := donorStore.Delete(ctx)
assert.NotNil(t, err)
}

func TestDonorStoreDeleteWhenAllKeysByPkErrors(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id", LpaID: "123"})

dynamoClient := newMockDynamoClient(t)
dynamoClient.
On("AllKeysByPk", ctx, "LPA#123").
Return(nil, expectedError)

donorStore := &donorStore{dynamoClient: dynamoClient}

err := donorStore.Delete(ctx)
assert.Equal(t, expectedError, err)
}

func TestDonorStoreDeleteWhenDeleteKeysErrors(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id", LpaID: "123"})

dynamoClient := newMockDynamoClient(t)
dynamoClient.
On("AllKeysByPk", ctx, "LPA#123").
Return([]dynamo.Key{{PK: "LPA#123", SK: "#DONOR#an-id"}}, nil)
dynamoClient.
On("DeleteKeys", ctx, mock.Anything).
Return(expectedError)

donorStore := &donorStore{dynamoClient: dynamoClient}

err := donorStore.Delete(ctx)
assert.Equal(t, expectedError, err)
}

func TestDonorStoreDeleteWhenSessionMissing(t *testing.T) {
testcases := map[string]context.Context{
"missing": context.Background(),
"no LpaID": page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id"}),
"no SessionID": page.ContextWithSessionData(context.Background(), &page.SessionData{LpaID: "123"}),
}

for name, ctx := range testcases {
t.Run(name, func(t *testing.T) {
donorStore := &donorStore{}

err := donorStore.Delete(ctx)
assert.NotNil(t, err)
})
}
}
40 changes: 40 additions & 0 deletions internal/app/mock_DynamoClient_test.go

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

43 changes: 43 additions & 0 deletions internal/dynamo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,27 @@ type Key struct {
SK string
}

func (c *Client) AllKeysByPk(ctx context.Context, pk string) ([]Key, error) {
response, err := c.svc.Query(ctx, &dynamodb.QueryInput{
TableName: aws.String(c.table),
ExpressionAttributeNames: map[string]string{"#PK": "PK"},
ExpressionAttributeValues: map[string]types.AttributeValue{
":PK": &types.AttributeValueMemberS{Value: pk},
},
KeyConditionExpression: aws.String("#PK = :PK"),
ProjectionExpression: aws.String("PK, SK"),
})

if err != nil {
return nil, err
}

var keys []Key
err = attributevalue.UnmarshalListOfMaps(response.Items, &keys)

return keys, err
}

func (c *Client) AllByKeys(ctx context.Context, keys []Key) ([]map[string]types.AttributeValue, error) {
var keyAttrs []map[string]types.AttributeValue
for _, key := range keys {
Expand Down Expand Up @@ -245,3 +266,25 @@ func (c *Client) Create(ctx context.Context, v interface{}) error {

return err
}

func (c *Client) DeleteKeys(ctx context.Context, keys []Key) error {
items := make([]types.TransactWriteItem, len(keys))

for i, key := range keys {
items[i] = types.TransactWriteItem{
Delete: &types.Delete{
TableName: aws.String(c.table),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: key.PK},
"SK": &types.AttributeValueMemberS{Value: key.SK},
},
},
}
}

_, err := c.svc.TransactWriteItems(ctx, &dynamodb.TransactWriteItemsInput{
TransactItems: items,
})

return err
}
Loading

0 comments on commit d1d1726

Please sign in to comment.