client/service_test: Use functional option to create mock client

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2025-08-29 15:17:23 +02:00
parent 98434a5ea4
commit 124bba478a
6 changed files with 223 additions and 242 deletions

View File

@@ -21,10 +21,9 @@ import (
)
func TestServiceCreateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, ServiceCreateOptions{})
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, ServiceCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -42,16 +41,48 @@ func TestServiceCreateConnectionError(t *testing.T) {
func TestServiceCreate(t *testing.T) {
expectedURL := "/services/create"
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodPost {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodPost {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
}
b, err := json.Marshal(swarm.ServiceCreateResponse{
ID: "service_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
}))
assert.NilError(t, err)
r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, ServiceCreateOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "service_id"))
}
func TestServiceCreateCompatiblePlatforms(t *testing.T) {
client, err := NewClientWithOpts(WithVersion("1.30"), WithMockClient(func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
var serviceSpec swarm.ServiceSpec
// check if the /distribution endpoint returned correct output
err := json.NewDecoder(req.Body).Decode(&serviceSpec)
if err != nil {
return nil, err
}
assert.Check(t, is.Equal("foobar:1.0@sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", serviceSpec.TaskTemplate.ContainerSpec.Image))
assert.Check(t, is.Len(serviceSpec.TaskTemplate.Placement.Platforms, 1))
p := serviceSpec.TaskTemplate.Placement.Platforms[0]
b, err := json.Marshal(swarm.ServiceCreateResponse{
ID: "service_id",
ID: "service_" + p.OS + "_" + p.Architecture,
})
if err != nil {
return nil, err
@@ -60,65 +91,30 @@ func TestServiceCreate(t *testing.T) {
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, ServiceCreateOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "service_id"))
}
func TestServiceCreateCompatiblePlatforms(t *testing.T) {
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
var serviceSpec swarm.ServiceSpec
// check if the /distribution endpoint returned correct output
err := json.NewDecoder(req.Body).Decode(&serviceSpec)
if err != nil {
return nil, err
}
assert.Check(t, is.Equal("foobar:1.0@sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", serviceSpec.TaskTemplate.ContainerSpec.Image))
assert.Check(t, is.Len(serviceSpec.TaskTemplate.Placement.Platforms, 1))
p := serviceSpec.TaskTemplate.Placement.Platforms[0]
b, err := json.Marshal(swarm.ServiceCreateResponse{
ID: "service_" + p.OS + "_" + p.Architecture,
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
b, err := json.Marshal(registrytypes.DistributionInspect{
Descriptor: ocispec.Descriptor{
Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
b, err := json.Marshal(registrytypes.DistributionInspect{
Descriptor: ocispec.Descriptor{
Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
},
Platforms: []ocispec.Platform{
{
Architecture: "amd64",
OS: "linux",
},
Platforms: []ocispec.Platform{
{
Architecture: "amd64",
OS: "linux",
},
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
} else {
return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
},
})
if err != nil {
return nil, err
}
}),
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
} else {
return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
}
}))
assert.NilError(t, err)
spec := swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "foobar:1.0"}}}
@@ -149,49 +145,47 @@ func TestServiceCreateDigestPinning(t *testing.T) {
{"cannotresolve", "cannotresolve:latest"},
}
client := &Client{
version: "1.30",
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
// reset and set image received by the service create endpoint
serviceCreateImage = ""
var service swarm.ServiceSpec
if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
return nil, errors.New("could not parse service create request")
}
serviceCreateImage = service.TaskTemplate.ContainerSpec.Image
b, err := json.Marshal(swarm.ServiceCreateResponse{
ID: "service_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
// unresolvable image
return nil, errors.New("cannot resolve image")
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
// resolvable images
b, err := json.Marshal(registrytypes.DistributionInspect{
Descriptor: ocispec.Descriptor{
Digest: digest.Digest(dgst),
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
client, err := NewClientWithOpts(WithVersion("1.30"), WithMockClient(func(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
// reset and set image received by the service create endpoint
serviceCreateImage = ""
var service swarm.ServiceSpec
if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
return nil, errors.New("could not parse service create request")
}
return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
}),
}
serviceCreateImage = service.TaskTemplate.ContainerSpec.Image
b, err := json.Marshal(swarm.ServiceCreateResponse{
ID: "service_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
// unresolvable image
return nil, errors.New("cannot resolve image")
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
// resolvable images
b, err := json.Marshal(registrytypes.DistributionInspect{
Descriptor: ocispec.Descriptor{
Digest: digest.Digest(dgst),
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(b)),
}, nil
}
return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
}))
assert.NilError(t, err)
// run pin by digest tests
for _, p := range pinByDigestTests {

View File

@@ -18,30 +18,27 @@ import (
)
func TestServiceInspectError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, _, err := client.ServiceInspectWithRaw(context.Background(), "nothing", ServiceInspectOptions{})
_, _, err = client.ServiceInspectWithRaw(context.Background(), "nothing", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestServiceInspectServiceNotFound(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
}
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, _, err := client.ServiceInspectWithRaw(context.Background(), "unknown", ServiceInspectOptions{})
_, _, err = client.ServiceInspectWithRaw(context.Background(), "unknown", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
func TestServiceInspectWithEmptyID(t *testing.T) {
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("should not make request")
}),
}
_, _, err := client.ServiceInspectWithRaw(context.Background(), "", ServiceInspectOptions{})
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, _, err = client.ServiceInspectWithRaw(context.Background(), "", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
@@ -52,23 +49,22 @@ func TestServiceInspectWithEmptyID(t *testing.T) {
func TestServiceInspect(t *testing.T) {
expectedURL := "/services/service_id"
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
content, err := json.Marshal(swarm.Service{
ID: "service_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
content, err := json.Marshal(swarm.Service{
ID: "service_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(content)),
}, nil
}))
assert.NilError(t, err)
serviceInspect, _, err := client.ServiceInspectWithRaw(context.Background(), "service_id", ServiceInspectOptions{})
assert.NilError(t, err)

View File

@@ -18,11 +18,10 @@ import (
)
func TestServiceListError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err := client.ServiceList(context.Background(), ServiceListOptions{})
_, err = client.ServiceList(context.Background(), ServiceListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -52,35 +51,34 @@ func TestServiceList(t *testing.T) {
},
}
for _, listCase := range listCases {
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
for key, expected := range listCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
query := req.URL.Query()
for key, expected := range listCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
content, err := json.Marshal([]swarm.Service{
{
ID: "service_id1",
},
{
ID: "service_id2",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
}
content, err := json.Marshal([]swarm.Service{
{
ID: "service_id1",
},
{
ID: "service_id2",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(content)),
}, nil
}))
assert.NilError(t, err)
services, err := client.ServiceList(context.Background(), listCase.options)
assert.NilError(t, err)

View File

@@ -20,10 +20,9 @@ import (
)
func TestServiceLogsError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ServiceLogs(context.Background(), "service_id", container.LogsOptions{})
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceLogs(context.Background(), "service_id", container.LogsOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ServiceLogs(context.Background(), "service_id", container.LogsOptions{
@@ -96,25 +95,24 @@ func TestServiceLogs(t *testing.T) {
},
}
for _, logCase := range cases {
client := &Client{
client: newMockClient(func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
client, err := NewClientWithOpts(WithMockClient(func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
}
// Check query parameters
query := r.URL.Query()
for key, expected := range logCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
// Check query parameters
query := r.URL.Query()
for key, expected := range logCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}))
assert.NilError(t, err)
body, err := client.ServiceLogs(context.Background(), "service_id", logCase.options)
if logCase.expectedError != "" {
assert.Check(t, is.Error(err, logCase.expectedError))

View File

@@ -15,11 +15,10 @@ import (
)
func TestServiceRemoveError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
err := client.ServiceRemove(context.Background(), "service_id")
err = client.ServiceRemove(context.Background(), "service_id")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
err = client.ServiceRemove(context.Background(), "")
@@ -32,11 +31,10 @@ func TestServiceRemoveError(t *testing.T) {
}
func TestServiceRemoveNotFoundError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusNotFound, "no such service: service_id")),
}
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusNotFound, "no such service: service_id")))
assert.NilError(t, err)
err := client.ServiceRemove(context.Background(), "service_id")
err = client.ServiceRemove(context.Background(), "service_id")
assert.Check(t, is.ErrorContains(err, "no such service: service_id"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -44,21 +42,20 @@ func TestServiceRemoveNotFoundError(t *testing.T) {
func TestServiceRemove(t *testing.T) {
expectedURL := "/services/service_id"
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodDelete {
return nil, fmt.Errorf("expected DELETE method, got %s", req.Method)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("body"))),
}, nil
}),
}
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodDelete {
return nil, fmt.Errorf("expected DELETE method, got %s", req.Method)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("body"))),
}, nil
}))
assert.NilError(t, err)
err := client.ServiceRemove(context.Background(), "service_id")
err = client.ServiceRemove(context.Background(), "service_id")
assert.NilError(t, err)
}

View File

@@ -16,11 +16,10 @@ import (
)
func TestServiceUpdateError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err := client.ServiceUpdate(context.Background(), "service_id", swarm.Version{}, swarm.ServiceSpec{}, ServiceUpdateOptions{})
_, err = client.ServiceUpdate(context.Background(), "service_id", swarm.Version{}, swarm.ServiceSpec{}, ServiceUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ServiceUpdate(context.Background(), "", swarm.Version{}, swarm.ServiceSpec{}, ServiceUpdateOptions{})
@@ -69,26 +68,25 @@ func TestServiceUpdate(t *testing.T) {
}
for _, updateCase := range updateCases {
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodPost {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
}
version := req.URL.Query().Get("version")
if version != updateCase.expectedVersion {
return nil, fmt.Errorf("version not set in URL query properly, expected '%s', got %s", updateCase.expectedVersion, version)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("{}"))),
}, nil
}),
}
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != http.MethodPost {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
}
version := req.URL.Query().Get("version")
if version != updateCase.expectedVersion {
return nil, fmt.Errorf("version not set in URL query properly, expected '%s', got %s", updateCase.expectedVersion, version)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("{}"))),
}, nil
}))
assert.NilError(t, err)
_, err := client.ServiceUpdate(context.Background(), "service_id", updateCase.swarmVersion, swarm.ServiceSpec{}, ServiceUpdateOptions{})
_, err = client.ServiceUpdate(context.Background(), "service_id", updateCase.swarmVersion, swarm.ServiceSpec{}, ServiceUpdateOptions{})
assert.NilError(t, err)
}
}