github.com/google/osv-scalibr@v0.4.1/converter/converter_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package converter_test
    16  
    17  import (
    18  	"math/rand"
    19  	"runtime"
    20  	"testing"
    21  
    22  	"github.com/CycloneDX/cyclonedx-go"
    23  	"github.com/google/go-cmp/cmp"
    24  	scalibr "github.com/google/osv-scalibr"
    25  	"github.com/google/osv-scalibr/converter"
    26  	"github.com/google/osv-scalibr/extractor"
    27  	"github.com/google/osv-scalibr/extractor/filesystem/language/python/wheelegg"
    28  	"github.com/google/osv-scalibr/inventory"
    29  	"github.com/google/osv-scalibr/purl"
    30  	"github.com/google/uuid"
    31  )
    32  
    33  func ptr[T any](v T) *T {
    34  	return &v
    35  }
    36  
    37  func TestToCDX(t *testing.T) {
    38  	// Make UUIDs deterministic
    39  	uuid.SetRand(rand.New(rand.NewSource(1)))
    40  	defaultBOM := cyclonedx.NewBOM()
    41  
    42  	testCases := []struct {
    43  		desc       string
    44  		scanResult *scalibr.ScanResult
    45  		config     converter.CDXConfig
    46  		want       *cyclonedx.BOM
    47  	}{
    48  		{
    49  			desc: "Package_with_custom_config",
    50  			scanResult: &scalibr.ScanResult{
    51  				Inventory: inventory.Inventory{
    52  					Packages: []*extractor.Package{{
    53  						Name:     "software",
    54  						Version:  "1.2.3",
    55  						PURLType: purl.TypePyPi,
    56  						Plugins:  []string{wheelegg.Name},
    57  					}},
    58  				},
    59  			},
    60  			config: converter.CDXConfig{
    61  				ComponentName:    "sbom-1",
    62  				ComponentVersion: "1.0.0",
    63  				Authors:          []string{"author"},
    64  			},
    65  			want: &cyclonedx.BOM{
    66  				Metadata: &cyclonedx.Metadata{
    67  					Component: &cyclonedx.Component{
    68  						Name:    "sbom-1",
    69  						Version: "1.0.0",
    70  						BOMRef:  "52fdfc07-2182-454f-963f-5f0f9a621d72",
    71  					},
    72  					Authors: ptr([]cyclonedx.OrganizationalContact{{Name: "author"}}),
    73  					Tools: &cyclonedx.ToolsChoice{
    74  						Components: &[]cyclonedx.Component{
    75  							{
    76  								Type: cyclonedx.ComponentTypeApplication,
    77  								Name: "SCALIBR",
    78  								ExternalReferences: ptr([]cyclonedx.ExternalReference{
    79  									{URL: "https://github.com/google/osv-scalibr", Type: cyclonedx.ERTypeWebsite},
    80  								}),
    81  							},
    82  						},
    83  					},
    84  				},
    85  				Components: ptr([]cyclonedx.Component{
    86  					{
    87  						BOMRef:     "9566c74d-1003-4c4d-bbbb-0407d1e2c649",
    88  						Type:       "library",
    89  						Name:       "software",
    90  						Version:    "1.2.3",
    91  						PackageURL: "pkg:pypi/software@1.2.3",
    92  					},
    93  				}),
    94  			},
    95  		},
    96  		{
    97  			desc: "Package_with_custom_config_and_cdx-component-type",
    98  			scanResult: &scalibr.ScanResult{
    99  				Inventory: inventory.Inventory{
   100  					Packages: []*extractor.Package{{
   101  						Name:     "software",
   102  						Version:  "1.2.3",
   103  						PURLType: purl.TypePyPi,
   104  						Plugins:  []string{wheelegg.Name},
   105  					}},
   106  				},
   107  			},
   108  			config: converter.CDXConfig{
   109  				ComponentName:    "sbom-2",
   110  				ComponentType:    "library",
   111  				ComponentVersion: "1.0.0",
   112  				Authors:          []string{"author"},
   113  			},
   114  			want: &cyclonedx.BOM{
   115  				Metadata: &cyclonedx.Metadata{
   116  					Component: &cyclonedx.Component{
   117  						Name:    "sbom-2",
   118  						Type:    cyclonedx.ComponentTypeLibrary,
   119  						Version: "1.0.0",
   120  						BOMRef:  "81855ad8-681d-4d86-91e9-1e00167939cb",
   121  					},
   122  					Authors: ptr([]cyclonedx.OrganizationalContact{{Name: "author"}}),
   123  					Tools: &cyclonedx.ToolsChoice{
   124  						Components: &[]cyclonedx.Component{
   125  							{
   126  								Type: cyclonedx.ComponentTypeApplication,
   127  								Name: "SCALIBR",
   128  								ExternalReferences: ptr([]cyclonedx.ExternalReference{
   129  									{URL: "https://github.com/google/osv-scalibr", Type: cyclonedx.ERTypeWebsite},
   130  								}),
   131  							},
   132  						},
   133  					},
   134  				},
   135  				Components: ptr([]cyclonedx.Component{
   136  					{
   137  						BOMRef:     "6694d2c4-22ac-4208-a007-2939487f6999",
   138  						Type:       "library",
   139  						Name:       "software",
   140  						Version:    "1.2.3",
   141  						PackageURL: "pkg:pypi/software@1.2.3",
   142  					},
   143  				}),
   144  			},
   145  		},
   146  	}
   147  
   148  	for _, tc := range testCases {
   149  		t.Run(tc.desc, func(t *testing.T) {
   150  			got := converter.ToCDX(tc.scanResult, tc.config)
   151  			// Can't mock time.Now() so skip verifying the timestamp.
   152  			tc.want.Metadata.Timestamp = got.Metadata.Timestamp
   153  			// Auto-populated fields
   154  			tc.want.XMLNS = defaultBOM.XMLNS
   155  			tc.want.JSONSchema = defaultBOM.JSONSchema
   156  			tc.want.BOMFormat = defaultBOM.BOMFormat
   157  			tc.want.SpecVersion = defaultBOM.SpecVersion
   158  			tc.want.Version = defaultBOM.Version
   159  
   160  			if diff := cmp.Diff(tc.want, got); diff != "" {
   161  				t.Errorf("converter.ToCDX(%v): unexpected diff (-want +got):\n%s", tc.scanResult, diff)
   162  			}
   163  		})
   164  	}
   165  }
   166  
   167  func TestToPURL(t *testing.T) {
   168  	tests := []struct {
   169  		desc   string
   170  		pkg    *extractor.Package
   171  		want   *purl.PackageURL
   172  		onGoos string
   173  	}{
   174  		{
   175  			desc: "Valid_package_extractor",
   176  			pkg: &extractor.Package{
   177  				Name:      "software",
   178  				Version:   "1.0.0",
   179  				PURLType:  purl.TypePyPi,
   180  				Locations: []string{"/file1"},
   181  				Plugins:   []string{wheelegg.Name},
   182  			},
   183  			want: &purl.PackageURL{
   184  				Type:    purl.TypePyPi,
   185  				Name:    "software",
   186  				Version: "1.0.0",
   187  			},
   188  		},
   189  	}
   190  
   191  	for _, tc := range tests {
   192  		t.Run(tc.desc, func(t *testing.T) {
   193  			if tc.onGoos != "" && tc.onGoos != runtime.GOOS {
   194  				t.Skipf("Skipping test on %s", runtime.GOOS)
   195  			}
   196  
   197  			got := converter.ToPURL(tc.pkg)
   198  
   199  			if diff := cmp.Diff(tc.want, got); diff != "" {
   200  				t.Errorf("converter.ToPURL(%v) returned unexpected diff (-want +got):\n%s", tc.pkg, diff)
   201  			}
   202  		})
   203  	}
   204  }