Skip to content

Commit

Permalink
VEGA-2667 correction when changing donor details (#272)
Browse files Browse the repository at this point in the history
* VEGA-2667 correction when changing donor details

* Make fields optional as not all fields will have changes for correction

* add correction test file

* Add correction json file

* Amend json file

* Only allow lpa signed on date to change if paper channel, add test coverage

* Stop registered LPAs from getting corrections
  • Loading branch information
MishNajam authored Nov 15, 2024
1 parent 444d36b commit 8949811
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 0 deletions.
45 changes: 45 additions & 0 deletions docs/correction.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"type": "CORRECTION",
"changes": [
{
"key": "/donor/firstNames",
"new": "Sam",
"old": "Sammy"
},
{
"key": "/donor/lastName",
"new": "Smith",
"old": "Smithy"
},
{
"key": "/donor/address/dateOfBirth",
"new": "2000-01-01",
"old": "1999-11-11"
},
{
"key": "/donor/address/line1",
"new": "Somewhere",
"old": "Anywhere"
},
{
"key": "/donor/address/town",
"new": "London",
"old": "Birmingham"
},
{
"key": "/donor/address/postcode",
"new": "E1 1AA",
"old": "B1 1AA"
},
{
"key": "/donor/address/country",
"new": "GB",
"old": ""
},
{
"key": "/signedAt",
"new": "2024-01-13T22:00:00Z",
"old": ""
}
]
}
81 changes: 81 additions & 0 deletions lambda/update/correction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package main

import (
"github.com/ministryofjustice/opg-data-lpa-store/internal/shared"
"github.com/ministryofjustice/opg-data-lpa-store/internal/validate"
"github.com/ministryofjustice/opg-data-lpa-store/lambda/update/parse"
"time"
)

type Correction struct {
DonorFirstNames string
DonorLastName string
DonorOtherNames string
DonorDob shared.Date
DonorAddress shared.Address
DonorEmail string
LPASignedAt time.Time
}

func (c Correction) Apply(lpa *shared.Lpa) []shared.FieldError {
if !c.LPASignedAt.IsZero() && lpa.Channel == shared.ChannelOnline {
return []shared.FieldError{{Source: "/signedAt", Detail: "LPA Signed on date cannot be changed for online LPAs"}}
}

if lpa.Status == shared.LpaStatusRegistered {
return []shared.FieldError{{Source: "/type", Detail: "Cannot make corrections to a Registered LPA"}}
}

lpa.Donor.FirstNames = c.DonorFirstNames
lpa.Donor.LastName = c.DonorLastName
lpa.Donor.OtherNamesKnownBy = c.DonorOtherNames
lpa.Donor.DateOfBirth = c.DonorDob
lpa.Donor.Address = c.DonorAddress
lpa.Donor.Email = c.DonorEmail
lpa.SignedAt = c.LPASignedAt

return nil
}

func validateCorrection(changes []shared.Change, lpa *shared.Lpa) (Correction, []shared.FieldError) {
var data Correction

data.DonorFirstNames = lpa.LpaInit.Donor.FirstNames
data.DonorLastName = lpa.LpaInit.Donor.LastName
data.DonorOtherNames = lpa.LpaInit.Donor.OtherNamesKnownBy
data.DonorDob = lpa.LpaInit.Donor.DateOfBirth
data.DonorAddress = lpa.LpaInit.Donor.Address
data.DonorEmail = lpa.LpaInit.Donor.Email
data.LPASignedAt = lpa.LpaInit.SignedAt

errors := parse.Changes(changes).
Prefix("/donor/address", func(p *parse.Parser) []shared.FieldError {
return p.
Field("/line1", &data.DonorAddress.Line1, parse.Optional()).
Field("/line2", &data.DonorAddress.Line2, parse.Optional()).
Field("/line3", &data.DonorAddress.Line3, parse.Optional()).
Field("/town", &data.DonorAddress.Town, parse.Optional()).
Field("/postcode", &data.DonorAddress.Postcode, parse.Optional()).
Field("/country", &data.DonorAddress.Country, parse.Validate(func() []shared.FieldError {
return validate.Country("", data.DonorAddress.Country)
}), parse.Optional()).
Consumed()
}, parse.Optional()).
Field("/donor/firstNames", &data.DonorFirstNames, parse.Validate(func() []shared.FieldError {
return validate.Required("", data.DonorFirstNames)
}), parse.Optional()).
Field("/donor/lastName", &data.DonorLastName, parse.Validate(func() []shared.FieldError {
return validate.Required("", data.DonorLastName)
}), parse.Optional()).
Field("/donor/otherNamesKnownBy", &data.DonorOtherNames, parse.Optional()).
Field("/donor/email", &data.DonorEmail, parse.Optional()).
Field("/donor/dateOfBirth", &data.DonorDob, parse.Validate(func() []shared.FieldError {
return validate.Date("", data.DonorDob)
}), parse.Optional()).
Field("/signedAt", &data.LPASignedAt, parse.Validate(func() []shared.FieldError {
return validate.Time("", data.LPASignedAt)
}), parse.Optional()).
Consumed()

return data, errors
}
169 changes: 169 additions & 0 deletions lambda/update/correction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package main

import (
"encoding/json"
"testing"
"time"

"github.com/ministryofjustice/opg-data-lpa-store/internal/shared"
"github.com/stretchr/testify/assert"
)

func createDate(date string) shared.Date {
d := shared.Date{}
_ = d.UnmarshalText([]byte(date))
return d
}

func TestCorrectionApply(t *testing.T) {
now := time.Now()
yesterday := now.Add(-24 * time.Hour)
lpa := &shared.Lpa{
LpaInit: shared.LpaInit{
Donor: shared.Donor{
Person: shared.Person{
FirstNames: "donor-firstname",
LastName: "donor-lastname",
},
OtherNamesKnownBy: "donor-otherNames",
DateOfBirth: createDate("1990-01-02"),
Address: shared.Address{
Line1: "123 Main St",
Town: "Anytown",
Postcode: "A11 B22",
Country: "IE",
},
Email: "[email protected]",
},
SignedAt: yesterday,
},
}

correction := Correction{
DonorFirstNames: "Jane",
DonorLastName: "Smith",
DonorOtherNames: "Janey",
DonorDob: createDate("2000-11-11"),
DonorAddress: shared.Address{
Line1: "456 Another St",
Town: "Othertown",
Postcode: "B22 A11",
Country: "IE",
},
DonorEmail: "[email protected]",
LPASignedAt: now,
}

errors := correction.Apply(lpa)

assert.Empty(t, errors)
assert.Equal(t, correction.DonorFirstNames, lpa.Donor.FirstNames)
assert.Equal(t, correction.DonorLastName, lpa.Donor.LastName)
assert.Equal(t, correction.DonorOtherNames, lpa.Donor.OtherNamesKnownBy)
assert.Equal(t, correction.DonorDob, lpa.Donor.DateOfBirth)
assert.Equal(t, correction.DonorAddress, lpa.Donor.Address)
assert.Equal(t, correction.DonorEmail, lpa.Donor.Email)
assert.Equal(t, correction.LPASignedAt, lpa.SignedAt)
}

func TestCorrectionRegisteredLpa(t *testing.T) {
lpa := &shared.Lpa{
Status: shared.LpaStatusRegistered,
LpaInit: shared.LpaInit{
Channel: "paper",
Donor: shared.Donor{
Person: shared.Person{
FirstNames: "donor-firstname",
},
},
},
}

correction := Correction{
DonorFirstNames: "Jane",
}
errors := correction.Apply(lpa)

assert.Equal(t, errors, []shared.FieldError{{Source: "/type", Detail: "Cannot make corrections to a Registered LPA"}})
}

func TestCorrectionLpaSignedOnlineChannel(t *testing.T) {
now := time.Now()
yesterday := now.Add(-24 * time.Hour)
lpa := &shared.Lpa{
LpaInit: shared.LpaInit{
Channel: "online",
SignedAt: yesterday,
},
}

correction := Correction{
LPASignedAt: now,
}
errors := correction.Apply(lpa)

assert.Equal(t, errors, []shared.FieldError{{Source: "/signedAt", Detail: "LPA Signed on date cannot be changed for online LPAs"}})
}

func TestValidateCorrection(t *testing.T) {
now := time.Now()

testcases := map[string]struct {
changes []shared.Change
lpa *shared.Lpa
errors []shared.FieldError
}{
"valid update": {
changes: []shared.Change{
{Key: "/donor/firstNames", New: json.RawMessage(`"Jane"`), Old: jsonNull},
{Key: "/donor/lastName", New: json.RawMessage(`"Doe"`), Old: jsonNull},
{Key: "/donor/otherNamesKnownBy", New: json.RawMessage(`"Janey"`), Old: jsonNull},
{Key: "/donor/email", New: json.RawMessage(`"[email protected]"`), Old: jsonNull},
{Key: "/donor/dateOfBirth", New: json.RawMessage(`"2000-01-01"`), Old: jsonNull},
{Key: "/signedAt", New: json.RawMessage(`"` + now.Format(time.RFC3339Nano) + `"`), Old: jsonNull},
},
lpa: &shared.Lpa{
LpaInit: shared.LpaInit{
Donor: shared.Donor{},
},
},
},
"missing required fields": {
changes: []shared.Change{
{Key: "/donor/firstNames", New: jsonNull, Old: jsonNull},
{Key: "/donor/lastName", New: jsonNull, Old: jsonNull},
},
lpa: &shared.Lpa{
LpaInit: shared.LpaInit{
Donor: shared.Donor{},
},
},
errors: []shared.FieldError{
{Source: "/changes/0/new", Detail: "field is required"},
{Source: "/changes/1/new", Detail: "field is required"},
},
},
"invalid country": {
changes: []shared.Change{
{Key: "/donor/address/country", New: json.RawMessage(`"United Kingdom"`), Old: jsonNull},
},
lpa: &shared.Lpa{
LpaInit: shared.LpaInit{
Donor: shared.Donor{
Address: shared.Address{},
},
},
},
errors: []shared.FieldError{
{Source: "/changes/0/new", Detail: "must be a valid ISO-3166-1 country code"},
},
},
}

for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
_, errors := validateCorrection(tc.changes, tc.lpa)
assert.ElementsMatch(t, tc.errors, errors)
})
}
}
8 changes: 8 additions & 0 deletions lambda/update/parse/changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ func oldEqualsExisting(old any, existing any) bool {

return shared.LpaStatus(old.(string)) == *v

case *shared.Date:
if old == nil {
return v.IsZero()
}
oldDate := &shared.Date{}
_ = oldDate.UnmarshalText([]byte(old.(string)))
return *oldDate == *v

case *string:
if old == nil {
return *v == ""
Expand Down
13 changes: 13 additions & 0 deletions lambda/update/parse/changes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ func TestFieldOldChannel(t *testing.T) {
assert.Equal(t, shared.ChannelOnline, v)
}

func TestFieldOldDate(t *testing.T) {
oldDate, newDate := shared.Date{}, shared.Date{}
_ = oldDate.UnmarshalText([]byte("2000-11-10"))
_ = newDate.UnmarshalText([]byte("1990-01-02"))

changes := []shared.Change{
{Key: "/thing", New: json.RawMessage(`"1990-01-02"`), Old: json.RawMessage(`"2000-11-10"`)},
}

Changes(changes).Field("/thing", &oldDate)
assert.Equal(t, &newDate, &oldDate)
}

func TestFieldWhenOldDoesNotMatchExisting(t *testing.T) {
testcases := map[string]json.RawMessage{
"string": json.RawMessage(`"not same as existing"`),
Expand Down
2 changes: 2 additions & 0 deletions lambda/update/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func validateUpdate(update shared.Update, lpa *shared.Lpa) (Applyable, []shared.
return validateAttorneyOptOut(update)
case "TRUST_CORPORATION_OPT_OUT":
return validateTrustCorporationOptOut(update)
case "CORRECTION":
return validateCorrection(update.Changes, lpa)
default:
return nil, []shared.FieldError{{Source: "/type", Detail: "invalid value"}}
}
Expand Down

0 comments on commit 8949811

Please sign in to comment.