Skip to content

Commit 7149880

Browse files
authored
Expose HTTP details of last run (#17)
1 parent 1eb0b7e commit 7149880

File tree

9 files changed

+128
-34
lines changed

9 files changed

+128
-34
lines changed

.github/workflows/golangci-lint.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,28 @@ jobs:
2121
steps:
2222
- uses: actions/setup-go@v3
2323
with:
24-
go-version: 1.22.x
24+
go-version: 1.23.x
2525
- uses: actions/checkout@v2
2626
- name: golangci-lint
27-
uses: golangci/golangci-lint-action@v6.0.1
27+
uses: golangci/golangci-lint-action@v6.1.0
2828
with:
2929
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
30-
version: v1.59.1
30+
version: v1.61.0
31+
32+
# Optional: working directory, useful for monorepos
33+
# working-directory: somedir
34+
35+
# Optional: golangci-lint command line arguments.
36+
# args: --issues-exit-code=0
37+
38+
# Optional: show only new issues if it's a pull request. The default value is `false`.
39+
# only-new-issues: true
40+
41+
# Optional: if set to true then the action will use pre-installed Go.
42+
# skip-go-installation: true
43+
44+
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
45+
# skip-pkg-cache: true
46+
47+
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
48+
# skip-build-cache: true

.github/workflows/gorelease.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ concurrency:
99
cancel-in-progress: true
1010

1111
env:
12-
GO_VERSION: 1.22.x
12+
GO_VERSION: 1.23.x
1313
jobs:
1414
gorelease:
1515
runs-on: ubuntu-latest

.github/workflows/test-unit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
test:
2222
strategy:
2323
matrix:
24-
go-version: [ 1.13.x, 1.21.x, 1.22.x ]
24+
go-version: [ 1.13.x, 1.22.x, 1.23.x ]
2525
runs-on: ubuntu-latest
2626
steps:
2727
- name: Install Go stable

.golangci.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ linters:
3636
- ireturn
3737
- exhaustruct
3838
- nonamedreturns
39-
- structcheck
4039
- testableexamples
4140
- dupword
4241
- depguard
@@ -51,21 +50,23 @@ issues:
5150
- linters:
5251
- staticcheck
5352
path: ".go"
54-
text: "\"io/ioutil\" has been deprecated since Go 1.16" # Keeping backwards compatibility with go1.13.
53+
text: "\"io/ioutil\" has been deprecated since" # Keeping backwards compatibility with go1.13.
5554
- linters:
5655
- gomnd
56+
- mnd
5757
- goconst
58-
- goerr113
5958
- noctx
6059
- funlen
6160
- dupl
6261
- structcheck
6362
- unused
6463
- unparam
65-
- nosnakecase
6664
path: "_test.go"
6765
- linters:
6866
- errcheck # Error checking omitted for brevity.
6967
- gosec
7068
path: "example_"
69+
- linters:
70+
- revive
71+
text: "unused-parameter: parameter"
7172

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#GOLANGCI_LINT_VERSION := "v1.59.1" # Optional configuration to pinpoint golangci-lint version.
1+
#GOLANGCI_LINT_VERSION := "v1.61.0" # Optional configuration to pinpoint golangci-lint version.
22

33
# The head of Makefile determines location of dev-go to include standard targets.
44
GO ?= go

client.go

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ type Client struct {
3737

3838
ctx context.Context //nolint:containedctx // Context is configured separately.
3939

40+
req *http.Request
4041
resp *http.Response
4142
respBody []byte
4243

44+
attempt int
45+
retryDelays []time.Duration
46+
4347
reqHeaders map[string]string
4448
reqCookies map[string]string
4549
reqQueryParams url.Values
@@ -114,6 +118,38 @@ func NewClient(baseURL string) *Client {
114118
return c
115119
}
116120

121+
// HTTPValue contains information about request and response.
122+
type HTTPValue struct {
123+
Req *http.Request
124+
ReqBody []byte
125+
126+
Resp *http.Response
127+
RespBody []byte
128+
129+
OtherResp *http.Response
130+
OtherRespBody []byte
131+
132+
Attempt int
133+
RetryDelays []time.Duration
134+
}
135+
136+
// Details returns HTTP request and response information of last run.
137+
func (c *Client) Details() HTTPValue {
138+
return HTTPValue{
139+
Req: c.req,
140+
ReqBody: c.reqBody,
141+
142+
Resp: c.resp,
143+
RespBody: c.respBody,
144+
145+
OtherResp: c.otherResp,
146+
OtherRespBody: c.otherRespBody,
147+
148+
Attempt: c.attempt,
149+
RetryDelays: c.retryDelays,
150+
}
151+
}
152+
117153
// SetBaseURL changes baseURL configured with constructor.
118154
func (c *Client) SetBaseURL(baseURL string) {
119155
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
@@ -132,6 +168,8 @@ func (c *Client) Reset() *Client {
132168
c.reqQueryParams = map[string][]string{}
133169
c.reqFormDataParams = map[string][]string{}
134170

171+
c.req = nil
172+
135173
c.resp = nil
136174
c.respBody = nil
137175

@@ -147,6 +185,9 @@ func (c *Client) Reset() *Client {
147185
c.otherRespBody = nil
148186
c.otherRespExpected = false
149187

188+
c.attempt = 0
189+
c.retryDelays = nil
190+
150191
return c
151192
}
152193

@@ -158,7 +199,7 @@ func (c *Client) Reset() *Client {
158199
// This method enables context-driven concurrent access to shared base Client.
159200
func (c *Client) Fork(ctx context.Context) (context.Context, *Client) {
160201
// Pointer to current Client is used as context key
161-
// to enable multiple different clients in sam context.
202+
// to enable multiple different clients in same context.
162203
if fc, ok := ctx.Value(c).(*Client); ok {
163204
return ctx, fc
164205
}
@@ -261,7 +302,9 @@ func (c *Client) WithURLEncodedFormDataParam(name, value string) *Client {
261302
return c
262303
}
263304

264-
func (c *Client) do() (err error) {
305+
func (c *Client) do() (err error) { //nolint:funlen
306+
c.attempt++
307+
265308
if c.reqConcurrency < 1 {
266309
c.reqConcurrency = 1
267310
}
@@ -289,7 +332,7 @@ func (c *Client) do() (err error) {
289332
wg.Done()
290333
}()
291334

292-
resp, er := c.doOnce()
335+
req, resp, er := c.doOnce()
293336
if er != nil {
294337
return
295338
}
@@ -305,6 +348,10 @@ func (c *Client) do() (err error) {
305348
}
306349

307350
mu.Lock()
351+
if c.req == nil {
352+
c.req = req
353+
}
354+
308355
if _, ok := statusCodeCount[resp.StatusCode]; !ok {
309356
resps[resp.StatusCode] = resp
310357
bodies[resp.StatusCode] = body
@@ -315,6 +362,7 @@ func (c *Client) do() (err error) {
315362
mu.Unlock()
316363
}()
317364
}
365+
318366
wg.Wait()
319367

320368
if err != nil {
@@ -329,6 +377,14 @@ func (c *Client) expectResp(check func() error) (err error) {
329377
return check()
330378
}
331379

380+
if len(c.reqBody) == 0 && len(c.reqFormDataParams) > 0 {
381+
c.reqBody = []byte(c.reqFormDataParams.Encode())
382+
383+
if c.reqMethod == "" {
384+
c.reqMethod = http.MethodPost
385+
}
386+
}
387+
332388
if c.retryBackOff != nil {
333389
for {
334390
if err = c.do(); err == nil {
@@ -343,6 +399,8 @@ func (c *Client) expectResp(check func() error) (err error) {
343399
return err
344400
}
345401

402+
c.retryDelays = append(c.retryDelays, dur)
403+
346404
time.Sleep(dur)
347405
}
348406
}
@@ -433,15 +491,17 @@ func (c *Client) buildURI() (string, error) {
433491
return uri, nil
434492
}
435493

494+
type readSeekNopCloser struct {
495+
io.ReadSeeker
496+
}
497+
498+
func (r *readSeekNopCloser) Close() error {
499+
return nil
500+
}
501+
436502
func (c *Client) buildBody() io.Reader {
437503
if len(c.reqBody) > 0 {
438-
return bytes.NewBuffer(c.reqBody)
439-
} else if len(c.reqFormDataParams) > 0 {
440-
if c.reqMethod == "" {
441-
c.reqMethod = http.MethodPost
442-
}
443-
444-
return strings.NewReader(c.reqFormDataParams.Encode())
504+
return &readSeekNopCloser{ReadSeeker: bytes.NewReader(c.reqBody)}
445505
}
446506

447507
return nil
@@ -486,17 +546,17 @@ func (c *Client) applyCookies(req *http.Request) {
486546
}
487547
}
488548

489-
func (c *Client) doOnce() (*http.Response, error) {
549+
func (c *Client) doOnce() (*http.Request, *http.Response, error) {
490550
uri, err := c.buildURI()
491551
if err != nil {
492-
return nil, err
552+
return nil, nil, err
493553
}
494554

495555
body := c.buildBody()
496556

497557
req, err := http.NewRequestWithContext(c.ctx, c.reqMethod, uri, body)
498558
if err != nil {
499-
return nil, err
559+
return nil, nil, err
500560
}
501561

502562
c.applyHeaders(req)
@@ -513,10 +573,14 @@ func (c *Client) doOnce() (*http.Response, error) {
513573
cl.Transport = tr
514574
cl.Jar = j
515575

516-
return cl.Do(req)
576+
resp, err := cl.Do(req)
577+
578+
return req, resp, err
517579
}
518580

519-
return tr.RoundTrip(req)
581+
resp, err := tr.RoundTrip(req)
582+
583+
return req, resp, err
520584
}
521585

522586
// ExpectResponseStatus sets expected response status code.

client_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ func TestNewClient(t *testing.T) {
104104
val, found := vars.Get("$var1")
105105
assert.True(t, found)
106106
assert.Equal(t, "abc", val)
107+
108+
details := c.Details()
109+
assert.NotNil(t, details.Req)
110+
assert.NotNil(t, details.Resp)
111+
assert.NotNil(t, details.OtherResp)
112+
113+
assert.Equal(t, http.StatusAccepted, details.Resp.StatusCode)
114+
assert.Equal(t, http.StatusConflict, details.OtherResp.StatusCode)
115+
116+
assert.Equal(t, 1, details.Attempt)
117+
assert.Empty(t, details.RetryDelays)
107118
}
108119

109120
func TestNewClient_failedExpectation(t *testing.T) {

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ module github.com/bool64/httpmock
33
go 1.18
44

55
require (
6-
github.com/bool64/dev v0.2.35
6+
github.com/bool64/dev v0.2.36
77
github.com/bool64/shared v0.1.5
88
github.com/stretchr/testify v1.8.2
9-
github.com/swaggest/assertjson v1.8.1
9+
github.com/swaggest/assertjson v1.9.0
1010
)
1111

1212
require (
1313
github.com/davecgh/go-spew v1.1.1 // indirect
14-
github.com/iancoleman/orderedmap v0.2.0 // indirect
14+
github.com/iancoleman/orderedmap v0.3.0 // indirect
1515
github.com/mattn/go-isatty v0.0.14 // indirect
1616
github.com/nxadm/tail v1.4.8 // indirect
1717
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk=
2-
github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
1+
github.com/bool64/dev v0.2.36 h1:yU3bbOTujoxhWnt8ig8t94PVmZXIkCaRj9C57OtqJBY=
2+
github.com/bool64/dev v0.2.36/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
33
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
44
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
55
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
77
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
99
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
10-
github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
11-
github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
10+
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
11+
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
1212
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
1313
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
1414
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -33,8 +33,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
3333
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
3434
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
3535
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
36-
github.com/swaggest/assertjson v1.8.1 h1:Be2EHY9S2qwKWV+xWZB747Cd7Y79YK6JLdeyrgFvyMo=
37-
github.com/swaggest/assertjson v1.8.1/go.mod h1:/8kNRmDZAZfavS5VeWYtCimLGebn0Ak1/iErFUi+DEM=
36+
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
37+
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
3838
github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff h1:7YqG491bE4vstXRz1lD38rbSgbXnirvROz1lZiOnPO8=
3939
github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
4040
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=

0 commit comments

Comments
 (0)