-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
VEGA-2485 Add update type <actor>_CONFIRM_IDENTITY #minor (#218)
* Add IdentityCheck property to donor and certificate provider * Add identity check complete change parser * Add ID check complete Apply() * Add ID_CHECK_COMPLETE to openapi spec * Fix unit test * Rename Date to CheckedAt * Incorporate @hawx's PR comments * Compare ID_CHECK_COMPLETED update fields with existing LPA * Add API test for update with ID_CHECK_COMPLETE * Make IdentityCheck a pointer so it can be excluded from JSON output * IdentityCheck is not always serialised now * Rename ID_CHECK_COMPLETE to <actor>_CONFIRM_IDENTITY * Change fieldname from 'date' to 'checkedAt' for CONFIRM_IDENTITY updates * Make the identity check explicit as a property * Incorporate improvements suggested by PR review * Add additional tests for CONFIRM_IDENTITY failure cases * Reference field is optional for CONFIRM_IDENTITY updates * Remove MustMatchExisting when validating CONFIRM_IDENTITY change fields This is now enforced for all changes. * Assert on presence of specific error messages
- Loading branch information
1 parent
b8d41ad
commit 973c839
Showing
9 changed files
with
359 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"type": "CERTIFICATE_PROVIDER_CONFIRM_IDENTITY", | ||
"changes": [ | ||
{ | ||
"key": "/certificateProvider/identityCheck/checkedAt", | ||
"new": "2024-01-13T22:00:00Z", | ||
"old": null | ||
}, | ||
{ | ||
"key": "/certificateProvider/identityCheck/type", | ||
"new": "one-login", | ||
"old": null | ||
}, | ||
{ | ||
"key": "/certificateProvider/identityCheck/reference", | ||
"new": "1234567890", | ||
"old": null | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"type": "DONOR_CONFIRM_IDENTITY", | ||
"changes": [ | ||
{ | ||
"key": "/donor/identityCheck/checkedAt", | ||
"new": "2024-01-13T22:00:00Z", | ||
"old": null | ||
}, | ||
{ | ||
"key": "/donor/identityCheck/type", | ||
"new": "one-login", | ||
"old": null | ||
}, | ||
{ | ||
"key": "/donor/identityCheck/reference", | ||
"new": "1234567890", | ||
"old": null | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
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" | ||
) | ||
|
||
type IdCheckComplete struct { | ||
Actor idccActor | ||
IdentityCheck *shared.IdentityCheck | ||
} | ||
|
||
type idccActor string | ||
|
||
var ( | ||
donor = idccActor("Donor") | ||
certificateProvider = idccActor("CertificateProvider") | ||
) | ||
|
||
func (idcc IdCheckComplete) Apply(lpa *shared.Lpa) []shared.FieldError { | ||
if idcc.Actor == donor { | ||
lpa.Donor.IdentityCheck = idcc.IdentityCheck | ||
} else { | ||
lpa.CertificateProvider.IdentityCheck = idcc.IdentityCheck | ||
} | ||
return nil | ||
} | ||
|
||
func validateConfirmIdentity(prefix string, actor idccActor, ic *shared.IdentityCheck, changes []shared.Change) (IdCheckComplete, []shared.FieldError) { | ||
var idcc IdCheckComplete | ||
|
||
errors := parse.Changes(changes). | ||
Prefix(prefix, func(p *parse.Parser) []shared.FieldError { | ||
idcc.Actor = actor | ||
|
||
if ic == nil { | ||
ic = &shared.IdentityCheck{} | ||
} | ||
idcc.IdentityCheck = ic | ||
|
||
return p. | ||
Field("/type", &ic.Type, parse.Validate(func() []shared.FieldError { | ||
return validate.IsValid("", ic.Type) | ||
})). | ||
Field("/checkedAt", &ic.CheckedAt, parse.Validate(func() []shared.FieldError { | ||
return validate.Time("", ic.CheckedAt) | ||
})). | ||
Field("/reference", &ic.Reference, parse.Optional()). | ||
Consumed() | ||
}). | ||
Consumed() | ||
|
||
return idcc, errors | ||
} | ||
|
||
func validateDonorConfirmIdentity(changes []shared.Change, lpa *shared.Lpa) (IdCheckComplete, []shared.FieldError) { | ||
return validateConfirmIdentity("/donor/identityCheck", donor, lpa.Donor.IdentityCheck, changes) | ||
} | ||
|
||
func validateCertificateProviderConfirmIdentity(changes []shared.Change, lpa *shared.Lpa) (IdCheckComplete, []shared.FieldError) { | ||
return validateConfirmIdentity("/certificateProvider/identityCheck", certificateProvider, lpa.CertificateProvider.IdentityCheck, changes) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ministryofjustice/opg-data-lpa-store/internal/shared" | ||
) | ||
|
||
func TestConfirmIdentityDonor(t *testing.T) { | ||
today := time.Now() | ||
|
||
changes := []shared.Change{ | ||
{ | ||
Key: "/donor/identityCheck/checkedAt", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"` + today.Format(time.RFC3339Nano) + `"`), | ||
}, | ||
{ | ||
Key: "/donor/identityCheck/reference", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"xyz"`), | ||
}, | ||
{ | ||
Key: "/donor/identityCheck/type", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"one-login"`), | ||
}, | ||
} | ||
|
||
idCheckComplete, errors := validateDonorConfirmIdentity(changes, &shared.Lpa{}) | ||
|
||
assert.Len(t, errors, 0) | ||
assert.Equal(t, "xyz", idCheckComplete.IdentityCheck.Reference) | ||
assert.Equal(t, shared.IdentityCheckTypeOneLogin, idCheckComplete.IdentityCheck.Type) | ||
assert.Equal(t, today.Format(time.RFC3339Nano), idCheckComplete.IdentityCheck.CheckedAt.Format(time.RFC3339Nano)) | ||
assert.Equal(t, donor, idCheckComplete.Actor) | ||
} | ||
|
||
func TestConfirmIdentityDonorBadFieldsFails(t *testing.T) { | ||
changes := []shared.Change{ | ||
// irrelevant field with no prefix | ||
{ | ||
Key: "/irrelevant", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"` + time.Now().Format(time.RFC3339Nano) + `"`), | ||
}, | ||
// irrelevant field with prefix | ||
{ | ||
Key: "/donor/identityCheck/irrelevant", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"` + time.Now().Format(time.RFC3339Nano) + `"`), | ||
}, | ||
// empty optional field - does not cause an error message | ||
{ | ||
Key: "/donor/identityCheck/reference", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`""`), | ||
}, | ||
// invalid value for field | ||
{ | ||
Key: "/donor/identityCheck/type", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"rinky-dink-login-system"`), | ||
}, | ||
} | ||
|
||
idCheckComplete, errors := validateDonorConfirmIdentity(changes, &shared.Lpa{}) | ||
|
||
assert.Len(t, errors, 4) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes", Detail: "missing /donor/identityCheck/checkedAt"}) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/0", Detail: "unexpected change provided"}) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/1", Detail: "unexpected change provided"}) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/3/new", Detail: "invalid value"}) | ||
assert.Equal(t, &shared.IdentityCheck{Type: "rinky-dink-login-system"}, idCheckComplete.IdentityCheck) | ||
assert.Equal(t, donor, idCheckComplete.Actor) | ||
} | ||
|
||
func TestConfirmIdentityDonorANDCertificateProviderFails(t *testing.T) { | ||
changes := []shared.Change{ | ||
{ | ||
Key: "/certificateProvider/identityCheck/checkedAt", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"` + time.Now().Format(time.RFC3339Nano) + `"`), | ||
}, | ||
{ | ||
Key: "/donor/identityCheck/reference", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"xyz"`), | ||
}, | ||
{ | ||
Key: "/donor/identityCheck/type", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"one-login"`), | ||
}, | ||
} | ||
|
||
idCheckComplete, errors := validateDonorConfirmIdentity(changes, &shared.Lpa{}) | ||
expectedIdCheckComplete := &shared.IdentityCheck{ | ||
Type: shared.IdentityCheckTypeOneLogin, | ||
Reference: "xyz", | ||
} | ||
|
||
assert.Len(t, errors, 2) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes", Detail: "missing /donor/identityCheck/checkedAt"}) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/0", Detail: "unexpected change provided"}) | ||
|
||
assert.Equal(t, expectedIdCheckComplete, idCheckComplete.IdentityCheck) | ||
assert.Equal(t, donor, idCheckComplete.Actor) | ||
} | ||
|
||
func TestConfirmIdentityDonorMismatchWithExistingLpaFails(t *testing.T) { | ||
changes := []shared.Change{ | ||
{ | ||
Key: "/donor/identityCheck/checkedAt", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"` + time.Now().Format(time.RFC3339Nano) + `"`), | ||
}, | ||
{ | ||
Key: "/donor/identityCheck/reference", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"xyz"`), | ||
}, | ||
{ | ||
Key: "/donor/identityCheck/type", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"one-login"`), | ||
}, | ||
} | ||
|
||
existingLpa := &shared.Lpa{ | ||
LpaInit: shared.LpaInit{ | ||
Donor: shared.Donor{ | ||
IdentityCheck: &shared.IdentityCheck{ | ||
CheckedAt: time.Now().AddDate(-1, 0, 0), | ||
Reference: "notxyz", | ||
Type: "not-one-login", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
idCheckComplete, errors := validateDonorConfirmIdentity(changes, existingLpa) | ||
|
||
assert.Len(t, errors, 3) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/0/old", Detail: "does not match existing value"}) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/1/old", Detail: "does not match existing value"}) | ||
assert.Contains(t, errors, shared.FieldError{Source: "/changes/2/old", Detail: "does not match existing value"}) | ||
assert.Equal(t, existingLpa.LpaInit.Donor.IdentityCheck, idCheckComplete.IdentityCheck) | ||
assert.Equal(t, donor, idCheckComplete.Actor) | ||
} | ||
|
||
func TestConfirmIdentityCertificateProvider(t *testing.T) { | ||
today := time.Now() | ||
|
||
changes := []shared.Change{ | ||
{ | ||
Key: "/certificateProvider/identityCheck/checkedAt", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"` + today.Format(time.RFC3339Nano) + `"`), | ||
}, | ||
{ | ||
Key: "/certificateProvider/identityCheck/reference", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"abn"`), | ||
}, | ||
{ | ||
Key: "/certificateProvider/identityCheck/type", | ||
Old: json.RawMessage("null"), | ||
New: json.RawMessage(`"opg-paper-id"`), | ||
}, | ||
} | ||
|
||
idCheckComplete, errors := validateCertificateProviderConfirmIdentity(changes, &shared.Lpa{}) | ||
|
||
assert.Len(t, errors, 0) | ||
assert.Equal(t, "abn", idCheckComplete.IdentityCheck.Reference) | ||
assert.Equal(t, shared.IdentityCheckTypeOpgPaperId, idCheckComplete.IdentityCheck.Type) | ||
assert.Equal(t, today.Format(time.RFC3339Nano), idCheckComplete.IdentityCheck.CheckedAt.Format(time.RFC3339Nano)) | ||
assert.Equal(t, certificateProvider, idCheckComplete.Actor) | ||
} | ||
|
||
func TestConfirmIdentityApplyDonor(t *testing.T) { | ||
check := IdCheckComplete{ | ||
Actor: donor, | ||
IdentityCheck: &shared.IdentityCheck{}, | ||
} | ||
|
||
lpa := shared.Lpa{} | ||
|
||
check.Apply(&lpa) | ||
|
||
assert.Equal(t, check.IdentityCheck, lpa.Donor.IdentityCheck) | ||
} | ||
|
||
func TestConfirmIdentityApplyCertificateProvider(t *testing.T) { | ||
check := IdCheckComplete{ | ||
Actor: certificateProvider, | ||
IdentityCheck: &shared.IdentityCheck{}, | ||
} | ||
|
||
lpa := shared.Lpa{} | ||
|
||
check.Apply(&lpa) | ||
|
||
assert.Equal(t, check.IdentityCheck, lpa.CertificateProvider.IdentityCheck) | ||
} |
Oops, something went wrong.