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 }