github.com/anchore/syft@v1.38.2/syft/format/internal/spdxutil/helpers/license_test.go (about) 1 package helpers 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/stretchr/testify/assert" 9 10 "github.com/anchore/syft/internal/spdxlicense" 11 "github.com/anchore/syft/syft/pkg" 12 ) 13 14 func Test_License(t *testing.T) { 15 ctx := context.TODO() 16 type expected struct { 17 concluded string 18 declared string 19 } 20 tests := []struct { 21 name string 22 input pkg.Package 23 expected expected 24 }{ 25 { 26 name: "no licenses", 27 input: pkg.Package{}, 28 expected: expected{ 29 concluded: "NOASSERTION", 30 declared: "NOASSERTION", 31 }, 32 }, 33 { 34 name: "no SPDX licenses", 35 input: pkg.Package{ 36 Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "made-up")), 37 }, 38 expected: expected{ 39 concluded: "NOASSERTION", 40 declared: "LicenseRef-made-up", 41 }, 42 }, 43 { 44 name: "with SPDX license", 45 input: pkg.Package{ 46 Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")), 47 }, 48 expected: struct { 49 concluded string 50 declared string 51 }{ 52 concluded: "NOASSERTION", 53 declared: "MIT", 54 }, 55 }, 56 { 57 name: "with SPDX license expression", 58 input: pkg.Package{ 59 Licenses: pkg.NewLicenseSet( 60 pkg.NewLicenseWithContext(ctx, "MIT"), 61 pkg.NewLicenseWithContext(ctx, "GPL-3.0-only"), 62 ), 63 }, 64 expected: expected{ 65 concluded: "NOASSERTION", 66 // because we sort licenses alphabetically GPL ends up at the start 67 declared: "GPL-3.0-only AND MIT", 68 }, 69 }, 70 { 71 name: "includes valid LicenseRef-", 72 input: pkg.Package{ 73 Licenses: pkg.NewLicenseSet( 74 pkg.NewLicenseWithContext(ctx, "one thing first"), 75 pkg.NewLicenseWithContext(ctx, "two things/#$^second"), 76 pkg.NewLicenseWithContext(ctx, "MIT"), 77 ), 78 }, 79 expected: expected{ 80 concluded: "NOASSERTION", 81 // because we separate licenses between valid SPDX and non valid, valid ID always end at the front 82 declared: "MIT AND LicenseRef-one-thing-first AND LicenseRef-two-things----second", 83 }, 84 }, 85 { 86 name: "join parentheses correctly", 87 input: pkg.Package{ 88 Licenses: pkg.NewLicenseSet( 89 pkg.NewLicenseWithContext(ctx, "one thing first"), 90 pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"), 91 pkg.NewLicenseWithContext(ctx, "MIT OR APACHE-2.0"), 92 ), 93 }, 94 expected: expected{ 95 concluded: "NOASSERTION", 96 // because we separate licenses between valid SPDX and non valid, valid ID always end at the front 97 declared: "(MIT AND GPL-3.0-only) AND (MIT OR APACHE-2.0) AND LicenseRef-one-thing-first", 98 }, 99 }, 100 } 101 for _, test := range tests { 102 t.Run(test.name, func(t *testing.T) { 103 c, d, _ := License(test.input) 104 assert.Equal(t, test.expected.concluded, c) 105 assert.Equal(t, test.expected.declared, d) 106 }) 107 } 108 } 109 110 func TestGenerateLicenseID(t *testing.T) { 111 tests := []struct { 112 name string 113 license pkg.License 114 expected string 115 }{ 116 { 117 name: "SPDX expression is preferred", 118 license: pkg.License{ 119 SPDXExpression: "Apache-2.0", 120 Value: "SomeValue", 121 Contents: "Some text", 122 }, 123 expected: "Apache-2.0", 124 }, 125 { 126 name: "Uses value if no SPDX expression", 127 license: pkg.License{ 128 Value: "my-sweet-custom-license", 129 }, 130 expected: spdxlicense.LicenseRefPrefix + "my-sweet-custom-license", 131 }, 132 { 133 // note: this is an oversight of the SPDX spec. It does NOT allow "+" in the ID even though they are 134 // significant to the licenses in the expressions below 135 name: "Long value is sanitized correctly", 136 license: pkg.License{ 137 Value: "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+ and GPLv2+ with exceptions and BSD and Inner-Net and ISC and Public Domain and GFDL", 138 }, 139 expected: spdxlicense.LicenseRefPrefix + 140 "LGPLv2--and-LGPLv2--with-exceptions-and-GPLv2--and-GPLv2--with-exceptions-and-BSD-and-Inner-Net-and-ISC-and-Public-Domain-and-GFDL", 141 }, 142 } 143 144 for _, tt := range tests { 145 t.Run(tt.name, func(t *testing.T) { 146 id := generateLicenseID(tt.license) 147 if tt.expected == "" { 148 assert.True(t, len(id) > len(spdxlicense.LicenseRefPrefix)) 149 assert.Contains(t, id, spdxlicense.LicenseRefPrefix) 150 } else { 151 assert.Equal(t, tt.expected, id) 152 } 153 }) 154 } 155 } 156 157 func Test_joinLicenses(t *testing.T) { 158 tests := []struct { 159 name string 160 args []SPDXLicense 161 want string 162 }{ 163 { 164 name: "multiple licenses", 165 args: []SPDXLicense{{ID: "MIT"}, {ID: "GPL-3.0-only"}}, 166 want: "MIT AND GPL-3.0-only", 167 }, 168 { 169 name: "multiple licenses with complex expressions", 170 args: []SPDXLicense{{ID: "MIT AND Apache"}, {ID: "GPL-3.0-only"}}, 171 want: "(MIT AND Apache) AND GPL-3.0-only", 172 }, 173 } 174 for _, tt := range tests { 175 t.Run(tt.name, func(t *testing.T) { 176 assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args) 177 }) 178 } 179 } 180 181 func TestCreateSPDXLicenseAndGenerateLicenseID(t *testing.T) { 182 tests := []struct { 183 name string 184 input pkg.License 185 expected SPDXLicense 186 }{ 187 { 188 name: "SPDX expression used as ID", 189 input: pkg.License{ 190 SPDXExpression: "MIT", 191 Value: "MIT", 192 Contents: "", 193 }, 194 expected: SPDXLicense{ 195 ID: "MIT", 196 LicenseName: "MIT", 197 FullText: "NOASSERTION", 198 }, 199 }, 200 { 201 name: "LicenseRef with contents", 202 input: pkg.License{ 203 Value: "sha256:123abc", 204 Contents: "license contents here", 205 }, 206 expected: SPDXLicense{ 207 ID: "LicenseRef-123abc", 208 LicenseName: "sha256:123abc", 209 FullText: "license contents here", 210 }, 211 }, 212 { 213 name: "LicenseRef without contents", 214 input: pkg.License{ 215 Value: "custom-license", 216 Contents: "", 217 }, 218 expected: SPDXLicense{ 219 ID: "LicenseRef-custom-license", 220 LicenseName: "custom-license", 221 FullText: "NOASSERTION", 222 }, 223 }, 224 { 225 name: "URL is passed through", 226 input: pkg.License{ 227 SPDXExpression: "MIT", 228 URLs: []string{ 229 "https://example.com/license", 230 }, 231 }, 232 expected: SPDXLicense{ 233 ID: "MIT", 234 FullText: "NOASSERTION", 235 URLs: []string{"https://example.com/license"}, 236 }, 237 }, 238 } 239 240 for _, tt := range tests { 241 t.Run(tt.name, func(t *testing.T) { 242 license := createSPDXLicense(tt.input) 243 if d := cmp.Diff(tt.expected, license); d != "" { 244 t.Errorf("createSPDXLicense() mismatch (-want +got):\n%s", d) 245 } 246 }) 247 } 248 }