github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/test/cli/trait_assertions_test.go (about) 1 package cli 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "regexp" 10 "sort" 11 "strings" 12 "testing" 13 14 "github.com/acarl005/stripansi" 15 "github.com/stretchr/testify/require" 16 ) 17 18 type traitAssertion func(tb testing.TB, stdout, stderr string, rc int) 19 20 func assertFileOutput(tb testing.TB, path string, assertions ...traitAssertion) traitAssertion { 21 tb.Helper() 22 23 return func(tb testing.TB, _, stderr string, rc int) { 24 content, err := os.ReadFile(path) 25 require.NoError(tb, err) 26 contentStr := string(content) 27 28 for _, assertion := range assertions { 29 // treat the file content as stdout 30 assertion(tb, contentStr, stderr, rc) 31 } 32 } 33 } 34 35 func assertJsonReport(tb testing.TB, stdout, _ string, _ int) { 36 tb.Helper() 37 var data interface{} 38 39 if err := json.Unmarshal([]byte(stdout), &data); err != nil { 40 tb.Errorf("expected to find a JSON report, but was unmarshalable: %+v", err) 41 } 42 } 43 44 func assertTableReport(tb testing.TB, stdout, _ string, _ int) { 45 tb.Helper() 46 if !strings.Contains(stdout, "NAME") || !strings.Contains(stdout, "VERSION") || !strings.Contains(stdout, "TYPE") { 47 tb.Errorf("expected to find a table report, but did not") 48 } 49 } 50 51 //func assertScope(scope source.Scope) traitAssertion { 52 // return func(tb testing.TB, stdout, stderr string, rc int) { 53 // tb.Helper() 54 // // we can only verify source with the json report 55 // assertJsonReport(tb, stdout, stderr, rc) 56 // 57 // if !strings.Contains(stdout, fmt.Sprintf(`"scope": "%s"`, scope.String())) { 58 // tb.Errorf("JSON report did not indicate the %q scope", scope) 59 // } 60 // } 61 //} 62 63 func assertLoggingLevel(level string) traitAssertion { 64 // match examples: 65 // "[0000] INFO" 66 // "[0012] DEBUG" 67 logPattern := regexp.MustCompile(`(?m)^\[\d\d\d\d\]\s+` + strings.ToUpper(level)) 68 return func(tb testing.TB, _, stderr string, _ int) { 69 tb.Helper() 70 if !logPattern.MatchString(stripansi.Strip(stderr)) { 71 tb.Errorf("output did not indicate the %q logging level", level) 72 } 73 } 74 } 75 76 func assertNotInOutput(data string) traitAssertion { 77 return func(tb testing.TB, stdout, stderr string, _ int) { 78 tb.Helper() 79 if strings.Contains(stripansi.Strip(stderr), data) { 80 tb.Errorf("data=%q was found in stderr, but should not have been there", data) 81 } 82 if strings.Contains(stripansi.Strip(stdout), data) { 83 tb.Errorf("data=%q was found in stdout, but should not have been there", data) 84 } 85 } 86 } 87 88 func assertInOutput(data string) traitAssertion { 89 return func(tb testing.TB, stdout, stderr string, _ int) { 90 tb.Helper() 91 if !strings.Contains(stripansi.Strip(stderr), data) && !strings.Contains(stripansi.Strip(stdout), data) { 92 tb.Errorf("data=%q was NOT found in any output, but should have been there", data) 93 } 94 } 95 } 96 97 func assertStdoutLengthGreaterThan(length uint) traitAssertion { 98 return func(tb testing.TB, stdout, _ string, _ int) { 99 tb.Helper() 100 if uint(len(stdout)) < length { 101 tb.Errorf("not enough output (expected at least %d, got %d)", length, len(stdout)) 102 } 103 } 104 } 105 106 func assertPackageCount(length uint) traitAssertion { 107 return func(tb testing.TB, stdout, _ string, _ int) { 108 tb.Helper() 109 type NameAndVersion struct { 110 Name string `json:"name"` 111 Version string `json:"version"` 112 } 113 type partial struct { 114 Artifacts []NameAndVersion `json:"artifacts"` 115 } 116 var data partial 117 118 if err := json.Unmarshal([]byte(stdout), &data); err != nil { 119 tb.Errorf("expected to find a JSON report, but was unmarshalable: %+v", err) 120 } 121 122 if uint(len(data.Artifacts)) != length { 123 tb.Errorf("expected package count of %d, but found %d", length, len(data.Artifacts)) 124 debugArtifacts := make([]string, len(data.Artifacts)) 125 for i, a := range data.Artifacts { 126 debugArtifacts[i] = fmt.Sprintf("%s:%s", a.Name, a.Version) 127 } 128 sort.Strings(debugArtifacts) 129 for i, a := range debugArtifacts { 130 tb.Errorf("package %d: %s", i+1, a) 131 } 132 133 } 134 } 135 } 136 137 func assertFailingReturnCode(tb testing.TB, _, _ string, rc int) { 138 tb.Helper() 139 if rc == 0 { 140 tb.Errorf("expected a failure but got rc=%d", rc) 141 } 142 } 143 144 func assertSuccessfulReturnCode(tb testing.TB, _, _ string, rc int) { 145 tb.Helper() 146 if rc != 0 { 147 tb.Errorf("expected no failure but got rc=%d", rc) 148 } 149 } 150 151 func assertVerifyAttestation(coverageImage string) traitAssertion { 152 return func(tb testing.TB, stdout, _ string, _ int) { 153 tb.Helper() 154 cosignPath := filepath.Join(repoRoot(tb), ".tmp/cosign") 155 err := os.WriteFile("attestation.json", []byte(stdout), 0664) 156 if err != nil { 157 tb.Errorf("could not write attestation to disk") 158 } 159 defer os.Remove("attestation.json") 160 attachCmd := exec.Command( 161 cosignPath, 162 "attach", 163 "attestation", 164 "--attestation", 165 "attestation.json", 166 coverageImage, // TODO which remote image to use? 167 ) 168 169 stdout, stderr, _ := runCommand(attachCmd, nil) 170 if attachCmd.ProcessState.ExitCode() != 0 { 171 tb.Log("STDOUT", stdout) 172 tb.Log("STDERR", stderr) 173 tb.Fatalf("could not attach image") 174 } 175 176 verifyCmd := exec.Command( 177 cosignPath, 178 "verify-attestation", 179 coverageImage, // TODO which remote image to use? 180 ) 181 182 stdout, stderr, _ = runCommand(verifyCmd, nil) 183 if attachCmd.ProcessState.ExitCode() != 0 { 184 tb.Log("STDOUT", stdout) 185 tb.Log("STDERR", stderr) 186 tb.Fatalf("could not verify attestation") 187 } 188 } 189 } 190 191 func assertFileExists(file string) traitAssertion { 192 return func(tb testing.TB, _, _ string, _ int) { 193 tb.Helper() 194 if _, err := os.Stat(file); err != nil { 195 tb.Errorf("expected file to exist %s", file) 196 } 197 } 198 }