package daemon
import (
"bytes"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/openeuler/Conch/internal/apperror"
conchimage "github.com/openeuler/Conch/internal/image"
)
func TestHandlePullImageUnavailable(t *testing.T) {
runtimeService := newHandlerRuntime(nil, nil, nil)
server := &Daemon{router: http.NewServeMux(), runtimeService: runtimeService}
server.routes()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/image/pull", bytes.NewBufferString(`{"image_name":"x"}`))
server.router.ServeHTTP(rec, req)
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status = %d, body = %s", rec.Code, rec.Body.String())
}
}
func TestWriteAPIErrorClassifiesImageErrors(t *testing.T) {
for _, test := range []struct {
name string
err error
wantStatus int
}{
{name: "invalid request", err: conchimage.ErrInvalidArgument.Wrap(errors.New("image_name is required")), wantStatus: http.StatusBadRequest},
{name: "conversion failure", err: conchimage.ErrConversionFailed.Wrap(errors.New("convert rootfs")), wantStatus: http.StatusInternalServerError},
} {
t.Run(test.name, func(t *testing.T) {
rec := httptest.NewRecorder()
writeAPIError(rec, test.err)
if rec.Code != test.wantStatus {
t.Fatalf("status = %d, want %d; body = %s", rec.Code, test.wantStatus, rec.Body.String())
}
})
}
}
func TestWriteImageErrorPreservesRegistryAuthStatusWithoutLeakingCredentials(t *testing.T) {
const (
registryUsername = "registry-user"
registryPassword = "registry-password"
)
for _, test := range []struct {
name string
statusCode int
prototype *apperror.Error
}{
{name: "Unauthorized", statusCode: http.StatusUnauthorized, prototype: conchimage.ErrRegistryUnauthenticated},
{name: "Forbidden", statusCode: http.StatusForbidden, prototype: conchimage.ErrRegistryPermissionDenied},
} {
t.Run(test.name, func(t *testing.T) {
rec := httptest.NewRecorder()
writeAPIError(rec, test.prototype.Wrap(errors.New("https://"+registryUsername+":"+registryPassword+"@registry.example.invalid/private response")))
if rec.Code != test.statusCode {
t.Fatalf("status = %d, want %d; body = %s", rec.Code, test.statusCode, rec.Body.String())
}
if strings.Contains(rec.Body.String(), registryUsername) || strings.Contains(rec.Body.String(), registryPassword) {
t.Fatalf("response body leaked registry credentials: %q", rec.Body.String())
}
})
}
}
func TestWriteAPIErrorOnlyTrustsApplicationClassification(t *testing.T) {
for _, test := range []struct {
name string
err error
wantStatus int
}{
{
name: "plain auth-like message",
err: errors.New("registry returned 401 Unauthorized"),
wantStatus: http.StatusInternalServerError,
},
{
name: "classified registry status",
wantStatus: http.StatusTooManyRequests,
err: conchimage.ErrRegistryRateLimited.New(),
},
} {
t.Run(test.name, func(t *testing.T) {
rec := httptest.NewRecorder()
writeAPIError(rec, test.err)
if rec.Code != test.wantStatus {
t.Fatalf("status = %d, want %d; body = %s", rec.Code, test.wantStatus, rec.Body.String())
}
})
}
}
func TestRemovedImageRoutesReturnNotFound(t *testing.T) {
server := &Daemon{router: http.NewServeMux()}
server.routes()
for _, path := range []string{"/api/image/convert", "/api/image/import", "/api/image/unpack"} {
t.Run(path, func(t *testing.T) {
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, path, bytes.NewBufferString("{}"))
server.router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("status = %d, want 404; body = %s", rec.Code, rec.Body.String())
}
})
}
}