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