Files
moby/client/image_push_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

197 lines
6.3 KiB
Go

package client
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"testing"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/registry"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestImagePushReferenceError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
return nil, nil
}))
assert.NilError(t, err)
// An empty reference is an invalid reference
_, err = client.ImagePush(context.Background(), "", ImagePushOptions{})
assert.Check(t, is.ErrorContains(err, "invalid reference format"))
// An canonical reference cannot be pushed
_, err = client.ImagePush(context.Background(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ImagePushOptions{})
assert.Check(t, is.Error(err, "cannot push a digest reference"))
}
func TestImagePushAnyError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImagePushStatusUnauthorizedError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
func TestImagePushWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
privilegeFunc := func(_ context.Context) (string, error) {
return "", errors.New("Error requesting privilege")
}
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.Error(err, "Error requesting privilege"))
}
func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
privilegeFunc := func(_ context.Context) (string, error) {
return "a-auth-header", nil
}
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
func TestImagePushWithPrivilegedFuncNoError(t *testing.T) {
const expectedURL = "/images/docker.io/myname/myimage/push"
const invalidAuth = "NotValid"
const validAuth = "IAmValid"
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodPost, expectedURL); err != nil {
return nil, err
}
auth := req.Header.Get(registry.AuthHeader)
if auth == invalidAuth {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: io.NopCloser(bytes.NewReader([]byte("Invalid credentials"))),
}, nil
}
if auth != validAuth {
return nil, fmt.Errorf("invalid auth header: expected %s, got %s", "IAmValid", auth)
}
query := req.URL.Query()
tag := query.Get("tag")
if tag != "tag" {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "tag", tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("hello world"))),
}, nil
}))
assert.NilError(t, err)
resp, err := client.ImagePush(context.Background(), "myname/myimage:tag", ImagePushOptions{
RegistryAuth: invalidAuth,
PrivilegeFunc: staticAuth(validAuth),
})
assert.NilError(t, err)
body, err := io.ReadAll(resp)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(body), "hello world"))
}
func TestImagePushWithoutErrors(t *testing.T) {
const (
expectedURLFormat = "/images/%s/push"
expectedOutput = "hello world"
)
testCases := []struct {
all bool
reference string
expectedImage string
expectedTag string
}{
{
all: false,
reference: "myimage",
expectedImage: "docker.io/library/myimage",
expectedTag: "latest",
},
{
all: false,
reference: "myimage:tag",
expectedImage: "docker.io/library/myimage",
expectedTag: "tag",
},
{
all: true,
reference: "myimage",
expectedImage: "docker.io/library/myimage",
expectedTag: "",
},
{
all: true,
reference: "myimage:anything",
expectedImage: "docker.io/library/myimage",
expectedTag: "",
},
{
reference: "myname/myimage",
expectedImage: "docker.io/myname/myimage",
expectedTag: "latest",
},
{
reference: "docker.io/myname/myimage",
expectedImage: "docker.io/myname/myimage",
expectedTag: "latest",
},
{
reference: "index.docker.io/myname/myimage:tag",
expectedImage: "docker.io/myname/myimage",
expectedTag: "tag",
},
{
reference: "localhost/myname/myimage",
expectedImage: "localhost/myname/myimage",
expectedTag: "latest",
},
{
reference: "registry.example.com:5000/myimage:tag",
expectedImage: "registry.example.com:5000/myimage",
expectedTag: "tag",
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s,all-tags=%t", tc.reference, tc.all), func(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(func(req *http.Request) (*http.Response, error) {
expectedURL := fmt.Sprintf(expectedURLFormat, tc.expectedImage)
if err := assertRequest(req, http.MethodPost, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
tag := query.Get("tag")
if tag != tc.expectedTag {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", tc.expectedTag, tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte(expectedOutput))),
}, nil
}))
assert.NilError(t, err)
resp, err := client.ImagePush(context.Background(), tc.reference, ImagePushOptions{
All: tc.all,
})
assert.NilError(t, err)
body, err := io.ReadAll(resp)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(body), expectedOutput))
})
}
}