dnscontrol/pkg/cloudflare-go/images_test.go
Tom Limoncelli 7fd6a74e0c
CLOUDFLAREAPI: CF_REDIRECT/CF_TEMP_REDIRECT should dtrt using Single Redirects (#3002)
Co-authored-by: Josh Zhang <jzhang1@stackoverflow.com>
2024-06-18 17:38:50 -04:00

539 lines
14 KiB
Go

package cloudflare
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/goccy/go-json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func timeMustParse(layout, value string) time.Time {
t, err := time.Parse(layout, value)
if err != nil {
panic(err)
}
return t
}
var expectedImageStruct = Image{
ID: "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
Filename: "avatar.png",
Meta: map[string]interface{}{
"meta": "metaID",
},
RequireSignedURLs: true,
Variants: []string{
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail",
},
Uploaded: timeMustParse(time.RFC3339, "2014-01-02T02:20:00Z"),
}
func TestUploadImage(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
u, err := parseImageMultipartUpload(r)
if !assert.NoError(t, err) {
w.WriteHeader(http.StatusBadRequest)
return
}
assert.Equal(t, u.RequireSignedURLs, true)
assert.Equal(t, u.Metadata, map[string]interface{}{"meta": "metaID"})
assert.Equal(t, u.File, []byte("this is definitely an image"))
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"filename": "avatar.png",
"meta": {
"meta": "metaID"
},
"requireSignedURLs": true,
"variants": [
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail"
],
"uploaded": "2014-01-02T02:20:00Z"
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1", handler)
want := expectedImageStruct
actual, err := client.UploadImage(context.Background(), AccountIdentifier(testAccountID), UploadImageParams{
File: fakeFile{
Buffer: bytes.NewBufferString("this is definitely an image"),
},
Name: "avatar.png",
RequireSignedURLs: true,
Metadata: map[string]interface{}{
"meta": "metaID",
},
})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestUploadImageByUrl(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
u, err := parseImageMultipartUpload(r)
if !assert.NoError(t, err) {
w.WriteHeader(http.StatusBadRequest)
return
}
assert.Equal(t, u.RequireSignedURLs, true)
assert.Equal(t, u.Metadata, map[string]interface{}{"meta": "metaID"})
assert.Equal(t, u.Url, "https://www.images-elsewhere.com/avatar.png")
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"filename": "avatar.png",
"meta": {
"meta": "metaID"
},
"requireSignedURLs": true,
"variants": [
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail"
],
"uploaded": "2014-01-02T02:20:00Z"
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1", handler)
want := expectedImageStruct
actual, err := client.UploadImage(context.Background(), AccountIdentifier(testAccountID), UploadImageParams{
URL: "https://www.images-elsewhere.com/avatar.png",
RequireSignedURLs: true,
Metadata: map[string]interface{}{
"meta": "metaID",
},
})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestUpdateImage(t *testing.T) {
setup()
defer teardown()
input := UpdateImageParams{
RequireSignedURLs: true,
Metadata: map[string]interface{}{
"meta": "metaID",
},
}
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PATCH', got %s", r.Method)
var v UpdateImageParams
err := json.NewDecoder(r.Body).Decode(&v)
require.NoError(t, err)
assert.Equal(t, input, v)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"filename": "avatar.png",
"meta": {
"meta": "metaID"
},
"requireSignedURLs": true,
"variants": [
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail"
],
"uploaded": "2014-01-02T02:20:00Z"
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1/ZxR0pLaXRldlBtaFhhO2FiZGVnaA", handler)
want := expectedImageStruct
actual, err := client.UpdateImage(context.Background(), AccountIdentifier(testAccountID), UpdateImageParams{
ID: "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
RequireSignedURLs: true,
Metadata: map[string]interface{}{
"meta": "metaID",
},
})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestCreateImageDirectUploadURL(t *testing.T) {
setup()
defer teardown()
expiry := time.Now().UTC().Add(30 * time.Minute)
input := CreateImageDirectUploadURLParams{
Expiry: &expiry,
}
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
var v CreateImageDirectUploadURLParams
err := json.NewDecoder(r.Body).Decode(&v)
require.NoError(t, err)
assert.Equal(t, input, v)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"uploadURL": "https://upload.imagedelivery.net/fgr33htrthytjtyereifjewoi338272s7w1383"
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1/direct_upload", handler)
want := ImageDirectUploadURL{
ID: "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
UploadURL: "https://upload.imagedelivery.net/fgr33htrthytjtyereifjewoi338272s7w1383",
}
actual, err := client.CreateImageDirectUploadURL(context.Background(), AccountIdentifier(testAccountID), input)
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestCreateImageConflictingTypes(t *testing.T) {
setup()
defer teardown()
_, err := client.UploadImage(context.Background(), AccountIdentifier(testAccountID), UploadImageParams{
URL: "https://example.com/foo.jpg",
File: fakeFile{
Buffer: bytes.NewBufferString("this is definitely an image"),
},
})
assert.Error(t, err)
}
func TestCreateImageDirectUploadURLV2(t *testing.T) {
setup()
defer teardown()
exp := time.Now().UTC().Add(30 * time.Minute)
metadata := map[string]interface{}{
"metaKey1": "metaValue1",
"metaKey2": "metaValue2",
}
requireSignedURLs := true
input := CreateImageDirectUploadURLParams{
Version: ImagesAPIVersionV2,
Expiry: &exp,
Metadata: metadata,
RequireSignedURLs: &requireSignedURLs,
}
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
require.Equal(t,
fmt.Sprintf("multipart/form-data; boundary=%s", imagesMultipartBoundary),
r.Header.Get("Content-Type"),
)
require.NoError(t, r.ParseMultipartForm(32<<20))
require.Equal(t, exp.Format(time.RFC3339), r.Form.Get("expiry"))
require.Equal(t, "true", r.Form.Get("requireSignedURLs"))
marshalledMetadata, err := json.Marshal(metadata)
require.NoError(t, err)
require.Equal(t, string(marshalledMetadata), r.Form.Get("metadata"))
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"uploadURL": "https://upload.imagedelivery.net/fgr33htrthytjtyereifjewoi338272s7w1383"
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v2/direct_upload", handler)
want := ImageDirectUploadURL{
ID: "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
UploadURL: "https://upload.imagedelivery.net/fgr33htrthytjtyereifjewoi338272s7w1383",
}
actual, err := client.CreateImageDirectUploadURL(context.Background(), AccountIdentifier(testAccountID), input)
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestListImages(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"images": [
{
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"filename": "avatar.png",
"meta": {
"meta": "metaID"
},
"requireSignedURLs": true,
"variants": [
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail"
],
"uploaded": "2014-01-02T02:20:00Z"
}
]
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1", handler)
want := []Image{expectedImageStruct}
actual, err := client.ListImages(context.Background(), AccountIdentifier(testAccountID), ListImagesParams{})
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestImageDetails(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "ZxR0pLaXRldlBtaFhhO2FiZGVnaA",
"filename": "avatar.png",
"meta": {
"meta": "metaID"
},
"requireSignedURLs": true,
"variants": [
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/hero",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/original",
"https://imagedelivery.net/MTt4OTd0b0w5aj/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/thumbnail"
],
"uploaded": "2014-01-02T02:20:00Z"
}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1/ZxR0pLaXRldlBtaFhhO2FiZGVnaA", handler)
want := expectedImageStruct
actual, err := client.GetImage(context.Background(), AccountIdentifier(testAccountID), "ZxR0pLaXRldlBtaFhhO2FiZGVnaA")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestBaseImage(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
w.Header().Set("content-type", "image/png")
_, _ = w.Write([]byte{})
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1/ZxR0pLaXRldlBtaFhhO2FiZGVnaA/blob", handler)
want := []byte{}
actual, err := client.GetBaseImage(context.Background(), AccountIdentifier(testAccountID), "ZxR0pLaXRldlBtaFhhO2FiZGVnaA")
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
func TestDeleteImage(t *testing.T) {
setup()
defer teardown()
handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {}
}
`)
}
mux.HandleFunc("/accounts/"+testAccountID+"/images/v1/ZxR0pLaXRldlBtaFhhO2FiZGVnaA", handler)
err := client.DeleteImage(context.Background(), AccountIdentifier(testAccountID), "ZxR0pLaXRldlBtaFhhO2FiZGVnaA")
require.NoError(t, err)
}
type fakeFile struct {
*bytes.Buffer
}
func (f fakeFile) Close() error {
return nil
}
type imageMultipartUpload struct {
// this is for testing, never read an entire file into memory,
// especially when being done on a per-http request basis.
File []byte
Url string
RequireSignedURLs bool
Metadata map[string]interface{}
}
func parseImageMultipartUpload(r *http.Request) (imageMultipartUpload, error) {
var u imageMultipartUpload
mdBytes, err := getImageFormValue(r, "metadata")
if err != nil {
if !strings.HasPrefix(err.Error(), "no value found for key") {
return u, err
}
}
if mdBytes != nil {
err = json.Unmarshal(mdBytes, &u.Metadata)
if err != nil {
return u, err
}
}
rsuBytes, err := getImageFormValue(r, "requireSignedURLs")
if err != nil {
if !strings.HasPrefix(err.Error(), "no value found for key") {
return u, err
}
}
if rsuBytes != nil {
if bytes.Equal(rsuBytes, []byte("true")) {
u.RequireSignedURLs = true
}
}
if _, ok := r.MultipartForm.Value["url"]; ok {
urlBytes, err := getImageFormValue(r, "url")
if err != nil {
if !strings.HasPrefix(err.Error(), "no value found for key") {
return u, err
}
}
if urlBytes != nil {
u.Url = string(urlBytes)
}
} else {
f, _, err := r.FormFile("file")
if err != nil {
return u, err
}
defer f.Close()
u.File, err = io.ReadAll(f)
if err != nil {
return u, err
}
}
return u, nil
}
// See getFormValue for more information, the only difference between
// getFormValue and this one is the max memory.
func getImageFormValue(r *http.Request, key string) ([]byte, error) {
err := r.ParseMultipartForm(10 * 1024 * 1024)
if err != nil {
return nil, err
}
if values, ok := r.MultipartForm.Value[key]; ok {
return []byte(values[0]), nil
}
if fileHeaders, ok := r.MultipartForm.File[key]; ok {
file, err := fileHeaders[0].Open()
if err != nil {
return nil, err
}
return io.ReadAll(file)
}
return nil, fmt.Errorf("no value found for key %v", key)
}