github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/syftjson/decoder_test.go (about)

     1  package syftjson
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/go-test/deep"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    15  	"github.com/anchore/syft/internal"
    16  	"github.com/anchore/syft/syft/cpe"
    17  	"github.com/anchore/syft/syft/file"
    18  	"github.com/anchore/syft/syft/format/internal/testutil"
    19  	"github.com/anchore/syft/syft/linux"
    20  	"github.com/anchore/syft/syft/pkg"
    21  	"github.com/anchore/syft/syft/sbom"
    22  	"github.com/anchore/syft/syft/source"
    23  )
    24  
    25  func Test_EncodeDecodeCycle(t *testing.T) {
    26  
    27  	table := []struct {
    28  		name         string
    29  		fixtureImage string
    30  		cfg          EncoderConfig
    31  	}{
    32  		{
    33  			name:         "go case",
    34  			fixtureImage: "image-simple",
    35  			cfg:          DefaultEncoderConfig(),
    36  		},
    37  		{
    38  			name:         "default alpine",
    39  			fixtureImage: "image-alpine",
    40  			cfg:          DefaultEncoderConfig(),
    41  		},
    42  		{
    43  			name:         "legacy alpine",
    44  			fixtureImage: "image-alpine",
    45  			cfg: EncoderConfig{
    46  				Legacy: true,
    47  			},
    48  		},
    49  	}
    50  
    51  	for _, tt := range table {
    52  		t.Run(tt.name, func(t *testing.T) {
    53  			originalSBOM := testutil.ImageInput(t, tt.fixtureImage)
    54  
    55  			enc, err := NewFormatEncoderWithConfig(tt.cfg)
    56  			require.NoError(t, err)
    57  			dec := NewFormatDecoder()
    58  
    59  			var buf bytes.Buffer
    60  			assert.NoError(t, enc.Encode(&buf, originalSBOM))
    61  
    62  			actualSBOM, decodedID, decodedVersion, err := dec.Decode(bytes.NewReader(buf.Bytes()))
    63  			assert.NoError(t, err)
    64  			assert.Equal(t, ID, decodedID)
    65  			assert.Equal(t, internal.JSONSchemaVersion, decodedVersion)
    66  
    67  			for _, d := range deep.Equal(originalSBOM.Source, actualSBOM.Source) {
    68  				if strings.HasSuffix(d, "<nil slice> != []") {
    69  					// semantically the same
    70  					continue
    71  				}
    72  				t.Errorf("metadata difference: %+v", d)
    73  			}
    74  
    75  			actualPackages := actualSBOM.Artifacts.Packages.Sorted()
    76  			for idx, p := range originalSBOM.Artifacts.Packages.Sorted() {
    77  				if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
    78  					t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
    79  					continue
    80  				}
    81  
    82  				for _, d := range deep.Equal(p, actualPackages[idx]) {
    83  					if strings.Contains(d, ".AccessPath: ") {
    84  						// location.Virtual path is not exposed in the json output
    85  						continue
    86  					}
    87  					if strings.HasSuffix(d, "<nil slice> != []") {
    88  						// semantically the same
    89  						continue
    90  					}
    91  					t.Errorf("%q package difference (%s): %+v", tt.fixtureImage, p.Name, d)
    92  				}
    93  			}
    94  		})
    95  	}
    96  }
    97  
    98  func TestOutOfDateParser(t *testing.T) {
    99  	tests := []struct {
   100  		name            string
   101  		documentVersion string
   102  		parserVersion   string
   103  		want            error
   104  	}{{
   105  		name:            "no warning when doc version is older",
   106  		documentVersion: "1.0.9",
   107  		parserVersion:   "3.1.0",
   108  	}, {
   109  		name:            "warning when parser is older",
   110  		documentVersion: "4.3.2",
   111  		parserVersion:   "3.1.0",
   112  		want:            fmt.Errorf("document has schema version %s, but parser has older schema version (%s)", "4.3.2", "3.1.0"),
   113  	}, {
   114  		name:            "warning when document version is unparseable",
   115  		documentVersion: "some-nonsense",
   116  		parserVersion:   "3.1.0",
   117  		want:            fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("Invalid Semantic Version")),
   118  	}, {
   119  		name:            "warning when parser version is unparseable",
   120  		documentVersion: "7.1.0",
   121  		parserVersion:   "some-nonsense",
   122  		want:            fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("Invalid Semantic Version")),
   123  	}}
   124  
   125  	for _, tt := range tests {
   126  		t.Run(tt.name, func(t *testing.T) {
   127  			got := checkSupportedSchema(tt.documentVersion, tt.parserVersion)
   128  			assert.Equal(t, tt.want, got)
   129  		})
   130  	}
   131  }
   132  
   133  func Test_encodeDecodeFileMetadata(t *testing.T) {
   134  	p := pkg.Package{
   135  		Name:    "pkg",
   136  		Version: "version",
   137  		FoundBy: "something",
   138  		Locations: file.NewLocationSet(file.Location{
   139  			LocationData: file.LocationData{
   140  				Coordinates: file.Coordinates{
   141  					RealPath:     "/somewhere",
   142  					FileSystemID: "id",
   143  				},
   144  			},
   145  			LocationMetadata: file.LocationMetadata{
   146  				Annotations: map[string]string{
   147  					"key": "value",
   148  				},
   149  			},
   150  		}),
   151  		Licenses: pkg.NewLicenseSet(pkg.License{
   152  			Value:          "MIT",
   153  			SPDXExpression: "MIT",
   154  			Type:           "MIT",
   155  			URLs:           []string{"https://example.org/license"},
   156  			Locations:      file.LocationSet{},
   157  		}),
   158  		Language: "language",
   159  		Type:     "type",
   160  		CPEs: []cpe.CPE{
   161  			{
   162  				Attributes: cpe.Attributes{
   163  					Part:    "a",
   164  					Vendor:  "vendor",
   165  					Product: "product",
   166  					Version: "version",
   167  					Update:  "update",
   168  				},
   169  				Source: "test-source",
   170  			},
   171  		},
   172  		PURL:     "pkg:generic/pkg@version",
   173  		Metadata: nil,
   174  	}
   175  	p.SetID()
   176  
   177  	c := file.Coordinates{
   178  		RealPath:     "some-file",
   179  		FileSystemID: "some-fs-id",
   180  	}
   181  
   182  	s := sbom.SBOM{
   183  		Artifacts: sbom.Artifacts{
   184  			Packages: pkg.NewCollection(p),
   185  			FileMetadata: map[file.Coordinates]file.Metadata{
   186  				c: {
   187  					FileInfo: stereoscopeFile.ManualInfo{
   188  						NameValue: c.RealPath,
   189  						ModeValue: 0644,
   190  						SizeValue: 7,
   191  					},
   192  					Path:     c.RealPath,
   193  					Type:     stereoscopeFile.TypeRegular,
   194  					UserID:   1,
   195  					GroupID:  2,
   196  					MIMEType: "text/plain",
   197  				},
   198  			},
   199  			FileDigests: map[file.Coordinates][]file.Digest{
   200  				c: {
   201  					{
   202  						Algorithm: "sha1",
   203  						Value:     "d34db33f",
   204  					},
   205  				},
   206  			},
   207  			FileContents: map[file.Coordinates]string{
   208  				c: "some contents",
   209  			},
   210  			FileLicenses: map[file.Coordinates][]file.License{
   211  				c: {
   212  					{
   213  						Value:          "MIT",
   214  						SPDXExpression: "MIT",
   215  						Type:           "MIT",
   216  						LicenseEvidence: &file.LicenseEvidence{
   217  							Confidence: 1,
   218  							Offset:     2,
   219  							Extent:     3,
   220  						},
   221  					},
   222  				},
   223  			},
   224  			Executables: map[file.Coordinates]file.Executable{
   225  				c: {
   226  					Format: file.ELF,
   227  					ELFSecurityFeatures: &file.ELFSecurityFeatures{
   228  						SymbolTableStripped:           false,
   229  						StackCanary:                   boolRef(true),
   230  						NoExecutable:                  false,
   231  						RelocationReadOnly:            "partial",
   232  						PositionIndependentExecutable: false,
   233  						DynamicSharedObject:           false,
   234  						LlvmSafeStack:                 boolRef(false),
   235  						LlvmControlFlowIntegrity:      boolRef(true),
   236  						ClangFortifySource:            boolRef(true),
   237  					},
   238  				},
   239  			},
   240  			LinuxDistribution: &linux.Release{
   241  				PrettyName:       "some os",
   242  				Name:             "os",
   243  				ID:               "os-id",
   244  				IDLike:           []string{"os"},
   245  				Version:          "version",
   246  				VersionID:        "version",
   247  				VersionCodename:  "codename",
   248  				BuildID:          "build-id",
   249  				ImageID:          "image-id",
   250  				ImageVersion:     "image-version",
   251  				Variant:          "variant",
   252  				VariantID:        "variant-id",
   253  				HomeURL:          "https://example.org/os",
   254  				SupportURL:       "https://example.org/os/support",
   255  				BugReportURL:     "https://example.org/os/bugs",
   256  				PrivacyPolicyURL: "https://example.org/os/privacy",
   257  				CPEName:          "os-cpe",
   258  				SupportEnd:       "now",
   259  			},
   260  		},
   261  		Relationships: nil,
   262  		Source: source.Description{
   263  			ID:      "some-id",
   264  			Name:    "some-name",
   265  			Version: "some-version",
   266  			Metadata: source.FileMetadata{
   267  				Path: "/some-file-source-path",
   268  				Digests: []file.Digest{
   269  					{
   270  						Algorithm: "sha1",
   271  						Value:     "d34db33f",
   272  					},
   273  				},
   274  				MIMEType: "file/zip",
   275  			},
   276  		},
   277  		Descriptor: sbom.Descriptor{
   278  			Name:    "syft",
   279  			Version: "this-version",
   280  		},
   281  	}
   282  
   283  	buf := &bytes.Buffer{}
   284  	enc := NewFormatEncoder()
   285  	err := enc.Encode(buf, s)
   286  	require.NoError(t, err)
   287  
   288  	dec := NewFormatDecoder()
   289  
   290  	got, decodedID, decodedVersion, err := dec.Decode(bytes.NewReader(buf.Bytes()))
   291  	require.NoError(t, err)
   292  	assert.Equal(t, ID, decodedID)
   293  	assert.Equal(t, internal.JSONSchemaVersion, decodedVersion)
   294  
   295  	require.Equal(t, s, *got)
   296  }