mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-02 22:34:56 -04:00
cli: state file validation (#2523)
* re-use `ReadFromFile` in `CreateOrRead` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip]: add constraints Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] error formatting Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * wip Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * formatted error messages Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * state file validation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * allow overriding the constraints Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * dont validate on read Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add pre-create constraints Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * finish pre-init validation test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * finish post-init validation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use state file validation in CLI Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix apply tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Update internal/validation/errors.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * use transformator for tests * tidy * use empty check directly Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * conditional validation per CSP Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix rebase Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add default case Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * validate state-file as last input Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
This commit is contained in:
parent
eaec73cca4
commit
744a605602
21 changed files with 1779 additions and 247 deletions
|
@ -14,34 +14,39 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var validDoc = func() *exampleDoc {
|
||||
return &exampleDoc{
|
||||
StrField: "abc",
|
||||
NumField: 42,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
OneOfField: "one",
|
||||
OrLeftField: "left",
|
||||
OrRightField: "right",
|
||||
AndLeftField: "left",
|
||||
AndRightField: "right",
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
doc Validatable
|
||||
doc func() *exampleDoc
|
||||
opts ValidateOptions
|
||||
wantErr bool
|
||||
errAssertion func(*assert.Assertions, error) bool
|
||||
}{
|
||||
"valid": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "abc",
|
||||
NumField: 42,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
},
|
||||
doc: validDoc,
|
||||
opts: ValidateOptions{},
|
||||
},
|
||||
"strField is not abc": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "def",
|
||||
NumField: 42,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.StrField = "def"
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
|
@ -50,14 +55,10 @@ func TestValidate(t *testing.T) {
|
|||
opts: ValidateOptions{},
|
||||
},
|
||||
"numField is not 42": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "abc",
|
||||
NumField: 43,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.NumField = 43
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
|
@ -65,14 +66,11 @@ func TestValidate(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"multiple errors": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "def",
|
||||
NumField: 43,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.StrField = "def"
|
||||
doc.NumField = 43
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
|
@ -82,75 +80,108 @@ func TestValidate(t *testing.T) {
|
|||
opts: ValidateOptions{},
|
||||
},
|
||||
"multiple errors, fail fast": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "def",
|
||||
NumField: 43,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.StrField = "def"
|
||||
doc.NumField = 43
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "validating exampleDoc.strField: def must be abc")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
FailFast: true,
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
"map field is not empty": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "abc",
|
||||
NumField: 42,
|
||||
MapField: &map[string]string{
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.MapField = &map[string]string{
|
||||
"empty": "haha!",
|
||||
},
|
||||
NotEmptyField: "certainly not.",
|
||||
MatchRegexField: "abc",
|
||||
}
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "validating exampleDoc.mapField[\"empty\"]: haha! must be empty")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
FailFast: true,
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
"empty field is not empty": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "abc",
|
||||
NumField: 42,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "",
|
||||
MatchRegexField: "abc",
|
||||
"not empty field is empty": {
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.NotEmptyField = ""
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "validating exampleDoc.notEmptyField: must not be empty")
|
||||
return assert.Contains(err.Error(), "validating exampleDoc.notEmptyField: must not be empty")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
FailFast: true,
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
"regex doesnt match": {
|
||||
doc: &exampleDoc{
|
||||
StrField: "abc",
|
||||
NumField: 42,
|
||||
MapField: &map[string]string{
|
||||
"empty": "",
|
||||
},
|
||||
NotEmptyField: "certainly not!",
|
||||
MatchRegexField: "dontmatch",
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.MatchRegexField = "dontmatch"
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "validating exampleDoc.matchRegexField: dontmatch must match the pattern ^a.c$")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
FailFast: true,
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
"field is not in 'oneof' values": {
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.OneOfField = "not in oneof"
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "validating exampleDoc.oneOfField: not in oneof must be one of [one two three]")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
"'or' violated": {
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.OrLeftField = "not left"
|
||||
doc.OrRightField = "not right"
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "at least one of the constraints must be satisfied:") &&
|
||||
assert.Contains(err.Error(), "validating exampleDoc.orLeftField: not left must be equal to left") &&
|
||||
assert.Contains(err.Error(), "validating exampleDoc.orRightField: not right must be equal to right")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
"'and' violated": {
|
||||
doc: func() *exampleDoc {
|
||||
doc := validDoc()
|
||||
doc.AndRightField = "not right"
|
||||
return doc
|
||||
},
|
||||
wantErr: true,
|
||||
errAssertion: func(assert *assert.Assertions, err error) bool {
|
||||
return assert.Contains(err.Error(), "all of the constraints must be satisfied:") &&
|
||||
assert.Contains(err.Error(), "validating exampleDoc.andRightField: not right must be equal to right")
|
||||
},
|
||||
opts: ValidateOptions{
|
||||
ErrStrategy: FailFast,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -160,7 +191,7 @@ func TestValidate(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
err := NewValidator().Validate(tc.doc, tc.opts)
|
||||
err := NewValidator().Validate(tc.doc(), tc.opts)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
if !tc.errAssertion(assert, err) {
|
||||
|
@ -179,13 +210,18 @@ type exampleDoc struct {
|
|||
MapField *map[string]string `json:"mapField"`
|
||||
NotEmptyField string `json:"notEmptyField"`
|
||||
MatchRegexField string `json:"matchRegexField"`
|
||||
OneOfField string `json:"oneOfField"`
|
||||
OrLeftField string `json:"orLeftField"`
|
||||
OrRightField string `json:"orRightField"`
|
||||
AndLeftField string `json:"andLeftField"`
|
||||
AndRightField string `json:"andRightField"`
|
||||
}
|
||||
|
||||
// Constraints implements the Validatable interface.
|
||||
func (d *exampleDoc) Constraints() []Constraint {
|
||||
func (d *exampleDoc) Constraints() []*Constraint {
|
||||
mapField := *(d.MapField)
|
||||
|
||||
return []Constraint{
|
||||
return []*Constraint{
|
||||
d.strFieldNeedsToBeAbc().
|
||||
WithFieldTrace(d, &d.StrField),
|
||||
Equal(d.NumField, 42).
|
||||
|
@ -196,17 +232,95 @@ func (d *exampleDoc) Constraints() []Constraint {
|
|||
WithFieldTrace(d, &d.NotEmptyField),
|
||||
MatchRegex(d.MatchRegexField, "^a.c$").
|
||||
WithFieldTrace(d, &d.MatchRegexField),
|
||||
OneOf(d.OneOfField, []string{"one", "two", "three"}).
|
||||
WithFieldTrace(d, &d.OneOfField),
|
||||
Or(
|
||||
Equal(d.OrLeftField, "left").
|
||||
WithFieldTrace(d, &d.OrLeftField),
|
||||
Equal(d.OrRightField, "right").
|
||||
WithFieldTrace(d, &d.OrRightField),
|
||||
),
|
||||
And(
|
||||
EvaluateAll,
|
||||
Equal(d.AndLeftField, "left").
|
||||
WithFieldTrace(d, &d.AndLeftField),
|
||||
Equal(d.AndRightField, "right").
|
||||
WithFieldTrace(d, &d.AndRightField),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// StrFieldNeedsToBeAbc is an example for a custom constraint.
|
||||
func (d *exampleDoc) strFieldNeedsToBeAbc() *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() error {
|
||||
Satisfied: func() *TreeError {
|
||||
if d.StrField != "abc" {
|
||||
return fmt.Errorf("%s must be abc", d.StrField)
|
||||
return NewErrorTree(
|
||||
fmt.Errorf("%s must be abc", d.StrField),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideConstraints(t *testing.T) {
|
||||
overrideConstraints := func(t *testing.T, wantCalled bool) func() []*Constraint {
|
||||
return func() []*Constraint {
|
||||
if !wantCalled {
|
||||
t.Fatal("overrideConstraints should not be called")
|
||||
}
|
||||
return []*Constraint{}
|
||||
}
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
doc exampleDocToOverride
|
||||
overrideFunc func() []*Constraint
|
||||
wantOverrideCalled bool
|
||||
wantErr bool
|
||||
}{
|
||||
"override constraints": {
|
||||
doc: exampleDocToOverride{},
|
||||
overrideFunc: overrideConstraints(t, true),
|
||||
wantOverrideCalled: true,
|
||||
},
|
||||
"do not override constraints": {
|
||||
doc: exampleDocToOverride{
|
||||
calledDocConstraints: true,
|
||||
},
|
||||
overrideFunc: nil,
|
||||
wantOverrideCalled: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
validator := NewValidator()
|
||||
err := validator.Validate(&tc.doc, ValidateOptions{
|
||||
OverrideConstraints: tc.overrideFunc,
|
||||
})
|
||||
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
} else {
|
||||
require.NoError(err)
|
||||
if tc.wantOverrideCalled {
|
||||
assert.Equal(tc.doc.calledDocConstraints, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type exampleDocToOverride struct {
|
||||
calledDocConstraints bool
|
||||
}
|
||||
|
||||
func (d *exampleDocToOverride) Constraints() []*Constraint {
|
||||
d.calledDocConstraints = true
|
||||
return []*Constraint{}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue