Skip to content

Commit 7cfd78b

Browse files
authored
fix: httpassert - unify accepted JSON paths for arrays (#293)
So far `JsonTemplate` and `JsonContentTemplate` did not accept e.g. `$[0].field`, whereas e.g. `JsonPath` did.
1 parent 90acb2a commit 7cfd78b

4 files changed

Lines changed: 62 additions & 14 deletions

File tree

pkg/httpassert/request.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"net/http"
1212
"net/http/httptest"
1313
"os"
14-
"strings"
1514
"testing"
1615
"time"
1716

@@ -244,10 +243,7 @@ func (m *request) JsonContentTemplate(body string, values map[string]any) Reques
244243
jsonBody := body
245244
// apply provided values into the template
246245
for k, v := range values {
247-
// normalize JSONPath-like keys (convert $.a[0].b to a.0.b)
248-
key := strings.TrimPrefix(k, "$.")
249-
key = strings.ReplaceAll(key, "[", ".")
250-
key = strings.ReplaceAll(key, "]", "")
246+
key := normalizeJSONPath(k)
251247

252248
if !gjson.Get(jsonBody, key).Exists() {
253249
assert.Fail(m.t, "Json key does not exist in template: "+k)

pkg/httpassert/request_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func TestRequest(t *testing.T) {
157157
StatusCode(http.StatusOK)
158158
})
159159

160-
t.Run("JSON content template", func(t *testing.T) {
160+
t.Run("JSON content template with object", func(t *testing.T) {
161161
var content []byte
162162
router := http.NewServeMux()
163163
router.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
@@ -182,6 +182,32 @@ func TestRequest(t *testing.T) {
182182

183183
AssertJSONCanonicalEq(t, `{"n": {"foo": "bar","asd":"123"}}`, string(content))
184184
})
185+
186+
t.Run("JSON content template with array", func(t *testing.T) {
187+
var content []byte
188+
router := http.NewServeMux()
189+
router.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
190+
bodyBytes, err := io.ReadAll(r.Body)
191+
require.NoError(t, err)
192+
content = bodyBytes
193+
w.WriteHeader(http.StatusOK)
194+
})
195+
196+
New(t, router).
197+
Post("/json").
198+
JsonContentTemplate(`[{
199+
"n": {
200+
"foo": "bar",
201+
"asd": ""
202+
}
203+
}]`, map[string]any{
204+
"$[0].n.asd": "123",
205+
}).
206+
Expect().
207+
StatusCode(http.StatusOK)
208+
209+
AssertJSONCanonicalEq(t, `[{"n": {"foo": "bar","asd":"123"}}]`, string(content))
210+
})
185211
}
186212

187213
func TestExpectEventually(t *testing.T) {

pkg/httpassert/response.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,7 @@ func (r *responseImpl) JsonTemplate(expectedJsonTemplate string, values map[stri
140140

141141
// apply provided values into the template
142142
for k, v := range values {
143-
// normalize JSONPath-like keys (convert $.a[0].b to a.0.b)
144-
key := strings.TrimPrefix(k, "$.")
145-
key = strings.ReplaceAll(key, "[", ".")
146-
key = strings.ReplaceAll(key, "]", "")
143+
key := normalizeJSONPath(k)
147144

148145
if !gjson.Get(expectedJson, key).Exists() {
149146
assert.Fail(r.t, "Json key does not exist in template: "+k)
@@ -164,9 +161,7 @@ func (r *responseImpl) JsonTemplate(expectedJsonTemplate string, values map[stri
164161
continue
165162
}
166163

167-
key := strings.TrimPrefix(k, "$.")
168-
key = strings.ReplaceAll(key, "[", ".")
169-
key = strings.ReplaceAll(key, "]", "")
164+
key := normalizeJSONPath(k)
170165

171166
if !gjson.Get(actual, key).Exists() {
172167
assert.Fail(r.t, "Json key does not exist in template: "+k)
@@ -235,3 +230,14 @@ func (r *responseImpl) Log() Response {
235230
r.response.Body)
236231
return r
237232
}
233+
234+
// normalizeJSONPath converts jsonpath syntax to gjson/sjson syntax.
235+
// E.g. convert $.a[0].b to a.0.b and $[0].a to 0.a
236+
// This allows a unified path syntax to the caller, regardless which library is used internally.
237+
func normalizeJSONPath(path string) string {
238+
path = strings.ReplaceAll(path, "[", ".")
239+
path = strings.ReplaceAll(path, "]", "")
240+
path = strings.TrimPrefix(path, "$.")
241+
242+
return path
243+
}

pkg/httpassert/response_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestResponse(t *testing.T) {
2424
JsonFile(path)
2525
})
2626

27-
t.Run("JsonTemplate value replacement and ignore handling", func(t *testing.T) {
27+
t.Run("JsonTemplate value replacement and ignore handling with object", func(t *testing.T) {
2828
router := http.NewServeMux()
2929
router.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
3030
w.Header().Set("Content-Type", "application/json")
@@ -77,6 +77,26 @@ func TestResponse(t *testing.T) {
7777
})
7878
})
7979

80+
t.Run("JsonTemplate value replacement with array", func(t *testing.T) {
81+
router := http.NewServeMux()
82+
router.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
83+
w.Header().Set("Content-Type", "application/json")
84+
w.WriteHeader(http.StatusOK)
85+
_, err := fmt.Fprint(w, `[{"id":42,"name":"Greenbone","version":"1.0.0","tags":["alpha","beta"]}]`)
86+
require.NoError(t, err)
87+
})
88+
89+
New(t, router).Get("/api").
90+
Expect().
91+
JsonTemplate(`[{"id":0,"name":"","version":"","tags":["x","y"]}]`, map[string]any{
92+
"$[0].id": 42,
93+
"$[0].name": "Greenbone",
94+
"$[0].tags[0]": "alpha",
95+
"$[0].tags[1]": "beta",
96+
"$[0].version": "1.0.0",
97+
})
98+
})
99+
80100
t.Run("GetBody returns non-empty", func(t *testing.T) {
81101
body := request.Post("/json").
82102
Expect().

0 commit comments

Comments
 (0)