144 lines
4.4 KiB
Go
144 lines
4.4 KiB
Go
package oci
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"mime"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type trackingReadCloser struct {
|
|
io.Reader
|
|
closed bool
|
|
}
|
|
|
|
type testMultipartFile struct {
|
|
name string
|
|
data []byte
|
|
}
|
|
|
|
func (r *trackingReadCloser) Close() error {
|
|
r.closed = true
|
|
return nil
|
|
}
|
|
|
|
func testImageResponse(status int, body string) (*http.Response, *trackingReadCloser) {
|
|
reader := &trackingReadCloser{Reader: strings.NewReader(body)}
|
|
return &http.Response{StatusCode: status, Body: reader}, reader
|
|
}
|
|
|
|
func readMultipartFields(t *testing.T, body []byte, contentType string) (map[string]string, map[string]testMultipartFile) {
|
|
t.Helper()
|
|
_, params, err := mime.ParseMediaType(contentType)
|
|
if err != nil {
|
|
t.Fatalf("parse content type: %v", err)
|
|
}
|
|
reader := multipart.NewReader(strings.NewReader(string(body)), params["boundary"])
|
|
fields, files := map[string]string{}, map[string]testMultipartFile{}
|
|
for {
|
|
part, err := reader.NextPart()
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("next part: %v", err)
|
|
}
|
|
data, _ := io.ReadAll(part)
|
|
if part.FileName() == "" {
|
|
fields[part.FormName()] = string(data)
|
|
} else {
|
|
files[part.FormName()] = testMultipartFile{name: part.FileName(), data: data}
|
|
}
|
|
}
|
|
return fields, files
|
|
}
|
|
|
|
func TestImageMultipartIncludesFileAndName(t *testing.T) {
|
|
fileName := "idp-icon-00112233445566778899aabbccddeeff.png"
|
|
body, contentType, err := imageMultipart(fileName, []byte("image-data"))
|
|
if err != nil {
|
|
t.Fatalf("imageMultipart: %v", err)
|
|
}
|
|
fields, files := readMultipartFields(t, body, contentType)
|
|
if got := fields["fileName"]; got != fileName {
|
|
t.Errorf("fileName = %q, want %q", got, fileName)
|
|
}
|
|
file := files["file"]
|
|
if file.name != fileName || string(file.data) != "image-data" {
|
|
t.Errorf("file part = %q, %q; want %q, image-data", file.name, file.data, fileName)
|
|
}
|
|
}
|
|
|
|
func TestParseImageUploadResponse(t *testing.T) {
|
|
cases := []struct {
|
|
name, body, wantURL, wantName string
|
|
status int
|
|
callErr error
|
|
wantErr bool
|
|
}{
|
|
{"complete", `{"fileUrl":"https://img/x","fileName":"images/x.png"}`, "https://img/x", "images/x.png", 201, nil, false},
|
|
{"missing name", `{"fileUrl":"https://img/x"}`, "", "", 201, nil, true},
|
|
{"missing url", `{"fileName":"images/x.png"}`, "", "", 200, nil, true},
|
|
{"bad json", `{`, "", "", 200, nil, true},
|
|
{"http error hides body", `secret-body`, "", "", 400, nil, true},
|
|
{"call error", `{}`, "", "", 500, errors.New("upstream"), true},
|
|
}
|
|
for _, tc := range cases {
|
|
resp, body := testImageResponse(tc.status, tc.body)
|
|
gotURL, gotName, err := parseImageUploadResponse(resp, tc.callErr)
|
|
if gotURL != tc.wantURL || gotName != tc.wantName || (err != nil) != tc.wantErr {
|
|
t.Errorf("result = %q, %q, %v; want %q, %q, err=%v", gotURL, gotName, err, tc.wantURL, tc.wantName, tc.wantErr)
|
|
}
|
|
if !body.closed {
|
|
t.Error("response body was not closed")
|
|
}
|
|
if err != nil && strings.Contains(err.Error(), "secret-body") {
|
|
t.Errorf("error leaked response body: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDomainImageDeleteRequestEncodesFileName(t *testing.T) {
|
|
fileName := "images/folder/a b+%.png"
|
|
req, err := newDomainImageDeleteRequest(context.Background(), "https://id.example/", fileName)
|
|
if err != nil {
|
|
t.Fatalf("newDomainImageDeleteRequest: %v", err)
|
|
}
|
|
if req.Method != http.MethodDelete || req.URL.Path != domainImagesPath {
|
|
t.Errorf("request = %s %s, want DELETE %s", req.Method, req.URL.Path, domainImagesPath)
|
|
}
|
|
if got := req.URL.Query().Get("fileName"); got != fileName {
|
|
t.Errorf("decoded fileName = %q, want %q", got, fileName)
|
|
}
|
|
if strings.Contains(req.URL.RawQuery, " ") || strings.Contains(req.URL.RawQuery, "/") {
|
|
t.Errorf("raw query is not encoded: %q", req.URL.RawQuery)
|
|
}
|
|
}
|
|
|
|
func TestFinishDomainImageDelete(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
status int
|
|
callErr error
|
|
wantErr bool
|
|
}{
|
|
{"deleted", http.StatusNoContent, nil, false},
|
|
{"already gone", http.StatusNotFound, errors.New("not found"), false},
|
|
{"upstream failure", http.StatusInternalServerError, errors.New("upstream"), true},
|
|
}
|
|
for _, tc := range cases {
|
|
resp, body := testImageResponse(tc.status, "response")
|
|
err := finishDomainImageDelete(resp, tc.callErr)
|
|
if (err != nil) != tc.wantErr {
|
|
t.Errorf("err = %v, wantErr %v", err, tc.wantErr)
|
|
}
|
|
if !body.closed {
|
|
t.Error("response body was not closed")
|
|
}
|
|
}
|
|
}
|