github.com/anchore/syft@v1.38.2/syft/format/internal/cyclonedxutil/helpers/licenses_test.go (about) 1 package helpers 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/CycloneDX/cyclonedx-go" 8 "github.com/google/go-cmp/cmp" 9 "github.com/stretchr/testify/assert" 10 11 "github.com/anchore/syft/syft/license" 12 "github.com/anchore/syft/syft/pkg" 13 ) 14 15 func Test_encodeLicense(t *testing.T) { 16 ctx := context.TODO() 17 tests := []struct { 18 name string 19 input pkg.Package 20 expected *cyclonedx.Licenses 21 }{ 22 { 23 name: "no licenses", 24 input: pkg.Package{}, 25 }, 26 { 27 name: "no SPDX licenses", 28 input: pkg.Package{ 29 Licenses: pkg.NewLicenseSet( 30 pkg.NewLicenseWithContext(ctx, "RandomLicense"), 31 ), 32 }, 33 expected: &cyclonedx.Licenses{ 34 { 35 License: &cyclonedx.License{ 36 Name: "RandomLicense", 37 }, 38 }, 39 }, 40 }, 41 { 42 name: "single SPDX ID and Non SPDX ID", 43 input: pkg.Package{ 44 Licenses: pkg.NewLicenseSet( 45 pkg.NewLicenseWithContext(ctx, "mit"), 46 pkg.NewLicenseWithContext(ctx, "FOOBAR"), 47 ), 48 }, 49 expected: &cyclonedx.Licenses{ 50 { 51 License: &cyclonedx.License{ 52 ID: "MIT", 53 }, 54 }, 55 { 56 License: &cyclonedx.License{ 57 Name: "FOOBAR", 58 }, 59 }, 60 }, 61 }, 62 { 63 name: "with complex SPDX license expression", 64 input: pkg.Package{ 65 Licenses: pkg.NewLicenseSet( 66 pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"), 67 ), 68 }, 69 expected: &cyclonedx.Licenses{ 70 { 71 Expression: "MIT AND GPL-3.0-only", 72 }, 73 }, 74 }, 75 { 76 name: "with multiple complex SPDX license expression", 77 input: pkg.Package{ 78 Licenses: pkg.NewLicenseSet( 79 pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"), 80 pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only WITH Classpath-exception-2.0"), 81 ), 82 }, 83 expected: &cyclonedx.Licenses{ 84 { 85 Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)", 86 }, 87 }, 88 }, 89 { 90 name: "with multiple URLs and expressions", 91 input: pkg.Package{ 92 Licenses: pkg.NewLicenseSet( 93 pkg.NewLicenseFromURLsWithContext(ctx, "MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"), 94 pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"), 95 pkg.NewLicenseFromURLsWithContext(ctx, "FakeLicense", "htts://someurl.com"), 96 ), 97 }, 98 expected: &cyclonedx.Licenses{ 99 { 100 License: &cyclonedx.License{ 101 ID: "MIT", 102 URL: "https://opensource.org/licenses/MIT", 103 }, 104 }, 105 { 106 License: &cyclonedx.License{ 107 ID: "MIT", 108 URL: "https://spdx.org/licenses/MIT.html", 109 }, 110 }, 111 { 112 License: &cyclonedx.License{ 113 Name: "FakeLicense", 114 URL: "htts://someurl.com", 115 }, 116 }, 117 { 118 License: &cyclonedx.License{ 119 Name: "MIT AND GPL-3.0-only", 120 }, 121 }, 122 }, 123 }, 124 { 125 name: "with URL only and no name or SPDX ID", 126 input: pkg.Package{ 127 Licenses: pkg.NewLicenseSet( 128 pkg.NewLicenseFromURLsWithContext(ctx, "", "http://jaxen.codehaus.org/license.html"), 129 ), 130 }, 131 expected: &cyclonedx.Licenses{ 132 { 133 License: &cyclonedx.License{ 134 Name: "http://jaxen.codehaus.org/license.html", 135 URL: "http://jaxen.codehaus.org/license.html", 136 }, 137 }, 138 }, 139 }, 140 { 141 name: "with multiple values licenses are deduplicated", 142 input: pkg.Package{ 143 Licenses: pkg.NewLicenseSet( 144 pkg.NewLicenseWithContext(ctx, "Apache-2"), 145 pkg.NewLicenseWithContext(ctx, "Apache-2.0"), 146 ), 147 }, 148 expected: &cyclonedx.Licenses{ 149 { 150 License: &cyclonedx.License{ 151 ID: "Apache-2.0", 152 }, 153 }, 154 }, 155 }, 156 { 157 name: "with multiple URLs and single with no URLs", 158 input: pkg.Package{ 159 Licenses: pkg.NewLicenseSet( 160 pkg.NewLicenseWithContext(ctx, "MIT"), 161 pkg.NewLicenseFromURLsWithContext(ctx, "MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"), 162 pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"), 163 ), 164 }, 165 expected: &cyclonedx.Licenses{ 166 { 167 License: &cyclonedx.License{ 168 ID: "MIT", 169 URL: "https://opensource.org/licenses/MIT", 170 }, 171 }, 172 { 173 License: &cyclonedx.License{ 174 ID: "MIT", 175 URL: "https://spdx.org/licenses/MIT.html", 176 }, 177 }, 178 { 179 License: &cyclonedx.License{ 180 Name: "MIT AND GPL-3.0-only", 181 }, 182 }, 183 }, 184 }, 185 { 186 name: "single parenthesized SPDX expression", 187 input: pkg.Package{ 188 Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, "(MIT OR Apache-2.0)")...), 189 }, 190 expected: &cyclonedx.Licenses{ 191 { 192 Expression: "MIT OR Apache-2.0", 193 }, 194 }, 195 }, 196 { 197 name: "single license AND to parenthesized SPDX expression", 198 // (LGPL-3.0-or-later OR GPL-2.0-or-later OR (LGPL-3.0-or-later AND GPL-2.0-or-later)) AND GFDL-1.3-invariants-or-later 199 input: pkg.Package{ 200 Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, "(LGPL-3.0-or-later OR GPL-2.0-or-later OR (LGPL-3.0-or-later AND GPL-2.0-or-later)) AND GFDL-1.3-invariants-or-later")...), 201 }, 202 expected: &cyclonedx.Licenses{ 203 { 204 Expression: "(LGPL-3.0-or-later OR GPL-2.0-or-later OR (LGPL-3.0-or-later AND GPL-2.0-or-later)) AND GFDL-1.3-invariants-or-later", 205 }, 206 }, 207 }, 208 // TODO: do we drop the non SPDX ID license and do a single expression 209 // OR do we keep the non SPDX ID license and do multiple licenses where the complex 210 // expressions are set as the NAME field? 211 //{ 212 // name: "with multiple complex SPDX license expression and a non spdx id", 213 // input: pkg.Package{ 214 // Licenses: []pkg.License{ 215 // { 216 // SPDXExpression: "MIT AND GPL-3.0-only", 217 // }, 218 // { 219 // SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0", 220 // }, 221 // { 222 // Value: "FOOBAR", 223 // }, 224 // }, 225 // }, 226 // expected: &cyclonedx.Licenses{ 227 // { 228 // Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)", 229 // }, 230 // }, 231 //}, 232 } 233 for _, test := range tests { 234 t.Run(test.name, func(t *testing.T) { 235 if d := cmp.Diff(test.expected, encodeLicenses(test.input)); d != "" { 236 t.Errorf("unexpected license (-want +got):\n%s", d) 237 } 238 }) 239 } 240 } 241 242 func TestDecodeLicenses(t *testing.T) { 243 tests := []struct { 244 name string 245 input *cyclonedx.Component 246 expected []pkg.License 247 }{ 248 { 249 name: "no licenses", 250 input: &cyclonedx.Component{}, 251 expected: []pkg.License{}, 252 }, 253 { 254 name: "no SPDX license ID or expression", 255 input: &cyclonedx.Component{ 256 Licenses: &cyclonedx.Licenses{ 257 { 258 License: &cyclonedx.License{ 259 Name: "RandomLicense", 260 }, 261 }, 262 }, 263 }, 264 expected: []pkg.License{ 265 { 266 Value: "RandomLicense", 267 // CycloneDX specification doesn't give a field for determining the license type 268 Type: license.Declared, 269 }, 270 }, 271 }, 272 { 273 name: "with SPDX license ID", 274 input: &cyclonedx.Component{ 275 Licenses: &cyclonedx.Licenses{ 276 { 277 License: &cyclonedx.License{ 278 ID: "MIT", 279 }, 280 }, 281 }, 282 }, 283 expected: []pkg.License{ 284 { 285 Value: "MIT", 286 SPDXExpression: "MIT", 287 Type: license.Declared, 288 }, 289 }, 290 }, 291 { 292 name: "with complex SPDX license expression", 293 input: &cyclonedx.Component{ 294 Licenses: &cyclonedx.Licenses{ 295 { 296 // CycloneDX specification doesn't allow to provide License if Expression is provided 297 License: nil, 298 Expression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0", 299 }, 300 }, 301 }, 302 expected: []pkg.License{ 303 { 304 Value: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0", 305 SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0", 306 Type: license.Declared, 307 }, 308 }, 309 }, 310 } 311 for _, test := range tests { 312 t.Run(test.name, func(t *testing.T) { 313 assert.Equal(t, test.expected, decodeLicenses(test.input)) 314 }) 315 } 316 }