github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/test/cli/cyclonedx_valid_test.go (about)

     1  package cli
     2  
     3  import (
     4  	"os"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/anchore/stereoscope/pkg/imagetest"
    12  	"github.com/anchore/syft/syft/format/cyclonedxjson"
    13  )
    14  
    15  // We have schema validation mechanims in schema/cyclonedx/
    16  // This test allows us to double check that validation against the cyclonedx-cli tool
    17  func TestValidCycloneDX(t *testing.T) {
    18  	imageFixture := func(t *testing.T) string {
    19  		fixtureImageName := "image-pkg-coverage"
    20  		imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
    21  		tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
    22  		return "docker-archive:" + tarPath
    23  	}
    24  
    25  	// TODO update image to exercise entire cyclonedx schema
    26  	tests := []struct {
    27  		name       string
    28  		subcommand string
    29  		args       []string
    30  		fixture    func(*testing.T) string
    31  		assertions []traitAssertion
    32  	}{
    33  		{
    34  			name:       "validate cyclonedx output",
    35  			subcommand: "scan",
    36  			args:       []string{"-o", "cyclonedx-json"},
    37  			fixture:    imageFixture,
    38  			assertions: []traitAssertion{
    39  				assertSuccessfulReturnCode,
    40  				assertValidCycloneDX,
    41  			},
    42  		},
    43  	}
    44  
    45  	for _, test := range tests {
    46  		t.Run(test.name, func(t *testing.T) {
    47  			fixtureRef := test.fixture(t)
    48  			args := []string{
    49  				test.subcommand, fixtureRef, "-q",
    50  			}
    51  			args = append(args, test.args...)
    52  
    53  			cmd, stdout, stderr := runSyft(t, nil, args...)
    54  			for _, traitFn := range test.assertions {
    55  				traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
    56  			}
    57  			logOutputOnFailure(t, cmd, stdout, stderr)
    58  
    59  			validateCycloneDXJSON(t, stdout)
    60  		})
    61  	}
    62  }
    63  
    64  func assertValidCycloneDX(tb testing.TB, stdout, stderr string, rc int) {
    65  	tb.Helper()
    66  	f, err := os.CreateTemp("", "tmpfile-")
    67  	if err != nil {
    68  		tb.Fatal(err)
    69  	}
    70  
    71  	// close and remove the temporary file at the end of the program
    72  	defer f.Close()
    73  	defer os.Remove(f.Name())
    74  
    75  	data := []byte(stdout)
    76  
    77  	if _, err := f.Write(data); err != nil {
    78  		tb.Fatal(err)
    79  	}
    80  
    81  	args := []string{
    82  		"validate",
    83  		"--input-format",
    84  		"json",
    85  		"--input-version",
    86  		"v1_4",
    87  		"--input-file",
    88  		"/sbom",
    89  	}
    90  
    91  	cmd, stdout, stderr := runCycloneDXInDocker(tb, nil, "cyclonedx/cyclonedx-cli", f, args...)
    92  	if cmd.ProcessState.ExitCode() != 0 {
    93  		tb.Errorf("expected no validation failures for cyclonedx-cli but got rc=%d", rc)
    94  	}
    95  
    96  	logOutputOnFailure(tb, cmd, stdout, stderr)
    97  }
    98  
    99  // validate --input-format json --input-version v1_4 --input-file bom.json
   100  func validateCycloneDXJSON(t *testing.T, stdout string) {
   101  	f, err := os.CreateTemp("", "tmpfile-")
   102  	require.NoError(t, err)
   103  
   104  	// close and remove the temporary file at the end of the program
   105  	t.Cleanup(func() {
   106  		assert.NoError(t, f.Close())
   107  		assert.NoError(t, os.Remove(f.Name()))
   108  	})
   109  
   110  	data := []byte(stdout)
   111  
   112  	if _, err := f.Write(data); err != nil {
   113  		t.Fatal(err)
   114  	}
   115  
   116  	// get the latest supported version of CycloneDX by syft and convert the expression to the format used by cyclonedx-cli
   117  	// e.g. "1.5" -> "v1_5"
   118  	versions := cyclonedxjson.SupportedVersions()
   119  	version := versions[len(versions)-1]
   120  	versionInput := "v" + strings.Replace(version, ".", "_", -1)
   121  
   122  	args := []string{
   123  		"validate",
   124  		"--input-format",
   125  		"json",
   126  		"--input-version",
   127  		versionInput,
   128  		"--input-file",
   129  		"/sbom",
   130  	}
   131  
   132  	cmd, stdout, stderr := runCycloneDXInDocker(t, nil, "cyclonedx/cyclonedx-cli", f, args...)
   133  	if strings.Contains(stdout, "BOM is not valid") {
   134  		t.Log("STDOUT:\n", stdout)
   135  		t.Errorf("expected no validation failures for cyclonedx-cli but found invalid BOM")
   136  	}
   137  
   138  	logOutputOnFailure(t, cmd, stdout, stderr)
   139  }