Skip to content

Commit fdffdb6

Browse files
add some tests
1 parent 34f3de8 commit fdffdb6

4 files changed

Lines changed: 482 additions & 0 deletions

File tree

checks/http_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package checks
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"strings"
9+
"testing"
10+
11+
api "github.com/bootdotdev/bootdev/client"
12+
)
13+
14+
func TestInterpolateVariables(t *testing.T) {
15+
got := InterpolateVariables(
16+
"${baseURL}/users/${id}?missing=${missing}",
17+
map[string]string{"baseURL": "http://localhost:8080", "id": "42"},
18+
)
19+
want := "http://localhost:8080/users/42?missing=${missing}"
20+
if got != want {
21+
t.Fatalf("InterpolateVariables() = %q, want %q", got, want)
22+
}
23+
}
24+
25+
func TestInterpolationNames(t *testing.T) {
26+
got := InterpolationNames("${baseURL}/users/${id}/${id}")
27+
want := []string{"baseURL", "id", "id"}
28+
if len(got) != len(want) {
29+
t.Fatalf("InterpolationNames() = %#v, want %#v", got, want)
30+
}
31+
for i := range want {
32+
if got[i] != want[i] {
33+
t.Fatalf("InterpolationNames() = %#v, want %#v", got, want)
34+
}
35+
}
36+
}
37+
38+
func TestRunHTTPRequestInterpolatesRequestAndCapturesResponseVariables(t *testing.T) {
39+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40+
if r.Method != http.MethodPost {
41+
t.Errorf("method = %q, want %q", r.Method, http.MethodPost)
42+
return
43+
}
44+
if r.URL.Path != "/users/42" {
45+
t.Errorf("path = %q, want %q", r.URL.Path, "/users/42")
46+
return
47+
}
48+
if r.Header.Get("X-User-ID") != "42" {
49+
t.Errorf("X-User-ID = %q, want %q", r.Header.Get("X-User-ID"), "42")
50+
return
51+
}
52+
53+
username, password, ok := r.BasicAuth()
54+
if !ok || username != "user" || password != "pass" {
55+
t.Errorf("BasicAuth() = %q, %q, %v; want user, pass, true", username, password, ok)
56+
return
57+
}
58+
59+
body, err := io.ReadAll(r.Body)
60+
if err != nil {
61+
t.Errorf("failed reading request body: %v", err)
62+
return
63+
}
64+
var payload map[string]string
65+
if err := json.Unmarshal(body, &payload); err != nil {
66+
t.Errorf("failed unmarshalling request body %q: %v", string(body), err)
67+
return
68+
}
69+
if payload["message"] != "hello Theo" {
70+
t.Errorf("message = %q, want %q", payload["message"], "hello Theo")
71+
return
72+
}
73+
74+
w.Header().Set("X-Request-OK", "yes")
75+
w.WriteHeader(http.StatusCreated)
76+
_, _ = w.Write([]byte(`{"token":"abc123"}`))
77+
}))
78+
defer server.Close()
79+
80+
variables := map[string]string{
81+
"id": "42",
82+
"name": "Theo",
83+
}
84+
requestStep := api.CLIStepHTTPRequest{
85+
ResponseVariables: []api.HTTPRequestResponseVariable{{Name: "token", Path: ".token"}},
86+
Request: api.HTTPRequest{
87+
Method: http.MethodPost,
88+
FullURL: api.BaseURLPlaceholder + "/users/${id}",
89+
Headers: map[string]string{
90+
"X-User-ID": "${id}",
91+
},
92+
BodyJSON: map[string]any{
93+
"message": "hello ${name}",
94+
},
95+
BasicAuth: &api.HTTPBasicAuth{Username: "user", Password: "pass"},
96+
},
97+
}
98+
99+
result := runHTTPRequest(server.Client(), server.URL, variables, requestStep)
100+
if result.Err != "" {
101+
t.Fatalf("unexpected request error: %s", result.Err)
102+
}
103+
if result.StatusCode != http.StatusCreated {
104+
t.Fatalf("StatusCode = %d, want %d", result.StatusCode, http.StatusCreated)
105+
}
106+
if result.ResponseHeaders["X-Request-Ok"] != "yes" {
107+
t.Fatalf("ResponseHeaders[X-Request-Ok] = %q, want %q", result.ResponseHeaders["X-Request-Ok"], "yes")
108+
}
109+
if result.BodyString != `{"token":"abc123"}` {
110+
t.Fatalf("BodyString = %q, want token response", result.BodyString)
111+
}
112+
if result.Variables["token"] != "abc123" {
113+
t.Fatalf("captured token = %q, want %q", result.Variables["token"], "abc123")
114+
}
115+
if result.Variables["id"] != "42" {
116+
t.Fatalf("original variable id = %q, want %q", result.Variables["id"], "42")
117+
}
118+
}
119+
120+
func TestTruncateAndStringifyBodyCapsBinaryBody(t *testing.T) {
121+
body := []byte(strings.Repeat("a", 20*1024))
122+
body[0] = 0
123+
124+
got := truncateAndStringifyBody(body)
125+
if len(got) != 16*1024 {
126+
t.Fatalf("len(truncateAndStringifyBody(binary)) = %d, want %d", len(got), 16*1024)
127+
}
128+
}

checks/jq_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package checks
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
"testing"
7+
8+
api "github.com/bootdotdev/bootdev/client"
9+
)
10+
11+
func TestRunStdoutJqQuery(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
stdout string
15+
test api.StdoutJqTest
16+
variables map[string]string
17+
want api.CLICommandJqOutput
18+
wantError string
19+
}{
20+
{
21+
name: "queries json with interpolated query",
22+
stdout: `{"users":[{"name":"Lane"},{"name":"Theo"}]}`,
23+
test: api.StdoutJqTest{
24+
InputMode: "json",
25+
Query: `.users[] | select(.name == "${name}") | .name`,
26+
},
27+
variables: map[string]string{"name": "Theo"},
28+
want: api.CLICommandJqOutput{
29+
Query: `.users[] | select(.name == "Theo") | .name`,
30+
Results: []string{`"Theo"`},
31+
},
32+
},
33+
{
34+
name: "queries jsonl as array",
35+
stdout: "{\"id\":1}\n{\"id\":2}\n",
36+
test: api.StdoutJqTest{
37+
InputMode: "jsonl",
38+
Query: `.[].id`,
39+
},
40+
want: api.CLICommandJqOutput{
41+
Query: `.[].id`,
42+
Results: []string{`1`, `2`},
43+
},
44+
},
45+
{
46+
name: "returns parse error",
47+
stdout: `{not json}`,
48+
test: api.StdoutJqTest{
49+
InputMode: "json",
50+
Query: `.name`,
51+
},
52+
want: api.CLICommandJqOutput{
53+
Query: `.name`,
54+
},
55+
wantError: "invalid character",
56+
},
57+
{
58+
name: "returns jq error",
59+
stdout: `{"name":"Theo"}`,
60+
test: api.StdoutJqTest{
61+
InputMode: "json",
62+
Query: `.name[`,
63+
},
64+
want: api.CLICommandJqOutput{
65+
Query: `.name[`,
66+
Error: "unexpected EOF",
67+
},
68+
},
69+
}
70+
71+
for _, tt := range tests {
72+
t.Run(tt.name, func(t *testing.T) {
73+
got := runStdoutJqQuery(tt.stdout, tt.test, tt.variables)
74+
if tt.wantError != "" {
75+
if got.Query != tt.want.Query {
76+
t.Fatalf("Query = %q, want %q", got.Query, tt.want.Query)
77+
}
78+
if !strings.Contains(got.Error, tt.wantError) {
79+
t.Fatalf("expected error containing %q, got %q", tt.wantError, got.Error)
80+
}
81+
return
82+
}
83+
if !reflect.DeepEqual(got, tt.want) {
84+
t.Fatalf("runStdoutJqQuery() = %#v, want %#v", got, tt.want)
85+
}
86+
})
87+
}
88+
}
89+
90+
func TestParseJqInputRejectsMultipleJSONValuesInJSONMode(t *testing.T) {
91+
_, err := parseJqInput("{\"id\":1}\n{\"id\":2}\n", "json")
92+
if err == nil {
93+
t.Fatal("expected error for multiple JSON values in json mode")
94+
}
95+
if err.Error() != "expected a single JSON value" {
96+
t.Fatalf("expected single-value error, got %q", err.Error())
97+
}
98+
}
99+
100+
func TestFormatJqResults(t *testing.T) {
101+
got := formatJqResults([]any{"hello", float64(42), true, nil, map[string]any{"id": float64(1)}})
102+
want := []string{`"hello"`, `42`, `true`, `null`, `{"id":1}`}
103+
if !reflect.DeepEqual(got, want) {
104+
t.Fatalf("formatJqResults() = %#v, want %#v", got, want)
105+
}
106+
}
107+
108+
func TestFormatJqExpectedValueInterpolatesOnlyStrings(t *testing.T) {
109+
variables := map[string]string{"name": "Theo"}
110+
111+
gotString := formatJqExpectedValue(api.JqExpectedResult{
112+
Type: api.JqTypeString,
113+
Value: "hello ${name}",
114+
}, variables)
115+
if gotString != `"hello Theo"` {
116+
t.Fatalf("expected interpolated string value, got %q", gotString)
117+
}
118+
119+
gotInt := formatJqExpectedValue(api.JqExpectedResult{
120+
Type: api.JqTypeInt,
121+
Value: "${name}",
122+
}, variables)
123+
if gotInt != `"${name}"` {
124+
t.Fatalf("expected non-string jq type to avoid interpolation, got %q", gotInt)
125+
}
126+
}
127+
128+
func TestValFromJqPath(t *testing.T) {
129+
tests := []struct {
130+
name string
131+
path string
132+
jsn string
133+
want any
134+
wantErr string
135+
}{
136+
{
137+
name: "returns one value",
138+
path: `.token`,
139+
jsn: `{"token":"abc123"}`,
140+
want: "abc123",
141+
},
142+
{
143+
name: "errors on missing value",
144+
path: `.missing`,
145+
jsn: `{"token":"abc123"}`,
146+
wantErr: "value not found",
147+
},
148+
{
149+
name: "errors on multiple values",
150+
path: `.items[].id`,
151+
jsn: `{"items":[{"id":1},{"id":2}]}`,
152+
wantErr: "invalid number of values found",
153+
},
154+
}
155+
156+
for _, tt := range tests {
157+
t.Run(tt.name, func(t *testing.T) {
158+
got, err := valFromJqPath(tt.path, tt.jsn)
159+
if tt.wantErr != "" {
160+
if err == nil {
161+
t.Fatalf("expected error %q", tt.wantErr)
162+
}
163+
if err.Error() != tt.wantErr {
164+
t.Fatalf("expected error %q, got %q", tt.wantErr, err.Error())
165+
}
166+
return
167+
}
168+
if err != nil {
169+
t.Fatalf("unexpected error: %v", err)
170+
}
171+
if !reflect.DeepEqual(got, tt.want) {
172+
t.Fatalf("valFromJqPath() = %#v, want %#v", got, tt.want)
173+
}
174+
})
175+
}
176+
}

0 commit comments

Comments
 (0)