github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/cyclonedxjson/encoder_test.go (about)

     1  package cyclonedxjson
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/anchore/syft/syft/pkg"
    13  	"github.com/anchore/syft/syft/sbom"
    14  	"github.com/lineaje-labs/syft/syft/format/internal/cyclonedxutil"
    15  	"github.com/lineaje-labs/syft/syft/format/internal/testutil"
    16  )
    17  
    18  var updateSnapshot = flag.Bool("update-cyclonedx-json", false, "update the *.golden files for cyclone-dx JSON encoders")
    19  var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing")
    20  
    21  func getEncoder(t testing.TB) sbom.FormatEncoder {
    22  	cfg := DefaultEncoderConfig()
    23  	cfg.Pretty = true
    24  
    25  	enc, err := NewFormatEncoderWithConfig(cfg)
    26  	require.NoError(t, err)
    27  	return enc
    28  }
    29  
    30  func TestPrettyOutput(t *testing.T) {
    31  	run := func(opt bool) string {
    32  		enc, err := NewFormatEncoderWithConfig(EncoderConfig{
    33  			Version: cyclonedxutil.DefaultVersion,
    34  			Pretty:  opt,
    35  		})
    36  		require.NoError(t, err)
    37  
    38  		dir := t.TempDir()
    39  		s := testutil.DirectoryInput(t, dir)
    40  
    41  		var buffer bytes.Buffer
    42  		err = enc.Encode(&buffer, s)
    43  		require.NoError(t, err)
    44  
    45  		return strings.TrimSpace(buffer.String())
    46  	}
    47  
    48  	t.Run("pretty", func(t *testing.T) {
    49  		actual := run(true)
    50  		assert.Contains(t, actual, "\n")
    51  	})
    52  
    53  	t.Run("compact", func(t *testing.T) {
    54  		actual := run(false)
    55  		assert.NotContains(t, actual, "\n")
    56  	})
    57  }
    58  
    59  func TestEscapeHTML(t *testing.T) {
    60  	dir := t.TempDir()
    61  	s := testutil.DirectoryInput(t, dir)
    62  	s.Artifacts.Packages.Add(pkg.Package{
    63  		Name: "<html-package>",
    64  	})
    65  
    66  	// by default we do not escape HTML
    67  	t.Run("default", func(t *testing.T) {
    68  		cfg := DefaultEncoderConfig()
    69  
    70  		enc, err := NewFormatEncoderWithConfig(cfg)
    71  		require.NoError(t, err)
    72  
    73  		var buffer bytes.Buffer
    74  		err = enc.Encode(&buffer, s)
    75  		require.NoError(t, err)
    76  
    77  		actual := buffer.String()
    78  		assert.Contains(t, actual, "<html-package>")
    79  		assert.NotContains(t, actual, "\\u003chtml-package\\u003e")
    80  	})
    81  
    82  }
    83  
    84  func TestCycloneDxDirectoryEncoder(t *testing.T) {
    85  	dir := t.TempDir()
    86  	testutil.AssertEncoderAgainstGoldenSnapshot(t,
    87  		testutil.EncoderSnapshotTestConfig{
    88  			Subject:                     testutil.DirectoryInput(t, dir),
    89  			Format:                      getEncoder(t),
    90  			UpdateSnapshot:              *updateSnapshot,
    91  			PersistRedactionsInSnapshot: true,
    92  			IsJSON:                      true,
    93  			Redactor:                    redactor(dir),
    94  		},
    95  	)
    96  }
    97  
    98  func TestCycloneDxImageEncoder(t *testing.T) {
    99  	testImage := "image-simple"
   100  	testutil.AssertEncoderAgainstGoldenImageSnapshot(t,
   101  		testutil.ImageSnapshotTestConfig{
   102  			Image:               testImage,
   103  			UpdateImageSnapshot: *updateImage,
   104  		},
   105  		testutil.EncoderSnapshotTestConfig{
   106  			Subject:                     testutil.ImageInput(t, testImage),
   107  			Format:                      getEncoder(t),
   108  			UpdateSnapshot:              *updateSnapshot,
   109  			PersistRedactionsInSnapshot: true,
   110  			IsJSON:                      true,
   111  			Redactor:                    redactor(),
   112  		},
   113  	)
   114  }
   115  
   116  func redactor(values ...string) testutil.Redactor {
   117  	return testutil.NewRedactions().
   118  		WithValuesRedacted(values...).
   119  		WithPatternRedactors(
   120  			map[string]string{
   121  				// UUIDs
   122  				`urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`: `urn:uuid:redacted`,
   123  
   124  				// timestamps
   125  				`([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`: `timestamp:redacted`,
   126  
   127  				// image hashes
   128  				`sha256:[A-Fa-f0-9]{64}`: `sha256:redacted`,
   129  
   130  				// BOM refs
   131  				`"bom-ref":\s*"[^"]+"`: `"bom-ref":"redacted"`,
   132  			},
   133  		)
   134  }
   135  
   136  func TestSupportedVersions(t *testing.T) {
   137  	encs := defaultFormatEncoders()
   138  	require.NotEmpty(t, encs)
   139  
   140  	versions := SupportedVersions()
   141  	require.Equal(t, len(versions), len(encs))
   142  
   143  	subject := testutil.DirectoryInput(t, t.TempDir())
   144  	dec := NewFormatDecoder()
   145  
   146  	for _, enc := range encs {
   147  		t.Run(enc.Version(), func(t *testing.T) {
   148  			require.Contains(t, versions, enc.Version())
   149  
   150  			var buf bytes.Buffer
   151  			require.NoError(t, enc.Encode(&buf, subject))
   152  
   153  			id, version := dec.Identify(bytes.NewReader(buf.Bytes()))
   154  			require.Equal(t, enc.ID(), id)
   155  			require.Equal(t, enc.Version(), version)
   156  
   157  			var s *sbom.SBOM
   158  			var err error
   159  			s, id, version, err = dec.Decode(bytes.NewReader(buf.Bytes()))
   160  			require.NoError(t, err)
   161  			require.Equal(t, enc.ID(), id)
   162  			require.Equal(t, enc.Version(), version)
   163  
   164  			require.NotEmpty(t, s.Artifacts.Packages.PackageCount())
   165  
   166  			assert.Equal(t, len(subject.Relationships), len(s.Relationships), "mismatched relationship count")
   167  
   168  			if !assert.Equal(t, subject.Artifacts.Packages.PackageCount(), s.Artifacts.Packages.PackageCount(), "mismatched package count") {
   169  				t.Logf("expected: %d", subject.Artifacts.Packages.PackageCount())
   170  				for _, p := range subject.Artifacts.Packages.Sorted() {
   171  					t.Logf("  - %s", p.String())
   172  				}
   173  				t.Logf("actual: %d", s.Artifacts.Packages.PackageCount())
   174  				for _, p := range s.Artifacts.Packages.Sorted() {
   175  					t.Logf("  - %s", p.String())
   176  				}
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  func defaultFormatEncoders() []sbom.FormatEncoder {
   183  	var encs []sbom.FormatEncoder
   184  	for _, version := range SupportedVersions() {
   185  		enc, err := NewFormatEncoderWithConfig(EncoderConfig{Version: version})
   186  		if err != nil {
   187  			panic(err)
   188  		}
   189  		encs = append(encs, enc)
   190  	}
   191  	return encs
   192  }