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

     1  package cyclonedxxml
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"regexp"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/syft/syft/format/internal/cyclonedxutil"
    14  	"github.com/anchore/syft/syft/format/internal/testutil"
    15  	"github.com/anchore/syft/syft/sbom"
    16  )
    17  
    18  var updateSnapshot = flag.Bool("update-cyclonedx-xml", false, "update the *.golden files for cyclone-dx XML 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  	enc, err := NewFormatEncoderWithConfig(EncoderConfig{
    32  		Version: cyclonedxutil.DefaultVersion,
    33  		Pretty:  false,
    34  	})
    35  	require.NoError(t, err)
    36  
    37  	dir := t.TempDir()
    38  	s := testutil.DirectoryInput(t, dir)
    39  
    40  	var buffer bytes.Buffer
    41  	err = enc.Encode(&buffer, s)
    42  	require.NoError(t, err)
    43  
    44  	actual := buffer.String()
    45  	lines := strings.Split(actual, "\n")
    46  	require.NotEmpty(t, lines)
    47  	whitespace := regexp.MustCompile(`^\s+`)
    48  	for _, line := range lines {
    49  		if len(line) == 0 {
    50  			continue
    51  		}
    52  
    53  		// require a non-whitespace character (tab, space, etc) as the first character of the line
    54  		require.False(t, whitespace.Match([]byte(line)), "line should not start with whitespace: %q", line)
    55  	}
    56  }
    57  
    58  func TestCycloneDxDirectoryEncoder(t *testing.T) {
    59  	dir := t.TempDir()
    60  	testutil.AssertEncoderAgainstGoldenSnapshot(t,
    61  		testutil.EncoderSnapshotTestConfig{
    62  			Subject:                     testutil.DirectoryInput(t, dir),
    63  			Format:                      getEncoder(t),
    64  			UpdateSnapshot:              *updateSnapshot,
    65  			PersistRedactionsInSnapshot: true,
    66  			IsJSON:                      false,
    67  			Redactor:                    redactor(dir),
    68  		},
    69  	)
    70  }
    71  
    72  func TestCycloneDxImageEncoder(t *testing.T) {
    73  	testImage := "image-simple"
    74  	testutil.AssertEncoderAgainstGoldenImageSnapshot(t,
    75  		testutil.ImageSnapshotTestConfig{
    76  			Image:               testImage,
    77  			UpdateImageSnapshot: *updateImage,
    78  		},
    79  		testutil.EncoderSnapshotTestConfig{
    80  			Subject:                     testutil.ImageInput(t, testImage),
    81  			Format:                      getEncoder(t),
    82  			UpdateSnapshot:              *updateSnapshot,
    83  			PersistRedactionsInSnapshot: true,
    84  			IsJSON:                      false,
    85  			Redactor:                    redactor(),
    86  		},
    87  	)
    88  }
    89  
    90  func redactor(values ...string) testutil.Redactor {
    91  	return testutil.NewRedactions().
    92  		WithValuesRedacted(values...).
    93  		WithPatternRedactors(
    94  			map[string]string{
    95  				// dates
    96  				`([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]))`: `redacted`,
    97  
    98  				// image hashes and BOM refs
    99  				`sha256:[A-Za-z0-9]{64}`: `sha256:redacted`,
   100  
   101  				// serial numbers and BOM refs
   102  				`(serialNumber|bom-ref)="[^"]+"`: `$1="redacted"`,
   103  			},
   104  		)
   105  }
   106  
   107  func TestSupportedVersions(t *testing.T) {
   108  	encs := defaultFormatEncoders()
   109  	require.NotEmpty(t, encs)
   110  
   111  	versions := SupportedVersions()
   112  	require.Equal(t, len(versions), len(encs))
   113  
   114  	subject := testutil.DirectoryInput(t, t.TempDir())
   115  	dec := NewFormatDecoder()
   116  
   117  	for _, enc := range encs {
   118  		t.Run(enc.Version(), func(t *testing.T) {
   119  			require.Contains(t, versions, enc.Version())
   120  
   121  			var buf bytes.Buffer
   122  			require.NoError(t, enc.Encode(&buf, subject))
   123  
   124  			id, version := dec.Identify(bytes.NewReader(buf.Bytes()))
   125  			require.Equal(t, enc.ID(), id)
   126  			require.Equal(t, enc.Version(), version)
   127  
   128  			var s *sbom.SBOM
   129  			var err error
   130  			s, id, version, err = dec.Decode(bytes.NewReader(buf.Bytes()))
   131  			require.NoError(t, err)
   132  			require.Equal(t, enc.ID(), id)
   133  			require.Equal(t, enc.Version(), version)
   134  
   135  			require.NotEmpty(t, s.Artifacts.Packages.PackageCount())
   136  
   137  			assert.Equal(t, len(subject.Relationships), len(s.Relationships), "mismatched relationship count")
   138  
   139  			if !assert.Equal(t, subject.Artifacts.Packages.PackageCount(), s.Artifacts.Packages.PackageCount(), "mismatched package count") {
   140  				t.Logf("expected: %d", subject.Artifacts.Packages.PackageCount())
   141  				for _, p := range subject.Artifacts.Packages.Sorted() {
   142  					t.Logf("  - %s", p.String())
   143  				}
   144  				t.Logf("actual: %d", s.Artifacts.Packages.PackageCount())
   145  				for _, p := range s.Artifacts.Packages.Sorted() {
   146  					t.Logf("  - %s", p.String())
   147  				}
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func defaultFormatEncoders() []sbom.FormatEncoder {
   154  	var encs []sbom.FormatEncoder
   155  	for _, version := range SupportedVersions() {
   156  		enc, err := NewFormatEncoderWithConfig(EncoderConfig{Version: version})
   157  		if err != nil {
   158  			panic(err)
   159  		}
   160  		encs = append(encs, enc)
   161  	}
   162  	return encs
   163  }