github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/internal/packagemetadata/names_test.go (about)

     1  package packagemetadata
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/anchore/syft/syft/pkg"
    12  )
    13  
    14  func TestAllNames(t *testing.T) {
    15  	// note: this is a form of completion testing relative to the current code base.
    16  
    17  	expected, err := DiscoverTypeNames()
    18  	require.NoError(t, err)
    19  
    20  	actual := AllTypeNames()
    21  
    22  	// ensure that the codebase (from ast analysis) reflects the latest code generated state
    23  	if !assert.ElementsMatch(t, expected, actual) {
    24  		t.Errorf("metadata types not fully represented: \n%s", cmp.Diff(expected, actual))
    25  		t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?")
    26  		t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)")
    27  	}
    28  
    29  	for _, ty := range AllTypes() {
    30  		assert.NotEmpty(t, JSONName(ty), "metadata type %q does not have a JSON name", reflect.TypeOf(ty).Name())
    31  	}
    32  }
    33  
    34  func TestReflectTypeFromJSONName(t *testing.T) {
    35  	tests := []struct {
    36  		name       string
    37  		lookup     string
    38  		wantRecord reflect.Type
    39  	}{
    40  		{
    41  			name:       "exact match on ID",
    42  			lookup:     "rust-cargo-lock-entry",
    43  			wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
    44  		},
    45  		{
    46  			name:       "exact match on former name",
    47  			lookup:     "RustCargoPackageMetadata",
    48  			wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
    49  		},
    50  		{
    51  			name:       "case insensitive on ID",
    52  			lookup:     "RUST-CARGO-lock-entrY",
    53  			wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
    54  		},
    55  		{
    56  			name:       "case insensitive on alias",
    57  			lookup:     "rusTcArgopacKagEmEtadATa",
    58  			wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
    59  		},
    60  		{
    61  			name: "consistent override",
    62  			// there are two correct answers for this -- we should always get the same answer.
    63  			lookup:     "HackageMetadataType",
    64  			wantRecord: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
    65  		},
    66  	}
    67  	for _, tt := range tests {
    68  		t.Run(tt.name, func(t *testing.T) {
    69  			got := ReflectTypeFromJSONName(tt.lookup)
    70  			require.NotNil(t, got)
    71  			assert.Equal(t, tt.wantRecord.Name(), got.Name())
    72  		})
    73  	}
    74  }
    75  
    76  func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
    77  	testCases := []struct {
    78  		name     string
    79  		input    string
    80  		expected reflect.Type
    81  	}{
    82  		// these cases are always 1:1
    83  		{
    84  			name:     "map pkg.AlpmDBEntry struct type",
    85  			input:    "AlpmMetadata",
    86  			expected: reflect.TypeOf(pkg.AlpmDBEntry{}),
    87  		},
    88  		{
    89  			name:     "map pkg.ApkDBEntry struct type",
    90  			input:    "ApkMetadata",
    91  			expected: reflect.TypeOf(pkg.ApkDBEntry{}),
    92  		},
    93  		{
    94  			name:     "map pkg.BinarySignature struct type",
    95  			input:    "BinaryMetadata",
    96  			expected: reflect.TypeOf(pkg.BinarySignature{}),
    97  		},
    98  		{
    99  			name:     "map pkg.CocoaPodfileLockEntry struct type",
   100  			input:    "CocoapodsMetadataType",
   101  			expected: reflect.TypeOf(pkg.CocoaPodfileLockEntry{}),
   102  		},
   103  		{
   104  			name:     "map pkg.ConanLockEntry struct type",
   105  			input:    "ConanLockMetadataType",
   106  			expected: reflect.TypeOf(pkg.ConanV1LockEntry{}),
   107  		},
   108  		{
   109  			name:     "map pkg.ConanfileEntry struct type",
   110  			input:    "ConanMetadataType",
   111  			expected: reflect.TypeOf(pkg.ConanfileEntry{}),
   112  		},
   113  		{
   114  			name:     "map pkg.DartPubspecLockEntry struct type",
   115  			input:    "DartPubMetadata",
   116  			expected: reflect.TypeOf(pkg.DartPubspecLockEntry{}),
   117  		},
   118  		{
   119  			name:     "map pkg.DotnetDepsEntry struct type",
   120  			input:    "DotnetDepsMetadata",
   121  			expected: reflect.TypeOf(pkg.DotnetDepsEntry{}),
   122  		},
   123  		{
   124  			name:     "map pkg.DpkgDBEntry struct type",
   125  			input:    "DpkgMetadata",
   126  			expected: reflect.TypeOf(pkg.DpkgDBEntry{}),
   127  		},
   128  		{
   129  			name:     "map pkg.RubyGemspec struct type",
   130  			input:    "GemMetadata",
   131  			expected: reflect.TypeOf(pkg.RubyGemspec{}),
   132  		},
   133  		{
   134  			name:     "map pkg.GolangBinaryBuildinfoEntry struct type",
   135  			input:    "GolangBinMetadata",
   136  			expected: reflect.TypeOf(pkg.GolangBinaryBuildinfoEntry{}),
   137  		},
   138  		{
   139  			name:     "map pkg.GolangModuleEntry struct type",
   140  			input:    "GolangModMetadata",
   141  			expected: reflect.TypeOf(pkg.GolangModuleEntry{}),
   142  		},
   143  		{
   144  			name:     "map pkg.JavaArchive struct type",
   145  			input:    "JavaMetadata",
   146  			expected: reflect.TypeOf(pkg.JavaArchive{}),
   147  		},
   148  		{
   149  			name:     "map pkg.MicrosoftKbPatch struct type",
   150  			input:    "KbPatchMetadata",
   151  			expected: reflect.TypeOf(pkg.MicrosoftKbPatch{}),
   152  		},
   153  		{
   154  			name:     "map pkg.LinuxKernel struct type",
   155  			input:    "LinuxKernel",
   156  			expected: reflect.TypeOf(pkg.LinuxKernel{}),
   157  		},
   158  		{
   159  			name:     "map pkg.LinuxKernelModule struct type",
   160  			input:    "LinuxKernelModule",
   161  			expected: reflect.TypeOf(pkg.LinuxKernelModule{}),
   162  		},
   163  		{
   164  			name:     "map pkg.ElixirMixLockEntry struct type",
   165  			input:    "MixLockMetadataType",
   166  			expected: reflect.TypeOf(pkg.ElixirMixLockEntry{}),
   167  		},
   168  		{
   169  			name:     "map pkg.NixStoreEntry struct type",
   170  			input:    "NixStoreMetadata",
   171  			expected: reflect.TypeOf(pkg.NixStoreEntry{}),
   172  		},
   173  		{
   174  			name:     "map pkg.NpmPackage struct type",
   175  			input:    "NpmPackageJsonMetadata",
   176  			expected: reflect.TypeOf(pkg.NpmPackage{}),
   177  		},
   178  		{
   179  			name:     "map pkg.NpmPackageLockEntry struct type",
   180  			input:    "NpmPackageLockJsonMetadata",
   181  			expected: reflect.TypeOf(pkg.NpmPackageLockEntry{}),
   182  		},
   183  		{
   184  			name:     "map pkg.PortageEntry struct type",
   185  			input:    "PortageMetadata",
   186  			expected: reflect.TypeOf(pkg.PortageEntry{}),
   187  		},
   188  		{
   189  			name:     "map pkg.PythonPackage struct type",
   190  			input:    "PythonPackageMetadata",
   191  			expected: reflect.TypeOf(pkg.PythonPackage{}),
   192  		},
   193  		{
   194  			name:     "map pkg.PythonPipfileLockEntry struct type",
   195  			input:    "PythonPipfileLockMetadata",
   196  			expected: reflect.TypeOf(pkg.PythonPipfileLockEntry{}),
   197  		},
   198  		{
   199  			name:     "map pkg.PythonRequirementsEntry struct type",
   200  			input:    "PythonRequirementsMetadata",
   201  			expected: reflect.TypeOf(pkg.PythonRequirementsEntry{}),
   202  		},
   203  		{
   204  			name:     "map pkg.PhpPeclEntry struct type",
   205  			input:    "PhpPeclMetadata",
   206  			expected: reflect.TypeOf(pkg.PhpPeclEntry{}),
   207  		},
   208  		{
   209  			name:     "map pkg.ErlangRebarLockEntry struct type",
   210  			input:    "RebarLockMetadataType",
   211  			expected: reflect.TypeOf(pkg.ErlangRebarLockEntry{}),
   212  		},
   213  		{
   214  			name:     "map pkg.RDescription struct type",
   215  			input:    "RDescriptionFileMetadataType",
   216  			expected: reflect.TypeOf(pkg.RDescription{}),
   217  		},
   218  		{
   219  			name:     "map pkg.RpmDBEntry struct type",
   220  			input:    "RpmdbMetadata",
   221  			expected: reflect.TypeOf(pkg.RpmDBEntry{}),
   222  		},
   223  		// these cases are 1:many
   224  		{
   225  			name:  "map pkg.RpmDBEntry struct type - overlap with RpmArchiveMetadata",
   226  			input: "RpmMetadata",
   227  			// this used to be shared as a use case for both RpmArchive and RpmDBEntry
   228  			// from a data-shape perspective either would be equally correct
   229  			// however, the RPMDBMetadata has been around longer and may have been more widely used
   230  			// so we'll map to that type for backwards compatibility.
   231  			expected: reflect.TypeOf(pkg.RpmDBEntry{}),
   232  		},
   233  		{
   234  			name:  "map pkg.HackageStackYamlLockEntry struct type - overlap with HackageStack*Metadata",
   235  			input: "HackageMetadataType",
   236  			// this used to be shared as a use case for both HackageStackYamlLockEntry and HackageStackYamlEntry
   237  			// but the HackageStackYamlLockEntry maps most closely to the original data shape.
   238  			expected: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
   239  		},
   240  		{
   241  			name:  "map pkg.PhpComposerLockEntry struct type",
   242  			input: "PhpComposerJsonMetadata",
   243  			// this used to be shared as a use case for both PhpComposerLockEntry and PhpComposerInstalledEntry
   244  			// neither of these is more correct over the other. These parsers were also introduced at the same time.
   245  			expected: reflect.TypeOf(pkg.PhpComposerLockEntry{}),
   246  		},
   247  		{
   248  			name:  "map pkg.RustCargoLockEntry struct type",
   249  			input: "RustCargoPackageMetadata",
   250  			// this used to be shared as a use case for both RustCargoLockEntry and RustBinaryAuditEntry
   251  			// neither of these is more correct over the other.
   252  			expected: reflect.TypeOf(pkg.RustCargoLockEntry{}),
   253  		},
   254  	}
   255  
   256  	for _, testCase := range testCases {
   257  		t.Run(testCase.name, func(t *testing.T) {
   258  			result := ReflectTypeFromJSONName(testCase.input)
   259  			assert.Equal(t, testCase.expected.Name(), result.Name())
   260  		})
   261  	}
   262  }
   263  
   264  func Test_JSONName_JSONLegacyName(t *testing.T) {
   265  	// note: these are all the types and names covered by the v11.x and v12.x JSON schemas
   266  	tests := []struct {
   267  		name               string
   268  		metadata           any
   269  		expectedJSONName   string
   270  		expectedLegacyName string
   271  	}{
   272  		{
   273  			name:               "AlpmMetadata",
   274  			metadata:           pkg.AlpmDBEntry{},
   275  			expectedJSONName:   "alpm-db-entry",
   276  			expectedLegacyName: "AlpmMetadata",
   277  		},
   278  		{
   279  			name:               "ApkMetadata",
   280  			metadata:           pkg.ApkDBEntry{},
   281  			expectedJSONName:   "apk-db-entry",
   282  			expectedLegacyName: "ApkMetadata",
   283  		},
   284  		{
   285  			name:               "BinaryMetadata",
   286  			metadata:           pkg.BinarySignature{},
   287  			expectedJSONName:   "binary-signature",
   288  			expectedLegacyName: "BinaryMetadata",
   289  		},
   290  		{
   291  			name:               "CocoapodsMetadata",
   292  			metadata:           pkg.CocoaPodfileLockEntry{},
   293  			expectedJSONName:   "cocoa-podfile-lock-entry",
   294  			expectedLegacyName: "CocoapodsMetadataType",
   295  		},
   296  		{
   297  			name:               "ConanLockMetadata",
   298  			metadata:           pkg.ConanV1LockEntry{},
   299  			expectedJSONName:   "c-conan-lock-entry",
   300  			expectedLegacyName: "ConanLockMetadataType",
   301  		},
   302  		{
   303  			name:               "ConanMetadata",
   304  			metadata:           pkg.ConanfileEntry{},
   305  			expectedJSONName:   "c-conan-file-entry",
   306  			expectedLegacyName: "ConanMetadataType",
   307  		},
   308  		{
   309  			name:               "DartPubMetadata",
   310  			metadata:           pkg.DartPubspecLockEntry{},
   311  			expectedJSONName:   "dart-pubspec-lock-entry",
   312  			expectedLegacyName: "DartPubMetadata",
   313  		},
   314  		{
   315  			name:               "DotnetDepsMetadata",
   316  			metadata:           pkg.DotnetDepsEntry{},
   317  			expectedJSONName:   "dotnet-deps-entry",
   318  			expectedLegacyName: "DotnetDepsMetadata",
   319  		},
   320  		{
   321  			name:               "DotnetPortableExecutableMetadata",
   322  			metadata:           pkg.DotnetPortableExecutableEntry{},
   323  			expectedJSONName:   "dotnet-portable-executable-entry",
   324  			expectedLegacyName: "dotnet-portable-executable-entry", // note: the legacy name should never be blank if it didn't exist pre v11.x
   325  		},
   326  		{
   327  			name:               "DpkgMetadata",
   328  			metadata:           pkg.DpkgDBEntry{},
   329  			expectedJSONName:   "dpkg-db-entry",
   330  			expectedLegacyName: "DpkgMetadata",
   331  		},
   332  		{
   333  			name:               "GemMetadata",
   334  			metadata:           pkg.RubyGemspec{},
   335  			expectedJSONName:   "ruby-gemspec",
   336  			expectedLegacyName: "GemMetadata",
   337  		},
   338  		{
   339  			name:               "GolangBinMetadata",
   340  			metadata:           pkg.GolangBinaryBuildinfoEntry{},
   341  			expectedJSONName:   "go-module-buildinfo-entry",
   342  			expectedLegacyName: "GolangBinMetadata",
   343  		},
   344  		{
   345  			name:               "GolangModMetadata",
   346  			metadata:           pkg.GolangModuleEntry{},
   347  			expectedJSONName:   "go-module-entry",
   348  			expectedLegacyName: "GolangModMetadata",
   349  		},
   350  		{
   351  			name:               "HackageStackYamlLockMetadata",
   352  			metadata:           pkg.HackageStackYamlLockEntry{},
   353  			expectedJSONName:   "haskell-hackage-stack-lock-entry",
   354  			expectedLegacyName: "HackageMetadataType", // this is closest to the original data shape in <=v11.x schema
   355  		},
   356  		{
   357  			name:               "HackageStackYamlMetadata",
   358  			metadata:           pkg.HackageStackYamlEntry{},
   359  			expectedJSONName:   "haskell-hackage-stack-entry",
   360  			expectedLegacyName: "HackageMetadataType", // note: this conflicts with <=v11.x schema for "haskell-hackage-stack-lock" metadata type
   361  		},
   362  		{
   363  			name:               "JavaMetadata",
   364  			metadata:           pkg.JavaArchive{},
   365  			expectedJSONName:   "java-archive",
   366  			expectedLegacyName: "JavaMetadata",
   367  		},
   368  		{
   369  			name:               "KbPatchMetadata",
   370  			metadata:           pkg.MicrosoftKbPatch{},
   371  			expectedJSONName:   "microsoft-kb-patch",
   372  			expectedLegacyName: "KbPatchMetadata",
   373  		},
   374  		{
   375  			name:               "LinuxKernel",
   376  			metadata:           pkg.LinuxKernel{},
   377  			expectedJSONName:   "linux-kernel-archive",
   378  			expectedLegacyName: "LinuxKernel",
   379  		},
   380  		{
   381  			name:               "LinuxKernelModule",
   382  			metadata:           pkg.LinuxKernelModule{},
   383  			expectedJSONName:   "linux-kernel-module",
   384  			expectedLegacyName: "LinuxKernelModule",
   385  		},
   386  		{
   387  			name:               "MixLockMetadata",
   388  			metadata:           pkg.ElixirMixLockEntry{},
   389  			expectedJSONName:   "elixir-mix-lock-entry",
   390  			expectedLegacyName: "MixLockMetadataType",
   391  		},
   392  		{
   393  			name:               "NixStoreMetadata",
   394  			metadata:           pkg.NixStoreEntry{},
   395  			expectedJSONName:   "nix-store-entry",
   396  			expectedLegacyName: "NixStoreMetadata",
   397  		},
   398  		{
   399  			name:               "NpmPackageJSONMetadata",
   400  			metadata:           pkg.NpmPackage{},
   401  			expectedJSONName:   "javascript-npm-package",
   402  			expectedLegacyName: "NpmPackageJsonMetadata",
   403  		},
   404  		{
   405  			name:               "NpmPackageLockJSONMetadata",
   406  			metadata:           pkg.NpmPackageLockEntry{},
   407  			expectedJSONName:   "javascript-npm-package-lock-entry",
   408  			expectedLegacyName: "NpmPackageLockJsonMetadata",
   409  		},
   410  		{
   411  			name:               "PhpComposerLockMetadata",
   412  			metadata:           pkg.PhpComposerLockEntry{},
   413  			expectedJSONName:   "php-composer-lock-entry",
   414  			expectedLegacyName: "PhpComposerJsonMetadata", // note: maps to multiple entries (v11-12 breaking change)
   415  		},
   416  		{
   417  			name:               "PhpComposerInstalledMetadata",
   418  			metadata:           pkg.PhpComposerInstalledEntry{},
   419  			expectedJSONName:   "php-composer-installed-entry",
   420  			expectedLegacyName: "PhpComposerJsonMetadata", // note: maps to multiple entries (v11-12 breaking change)
   421  		},
   422  		{
   423  			name:               "PhpPeclMetadata",
   424  			metadata:           pkg.PhpPeclEntry{},
   425  			expectedJSONName:   "php-pecl-entry",
   426  			expectedLegacyName: "PhpPeclMetadata",
   427  		},
   428  		{
   429  			name:               "PortageMetadata",
   430  			metadata:           pkg.PortageEntry{},
   431  			expectedJSONName:   "portage-db-entry",
   432  			expectedLegacyName: "PortageMetadata",
   433  		},
   434  		{
   435  			name:               "PythonPackageMetadata",
   436  			metadata:           pkg.PythonPackage{},
   437  			expectedJSONName:   "python-package",
   438  			expectedLegacyName: "PythonPackageMetadata",
   439  		},
   440  		{
   441  			name:               "PythonPipfileLockMetadata",
   442  			metadata:           pkg.PythonPipfileLockEntry{},
   443  			expectedJSONName:   "python-pipfile-lock-entry",
   444  			expectedLegacyName: "PythonPipfileLockMetadata",
   445  		},
   446  		{
   447  			name:               "PythonRequirementsMetadata",
   448  			metadata:           pkg.PythonRequirementsEntry{},
   449  			expectedJSONName:   "python-pip-requirements-entry",
   450  			expectedLegacyName: "PythonRequirementsMetadata",
   451  		},
   452  		{
   453  			name:               "RebarLockMetadata",
   454  			metadata:           pkg.ErlangRebarLockEntry{},
   455  			expectedJSONName:   "erlang-rebar-lock-entry",
   456  			expectedLegacyName: "RebarLockMetadataType",
   457  		},
   458  		{
   459  			name:               "RDescriptionFileMetadata",
   460  			metadata:           pkg.RDescription{},
   461  			expectedJSONName:   "r-description",
   462  			expectedLegacyName: "RDescriptionFileMetadataType",
   463  		},
   464  		{
   465  			name:               "RpmDBMetadata",
   466  			metadata:           pkg.RpmDBEntry{},
   467  			expectedJSONName:   "rpm-db-entry",
   468  			expectedLegacyName: "RpmMetadata", // not accurate, but how it was pre v12 of the schema
   469  		},
   470  		{
   471  			name:               "RpmArchiveMetadata",
   472  			metadata:           pkg.RpmArchive{},
   473  			expectedJSONName:   "rpm-archive",
   474  			expectedLegacyName: "RpmMetadata", // note: conflicts with <=v11.x schema for "rpm-db-entry" metadata type
   475  		},
   476  		{
   477  			name:               "SwiftPackageManagerMetadata",
   478  			metadata:           pkg.SwiftPackageManagerResolvedEntry{},
   479  			expectedJSONName:   "swift-package-manager-lock-entry",
   480  			expectedLegacyName: "SwiftPackageManagerMetadata",
   481  		},
   482  		{
   483  			name:               "CargoPackageMetadata",
   484  			metadata:           pkg.RustCargoLockEntry{},
   485  			expectedJSONName:   "rust-cargo-lock-entry",
   486  			expectedLegacyName: "RustCargoPackageMetadata", // note: maps to multiple entries (v11-12 breaking change)
   487  		},
   488  		{
   489  			name:               "CargoPackageMetadata (audit binary)",
   490  			metadata:           pkg.RustBinaryAuditEntry{},
   491  			expectedJSONName:   "rust-cargo-audit-entry",
   492  			expectedLegacyName: "RustCargoPackageMetadata", // note: maps to multiple entries (v11-12 breaking change)
   493  		},
   494  	}
   495  
   496  	for _, test := range tests {
   497  		t.Run(test.name, func(t *testing.T) {
   498  			actualJSONName := JSONName(test.metadata)
   499  			actualLegacyName := JSONLegacyName(test.metadata)
   500  			assert.Equal(t, test.expectedJSONName, actualJSONName, "unexpected name")
   501  			assert.Equal(t, test.expectedLegacyName, actualLegacyName, "unexpected legacy name")
   502  		})
   503  	}
   504  }