constellation/internal/validation/errors_test.go

477 lines
14 KiB
Go
Raw Normal View History

validation: add generic validation framework (#2480) * [wip] validation framework Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] wip Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * working for shallow structs!!! Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix needle pointer deref Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add comment Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix nested structs Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix nested struct pointers Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix slices / arrays Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix struct parsing Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * extend tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * expose API Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * extend in-package documentation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix naming Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add missing license headers Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * align with review 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>
2023-10-24 05:38:05 -04:00
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package validation
import (
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Tests for primitive / shallow fields
func TestNewValidationErrorSingleField(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
}
doc, field := references(t, st, &st.OtherField, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.otherField: %s", assert.AnError))
}
func TestNewValidationErrorSingleFieldPtr(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
PointerField: new(int),
}
doc, field := references(t, st, &st.PointerField, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.pointerField: %s", assert.AnError))
}
func TestNewValidationErrorSingleFieldDoublePtr(t *testing.T) {
intp := new(int)
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
DoublePointerField: &intp,
}
doc, field := references(t, st, &st.DoublePointerField, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.doublePointerField: %s", assert.AnError))
}
func TestNewValidationErrorSingleFieldInexistent(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
PointerField: new(int),
}
inexistentField := 123
doc, field := references(t, st, &inexistentField, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), "cannot find path to field: cannot traverse anymore")
}
// Tests for nested structs
func TestNewValidationErrorNestedField(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NestedField: nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
},
}
doc, field := references(t, st, &st.NestedField.OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.otherField: %s", assert.AnError))
}
func TestNewValidationErrorPointerInNestedField(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NestedField: nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
PointerField: new(int),
},
}
doc, field := references(t, st, &st.NestedField.PointerField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.pointerField: %s", assert.AnError))
}
func TestNewValidationErrorNestedFieldPtr(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NestedField: nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
},
NestedPointerField: &nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
},
}
doc, field := references(t, st, &st.NestedPointerField.OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedPointerField.otherField: %s", assert.AnError))
}
func TestNewValidationErrorNestedNestedField(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NestedField: nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
NestedField: nestedNestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
},
},
}
doc, field := references(t, st, &st.NestedField.NestedField.OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.nestedField.otherField: %s", assert.AnError))
}
func TestNewValidationErrorNestedNestedFieldPtr(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NestedField: nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
NestedPointerField: &nestedNestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
},
},
}
doc, field := references(t, st, &st.NestedField.NestedPointerField.OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.nestedPointerField.otherField: %s", assert.AnError))
}
func TestNewValidationErrorNestedPtrNestedFieldPtr(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NestedPointerField: &nestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
NestedPointerField: &nestedNestederrorTestDoc{
ExportedField: "nested",
OtherField: 123,
},
},
}
doc, field := references(t, st, &st.NestedPointerField.NestedPointerField.OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedPointerField.nestedPointerField.otherField: %s", assert.AnError))
}
// Tests for slices / arrays
func TestNewValidationErrorPrimitiveSlice(t *testing.T) {
st := &sliceErrorTestDoc{
PrimitiveSlice: []string{"abc", "def"},
}
doc, field := references(t, st, &st.PrimitiveSlice[1], "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.primitiveSlice[1]: %s", assert.AnError))
}
func TestNewValidationErrorPrimitiveArray(t *testing.T) {
st := &sliceErrorTestDoc{
PrimitiveArray: [3]int{1, 2, 3},
}
doc, field := references(t, st, &st.PrimitiveArray[1], "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.primitiveArray[1]: %s", assert.AnError))
}
func TestNewValidationErrorStructSlice(t *testing.T) {
st := &sliceErrorTestDoc{
StructSlice: []errorTestDoc{
{
ExportedField: "abc",
OtherField: 123,
},
{
ExportedField: "def",
OtherField: 456,
},
},
}
doc, field := references(t, st, &st.StructSlice[1].OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structSlice[1].otherField: %s", assert.AnError))
}
func TestNewValidationErrorStructArray(t *testing.T) {
st := &sliceErrorTestDoc{
StructArray: [3]errorTestDoc{
{
ExportedField: "abc",
OtherField: 123,
},
{
ExportedField: "def",
OtherField: 456,
},
},
}
doc, field := references(t, st, &st.StructArray[1].OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structArray[1].otherField: %s", assert.AnError))
}
func TestNewValidationErrorStructPointerSlice(t *testing.T) {
st := &sliceErrorTestDoc{
StructPointerSlice: []*errorTestDoc{
{
ExportedField: "abc",
OtherField: 123,
},
{
ExportedField: "def",
OtherField: 456,
},
},
}
doc, field := references(t, st, &st.StructPointerSlice[1].OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structPointerSlice[1].otherField: %s", assert.AnError))
}
func TestNewValidationErrorStructPointerArray(t *testing.T) {
st := &sliceErrorTestDoc{
StructPointerArray: [3]*errorTestDoc{
{
ExportedField: "abc",
OtherField: 123,
},
{
ExportedField: "def",
OtherField: 456,
},
},
}
doc, field := references(t, st, &st.StructPointerArray[1].OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structPointerArray[1].otherField: %s", assert.AnError))
}
func TestNewValidationErrorPrimitiveSliceSlice(t *testing.T) {
st := &sliceErrorTestDoc{
PrimitiveSliceSlice: [][]string{
{"abc", "def"},
{"ghi", "jkl"},
},
}
doc, field := references(t, st, &st.PrimitiveSliceSlice[1][1], "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.primitiveSliceSlice[1][1]: %s", assert.AnError))
}
// Tests for maps
func TestNewValidationErrorPrimitiveMap(t *testing.T) {
st := &mapErrorTestDoc{
PrimitiveMap: map[string]string{
"abc": "def",
"ghi": "jkl",
},
}
doc, field := references(t, st, &st.PrimitiveMap, "ghi")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating mapErrorTestDoc.primitiveMap[\"ghi\"]: %s", assert.AnError))
}
func TestNewValidationErrorStructPointerMap(t *testing.T) {
st := &mapErrorTestDoc{
StructPointerMap: map[string]*errorTestDoc{
"abc": {
ExportedField: "abc",
OtherField: 123,
},
"ghi": {
ExportedField: "ghi",
OtherField: 456,
},
},
}
doc, field := references(t, st, &st.StructPointerMap["ghi"].OtherField, "")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating mapErrorTestDoc.structPointerMap[\"ghi\"].otherField: %s", assert.AnError))
}
func TestNewValidationErrorNestedPrimitiveMap(t *testing.T) {
st := &mapErrorTestDoc{
NestedPointerMap: map[string]*map[string]string{
"abc": {
"def": "ghi",
},
"jkl": {
"mno": "pqr",
},
},
}
doc, field := references(t, st, st.NestedPointerMap["jkl"], "mno")
err := newError(doc, field, assert.AnError)
t.Log(err)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating mapErrorTestDoc.nestedPointerMap[\"jkl\"][\"mno\"]: %s", assert.AnError))
}
// Special cases
func TestNewValidationErrorTopLevelIsNeedle(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
}
doc, field := references(t, st, st, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc: %s", assert.AnError))
}
func TestNewValidationErrorUntaggedField(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NoTagField: 123,
}
doc, field := references(t, st, &st.NoTagField, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.NoTagField: %s", assert.AnError))
}
func TestNewValidationErrorOnlyYamlTaggedField(t *testing.T) {
st := &errorTestDoc{
ExportedField: "abc",
OtherField: 42,
NoTagField: 123,
OnlyYamlKey: "abc",
}
doc, field := references(t, st, &st.OnlyYamlKey, "")
err := newError(doc, field, assert.AnError)
require.Error(t, err)
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.onlyYamlKey: %s", assert.AnError))
}
type errorTestDoc struct {
ExportedField string `json:"exportedField" yaml:"exportedField"`
OtherField int `json:"otherField" yaml:"otherField"`
PointerField *int `json:"pointerField" yaml:"pointerField"`
DoublePointerField **int `json:"doublePointerField" yaml:"doublePointerField"`
NestedField nestederrorTestDoc `json:"nestedField" yaml:"nestedField"`
NestedPointerField *nestederrorTestDoc `json:"nestedPointerField" yaml:"nestedPointerField"`
NoTagField int
OnlyYamlKey string `yaml:"onlyYamlKey"`
}
type nestederrorTestDoc struct {
ExportedField string `json:"exportedField" yaml:"exportedField"`
OtherField int `json:"otherField" yaml:"otherField"`
PointerField *int `json:"pointerField" yaml:"pointerField"`
NestedField nestedNestederrorTestDoc `json:"nestedField" yaml:"nestedField"`
NestedPointerField *nestedNestederrorTestDoc `json:"nestedPointerField" yaml:"nestedPointerField"`
}
type nestedNestederrorTestDoc struct {
ExportedField string `json:"exportedField" yaml:"exportedField"`
OtherField int `json:"otherField" yaml:"otherField"`
PointerField *int `json:"pointerField" yaml:"pointerField"`
}
type sliceErrorTestDoc struct {
PrimitiveSlice []string `json:"primitiveSlice" yaml:"primitiveSlice"`
PrimitiveArray [3]int `json:"primitiveArray" yaml:"primitiveArray"`
StructSlice []errorTestDoc `json:"structSlice" yaml:"structSlice"`
StructArray [3]errorTestDoc `json:"structArray" yaml:"structArray"`
StructPointerSlice []*errorTestDoc `json:"structPointerSlice" yaml:"structPointerSlice"`
StructPointerArray [3]*errorTestDoc `json:"structPointerArray" yaml:"structPointerArray"`
PrimitiveSliceSlice [][]string `json:"primitiveSliceSlice" yaml:"primitiveSliceSlice"`
}
type mapErrorTestDoc struct {
PrimitiveMap map[string]string `json:"primitiveMap" yaml:"primitiveMap"`
StructPointerMap map[string]*errorTestDoc `json:"structPointerMap" yaml:"structPointerMap"`
NestedPointerMap map[string]*map[string]string `json:"nestedPointerMap" yaml:"nestedPointerMap"`
}
// references returns referenceableValues for the given doc and field for testing purposes.
func references(t *testing.T, doc, field any, mapKey string) (haystack, needle referenceableValue) {
t.Helper()
derefedField := pointerDeref(reflect.ValueOf(field))
fieldRef := referenceableValue{
value: derefedField,
addr: derefedField.UnsafeAddr(),
_type: derefedField.Type(),
mapKey: mapKey,
}
derefedDoc := pointerDeref(reflect.ValueOf(doc))
docRef := referenceableValue{
value: derefedDoc,
addr: derefedDoc.UnsafeAddr(),
_type: derefedDoc.Type(),
}
return docRef, fieldRef
}