// Copyright 2021 Tencent Inc. All rights reserved.

package core_test

import (
	"bytes"
	"context"
	"crypto"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strconv"
	"strings"
	"testing"
	"time"

	"git.nanodreamtech.com/sg/wechatpay-go/core"
	"git.nanodreamtech.com/sg/wechatpay-go/core/auth"
	"git.nanodreamtech.com/sg/wechatpay-go/core/auth/signers"
	"git.nanodreamtech.com/sg/wechatpay-go/core/auth/verifiers"
	"git.nanodreamtech.com/sg/wechatpay-go/core/option"
	"git.nanodreamtech.com/sg/wechatpay-go/utils"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	testMchID                   = "example-mchid"
	testCertificateSerialNumber = "example-sn"

	// NOTE: 以下是随机生成的测试密钥,请勿用于生产环境
	testPrivateKey = "-----BEGIN TESTING KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkxOav8p5RFFmN\n7hjLGrNtXPYgCd0Zuvxabv+IWl1HVkWi/1iVqac+XwKH/ZeCYDURqZ6P0iiq8NBd\npygoeJiQM+qzaTV3alNXdLcpcaQSNqJ16a7Z2Co5LgOkBJHIF1qUWf+BpAtyLqo5\niGUQ47w3IWQHtfpW7RrECiNsI7kGKuSc4U2JX/gxrFG7ugpRA9Gp1eF0/wBMWSKV\nmKveKERvAueaTKEujpN4lctoO1wsMW93nuFNH1gtHPYmkaZaJS88GEp0VYJIcOpX\n2HlVPPiWx+0KAndcqMLVQ+qTk21tivpjuqPDstxcT9cXn/3CzSEkYrKkMLpjpSl/\nIn+amHddAgMBAAECggEADtQRlsAU82MLdDR7UrwCbdMx60w387raPyFCKflH78WZ\n2sN0K3PrMzfFuItf+UHDROWo+XSGaGvntKX4fTvtLv0dICxVvXt6KKK+YSJzC5iT\nIl13eO91TVQQy9AFdqZzZmp7DiW/SfVdKHRX9B8qryN4JyF/eBc6k23+JhtI6X8J\nrPeXGcw/Muo2cvoZ6oarRvzKU7pDivZADavAsgGwi+QwZUtrpUhDbJxWxoasCrWz\n6X24D+JbKndf/AeyHC2mqoAwTYdQgkkPHLJWGsqAt/GHtHLmZHIPc0dXfksP5omX\nOIii34YNud1j2/X0ryxoRBKPGIolV5Tyyh9PX75UAQKBgQDa/oPFUNsBo+NkfE4r\nMry+mnAeBM06vZh65acbHu3prqzn7tzfQR5rF9nIEUjNkach+ZeTnUxKfX4TwfiL\nibbM1Epb+yhEpSlA9HhY1AGhdNKpFbq63oVzS+lwpmLcXFLHhOYw8GrnYH9lcE+E\nYCqFcuk4t/y3rU/8Y5GhZKZ53QKBgQDAnKk6CEgnSkXfZLmzVtoM/5hnI0GEDOUo\nV35alqvgdJtCiPs4C03snYLVHqHjAknGzLGONAQ6h9au2qwcHy1qH/noq151u92b\nbCrKmghnm2SoIgCaZ7i2scWm6NM9Da60H662WxjaKcZMnUClm+G+Irl9m5cm1i3V\n56ZU63nbgQKBgHyAlFO6mzg8f4via+J9TvciADngyvjpT2YXaECv/dyL9TtK/oFi\nmTOTdLocsYJFm3piVv2SQQxcejArZ+2U1rtuufO/P25/Y4vNMRp3NZIgQ5/jfay9\n06rv7oCf57aWOm26LdCG7pAquWLnTh3ZOnNyGAup9mBKhR3dUa8q9MZ1AoGATZGJ\n0VYugKw3sXymEKRkkiGJJdgb9WsgCnwZ5a+SLoWnVUdHLM3YpvbUDrIUbhCo14ft\n5Z/rKAs2mRp1f6nKp1eTVHFXTEDJQWNxZEBeLCN3iQKQjZ5B1EmJmOtgztCoz9+G\ng+fx/UIfmxElTMyXP/RKEVzMpZZRxThSUxa174ECgYEAsviAmgBskM2ibtrA8tNW\njHdgut0xAtIJIIHlYmtksWgAWD4cPtCg7HPurXqBKxMogH/ZsZc6/5PpOIRYBNl0\nEebH0MZ/yiEOrmgsFJ1gKWk3fx8/yLQBlhn32AIhj7wmcFwzi/4hcwihHRCjS7t5\nQhVpKswxQyxqeIdQw0CgKZY=\n-----END TESTING KEY-----"

	testWechatPrivateKeyStr  = "-----BEGIN TESTING KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDzbMaiMbCzJ11sZ2r7/XisolGu1pVpWvnv1PVOAWZZAWr/WcNNuLni8ddJkIs8NdTz+iAHtb9XMNG4hj7d10cy8QE6QG8YUef1fGb47Wee3DjJRk8N9lWyPDAy9AW70yYWItl/05XgkGt2eJuoU5CcZ+Cy0u2nAXxEGs2Z4Fg/100Ylcyq4GrimjngIyUFLnowOcZltJUQSw/Iu63V0BEh9PMNnXhKkwS2xfkA2nZRzSgzpMvX74v8F7Zf+HkyrHHzwY/7YUWg3pSj3GD0xKsJOwzz0MLlS8uIdLj1lKGzzt1ROgwe0sM5LL5XMfmbjDhcVBmyxQI80WiNaC281tVhAgMBAAECggEBAJ134Wrs0Ayky2ej4u5OAvFSM5rxj0fPJV3DGkiy2R18sFWtII03kXBA1+7rxVZW0IJfbLbwGG3z08cVeLeTWqiWhR/ErNlDqtT/+7DOCrkWZtm1VNCIaNla3Ccp+keNiNbLBn4NRqg1ZH8H+FHEdQjonc+waTIe4N9Bo30GRrBMeMbAgN8mwQhZ+6R23j3GsrJOViFpPRgGhih4aEAORxU+DWl22vklxO8lnDzSwfnXvNDvapnPaA8VXekkThxABq3p/ggv5MI1QPYl8BU5PWd6AJlPs/u2nzFGmCgVufHMjdszWYd3hbj5EmhlL1VMs4uxhCC8OM+ypbnx0CmBB5ECgYEA+W3KJciw4qG6XqWgjK9hkgiZrO8z3tu6tQ6ge28f3BxYEbGUab8bKKzacbYODRiRCM5oKcrXfTqP6IbqUoESiuoz0CUPbShp7k00wXKd7BH8LoIECDbctz77NG0KBE31OGEJw4hm762M14V9nU0KDHGuud1CH1fqivTGG0g2HiUCgYEA+dZ+e5iSvin+omXkr3Vhwf3kutX+GNkKm5LrWZNmWybOKw/K1YFvESpfl1b6YgjA/qUXj0tbOumFQw6e/OLfxIvK/dtkd+7pbSC4T9w8rH7zgjYdJ030Nyv3UGfHDbES+z9MDMo5h+3RKsLN2bp1JcXp6vht9CiXDYm1df3O340CgYB118Qg08+WU1iM7O2MajPL3dpVFPJJwUBV2GJDzv2bbZzCR0baKxr2vau6+4tp7ohfQ718uUPT+34QGuXMMwUCsqHmHgxKw0RA/SMGnlM0PE8L3gtvohPnU481dqq72+UWTOpjAie35yPak0wErGgp9u/ZCkr6Kfw6yGhsbVJ8LQKBgEBLxS1FrK4n3JIqqtnE2a21C4JRxBzc7m/vNYZN+s+GgxRt8gNUViMSxpsKFVHZcuGV1yRXflkA8/y37I6kTHYmi80dAxQidgxRmV1kDnFOEpj2GDafRzRTqkgVDRMm+P2T4pyABqJGv8fDbnqUE8Xu0y5XVOS69XTUddCxyuWZAoGAbpF6JOh6B7OV4XRTDm98Z8OPYmYd9JQ4xt8bqsG9LdzvhU/PI4zwIaKDqZ8vzCI+r8TOrC6SBEfjAe6o2FEExmFWTjBAVCp+Qvnz+Pj7d+WP3kCX/B62IZckVhdV3a2frMTBPvAh8XdENdvsu4DsWJBCA54GLU3wdUa/FO0RUsA=\n-----END TESTING KEY-----\n"
	testWechatCertificateStr = "-----BEGIN CERTIFICATE-----\nMIID1TCCAr2gAwIBAgIRAJ8qZJYAQUwUheimQ8sQNZMwDQYJKoZIhvcNAQELBQAw\nXjELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSswKQYDVQQLEyJNeVNTTCBU\nZXN0IFJTQSAtIEZvciB0ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20w\nHhcNMjEwNjI3MTMwNTM2WhcNMjIwNjI3MTMwNTM2WjAkMQswCQYDVQQGEwJDTjEV\nMBMGA1UEAxMMd2VjaGF0cGF5LWdvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA82zGojGwsyddbGdq+/14rKJRrtaVaVr579T1TgFmWQFq/1nDTbi54vHX\nSZCLPDXU8/ogB7W/VzDRuIY+3ddHMvEBOkBvGFHn9Xxm+O1nntw4yUZPDfZVsjww\nMvQFu9MmFiLZf9OV4JBrdnibqFOQnGfgstLtpwF8RBrNmeBYP9dNGJXMquBq4po5\n4CMlBS56MDnGZbSVEEsPyLut1dARIfTzDZ14SpMEtsX5ANp2Uc0oM6TL1++L/Be2\nX/h5Mqxx88GP+2FFoN6Uo9xg9MSrCTsM89DC5UvLiHS49ZShs87dUToMHtLDOSy+\nVzH5m4w4XFQZssUCPNFojWgtvNbVYQIDAQABo4HHMIHEMA4GA1UdDwEB/wQEAwIH\ngDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSMEGDAWgBQogSYF0TQaP8FzD7uT\nzxUcPwO/fzBjBggrBgEFBQcBAQRXMFUwIQYIKwYBBQUHMAGGFWh0dHA6Ly9vY3Nw\nLm15c3NsLmNvbTAwBggrBgEFBQcwAoYkaHR0cDovL2NhLm15c3NsLmNvbS9teXNz\nbHRlc3Ryc2EuY3J0MBcGA1UdEQQQMA6CDHdlY2hhdHBheS1nbzANBgkqhkiG9w0B\nAQsFAAOCAQEAjh4oxMcJqsVaN5/aA+4+NSfV9wR4uzTVtAyL/dApymZn6Wjknd85\nDltekcTflNP84bDiFEE3Ls3RYatjRx9pWeW7QbpdYvfDtWuxL5dhzRYtUO83z8wT\n+/sceeyNOQAWGD6Gt7Aw7yb7bIZ5slcZYepqdKSHyMnn06CCNRZtDVTuQYRqnmoh\nCaK5RNe4lYM/hncMgddE/DugTxzh5NUMpAY4xAsqOofkVmptX3trVZVILPglJ6nQ\n5dALCpp2UCuxikwFdEpvvGIC2qZQv5jmemFLCDIQZ227GUZ/EcbuTtAdQYHUnGJT\nvrFBSDe4FbKwljUmrccD/LkR9FmPn6gWKA==\n-----END CERTIFICATE-----\n"

	fileName = "picture.jpeg"

	responseBody   = `{"hello":"client"}`
	testRequestUri = "/v3/resource?first=this+is+a+field&second=was+it+clear+%28already%29%3F"
)

var (
	privateKey           *rsa.PrivateKey
	wechatPayPrivateKey  *rsa.PrivateKey
	wechatPayCertificate *x509.Certificate
	signer               auth.Signer
	verifier             auth.Verifier
	ctx                  context.Context
)

type signParameter map[string]string

func init() {
	ctx = context.Background()

	var err error
	privateKey, err = utils.LoadPrivateKey(testingKey(testPrivateKey))
	if err != nil {
		panic(fmt.Errorf("load merchant testing key err:%s", err.Error()))
	}
	wechatPayCertificate, err = utils.LoadCertificate(testWechatCertificateStr)
	if err != nil {
		panic(fmt.Errorf("generate wechatpay testing certificate err:%s", err.Error()))
	}
	wechatPayPrivateKey, err = utils.LoadPrivateKey(testingKey(testWechatPrivateKeyStr))
	if err != nil {
		panic(fmt.Errorf("generate wechatpay testing key err:%s", err.Error()))
	}
}

func writeResponse(w http.ResponseWriter) {
	writeSignature(w, responseBody)
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, responseBody)
}

func writeSignature(w http.ResponseWriter, body string) {
	w.Header().Set("Request-Id", "0")
	w.Header().Set("Wechatpay-Serial", utils.GetCertificateSerialNumber(*wechatPayCertificate))

	nonce := "this-is-a-nonce"
	w.Header().Set("Wechatpay-Nonce", nonce)

	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	w.Header().Set("Wechatpay-Timestamp", timestamp)

	signature, _ := utils.SignSHA256WithRSA(
		fmt.Sprintf("%s\n%s\n%s\n", timestamp, nonce, body), wechatPayPrivateKey)
	w.Header().Set("Wechatpay-Signature", signature)
}

func parseAuthorization(t *testing.T, authorization string) (schema string, param signParameter) {
	m := make(signParameter)

	s1 := strings.Split(authorization, " ")
	assert.Equal(t, len(s1), 2)

	s2 := strings.Split(s1[1], ",")
	assert.Equal(t, len(s2), 5)

	for _, v := range s2 {
		s3 := strings.SplitN(v, "=", 2)
		assert.Equal(t, len(s3), 2)
		pk := s3[0]
		pv := strings.Trim(s3[1], "\"")
		m[pk] = pv
	}

	return s1[0], m
}

func assertAuthorization(t *testing.T, schema, method, uri string, params signParameter, body []byte) {
	assert.Equal(t, schema, "WECHATPAY2-SHA256-RSA2048")
	assert.Equal(t, params["mchid"], testMchID)
	assert.Equal(t, params["serial_no"], testCertificateSerialNumber)

	message := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n",
		method,
		uri,
		params["timestamp"],
		params["nonce_str"],
		body)
	hashed := sha256.Sum256([]byte(message))
	signBytes, err := base64.StdEncoding.DecodeString(params["signature"])
	assert.NoError(t, err)
	assert.NoError(t, rsa.VerifyPKCS1v15(&privateKey.PublicKey, crypto.SHA256, hashed[:], signBytes))
}

func TestGet(t *testing.T) {
	opts := []core.ClientOption{
		option.WithMerchantCredential(testMchID, testCertificateSerialNumber, privateKey),
		option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}),
	}
	client, err := core.NewClient(ctx, opts...)
	require.NoError(t, err)

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, r.Method, "GET")
		assert.Equal(t, r.RequestURI, testRequestUri)

		schema, params := parseAuthorization(t, r.Header.Get("Authorization"))
		assertAuthorization(t, schema, r.Method, r.RequestURI, params, make([]byte, 0))

		writeResponse(w)
	}))
	defer ts.Close()

	result, err := client.Get(ctx, ts.URL+testRequestUri)
	assert.NoError(t, err)
	body, err := ioutil.ReadAll(result.Response.Body)
	assert.NoError(t, err)
	assert.Equal(t, string(body), responseBody)
}

type testData struct {
	StockID           string `json:"stock_id"`
	StockCreatorMchID string `json:"stock_creator_mchid"`
	OutRequestNo      string `json:"out_request_no"`
	AppID             string `json:"appid"`
}

func TestPost(t *testing.T) {
	opts := []core.ClientOption{
		option.WithMerchantCredential(testMchID, testCertificateSerialNumber, privateKey),
		option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}),
	}
	client, err := core.NewClient(ctx, opts...)
	require.NoError(t, err)
	data := &testData{
		StockID:           "xxx",
		StockCreatorMchID: "xxx",
		OutRequestNo:      "xxx",
		AppID:             "xxx",
	}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, r.Method, "POST")
		assert.Equal(t, r.RequestURI, testRequestUri)

		schema, params := parseAuthorization(t, r.Header.Get("Authorization"))
		body, _ := ioutil.ReadAll(r.Body)
		assertAuthorization(t, schema, r.Method, r.RequestURI, params, body)

		writeResponse(w)
	}))
	defer ts.Close()

	result, err := client.Post(ctx, ts.URL+testRequestUri, data)
	assert.NoError(t, err)
	body, err := ioutil.ReadAll(result.Response.Body)
	assert.NoError(t, err)
	assert.Equal(t, string(body), responseBody)
}

func TestRequest(t *testing.T) {
	opts := []core.ClientOption{
		option.WithMerchantCredential(testMchID, testCertificateSerialNumber, privateKey),
		option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}),
	}
	client, err := core.NewClient(ctx, opts...)
	require.NoError(t, err)

	data := &testData{
		StockID:           "xxx",
		StockCreatorMchID: "xxx",
		OutRequestNo:      "xxx",
		AppID:             "xxx",
	}

	tt := []struct {
		method      string
		uri         string
		contentType string
		body        interface{}
		header      http.Header
	}{
		{
			http.MethodGet,
			"/v3/get",
			"",
			nil,
			http.Header{"My-Id": {"1234"}},
		},
		{
			http.MethodPost,
			testRequestUri,
			"application/json",
			data,
			http.Header{"My-Id": {"1234"}},
		},
		{
			http.MethodDelete,
			"/v3/delete",
			"",
			nil,
			nil,
		},
		{
			http.MethodPut,
			"/v3/put",
			"",
			data,
			http.Header{"My-Id": {"1234"}},
		},
		{
			http.MethodPatch,
			"/v3/patch",
			"",
			data,
			nil,
		},
	}

	for _, test := range tt {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, test.method, r.Method)
			assert.Equal(t, test.uri, r.RequestURI)
			if test.header != nil {
				assert.Equal(t, "1234", r.Header.Get("My-Id"))
			}

			schema, params := parseAuthorization(t, r.Header.Get("Authorization"))
			body, _ := ioutil.ReadAll(r.Body)
			assertAuthorization(t, schema, r.Method, r.RequestURI, params, body)
			assert.Equal(t, "9F2A649600414C1485E8A643CB103593", r.Header.Get("Wechatpay-Serial"))

			if test.body != nil {
				assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

				var req testData
				err = json.Unmarshal(body, &req)
				assert.NoError(t, err)
				assert.Equal(t, data, &req)
			}

			writeResponse(w)
		}))

		testUrl, err := url.Parse(ts.URL + test.uri)
		assert.NoError(t, err)

		result, err := client.Request(
			ctx,
			test.method,
			ts.URL+testUrl.Path,
			test.header,
			testUrl.Query(),
			test.body,
			test.contentType,
		)
		assert.NoError(t, err)
		body, err := ioutil.ReadAll(result.Response.Body)
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.Response.StatusCode)
		assert.Equal(t, responseBody, string(body))
		ts.Close()
	}
}

func TestClientVerifyFail(t *testing.T) {
	opts := []core.ClientOption{
		option.WithMerchantCredential(testMchID, testCertificateSerialNumber, privateKey),
		option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}),
	}
	client, err := core.NewClient(ctx, opts...)
	require.NoError(t, err)

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Request-Id", "0")
		w.Header().Set("Wechatpay-Serial", utils.GetCertificateSerialNumber(*wechatPayCertificate))

		nonce := "this-is-a-nonce"
		w.Header().Set("Wechatpay-Nonce", nonce)

		timestamp := strconv.FormatInt(time.Now().Unix(), 10)
		w.Header().Set("Wechatpay-Timestamp", timestamp)

		w.Header().Set("Wechatpay-Signature", "AABB")
		w.WriteHeader(http.StatusOK)
	}))
	defer ts.Close()

	_, err = client.Get(ctx, ts.URL+testRequestUri)
	assert.Contains(t, err.Error(), "verify fail")
}

func TestClientNoAuth(t *testing.T) {
	opts := []core.ClientOption{
		option.WithMerchantCredential(testMchID, testCertificateSerialNumber, privateKey),
		option.WithWechatPayCertificate([]*x509.Certificate{wechatPayCertificate}),
	}
	client, err := core.NewClient(ctx, opts...)
	require.NoError(t, err)

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(401)
		w.Header().Set("Request-Id", "0")
		fmt.Fprint(w, `{"code":"SIGN_ERROR","message":"sign error"}`)
	}))
	defer ts.Close()

	_, err = client.Get(ctx, ts.URL+testRequestUri)
	apiError, ok := err.(*core.APIError)
	assert.True(t, ok)
	assert.Equal(t, 401, apiError.StatusCode)
	assert.Equal(t, "SIGN_ERROR", apiError.Code)
	assert.Equal(t, "sign error", apiError.Message)
}

type meta struct {
	FileName string `json:"filename" binding:"required"` // 商户上传的媒体图片的名称,商户自定义,必须以JPG、BMP、PNG为后缀。
	Sha256   string `json:"sha256" binding:"required"`   // 图片文件的文件摘要,即对图片文件的二进制内容进行sha256计算得到的值。
}

func TestClient_Upload(t *testing.T) {
	// 如果你有自定义的Signer或者Verifier
	signer = &signers.SHA256WithRSASigner{
		MchID:               testMchID,
		PrivateKey:          privateKey,
		CertificateSerialNo: testCertificateSerialNumber,
	}

	verifier = verifiers.NewSHA256WithRSAVerifier(
		core.NewCertificateMap(
			map[string]*x509.Certificate{utils.GetCertificateSerialNumber(*wechatPayCertificate): wechatPayCertificate},
		),
	)

	client, err := core.NewClient(ctx, option.WithSigner(signer), option.WithVerifier(verifier))
	require.NoError(t, err)
	pictureBytes := make([]byte, 1024)
	// 随机的数据充当图片数据
	rand.Read(pictureBytes)
	// 计算文件序列化后的sha256
	h := sha256.New()
	_, err = h.Write(pictureBytes)
	assert.NoError(t, err)
	metaObject := &meta{}
	pictureSha256 := h.Sum(nil)
	metaObject.FileName = fileName
	metaObject.Sha256 = fmt.Sprintf("%x", string(pictureSha256))
	metaByte, _ := json.Marshal(metaObject)
	reqBody := &bytes.Buffer{}
	writer := multipart.NewWriter(reqBody)
	err = core.CreateFormField(writer, "meta", "application/json", metaByte)
	assert.NoError(t, err)
	err = core.CreateFormFile(writer, fileName, "image/jpg", pictureBytes)
	assert.NoError(t, err)
	err = writer.Close()
	assert.NoError(t, err)

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, r.Method, "POST")
		assert.Equal(t, r.RequestURI, testRequestUri)

		mr, err := r.MultipartReader()
		assert.NoError(t, err)

		var body []byte
		for {
			p, err := mr.NextPart()
			if err == io.EOF {
				break
			}
			assert.NoError(t, err)
			if p.FormName() == "meta" {
				body, _ = ioutil.ReadAll(p)
				assert.Equal(t, metaByte, body)
			} else if p.FormName() == "file" {
				slurp, _ := ioutil.ReadAll(p)
				assert.Equal(t, pictureBytes, slurp)
			}
		}

		schema, params := parseAuthorization(t, r.Header.Get("Authorization"))
		assertAuthorization(t, schema, r.Method, r.RequestURI, params, body)

		writeResponse(w)
	}))
	defer ts.Close()

	result, err := client.Upload(
		ctx,
		ts.URL+testRequestUri,
		string(metaByte),
		reqBody.String(),
		writer.FormDataContentType())
	assert.NoError(t, err)
	if result.Response.Body != nil {
		defer result.Response.Body.Close()
	}
	body, err := ioutil.ReadAll(result.Response.Body)
	assert.NoError(t, err)
	t.Log(string(body))
	assert.Equal(t, string(body), responseBody)
}

func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }