Skip to content

Commit 65566db

Browse files
authored
fix: exit with non-zero code if the api is not happy (#352)
* fix: exit with a non-zero code if the api is not happy * test: add cases * test: use custom tmp directory * test: normalize localhost:<port> references
1 parent d56cb61 commit 65566db

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed

__snapshots__/main_test.snap

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,44 @@ You must provide at least one path to either a lockfile or a directory containin
5858

5959
---
6060

61+
[TestRun_APIError/#00 - 1]
62+
Loaded the following OSV databases:
63+
api#http://<localhost>:<port> (using batches of 1000)
64+
RubyGems (%% vulnerabilities, including withdrawn - last updated %%)
65+
66+
<tempdir>/Gemfile.lock: found 1 package
67+
Using config at <tempdir>/.osv-detector.yml (0 ignores)
68+
Using db api#http://<localhost>:<port> (using batches of 1000)
69+
Using db RubyGems (%% vulnerabilities, including withdrawn - last updated %%)
70+
71+
no known vulnerabilities found
72+
73+
---
74+
75+
[TestRun_APIError/#00 - 2]
76+
an api error occurred while trying to check the packages listed in <tempdir>/Gemfile.lock: api returned unexpected status (POST http://<localhost>:<port>/querybatch 400)
77+
78+
---
79+
80+
[TestRun_APIError/#01 - 1]
81+
Loaded the following OSV databases:
82+
api#http://<localhost>:<port> (using batches of 1000)
83+
RubyGems (%% vulnerabilities, including withdrawn - last updated %%)
84+
85+
<tempdir>/Gemfile.lock: found 1 package
86+
Using config at <tempdir>/.osv-detector.yml (0 ignores)
87+
Using db api#http://<localhost>:<port> (using batches of 1000)
88+
Using db RubyGems (%% vulnerabilities, including withdrawn - last updated %%)
89+
90+
no known vulnerabilities found
91+
92+
---
93+
94+
[TestRun_APIError/#01 - 2]
95+
an api error occurred while trying to check the packages listed in <tempdir>/Gemfile.lock: api response could not be parsed as json (POST http://<localhost>:<port>/querybatch): invalid character '<' looking for beginning of value
96+
97+
---
98+
6199
[TestRun_Configs/#00 - 1]
62100
Loaded the following OSV databases:
63101

internal/reporter/reporter.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type Reporter struct {
1616
stderr io.Writer
1717
outputAsJSON bool
1818
results []Result
19+
20+
hasErrored bool
1921
}
2022

2123
func New(stdout io.Writer, stderr io.Writer, outputAsJSON bool) *Reporter {
@@ -27,9 +29,15 @@ func New(stdout io.Writer, stderr io.Writer, outputAsJSON bool) *Reporter {
2729
}
2830
}
2931

32+
func (r *Reporter) HasErrored() bool {
33+
return r.hasErrored
34+
}
35+
3036
// PrintErrorf writes the given message to stderr, regardless of if the reporter
3137
// is outputting as JSON or not
3238
func (r *Reporter) PrintErrorf(msg string, a ...any) {
39+
r.hasErrored = true
40+
3341
fmt.Fprintf(r.stderr, msg, a...)
3442
}
3543

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,10 @@ This flag can be passed multiple times to ignore different vulnerabilities`)
733733
writeUpdatedConfigs(r, vulnsPerConfig)
734734
}
735735

736+
if r.HasErrored() && exitCode == 0 {
737+
exitCode = 127
738+
}
739+
736740
return exitCode
737741
}
738742

main_normalize_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ func normalizeDatabaseStats(t *testing.T, str string) string {
8989
return re.ReplaceAllString(str, "$1 (%% vulnerabilities, including withdrawn - last updated %%)")
9090
}
9191

92+
// normalizeLocalhostPort attempts to replace references to 127.0.0.1:<port>
93+
// with a placeholder, to ensure tests pass when using httptest.Server
94+
func normalizeLocalhostPort(t *testing.T, str string) string {
95+
t.Helper()
96+
97+
re := cachedregexp.MustCompile(`127\.0\.0\.1:\d+`)
98+
99+
return re.ReplaceAllString(str, "<localhost>:<port>")
100+
}
101+
92102
// normalizeErrors attempts to replace error messages on alternative OSs with their
93103
// known linux equivalents, to ensure tests pass across different OSs
94104
func normalizeErrors(t *testing.T, str string) string {
@@ -110,6 +120,7 @@ func normalizeSnapshot(t *testing.T, str string) string {
110120
normalizeTempDirectory,
111121
normalizeUserCacheDirectory,
112122
normalizeDatabaseStats,
123+
normalizeLocalhostPort,
113124
normalizeErrors,
114125
} {
115126
str = normalizer(t, str)

main_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package main
33
import (
44
"bytes"
55
"fmt"
6+
"net/http"
7+
"net/http/httptest"
68
"os"
79
"path/filepath"
810
"strings"
@@ -883,3 +885,70 @@ func TestRun_EndToEnd(t *testing.T) {
883885
})
884886
}
885887
}
888+
889+
func TestRun_APIError(t *testing.T) {
890+
t.Parallel()
891+
892+
tests := []struct{ handler http.HandlerFunc }{
893+
{
894+
handler: func(w http.ResponseWriter, _ *http.Request) {
895+
w.WriteHeader(http.StatusBadRequest)
896+
_, _ = w.Write([]byte("{}"))
897+
},
898+
},
899+
{
900+
handler: func(w http.ResponseWriter, _ *http.Request) {
901+
_, _ = w.Write([]byte("<html></html>"))
902+
},
903+
},
904+
}
905+
for _, tt := range tests {
906+
t.Run("", func(t *testing.T) {
907+
t.Parallel()
908+
909+
//nolint:usetesting // we need to customize the directory name to replace in snapshots
910+
p, err := os.MkdirTemp("", "osv-detector-test-*")
911+
if err != nil {
912+
t.Fatalf("could not create test directory: %v", err)
913+
}
914+
915+
// ensure the test directory is removed when we're done testing
916+
t.Cleanup(func() {
917+
_ = os.RemoveAll(p)
918+
})
919+
920+
// create a file for scanning
921+
err = os.WriteFile(filepath.Join(p, "Gemfile.lock"), []byte(`
922+
GEM
923+
remote: https://rubygems.org/
924+
specs:
925+
ast (2.4.2)
926+
`), 0600)
927+
928+
if err != nil {
929+
t.Fatal(err)
930+
}
931+
932+
// setup a fake api server
933+
ts := httptest.NewServer(tt.handler)
934+
t.Cleanup(ts.Close)
935+
936+
// create a config file setting up our api server
937+
err = os.WriteFile(filepath.Join(p, ".osv-detector.yml"), []byte(`
938+
extra-databases:
939+
- url: `+ts.URL,
940+
), 0600)
941+
942+
if err != nil {
943+
t.Fatal(err)
944+
}
945+
946+
// run the cli in our tmp directory
947+
testCli(t, cliTestCase{
948+
name: "",
949+
args: []string{p},
950+
exit: 127,
951+
})
952+
})
953+
}
954+
}

0 commit comments

Comments
 (0)