Repository: FairwindsOps/polaris
Pull request #520 (2021-03-22 17:20:21)

Add support for check templates

codecov[bot]: # [Codecov](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=h1) Report
> Merging [#520](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=desc) (0f90a7b) into [master](https://codecov.io/gh/FairwindsOps/polaris/commit/63fd576d3ec42773b1e43d6b8baca364d6e93eb0?el=desc) (63fd576) will **decrease** coverage by `1.33%`.
> The diff coverage is `40.00%`.

[![Impacted file tree graph](https://codecov.io/gh/FairwindsOps/polaris/pull/520/graphs/tree.svg?width=650&height=150&src=pr&token=RR1ySHa61e)](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #520      +/-   ##
==========================================
- Coverage   47.98%   46.64%   -1.34%     
==========================================
  Files          10       10              
  Lines         769      791      +22     
==========================================
  Hits          369      369              
- Misses        340      361      +21     
- Partials       60       61       +1     
```


| [Impacted Files](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/validator/output.go](https://codecov.io/gh/FairwindsOps/polaris/pull/520/diff?src=pr&el=tree#diff-cGtnL3ZhbGlkYXRvci9vdXRwdXQuZ28=) | `0.00% <ø> (ø)` | |
| [pkg/config/schema.go](https://codecov.io/gh/FairwindsOps/polaris/pull/520/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZy9zY2hlbWEuZ28=) | `11.45% <21.87%> (+0.34%)` | :arrow_up: |
| [pkg/validator/schema.go](https://codecov.io/gh/FairwindsOps/polaris/pull/520/diff?src=pr&el=tree#diff-cGtnL3ZhbGlkYXRvci9zY2hlbWEuZ28=) | `79.43% <84.61%> (-0.99%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=footer). Last update [63fd576...0f90a7b](https://codecov.io/gh/FairwindsOps/polaris/pull/520?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/checks/metadataAndNameMismatched.yaml b/checks/metadataAndNameMismatched.yaml
new file mode 100644
index 00000000..62746c72
--- /dev/null
+++ b/checks/metadataAndNameMismatched.yaml
@@ -0,0 +1,17 @@
+successMessage: Label app.kubernetes.io/name matches metadata.name
+failureMessage: Label app.kubernetes.io/name must match metadata.name
+target: Controller
+schema:
+  '$schema': http://json-schema.org/draft-07/schema
+  type: object
+  properties:
+    metadata:
+      type: object
+      required: ["labels"]
+      properties:
+        labels:
+          type: object
+          required: ["app.kubernetes.io/name"]
+          properties:
+            app.kubernetes.io/name:
+              const: "{{ .metadata.name }}"
diff --git a/go.sum b/go.sum
index 595706e3..a36a64af 100644
--- a/go.sum
+++ b/go.sum
@@ -168,6 +168,7 @@ github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQo
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -431,10 +432,12 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -577,6 +580,7 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
 github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
diff --git a/pkg/config/schema.go b/pkg/config/schema.go
index 730ec9c9..4a33d10b 100644
--- a/pkg/config/schema.go
+++ b/pkg/config/schema.go
@@ -1,14 +1,18 @@
 package config
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
+	"io"
 	"strings"
+	"text/template"
 
 	"github.com/qri-io/jsonschema"
 	"github.com/thoas/go-funk"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
 )
 
 // TargetKind represents the part of the config to be validated
@@ -32,21 +36,39 @@ var HandledTargets = []TargetKind{
 
 // SchemaCheck is a Polaris check that runs using JSON Schema
 type SchemaCheck struct {
-	ID             string                `yaml:"id"`
-	Category       string                `yaml:"category"`
-	SuccessMessage string                `yaml:"successMessage"`
-	FailureMessage string                `yaml:"failureMessage"`
-	Controllers    includeExcludeList    `yaml:"controllers"`
-	Containers     includeExcludeList    `yaml:"containers"`
-	Target         TargetKind            `yaml:"target"`
-	SchemaTarget   TargetKind            `yaml:"schemaTarget"`
-	Schema         jsonschema.RootSchema `yaml:"schema"`
-	JSONSchema     string                `yaml:"jsonSchema"`
+	ID             string                 `yaml:"id" json:"id"`
+	Category       string                 `yaml:"category" json:"category"`
+	SuccessMessage string                 `yaml:"successMessage" json:"successMessage"`
+	FailureMessage string                 `yaml:"failureMessage" json:"failureMessage"`
+	Controllers    includeExcludeList     `yaml:"controllers" json:"controllers"`
+	Containers     includeExcludeList     `yaml:"containers" json:"containers"`
+	Target         TargetKind             `yaml:"target" json:"target"`
+	SchemaTarget   TargetKind             `yaml:"schemaTarget" json:"schemaTarget"`
+	Schema         map[string]interface{} `yaml:"schema" json:"schema"`
+	SchemaString   string                 `yaml:"jsonSchema" json:"jsonSchema"`
+	Validator      jsonschema.RootSchema  `yaml:"-"`
 }
 
 type resourceMinimum string
 type resourceMaximum string
 
+// ParseCheck parses a check from a byte array
+func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
+	reader := bytes.NewReader(rawBytes)
+	check := SchemaCheck{}
+	d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
+	for {
+		if err := d.Decode(&check); err != nil {
+			if err == io.EOF {
+				break
+			}
+			return check, fmt.Errorf("Decoding schema check failed: %v", err)
+		}
+	}
+	check.Initialize(id)
+	return check, nil
+}
+
 func init() {
 	jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
 	jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
@@ -126,38 +148,61 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
 // Initialize sets up the schema
 func (check *SchemaCheck) Initialize(id string) error {
 	check.ID = id
-	if check.JSONSchema != "" {
-		if err := json.Unmarshal([]byte(check.JSONSchema), &check.Schema); err != nil {
+	if check.SchemaString == "" {
+		jsonBytes, err := json.Marshal(check.Schema)
+		if err != nil {
 			return err
 		}
+		check.SchemaString = string(jsonBytes)
 	}
-	return nil
+	err := json.Unmarshal([]byte(check.SchemaString), &check.Validator)
+	return err
+}
+
+// TemplateForResource fills out a check's templated fields given a particular resource
+func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
+	newCheck := check // Make a copy of the check, since we're going to modify the schema
+	tmpl := template.New(newCheck.ID)
+	tmpl, err := tmpl.Parse(newCheck.SchemaString)
+	if err != nil {
+		return nil, err
+	}
+
+	w := bytes.Buffer{}
+	err = tmpl.Execute(&w, res)
+	if err != nil {
+		return nil, err
+	}
+
+	newCheck.SchemaString = w.String()
+	newCheck.Initialize(newCheck.ID)
+	return &newCheck, err
 }
 
 // CheckPod checks a pod spec against the schema
-func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, error) {
+func (check SchemaCheck) CheckPod(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
 	return check.CheckObject(pod)
 }
 
 // CheckController checks a controler's spec against the schema
-func (check SchemaCheck) CheckController(bytes []byte) (bool, error) {
-	errs, err := check.Schema.ValidateBytes(bytes)
-	return len(errs) == 0, err
+func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
+	errs, err := check.Validator.ValidateBytes(bytes)
+	return len(errs) == 0, errs, err
 }
 
 // CheckContainer checks a container spec against the schema
-func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, error) {
+func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
 	return check.CheckObject(container)
 }
 
 // CheckObject checks arbitrary data against the schema
-func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
+func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, error) {
 	bytes, err := json.Marshal(obj)
 	if err != nil {
-		return false, err
+		return false, nil, err
 	}
-	errs, err := check.Schema.ValidateBytes(bytes)
-	return len(errs) == 0, err
+	errs, err := check.Validator.ValidateBytes(bytes)
+	return len(errs) == 0, errs, err
 }
 
 // IsActionable decides if this check applies to a particular target
diff --git a/pkg/validator/output.go b/pkg/validator/output.go
index 85ddd8a5..5a99f0bd 100644
--- a/pkg/validator/output.go
+++ b/pkg/validator/output.go
@@ -71,6 +71,7 @@ type ClusterInfo struct {
 type ResultMessage struct {
 	ID       string
 	Message  string
+	Details  []string
 	Success  bool
 	Severity config.Severity
 	Category string
diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go
index e4f10e66..7d9638a2 100644
--- a/pkg/validator/schema.go
+++ b/pkg/validator/schema.go
@@ -1,16 +1,14 @@
 package validator
 
 import (
-	"bytes"
 	"fmt"
-	"io"
 	"sort"
 	"strings"
 
 	"github.com/gobuffalo/packr/v2"
+	"github.com/qri-io/jsonschema"
 	corev1 "k8s.io/api/core/v1"
 	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/yaml"
 
 	"github.com/fairwindsops/polaris/pkg/config"
 	"github.com/fairwindsops/polaris/pkg/kube"
@@ -48,6 +46,7 @@ var (
 		// Other checks
 		"tlsSettingsMissing",
 		"pdbDisruptionsAllowedGreaterThanZero",
+		"metadataAndNameMismatched",
 	}
 )
 
@@ -65,29 +64,14 @@ func init() {
 		if err != nil {
 			panic(err)
 		}
-		check, err := parseCheck(contents)
+		check, err := config.ParseCheck(checkID, contents)
 		if err != nil {
 			panic(err)
 		}
-		check.ID = checkID
 		builtInChecks[checkID] = check
 	}
 }
 
-func parseCheck(rawBytes []byte) (config.SchemaCheck, error) {
-	reader := bytes.NewReader(rawBytes)
-	check := config.SchemaCheck{}
-	d := yaml.NewYAMLOrJSONDecoder(reader, 4096)
-	for {
-		if err := d.Decode(&check); err != nil {
-			if err == io.EOF {
-				return check, nil
-			}
-			return check, fmt.Errorf("Decoding schema check failed: %v", err)
-		}
-	}
-}
-
 func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) {
 	if !conf.DisallowExemptions && hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) {
 		return nil, nil
@@ -110,15 +94,25 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
 	if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) {
 		return nil, nil
 	}
-	return &check, nil
+	checkPtr, err := check.TemplateForResource(test.Resource.Resource.Object)
+	if err != nil {
+		return nil, err
+	}
+	return checkPtr, nil
 }
 
-func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool) ResultMessage {
+func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.ValError) ResultMessage {
+	details := []string{}
+	for _, issue := range issues {
+		details = append(details, issue.Message)
+	}
 	result := ResultMessage{
 		ID:       check.ID,
 		Severity: conf.Checks[check.ID],
 		Category: check.Category,
 		Success:  passes,
+		// FIXME: need to fix the tests before adding this back
+		//Details: details,
 	}
 	if passes {
 		result.Message = check.SuccessMessage
@@ -276,26 +270,27 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
 		return nil, nil
 	}
 	var passes bool
+	var issues []jsonschema.ValError
 	if check.SchemaTarget != "" {
 		if check.SchemaTarget == config.TargetPod && check.Target == config.TargetContainer {
 			podCopy := *test.Resource.PodSpec
 			podCopy.InitContainers = []corev1.Container{}
 			podCopy.Containers = []corev1.Container{*test.Container}
-			passes, err = check.CheckPod(&podCopy)
+			passes, issues, err = check.CheckPod(&podCopy)
 		} else {
 			return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
 		}
 	} else if check.Target == config.TargetPod {
-		passes, err = check.CheckPod(test.Resource.PodSpec)
+		passes, issues, err = check.CheckPod(test.Resource.PodSpec)
 	} else if check.Target == config.TargetContainer {
-		passes, err = check.CheckContainer(test.Container)
+		passes, issues, err = check.CheckContainer(test.Container)
 	} else {
-		passes, err = check.CheckObject(test.Resource.Resource.Object)
+		passes, issues, err = check.CheckObject(test.Resource.Resource.Object)
 	}
 	if err != nil {
 		return nil, err
 	}
-	result := makeResult(conf, check, passes)
+	result := makeResult(conf, check, passes, issues)
 	return &result, nil
 }
 


Test patch:
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 1480abd8..8b165b6f 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -16,6 +16,7 @@ package config
 
 import (
 	"context"
+	"fmt"
 	"io"
 	"log"
 	"net/http"
@@ -142,7 +143,9 @@ func TestConfigNoServerError(t *testing.T) {
 
 func TestConfigWithCustomChecks(t *testing.T) {
 	valid := map[string]interface{}{
-		"securityContext": map[string]interface{}{},
+		"securityContext": map[string]interface{}{
+			"foo": "bar",
+		},
 	}
 	invalid := map[string]interface{}{
 		"notSecurityContext": map[string]interface{}{},
@@ -151,20 +154,22 @@ func TestConfigWithCustomChecks(t *testing.T) {
 	parsedConf, err := Parse([]byte(confCustomChecks))
 	assert.NoError(t, err, "Expected no error when parsing YAML config")
 	assert.Equal(t, 1, len(parsedConf.CustomChecks))
-	isValid, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
+	isValid, _, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
 	assert.NoError(t, err)
 	assert.Equal(t, true, isValid)
-	isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
+	isValid, _, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
 	assert.NoError(t, err)
 	assert.Equal(t, false, isValid)
 
 	parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
 	assert.NoError(t, err, "Expected no error when parsing YAML config")
 	assert.Equal(t, 1, len(parsedConf.CustomChecks))
-	isValid, err = parsedConf.CustomChecks["foo"].CheckObject(valid)
+	isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
 	assert.NoError(t, err)
-	assert.Equal(t, true, isValid)
-	isValid, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
+	if !assert.Equal(t, true, isValid) {
+		fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
+	}
+	isValid, _, err = parsedConf.CustomChecks["foo"].CheckObject(invalid)
 	assert.NoError(t, err)
 	assert.Equal(t, false, isValid)
 }
diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go
index 010d3273..f450a85c 100644
--- a/pkg/validator/container_test.go
+++ b/pkg/validator/container_test.go
@@ -75,14 +75,17 @@ func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourc
 	}
 	summary := results.GetSummary()
 
-	assert.Equal(t, uint(len(expectedWarnings)), summary.Warnings)
-	assert.ElementsMatch(t, expectedWarnings, results.GetWarnings())
+	if assert.Equal(t, uint(len(expectedWarnings)), summary.Warnings) {
+		assert.ElementsMatch(t, expectedWarnings, results.GetWarnings())
+	}
 
-	assert.Equal(t, uint(len(expectedDangers)), summary.Dangers)
-	assert.ElementsMatch(t, expectedDangers, results.GetDangers())
+	if assert.Equal(t, uint(len(expectedDangers)), summary.Dangers) {
+		assert.ElementsMatch(t, expectedDangers, results.GetDangers())
+	}
 
-	assert.Equal(t, uint(len(expectedSuccesses)), summary.Successes)
-	assert.ElementsMatch(t, expectedSuccesses, results.GetSuccesses())
+	if assert.Equal(t, uint(len(expectedSuccesses)), summary.Successes) {
+		assert.ElementsMatch(t, expectedSuccesses, results.GetSuccesses())
+	}
 }
 
 func TestValidateResourcesEmptyConfig(t *testing.T) {
diff --git a/test/checks/metadataAndNameMismatched/failure.unspecified.yaml b/test/checks/metadataAndNameMismatched/failure.unspecified.yaml
new file mode 100644
index 00000000..4c751878
--- /dev/null
+++ b/test/checks/metadataAndNameMismatched/failure.unspecified.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: nginx
+spec:
+  containers:
+  - name: nginx
+    image: nginx
diff --git a/test/checks/metadataAndNameMismatched/failure.yaml b/test/checks/metadataAndNameMismatched/failure.yaml
new file mode 100644
index 00000000..dafaff68
--- /dev/null
+++ b/test/checks/metadataAndNameMismatched/failure.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: nginx
+  labels:
+    app.kubernetes.io/name: not-nginx
+spec:
+  containers:
+  - name: nginx
+    image: nginx
diff --git a/test/checks/metadataAndNameMismatched/success.yaml b/test/checks/metadataAndNameMismatched/success.yaml
new file mode 100644
index 00000000..5e949d47
--- /dev/null
+++ b/test/checks/metadataAndNameMismatched/success.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: nginx
+  labels:
+    app.kubernetes.io/name: nginx
+spec:
+  containers:
+  - name: nginx
+    image: nginx
diff --git a/test/checks/runAsRootAllowed/failure.yaml b/test/checks/runAsRootAllowed/failure.yaml
new file mode 100644
index 00000000..84d7d896
--- /dev/null
+++ b/test/checks/runAsRootAllowed/failure.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Deployment
+metadata:
+  name: nginx
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    securityContext:
+      runAsNonRoot: false
diff --git a/test/checks/runAsRootAllowed/success.yaml b/test/checks/runAsRootAllowed/success.yaml
new file mode 100644
index 00000000..e2f7825b
--- /dev/null
+++ b/test/checks/runAsRootAllowed/success.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Deployment
+metadata:
+  name: nginx
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    securityContext:
+      runAsNonRoot: true
diff --git a/test/checks_test.go b/test/schema_test.go
similarity index 79%
rename from test/checks_test.go
rename to test/schema_test.go
index 22cc632a..38086ed3 100644
--- a/test/checks_test.go
+++ b/test/schema_test.go
@@ -18,9 +18,10 @@ import (
 var testCases = []testCase{}
 
 type testCase struct {
-	check   string
-	input   []byte
-	failure bool
+	check    string
+	filename string
+	input    []byte
+	failure  bool
 }
 
 func init() {
@@ -43,9 +44,10 @@ func init() {
 				panic(err)
 			}
 			testCases = append(testCases, testCase{
-				check:   check,
-				input:   body,
-				failure: strings.Contains(tc.Name(), "failure"),
+				filename: tc.Name(),
+				check:    check,
+				input:    body,
+				failure:  strings.Contains(tc.Name(), "failure"),
 			})
 		}
 	}
@@ -59,19 +61,23 @@ func TestChecks(t *testing.T) {
 			panic(err)
 		}
 		c, err := config.Parse([]byte("checks:\n  " + tc.check + ": danger"))
-		assert.NoError(t, err)
+		if err != nil {
+			panic(err)
+		}
 		result, err := validator.ApplyAllSchemaChecks(&c, res)
-		assert.NoError(t, err)
+		if err != nil {
+			panic(err)
+		}
 		summary := result.GetSummary()
 		total := summary.Successes + summary.Dangers
 		msg := fmt.Sprintf("Check %s ran %d times instead of 1", tc.check, total)
 		if assert.Equal(t, uint(1), total, msg) {
 			if tc.failure {
-				message := "Check " + tc.check + " passed unexpectedly"
+				message := "Check " + tc.check + " passed unexpectedly for " + tc.filename
 				assert.Equal(t, uint(0), summary.Successes, message)
 				assert.Equal(t, uint(1), summary.Dangers, message)
 			} else {
-				message := "Check " + tc.check + " failed unexpectedly"
+				message := "Check " + tc.check + " failed unexpectedly for " + tc.filename
 				assert.Equal(t, uint(1), summary.Successes, message)
 				assert.Equal(t, uint(0), summary.Dangers, message)
 			}