github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/cyclonedxjson/encoder_test.go (about) 1 package cyclonedxjson 2 3 import ( 4 "bytes" 5 "flag" 6 "strings" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/anchore/syft/syft/pkg" 13 "github.com/anchore/syft/syft/sbom" 14 "github.com/lineaje-labs/syft/syft/format/internal/cyclonedxutil" 15 "github.com/lineaje-labs/syft/syft/format/internal/testutil" 16 ) 17 18 var updateSnapshot = flag.Bool("update-cyclonedx-json", false, "update the *.golden files for cyclone-dx JSON encoders") 19 var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing") 20 21 func getEncoder(t testing.TB) sbom.FormatEncoder { 22 cfg := DefaultEncoderConfig() 23 cfg.Pretty = true 24 25 enc, err := NewFormatEncoderWithConfig(cfg) 26 require.NoError(t, err) 27 return enc 28 } 29 30 func TestPrettyOutput(t *testing.T) { 31 run := func(opt bool) string { 32 enc, err := NewFormatEncoderWithConfig(EncoderConfig{ 33 Version: cyclonedxutil.DefaultVersion, 34 Pretty: opt, 35 }) 36 require.NoError(t, err) 37 38 dir := t.TempDir() 39 s := testutil.DirectoryInput(t, dir) 40 41 var buffer bytes.Buffer 42 err = enc.Encode(&buffer, s) 43 require.NoError(t, err) 44 45 return strings.TrimSpace(buffer.String()) 46 } 47 48 t.Run("pretty", func(t *testing.T) { 49 actual := run(true) 50 assert.Contains(t, actual, "\n") 51 }) 52 53 t.Run("compact", func(t *testing.T) { 54 actual := run(false) 55 assert.NotContains(t, actual, "\n") 56 }) 57 } 58 59 func TestEscapeHTML(t *testing.T) { 60 dir := t.TempDir() 61 s := testutil.DirectoryInput(t, dir) 62 s.Artifacts.Packages.Add(pkg.Package{ 63 Name: "<html-package>", 64 }) 65 66 // by default we do not escape HTML 67 t.Run("default", func(t *testing.T) { 68 cfg := DefaultEncoderConfig() 69 70 enc, err := NewFormatEncoderWithConfig(cfg) 71 require.NoError(t, err) 72 73 var buffer bytes.Buffer 74 err = enc.Encode(&buffer, s) 75 require.NoError(t, err) 76 77 actual := buffer.String() 78 assert.Contains(t, actual, "<html-package>") 79 assert.NotContains(t, actual, "\\u003chtml-package\\u003e") 80 }) 81 82 } 83 84 func TestCycloneDxDirectoryEncoder(t *testing.T) { 85 dir := t.TempDir() 86 testutil.AssertEncoderAgainstGoldenSnapshot(t, 87 testutil.EncoderSnapshotTestConfig{ 88 Subject: testutil.DirectoryInput(t, dir), 89 Format: getEncoder(t), 90 UpdateSnapshot: *updateSnapshot, 91 PersistRedactionsInSnapshot: true, 92 IsJSON: true, 93 Redactor: redactor(dir), 94 }, 95 ) 96 } 97 98 func TestCycloneDxImageEncoder(t *testing.T) { 99 testImage := "image-simple" 100 testutil.AssertEncoderAgainstGoldenImageSnapshot(t, 101 testutil.ImageSnapshotTestConfig{ 102 Image: testImage, 103 UpdateImageSnapshot: *updateImage, 104 }, 105 testutil.EncoderSnapshotTestConfig{ 106 Subject: testutil.ImageInput(t, testImage), 107 Format: getEncoder(t), 108 UpdateSnapshot: *updateSnapshot, 109 PersistRedactionsInSnapshot: true, 110 IsJSON: true, 111 Redactor: redactor(), 112 }, 113 ) 114 } 115 116 func redactor(values ...string) testutil.Redactor { 117 return testutil.NewRedactions(). 118 WithValuesRedacted(values...). 119 WithPatternRedactors( 120 map[string]string{ 121 // UUIDs 122 `urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`: `urn:uuid:redacted`, 123 124 // timestamps 125 `([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`: `timestamp:redacted`, 126 127 // image hashes 128 `sha256:[A-Fa-f0-9]{64}`: `sha256:redacted`, 129 130 // BOM refs 131 `"bom-ref":\s*"[^"]+"`: `"bom-ref":"redacted"`, 132 }, 133 ) 134 } 135 136 func TestSupportedVersions(t *testing.T) { 137 encs := defaultFormatEncoders() 138 require.NotEmpty(t, encs) 139 140 versions := SupportedVersions() 141 require.Equal(t, len(versions), len(encs)) 142 143 subject := testutil.DirectoryInput(t, t.TempDir()) 144 dec := NewFormatDecoder() 145 146 for _, enc := range encs { 147 t.Run(enc.Version(), func(t *testing.T) { 148 require.Contains(t, versions, enc.Version()) 149 150 var buf bytes.Buffer 151 require.NoError(t, enc.Encode(&buf, subject)) 152 153 id, version := dec.Identify(bytes.NewReader(buf.Bytes())) 154 require.Equal(t, enc.ID(), id) 155 require.Equal(t, enc.Version(), version) 156 157 var s *sbom.SBOM 158 var err error 159 s, id, version, err = dec.Decode(bytes.NewReader(buf.Bytes())) 160 require.NoError(t, err) 161 require.Equal(t, enc.ID(), id) 162 require.Equal(t, enc.Version(), version) 163 164 require.NotEmpty(t, s.Artifacts.Packages.PackageCount()) 165 166 assert.Equal(t, len(subject.Relationships), len(s.Relationships), "mismatched relationship count") 167 168 if !assert.Equal(t, subject.Artifacts.Packages.PackageCount(), s.Artifacts.Packages.PackageCount(), "mismatched package count") { 169 t.Logf("expected: %d", subject.Artifacts.Packages.PackageCount()) 170 for _, p := range subject.Artifacts.Packages.Sorted() { 171 t.Logf(" - %s", p.String()) 172 } 173 t.Logf("actual: %d", s.Artifacts.Packages.PackageCount()) 174 for _, p := range s.Artifacts.Packages.Sorted() { 175 t.Logf(" - %s", p.String()) 176 } 177 } 178 }) 179 } 180 } 181 182 func defaultFormatEncoders() []sbom.FormatEncoder { 183 var encs []sbom.FormatEncoder 184 for _, version := range SupportedVersions() { 185 enc, err := NewFormatEncoderWithConfig(EncoderConfig{Version: version}) 186 if err != nil { 187 panic(err) 188 } 189 encs = append(encs, enc) 190 } 191 return encs 192 }