mirror of
https://github.com/moby/moby.git
synced 2026-01-11 02:31:44 +00:00
client: improve mocking responses
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>
This commit is contained in:
@@ -5,9 +5,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/common"
|
||||
"github.com/moby/moby/api/types/swarm"
|
||||
)
|
||||
@@ -60,8 +62,70 @@ func ensureBody(f func(req *http.Request) (*http.Response, error)) testRoundTrip
|
||||
}
|
||||
}
|
||||
|
||||
// WithMockClient is a test helper that allows you to inject a mock client for testing.
|
||||
// makeTestRoundTripper makes sure the response has a Body, using [http.NoBody] if
|
||||
// none is present, and returns it as a testRoundTripper. If withDefaults is set,
|
||||
// it also mocks the "/_ping" endpoint and sets default headers as returned
|
||||
// by the daemon.
|
||||
func makeTestRoundTripper(f func(req *http.Request) (*http.Response, error)) testRoundTripper {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Path == "/_ping" {
|
||||
return mockPingResponse(http.StatusOK, PingResult{
|
||||
APIVersion: MaxAPIVersion,
|
||||
OSType: runtime.GOOS,
|
||||
Experimental: true,
|
||||
BuilderVersion: build.BuilderBuildKit,
|
||||
SwarmStatus: &SwarmStatus{
|
||||
NodeState: swarm.LocalNodeStateActive,
|
||||
ControlAvailable: true,
|
||||
},
|
||||
})(req)
|
||||
}
|
||||
resp, err := f(req)
|
||||
if resp != nil {
|
||||
if resp.Body == nil {
|
||||
resp.Body = http.NoBody
|
||||
}
|
||||
if resp.Request == nil {
|
||||
resp.Request = req
|
||||
}
|
||||
}
|
||||
applyDefaultHeaders(resp)
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
// applyDefaultHeaders mocks the headers set by the daemon's VersionMiddleware.
|
||||
func applyDefaultHeaders(resp *http.Response) {
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
if resp.Header == nil {
|
||||
resp.Header = make(http.Header)
|
||||
}
|
||||
if resp.Header.Get("Server") == "" {
|
||||
resp.Header.Set("Server", fmt.Sprintf("Docker/%s (%s)", "v99.99.99", runtime.GOOS))
|
||||
}
|
||||
if resp.Header.Get("Api-Version") == "" {
|
||||
resp.Header.Set("Api-Version", MaxAPIVersion)
|
||||
}
|
||||
if resp.Header.Get("Ostype") == "" {
|
||||
resp.Header.Set("Ostype", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
// WithMockClient is a test helper that allows you to inject a mock client for
|
||||
// testing. By default, it mocks the "/_ping" endpoint, so allow the client
|
||||
// to perform API-version negotiation. Other endpoints are handled by "doer".
|
||||
func WithMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
|
||||
return WithHTTPClient(&http.Client{
|
||||
Transport: makeTestRoundTripper(doer),
|
||||
})
|
||||
}
|
||||
|
||||
// WithBaseMockClient is a test helper that allows you to inject a mock client
|
||||
// for testing. It is identical to [WithMockClient], but does not mock the "/_ping"
|
||||
// endpoint, and doesn't set the default headers.
|
||||
func WithBaseMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
|
||||
return WithHTTPClient(&http.Client{
|
||||
Transport: ensureBody(doer),
|
||||
})
|
||||
|
||||
@@ -258,7 +258,7 @@ func TestNegotiateAPIVersionEmpty(t *testing.T) {
|
||||
|
||||
client, err := New(FromEnv,
|
||||
WithAPIVersionNegotiation(),
|
||||
WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: expected})),
|
||||
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: expected})),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -331,7 +331,7 @@ func TestNegotiateAPIVersion(t *testing.T) {
|
||||
opts := []Opt{
|
||||
FromEnv,
|
||||
WithAPIVersionNegotiation(),
|
||||
WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: tc.pingVersion})),
|
||||
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: tc.pingVersion})),
|
||||
}
|
||||
|
||||
if tc.clientVersion != "" {
|
||||
@@ -363,7 +363,7 @@ func TestNegotiateAPIVersionOverride(t *testing.T) {
|
||||
|
||||
client, err := New(
|
||||
FromEnv,
|
||||
WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.45"})),
|
||||
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.45"})),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -393,7 +393,7 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
|
||||
|
||||
ctx := t.Context()
|
||||
client, err := New(
|
||||
WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
return mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})(req)
|
||||
}),
|
||||
WithAPIVersionNegotiation(),
|
||||
@@ -422,7 +422,7 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
|
||||
func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
|
||||
client, err := New(
|
||||
WithAPIVersion(""),
|
||||
WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.50"})),
|
||||
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.50"})),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -442,7 +442,7 @@ func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) {
|
||||
)
|
||||
client, err := New(
|
||||
WithAPIVersion(customVersion),
|
||||
WithMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})),
|
||||
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// panics.
|
||||
func TestPingFail(t *testing.T) {
|
||||
var withHeader bool
|
||||
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
var hdr http.Header
|
||||
if withHeader {
|
||||
hdr = http.Header{}
|
||||
@@ -48,7 +48,7 @@ func TestPingFail(t *testing.T) {
|
||||
// TestPingWithError tests the case where there is a protocol error in the ping.
|
||||
// This test is mostly just testing that there are no panics in this code path.
|
||||
func TestPingWithError(t *testing.T) {
|
||||
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
return nil, errors.New("some connection error")
|
||||
}))
|
||||
assert.NilError(t, err)
|
||||
@@ -64,7 +64,7 @@ func TestPingWithError(t *testing.T) {
|
||||
// TestPingSuccess tests that we are able to get the expected API headers/ping
|
||||
// details on success.
|
||||
func TestPingSuccess(t *testing.T) {
|
||||
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
hdr := http.Header{}
|
||||
hdr.Set("Api-Version", "awesome")
|
||||
hdr.Set("Docker-Experimental", "true")
|
||||
@@ -110,7 +110,7 @@ func TestPingHeadFallback(t *testing.T) {
|
||||
for _, tc := range tests {
|
||||
t.Run(http.StatusText(tc.status), func(t *testing.T) {
|
||||
var reqs []string
|
||||
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if !strings.HasPrefix(req.URL.Path, expectedPath) {
|
||||
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedPath, req.URL.Path)
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func TestResponseErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
return mockResponse(http.StatusBadRequest, http.Header{"Content-Type": []string{tc.contentType}}, tc.response)(req)
|
||||
}))
|
||||
if tc.apiVersion != "" {
|
||||
@@ -210,7 +210,7 @@ func TestResponseErrors(t *testing.T) {
|
||||
|
||||
func TestInfiniteError(t *testing.T) {
|
||||
infinitR := rand.New(rand.NewSource(42))
|
||||
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Header: http.Header{},
|
||||
|
||||
Reference in New Issue
Block a user