github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/sbom/spdx/unmarshal_test.go (about) 1 package spdx_test 2 3 import ( 4 "encoding/json" 5 "os" 6 "sort" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 ftypes "github.com/devseccon/trivy/pkg/fanal/types" 13 "github.com/devseccon/trivy/pkg/sbom/spdx" 14 "github.com/devseccon/trivy/pkg/types" 15 ) 16 17 func TestUnmarshaler_Unmarshal(t *testing.T) { 18 tests := []struct { 19 name string 20 inputFile string 21 want types.SBOM 22 wantErr string 23 }{ 24 { 25 name: "happy path", 26 inputFile: "testdata/happy/bom.json", 27 want: types.SBOM{ 28 OS: ftypes.OS{ 29 Family: "alpine", 30 Name: "3.16.0", 31 }, 32 Packages: []ftypes.PackageInfo{ 33 { 34 Packages: ftypes.Packages{ 35 { 36 Name: "musl", 37 Version: "1.2.3-r0", 38 SrcName: "musl", 39 SrcVersion: "1.2.3-r0", 40 Licenses: []string{"MIT"}, 41 Ref: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", 42 Layer: ftypes.Layer{ 43 DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", 44 }, 45 }, 46 }, 47 }, 48 }, 49 Applications: []ftypes.Application{ 50 { 51 Type: "composer", 52 FilePath: "app/composer/composer.lock", 53 Libraries: ftypes.Packages{ 54 { 55 Name: "pear/log", 56 Version: "1.13.1", 57 Ref: "pkg:composer/pear/log@1.13.1", 58 Layer: ftypes.Layer{ 59 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 60 }, 61 }, 62 { 63 64 Name: "pear/pear_exception", 65 Version: "v1.0.0", 66 Ref: "pkg:composer/pear/pear_exception@v1.0.0", 67 Layer: ftypes.Layer{ 68 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 69 }, 70 }, 71 }, 72 }, 73 { 74 Type: "gobinary", 75 FilePath: "app/gobinary/gobinary", 76 Libraries: ftypes.Packages{ 77 { 78 Name: "github.com/package-url/packageurl-go", 79 Version: "v0.1.1-0.20220203205134-d70459300c8a", 80 Ref: "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", 81 Layer: ftypes.Layer{ 82 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 83 }, 84 }, 85 }, 86 }, 87 { 88 Type: "jar", 89 Libraries: ftypes.Packages{ 90 { 91 Name: "org.codehaus.mojo:child-project", 92 Ref: "pkg:maven/org.codehaus.mojo/child-project@1.0", 93 Version: "1.0", 94 Layer: ftypes.Layer{ 95 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 96 }, 97 }, 98 }, 99 }, 100 { 101 Type: "node-pkg", 102 Libraries: ftypes.Packages{ 103 { 104 Name: "bootstrap", 105 Version: "5.0.2", 106 Ref: "pkg:npm/bootstrap@5.0.2", 107 Licenses: []string{"MIT"}, 108 Layer: ftypes.Layer{ 109 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 110 }, 111 }, 112 }, 113 }, 114 }, 115 }, 116 }, 117 { 118 name: "happy path for bom with hasFiles field", 119 inputFile: "testdata/happy/with-hasfiles-bom.json", 120 want: types.SBOM{ 121 Applications: []ftypes.Application{ 122 { 123 Type: "node-pkg", 124 Libraries: ftypes.Packages{ 125 { 126 ID: "yargs-parser@21.1.1", 127 Name: "yargs-parser", 128 Version: "21.1.1", 129 Licenses: []string{"ISC"}, 130 Ref: "pkg:npm/yargs-parser@21.1.1", 131 FilePath: "node_modules/yargs-parser/package.json", 132 }, 133 }, 134 }, 135 }, 136 }, 137 }, 138 { 139 name: "happy path for bom with hasFiles field", 140 inputFile: "testdata/happy/with-hasfiles-bom.json", 141 want: types.SBOM{ 142 Applications: []ftypes.Application{ 143 { 144 Type: "node-pkg", 145 Libraries: ftypes.Packages{ 146 { 147 ID: "yargs-parser@21.1.1", 148 Name: "yargs-parser", 149 Version: "21.1.1", 150 Licenses: []string{"ISC"}, 151 Ref: "pkg:npm/yargs-parser@21.1.1", 152 FilePath: "node_modules/yargs-parser/package.json", 153 }, 154 }, 155 }, 156 }, 157 }, 158 }, 159 { 160 name: "happy path for bom files in relationships", 161 inputFile: "testdata/happy/with-files-in-relationships-bom.json", 162 want: types.SBOM{ 163 Applications: []ftypes.Application{ 164 { 165 Type: "node-pkg", 166 Libraries: ftypes.Packages{ 167 { 168 ID: "yargs-parser@21.1.1", 169 Name: "yargs-parser", 170 Version: "21.1.1", 171 Licenses: []string{"ISC"}, 172 Ref: "pkg:npm/yargs-parser@21.1.1", 173 FilePath: "node_modules/yargs-parser/package.json", 174 }, 175 }, 176 }, 177 }, 178 }, 179 }, 180 { 181 name: "happy path for unrelated bom", 182 inputFile: "testdata/happy/unrelated-bom.json", 183 want: types.SBOM{ 184 Applications: []ftypes.Application{ 185 { 186 Type: "composer", 187 FilePath: "app/composer/composer.lock", 188 Libraries: ftypes.Packages{ 189 { 190 Name: "pear/log", 191 Version: "1.13.1", 192 Ref: "pkg:composer/pear/log@1.13.1", 193 }, 194 { 195 196 Name: "pear/pear_exception", 197 Version: "v1.0.0", 198 Ref: "pkg:composer/pear/pear_exception@v1.0.0", 199 }, 200 }, 201 }, 202 }, 203 }, 204 }, 205 { 206 name: "happy path with no relationship", 207 inputFile: "testdata/happy/no-relationship.json", 208 want: types.SBOM{ 209 Applications: []ftypes.Application{ 210 { 211 Type: ftypes.Jar, 212 Libraries: ftypes.Packages{ 213 { 214 FilePath: "modules/apm/elastic-apm-agent-1.36.0.jar", 215 Name: "co.elastic.apm:apm-agent", 216 Version: "1.36.0", 217 Ref: "pkg:maven/co.elastic.apm/apm-agent@1.36.0", 218 }, 219 { 220 FilePath: "modules/apm/elastic-apm-agent-1.36.0.jar", 221 Name: "co.elastic.apm:apm-agent-cached-lookup-key", 222 Version: "1.36.0", 223 Ref: "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0", 224 }, 225 }, 226 }, 227 }, 228 }, 229 }, 230 { 231 name: "happy path only os component", 232 inputFile: "testdata/happy/os-only-bom.json", 233 want: types.SBOM{ 234 OS: ftypes.OS{ 235 Family: "alpine", 236 Name: "3.16.0", 237 }, 238 }, 239 }, 240 { 241 name: "happy path empty component", 242 inputFile: "testdata/happy/empty-bom.json", 243 want: types.SBOM{}, 244 }, 245 { 246 name: "sad path invalid purl", 247 inputFile: "testdata/sad/invalid-source-info.json", 248 wantErr: "failed to parse source info:", 249 }, 250 } 251 252 for _, tt := range tests { 253 t.Run(tt.name, func(t *testing.T) { 254 f, err := os.Open(tt.inputFile) 255 require.NoError(t, err) 256 defer f.Close() 257 258 v := &spdx.SPDX{SBOM: &types.SBOM{}} 259 err = json.NewDecoder(f).Decode(v) 260 if tt.wantErr != "" { 261 require.Error(t, err) 262 assert.Contains(t, err.Error(), tt.wantErr) 263 return 264 } 265 266 // Not compare the SPDX field 267 v.SPDX = nil 268 269 sort.Slice(v.Applications, func(i, j int) bool { 270 return v.Applications[i].Type < v.Applications[j].Type 271 }) 272 require.NoError(t, err) 273 assert.Equal(t, tt.want, *v.SBOM) 274 }) 275 } 276 }