github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/formats/syftjson/to_syft_model_test.go (about) 1 package syftjson 2 3 import ( 4 "errors" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 stereoFile "github.com/anchore/stereoscope/pkg/file" 11 "github.com/anchore/syft/syft/artifact" 12 "github.com/anchore/syft/syft/file" 13 "github.com/anchore/syft/syft/formats/syftjson/model" 14 "github.com/anchore/syft/syft/internal/sourcemetadata" 15 "github.com/anchore/syft/syft/pkg" 16 "github.com/anchore/syft/syft/sbom" 17 "github.com/anchore/syft/syft/source" 18 ) 19 20 func Test_toSyftSourceData(t *testing.T) { 21 tracker := sourcemetadata.NewCompletionTester(t) 22 23 tests := []struct { 24 name string 25 src model.Source 26 expected *source.Description 27 }{ 28 { 29 name: "directory", 30 src: model.Source{ 31 ID: "the-id", 32 Name: "some-name", 33 Version: "some-version", 34 Type: "directory", 35 Metadata: source.DirectorySourceMetadata{ 36 Path: "some/path", 37 Base: "some/base", 38 }, 39 }, 40 expected: &source.Description{ 41 ID: "the-id", 42 Name: "some-name", 43 Version: "some-version", 44 Metadata: source.DirectorySourceMetadata{ 45 Path: "some/path", 46 Base: "some/base", 47 }, 48 }, 49 }, 50 { 51 name: "file", 52 src: model.Source{ 53 ID: "the-id", 54 Name: "some-name", 55 Version: "some-version", 56 Type: "file", 57 Metadata: source.FileSourceMetadata{ 58 Path: "some/path", 59 Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}}, 60 MIMEType: "text/plain", 61 }, 62 }, 63 expected: &source.Description{ 64 ID: "the-id", 65 Name: "some-name", 66 Version: "some-version", 67 Metadata: source.FileSourceMetadata{ 68 Path: "some/path", 69 Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}}, 70 MIMEType: "text/plain", 71 }, 72 }, 73 }, 74 { 75 name: "image", 76 src: model.Source{ 77 ID: "the-id", 78 Name: "some-name", 79 Version: "some-version", 80 Type: "image", 81 Metadata: source.StereoscopeImageSourceMetadata{ 82 UserInput: "user-input", 83 ID: "id...", 84 ManifestDigest: "digest...", 85 MediaType: "type...", 86 }, 87 }, 88 expected: &source.Description{ 89 ID: "the-id", 90 Name: "some-name", 91 Version: "some-version", 92 Metadata: source.StereoscopeImageSourceMetadata{ 93 UserInput: "user-input", 94 ID: "id...", 95 ManifestDigest: "digest...", 96 MediaType: "type...", 97 }, 98 }, 99 }, 100 // below are regression tests for when the name/version are not provided 101 // historically we've hoisted up the name/version from the metadata, now it is a simple pass-through 102 { 103 name: "directory - no name/version", 104 src: model.Source{ 105 ID: "the-id", 106 Type: "directory", 107 Metadata: source.DirectorySourceMetadata{ 108 Path: "some/path", 109 Base: "some/base", 110 }, 111 }, 112 expected: &source.Description{ 113 ID: "the-id", 114 Metadata: source.DirectorySourceMetadata{ 115 Path: "some/path", 116 Base: "some/base", 117 }, 118 }, 119 }, 120 { 121 name: "file - no name/version", 122 src: model.Source{ 123 ID: "the-id", 124 Type: "file", 125 Metadata: source.FileSourceMetadata{ 126 Path: "some/path", 127 Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}}, 128 MIMEType: "text/plain", 129 }, 130 }, 131 expected: &source.Description{ 132 ID: "the-id", 133 Metadata: source.FileSourceMetadata{ 134 Path: "some/path", 135 Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}}, 136 MIMEType: "text/plain", 137 }, 138 }, 139 }, 140 { 141 name: "image - no name/version", 142 src: model.Source{ 143 ID: "the-id", 144 Type: "image", 145 Metadata: source.StereoscopeImageSourceMetadata{ 146 UserInput: "user-input", 147 ID: "id...", 148 ManifestDigest: "digest...", 149 MediaType: "type...", 150 }, 151 }, 152 expected: &source.Description{ 153 ID: "the-id", 154 Metadata: source.StereoscopeImageSourceMetadata{ 155 UserInput: "user-input", 156 ID: "id...", 157 ManifestDigest: "digest...", 158 MediaType: "type...", 159 }, 160 }, 161 }, 162 } 163 for _, test := range tests { 164 t.Run(test.name, func(t *testing.T) { 165 // assert the model transformation is correct 166 actual := toSyftSourceData(test.src) 167 assert.Equal(t, test.expected, actual) 168 169 tracker.Tested(t, test.expected.Metadata) 170 }) 171 } 172 } 173 174 func Test_idsHaveChanged(t *testing.T) { 175 s, err := toSyftModel(model.Document{ 176 Source: model.Source{ 177 Type: "file", 178 Metadata: source.FileSourceMetadata{Path: "some/path"}, 179 }, 180 Artifacts: []model.Package{ 181 { 182 PackageBasicData: model.PackageBasicData{ 183 ID: "1", 184 Name: "pkg-1", 185 }, 186 }, 187 { 188 PackageBasicData: model.PackageBasicData{ 189 ID: "2", 190 Name: "pkg-2", 191 }, 192 }, 193 }, 194 ArtifactRelationships: []model.Relationship{ 195 { 196 Parent: "1", 197 Child: "2", 198 Type: string(artifact.ContainsRelationship), 199 }, 200 }, 201 }) 202 203 require.NoError(t, err) 204 require.Len(t, s.Relationships, 1) 205 206 r := s.Relationships[0] 207 208 from := s.Artifacts.Packages.Package(r.From.ID()) 209 require.NotNil(t, from) 210 assert.Equal(t, "pkg-1", from.Name) 211 212 to := s.Artifacts.Packages.Package(r.To.ID()) 213 require.NotNil(t, to) 214 assert.Equal(t, "pkg-2", to.Name) 215 } 216 217 func Test_toSyftFiles(t *testing.T) { 218 coord := file.Coordinates{ 219 RealPath: "/somerwhere/place", 220 FileSystemID: "abc", 221 } 222 223 tests := []struct { 224 name string 225 files []model.File 226 want sbom.Artifacts 227 }{ 228 { 229 name: "empty", 230 files: []model.File{}, 231 want: sbom.Artifacts{ 232 FileMetadata: map[file.Coordinates]file.Metadata{}, 233 FileDigests: map[file.Coordinates][]file.Digest{}, 234 }, 235 }, 236 { 237 name: "no metadata", 238 files: []model.File{ 239 { 240 ID: string(coord.ID()), 241 Location: coord, 242 Metadata: nil, 243 Digests: []file.Digest{ 244 { 245 Algorithm: "sha256", 246 Value: "123", 247 }, 248 }, 249 }, 250 }, 251 want: sbom.Artifacts{ 252 FileMetadata: map[file.Coordinates]file.Metadata{}, 253 FileDigests: map[file.Coordinates][]file.Digest{ 254 coord: { 255 { 256 Algorithm: "sha256", 257 Value: "123", 258 }, 259 }, 260 }, 261 }, 262 }, 263 { 264 name: "single file", 265 files: []model.File{ 266 { 267 ID: string(coord.ID()), 268 Location: coord, 269 Metadata: &model.FileMetadataEntry{ 270 Mode: 777, 271 Type: "RegularFile", 272 LinkDestination: "", 273 UserID: 42, 274 GroupID: 32, 275 MIMEType: "text/plain", 276 Size: 92, 277 }, 278 Digests: []file.Digest{ 279 { 280 Algorithm: "sha256", 281 Value: "123", 282 }, 283 }, 284 }, 285 }, 286 want: sbom.Artifacts{ 287 FileMetadata: map[file.Coordinates]file.Metadata{ 288 coord: { 289 FileInfo: stereoFile.ManualInfo{ 290 NameValue: "place", 291 SizeValue: 92, 292 ModeValue: 511, // 777 octal = 511 decimal 293 }, 294 Path: coord.RealPath, 295 LinkDestination: "", 296 UserID: 42, 297 GroupID: 32, 298 Type: stereoFile.TypeRegular, 299 MIMEType: "text/plain", 300 }, 301 }, 302 FileDigests: map[file.Coordinates][]file.Digest{ 303 coord: { 304 { 305 Algorithm: "sha256", 306 Value: "123", 307 }, 308 }, 309 }, 310 }, 311 }, 312 } 313 for _, tt := range tests { 314 t.Run(tt.name, func(t *testing.T) { 315 tt.want.FileContents = make(map[file.Coordinates]string) 316 tt.want.FileLicenses = make(map[file.Coordinates][]file.License) 317 assert.Equal(t, tt.want, toSyftFiles(tt.files)) 318 }) 319 } 320 } 321 322 func Test_toSyfRelationship(t *testing.T) { 323 packageWithId := func(id string) *pkg.Package { 324 p := &pkg.Package{} 325 p.OverrideID(artifact.ID(id)) 326 return p 327 } 328 childPackage := packageWithId("some-child-id") 329 parentPackage := packageWithId("some-parent-id") 330 tests := []struct { 331 name string 332 idMap map[string]interface{} 333 idAliases map[string]string 334 relationships model.Relationship 335 want *artifact.Relationship 336 wantError error 337 }{ 338 { 339 name: "one relationship no warnings", 340 idMap: map[string]interface{}{ 341 "some-child-id": childPackage, 342 "some-parent-id": parentPackage, 343 }, 344 idAliases: map[string]string{}, 345 relationships: model.Relationship{ 346 Parent: "some-parent-id", 347 Child: "some-child-id", 348 Type: string(artifact.ContainsRelationship), 349 }, 350 want: &artifact.Relationship{ 351 To: childPackage, 352 From: parentPackage, 353 Type: artifact.ContainsRelationship, 354 }, 355 }, 356 { 357 name: "relationship unknown type one warning", 358 idMap: map[string]interface{}{ 359 "some-child-id": childPackage, 360 "some-parent-id": parentPackage, 361 }, 362 idAliases: map[string]string{}, 363 relationships: model.Relationship{ 364 Parent: "some-parent-id", 365 Child: "some-child-id", 366 Type: "some-unknown-relationship-type", 367 }, 368 wantError: errors.New( 369 "unknown relationship type: some-unknown-relationship-type", 370 ), 371 }, 372 { 373 name: "relationship missing child ID one warning", 374 idMap: map[string]interface{}{ 375 "some-parent-id": parentPackage, 376 }, 377 idAliases: map[string]string{}, 378 relationships: model.Relationship{ 379 Parent: "some-parent-id", 380 Child: "some-child-id", 381 Type: string(artifact.ContainsRelationship), 382 }, 383 wantError: errors.New( 384 "relationship mapping to key some-child-id is not a valid artifact.Identifiable type: <nil>", 385 ), 386 }, 387 { 388 name: "relationship missing parent ID one warning", 389 idMap: map[string]interface{}{ 390 "some-child-id": childPackage, 391 }, 392 idAliases: map[string]string{}, 393 relationships: model.Relationship{ 394 Parent: "some-parent-id", 395 Child: "some-child-id", 396 Type: string(artifact.ContainsRelationship), 397 }, 398 wantError: errors.New("relationship mapping from key some-parent-id is not a valid artifact.Identifiable type: <nil>"), 399 }, 400 } 401 402 for _, tt := range tests { 403 t.Run(tt.name, func(t *testing.T) { 404 got, gotErr := toSyftRelationship(tt.idMap, tt.relationships, tt.idAliases) 405 assert.Equal(t, tt.want, got) 406 assert.Equal(t, tt.wantError, gotErr) 407 }) 408 } 409 } 410 411 func Test_deduplicateErrors(t *testing.T) { 412 tests := []struct { 413 name string 414 errors []error 415 want []string 416 }{ 417 { 418 name: "no errors, nil slice", 419 }, 420 { 421 name: "deduplicates errors", 422 errors: []error{ 423 errors.New("some error"), 424 errors.New("some error"), 425 }, 426 want: []string{ 427 `"some error" occurred 2 time(s)`, 428 }, 429 }, 430 } 431 for _, tt := range tests { 432 t.Run(tt.name, func(t *testing.T) { 433 got := deduplicateErrors(tt.errors) 434 assert.Equal(t, tt.want, got) 435 }) 436 } 437 }