github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/common/cpe/generate_test.go (about)

     1  package cpe
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/scylladb/go-set"
    10  	"github.com/scylladb/go-set/strset"
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"github.com/anchore/syft/syft/cpe"
    14  	"github.com/anchore/syft/syft/pkg"
    15  )
    16  
    17  func TestGeneratePackageCPEs(t *testing.T) {
    18  	tests := []struct {
    19  		name     string
    20  		p        pkg.Package
    21  		expected []string
    22  	}{
    23  		{
    24  			name: "hyphen replacement",
    25  			p: pkg.Package{
    26  				Name:     "name-part",
    27  				Version:  "3.2",
    28  				FoundBy:  "some-analyzer",
    29  				Language: pkg.Python,
    30  				Type:     pkg.DebPkg,
    31  			},
    32  			expected: []string{
    33  				"cpe:2.3:a:name-part:name-part:3.2:*:*:*:*:*:*:*",
    34  				"cpe:2.3:a:name-part:name_part:3.2:*:*:*:*:*:*:*",
    35  				"cpe:2.3:a:name-part:python-name-part:3.2:*:*:*:*:*:*:*",
    36  				"cpe:2.3:a:name-part:python_name_part:3.2:*:*:*:*:*:*:*",
    37  				"cpe:2.3:a:name:name-part:3.2:*:*:*:*:*:*:*",
    38  				"cpe:2.3:a:name:name_part:3.2:*:*:*:*:*:*:*",
    39  				"cpe:2.3:a:name:python-name-part:3.2:*:*:*:*:*:*:*",
    40  				"cpe:2.3:a:name:python_name_part:3.2:*:*:*:*:*:*:*",
    41  				"cpe:2.3:a:name_part:name-part:3.2:*:*:*:*:*:*:*",
    42  				"cpe:2.3:a:name_part:name_part:3.2:*:*:*:*:*:*:*",
    43  				"cpe:2.3:a:name_part:python-name-part:3.2:*:*:*:*:*:*:*",
    44  				"cpe:2.3:a:name_part:python_name_part:3.2:*:*:*:*:*:*:*",
    45  				"cpe:2.3:a:python-name-part:name-part:3.2:*:*:*:*:*:*:*",
    46  				"cpe:2.3:a:python-name-part:name_part:3.2:*:*:*:*:*:*:*",
    47  				"cpe:2.3:a:python-name-part:python-name-part:3.2:*:*:*:*:*:*:*",
    48  				"cpe:2.3:a:python-name-part:python_name_part:3.2:*:*:*:*:*:*:*",
    49  				"cpe:2.3:a:python-name:name-part:3.2:*:*:*:*:*:*:*",
    50  				"cpe:2.3:a:python-name:name_part:3.2:*:*:*:*:*:*:*",
    51  				"cpe:2.3:a:python-name:python-name-part:3.2:*:*:*:*:*:*:*",
    52  				"cpe:2.3:a:python-name:python_name_part:3.2:*:*:*:*:*:*:*",
    53  				"cpe:2.3:a:python:name-part:3.2:*:*:*:*:*:*:*",
    54  				"cpe:2.3:a:python:name_part:3.2:*:*:*:*:*:*:*",
    55  				"cpe:2.3:a:python:python-name-part:3.2:*:*:*:*:*:*:*",
    56  				"cpe:2.3:a:python:python_name_part:3.2:*:*:*:*:*:*:*",
    57  				"cpe:2.3:a:python_name:name-part:3.2:*:*:*:*:*:*:*",
    58  				"cpe:2.3:a:python_name:name_part:3.2:*:*:*:*:*:*:*",
    59  				"cpe:2.3:a:python_name:python-name-part:3.2:*:*:*:*:*:*:*",
    60  				"cpe:2.3:a:python_name:python_name_part:3.2:*:*:*:*:*:*:*",
    61  				"cpe:2.3:a:python_name_part:name-part:3.2:*:*:*:*:*:*:*",
    62  				"cpe:2.3:a:python_name_part:name_part:3.2:*:*:*:*:*:*:*",
    63  				"cpe:2.3:a:python_name_part:python-name-part:3.2:*:*:*:*:*:*:*",
    64  				"cpe:2.3:a:python_name_part:python_name_part:3.2:*:*:*:*:*:*:*",
    65  			},
    66  		},
    67  		{
    68  			name: "python language",
    69  			p: pkg.Package{
    70  				Name:     "name",
    71  				Version:  "3.2",
    72  				FoundBy:  "some-analyzer",
    73  				Language: pkg.Python,
    74  				Type:     pkg.DebPkg,
    75  				Metadata: pkg.PythonPackage{
    76  					Author:      "alex goodman",
    77  					AuthorEmail: "william.goodman@anchore.com",
    78  				},
    79  			},
    80  			expected: []string{
    81  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
    82  				"cpe:2.3:a:name:python-name:3.2:*:*:*:*:*:*:*",
    83  				"cpe:2.3:a:name:python_name:3.2:*:*:*:*:*:*:*",
    84  				"cpe:2.3:a:python-name:name:3.2:*:*:*:*:*:*:*",
    85  				"cpe:2.3:a:python-name:python-name:3.2:*:*:*:*:*:*:*",
    86  				"cpe:2.3:a:python-name:python_name:3.2:*:*:*:*:*:*:*",
    87  				"cpe:2.3:a:python:name:3.2:*:*:*:*:*:*:*",
    88  				"cpe:2.3:a:python:python-name:3.2:*:*:*:*:*:*:*",
    89  				"cpe:2.3:a:python:python_name:3.2:*:*:*:*:*:*:*",
    90  				"cpe:2.3:a:python_name:name:3.2:*:*:*:*:*:*:*",
    91  				"cpe:2.3:a:python_name:python-name:3.2:*:*:*:*:*:*:*",
    92  				"cpe:2.3:a:python_name:python_name:3.2:*:*:*:*:*:*:*",
    93  				"cpe:2.3:a:alex_goodman:name:3.2:*:*:*:*:*:*:*",
    94  				"cpe:2.3:a:alex_goodman:python-name:3.2:*:*:*:*:*:*:*",
    95  				"cpe:2.3:a:alex_goodman:python_name:3.2:*:*:*:*:*:*:*",
    96  				"cpe:2.3:a:william-goodman:name:3.2:*:*:*:*:*:*:*",
    97  				"cpe:2.3:a:william-goodman:python-name:3.2:*:*:*:*:*:*:*",
    98  				"cpe:2.3:a:william-goodman:python_name:3.2:*:*:*:*:*:*:*",
    99  				"cpe:2.3:a:william_goodman:name:3.2:*:*:*:*:*:*:*",
   100  				"cpe:2.3:a:william_goodman:python-name:3.2:*:*:*:*:*:*:*",
   101  				"cpe:2.3:a:william_goodman:python_name:3.2:*:*:*:*:*:*:*",
   102  				"cpe:2.3:a:alex_goodman_project:python_name:3.2:*:*:*:*:*:*:*",
   103  				"cpe:2.3:a:alex_goodman_project:name:3.2:*:*:*:*:*:*:*",
   104  				"cpe:2.3:a:alex_goodman_project:python-name:3.2:*:*:*:*:*:*:*",
   105  				"cpe:2.3:a:alex_goodmanproject:name:3.2:*:*:*:*:*:*:*",
   106  				"cpe:2.3:a:alex_goodmanproject:python-name:3.2:*:*:*:*:*:*:*",
   107  				"cpe:2.3:a:alex_goodmanproject:python_name:3.2:*:*:*:*:*:*:*",
   108  				"cpe:2.3:a:william_goodman_project:name:3.2:*:*:*:*:*:*:*",
   109  				"cpe:2.3:a:william_goodman_project:python-name:3.2:*:*:*:*:*:*:*",
   110  				"cpe:2.3:a:william_goodman_project:python_name:3.2:*:*:*:*:*:*:*",
   111  				"cpe:2.3:a:william_goodmanproject:name:3.2:*:*:*:*:*:*:*",
   112  				"cpe:2.3:a:william_goodmanproject:python-name:3.2:*:*:*:*:*:*:*",
   113  				"cpe:2.3:a:william_goodmanproject:python_name:3.2:*:*:*:*:*:*:*",
   114  			},
   115  		},
   116  		{
   117  			name: "javascript language",
   118  			p: pkg.Package{
   119  				Name:     "name",
   120  				Version:  "3.2",
   121  				FoundBy:  "some-analyzer",
   122  				Language: pkg.JavaScript,
   123  				Metadata: pkg.NpmPackage{
   124  					Author: "jon",
   125  					URL:    "https://github.com/bob/npm-name",
   126  				},
   127  			},
   128  			expected: []string{
   129  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   130  				"cpe:2.3:a:bob:name:3.2:*:*:*:*:*:*:*",
   131  			},
   132  		},
   133  		{
   134  			name: "ruby language",
   135  			p: pkg.Package{
   136  				Name:     "name",
   137  				Version:  "3.2",
   138  				FoundBy:  "some-analyzer",
   139  				Language: pkg.Ruby,
   140  				Type:     pkg.DebPkg,
   141  				Metadata: pkg.RubyGemspec{
   142  					Authors: []string{
   143  						"someones name",
   144  						"someones.elses.name@gmail.com",
   145  					},
   146  					Homepage: "https://github.com/tom/ruby-name",
   147  				},
   148  			},
   149  			expected: []string{
   150  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   151  				"cpe:2.3:a:ruby-lang:name:3.2:*:*:*:*:*:*:*",
   152  				"cpe:2.3:a:ruby:name:3.2:*:*:*:*:*:*:*",
   153  				"cpe:2.3:a:ruby_lang:name:3.2:*:*:*:*:*:*:*",
   154  				"cpe:2.3:a:someones-elses-name:name:3.2:*:*:*:*:*:*:*",
   155  				"cpe:2.3:a:someones-name:name:3.2:*:*:*:*:*:*:*",
   156  				"cpe:2.3:a:someones_elses_name:name:3.2:*:*:*:*:*:*:*",
   157  				"cpe:2.3:a:someones_name:name:3.2:*:*:*:*:*:*:*",
   158  				"cpe:2.3:a:tom:name:3.2:*:*:*:*:*:*:*",
   159  			},
   160  		},
   161  		{
   162  			name: "java language",
   163  			p: pkg.Package{
   164  				Name:     "name",
   165  				Version:  "3.2",
   166  				FoundBy:  "some-analyzer",
   167  				Language: pkg.Java,
   168  				Type:     pkg.JavaPkg,
   169  			},
   170  			expected: []string{
   171  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   172  			},
   173  		},
   174  		{
   175  			name: "java language with groupID",
   176  			p: pkg.Package{
   177  				Name:     "name",
   178  				Version:  "3.2",
   179  				FoundBy:  "some-analyzer",
   180  				Language: pkg.Java,
   181  				Type:     pkg.JavaPkg,
   182  				Metadata: pkg.JavaArchive{
   183  					PomProperties: &pkg.JavaPomProperties{
   184  						GroupID: "org.sonatype.nexus",
   185  					},
   186  				},
   187  			},
   188  			expected: []string{
   189  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   190  				"cpe:2.3:a:name:nexus:3.2:*:*:*:*:*:*:*",
   191  				"cpe:2.3:a:nexus:name:3.2:*:*:*:*:*:*:*",
   192  				"cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:*:*:*",
   193  				"cpe:2.3:a:sonatype:name:3.2:*:*:*:*:*:*:*",
   194  				"cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:*:*:*",
   195  			},
   196  		},
   197  		{
   198  			name: "java with URL in metadata", // regression: https://github.com/anchore/grype/issues/417
   199  			p: pkg.Package{
   200  				Name:    "wstx-asl",
   201  				Version: "3.2.7",
   202  				Type:    pkg.JavaPkg,
   203  				Metadata: pkg.JavaArchive{
   204  					Manifest: &pkg.JavaManifest{
   205  						Main: map[string]string{
   206  							"Ant-Version":            "Apache Ant 1.6.5",
   207  							"Built-By":               "tatu",
   208  							"Created-By":             "1.4.2_03-b02 (Sun Microsystems Inc.)",
   209  							"Implementation-Title":   "WoodSToX XML-processor",
   210  							"Implementation-Vendor":  "woodstox.codehaus.org",
   211  							"Implementation-Version": "3.2.7",
   212  							"Manifest-Version":       "1.0",
   213  							"Specification-Title":    "StAX 1.0 API",
   214  							"Specification-Vendor":   "http://jcp.org/en/jsr/detail?id=173",
   215  							"Specification-Version":  "1.0",
   216  						},
   217  					},
   218  				},
   219  			},
   220  			expected: []string{
   221  				"cpe:2.3:a:woodstox_codehaus_org:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   222  				"cpe:2.3:a:woodstox_codehaus_org:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   223  				"cpe:2.3:a:woodstox-codehaus-org:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   224  				"cpe:2.3:a:woodstox-codehaus-org:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   225  				"cpe:2.3:a:wstx_asl:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   226  				"cpe:2.3:a:wstx-asl:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   227  				"cpe:2.3:a:wstx-asl:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   228  				"cpe:2.3:a:wstx_asl:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   229  				"cpe:2.3:a:wstx:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   230  				"cpe:2.3:a:wstx:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   231  			},
   232  		},
   233  		{
   234  			name: "jenkins package identified via pkg type",
   235  			p: pkg.Package{
   236  				Name:     "name",
   237  				Version:  "3.2",
   238  				FoundBy:  "some-analyzer",
   239  				Language: pkg.Java,
   240  				Type:     pkg.JenkinsPluginPkg,
   241  			},
   242  			expected: []string{
   243  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   244  			},
   245  		},
   246  		{
   247  			name: "java language - multi tier manifest fields",
   248  			p: pkg.Package{
   249  				Name:     "cxf-rt-bindings-xml",
   250  				Version:  "3.3.10",
   251  				FoundBy:  "java-cataloger",
   252  				Language: pkg.Java,
   253  				Type:     pkg.JavaPkg,
   254  				Metadata: pkg.JavaArchive{
   255  					VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar",
   256  					Manifest: &pkg.JavaManifest{
   257  						Main: map[string]string{
   258  							"Automatic-Module-Name":    "org.apache.cxf.binding.xml",
   259  							"Bnd-LastModified":         "1615836524860",
   260  							"Build-Jdk":                "1.8.0_261",
   261  							"Built-By":                 "dkulp",
   262  							"Bundle-ActivationPolicy":  "lazy",
   263  							"Bundle-Description":       "Apache CXF Runtime XML Binding",
   264  							"Bundle-DocURL":            "http://cxf.apache.org",
   265  							"Bundle-License":           "https://www.apache.org/licenses/LICENSE-2.0.txt",
   266  							"Bundle-ManifestVersion":   "2",
   267  							"Bundle-Name":              "Apache CXF Runtime XML Binding",
   268  							"Bundle-SymbolicName":      "org.apache.cxf.cxf-rt-bindings-xml",
   269  							"Bundle-Vendor":            "The Apache Software Foundation",
   270  							"Bundle-Version":           "3.3.10",
   271  							"Created-By":               "Apache Maven Bundle Plugin",
   272  							"Export-Package":           "org.apache.cxf.binding.xml;version=\"3.3.10\",org.apache.cxf.binding.xml.wsdl11;version=\"3.3.10\",org.apache.cxf.binding.xml.interceptor;version=\"3.3.10\",org.apache.cxf.bindings.xformat;version=\"3.3.10\"",
   273  							"Implementation-Vendor":    "The Apache Software Foundation",
   274  							"Implementation-Vendor-Id": "org.apache",
   275  							"Implementation-Version":   "3.3.10",
   276  							"Import-Package":           "javax.xml.bind;version=\"[0,3)\",javax.xml.bind.annotation;version=\"[0,3)\",javax.wsdl;resolution:=optional,javax.wsdl.extensions;resolution:=optional,javax.wsdl.extensions.http;resolution:=optional,javax.xml.namespace,javax.xml.stream,org.apache.cxf;version=\"[3.3,4)\",org.apache.cxf.binding;version=\"[3.3,4)\",org.apache.cxf.binding.xml,org.apache.cxf.binding.xml.interceptor,org.apache.cxf.bindings.xformat,org.apache.cxf.common.i18n;version=\"[3.3,4)\",org.apache.cxf.common.injection;version=\"[3.3,4)\",org.apache.cxf.common.logging;version=\"[3.3,4)\",org.apache.cxf.common.util;version=\"[3.3,4)\",org.apache.cxf.endpoint;version=\"[3.3,4)\",org.apache.cxf.helpers;version=\"[3.3,4)\",org.apache.cxf.interceptor;version=\"[3.3,4)\",org.apache.cxf.message;version=\"[3.3,4)\",org.apache.cxf.service.model;version=\"[3.3,4)\",org.apache.cxf.staxutils;version=\"[3.3,4)\",org.apache.cxf.tools.common;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.tools.validator;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.transport;version=\"[3.3,4)\",org.apache.cxf.wsdl;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.wsdl.http;version=\"[3.3,4)\",org.apache.cxf.wsdl.interceptors;version=\"[3.3,4)\";resolution:=optional,org.w3c.dom",
   277  							"Manifest-Version":         "1.0",
   278  							"Require-Capability":       "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"",
   279  							"Specification-Vendor":     "The Apache Software Foundation",
   280  							"Specification-Version":    "3.3.10",
   281  							"Tool":                     "Bnd-4.2.0.201903051501",
   282  						},
   283  					},
   284  					PomProperties: &pkg.JavaPomProperties{
   285  						Path:       "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties",
   286  						GroupID:    "org.apache.cxf",
   287  						ArtifactID: "cxf-rt-bindings-xml",
   288  						Version:    "3.3.10",
   289  					},
   290  				},
   291  			},
   292  			expected: []string{
   293  				"cpe:2.3:a:apache:cxf-rt-bindings-xml:3.3.10:*:*:*:*:*:*:*",
   294  				"cpe:2.3:a:apache:cxf:3.3.10:*:*:*:*:*:*:*",
   295  				"cpe:2.3:a:apache:cxf_rt_bindings_xml:3.3.10:*:*:*:*:*:*:*",
   296  			},
   297  		},
   298  		{
   299  			name: "rpm vendor selection",
   300  			p: pkg.Package{
   301  				Name:    "name",
   302  				Version: "3.2",
   303  				FoundBy: "some-analyzer",
   304  				Type:    pkg.RpmPkg,
   305  				Metadata: pkg.RpmDBEntry{
   306  					Vendor: "some-vendor",
   307  				},
   308  			},
   309  			expected: []string{
   310  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   311  				"cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
   312  				"cpe:2.3:a:some_vendor:name:3.2:*:*:*:*:*:*:*",
   313  			},
   314  		},
   315  		{
   316  			name: "rpm with epoch",
   317  			p: pkg.Package{
   318  				Name:    "name",
   319  				Version: "1:3.2",
   320  				FoundBy: "some-analyzer",
   321  				Type:    pkg.RpmPkg,
   322  				Metadata: pkg.RpmDBEntry{
   323  					Vendor: "some-vendor",
   324  				},
   325  			},
   326  			expected: []string{
   327  				"cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*",
   328  				"cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*",
   329  				"cpe:2.3:a:some_vendor:name:1\\:3.2:*:*:*:*:*:*:*",
   330  			},
   331  		},
   332  		{
   333  			name: "deb with epoch",
   334  			p: pkg.Package{
   335  				Name:     "name",
   336  				Version:  "1:3.2",
   337  				FoundBy:  "some-analyzer",
   338  				Type:     pkg.DebPkg,
   339  				Metadata: pkg.DpkgDBEntry{},
   340  			},
   341  			expected: []string{
   342  				"cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*",
   343  			},
   344  		},
   345  		{
   346  			name: "cloudbees jenkins package identified via groupId",
   347  			p: pkg.Package{
   348  				Name:     "name",
   349  				Version:  "3.2",
   350  				FoundBy:  "some-analyzer",
   351  				Language: pkg.Java,
   352  				Type:     pkg.JenkinsPluginPkg,
   353  				Metadata: pkg.JavaArchive{
   354  					PomProperties: &pkg.JavaPomProperties{
   355  						GroupID: "com.cloudbees.jenkins.plugins",
   356  					},
   357  				},
   358  			},
   359  			expected: []string{
   360  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   361  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   362  				"cpe:2.3:a:cloudbees:name:3.2:*:*:*:*:*:*:*",
   363  			},
   364  		},
   365  		{
   366  			name: "jenkins.io package identified via groupId prefix",
   367  			p: pkg.Package{
   368  				Name:     "name",
   369  				Version:  "3.2",
   370  				FoundBy:  "some-analyzer",
   371  				Language: pkg.Java,
   372  				Type:     pkg.JenkinsPluginPkg,
   373  				Metadata: pkg.JavaArchive{
   374  					PomProperties: &pkg.JavaPomProperties{
   375  						GroupID: "io.jenkins.plugins.name.something",
   376  					},
   377  				},
   378  			},
   379  			expected: []string{
   380  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   381  				"cpe:2.3:a:name:something:3.2:*:*:*:*:*:*:*",
   382  				"cpe:2.3:a:something:name:3.2:*:*:*:*:*:*:*",
   383  				"cpe:2.3:a:something:something:3.2:*:*:*:*:*:*:*",
   384  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   385  				"cpe:2.3:a:jenkins:something:3.2:*:*:*:*:*:*:*",
   386  			},
   387  		},
   388  		{
   389  			name: "jenkins.io package identified via groupId",
   390  			p: pkg.Package{
   391  				Name:     "name",
   392  				Version:  "3.2",
   393  				FoundBy:  "some-analyzer",
   394  				Language: pkg.Java,
   395  				Type:     pkg.JenkinsPluginPkg,
   396  				Metadata: pkg.JavaArchive{
   397  					PomProperties: &pkg.JavaPomProperties{
   398  						GroupID: "io.jenkins.plugins",
   399  					},
   400  				},
   401  			},
   402  			expected: []string{
   403  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   404  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   405  			},
   406  		},
   407  		{
   408  			name: "jenkins-ci.io package identified via groupId",
   409  			p: pkg.Package{
   410  				Name:     "name",
   411  				Version:  "3.2",
   412  				FoundBy:  "some-analyzer",
   413  				Language: pkg.Java,
   414  				Type:     pkg.JenkinsPluginPkg,
   415  				Metadata: pkg.JavaArchive{
   416  					PomProperties: &pkg.JavaPomProperties{
   417  						GroupID: "io.jenkins-ci.plugins",
   418  					},
   419  				},
   420  			},
   421  			expected: []string{
   422  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   423  				"cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*",
   424  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   425  				"cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*",
   426  			},
   427  		},
   428  		{
   429  			name: "jenkins-ci.org package identified via groupId",
   430  			p: pkg.Package{
   431  				Name:     "name",
   432  				Version:  "3.2",
   433  				FoundBy:  "some-analyzer",
   434  				Language: pkg.Java,
   435  				Type:     pkg.JenkinsPluginPkg,
   436  				Metadata: pkg.JavaArchive{
   437  					PomProperties: &pkg.JavaPomProperties{
   438  						GroupID: "org.jenkins-ci.plugins",
   439  					},
   440  				},
   441  			},
   442  			expected: []string{
   443  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   444  				"cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*",
   445  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   446  				"cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*",
   447  			},
   448  		},
   449  		{
   450  			name: "jira-atlassian filtering",
   451  			p: pkg.Package{
   452  				Name:     "jira_client_core",
   453  				Version:  "3.2",
   454  				FoundBy:  "some-analyzer",
   455  				Language: pkg.Java,
   456  				Type:     pkg.JavaPkg,
   457  				Metadata: pkg.JavaArchive{
   458  					PomProperties: &pkg.JavaPomProperties{
   459  						GroupID:    "org.atlassian.jira",
   460  						ArtifactID: "jira_client_core",
   461  					},
   462  				},
   463  			},
   464  			expected: []string{
   465  				"cpe:2.3:a:atlassian:jira-client-core:3.2:*:*:*:*:*:*:*",
   466  				"cpe:2.3:a:atlassian:jira_client_core:3.2:*:*:*:*:*:*:*",
   467  				"cpe:2.3:a:jira-client-core:jira-client-core:3.2:*:*:*:*:*:*:*",
   468  				"cpe:2.3:a:jira-client-core:jira:3.2:*:*:*:*:*:*:*",
   469  				"cpe:2.3:a:jira-client-core:jira_client_core:3.2:*:*:*:*:*:*:*",
   470  				"cpe:2.3:a:jira-client:jira-client-core:3.2:*:*:*:*:*:*:*",
   471  				"cpe:2.3:a:jira-client:jira:3.2:*:*:*:*:*:*:*",
   472  				"cpe:2.3:a:jira-client:jira_client_core:3.2:*:*:*:*:*:*:*",
   473  				"cpe:2.3:a:jira:jira-client-core:3.2:*:*:*:*:*:*:*",
   474  				"cpe:2.3:a:jira:jira_client_core:3.2:*:*:*:*:*:*:*",
   475  				"cpe:2.3:a:jira_client:jira-client-core:3.2:*:*:*:*:*:*:*",
   476  				"cpe:2.3:a:jira_client:jira:3.2:*:*:*:*:*:*:*",
   477  				"cpe:2.3:a:jira_client:jira_client_core:3.2:*:*:*:*:*:*:*",
   478  				"cpe:2.3:a:jira_client_core:jira-client-core:3.2:*:*:*:*:*:*:*",
   479  				"cpe:2.3:a:jira_client_core:jira:3.2:*:*:*:*:*:*:*",
   480  				"cpe:2.3:a:jira_client_core:jira_client_core:3.2:*:*:*:*:*:*:*",
   481  			},
   482  		},
   483  		{
   484  			name: "jenkins filtering",
   485  			p: pkg.Package{
   486  				Name:     "cloudbees-installation-manager",
   487  				Version:  "2.89.0.33",
   488  				FoundBy:  "some-analyzer",
   489  				Language: pkg.Java,
   490  				Type:     pkg.JavaPkg,
   491  				Metadata: pkg.JavaArchive{
   492  					PomProperties: &pkg.JavaPomProperties{
   493  						GroupID:    "com.cloudbees.jenkins.modules",
   494  						ArtifactID: "cloudbees-installation-manager",
   495  					},
   496  				},
   497  			},
   498  			expected: []string{
   499  				"cpe:2.3:a:cloudbees-installation-manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   500  				"cpe:2.3:a:cloudbees-installation-manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   501  				"cpe:2.3:a:cloudbees-installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   502  				"cpe:2.3:a:cloudbees-installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   503  				"cpe:2.3:a:cloudbees:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   504  				"cpe:2.3:a:cloudbees:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   505  				"cpe:2.3:a:cloudbees_installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   506  				"cpe:2.3:a:cloudbees_installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   507  				"cpe:2.3:a:cloudbees_installation_manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   508  				"cpe:2.3:a:cloudbees_installation_manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   509  				"cpe:2.3:a:jenkins:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   510  				"cpe:2.3:a:jenkins:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   511  				"cpe:2.3:a:modules:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   512  				"cpe:2.3:a:modules:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   513  			},
   514  		},
   515  		{
   516  			name: "go product and vendor candidates are wired up",
   517  			p: pkg.Package{
   518  				Name:     "github.com/someone/something",
   519  				Version:  "3.2",
   520  				FoundBy:  "go-cataloger",
   521  				Language: pkg.Go,
   522  				Type:     pkg.GoModulePkg,
   523  			},
   524  			expected: []string{
   525  				"cpe:2.3:a:someone:something:3.2:*:*:*:*:*:*:*",
   526  			},
   527  		},
   528  		{
   529  			name: "go product with vendor candidates and an extra sub-item",
   530  			p: pkg.Package{
   531  				Name:     "github.com/someone/something/more",
   532  				Version:  "3.2",
   533  				FoundBy:  "go-cataloger",
   534  				Language: pkg.Go,
   535  				Type:     pkg.GoModulePkg,
   536  			},
   537  			expected: []string{
   538  				"cpe:2.3:a:someone:something\\/more:3.2:*:*:*:*:*:*:*",
   539  			},
   540  		},
   541  		{
   542  			name: "generate no CPEs for indeterminate golang package name",
   543  			p: pkg.Package{
   544  				Name:     "github.com/what",
   545  				Version:  "3.2",
   546  				FoundBy:  "go-cataloger",
   547  				Language: pkg.Go,
   548  				Type:     pkg.GoModulePkg,
   549  			},
   550  			expected: []string{},
   551  		},
   552  		{
   553  			name: "regression: handlebars within java archive",
   554  			p: pkg.Package{
   555  				Name:     "handlebars",
   556  				Version:  "3.0.8",
   557  				Type:     pkg.JavaPkg,
   558  				Language: pkg.Java,
   559  				FoundBy:  "java-cataloger",
   560  				Metadata: pkg.JavaArchive{
   561  					Manifest: &pkg.JavaManifest{
   562  						Main: map[string]string{
   563  							"Extension-Name":         "handlebars",
   564  							"Group-Id":               "org.jenkins-ci.ui",
   565  							"Hudson-Version":         "2.204",
   566  							"Implementation-Title":   "handlebars",
   567  							"Implementation-Version": "3.0.8",
   568  							"Plugin-Version":         "3.0.8",
   569  							"Short-Name":             "handlebars",
   570  						},
   571  					},
   572  					PomProperties: &pkg.JavaPomProperties{
   573  						GroupID:    "org.jenkins-ci.ui",
   574  						ArtifactID: "handlebars",
   575  						Version:    "3.0.8",
   576  					},
   577  				},
   578  			},
   579  			expected: []string{
   580  				"cpe:2.3:a:handlebars:handlebars:3.0.8:*:*:*:*:*:*:*",
   581  				"cpe:2.3:a:handlebarsjs:handlebars:3.0.8:*:*:*:*:*:*:*", // important!
   582  				"cpe:2.3:a:jenkins-ci:handlebars:3.0.8:*:*:*:*:*:*:*",
   583  				"cpe:2.3:a:jenkins:handlebars:3.0.8:*:*:*:*:*:*:*",
   584  				"cpe:2.3:a:jenkins_ci:handlebars:3.0.8:*:*:*:*:*:*:*",
   585  				"cpe:2.3:a:ui:handlebars:3.0.8:*:*:*:*:*:*:*",
   586  			},
   587  		},
   588  		{
   589  			name: "regression: jenkins plugin active-directory",
   590  			p: pkg.Package{
   591  				Name:     "active-directory",
   592  				Version:  "2.25.1",
   593  				Type:     pkg.JenkinsPluginPkg,
   594  				FoundBy:  "java-cataloger",
   595  				Language: pkg.Java,
   596  				Metadata: pkg.JavaArchive{
   597  					Manifest: &pkg.JavaManifest{
   598  						Main: map[string]string{
   599  							"Extension-Name": "active-directory",
   600  							"Group-Id":       "org.jenkins-ci.plugins",
   601  						},
   602  					},
   603  					PomProperties: &pkg.JavaPomProperties{
   604  						GroupID:    "org.jenkins-ci.plugins",
   605  						ArtifactID: "org.jenkins-ci.plugins",
   606  						Version:    "2.25.1",
   607  					},
   608  				},
   609  			},
   610  			expected: []string{
   611  				"cpe:2.3:a:active-directory:active-directory:2.25.1:*:*:*:*:*:*:*",
   612  				"cpe:2.3:a:active-directory:active_directory:2.25.1:*:*:*:*:*:*:*",
   613  				"cpe:2.3:a:active:active-directory:2.25.1:*:*:*:*:*:*:*",
   614  				"cpe:2.3:a:active:active_directory:2.25.1:*:*:*:*:*:*:*",
   615  				"cpe:2.3:a:active_directory:active-directory:2.25.1:*:*:*:*:*:*:*",
   616  				"cpe:2.3:a:active_directory:active_directory:2.25.1:*:*:*:*:*:*:*",
   617  				"cpe:2.3:a:jenkins-ci:active-directory:2.25.1:*:*:*:*:*:*:*",
   618  				"cpe:2.3:a:jenkins-ci:active_directory:2.25.1:*:*:*:*:*:*:*",
   619  				"cpe:2.3:a:jenkins:active-directory:2.25.1:*:*:*:*:*:*:*", // important!
   620  				"cpe:2.3:a:jenkins:active_directory:2.25.1:*:*:*:*:*:*:*", // important!
   621  				"cpe:2.3:a:jenkins_ci:active-directory:2.25.1:*:*:*:*:*:*:*",
   622  				"cpe:2.3:a:jenkins_ci:active_directory:2.25.1:*:*:*:*:*:*:*",
   623  			},
   624  		},
   625  		{
   626  			name: "regression: special characters in CPE should result in no generation",
   627  			p: pkg.Package{
   628  				Name:     "bundler",
   629  				Version:  "2.1.4",
   630  				Type:     pkg.GemPkg,
   631  				FoundBy:  "gem-cataloger",
   632  				Language: pkg.Ruby,
   633  				Metadata: pkg.RubyGemspec{
   634  					Name:    "bundler",
   635  					Version: "2.1.4",
   636  					Authors: []string{
   637  						"jessica lynn suttles",
   638  						"stephanie morillo",
   639  						"david rodríguez",
   640  						"andré medeiros",
   641  					},
   642  				},
   643  			},
   644  			expected: []string{
   645  				"cpe:2.3:a:bundler:bundler:2.1.4:*:*:*:*:*:*:*",
   646  				"cpe:2.3:a:ruby-lang:bundler:2.1.4:*:*:*:*:*:*:*",
   647  				"cpe:2.3:a:ruby:bundler:2.1.4:*:*:*:*:*:*:*",
   648  				"cpe:2.3:a:ruby_lang:bundler:2.1.4:*:*:*:*:*:*:*",
   649  				"cpe:2.3:a:jessica-lynn-suttles:bundler:2.1.4:*:*:*:*:*:*:*",
   650  				"cpe:2.3:a:jessica_lynn_suttles:bundler:2.1.4:*:*:*:*:*:*:*",
   651  				"cpe:2.3:a:stephanie-morillo:bundler:2.1.4:*:*:*:*:*:*:*",
   652  				"cpe:2.3:a:stephanie_morillo:bundler:2.1.4:*:*:*:*:*:*:*",
   653  			},
   654  		},
   655  		{
   656  			name: "regression: python redis shadows normal redis",
   657  			p: pkg.Package{
   658  				Name:     "redis",
   659  				Version:  "2.1.4",
   660  				Type:     pkg.PythonPkg,
   661  				FoundBy:  "some-analyzer",
   662  				Language: pkg.Python,
   663  			},
   664  			expected: []string{
   665  				"cpe:2.3:a:python-redis:python-redis:2.1.4:*:*:*:*:*:*:*",
   666  				"cpe:2.3:a:python-redis:python_redis:2.1.4:*:*:*:*:*:*:*",
   667  				"cpe:2.3:a:python-redis:redis:2.1.4:*:*:*:*:*:*:*",
   668  				"cpe:2.3:a:python:python-redis:2.1.4:*:*:*:*:*:*:*",
   669  				"cpe:2.3:a:python:python_redis:2.1.4:*:*:*:*:*:*:*",
   670  				"cpe:2.3:a:python:redis:2.1.4:*:*:*:*:*:*:*",
   671  				"cpe:2.3:a:python_redis:python-redis:2.1.4:*:*:*:*:*:*:*",
   672  				"cpe:2.3:a:python_redis:python_redis:2.1.4:*:*:*:*:*:*:*",
   673  				"cpe:2.3:a:python_redis:redis:2.1.4:*:*:*:*:*:*:*",
   674  			},
   675  		},
   676  		{
   677  			name: "regression: ruby-rake apk missing expected ruby-lang:rake CPE",
   678  			p: pkg.Package{
   679  				Name:     "ruby-rake",
   680  				Version:  "2.7.6-r0",
   681  				Type:     pkg.ApkPkg,
   682  				FoundBy:  "apk-db-analyzer",
   683  				Language: pkg.UnknownLanguage,
   684  				Metadata: pkg.ApkDBEntry{
   685  					Package:       "ruby-rake",
   686  					URL:           "https://www.ruby-lang.org/",
   687  					OriginPackage: "ruby",
   688  				},
   689  			},
   690  			expected: []string{
   691  				"cpe:2.3:a:ruby-lang:rake:2.7.6-r0:*:*:*:*:*:*:*",
   692  				"cpe:2.3:a:rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
   693  				"cpe:2.3:a:rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   694  				"cpe:2.3:a:rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   695  				"cpe:2.3:a:ruby-lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   696  				"cpe:2.3:a:ruby-lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   697  				"cpe:2.3:a:ruby-rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
   698  				"cpe:2.3:a:ruby-rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   699  				"cpe:2.3:a:ruby-rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   700  				"cpe:2.3:a:ruby:rake:2.7.6-r0:*:*:*:*:*:*:*",
   701  				"cpe:2.3:a:ruby:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   702  				"cpe:2.3:a:ruby:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   703  				"cpe:2.3:a:ruby_lang:rake:2.7.6-r0:*:*:*:*:*:*:*",
   704  				"cpe:2.3:a:ruby_lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   705  				"cpe:2.3:a:ruby_lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   706  				"cpe:2.3:a:ruby_rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
   707  				"cpe:2.3:a:ruby_rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   708  				"cpe:2.3:a:ruby_rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   709  			},
   710  		},
   711  	}
   712  
   713  	for _, test := range tests {
   714  		t.Run(test.name, func(t *testing.T) {
   715  			actual := Generate(test.p)
   716  
   717  			expectedCpeSet := set.NewStringSet(test.expected...)
   718  			actualCpeSet := set.NewStringSet()
   719  			for _, a := range actual {
   720  				actualCpeSet.Add(cpe.String(a))
   721  			}
   722  
   723  			extra := strset.Difference(actualCpeSet, expectedCpeSet).List()
   724  			sort.Strings(extra)
   725  			if len(extra) > 0 {
   726  				t.Errorf("found extra CPEs:")
   727  				for _, d := range extra {
   728  					t.Logf("   %q,\n", d)
   729  				}
   730  			}
   731  
   732  			missing := strset.Difference(expectedCpeSet, actualCpeSet).List()
   733  			sort.Strings(missing)
   734  			if len(missing) > 0 {
   735  				t.Errorf("missing CPEs:")
   736  				for _, d := range missing {
   737  					t.Logf("   %q,\n", d)
   738  				}
   739  			}
   740  		})
   741  	}
   742  }
   743  
   744  func TestCandidateProducts(t *testing.T) {
   745  	tests := []struct {
   746  		name     string
   747  		p        pkg.Package
   748  		expected []string
   749  	}{
   750  		{
   751  			name: "apache-cassandra",
   752  			p: pkg.Package{
   753  				Name: "apache-cassandra",
   754  				Type: pkg.JavaPkg,
   755  			},
   756  			expected: []string{"cassandra" /* <-- known good names | default guess --> */, "apache-cassandra", "apache_cassandra"},
   757  		},
   758  		{
   759  			name: "springframework",
   760  			p: pkg.Package{
   761  				Name: "springframework",
   762  				Type: pkg.JavaPkg,
   763  			},
   764  			expected: []string{"spring_framework", "springsource_spring_framework" /* <-- known good names | default guess --> */, "springframework"},
   765  		},
   766  		{
   767  			name: "spring-security-core",
   768  			p: pkg.Package{
   769  				Name: "spring-security-core",
   770  				Type: pkg.JavaPkg,
   771  			},
   772  			expected: []string{"spring-security-core", "spring_security", "spring_security_core"},
   773  		},
   774  		{
   775  			name: "java",
   776  			p: pkg.Package{
   777  				Name:     "some-java-package-with-group-id",
   778  				Type:     pkg.JavaPkg,
   779  				Language: pkg.Java,
   780  				Metadata: pkg.JavaArchive{
   781  					PomProperties: &pkg.JavaPomProperties{
   782  						GroupID: "com.apple.itunes",
   783  					},
   784  				},
   785  			},
   786  			expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"},
   787  		},
   788  		{
   789  			name: "java-with-asterisk",
   790  			p: pkg.Package{
   791  				Name:     "some-java-package-with-group-id",
   792  				Type:     pkg.JavaPkg,
   793  				Language: pkg.Java,
   794  				Metadata: pkg.JavaArchive{
   795  					PomProperties: &pkg.JavaPomProperties{
   796  						GroupID: "com.apple.itunes.*",
   797  					},
   798  				},
   799  			},
   800  			expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"},
   801  		},
   802  		{
   803  			name: "jenkins-plugin",
   804  			p: pkg.Package{
   805  				Name:     "some-jenkins-plugin",
   806  				Type:     pkg.JenkinsPluginPkg,
   807  				Language: pkg.Java,
   808  				Metadata: pkg.JavaArchive{
   809  					PomProperties: &pkg.JavaPomProperties{
   810  						GroupID: "com.cloudbees.jenkins.plugins",
   811  					},
   812  				},
   813  			},
   814  			expected: []string{"some-jenkins-plugin", "some_jenkins_plugin", "jenkins"},
   815  		},
   816  		{
   817  			name: "javascript",
   818  			p: pkg.Package{
   819  				Name: "handlebars.js",
   820  				Type: pkg.NpmPkg,
   821  			},
   822  			expected: []string{"handlebars" /* <-- known good names | default guess --> */, "handlebars.js"},
   823  		},
   824  		{
   825  			name: "gem",
   826  			p: pkg.Package{
   827  				Name: "RedCloth",
   828  				Type: pkg.GemPkg,
   829  			},
   830  			expected: []string{"redcloth_library" /* <-- known good names | default guess --> */, "RedCloth"},
   831  		},
   832  		{
   833  			name: "python",
   834  			p: pkg.Package{
   835  				Name: "python-rrdtool",
   836  				Type: pkg.PythonPkg,
   837  			},
   838  			expected: []string{"rrdtool" /* <-- known good names | default guess --> */, "python-rrdtool", "python_rrdtool"},
   839  		},
   840  	}
   841  
   842  	for _, test := range tests {
   843  		t.Run(test.name, func(t *testing.T) {
   844  			assert.ElementsMatch(t, test.expected, candidateProducts(test.p))
   845  		})
   846  	}
   847  }
   848  
   849  func TestCandidateVendor(t *testing.T) {
   850  	tests := []struct {
   851  		name     string
   852  		p        pkg.Package
   853  		expected []string
   854  	}{
   855  		{
   856  			name: "elasticsearch",
   857  			p: pkg.Package{
   858  				Name: "elasticsearch",
   859  				Type: pkg.JavaPkg,
   860  			},
   861  			expected: []string{"elastic" /* <-- known good names | default guess --> */, "elasticsearch"},
   862  		},
   863  		{
   864  			name: "spring-security",
   865  			p: pkg.Package{
   866  				Name: "spring-security-core",
   867  				Type: pkg.JavaPkg,
   868  			},
   869  			expected: []string{"vmware" /* <-- known good names | default guess --> */, "spring", "spring-security", "spring-security-core", "spring_security_core", "spring_security"},
   870  		},
   871  		{
   872  			name: "log4j",
   873  			p: pkg.Package{
   874  				Name: "log4j",
   875  				Type: pkg.JavaPkg,
   876  			},
   877  			expected: []string{"apache"},
   878  		},
   879  		{
   880  			name: "Django",
   881  			p: pkg.Package{
   882  				Name: "Django",
   883  				Type: pkg.PythonPkg,
   884  			},
   885  			expected: []string{"djangoproject" /* <-- known good names | default guess --> */, "Django"},
   886  		},
   887  	}
   888  
   889  	for _, test := range tests {
   890  		t.Run(fmt.Sprintf("%+v %+v", test.p, test.expected), func(t *testing.T) {
   891  			assert.ElementsMatch(t, test.expected, candidateVendors(test.p))
   892  		})
   893  	}
   894  }
   895  
   896  func Test_generateSubSelections(t *testing.T) {
   897  	tests := []struct {
   898  		field    string
   899  		expected []string
   900  	}{
   901  		{
   902  			field:    "jenkins",
   903  			expected: []string{"jenkins"},
   904  		},
   905  		{
   906  			field:    "jenkins-ci",
   907  			expected: []string{"jenkins", "jenkins-ci"},
   908  		},
   909  		{
   910  			field:    "jenkins--ci",
   911  			expected: []string{"jenkins", "jenkins-ci"},
   912  		},
   913  		{
   914  			field:    "jenkins_ci_tools",
   915  			expected: []string{"jenkins", "jenkins_ci", "jenkins_ci_tools"},
   916  		},
   917  		{
   918  			field:    "-jenkins",
   919  			expected: []string{"jenkins"},
   920  		},
   921  		{
   922  			field:    "jenkins_",
   923  			expected: []string{"jenkins"},
   924  		},
   925  		{
   926  			field:    "",
   927  			expected: nil,
   928  		},
   929  		{
   930  			field:    "-",
   931  			expected: nil,
   932  		},
   933  		{
   934  			field:    "_",
   935  			expected: nil,
   936  		},
   937  	}
   938  	for _, test := range tests {
   939  		t.Run(test.field, func(t *testing.T) {
   940  			assert.ElementsMatch(t, test.expected, generateSubSelections(test.field))
   941  		})
   942  	}
   943  }
   944  
   945  func Test_addSeparatorVariations(t *testing.T) {
   946  	tests := []struct {
   947  		input    []string
   948  		expected []string
   949  	}{
   950  		{
   951  			input:    []string{"jenkins-ci"},
   952  			expected: []string{"jenkins-ci", "jenkins_ci"}, //, "jenkinsci"},
   953  		},
   954  		{
   955  			input:    []string{"jenkins_ci"},
   956  			expected: []string{"jenkins_ci", "jenkins-ci"}, //, "jenkinsci"},
   957  		},
   958  		{
   959  			input:    []string{"jenkins"},
   960  			expected: []string{"jenkins"},
   961  		},
   962  		{
   963  			input:    []string{"jenkins-ci", "circle-ci"},
   964  			expected: []string{"jenkins-ci", "jenkins_ci", "circle-ci", "circle_ci"}, //, "jenkinsci", "circleci"},
   965  		},
   966  	}
   967  	for _, test := range tests {
   968  		t.Run(strings.Join(test.input, ","), func(t *testing.T) {
   969  			val := newFieldCandidateSet(test.input...)
   970  			addDelimiterVariations(val)
   971  			assert.ElementsMatch(t, test.expected, val.values())
   972  		})
   973  	}
   974  }
   975  
   976  func TestDictionaryFindIsWired(t *testing.T) {
   977  
   978  	tests := []struct {
   979  		name       string
   980  		pkg        pkg.Package
   981  		want       string
   982  		wantExists bool
   983  	}{
   984  		{
   985  			name: "sanity check that cpe data is wired up",
   986  			pkg: pkg.Package{
   987  				Name:    "openssl",
   988  				Version: "1.0.2k",
   989  				Type:    pkg.GemPkg,
   990  			},
   991  			want: "cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:*:*:*",
   992  			// without the cpe data wired up, this would be empty (generation also creates cpe:2.3:a:openssl:openssl:1.0.2k:*:*:*:*:*:*:*)
   993  			wantExists: true,
   994  		},
   995  	}
   996  	for _, tt := range tests {
   997  		t.Run(tt.name, func(t *testing.T) {
   998  			got, gotExists := DictionaryFind(tt.pkg)
   999  
  1000  			assert.Equal(t, tt.want, got.BindToFmtString())
  1001  			assert.Equal(t, tt.wantExists, gotExists)
  1002  		})
  1003  	}
  1004  }