mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
554 lines
12 KiB
Go
554 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestFindSectionNode(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
yamlContent string
|
|
sectionName string
|
|
wantFound bool
|
|
wantValue string // expected value for scalar nodes
|
|
}{
|
|
{
|
|
name: "finds existing configs section",
|
|
yamlContent: `
|
|
configs:
|
|
key: value
|
|
catalogers:
|
|
- name: test
|
|
`,
|
|
sectionName: "configs",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "finds existing catalogers section",
|
|
yamlContent: `
|
|
configs:
|
|
key: value
|
|
catalogers:
|
|
- name: test
|
|
`,
|
|
sectionName: "catalogers",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "returns nil for non-existent section",
|
|
yamlContent: `
|
|
configs:
|
|
key: value
|
|
`,
|
|
sectionName: "nonexistent",
|
|
wantFound: false,
|
|
},
|
|
{
|
|
name: "handles empty mapping",
|
|
yamlContent: `{}`,
|
|
sectionName: "any",
|
|
wantFound: false,
|
|
},
|
|
{
|
|
name: "finds section with scalar value",
|
|
yamlContent: `
|
|
name: test-cataloger
|
|
type: custom
|
|
`,
|
|
sectionName: "name",
|
|
wantFound: true,
|
|
wantValue: "test-cataloger",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var rootNode yaml.Node
|
|
err := yaml.Unmarshal([]byte(tt.yamlContent), &rootNode)
|
|
require.NoError(t, err)
|
|
|
|
// get the mapping node
|
|
var mappingNode *yaml.Node
|
|
if rootNode.Kind == yaml.DocumentNode && len(rootNode.Content) > 0 {
|
|
mappingNode = rootNode.Content[0]
|
|
} else {
|
|
mappingNode = &rootNode
|
|
}
|
|
|
|
got := findSectionNode(mappingNode, tt.sectionName)
|
|
|
|
if tt.wantFound {
|
|
require.NotNil(t, got)
|
|
if tt.wantValue != "" {
|
|
require.Equal(t, tt.wantValue, got.Value)
|
|
}
|
|
} else {
|
|
require.Nil(t, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindFieldValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
yamlContent string
|
|
fieldName string
|
|
want string
|
|
}{
|
|
{
|
|
name: "finds simple string field",
|
|
yamlContent: `
|
|
name: test-cataloger
|
|
type: custom
|
|
`,
|
|
fieldName: "name",
|
|
want: "test-cataloger",
|
|
},
|
|
{
|
|
name: "finds type field",
|
|
yamlContent: `
|
|
name: test-cataloger
|
|
type: generic
|
|
`,
|
|
fieldName: "type",
|
|
want: "generic",
|
|
},
|
|
{
|
|
name: "returns empty for non-existent field",
|
|
yamlContent: `
|
|
name: test-cataloger
|
|
`,
|
|
fieldName: "nonexistent",
|
|
want: "",
|
|
},
|
|
{
|
|
name: "finds parser_function field",
|
|
yamlContent: `
|
|
parser_function: parseGoMod
|
|
metadata_types: [GoModMetadata]
|
|
`,
|
|
fieldName: "parser_function",
|
|
want: "parseGoMod",
|
|
},
|
|
{
|
|
name: "handles empty mapping",
|
|
yamlContent: `{}`,
|
|
fieldName: "any",
|
|
want: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var rootNode yaml.Node
|
|
err := yaml.Unmarshal([]byte(tt.yamlContent), &rootNode)
|
|
require.NoError(t, err)
|
|
|
|
// get the mapping node
|
|
var mappingNode *yaml.Node
|
|
if rootNode.Kind == yaml.DocumentNode && len(rootNode.Content) > 0 {
|
|
mappingNode = rootNode.Content[0]
|
|
} else {
|
|
mappingNode = &rootNode
|
|
}
|
|
|
|
got := findFieldValue(mappingNode, tt.fieldName)
|
|
require.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreserveMappingNodeComments(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
checkField string
|
|
wantHeadComment string
|
|
wantLineComment string
|
|
}{
|
|
{
|
|
name: "preserves line comment on field",
|
|
checkField: "name",
|
|
wantLineComment: "AUTO-GENERATED",
|
|
},
|
|
{
|
|
name: "preserves head comment on field",
|
|
checkField: "type",
|
|
wantHeadComment: "Important field",
|
|
wantLineComment: "AUTO-GENERATED",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// manually construct nodes with comments
|
|
existingMapping := &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Content: []*yaml.Node{
|
|
{Kind: yaml.ScalarNode, Value: "name", LineComment: "AUTO-GENERATED"},
|
|
{Kind: yaml.ScalarNode, Value: "test", HeadComment: "value comment"},
|
|
{Kind: yaml.ScalarNode, Value: "type", HeadComment: "Important field", LineComment: "AUTO-GENERATED"},
|
|
{Kind: yaml.ScalarNode, Value: "custom"},
|
|
},
|
|
}
|
|
|
|
newMapping := &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Content: []*yaml.Node{
|
|
{Kind: yaml.ScalarNode, Value: "name"},
|
|
{Kind: yaml.ScalarNode, Value: "test-new"},
|
|
{Kind: yaml.ScalarNode, Value: "type"},
|
|
{Kind: yaml.ScalarNode, Value: "generic"},
|
|
},
|
|
}
|
|
|
|
preserveMappingNodeComments(existingMapping, newMapping)
|
|
|
|
// find the field we're checking
|
|
keyNode, valueNode := findFieldNodes(newMapping, tt.checkField)
|
|
require.NotNil(t, keyNode, "field %s not found", tt.checkField)
|
|
|
|
// check comments were preserved
|
|
if tt.wantHeadComment != "" {
|
|
require.Equal(t, tt.wantHeadComment, keyNode.HeadComment)
|
|
}
|
|
if tt.wantLineComment != "" {
|
|
require.Equal(t, tt.wantLineComment, keyNode.LineComment)
|
|
}
|
|
|
|
// verify that value node comments are also preserved
|
|
if tt.checkField == "name" {
|
|
require.Equal(t, "value comment", valueNode.HeadComment)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreserveSequenceNodeComments(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
existingYAML string
|
|
newYAML string
|
|
wantHeadComment string
|
|
}{
|
|
{
|
|
name: "preserves parser comments by parser_function",
|
|
existingYAML: `
|
|
- parser_function: parseGoMod # old parser
|
|
metadata_types: [GoModMetadata]
|
|
- parser_function: parseGoSum
|
|
metadata_types: [GoSumMetadata]
|
|
`,
|
|
newYAML: `
|
|
- parser_function: parseGoMod
|
|
metadata_types: [GoModMetadataNew]
|
|
- parser_function: parseGoSum
|
|
metadata_types: [GoSumMetadataNew]
|
|
`,
|
|
// we'll verify in the test body that comments are preserved
|
|
},
|
|
{
|
|
name: "handles new parsers not in existing",
|
|
existingYAML: `
|
|
- parser_function: parseGoMod
|
|
metadata_types: [GoModMetadata]
|
|
`,
|
|
newYAML: `
|
|
- parser_function: parseGoMod
|
|
metadata_types: [GoModMetadata]
|
|
- parser_function: parseGoSum
|
|
metadata_types: [GoSumMetadata]
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var existingNode, newNode yaml.Node
|
|
err := yaml.Unmarshal([]byte(tt.existingYAML), &existingNode)
|
|
require.NoError(t, err)
|
|
err = yaml.Unmarshal([]byte(tt.newYAML), &newNode)
|
|
require.NoError(t, err)
|
|
|
|
// get sequence nodes
|
|
existingSeq := getSequenceNode(&existingNode)
|
|
newSeq := getSequenceNode(&newNode)
|
|
|
|
preserveSequenceNodeComments(existingSeq, newSeq)
|
|
|
|
// verify that the function ran without panicking
|
|
require.NotNil(t, newSeq)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreserveFieldComments(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
existingYAML string
|
|
newYAML string
|
|
wantPreserve bool
|
|
}{
|
|
{
|
|
name: "preserves mapping node comments",
|
|
existingYAML: `
|
|
name: test # AUTO-GENERATED
|
|
type: custom
|
|
`,
|
|
newYAML: `
|
|
name: test-new
|
|
type: custom
|
|
`,
|
|
wantPreserve: true,
|
|
},
|
|
{
|
|
name: "handles kind mismatch gracefully",
|
|
existingYAML: `
|
|
- item1
|
|
- item2
|
|
`,
|
|
newYAML: `
|
|
name: test
|
|
`,
|
|
wantPreserve: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var existingNode, newNode yaml.Node
|
|
err := yaml.Unmarshal([]byte(tt.existingYAML), &existingNode)
|
|
require.NoError(t, err)
|
|
err = yaml.Unmarshal([]byte(tt.newYAML), &newNode)
|
|
require.NoError(t, err)
|
|
|
|
existingContent := getContentNode(&existingNode)
|
|
newContent := getContentNode(&newNode)
|
|
|
|
preserveFieldComments(existingContent, newContent)
|
|
|
|
// verify the function completed without panicking
|
|
require.NotNil(t, newContent)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateOrAddSection(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
existingYAML string
|
|
newYAML string
|
|
sectionName string
|
|
wantUpdated bool
|
|
wantAdded bool
|
|
}{
|
|
{
|
|
name: "updates existing section",
|
|
existingYAML: `
|
|
configs:
|
|
old: value
|
|
catalogers:
|
|
- name: test
|
|
`,
|
|
newYAML: `
|
|
configs:
|
|
new: value
|
|
`,
|
|
sectionName: "configs",
|
|
wantUpdated: true,
|
|
},
|
|
{
|
|
name: "adds new section",
|
|
existingYAML: `
|
|
catalogers:
|
|
- name: test
|
|
`,
|
|
newYAML: `
|
|
configs:
|
|
new: value
|
|
`,
|
|
sectionName: "configs",
|
|
wantAdded: true,
|
|
},
|
|
{
|
|
name: "handles application section",
|
|
existingYAML: `
|
|
catalogers:
|
|
- name: test
|
|
`,
|
|
newYAML: `
|
|
application:
|
|
key: value
|
|
`,
|
|
sectionName: "application",
|
|
wantAdded: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var existingNode, newNode yaml.Node
|
|
err := yaml.Unmarshal([]byte(tt.existingYAML), &existingNode)
|
|
require.NoError(t, err)
|
|
err = yaml.Unmarshal([]byte(tt.newYAML), &newNode)
|
|
require.NoError(t, err)
|
|
|
|
existingMapping := getMappingNode(&existingNode)
|
|
newMapping := getMappingNode(&newNode)
|
|
|
|
updateOrAddSection(existingMapping, newMapping, tt.sectionName)
|
|
|
|
// verify the section exists in the result
|
|
resultSection := findSectionNode(existingMapping, tt.sectionName)
|
|
require.NotNil(t, resultSection, "section %s should exist after update", tt.sectionName)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddCatalogerFieldComment(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fieldName string
|
|
fieldValue string
|
|
catalogerName string
|
|
wantLineComment string
|
|
}{
|
|
{
|
|
name: "ecosystem is MANUAL",
|
|
fieldName: "ecosystem",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: "MANUAL",
|
|
},
|
|
{
|
|
name: "name is AUTO-GENERATED",
|
|
fieldName: "name",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "type is AUTO-GENERATED",
|
|
fieldName: "type",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "source is AUTO-GENERATED",
|
|
fieldName: "source",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "config is AUTO-GENERATED",
|
|
fieldName: "config",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "selectors is AUTO-GENERATED",
|
|
fieldName: "selectors",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "parsers is AUTO-GENERATED structure",
|
|
fieldName: "parsers",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: "AUTO-GENERATED structure",
|
|
},
|
|
{
|
|
name: "detectors for binary-classifier-cataloger is AUTO-GENERATED",
|
|
fieldName: "detectors",
|
|
catalogerName: "binary-classifier-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "detectors for other catalogers is MANUAL",
|
|
fieldName: "detectors",
|
|
catalogerName: "java-archive-cataloger",
|
|
wantLineComment: "MANUAL - edit detectors here",
|
|
},
|
|
{
|
|
name: "metadata_types is AUTO-GENERATED",
|
|
fieldName: "metadata_types",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "package_types is AUTO-GENERATED",
|
|
fieldName: "package_types",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "json_schema_types is AUTO-GENERATED",
|
|
fieldName: "json_schema_types",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: autoGeneratedComment,
|
|
},
|
|
{
|
|
name: "capabilities is MANUAL",
|
|
fieldName: "capabilities",
|
|
catalogerName: "test-cataloger",
|
|
wantLineComment: "MANUAL - edit capabilities here",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// create key and value nodes
|
|
keyNode := &yaml.Node{
|
|
Kind: yaml.ScalarNode,
|
|
Value: tt.fieldName,
|
|
}
|
|
valueNode := &yaml.Node{
|
|
Kind: yaml.ScalarNode,
|
|
Value: tt.fieldValue,
|
|
}
|
|
|
|
addCatalogerFieldComment(keyNode, valueNode, tt.catalogerName)
|
|
|
|
require.Equal(t, tt.wantLineComment, keyNode.LineComment)
|
|
})
|
|
}
|
|
}
|
|
|
|
// helper functions
|
|
|
|
func getMappingNode(node *yaml.Node) *yaml.Node {
|
|
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
|
|
return node.Content[0]
|
|
}
|
|
return node
|
|
}
|
|
|
|
func getSequenceNode(node *yaml.Node) *yaml.Node {
|
|
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
|
|
return node.Content[0]
|
|
}
|
|
return node
|
|
}
|
|
|
|
func getContentNode(node *yaml.Node) *yaml.Node {
|
|
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
|
|
return node.Content[0]
|
|
}
|
|
return node
|
|
}
|
|
|
|
func findFieldNodes(mappingNode *yaml.Node, fieldName string) (*yaml.Node, *yaml.Node) {
|
|
if mappingNode.Kind != yaml.MappingNode {
|
|
return nil, nil
|
|
}
|
|
|
|
for i := 0; i < len(mappingNode.Content); i += 2 {
|
|
if mappingNode.Content[i].Value == fieldName {
|
|
return mappingNode.Content[i], mappingNode.Content[i+1]
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|