github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/spdxjson/encoder_test.go (about) 1 package spdxjson 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/spdxutil" 15 "github.com/lineaje-labs/syft/syft/format/internal/testutil" 16 ) 17 18 var updateSnapshot = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-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: spdxutil.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 TestSPDXJSONDirectoryEncoder(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 TestSPDXJSONImageEncoder(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, testutil.FromSnapshot()), 107 Format: getEncoder(t), 108 UpdateSnapshot: *updateSnapshot, 109 PersistRedactionsInSnapshot: true, 110 IsJSON: true, 111 Redactor: redactor(), 112 }, 113 ) 114 } 115 116 func TestSPDXRelationshipOrder(t *testing.T) { 117 testImage := "image-simple" 118 119 s := testutil.ImageInput(t, testImage, testutil.FromSnapshot()) 120 testutil.AddSampleFileRelationships(&s) 121 122 testutil.AssertEncoderAgainstGoldenImageSnapshot(t, 123 testutil.ImageSnapshotTestConfig{ 124 Image: testImage, 125 UpdateImageSnapshot: *updateImage, 126 }, 127 testutil.EncoderSnapshotTestConfig{ 128 Subject: s, 129 Format: getEncoder(t), 130 UpdateSnapshot: *updateSnapshot, 131 PersistRedactionsInSnapshot: true, 132 IsJSON: true, 133 Redactor: redactor(), 134 }, 135 ) 136 } 137 138 func redactor(values ...string) testutil.Redactor { 139 return testutil.NewRedactions(). 140 WithValuesRedacted(values...). 141 WithPatternRedactors( 142 map[string]string{ 143 // each SBOM reports the time it was generated, which is not useful during snapshot testing 144 `"created":\s+"[^"]*"`: `"created":"redacted"`, 145 146 // each SBOM reports a unique documentNamespace when generated, this is not useful for snapshot testing 147 `"documentNamespace":\s+"[^"]*"`: `"documentNamespace":"redacted"`, 148 149 // the license list will be updated periodically, the value here should not be directly tested in snapshot tests 150 `"licenseListVersion":\s+"[^"]*"`: `"licenseListVersion":"redacted"`, 151 }, 152 ) 153 } 154 155 func TestSupportedVersions(t *testing.T) { 156 encs := defaultFormatEncoders() 157 require.NotEmpty(t, encs) 158 159 versions := SupportedVersions() 160 require.Equal(t, len(versions), len(encs)) 161 162 subject := testutil.DirectoryInput(t, t.TempDir()) 163 dec := NewFormatDecoder() 164 165 relationshipOffsetPerVersion := map[string]int{ 166 // the package representing the source gets a relationship from the source package to all other packages found 167 // these relationships cannot be removed until the primaryPackagePurpose info is available in 2.3 168 "2.1": 2, 169 "2.2": 2, 170 // the source-to-package relationships can be removed since the primaryPackagePurpose info is available in 2.3 171 "2.3": 0, 172 } 173 174 pkgCountOffsetPerVersion := map[string]int{ 175 "2.1": 1, // the source is mapped as a package, but cannot distinguish it since the primaryPackagePurpose info is not available until 2.3 176 "2.2": 1, // the source is mapped as a package, but cannot distinguish it since the primaryPackagePurpose info is not available until 2.3 177 "2.3": 0, // the source package can be removed since the primaryPackagePurpose info is available 178 } 179 180 for _, enc := range encs { 181 t.Run(enc.Version(), func(t *testing.T) { 182 require.Contains(t, versions, enc.Version()) 183 184 var buf bytes.Buffer 185 require.NoError(t, enc.Encode(&buf, subject)) 186 187 id, version := dec.Identify(bytes.NewReader(buf.Bytes())) 188 assert.Equal(t, enc.ID(), id) 189 assert.Equal(t, enc.Version(), version) 190 191 var s *sbom.SBOM 192 var err error 193 s, id, version, err = dec.Decode(bytes.NewReader(buf.Bytes())) 194 require.NoError(t, err) 195 196 assert.Equal(t, enc.ID(), id) 197 assert.Equal(t, enc.Version(), version) 198 199 require.NotEmpty(t, s.Artifacts.Packages.PackageCount()) 200 201 offset := relationshipOffsetPerVersion[enc.Version()] 202 203 assert.Equal(t, len(subject.Relationships)+offset, len(s.Relationships), "mismatched relationship count") 204 205 offset = pkgCountOffsetPerVersion[enc.Version()] 206 207 if !assert.Equal(t, subject.Artifacts.Packages.PackageCount()+offset, s.Artifacts.Packages.PackageCount(), "mismatched package count") { 208 t.Logf("expected: %d", subject.Artifacts.Packages.PackageCount()) 209 for _, p := range subject.Artifacts.Packages.Sorted() { 210 t.Logf(" - %s", p.String()) 211 } 212 t.Logf("actual: %d", s.Artifacts.Packages.PackageCount()) 213 for _, p := range s.Artifacts.Packages.Sorted() { 214 t.Logf(" - %s", p.String()) 215 } 216 } 217 }) 218 } 219 } 220 221 func defaultFormatEncoders() []sbom.FormatEncoder { 222 var encs []sbom.FormatEncoder 223 for _, version := range SupportedVersions() { 224 enc, err := NewFormatEncoderWithConfig(EncoderConfig{Version: version}) 225 if err != nil { 226 panic(err) 227 } 228 encs = append(encs, enc) 229 } 230 return encs 231 }