Make the mocked responses match the API closer;
- Add headers as returned by the daemon's VersionMiddleware
- By default handle "/_ping" requests to allow the client to
perform API-version negotiation as part of tests.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -5,9 +5,11 @@ import ( |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"io" |
| 7 | 7 |
"net/http" |
| 8 |
+ "runtime" |
|
| 8 | 9 |
"strconv" |
| 9 | 10 |
"strings" |
| 10 | 11 |
|
| 12 |
+ "github.com/moby/moby/api/types/build" |
|
| 11 | 13 |
"github.com/moby/moby/api/types/common" |
| 12 | 14 |
"github.com/moby/moby/api/types/swarm" |
| 13 | 15 |
) |
| ... | ... |
@@ -60,9 +62,71 @@ func ensureBody(f func(req *http.Request) (*http.Response, error)) testRoundTrip |
| 60 | 60 |
} |
| 61 | 61 |
} |
| 62 | 62 |
|
| 63 |
-// WithMockClient is a test helper that allows you to inject a mock client for testing. |
|
| 63 |
+// makeTestRoundTripper makes sure the response has a Body, using [http.NoBody] if |
|
| 64 |
+// none is present, and returns it as a testRoundTripper. If withDefaults is set, |
|
| 65 |
+// it also mocks the "/_ping" endpoint and sets default headers as returned |
|
| 66 |
+// by the daemon. |
|
| 67 |
+func makeTestRoundTripper(f func(req *http.Request) (*http.Response, error)) testRoundTripper {
|
|
| 68 |
+ return func(req *http.Request) (*http.Response, error) {
|
|
| 69 |
+ if req.URL.Path == "/_ping" {
|
|
| 70 |
+ return mockPingResponse(http.StatusOK, PingResult{
|
|
| 71 |
+ APIVersion: MaxAPIVersion, |
|
| 72 |
+ OSType: runtime.GOOS, |
|
| 73 |
+ Experimental: true, |
|
| 74 |
+ BuilderVersion: build.BuilderBuildKit, |
|
| 75 |
+ SwarmStatus: &SwarmStatus{
|
|
| 76 |
+ NodeState: swarm.LocalNodeStateActive, |
|
| 77 |
+ ControlAvailable: true, |
|
| 78 |
+ }, |
|
| 79 |
+ })(req) |
|
| 80 |
+ } |
|
| 81 |
+ resp, err := f(req) |
|
| 82 |
+ if resp != nil {
|
|
| 83 |
+ if resp.Body == nil {
|
|
| 84 |
+ resp.Body = http.NoBody |
|
| 85 |
+ } |
|
| 86 |
+ if resp.Request == nil {
|
|
| 87 |
+ resp.Request = req |
|
| 88 |
+ } |
|
| 89 |
+ } |
|
| 90 |
+ applyDefaultHeaders(resp) |
|
| 91 |
+ return resp, err |
|
| 92 |
+ } |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// applyDefaultHeaders mocks the headers set by the daemon's VersionMiddleware. |
|
| 96 |
+func applyDefaultHeaders(resp *http.Response) {
|
|
| 97 |
+ if resp == nil {
|
|
| 98 |
+ return |
|
| 99 |
+ } |
|
| 100 |
+ if resp.Header == nil {
|
|
| 101 |
+ resp.Header = make(http.Header) |
|
| 102 |
+ } |
|
| 103 |
+ if resp.Header.Get("Server") == "" {
|
|
| 104 |
+ resp.Header.Set("Server", fmt.Sprintf("Docker/%s (%s)", "v99.99.99", runtime.GOOS))
|
|
| 105 |
+ } |
|
| 106 |
+ if resp.Header.Get("Api-Version") == "" {
|
|
| 107 |
+ resp.Header.Set("Api-Version", MaxAPIVersion)
|
|
| 108 |
+ } |
|
| 109 |
+ if resp.Header.Get("Ostype") == "" {
|
|
| 110 |
+ resp.Header.Set("Ostype", runtime.GOOS)
|
|
| 111 |
+ } |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// WithMockClient is a test helper that allows you to inject a mock client for |
|
| 115 |
+// testing. By default, it mocks the "/_ping" endpoint, so allow the client |
|
| 116 |
+// to perform API-version negotiation. Other endpoints are handled by "doer". |
|
| 64 | 117 |
func WithMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
|
| 65 | 118 |
return WithHTTPClient(&http.Client{
|
| 119 |
+ Transport: makeTestRoundTripper(doer), |
|
| 120 |
+ }) |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+// WithBaseMockClient is a test helper that allows you to inject a mock client |
|
| 124 |
+// for testing. It is identical to [WithMockClient], but does not mock the "/_ping" |
|
| 125 |
+// endpoint, and doesn't set the default headers. |
|
| 126 |
+func WithBaseMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
|
|
| 127 |
+ return WithHTTPClient(&http.Client{
|
|
| 66 | 128 |
Transport: ensureBody(doer), |
| 67 | 129 |
}) |
| 68 | 130 |
} |
| ... | ... |
@@ -258,7 +258,7 @@ func TestNegotiateAPIVersionEmpty(t *testing.T) {
|
| 258 | 258 |
|
| 259 | 259 |
client, err := New(FromEnv, |
| 260 | 260 |
WithAPIVersionNegotiation(), |
| 261 |
- WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: expected})),
|
|
| 261 |
+ WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: expected})),
|
|
| 262 | 262 |
) |
| 263 | 263 |
assert.NilError(t, err) |
| 264 | 264 |
|
| ... | ... |
@@ -331,7 +331,7 @@ func TestNegotiateAPIVersion(t *testing.T) {
|
| 331 | 331 |
opts := []Opt{
|
| 332 | 332 |
FromEnv, |
| 333 | 333 |
WithAPIVersionNegotiation(), |
| 334 |
- WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: tc.pingVersion})),
|
|
| 334 |
+ WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: tc.pingVersion})),
|
|
| 335 | 335 |
} |
| 336 | 336 |
|
| 337 | 337 |
if tc.clientVersion != "" {
|
| ... | ... |
@@ -363,7 +363,7 @@ func TestNegotiateAPIVersionOverride(t *testing.T) {
|
| 363 | 363 |
|
| 364 | 364 |
client, err := New( |
| 365 | 365 |
FromEnv, |
| 366 |
- WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.45"})),
|
|
| 366 |
+ WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.45"})),
|
|
| 367 | 367 |
) |
| 368 | 368 |
assert.NilError(t, err) |
| 369 | 369 |
|
| ... | ... |
@@ -393,7 +393,7 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
|
| 393 | 393 |
|
| 394 | 394 |
ctx := t.Context() |
| 395 | 395 |
client, err := New( |
| 396 |
- WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 396 |
+ WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 397 | 397 |
return mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})(req)
|
| 398 | 398 |
}), |
| 399 | 399 |
WithAPIVersionNegotiation(), |
| ... | ... |
@@ -422,7 +422,7 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
|
| 422 | 422 |
func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
|
| 423 | 423 |
client, err := New( |
| 424 | 424 |
WithAPIVersion(""),
|
| 425 |
- WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.50"})),
|
|
| 425 |
+ WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.50"})),
|
|
| 426 | 426 |
) |
| 427 | 427 |
assert.NilError(t, err) |
| 428 | 428 |
|
| ... | ... |
@@ -442,7 +442,7 @@ func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) {
|
| 442 | 442 |
) |
| 443 | 443 |
client, err := New( |
| 444 | 444 |
WithAPIVersion(customVersion), |
| 445 |
- WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})),
|
|
| 445 |
+ WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})),
|
|
| 446 | 446 |
) |
| 447 | 447 |
assert.NilError(t, err) |
| 448 | 448 |
|
| ... | ... |
@@ -18,7 +18,7 @@ import ( |
| 18 | 18 |
// panics. |
| 19 | 19 |
func TestPingFail(t *testing.T) {
|
| 20 | 20 |
var withHeader bool |
| 21 |
- client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 21 |
+ client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 22 | 22 |
var hdr http.Header |
| 23 | 23 |
if withHeader {
|
| 24 | 24 |
hdr = http.Header{}
|
| ... | ... |
@@ -48,7 +48,7 @@ func TestPingFail(t *testing.T) {
|
| 48 | 48 |
// TestPingWithError tests the case where there is a protocol error in the ping. |
| 49 | 49 |
// This test is mostly just testing that there are no panics in this code path. |
| 50 | 50 |
func TestPingWithError(t *testing.T) {
|
| 51 |
- client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 51 |
+ client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 52 | 52 |
return nil, errors.New("some connection error")
|
| 53 | 53 |
})) |
| 54 | 54 |
assert.NilError(t, err) |
| ... | ... |
@@ -64,7 +64,7 @@ func TestPingWithError(t *testing.T) {
|
| 64 | 64 |
// TestPingSuccess tests that we are able to get the expected API headers/ping |
| 65 | 65 |
// details on success. |
| 66 | 66 |
func TestPingSuccess(t *testing.T) {
|
| 67 |
- client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 67 |
+ client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 68 | 68 |
hdr := http.Header{}
|
| 69 | 69 |
hdr.Set("Api-Version", "awesome")
|
| 70 | 70 |
hdr.Set("Docker-Experimental", "true")
|
| ... | ... |
@@ -110,7 +110,7 @@ func TestPingHeadFallback(t *testing.T) {
|
| 110 | 110 |
for _, tc := range tests {
|
| 111 | 111 |
t.Run(http.StatusText(tc.status), func(t *testing.T) {
|
| 112 | 112 |
var reqs []string |
| 113 |
- client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 113 |
+ client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 114 | 114 |
if !strings.HasPrefix(req.URL.Path, expectedPath) {
|
| 115 | 115 |
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedPath, req.URL.Path)
|
| 116 | 116 |
} |
| ... | ... |
@@ -194,7 +194,7 @@ func TestResponseErrors(t *testing.T) {
|
| 194 | 194 |
} |
| 195 | 195 |
for _, tc := range tests {
|
| 196 | 196 |
t.Run(tc.doc, func(t *testing.T) {
|
| 197 |
- client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 197 |
+ client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 198 | 198 |
return mockResponse(http.StatusBadRequest, http.Header{"Content-Type": []string{tc.contentType}}, tc.response)(req)
|
| 199 | 199 |
})) |
| 200 | 200 |
if tc.apiVersion != "" {
|
| ... | ... |
@@ -210,7 +210,7 @@ func TestResponseErrors(t *testing.T) {
|
| 210 | 210 |
|
| 211 | 211 |
func TestInfiniteError(t *testing.T) {
|
| 212 | 212 |
infinitR := rand.New(rand.NewSource(42)) |
| 213 |
- client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 213 |
+ client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 214 | 214 |
resp := &http.Response{
|
| 215 | 215 |
StatusCode: http.StatusInternalServerError, |
| 216 | 216 |
Header: http.Header{},
|