mirror of
https://github.com/anchore/syft.git
synced 2026-06-17 09:48:24 +02:00
382 lines
8.9 KiB
Go
382 lines
8.9 KiB
Go
package commands
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/anchore/syft/internal/capabilities"
|
|
)
|
|
|
|
func Test_isDeprecatedCataloger(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selectors []string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "empty selectors",
|
|
selectors: nil,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "no deprecated selector",
|
|
selectors: []string{"python", "pip", "package"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "has deprecated selector",
|
|
selectors: []string{"python", "deprecated", "package"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "only deprecated selector",
|
|
selectors: []string{"deprecated"},
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := isDeprecatedCataloger(tt.selectors)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_hasBothDirectAndIndirect(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
items []string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "empty slice",
|
|
items: nil,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "only direct",
|
|
items: []string{"direct"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "only indirect",
|
|
items: []string{"indirect"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "both direct and indirect",
|
|
items: []string{"direct", "indirect"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "both with other items",
|
|
items: []string{"other", "direct", "more", "indirect", "stuff"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "unrelated items",
|
|
items: []string{"foo", "bar", "baz"},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := hasBothDirectAndIndirect(tt.items)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_filterCatalogersByName(t *testing.T) {
|
|
catalogers := []capabilities.CatalogerEntry{
|
|
{Name: "python-cataloger", Ecosystem: "python"},
|
|
{Name: "java-cataloger", Ecosystem: "java"},
|
|
{Name: "go-cataloger", Ecosystem: "golang"},
|
|
{Name: "npm-cataloger", Ecosystem: "javascript"},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
catalogers []capabilities.CatalogerEntry
|
|
names []string
|
|
want []capabilities.CatalogerEntry
|
|
}{
|
|
{
|
|
name: "empty names returns empty",
|
|
catalogers: catalogers,
|
|
names: nil,
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "single match",
|
|
catalogers: catalogers,
|
|
names: []string{"python-cataloger"},
|
|
want: []capabilities.CatalogerEntry{
|
|
{Name: "python-cataloger", Ecosystem: "python"},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple matches",
|
|
catalogers: catalogers,
|
|
names: []string{"python-cataloger", "java-cataloger"},
|
|
want: []capabilities.CatalogerEntry{
|
|
{Name: "python-cataloger", Ecosystem: "python"},
|
|
{Name: "java-cataloger", Ecosystem: "java"},
|
|
},
|
|
},
|
|
{
|
|
name: "no matches",
|
|
catalogers: catalogers,
|
|
names: []string{"nonexistent"},
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "partial matches",
|
|
catalogers: catalogers,
|
|
names: []string{"python-cataloger", "nonexistent"},
|
|
want: []capabilities.CatalogerEntry{
|
|
{Name: "python-cataloger", Ecosystem: "python"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := filterCatalogersByName(tt.catalogers, tt.names)
|
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
t.Errorf("filterCatalogersByName() mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_formatCriteria(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
detectors []capabilities.Detector
|
|
want string
|
|
}{
|
|
{
|
|
name: "empty detectors",
|
|
detectors: nil,
|
|
want: "",
|
|
},
|
|
{
|
|
name: "single glob detector",
|
|
detectors: []capabilities.Detector{
|
|
{
|
|
Method: capabilities.GlobDetection,
|
|
Criteria: []string{"**/*.py"},
|
|
},
|
|
},
|
|
want: "**/*.py",
|
|
},
|
|
{
|
|
name: "multiple criteria single detector",
|
|
detectors: []capabilities.Detector{
|
|
{
|
|
Method: capabilities.GlobDetection,
|
|
Criteria: []string{"**/*.py", "**/*.pyc", "**/requirements.txt"},
|
|
},
|
|
},
|
|
want: "**/*.py, **/*.pyc, **/requirements.txt",
|
|
},
|
|
{
|
|
name: "path detection shows method",
|
|
detectors: []capabilities.Detector{
|
|
{
|
|
Method: capabilities.PathDetection,
|
|
Criteria: []string{"/usr/bin/python"},
|
|
},
|
|
},
|
|
want: "/usr/bin/python (path)",
|
|
},
|
|
{
|
|
name: "mimetype detection shows method",
|
|
detectors: []capabilities.Detector{
|
|
{
|
|
Method: capabilities.MIMETypeDetection,
|
|
Criteria: []string{"application/x-python"},
|
|
},
|
|
},
|
|
want: "application/x-python (mimetype)",
|
|
},
|
|
{
|
|
name: "multiple detectors combine criteria",
|
|
detectors: []capabilities.Detector{
|
|
{
|
|
Method: capabilities.GlobDetection,
|
|
Criteria: []string{"**/*.py"},
|
|
},
|
|
{
|
|
Method: capabilities.GlobDetection,
|
|
Criteria: []string{"**/*.pyc"},
|
|
},
|
|
},
|
|
want: "**/*.py, **/*.pyc",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := formatCriteria(tt.detectors)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testDocument() *capabilities.Document {
|
|
return &capabilities.Document{
|
|
Configs: map[string]capabilities.CatalogerConfigEntry{
|
|
"test-config": {
|
|
Fields: []capabilities.CatalogerConfigFieldEntry{
|
|
{Key: "field1", Description: "test field"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func Test_catalogerInfoReport(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
opts *catalogerInfoOptions
|
|
doc *capabilities.Document
|
|
catalogers []capabilities.CatalogerEntry
|
|
wantErr require.ErrorAssertionFunc
|
|
assertions func(t *testing.T, got string)
|
|
}{
|
|
{
|
|
name: "empty catalogers table",
|
|
opts: &catalogerInfoOptions{Output: "table"},
|
|
doc: testDocument(),
|
|
assertions: func(t *testing.T, got string) {
|
|
assert.Contains(t, got, "No catalogers found")
|
|
},
|
|
},
|
|
{
|
|
name: "empty catalogers json",
|
|
opts: &catalogerInfoOptions{Output: "json"},
|
|
doc: testDocument(),
|
|
assertions: func(t *testing.T, got string) {
|
|
assert.Contains(t, got, `"catalogers":null`)
|
|
},
|
|
},
|
|
{
|
|
name: "single cataloger table output",
|
|
opts: &catalogerInfoOptions{Output: "table"},
|
|
doc: testDocument(),
|
|
catalogers: []capabilities.CatalogerEntry{
|
|
{
|
|
Name: "test-cataloger",
|
|
Ecosystem: "test",
|
|
Type: "custom",
|
|
Detectors: []capabilities.Detector{
|
|
{Method: capabilities.GlobDetection, Criteria: []string{"**/*.test"}},
|
|
},
|
|
PURLTypes: []string{"npm"},
|
|
},
|
|
},
|
|
assertions: func(t *testing.T, got string) {
|
|
assert.Contains(t, got, "test-cataloger")
|
|
assert.Contains(t, got, "ECOSYSTEM")
|
|
assert.Contains(t, got, "CATALOGER")
|
|
assert.Contains(t, got, "PURL")
|
|
assert.Contains(t, got, "npm")
|
|
},
|
|
},
|
|
{
|
|
name: "single cataloger json output",
|
|
opts: &catalogerInfoOptions{Output: "json"},
|
|
doc: testDocument(),
|
|
catalogers: []capabilities.CatalogerEntry{
|
|
{
|
|
Name: "test-cataloger",
|
|
Ecosystem: "test",
|
|
Type: "custom",
|
|
Selectors: []string{"test", "custom"},
|
|
Detectors: []capabilities.Detector{
|
|
{Method: capabilities.GlobDetection, Criteria: []string{"**/*.test"}},
|
|
},
|
|
PURLTypes: []string{"npm"},
|
|
},
|
|
},
|
|
assertions: func(t *testing.T, got string) {
|
|
assert.Contains(t, got, `"name":"test-cataloger"`)
|
|
assert.Contains(t, got, `"ecosystem":"test"`)
|
|
assert.Contains(t, got, `"type":"custom"`)
|
|
assert.Contains(t, got, `"purl_types":["npm"]`)
|
|
},
|
|
},
|
|
{
|
|
name: "deprecated cataloger json output",
|
|
opts: &catalogerInfoOptions{Output: "json"},
|
|
doc: testDocument(),
|
|
catalogers: []capabilities.CatalogerEntry{
|
|
{
|
|
Name: "old-cataloger",
|
|
Ecosystem: "legacy",
|
|
Type: "custom",
|
|
Selectors: []string{"legacy", "deprecated"},
|
|
},
|
|
},
|
|
assertions: func(t *testing.T, got string) {
|
|
assert.Contains(t, got, `"deprecated":true`)
|
|
},
|
|
},
|
|
{
|
|
name: "invalid output format",
|
|
opts: &catalogerInfoOptions{Output: "invalid"},
|
|
doc: testDocument(),
|
|
wantErr: require.Error,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.wantErr == nil {
|
|
tt.wantErr = require.NoError
|
|
}
|
|
|
|
got, err := catalogerInfoReport(tt.opts, tt.doc, tt.catalogers)
|
|
tt.wantErr(t, err)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if tt.assertions != nil {
|
|
tt.assertions(t, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_formatCriteria_wordWrapping(t *testing.T) {
|
|
// test word wrapping behavior with long criteria lists
|
|
longCriteria := make([]string, 20)
|
|
for i := range longCriteria {
|
|
longCriteria[i] = "**/file" + strings.Repeat("x", 10)
|
|
}
|
|
|
|
detector := capabilities.Detector{
|
|
Method: capabilities.GlobDetection,
|
|
Criteria: longCriteria,
|
|
}
|
|
|
|
got := formatCriteria([]capabilities.Detector{detector})
|
|
|
|
// should contain newlines for wrapping
|
|
assert.Contains(t, got, "\n")
|
|
// should contain all criteria (joined with ", " and possibly split across lines)
|
|
for _, c := range longCriteria {
|
|
assert.Contains(t, got, c)
|
|
}
|
|
}
|