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  }