Skip to content

Commit af554f9

Browse files
Cédric Rousselmaxatome
Cédric Roussel
authored andcommitted
feat(tdhttp): allow usage of tdhttp.Q when using PostForm helpers
1 parent 34efbcd commit af554f9

File tree

5 files changed

+80
-6
lines changed

5 files changed

+80
-6
lines changed

helpers/tdhttp/q.go

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import (
3737
// - pointer on any type above, plus any or any other pointer
3838
type Q map[string]any
3939

40+
var _ URLValuesEncoder = Q(nil)
41+
4042
// AddTo adds the q contents to qp.
4143
func (q Q) AddTo(qp url.Values) error {
4244
for param, value := range q {

helpers/tdhttp/request.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,14 @@ func post(target string, body io.Reader, headersQueryParams ...any) (*http.Reque
141141
return newRequest(http.MethodPost, target, body, headersQueryParams)
142142
}
143143

144-
func postForm(target string, data url.Values, headersQueryParams ...any) (*http.Request, error) {
144+
func postForm(target string, data URLValuesEncoder, headersQueryParams ...any) (*http.Request, error) {
145+
var body string
146+
if data != nil {
147+
body = data.Encode()
148+
}
149+
145150
return newRequest(
146-
http.MethodPost, target, strings.NewReader(data.Encode()),
151+
http.MethodPost, target, strings.NewReader(body),
147152
append(headersQueryParams, "Content-Type", "application/x-www-form-urlencoded"),
148153
)
149154
}
@@ -336,6 +341,16 @@ func Post(target string, body io.Reader, headersQueryParams ...any) *http.Reques
336341
return req
337342
}
338343

344+
// URLValuesEncoder is an interface [PostForm] and [TestAPI.PostForm] data
345+
// must implement.
346+
// Encode can be called to generate a "URL encoded" form such as
347+
// ("bar=baz&foo=quux") sorted by key.
348+
//
349+
// [url.Values] and [Q] implement this interface.
350+
type URLValuesEncoder interface {
351+
Encode() string
352+
}
353+
339354
// PostForm creates a HTTP POST with data's keys and values
340355
// URL-encoded as the request body. "Content-Type" header is
341356
// automatically set to "application/x-www-form-urlencoded". Other
@@ -351,7 +366,7 @@ func Post(target string, body io.Reader, headersQueryParams ...any) *http.Reques
351366
// )
352367
//
353368
// See [NewRequest] for all possible formats accepted in headersQueryParams.
354-
func PostForm(target string, data url.Values, headersQueryParams ...any) *http.Request {
369+
func PostForm(target string, data URLValuesEncoder, headersQueryParams ...any) *http.Request {
355370
req, err := postForm(target, data, headersQueryParams...)
356371
if err != nil {
357372
panic(err)

helpers/tdhttp/request_test.go

+44-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func TestNewRequest(tt *testing.T) {
232232
"URL": td.String("/path"),
233233
}))
234234

235-
// PostForm
235+
// PostForm - url.Values
236236
t.Cmp(
237237
tdhttp.PostForm("/path",
238238
url.Values{
@@ -256,6 +256,49 @@ func TestNewRequest(tt *testing.T) {
256256
),
257257
}))
258258

259+
// PostForm - td.Q
260+
t.Cmp(
261+
tdhttp.PostForm("/path",
262+
tdhttp.Q{
263+
"param1": "val1",
264+
"param2": "val2",
265+
},
266+
"Foo", "Bar"),
267+
td.Struct(
268+
&http.Request{
269+
Method: "POST",
270+
Header: http.Header{
271+
"Content-Type": []string{"application/x-www-form-urlencoded"},
272+
"Foo": []string{"Bar"},
273+
},
274+
},
275+
td.StructFields{
276+
"URL": td.String("/path"),
277+
"Body": td.Smuggle(
278+
io.ReadAll,
279+
[]byte("param1=val1&param2=val2"),
280+
),
281+
}))
282+
283+
// PostForm - nil data
284+
t.Cmp(
285+
tdhttp.PostForm("/path", nil, "Foo", "Bar"),
286+
td.Struct(
287+
&http.Request{
288+
Method: "POST",
289+
Header: http.Header{
290+
"Content-Type": []string{"application/x-www-form-urlencoded"},
291+
"Foo": []string{"Bar"},
292+
},
293+
},
294+
td.StructFields{
295+
"URL": td.String("/path"),
296+
"Body": td.Smuggle(
297+
io.ReadAll,
298+
[]byte{},
299+
),
300+
}))
301+
259302
// PostMultipartFormData
260303
req := tdhttp.PostMultipartFormData("/path",
261304
&tdhttp.MultipartBody{

helpers/tdhttp/test_api.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"io"
1414
"net/http"
1515
"net/http/httptest"
16-
"net/url"
1716
"reflect"
1817
"runtime"
1918
"strings"
@@ -262,7 +261,7 @@ func (ta *TestAPI) Post(target string, body io.Reader, headersQueryParams ...any
262261
// Note that [TestAPI.Failed] status is reset just after this call.
263262
//
264263
// See [NewRequest] for all possible formats accepted in headersQueryParams.
265-
func (ta *TestAPI) PostForm(target string, data url.Values, headersQueryParams ...any) *TestAPI {
264+
func (ta *TestAPI) PostForm(target string, data URLValuesEncoder, headersQueryParams ...any) *TestAPI {
266265
ta.t.Helper()
267266
req, err := postForm(target, data, headersQueryParams...)
268267
if err != nil {

helpers/tdhttp/test_api_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,21 @@ func TestNewTestAPI(t *testing.T) {
248248
Failed())
249249
td.CmpEmpty(t, mockT.LogBuf())
250250

251+
mockT = tdutil.NewT("test")
252+
td.CmpFalse(t,
253+
tdhttp.NewTestAPI(mockT, mux).
254+
PostForm("/any", tdhttp.Q{"p1": "v1", "p2": "v2"}).
255+
CmpStatus(200).
256+
CmpHeader(containsKey).
257+
CmpBody("POST!\n---\np1=v1&p2=v2").
258+
CmpResponse(td.Code(func(assert *td.T, resp *http.Response) {
259+
assert.Cmp(resp.StatusCode, 200)
260+
assert.Cmp(resp.Header, containsKey)
261+
assert.Smuggle(resp.Body, io.ReadAll, td.String("POST!\n---\np1=v1&p2=v2"))
262+
})).
263+
Failed())
264+
td.CmpEmpty(t, mockT.LogBuf())
265+
251266
mockT = tdutil.NewT("test")
252267
td.CmpFalse(t,
253268
tdhttp.NewTestAPI(mockT, mux).

0 commit comments

Comments
 (0)