Alex Goodman d6512456b3 improve testing a docs
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-10-29 10:08:29 -04:00

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
}