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") } } }