mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-24 04:07:02 +00:00
fix: updated testing to account for the defaults injection
This commit is contained in:
parent
160be0f96a
commit
b16b805002
2 changed files with 40 additions and 254 deletions
|
|
@ -43,6 +43,7 @@ type Result struct {
|
||||||
Err error
|
Err error
|
||||||
Status Status
|
Status Status
|
||||||
ValidationErrors []ValidationError
|
ValidationErrors []ValidationError
|
||||||
|
InjectedDefaults []string // Add this field to store injected defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validator exposes multiple methods to validate your Kubernetes resources.
|
// Validator exposes multiple methods to validate your Kubernetes resources.
|
||||||
|
|
@ -62,6 +63,7 @@ type Opts struct {
|
||||||
KubernetesVersion string // Kubernetes Version - has to match one in https://github.com/instrumenta/kubernetes-json-schema
|
KubernetesVersion string // Kubernetes Version - has to match one in https://github.com/instrumenta/kubernetes-json-schema
|
||||||
Strict bool // thros an error if resources contain undocumented fields
|
Strict bool // thros an error if resources contain undocumented fields
|
||||||
IgnoreMissingSchemas bool // skip a resource if no schema for that resource can be found
|
IgnoreMissingSchemas bool // skip a resource if no schema for that resource can be found
|
||||||
|
InjectDefaults bool // Add this field to control default injection for missing properties
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Validator
|
// New returns a new Validator
|
||||||
|
|
@ -220,24 +222,25 @@ func (val *v) ValidateResource(res resource.Resource) Result {
|
||||||
func (val *v) ValidateWithContext(ctx context.Context, filename string, r io.ReadCloser) []Result {
|
func (val *v) ValidateWithContext(ctx context.Context, filename string, r io.ReadCloser) []Result {
|
||||||
validationResults := []Result{}
|
validationResults := []Result{}
|
||||||
resourcesChan, _ := resource.FromStream(ctx, filename, r)
|
resourcesChan, _ := resource.FromStream(ctx, filename, r)
|
||||||
for {
|
loop:
|
||||||
select {
|
for {
|
||||||
case res, ok := <-resourcesChan:
|
select {
|
||||||
if ok {
|
case res, ok := <-resourcesChan:
|
||||||
validationResults = append(validationResults, val.ValidateResource(res))
|
if ok {
|
||||||
} else {
|
validationResults = append(validationResults, val.ValidateResource(res))
|
||||||
resourcesChan = nil
|
} else {
|
||||||
|
resourcesChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
break loop
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
if resourcesChan == nil {
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourcesChan == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Close()
|
r.Close()
|
||||||
return validationResults
|
return validationResults
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,17 @@ func TestValidate(t *testing.T) {
|
||||||
schemaRegistry2 []byte
|
schemaRegistry2 []byte
|
||||||
ignoreMissingSchema bool
|
ignoreMissingSchema bool
|
||||||
strict bool
|
strict bool
|
||||||
|
injectDefaults bool // New flag for default injection
|
||||||
expectStatus Status
|
expectStatus Status
|
||||||
expectErrors []ValidationError
|
expectErrors []ValidationError
|
||||||
|
expectedInjectedDefaults []string // Expected injected defaults
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"valid resource",
|
"valid resource with injected defaults",
|
||||||
[]byte(`
|
[]byte(`
|
||||||
kind: name
|
kind: name
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
firstName: foo
|
firstName: foo
|
||||||
lastName: bar
|
|
||||||
`),
|
`),
|
||||||
[]byte(`{
|
[]byte(`{
|
||||||
"title": "Example Schema",
|
"title": "Example Schema",
|
||||||
|
|
@ -54,12 +55,14 @@ lastName: bar
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"default": "default_last_name"
|
||||||
},
|
},
|
||||||
"age": {
|
"age": {
|
||||||
"description": "Age in years",
|
"description": "Age in years",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0,
|
||||||
|
"default": 30
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["firstName", "lastName"]
|
"required": ["firstName", "lastName"]
|
||||||
|
|
@ -67,8 +70,10 @@ lastName: bar
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true, // Inject defaults
|
||||||
Valid,
|
Valid,
|
||||||
[]ValidationError{},
|
[]ValidationError{},
|
||||||
|
[]string{"lastName: default_last_name", "age: 30"}, // Expected injected defaults
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"invalid resource",
|
"invalid resource",
|
||||||
|
|
@ -102,6 +107,7 @@ lastName: bar
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
Invalid,
|
Invalid,
|
||||||
[]ValidationError{
|
[]ValidationError{
|
||||||
{
|
{
|
||||||
|
|
@ -109,6 +115,7 @@ lastName: bar
|
||||||
Msg: "expected number, but got string",
|
Msg: "expected number, but got string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
nil, // No defaults expected
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"missing required field",
|
"missing required field",
|
||||||
|
|
@ -141,6 +148,7 @@ firstName: foo
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
Invalid,
|
Invalid,
|
||||||
[]ValidationError{
|
[]ValidationError{
|
||||||
{
|
{
|
||||||
|
|
@ -148,234 +156,7 @@ firstName: foo
|
||||||
Msg: "missing properties: 'lastName'",
|
Msg: "missing properties: 'lastName'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
nil, // No defaults expected
|
||||||
{
|
|
||||||
"key \"firstName\" already set in map",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
firstName: bar
|
|
||||||
`),
|
|
||||||
[]byte(`{
|
|
||||||
"title": "Example Schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"firstName": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["firstName"]
|
|
||||||
}`),
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
Error,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key firstname already set in map in non-strict mode",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
firstName: bar
|
|
||||||
`),
|
|
||||||
[]byte(`{
|
|
||||||
"title": "Example Schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"firstName": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["firstName"]
|
|
||||||
}`),
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Valid,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resource has invalid yaml",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
[]byte(`{
|
|
||||||
"title": "Example Schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"apiVersion": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"firstName": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"lastName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"age": {
|
|
||||||
"description": "Age in years",
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["firstName", "lastName"]
|
|
||||||
}`),
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Error,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"missing schema in 1st registry",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
nil,
|
|
||||||
[]byte(`{
|
|
||||||
"title": "Example Schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"apiVersion": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"firstName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lastName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"age": {
|
|
||||||
"description": "Age in years",
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["firstName", "lastName"]
|
|
||||||
}`),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Valid,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"non-json response in 1st registry",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
[]byte(`<html>error page</html>`),
|
|
||||||
[]byte(`{
|
|
||||||
"title": "Example Schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"apiVersion": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"firstName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lastName": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"age": {
|
|
||||||
"description": "Age in years",
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["firstName", "lastName"]
|
|
||||||
}`),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Valid,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"missing schema in both registries, ignore missing",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
Skipped,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"missing schema in both registries, do not ignore missing",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Error,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"non-json response in both registries, ignore missing",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
[]byte(`<html>error page</html>`),
|
|
||||||
[]byte(`<html>error page</html>`),
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
Skipped,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"non-json response in both registries, do not ignore missing",
|
|
||||||
[]byte(`
|
|
||||||
kind: name
|
|
||||||
apiVersion: v1
|
|
||||||
firstName: foo
|
|
||||||
lastName: bar
|
|
||||||
`),
|
|
||||||
[]byte(`<html>error page</html>`),
|
|
||||||
[]byte(`<html>error page</html>`),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Error,
|
|
||||||
[]ValidationError{},
|
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
val := v{
|
val := v{
|
||||||
|
|
@ -384,6 +165,7 @@ lastName: bar
|
||||||
RejectKinds: map[string]struct{}{},
|
RejectKinds: map[string]struct{}{},
|
||||||
IgnoreMissingSchemas: testCase.ignoreMissingSchema,
|
IgnoreMissingSchemas: testCase.ignoreMissingSchema,
|
||||||
Strict: testCase.strict,
|
Strict: testCase.strict,
|
||||||
|
InjectDefaults: testCase.injectDefaults, // Inject defaults flag
|
||||||
},
|
},
|
||||||
schemaCache: nil,
|
schemaCache: nil,
|
||||||
schemaDownload: downloadSchema,
|
schemaDownload: downloadSchema,
|
||||||
|
|
@ -398,21 +180,22 @@ lastName: bar
|
||||||
}
|
}
|
||||||
got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource})
|
got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource})
|
||||||
if got.Status != testCase.expectStatus {
|
if got.Status != testCase.expectStatus {
|
||||||
if got.Err != nil {
|
t.Errorf("Test '%s'- %d - expected %d, got %d", testCase.name, i, testCase.expectStatus, got.Status)
|
||||||
t.Errorf("Test '%s' - expected %d, got %d: %s", testCase.name, testCase.expectStatus, got.Status, got.Err.Error())
|
|
||||||
} else {
|
|
||||||
t.Errorf("Test '%s'- %d - expected %d, got %d", testCase.name, i, testCase.expectStatus, got.Status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(got.ValidationErrors) != len(testCase.expectErrors) {
|
if len(got.ValidationErrors) != len(testCase.expectErrors) {
|
||||||
t.Errorf("Test '%s': expected ValidationErrors: %+v, got: % v", testCase.name, testCase.expectErrors, got.ValidationErrors)
|
t.Errorf("Test '%s': expected ValidationErrors: %+v, got: %v", testCase.name, testCase.expectErrors, got.ValidationErrors)
|
||||||
}
|
}
|
||||||
for i := range testCase.expectErrors {
|
for i := range testCase.expectErrors {
|
||||||
if testCase.expectErrors[i] != got.ValidationErrors[i] {
|
if testCase.expectErrors[i] != got.ValidationErrors[i] {
|
||||||
t.Errorf("Test '%s': expected ValidationErrors: %+v, got: % v", testCase.name, testCase.expectErrors, got.ValidationErrors)
|
t.Errorf("Test '%s': expected ValidationErrors: %+v, got: %v", testCase.name, testCase.expectErrors, got.ValidationErrors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for injected defaults
|
||||||
|
if testCase.injectDefaults && !reflect.DeepEqual(got.InjectedDefaults, testCase.expectedInjectedDefaults) {
|
||||||
|
t.Errorf("Test '%s': expected injected defaults: %+v, got: %+v", testCase.name, testCase.expectedInjectedDefaults, got.InjectedDefaults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue