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 }