Files
moby/client/container_copy_test.go
Sebastiaan van Stijn 839c2709af client: WithMockClient: match version behavior of actual client
The WithMockClient option was explicitly resetting the client's API
version (see [1]), which differs from the regular client, which is
initialized with the current API version used by the client (see [2]).

This patch:

- reduces the `WithMockClient` to only set the custom HTTP client, leaving
  other fields un-touched.
- adds a test utility and updates tests to handle the API-version prefix
- removes redundant uses of `WithVersion()` in tests; for most test-cases
  it was used to make sure a current API version is used that supports the
  feature being tested, but there was no test to verify the behavior for
  lower API versions, so we may as well test against "latest".

[1]: 5a582729d8/client/client_mock_test.go (L22-L36)
[2]: 5a582729d8/client/client.go (L167-L190)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-18 11:37:56 +02:00

303 lines
9.7 KiB
Go

package client
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"testing"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestContainerStatPathError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusInternalServerError, "Server error")),
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", "path")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerStatPath(context.Background(), "", "path")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerStatPath(context.Background(), " ", "path")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestContainerStatPathNotFoundError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusNotFound, "Not found")),
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", "path")
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
func TestContainerStatPathNoHeaderError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", "path/to/file")
assert.Check(t, err != nil, "expected an error, got nothing")
}
func TestContainerStatPath(t *testing.T) {
const (
expectedURL = "/containers/container_id/archive"
expectedPath = "path/to/file"
)
client, err := NewClientWithOpts(
WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodHead, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
path := query.Get("path")
if path != expectedPath {
return nil, errors.New("path not set in URL query properly")
}
content, err := json.Marshal(container.PathStat{
Name: "name",
Mode: 0o700,
})
if err != nil {
return nil, err
}
base64PathStat := base64.StdEncoding.EncodeToString(content)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(""))),
Header: http.Header{
"X-Docker-Container-Path-Stat": []string{base64PathStat},
},
}, nil
}),
)
assert.NilError(t, err)
stat, err := client.ContainerStatPath(context.Background(), "container_id", expectedPath)
assert.NilError(t, err)
assert.Check(t, is.Equal(stat.Name, "name"))
assert.Check(t, is.Equal(stat.Mode, os.FileMode(0o700)))
}
func TestCopyToContainerError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusInternalServerError, "Server error")),
)
assert.NilError(t, err)
err = client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
err = client.CopyToContainer(context.Background(), "", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
err = client.CopyToContainer(context.Background(), " ", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestCopyToContainerNotFoundError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusNotFound, "Not found")),
)
assert.NilError(t, err)
err = client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
// TestCopyToContainerEmptyResponse verifies that no error is returned when a
// "204 No Content" is returned by the API.
func TestCopyToContainerEmptyResponse(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusNoContent, "No content")),
)
assert.NilError(t, err)
err = client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{})
assert.NilError(t, err)
}
func TestCopyToContainer(t *testing.T) {
const (
expectedURL = "/containers/container_id/archive"
expectedPath = "path/to/file"
)
client, err := NewClientWithOpts(
WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodPut, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
path := query.Get("path")
if path != expectedPath {
return nil, fmt.Errorf("path not set in URL query properly, expected '%s', got %s", expectedPath, path)
}
noOverwriteDirNonDir := query.Get("noOverwriteDirNonDir")
if noOverwriteDirNonDir != "true" {
return nil, fmt.Errorf("noOverwriteDirNonDir not set in URL query properly, expected true, got %s", noOverwriteDirNonDir)
}
content, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
if err := req.Body.Close(); err != nil {
return nil, err
}
if string(content) != "content" {
return nil, fmt.Errorf("expected content to be 'content', got %s", string(content))
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
)
assert.NilError(t, err)
err = client.CopyToContainer(context.Background(), "container_id", expectedPath, bytes.NewReader([]byte("content")), CopyToContainerOptions{
AllowOverwriteDirWithFile: false,
})
assert.NilError(t, err)
}
func TestCopyFromContainerError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusInternalServerError, "Server error")),
)
assert.NilError(t, err)
_, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, _, err = client.CopyFromContainer(context.Background(), "", "path/to/file")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, _, err = client.CopyFromContainer(context.Background(), " ", "path/to/file")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
func TestCopyFromContainerNotFoundError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(errorMock(http.StatusNotFound, "Not found")),
)
assert.NilError(t, err)
_, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
// TestCopyFromContainerEmptyResponse verifies that no error is returned when a
// "204 No Content" is returned by the API.
func TestCopyFromContainerEmptyResponse(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(func(req *http.Request) (*http.Response, error) {
content, err := json.Marshal(container.PathStat{
Name: "path/to/file",
Mode: 0o700,
})
if err != nil {
return nil, err
}
base64PathStat := base64.StdEncoding.EncodeToString(content)
return &http.Response{
StatusCode: http.StatusNoContent,
Header: http.Header{
"X-Docker-Container-Path-Stat": []string{base64PathStat},
},
}, nil
}),
)
assert.NilError(t, err)
_, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
assert.NilError(t, err)
}
func TestCopyFromContainerNoHeaderError(t *testing.T) {
client, err := NewClientWithOpts(
WithMockClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
)
assert.NilError(t, err)
_, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
assert.Check(t, err != nil, "expected an error, got nothing")
}
func TestCopyFromContainer(t *testing.T) {
const (
expectedURL = "/containers/container_id/archive"
expectedPath = "path/to/file"
)
client, err := NewClientWithOpts(
WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodGet, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
path := query.Get("path")
if path != expectedPath {
return nil, fmt.Errorf("path not set in URL query properly, expected '%s', got %s", expectedPath, path)
}
headercontent, err := json.Marshal(container.PathStat{
Name: "name",
Mode: 0o700,
})
if err != nil {
return nil, err
}
base64PathStat := base64.StdEncoding.EncodeToString(headercontent)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("content"))),
Header: http.Header{
"X-Docker-Container-Path-Stat": []string{base64PathStat},
},
}, nil
}),
)
assert.NilError(t, err)
r, stat, err := client.CopyFromContainer(context.Background(), "container_id", expectedPath)
assert.NilError(t, err)
assert.Check(t, is.Equal(stat.Name, "name"))
assert.Check(t, is.Equal(stat.Mode, os.FileMode(0o700)))
content, err := io.ReadAll(r)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(content), "content"))
assert.NilError(t, r.Close())
}