From 8949811e4eb2cbfa50fc41bdfb3df4f25be89485 Mon Sep 17 00:00:00 2001 From: MishNajam <61416092+MishNajam@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:52:18 +0000 Subject: [PATCH] VEGA-2667 correction when changing donor details (#272) * 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 --- docs/correction.json | 45 ++++++++ lambda/update/correction.go | 81 +++++++++++++ lambda/update/correction_test.go | 169 ++++++++++++++++++++++++++++ lambda/update/parse/changes.go | 8 ++ lambda/update/parse/changes_test.go | 13 +++ lambda/update/validate.go | 2 + 6 files changed, 318 insertions(+) create mode 100644 docs/correction.json create mode 100644 lambda/update/correction.go create mode 100644 lambda/update/correction_test.go diff --git a/docs/correction.json b/docs/correction.json new file mode 100644 index 00000000..473d0e22 --- /dev/null +++ b/docs/correction.json @@ -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": "" + } + ] +} diff --git a/lambda/update/correction.go b/lambda/update/correction.go new file mode 100644 index 00000000..1c149bf7 --- /dev/null +++ b/lambda/update/correction.go @@ -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 +} diff --git a/lambda/update/correction_test.go b/lambda/update/correction_test.go new file mode 100644 index 00000000..34b839e2 --- /dev/null +++ b/lambda/update/correction_test.go @@ -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: "john.doe@example.com", + }, + 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: "jane.smith@example.com", + 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(`"jane.doe@example.com"`), 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) + }) + } +} diff --git a/lambda/update/parse/changes.go b/lambda/update/parse/changes.go index b29a35b9..3102c210 100644 --- a/lambda/update/parse/changes.go +++ b/lambda/update/parse/changes.go @@ -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 == "" diff --git a/lambda/update/parse/changes_test.go b/lambda/update/parse/changes_test.go index 8ddb7865..6fc8f63d 100644 --- a/lambda/update/parse/changes_test.go +++ b/lambda/update/parse/changes_test.go @@ -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"`), diff --git a/lambda/update/validate.go b/lambda/update/validate.go index 630b77b6..00e9ff75 100644 --- a/lambda/update/validate.go +++ b/lambda/update/validate.go @@ -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"}} }