From 041c2f06dfe593dc0c2acb090dbfbc3737782727 Mon Sep 17 00:00:00 2001 From: Calvin Lobo Date: Thu, 9 Jan 2025 12:04:35 -0500 Subject: [PATCH] truthy and defined functions were not returning the located object path for the infraction because it was looking for a node that did not exist. --- functions/core/defined.go | 26 +++++++++++-------- functions/core/defined_test.go | 42 +++++++++++++++++++++++++------ functions/core/truthy.go | 11 +++++--- functions/core/truthy_test.go | 43 +++++++++++++++++++++++++++----- functions/core/undefined_test.go | 33 ++++++++++++++++++++++-- 5 files changed, 126 insertions(+), 29 deletions(-) diff --git a/functions/core/defined.go b/functions/core/defined.go index 0f2b940e..0dee9197 100644 --- a/functions/core/defined.go +++ b/functions/core/defined.go @@ -51,23 +51,27 @@ func (d Defined) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) } for _, node := range nodes { - fieldNode, fieldNodeValue := utils.FindKeyNode(context.RuleAction.Field, node.Content) + fieldNode, _ := utils.FindKeyNode(context.RuleAction.Field, node.Content) var locatedObjects []base.Foundational var allPaths []string var err error - locatedPath := pathValue - if context.DrDocument != nil { - locatedObjects, err = context.DrDocument.LocateModelsByKeyAndValue(fieldNode, fieldNodeValue) - if err == nil && locatedObjects != nil { - for x, obj := range locatedObjects { - if x == 0 { - locatedPath = obj.GenerateJSONPath() + + if fieldNode == nil { + + locatedPath := pathValue + if context.DrDocument != nil { + // Since the field is undefined, locate the parent node to be the locatedPath of infraction + locatedObjects, err = context.DrDocument.LocateModel(node) + if err == nil && locatedObjects != nil { + for x, obj := range locatedObjects { + if x == 0 { + locatedPath = obj.GenerateJSONPath() + } + allPaths = append(allPaths, obj.GenerateJSONPath()) } - allPaths = append(allPaths, obj.GenerateJSONPath()) } } - } - if fieldNode == nil { + result := model.RuleFunctionResult{ Message: vacuumUtils.SuppliedOrDefault(message, fmt.Sprintf("%s: `%s` must be defined", ruleMessage, context.RuleAction.Field)), diff --git a/functions/core/defined_test.go b/functions/core/defined_test.go index 6a1786c2..11145ad7 100644 --- a/functions/core/defined_test.go +++ b/functions/core/defined_test.go @@ -45,24 +45,52 @@ func TestDefined_RunRule_Success(t *testing.T) { func TestDefined_RunRule_Fail(t *testing.T) { - sampleYaml := `openapi: 3.0.0 -pizza: - noCake: "noFun"` - - path := "$.pizza" + sampleYaml := + `openapi: 3.0.0 +paths: + /v1/cake: + get: + responses: + '200': + content: + application/xml: + schema: + type: object + post: + responses: + '200': + content: + application/json: + schema: + type: object +` + + path := "$.paths.*.*.responses[*].content" nodes, _ := utils.FindNodes([]byte(sampleYaml), path) - assert.Len(t, nodes, 1) + assert.Len(t, nodes, 2) - rule := buildCoreTestRule(path, model.SeverityError, "defined", "cake", nil) + document, err := libopenapi.NewDocument([]byte(sampleYaml)) + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + m, _ := document.BuildV3Model() + + drDocument := drModel.NewDrDocument(m) + + rule := buildCoreTestRule(path, model.SeverityError, "defined", "application/json", nil) ctx := buildCoreTestContext(model.CastToRuleAction(rule.Then), nil) ctx.Given = path ctx.Rule = &rule + ctx.Document = document + ctx.DrDocument = drDocument def := Defined{} res := def.RunRule(nodes, ctx) assert.Len(t, res, 1) + assert.Equal(t, res[0].Path, "$.paths['/v1/cake'].get.responses['200'].content['application/xml']") } func TestDefined_RunRule_DrNodeLookup(t *testing.T) { diff --git a/functions/core/truthy.go b/functions/core/truthy.go index cf75aaa0..96d6e88e 100644 --- a/functions/core/truthy.go +++ b/functions/core/truthy.go @@ -92,13 +92,18 @@ func (t *Truthy) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) var err error locatedPath := pathValue if context.DrDocument != nil { - locatedObjects, err = context.DrDocument.LocateModelsByKeyAndValue(fieldNode, fieldNodeValue) + if fieldNode == nil { + locatedObjects, err = context.DrDocument.LocateModel(node) + } else { + locatedObjects, err = context.DrDocument.LocateModelsByKeyAndValue(fieldNode, fieldNodeValue) + } if err == nil && locatedObjects != nil { for x, obj := range locatedObjects { + p := fmt.Sprintf("%s.%s", obj.GenerateJSONPath(), context.RuleAction.Field) if x == 0 { - locatedPath = obj.GenerateJSONPath() + locatedPath = p } - allPaths = append(allPaths, obj.GenerateJSONPath()) + allPaths = append(allPaths, p) } } } diff --git a/functions/core/truthy_test.go b/functions/core/truthy_test.go index a06c3c1b..52df6772 100644 --- a/functions/core/truthy_test.go +++ b/functions/core/truthy_test.go @@ -1,7 +1,10 @@ package core import ( + "fmt" "github.com/daveshanley/vacuum/model" + drModel "github.com/pb33f/doctor/model" + "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/utils" "github.com/stretchr/testify/assert" "testing" @@ -105,22 +108,50 @@ tags: func TestTruthy_RunRule_NoContent(t *testing.T) { - sampleYaml := `info: test` - - path := "$.info" + sampleYaml := + `openapi: 3.0.0 +paths: + /v1/cake: + get: + parameters: + - in: query + name: type + required: true + - in: query + name: flavor + required: false + - in: query + name: weight +` + + path := "$.paths.*.*.parameters[*]" nodes, _ := utils.FindNodes([]byte(sampleYaml), path) - assert.Len(t, nodes, 1) + assert.Len(t, nodes, 3) + + document, err := libopenapi.NewDocument([]byte(sampleYaml)) + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + m, _ := document.BuildV3Model() - rule := buildCoreTestRule(path, model.SeverityError, "truthy", "info", nil) + drDocument := drModel.NewDrDocument(m) + + rule := buildCoreTestRule(path, model.SeverityError, "truthy", "required", nil) ctx := buildCoreTestContext(model.CastToRuleAction(rule.Then), nil) ctx.Given = path ctx.Rule = &rule + ctx.Document = document + ctx.DrDocument = drDocument tru := Truthy{} res := tru.RunRule(nodes, ctx) - assert.Len(t, res, 1) + // Two of the three nodes should match because one has a truthy value + assert.Len(t, res, 2) + assert.Equal(t, res[0].Path, "$.paths['/v1/cake'].get.parameters[1].required") + assert.Equal(t, res[1].Path, "$.paths['/v1/cake'].get.parameters[2].required") } func TestTruthy_RunRule_ArrayTest(t *testing.T) { diff --git a/functions/core/undefined_test.go b/functions/core/undefined_test.go index 906261aa..0b44d658 100644 --- a/functions/core/undefined_test.go +++ b/functions/core/undefined_test.go @@ -1,7 +1,10 @@ package core import ( + "fmt" "github.com/daveshanley/vacuum/model" + drModel "github.com/pb33f/doctor/model" + "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/utils" "github.com/stretchr/testify/assert" "testing" @@ -20,11 +23,22 @@ func TestUndefined_RunRule(t *testing.T) { func TestUndefined_RunRule_Success(t *testing.T) { - sampleYaml := `pizza: + sampleYaml := + `openapi: 3.0.0 +pizza: cake: "fridge"` path := "$.pizza" + document, err := libopenapi.NewDocument([]byte(sampleYaml)) + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + m, _ := document.BuildV3Model() + + drDocument := drModel.NewDrDocument(m) + nodes, _ := utils.FindNodes([]byte(sampleYaml), path) assert.Len(t, nodes, 1) @@ -32,6 +46,8 @@ func TestUndefined_RunRule_Success(t *testing.T) { ctx := buildCoreTestContext(model.CastToRuleAction(rule.Then), nil) ctx.Given = path ctx.Rule = &rule + ctx.Document = document + ctx.DrDocument = drDocument def := Undefined{} res := def.RunRule(nodes, ctx) @@ -41,11 +57,22 @@ func TestUndefined_RunRule_Success(t *testing.T) { func TestUndefined_RunRule_Fail(t *testing.T) { - sampleYaml := `pizza: + sampleYaml := + `openapi: 3.0.0 +pizza: noCake: "noFun"` path := "$.pizza" + document, err := libopenapi.NewDocument([]byte(sampleYaml)) + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + m, _ := document.BuildV3Model() + + drDocument := drModel.NewDrDocument(m) + nodes, _ := utils.FindNodes([]byte(sampleYaml), path) assert.Len(t, nodes, 1) @@ -53,6 +80,8 @@ func TestUndefined_RunRule_Fail(t *testing.T) { ctx := buildCoreTestContext(model.CastToRuleAction(rule.Then), nil) ctx.Given = path ctx.Rule = &rule + ctx.Document = document + ctx.DrDocument = drDocument def := Undefined{} res := def.RunRule(nodes, ctx)