package weapp

import (
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestAbnormalConfirm(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/confirm_return" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("access_token can not be empty")
		}

		params := struct {
			ShopID       string `json:"shopid"`        // 商家id, 由配送公司分配的appkey
			ShopOrderID  string `json:"shop_order_id"` // 唯一标识订单的 ID,由商户生成
			ShopNo       string `json:"shop_no"`       // 商家门店编号, 在配送公司登记,如果只有一个门店,可以不填
			DeliverySign string `json:"delivery_sign"` // 用配送公司提供的appSecret加密的校验串
			WaybillID    string `json:"waybill_id"`    // 配送单id
			Remark       string `json:"remark"`        // 备注
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}
		if params.ShopID == "" {
			t.Error("Response column shopid can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column shop_order_id can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Response column shop_no can not be empty")
		}

		if params.DeliverySign == "" {
			t.Error("Response column delivery_sign can not be empty")
		}

		if params.WaybillID == "" {
			t.Error("Response column waybill_id can not be empty")
		}
		if params.Remark == "" {
			t.Error("Response column remark can not be empty")
		}
		w.WriteHeader(http.StatusOK)

		raw := `{
			"errcode": 1,
			"errmsg": "ok"
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
			"shopid": "123456",
			"shop_order_id": "123456",
			"shop_no": "shop_no_111",
			"waybill_id": "123456",
			"remark": "remark",
			"delivery_sign": "123456"
		}`

	confirmer := new(AbnormalConfirmer)
	err := json.Unmarshal([]byte(raw), confirmer)
	if err != nil {
		t.Fatal(err)
	}

	_, err = confirmer.confirm(ts.URL+apiAbnormalConfirm, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}
}

func TestAddDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/add" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("access_token can not be empty")
		}

		params := struct {
			DeliveryToken string `json:"delivery_token"` // 预下单接口返回的参数,配送公司可保证在一段时间内运费不变
			ShopID        string `json:"shopid"`         // 商家id, 由配送公司分配的appkey
			ShopOrderID   string `json:"shop_order_id"`  // 唯一标识订单的 ID,由商户生成
			ShopNo        string `json:"shop_no"`        // 商家门店编号, 在配送公司登记,如果只有一个门店,可以不填
			DeliverySign  string `json:"delivery_sign"`  // 用配送公司提供的appSecret加密的校验串
			DeliveryID    string `json:"delivery_id"`    // 配送公司ID
			OpenID        string `json:"openid"`         // 下单用户的openid
			SubBizID      string `json:"sub_biz_id"`     // 子商户id,区分小程序内部多个子商户
			Sender        struct {
				Name           string  `json:"name"`            // 姓名,最长不超过256个字符
				City           string  `json:"city"`            // 城市名称,如广州市
				Address        string  `json:"address"`         // 地址(街道、小区、大厦等,用于定位)
				AddressDetail  string  `json:"address_detail"`  // 地址详情(楼号、单元号、层号)
				Phone          string  `json:"phone"`           // 电话/手机号,最长不超过64个字符
				Lng            float64 `json:"lng"`             // 经度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,确到小数点后6位
				Lat            float64 `json:"lat"`             // 纬度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,精确到小数点后6位)
				CoordinateType uint8   `json:"coordinate_type"` // 坐标类型,0:火星坐标(高德,腾讯地图均采用火星坐标) 1:百度坐标
			} `json:"sender"` // 发件人信息,闪送、顺丰同城急送必须填写,美团配送、达达,若传了shop_no的值可不填该字段
			Receiver struct {
				Name           string  `json:"name"`            // 姓名,最长不超过256个字符
				City           string  `json:"city"`            // 城市名称,如广州市
				Address        string  `json:"address"`         // 地址(街道、小区、大厦等,用于定位)
				AddressDetail  string  `json:"address_detail"`  // 地址详情(楼号、单元号、层号)
				Phone          string  `json:"phone"`           // 电话/手机号,最长不超过64个字符
				Lng            float64 `json:"lng"`             // 经度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,确到小数点后6位
				Lat            float64 `json:"lat"`             // 纬度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,精确到小数点后6位)
				CoordinateType uint8   `json:"coordinate_type"` // 坐标类型,0:火星坐标(高德,腾讯地图均采用火星坐标) 1:百度坐标
			} `json:"receiver"` // 收件人信息
			Cargo struct {
				GoodsValue  float64 `json:"goods_value"`  // 货物价格,单位为元,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-5000]
				GoodsHeight float64 `json:"goods_height"` // 货物高度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-45]
				GoodsLength float64 `json:"goods_length"` // 货物长度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-65]
				GoodsWidth  float64 `json:"goods_width"`  // 货物宽度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-50]
				GoodsWeight float64 `json:"goods_weight"` // 货物重量,单位为kg,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-50]
				GoodsDetail struct {
					Goods []struct {
						Count uint    `json:"good_count"` // 货物数量
						Name  string  `json:"good_name"`  // 货品名称
						Price float32 `json:"good_price"` // 货品单价,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数)
						Unit  string  `json:"good_unit"`  // 货品单位,最长不超过20个字符
					} `json:"goods"` // 货物交付信息,最长不超过100个字符
				} `json:"goods_detail"` // 货物详情,最长不超过10240个字符
				GoodsPickupInfo   string `json:"goods_pickup_info"`   // 货物取货信息,用于骑手到店取货,最长不超过100个字符
				GoodsDeliveryInfo string `json:"goods_delivery_info"` // 货物交付信息,最长不超过100个字符
				CargoFirstClass   string `json:"cargo_first_class"`   // 品类一级类目
				CargoSecondClass  string `json:"cargo_second_class"`  // 品类二级类目
			} `json:"cargo"` // 货物信息
			OrderInfo struct {
				DeliveryServiceCode  string  `json:"delivery_service_code"`  // 配送服务代码 不同配送公司自定义,微信侧不理解
				OrderType            uint8   `json:"order_type"`             // 订单类型, 0: 即时单 1 预约单,如预约单,需要设置expected_delivery_time或expected_finish_time或expected_pick_time
				ExpectedDeliveryTime uint    `json:"expected_delivery_time"` // 期望派单时间(顺丰同城急送、达达、支持),unix-timestamp
				ExpectedFinishTime   uint    `json:"expected_finish_time"`   // 期望送达时间(顺丰同城急送、美团配送支持),unix-timestamp
				ExpectedPickTime     uint    `json:"expected_pick_time"`     // 期望取件时间(闪送支持),unix-timestamp
				PoiSeq               string  `json:"poi_seq"`                // 门店订单流水号,建议提供,方便骑手门店取货,最长不超过32个字符
				Note                 string  `json:"note"`                   // 备注,最长不超过200个字符
				OrderTime            uint    `json:"order_time"`             // 用户下单付款时间
				IsInsured            uint8   `json:"is_insured"`             // 是否保价,0,非保价,1.保价
				DeclaredValue        float64 `json:"declared_value"`         // 保价金额,单位为元,精确到分
				Tips                 float64 `json:"tips"`                   // 小费,单位为元, 下单一般不加小费
				IsDirectDelivery     uint    `json:"is_direct_delivery"`     // 是否选择直拿直送(0:不需要;1:需要。选择直拿直送后,同一时间骑手只能配送此订单至完成,配送费用也相应高一些,闪送必须选1,达达可选0或1,其余配送公司不支持直拿直送)
				CashOnDelivery       uint    `json:"cash_on_delivery"`       // 骑手应付金额,单位为元,精确到分
				CashOnPickup         uint    `json:"cash_on_pickup"`         // 骑手应收金额,单位为元,精确到分
				RiderPickMethod      uint8   `json:"rider_pick_method"`      // 物流流向,1:从门店取件送至用户;2:从用户取件送至门店
				IsFinishCodeNeeded   uint8   `json:"is_finish_code_needed"`  // 收货码(0:不需要;1:需要。收货码的作用是:骑手必须输入收货码才能完成订单妥投)
				IsPickupCodeNeeded   uint8   `json:"is_pickup_code_needed"`  // 取货码(0:不需要;1:需要。取货码的作用是:骑手必须输入取货码才能从商家取货)
			} `json:"order_info"` // 订单信息
			Shop struct {
				WxaPath    string `json:"wxa_path"`    // 商家小程序的路径,建议为订单页面
				ImgURL     string `json:"img_url"`     // 商品缩略图 url
				GoodsName  string `json:"goods_name"`  // 商品名称
				GoodsCount uint   `json:"goods_count"` // 商品数量
			} `json:"shop"` // 商品信息,会展示到物流通知消息中
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}

		if params.DeliveryToken == "" {
			t.Error("Response column 'delivery_token' can not be empty")
		}
		if params.ShopID == "" {
			t.Error("Response column 'shopid' can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column 'shop_order_id' can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Response column 'shop_no' can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Response column 'delivery_sign' can not be empty")
		}
		if params.DeliveryID == "" {
			t.Error("Response column 'delivery_id' can not be empty")
		}
		if params.OpenID == "" {
			t.Error("Response column 'openid' can not be empty")
		}
		if params.SubBizID == "" {
			t.Error("Response column 'sub_biz_id' can not be empty")
		}

		if params.Sender.Name == "" {
			t.Error("Param 'sender.name' can not be empty")
		}
		if params.Sender.City == "" {
			t.Error("Param 'sender.city' can not be empty")
		}
		if params.Sender.Address == "" {
			t.Error("Param 'sender.address' can not be empty")
		}
		if params.Sender.AddressDetail == "" {
			t.Error("Param 'sender.address_detail' can not be empty")
		}
		if params.Sender.Phone == "" {
			t.Error("Param 'sender.phone' can not be empty")
		}
		if params.Sender.Lng == 0 {
			t.Error("Param 'sender.lng' can not be empty")
		}
		if params.Sender.Lat == 0 {
			t.Error("Param 'sender.lat' can not be empty")
		}
		if params.Sender.CoordinateType == 0 {
			t.Error("Param 'sender.coordinate_type' can not be empty")
		}

		if params.Receiver.Name == "" {
			t.Error("Param 'receiver.name' can not be empty")
		}
		if params.Receiver.City == "" {
			t.Error("Param 'receiver.city' can not be empty")
		}
		if params.Receiver.Address == "" {
			t.Error("Param 'receiver.address' can not be empty")
		}
		if params.Receiver.AddressDetail == "" {
			t.Error("Param 'receiver.address_detail' can not be empty")
		}
		if params.Receiver.Phone == "" {
			t.Error("Param 'receiver.phone' can not be empty")
		}
		if params.Receiver.Lng == 0 {
			t.Error("Param 'receiver.lng' can not be empty")
		}
		if params.Receiver.Lat == 0 {
			t.Error("Param 'receiver.lat' can not be empty")
		}
		if params.Receiver.CoordinateType == 0 {
			t.Error("Param 'receiver.coordinate_type' can not be empty")
		}
		if params.Cargo.GoodsValue == 0 {
			t.Error("Param 'cargo.goods_value' can not be empty")
		}
		if params.Cargo.GoodsHeight == 0 {
			t.Error("Param 'cargo.goods_height' can not be empty")
		}
		if params.Cargo.GoodsLength == 0 {
			t.Error("Param 'cargo.goods_length' can not be empty")
		}
		if params.Cargo.GoodsWidth == 0 {
			t.Error("Param 'cargo.goods_width' can not be empty")
		}
		if params.Cargo.GoodsWeight == 0 {
			t.Error("Param 'cargo.goods_weight' can not be empty")
		}
		if params.Cargo.CargoFirstClass == "" {
			t.Error("Param 'cargo.cargo_first_class' can not be empty")
		}
		if params.Cargo.CargoSecondClass == "" {
			t.Error("Param 'cargo.cargo_second_class' can not be empty")
		}
		if len(params.Cargo.GoodsDetail.Goods) > 0 {
			if params.Cargo.GoodsDetail.Goods[0].Count == 0 {
				t.Error("Param 'cargo.goods_detail.goods.good_count' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Name == "" {
				t.Error("Param 'cargo.goods_detail.goods.good_name' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Price == 0 {
				t.Error("Param 'cargo.goods_detail.goods.good_price' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Unit == "" {
				t.Error("Param 'cargo.goods_detail.goods.good_unit' can not be empty")
			}
		}
		if params.OrderInfo.DeliveryServiceCode == "" {
			t.Error("Param 'order_info.delivery_service_code' can not be empty")
		}
		if params.Shop.WxaPath == "" {
			t.Error("Param 'shop.wxa_path' can not be empty")
		}
		if params.Shop.ImgURL == "" {
			t.Error("Param 'shop.img_url' can not be empty")
		}
		if params.Shop.GoodsName == "" {
			t.Error("Param 'shop.goods_name' can not be empty")
		}
		if params.Shop.GoodsCount == 0 {
			t.Error("Param 'shop.goods_count' can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"fee": 11,
			"deliverfee": 11,
			"couponfee": 1,
			"tips": 1,
			"insurancefee": 1000,
			"insurancfee": 1,
			"distance": 1001,
			"waybill_id": "123456789",
			"order_status": 101,
			"finish_code": 1024,
			"pickup_code": 2048,
			"dispatch_duration": 300
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
		"cargo": {
			"cargo_first_class": "美食宵夜",
			"cargo_second_class": "零食小吃",
			"goods_detail": {
				"goods": [
					{
						"good_count": 1,
						"good_name": "水果",
						"good_price": 11,
						"good_unit": "元"
					},
					{
						"good_count": 2,
						"good_name": "蔬菜",
						"good_price": 21,
						"good_unit": "元"
					}
				]
			},
			"goods_height": 1,
			"goods_length": 3,
			"goods_value": 5,
			"goods_weight": 1,
			"goods_width": 2
		},
		"delivery_id": "SFTC",
		"delivery_sign": "01234567890123456789",
		"openid": "oABC123456",
		"order_info": {
			"delivery_service_code": "xxx",
			"expected_delivery_time": 1,
			"is_direct_delivery": 1,
			"is_finish_code_needed": 1,
			"is_insured": 1,
			"is_pickup_code_needed": 1,
			"note": "test_note",
			"order_time": 1555220757,
			"order_type": 1,
			"poi_seq": "1111",
			"tips": 0
		},
		"receiver": {
			"address": "xxx地铁站",
			"address_detail": "2号楼202",
			"city": "北京市",
			"coordinate_type": 1,
			"lat": 40.1529600001,
			"lng": 116.5060300001,
			"name": "老王",
			"phone": "18512345678"
		},
		"sender": {
			"address": "xx大厦",
			"address_detail": "1号楼101",
			"city": "北京市",
			"coordinate_type": 1,
			"lat": 40.4486120001,
			"lng": 116.3830750001,
			"name": "刘一",
			"phone": "13712345678"
		},
		"shop": {
			"goods_count": 2,
			"goods_name": "宝贝",
			"img_url": "https://mmbiz.qpic.cn/mmbiz_png/xxxxxxxxx/0?wx_fmt=png",
			"wxa_path": "/page/index/index"
		},
		"shop_no": "12345678",
		"sub_biz_id": "sub_biz_id_1",
		"shop_order_id": "SFTC_001",
		"shopid": "122222222",
		"delivery_token": "xxxxxxxx"
	 }`

	creator := new(DeliveryOrderCreator)
	err := json.Unmarshal([]byte(raw), creator)
	if err != nil {
		t.Fatal(err)
	}

	res, err := creator.create(ts.URL+apiAddDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.Fee == 0 {
		t.Error("Response 'fee' can not be empty")
	}
	if res.Deliverfee == 0 {
		t.Error("Response 'deliverfee' can not be empty")
	}
	if res.Couponfee == 0 {
		t.Error("Response 'couponfee' can not be empty")
	}
	if res.Tips == 0 {
		t.Error("Response 'tips' can not be empty")
	}
	if res.Insurancefee == 0 {
		t.Error("Response 'insurancefee' can not be empty")
	}
	if res.Distance == 0 {
		t.Error("Response 'distance' can not be empty")
	}
	if res.WaybillID == "" {
		t.Error("Response 'waybill_id' can not be empty")
	}
	if res.OrderStatus == 0 {
		t.Error("Response 'order_status' can not be empty")
	}
	if res.FinishCode == 0 {
		t.Error("Response 'finish_code' can not be empty")
	}
	if res.PickupCode == 0 {
		t.Error("Response 'pickup_code' can not be empty")
	}
	if res.DispatchDuration == 0 {
		t.Error("Response 'dispatch_duration' can not be empty")
	}
	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
}

func TestAddDeliveryTip(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/addtips" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		params := struct {
			ShopID       string  `json:"shopid"`
			ShopOrderID  string  `json:"shop_order_id"`
			ShopNo       string  `json:"shop_no"`
			DeliverySign string  `json:"delivery_sign"`
			WaybillID    string  `json:"waybill_id"`
			OpenID       string  `json:"openid"`
			Tips         float64 `json:"tips"`
			Remark       string  `json:"remark"`
		}{}

		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}

		if params.ShopID == "" {
			t.Error("Param 'shopid' can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Param 'shop_order_id' can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Param 'shop_no' can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Param 'delivery_sign' can not be empty")
		}
		if params.WaybillID == "" {
			t.Error("Param 'waybill_id' can not be empty")
		}
		if params.OpenID == "" {
			t.Error("Param 'openid' can not be empty")
		}
		if params.Tips == 0 {
			t.Error("Param 'tips' can not be empty")
		}
		if params.Remark == "" {
			t.Error("Param 'remark' can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok"
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
		"shopid": "123456",
		"shop_order_id": "123456",
		"waybill_id": "123456",
		"tips": 5,
		"openid": "mock-open-id",
		"remark": "gogogo",
		"delivery_sign": "123456",
		"shop_no": "shop_no_111"
	 }`

	adder := new(DeliveryTipAdder)
	err := json.Unmarshal([]byte(raw), adder)
	if err != nil {
		t.Fatal(err)
	}

	res, err := adder.add(ts.URL+apiAddDeliveryTip, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
}

func TestCancelDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/cancel" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		params := struct {
			ShopID       string `json:"shopid"`           // 商家id, 由配送公司分配的appkey
			ShopOrderID  string `json:"shop_order_id"`    // 唯一标识订单的 ID,由商户生成
			ShopNo       string `json:"shop_no"`          // 商家门店编号, 在配送公司登记,如果只有一个门店,可以不填
			DeliverySign string `json:"delivery_sign"`    // 用配送公司提供的appSecret加密的校验串
			DeliveryID   string `json:"delivery_id"`      // 快递公司ID
			WaybillID    string `json:"waybill_id"`       // 配送单id
			ReasonID     uint8  `json:"cancel_reason_id"` // 取消原因Id
			Reason       string `json:"cancel_reason"`    // 取消原因
		}{}

		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}

		if params.ShopID == "" {
			t.Error("Param 'shopid' can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Param 'shop_order_id' can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Param 'shop_no' can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Param 'delivery_sign' can not be empty")
		}
		if params.WaybillID == "" {
			t.Error("Param 'waybill_id' can not be empty")
		}
		if params.DeliveryID == "" {
			t.Error("Param 'delivery_id' can not be empty")
		}
		if params.ReasonID == 0 {
			t.Error("Param 'cancel_reason_id' can not be empty")
		}
		if params.Reason == "" {
			t.Error("Param 'cancel_reason' can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"deduct_fee": 5,
			"desc": "blabla"
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
		"shopid": "123456",
		"shop_order_id": "123456",
		"waybill_id": "123456",
		"delivery_id": "123456",
		"cancel_reason_id": 1,
		"cancel_reason": "mock-cancel-reson",
		"delivery_sign": "123456",
		"shop_no": "shop_no_111"
	 }`

	canceler := new(DeliveryOrderCanceler)
	err := json.Unmarshal([]byte(raw), canceler)
	if err != nil {
		t.Fatal(err)
	}

	res, err := canceler.cancel(ts.URL+apiCancelDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
	if res.DeductFee == 0 {
		t.Error("Response 'deduct_fee' can not be empty")
	}
	if res.Desc == "" {
		t.Error("Response 'desc' can not be empty")
	}
}

func TestGetAllImmediateDelivery(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/delivery/getall" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"list": [
			   {
				   "delivery_id": "SFTC",
				   "delivery_name": "顺发同城"
			   },
			   {
				   "delivery_id": "MTPS",
				   "delivery_name": "美团配送"
			   }
			]
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))

	res, err := getAllImmediateDelivery(ts.URL+apiGetAllImmediateDelivery, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
	if len(res.List) == 0 {
		t.Error("Response 'list' can not be empty")
	} else {
		for _, item := range res.List {
			if item.ID == "" {
				t.Error("Response 'list.delivery_id' can not be empty")
			}
			if item.Name == "" {
				t.Error("Response 'list.delivery_name' can not be empty")
			}
		}
	}
}

func TestGetBindAccount(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/shop/get" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}
		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"shop_list": [
				{
				 "delivery_id": "SFTC",
				 "shopid": "123456",
				 "audit_result": 1
				},
				{
				 "delivery_id": "MTPS",
				 "shopid": "123456",
				 "audit_result": 1
				}
			]
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))

	res, err := getBindAccount(ts.URL+apiGetDeliveryBindAccount, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
	if len(res.ShopList) == 0 {
		t.Error("Response 'shop_list' can not be empty")
	} else {
		for _, item := range res.ShopList {
			if item.DeliveryID == "" {
				t.Error("Response 'shop_list.delivery_id' can not be empty")
			}
			if item.ShopID == "" {
				t.Error("Response 'shop_list.shopid' can not be empty")
			}
			if item.AuditResult == 0 {
				t.Error("Response 'audit_result.shopid' can not be empty")
			}
		}
	}
}

func TestGetDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/get" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		params := struct {
			ShopID       string `json:"shopid"`
			ShopOrderID  string `json:"shop_order_id"`
			ShopNo       string `json:"shop_no"`
			DeliverySign string `json:"delivery_sign"`
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}
		if params.ShopID == "" {
			t.Error("Response column shopid can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column shop_order_id can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Response column shop_no can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Response column delivery_sign can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"order_status":	1,	
			"waybill_id":	"string",
			"rider_name":	"string",
			"rider_phone":	"string",	
			"rider_lng": 3.14,	
			"rider_lat": 3.14
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))

	raw := `{
		"shopid": "xxxxxx",
		"shop_order_id": "xxxxxx",
		"shop_no": "xxxxxx",
		"delivery_sign": "xxxxxx"
	 }`

	getter := new(DeliveryOrderGetter)
	err := json.Unmarshal([]byte(raw), getter)
	if err != nil {
		t.Fatal(err)
	}

	res, err := getter.get(ts.URL+apiGetDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}
	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
	if res.WaybillID == "" {
		t.Error("Response 'waybill_id' can not be empty")
	}
	if res.OrderStatus == 0 {
		t.Error("Response 'order_status' can not be empty")
	}

	if res.RiderName == "" {
		t.Error("Response 'rider_name' can not be empty")
	}
	if res.RiderPhone == "" {
		t.Error("Response 'rider_phone' can not be empty")
	}
	if res.RiderLng == 0 {
		t.Error("Response 'rider_lng' can not be empty")
	}
	if res.RiderLat == 0 {
		t.Error("Response 'rider_lat' can not be empty")
	}
}

func TestMockUpdateDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/test_update_order" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		params := struct {
			ShopID      string `json:"shopid"`
			ShopOrderID string `json:"shop_order_id"`
			ActionTime  uint   `json:"action_time"`
			OrderStatus int    `json:"order_status"`
			ActionMsg   string `json:"action_msg"`
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}
		if params.ShopID == "" {
			t.Error("Response column shopid can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column shop_order_id can not be empty")
		}
		if params.ActionTime == 0 {
			t.Error("Response column action_time can not be empty")
		}
		if params.OrderStatus == 0 {
			t.Error("Response column order_status can not be empty")
		}
		if params.ActionMsg == "" {
			t.Error("Response column action_msg can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok"
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))

	raw := `{
		"shopid": "test_shop_id",
		"shop_order_id": "xxxxxxxxxxx",
		"waybill_id": "xxxxxxxxxxxxx",
		"action_time": 12345678,
		"order_status": 101,
		"action_msg": "xxxxxx"
	 }`

	mocker := new(UpdateDeliveryOrderMocker)
	err := json.Unmarshal([]byte(raw), mocker)
	if err != nil {
		t.Fatal(err)
	}

	res, err := mocker.mock(ts.URL+apiMockUpdateDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}
	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
}

func TestPreAddDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/pre_add" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("access_token can not be empty")
		}

		params := struct {
			ShopID       string `json:"shopid"`        // 商家id, 由配送公司分配的appkey
			ShopOrderID  string `json:"shop_order_id"` // 唯一标识订单的 ID,由商户生成
			ShopNo       string `json:"shop_no"`       // 商家门店编号, 在配送公司登记,如果只有一个门店,可以不填
			DeliverySign string `json:"delivery_sign"` // 用配送公司提供的appSecret加密的校验串
			DeliveryID   string `json:"delivery_id"`   // 配送公司ID
			OpenID       string `json:"openid"`        // 下单用户的openid
			SubBizID     string `json:"sub_biz_id"`    // 子商户id,区分小程序内部多个子商户
			Sender       struct {
				Name           string  `json:"name"`            // 姓名,最长不超过256个字符
				City           string  `json:"city"`            // 城市名称,如广州市
				Address        string  `json:"address"`         // 地址(街道、小区、大厦等,用于定位)
				AddressDetail  string  `json:"address_detail"`  // 地址详情(楼号、单元号、层号)
				Phone          string  `json:"phone"`           // 电话/手机号,最长不超过64个字符
				Lng            float64 `json:"lng"`             // 经度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,确到小数点后6位
				Lat            float64 `json:"lat"`             // 纬度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,精确到小数点后6位)
				CoordinateType uint8   `json:"coordinate_type"` // 坐标类型,0:火星坐标(高德,腾讯地图均采用火星坐标) 1:百度坐标
			} `json:"sender"` // 发件人信息,闪送、顺丰同城急送必须填写,美团配送、达达,若传了shop_no的值可不填该字段
			Receiver struct {
				Name           string  `json:"name"`            // 姓名,最长不超过256个字符
				City           string  `json:"city"`            // 城市名称,如广州市
				Address        string  `json:"address"`         // 地址(街道、小区、大厦等,用于定位)
				AddressDetail  string  `json:"address_detail"`  // 地址详情(楼号、单元号、层号)
				Phone          string  `json:"phone"`           // 电话/手机号,最长不超过64个字符
				Lng            float64 `json:"lng"`             // 经度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,确到小数点后6位
				Lat            float64 `json:"lat"`             // 纬度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,精确到小数点后6位)
				CoordinateType uint8   `json:"coordinate_type"` // 坐标类型,0:火星坐标(高德,腾讯地图均采用火星坐标) 1:百度坐标
			} `json:"receiver"` // 收件人信息
			Cargo struct {
				GoodsValue  float64 `json:"goods_value"`  // 货物价格,单位为元,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-5000]
				GoodsHeight float64 `json:"goods_height"` // 货物高度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-45]
				GoodsLength float64 `json:"goods_length"` // 货物长度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-65]
				GoodsWidth  float64 `json:"goods_width"`  // 货物宽度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-50]
				GoodsWeight float64 `json:"goods_weight"` // 货物重量,单位为kg,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-50]
				GoodsDetail struct {
					Goods []struct {
						Count uint    `json:"good_count"` // 货物数量
						Name  string  `json:"good_name"`  // 货品名称
						Price float32 `json:"good_price"` // 货品单价,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数)
						Unit  string  `json:"good_unit"`  // 货品单位,最长不超过20个字符
					} `json:"goods"` // 货物交付信息,最长不超过100个字符
				} `json:"goods_detail"` // 货物详情,最长不超过10240个字符
				GoodsPickupInfo   string `json:"goods_pickup_info"`   // 货物取货信息,用于骑手到店取货,最长不超过100个字符
				GoodsDeliveryInfo string `json:"goods_delivery_info"` // 货物交付信息,最长不超过100个字符
				CargoFirstClass   string `json:"cargo_first_class"`   // 品类一级类目
				CargoSecondClass  string `json:"cargo_second_class"`  // 品类二级类目
			} `json:"cargo"` // 货物信息
			OrderInfo struct {
				DeliveryServiceCode  string  `json:"delivery_service_code"`  // 配送服务代码 不同配送公司自定义,微信侧不理解
				OrderType            uint8   `json:"order_type"`             // 订单类型, 0: 即时单 1 预约单,如预约单,需要设置expected_delivery_time或expected_finish_time或expected_pick_time
				ExpectedDeliveryTime uint    `json:"expected_delivery_time"` // 期望派单时间(顺丰同城急送、达达、支持),unix-timestamp
				ExpectedFinishTime   uint    `json:"expected_finish_time"`   // 期望送达时间(顺丰同城急送、美团配送支持),unix-timestamp
				ExpectedPickTime     uint    `json:"expected_pick_time"`     // 期望取件时间(闪送支持),unix-timestamp
				PoiSeq               string  `json:"poi_seq"`                // 门店订单流水号,建议提供,方便骑手门店取货,最长不超过32个字符
				Note                 string  `json:"note"`                   // 备注,最长不超过200个字符
				OrderTime            uint    `json:"order_time"`             // 用户下单付款时间
				IsInsured            uint8   `json:"is_insured"`             // 是否保价,0,非保价,1.保价
				DeclaredValue        float64 `json:"declared_value"`         // 保价金额,单位为元,精确到分
				Tips                 float64 `json:"tips"`                   // 小费,单位为元, 下单一般不加小费
				IsDirectDelivery     uint    `json:"is_direct_delivery"`     // 是否选择直拿直送(0:不需要;1:需要。选择直拿直送后,同一时间骑手只能配送此订单至完成,配送费用也相应高一些,闪送必须选1,达达可选0或1,其余配送公司不支持直拿直送)
				CashOnDelivery       uint    `json:"cash_on_delivery"`       // 骑手应付金额,单位为元,精确到分
				CashOnPickup         uint    `json:"cash_on_pickup"`         // 骑手应收金额,单位为元,精确到分
				RiderPickMethod      uint8   `json:"rider_pick_method"`      // 物流流向,1:从门店取件送至用户;2:从用户取件送至门店
				IsFinishCodeNeeded   uint8   `json:"is_finish_code_needed"`  // 收货码(0:不需要;1:需要。收货码的作用是:骑手必须输入收货码才能完成订单妥投)
				IsPickupCodeNeeded   uint8   `json:"is_pickup_code_needed"`  // 取货码(0:不需要;1:需要。取货码的作用是:骑手必须输入取货码才能从商家取货)
			} `json:"order_info"` // 订单信息
			Shop struct {
				WxaPath    string `json:"wxa_path"`    // 商家小程序的路径,建议为订单页面
				ImgURL     string `json:"img_url"`     // 商品缩略图 url
				GoodsName  string `json:"goods_name"`  // 商品名称
				GoodsCount uint   `json:"goods_count"` // 商品数量
			} `json:"shop"` // 商品信息,会展示到物流通知消息中
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}

		if params.ShopID == "" {
			t.Error("Response column 'shopid' can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column 'shop_order_id' can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Response column 'shop_no' can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Response column 'delivery_sign' can not be empty")
		}
		if params.DeliveryID == "" {
			t.Error("Response column 'delivery_id' can not be empty")
		}
		if params.OpenID == "" {
			t.Error("Response column 'openid' can not be empty")
		}
		if params.SubBizID == "" {
			t.Error("Response column 'sub_biz_id' can not be empty")
		}

		if params.Sender.Name == "" {
			t.Error("Param 'sender.name' can not be empty")
		}
		if params.Sender.City == "" {
			t.Error("Param 'sender.city' can not be empty")
		}
		if params.Sender.Address == "" {
			t.Error("Param 'sender.address' can not be empty")
		}
		if params.Sender.AddressDetail == "" {
			t.Error("Param 'sender.address_detail' can not be empty")
		}
		if params.Sender.Phone == "" {
			t.Error("Param 'sender.phone' can not be empty")
		}
		if params.Sender.Lng == 0 {
			t.Error("Param 'sender.lng' can not be empty")
		}
		if params.Sender.Lat == 0 {
			t.Error("Param 'sender.lat' can not be empty")
		}
		if params.Sender.CoordinateType == 0 {
			t.Error("Param 'sender.coordinate_type' can not be empty")
		}

		if params.Receiver.Name == "" {
			t.Error("Param 'receiver.name' can not be empty")
		}
		if params.Receiver.City == "" {
			t.Error("Param 'receiver.city' can not be empty")
		}
		if params.Receiver.Address == "" {
			t.Error("Param 'receiver.address' can not be empty")
		}
		if params.Receiver.AddressDetail == "" {
			t.Error("Param 'receiver.address_detail' can not be empty")
		}
		if params.Receiver.Phone == "" {
			t.Error("Param 'receiver.phone' can not be empty")
		}
		if params.Receiver.Lng == 0 {
			t.Error("Param 'receiver.lng' can not be empty")
		}
		if params.Receiver.Lat == 0 {
			t.Error("Param 'receiver.lat' can not be empty")
		}
		if params.Receiver.CoordinateType == 0 {
			t.Error("Param 'receiver.coordinate_type' can not be empty")
		}
		if params.Cargo.GoodsValue == 0 {
			t.Error("Param 'cargo.goods_value' can not be empty")
		}
		if params.Cargo.GoodsHeight == 0 {
			t.Error("Param 'cargo.goods_height' can not be empty")
		}
		if params.Cargo.GoodsLength == 0 {
			t.Error("Param 'cargo.goods_length' can not be empty")
		}
		if params.Cargo.GoodsWidth == 0 {
			t.Error("Param 'cargo.goods_width' can not be empty")
		}
		if params.Cargo.GoodsWeight == 0 {
			t.Error("Param 'cargo.goods_weight' can not be empty")
		}
		if params.Cargo.CargoFirstClass == "" {
			t.Error("Param 'cargo.cargo_first_class' can not be empty")
		}
		if params.Cargo.CargoSecondClass == "" {
			t.Error("Param 'cargo.cargo_second_class' can not be empty")
		}
		if len(params.Cargo.GoodsDetail.Goods) > 0 {
			if params.Cargo.GoodsDetail.Goods[0].Count == 0 {
				t.Error("Param 'cargo.goods_detail.goods.good_count' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Name == "" {
				t.Error("Param 'cargo.goods_detail.goods.good_name' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Price == 0 {
				t.Error("Param 'cargo.goods_detail.goods.good_price' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Unit == "" {
				t.Error("Param 'cargo.goods_detail.goods.good_unit' can not be empty")
			}
		}
		if params.OrderInfo.DeliveryServiceCode == "" {
			t.Error("Param 'order_info.delivery_service_code' can not be empty")
		}
		if params.Shop.WxaPath == "" {
			t.Error("Param 'shop.wxa_path' can not be empty")
		}
		if params.Shop.ImgURL == "" {
			t.Error("Param 'shop.img_url' can not be empty")
		}
		if params.Shop.GoodsName == "" {
			t.Error("Param 'shop.goods_name' can not be empty")
		}
		if params.Shop.GoodsCount == 0 {
			t.Error("Param 'shop.goods_count' can not be empty")
		}

		w.WriteHeader(http.StatusOK)
		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"fee": 11,
			"deliverfee": 11,
			"couponfee": 1,
			"insurancefee": 1000,
			"tips": 1,
			"insurancfee": 1,
			"distance": 1001,
			"dispatch_duration": 301,
			"delivery_token": "1111111"
			}`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
		"cargo": {
			"cargo_first_class": "美食宵夜",
			"cargo_second_class": "零食小吃",
			"goods_detail": {
				"goods": [
					{
						"good_count": 1,
						"good_name": "水果",
						"good_price": 11,
						"good_unit": "元"
					},
					{
						"good_count": 2,
						"good_name": "蔬菜",
						"good_price": 21,
						"good_unit": "元"
					}
				]
			},
			"goods_height": 1,
			"goods_length": 3,
			"goods_value": 5,
			"goods_weight": 1,
			"goods_width": 2
		},
		"delivery_id": "SFTC",
		"delivery_sign": "01234567890123456789",
		"openid": "oABC123456",
		"order_info": {
			"delivery_service_code": "xxx",
			"expected_delivery_time": 1,
			"is_direct_delivery": 1,
			"is_finish_code_needed": 1,
			"is_insured": 1,
			"is_pickup_code_needed": 1,
			"note": "test_note",
			"order_time": 1555220757,
			"order_type": 1,
			"poi_seq": "1111",
			"tips": 0
		},
		"receiver": {
			"address": "xxx地铁站",
			"address_detail": "2号楼202",
			"city": "北京市",
			"coordinate_type": 1,
			"lat": 40.1529600001,
			"lng": 116.5060300001,
			"name": "老王",
			"phone": "18512345678"
		},
		"sender": {
			"address": "xx大厦",
			"address_detail": "1号楼101",
			"city": "北京市",
			"coordinate_type": 1,
			"lat": 40.4486120001,
			"lng": 116.3830750001,
			"name": "刘一",
			"phone": "13712345678"
		},
		"shop": {
			"goods_count": 2,
			"goods_name": "宝贝",
			"img_url": "https://mmbiz.qpic.cn/mmbiz_png/xxxxxxxxx/0?wx_fmt=png",
			"wxa_path": "/page/index/index"
		},
		"shop_no": "12345678",
		"sub_biz_id": "sub_biz_id_1",
		"shop_order_id": "SFTC_001",
		"shopid": "122222222"
	 }`

	creator := new(DeliveryOrderCreator)
	err := json.Unmarshal([]byte(raw), creator)
	if err != nil {
		t.Fatal(err)
	}

	res, err := creator.prepare(ts.URL+apiPreAddDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.Fee == 0 {
		t.Error("Response 'fee' can not be empty")
	}
	if res.Deliverfee == 0 {
		t.Error("Response 'deliverfee' can not be empty")
	}
	if res.Couponfee == 0 {
		t.Error("Response 'couponfee' can not be empty")
	}
	if res.Tips == 0 {
		t.Error("Response 'tips' can not be empty")
	}
	if res.Insurancefee == 0 {
		t.Error("Response 'insurancefee' can not be empty")
	}
	if res.Distance == 0 {
		t.Error("Response 'distance' can not be empty")
	}
	if res.DispatchDuration == 0 {
		t.Error("Response 'dispatch_duration' can not be empty")
	}
}

func TestPreCancelDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/precancel" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		params := struct {
			ShopID       string `json:"shopid"`           // 商家id, 由配送公司分配的appkey
			ShopOrderID  string `json:"shop_order_id"`    // 唯一标识订单的 ID,由商户生成
			ShopNo       string `json:"shop_no"`          // 商家门店编号, 在配送公司登记,如果只有一个门店,可以不填
			DeliverySign string `json:"delivery_sign"`    // 用配送公司提供的appSecret加密的校验串
			DeliveryID   string `json:"delivery_id"`      // 快递公司ID
			WaybillID    string `json:"waybill_id"`       // 配送单id
			ReasonID     uint8  `json:"cancel_reason_id"` // 取消原因Id
			Reason       string `json:"cancel_reason"`    // 取消原因
		}{}

		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}

		if params.ShopID == "" {
			t.Error("Param 'shopid' can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Param 'shop_order_id' can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Param 'shop_no' can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Param 'delivery_sign' can not be empty")
		}
		if params.WaybillID == "" {
			t.Error("Param 'waybill_id' can not be empty")
		}
		if params.DeliveryID == "" {
			t.Error("Param 'delivery_id' can not be empty")
		}
		if params.ReasonID == 0 {
			t.Error("Param 'cancel_reason_id' can not be empty")
		}
		if params.Reason == "" {
			t.Error("Param 'cancel_reason' can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"deduct_fee": 5,
			"desc": "blabla"
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
		"shopid": "123456",
		"shop_order_id": "123456",
		"waybill_id": "123456",
		"delivery_id": "123456",
		"cancel_reason_id": 1,
		"cancel_reason": "xxxxxx",
		"delivery_sign": "123456",
		"shop_no": "shop_no_111"
	 }`

	canceler := new(DeliveryOrderCanceler)
	err := json.Unmarshal([]byte(raw), canceler)
	if err != nil {
		t.Fatal(err)
	}

	res, err := canceler.prepare(ts.URL+apiPreCancelDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
	if res.DeductFee == 0 {
		t.Error("Response 'deduct_fee' can not be empty")
	}
	if res.Desc == "" {
		t.Error("Response 'desc' can not be empty")
	}
}

func TestReAddDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/business/order/readd" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("access_token can not be empty")
		}

		params := struct {
			DeliveryToken string `json:"delivery_token"` // 预下单接口返回的参数,配送公司可保证在一段时间内运费不变
			ShopID        string `json:"shopid"`         // 商家id, 由配送公司分配的appkey
			ShopOrderID   string `json:"shop_order_id"`  // 唯一标识订单的 ID,由商户生成
			ShopNo        string `json:"shop_no"`        // 商家门店编号, 在配送公司登记,如果只有一个门店,可以不填
			DeliverySign  string `json:"delivery_sign"`  // 用配送公司提供的appSecret加密的校验串
			DeliveryID    string `json:"delivery_id"`    // 配送公司ID
			OpenID        string `json:"openid"`         // 下单用户的openid
			SubBizID      string `json:"sub_biz_id"`     // 子商户id,区分小程序内部多个子商户
			Sender        struct {
				Name           string  `json:"name"`            // 姓名,最长不超过256个字符
				City           string  `json:"city"`            // 城市名称,如广州市
				Address        string  `json:"address"`         // 地址(街道、小区、大厦等,用于定位)
				AddressDetail  string  `json:"address_detail"`  // 地址详情(楼号、单元号、层号)
				Phone          string  `json:"phone"`           // 电话/手机号,最长不超过64个字符
				Lng            float64 `json:"lng"`             // 经度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,确到小数点后6位
				Lat            float64 `json:"lat"`             // 纬度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,精确到小数点后6位)
				CoordinateType uint8   `json:"coordinate_type"` // 坐标类型,0:火星坐标(高德,腾讯地图均采用火星坐标) 1:百度坐标
			} `json:"sender"` // 发件人信息,闪送、顺丰同城急送必须填写,美团配送、达达,若传了shop_no的值可不填该字段
			Receiver struct {
				Name           string  `json:"name"`            // 姓名,最长不超过256个字符
				City           string  `json:"city"`            // 城市名称,如广州市
				Address        string  `json:"address"`         // 地址(街道、小区、大厦等,用于定位)
				AddressDetail  string  `json:"address_detail"`  // 地址详情(楼号、单元号、层号)
				Phone          string  `json:"phone"`           // 电话/手机号,最长不超过64个字符
				Lng            float64 `json:"lng"`             // 经度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,确到小数点后6位
				Lat            float64 `json:"lat"`             // 纬度(火星坐标或百度坐标,和 coordinate_type 字段配合使用,精确到小数点后6位)
				CoordinateType uint8   `json:"coordinate_type"` // 坐标类型,0:火星坐标(高德,腾讯地图均采用火星坐标) 1:百度坐标
			} `json:"receiver"` // 收件人信息
			Cargo struct {
				GoodsValue  float64 `json:"goods_value"`  // 货物价格,单位为元,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-5000]
				GoodsHeight float64 `json:"goods_height"` // 货物高度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-45]
				GoodsLength float64 `json:"goods_length"` // 货物长度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-65]
				GoodsWidth  float64 `json:"goods_width"`  // 货物宽度,单位为cm,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-50]
				GoodsWeight float64 `json:"goods_weight"` // 货物重量,单位为kg,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数),范围为(0-50]
				GoodsDetail struct {
					Goods []struct {
						Count uint    `json:"good_count"` // 货物数量
						Name  string  `json:"good_name"`  // 货品名称
						Price float32 `json:"good_price"` // 货品单价,精确到小数点后两位(如果小数点后位数多于两位,则四舍五入保留两位小数)
						Unit  string  `json:"good_unit"`  // 货品单位,最长不超过20个字符
					} `json:"goods"` // 货物交付信息,最长不超过100个字符
				} `json:"goods_detail"` // 货物详情,最长不超过10240个字符
				GoodsPickupInfo   string `json:"goods_pickup_info"`   // 货物取货信息,用于骑手到店取货,最长不超过100个字符
				GoodsDeliveryInfo string `json:"goods_delivery_info"` // 货物交付信息,最长不超过100个字符
				CargoFirstClass   string `json:"cargo_first_class"`   // 品类一级类目
				CargoSecondClass  string `json:"cargo_second_class"`  // 品类二级类目
			} `json:"cargo"` // 货物信息
			OrderInfo struct {
				DeliveryServiceCode  string  `json:"delivery_service_code"`  // 配送服务代码 不同配送公司自定义,微信侧不理解
				OrderType            uint8   `json:"order_type"`             // 订单类型, 0: 即时单 1 预约单,如预约单,需要设置expected_delivery_time或expected_finish_time或expected_pick_time
				ExpectedDeliveryTime uint    `json:"expected_delivery_time"` // 期望派单时间(顺丰同城急送、达达、支持),unix-timestamp
				ExpectedFinishTime   uint    `json:"expected_finish_time"`   // 期望送达时间(顺丰同城急送、美团配送支持),unix-timestamp
				ExpectedPickTime     uint    `json:"expected_pick_time"`     // 期望取件时间(闪送支持),unix-timestamp
				PoiSeq               string  `json:"poi_seq"`                // 门店订单流水号,建议提供,方便骑手门店取货,最长不超过32个字符
				Note                 string  `json:"note"`                   // 备注,最长不超过200个字符
				OrderTime            uint    `json:"order_time"`             // 用户下单付款时间
				IsInsured            uint8   `json:"is_insured"`             // 是否保价,0,非保价,1.保价
				DeclaredValue        float64 `json:"declared_value"`         // 保价金额,单位为元,精确到分
				Tips                 float64 `json:"tips"`                   // 小费,单位为元, 下单一般不加小费
				IsDirectDelivery     uint    `json:"is_direct_delivery"`     // 是否选择直拿直送(0:不需要;1:需要。选择直拿直送后,同一时间骑手只能配送此订单至完成,配送费用也相应高一些,闪送必须选1,达达可选0或1,其余配送公司不支持直拿直送)
				CashOnDelivery       uint    `json:"cash_on_delivery"`       // 骑手应付金额,单位为元,精确到分
				CashOnPickup         uint    `json:"cash_on_pickup"`         // 骑手应收金额,单位为元,精确到分
				RiderPickMethod      uint8   `json:"rider_pick_method"`      // 物流流向,1:从门店取件送至用户;2:从用户取件送至门店
				IsFinishCodeNeeded   uint8   `json:"is_finish_code_needed"`  // 收货码(0:不需要;1:需要。收货码的作用是:骑手必须输入收货码才能完成订单妥投)
				IsPickupCodeNeeded   uint8   `json:"is_pickup_code_needed"`  // 取货码(0:不需要;1:需要。取货码的作用是:骑手必须输入取货码才能从商家取货)
			} `json:"order_info"` // 订单信息
			Shop struct {
				WxaPath    string `json:"wxa_path"`    // 商家小程序的路径,建议为订单页面
				ImgURL     string `json:"img_url"`     // 商品缩略图 url
				GoodsName  string `json:"goods_name"`  // 商品名称
				GoodsCount uint   `json:"goods_count"` // 商品数量
			} `json:"shop"` // 商品信息,会展示到物流通知消息中
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}

		if params.DeliveryToken == "" {
			t.Error("Response column 'delivery_token' can not be empty")
		}
		if params.ShopID == "" {
			t.Error("Response column 'shopid' can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column 'shop_order_id' can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Response column 'shop_no' can not be empty")
		}
		if params.DeliverySign == "" {
			t.Error("Response column 'delivery_sign' can not be empty")
		}
		if params.DeliveryID == "" {
			t.Error("Response column 'delivery_id' can not be empty")
		}
		if params.OpenID == "" {
			t.Error("Response column 'openid' can not be empty")
		}
		if params.SubBizID == "" {
			t.Error("Response column 'sub_biz_id' can not be empty")
		}

		if params.Sender.Name == "" {
			t.Error("Param 'sender.name' can not be empty")
		}
		if params.Sender.City == "" {
			t.Error("Param 'sender.city' can not be empty")
		}
		if params.Sender.Address == "" {
			t.Error("Param 'sender.address' can not be empty")
		}
		if params.Sender.AddressDetail == "" {
			t.Error("Param 'sender.address_detail' can not be empty")
		}
		if params.Sender.Phone == "" {
			t.Error("Param 'sender.phone' can not be empty")
		}
		if params.Sender.Lng == 0 {
			t.Error("Param 'sender.lng' can not be empty")
		}
		if params.Sender.Lat == 0 {
			t.Error("Param 'sender.lat' can not be empty")
		}
		if params.Sender.CoordinateType == 0 {
			t.Error("Param 'sender.coordinate_type' can not be empty")
		}

		if params.Receiver.Name == "" {
			t.Error("Param 'receiver.name' can not be empty")
		}
		if params.Receiver.City == "" {
			t.Error("Param 'receiver.city' can not be empty")
		}
		if params.Receiver.Address == "" {
			t.Error("Param 'receiver.address' can not be empty")
		}
		if params.Receiver.AddressDetail == "" {
			t.Error("Param 'receiver.address_detail' can not be empty")
		}
		if params.Receiver.Phone == "" {
			t.Error("Param 'receiver.phone' can not be empty")
		}
		if params.Receiver.Lng == 0 {
			t.Error("Param 'receiver.lng' can not be empty")
		}
		if params.Receiver.Lat == 0 {
			t.Error("Param 'receiver.lat' can not be empty")
		}
		if params.Receiver.CoordinateType == 0 {
			t.Error("Param 'receiver.coordinate_type' can not be empty")
		}
		if params.Cargo.GoodsValue == 0 {
			t.Error("Param 'cargo.goods_value' can not be empty")
		}
		if params.Cargo.GoodsHeight == 0 {
			t.Error("Param 'cargo.goods_height' can not be empty")
		}
		if params.Cargo.GoodsLength == 0 {
			t.Error("Param 'cargo.goods_length' can not be empty")
		}
		if params.Cargo.GoodsWidth == 0 {
			t.Error("Param 'cargo.goods_width' can not be empty")
		}
		if params.Cargo.GoodsWeight == 0 {
			t.Error("Param 'cargo.goods_weight' can not be empty")
		}
		if params.Cargo.CargoFirstClass == "" {
			t.Error("Param 'cargo.cargo_first_class' can not be empty")
		}
		if params.Cargo.CargoSecondClass == "" {
			t.Error("Param 'cargo.cargo_second_class' can not be empty")
		}
		if len(params.Cargo.GoodsDetail.Goods) > 0 {
			if params.Cargo.GoodsDetail.Goods[0].Count == 0 {
				t.Error("Param 'cargo.goods_detail.goods.good_count' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Name == "" {
				t.Error("Param 'cargo.goods_detail.goods.good_name' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Price == 0 {
				t.Error("Param 'cargo.goods_detail.goods.good_price' can not be empty")
			}
			if params.Cargo.GoodsDetail.Goods[0].Unit == "" {
				t.Error("Param 'cargo.goods_detail.goods.good_unit' can not be empty")
			}
		}
		if params.OrderInfo.DeliveryServiceCode == "" {
			t.Error("Param 'order_info.delivery_service_code' can not be empty")
		}
		if params.Shop.WxaPath == "" {
			t.Error("Param 'shop.wxa_path' can not be empty")
		}
		if params.Shop.ImgURL == "" {
			t.Error("Param 'shop.img_url' can not be empty")
		}
		if params.Shop.GoodsName == "" {
			t.Error("Param 'shop.goods_name' can not be empty")
		}
		if params.Shop.GoodsCount == 0 {
			t.Error("Param 'shop.goods_count' can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok",
			"fee": 11,
			"deliverfee": 11,
			"couponfee": 1,
			"tips": 1,
			"insurancefee": 1000,
			"insurancfee": 1,
			"distance": 1001,
			"waybill_id": "123456789",
			"order_status": 101,
			"finish_code": 1024,
			"pickup_code": 2048,
			"dispatch_duration": 300
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	raw := `{
		"cargo": {
			"cargo_first_class": "美食宵夜",
			"cargo_second_class": "零食小吃",
			"goods_detail": {
				"goods": [
					{
						"good_count": 1,
						"good_name": "水果",
						"good_price": 11,
						"good_unit": "元"
					},
					{
						"good_count": 2,
						"good_name": "蔬菜",
						"good_price": 21,
						"good_unit": "元"
					}
				]
			},
			"goods_height": 1,
			"goods_length": 3,
			"goods_value": 5,
			"goods_weight": 1,
			"goods_width": 2
		},
		"delivery_id": "SFTC",
		"delivery_sign": "01234567890123456789",
		"openid": "oABC123456",
		"order_info": {
			"delivery_service_code": "xxx",
			"expected_delivery_time": 1,
			"is_direct_delivery": 1,
			"is_finish_code_needed": 1,
			"is_insured": 1,
			"is_pickup_code_needed": 1,
			"note": "test_note",
			"order_time": 1555220757,
			"order_type": 1,
			"poi_seq": "1111",
			"tips": 0
		},
		"receiver": {
			"address": "xxx地铁站",
			"address_detail": "2号楼202",
			"city": "北京市",
			"coordinate_type": 1,
			"lat": 40.1529600001,
			"lng": 116.5060300001,
			"name": "老王",
			"phone": "18512345678"
		},
		"sender": {
			"address": "xx大厦",
			"address_detail": "1号楼101",
			"city": "北京市",
			"coordinate_type": 1,
			"lat": 40.4486120001,
			"lng": 116.3830750001,
			"name": "刘一",
			"phone": "13712345678"
		},
		"shop": {
			"goods_count": 2,
			"goods_name": "宝贝",
			"img_url": "https://mmbiz.qpic.cn/mmbiz_png/xxxxxxxxx/0?wx_fmt=png",
			"wxa_path": "/page/index/index"
		},
		"shop_no": "12345678",
		"sub_biz_id": "sub_biz_id_1",
		"shop_order_id": "SFTC_001",
		"shopid": "122222222",
		"delivery_token": "xxxxxxxx"
	 }`

	creator := new(DeliveryOrderCreator)
	err := json.Unmarshal([]byte(raw), creator)
	if err != nil {
		t.Fatal(err)
	}

	res, err := creator.recreate(ts.URL+apiReAddDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}

	if res.Fee == 0 {
		t.Error("Response 'fee' can not be empty")
	}
	if res.Deliverfee == 0 {
		t.Error("Response 'deliverfee' can not be empty")
	}
	if res.Couponfee == 0 {
		t.Error("Response 'couponfee' can not be empty")
	}
	if res.Tips == 0 {
		t.Error("Response 'tips' can not be empty")
	}
	if res.Insurancefee == 0 {
		t.Error("Response 'insurancefee' can not be empty")
	}
	if res.Distance == 0 {
		t.Error("Response 'distance' can not be empty")
	}
	if res.WaybillID == "" {
		t.Error("Response 'waybill_id' can not be empty")
	}
	if res.OrderStatus == 0 {
		t.Error("Response 'order_status' can not be empty")
	}
	if res.FinishCode == 0 {
		t.Error("Response 'finish_code' can not be empty")
	}
	if res.PickupCode == 0 {
		t.Error("Response 'pickup_code' can not be empty")
	}
	if res.DispatchDuration == 0 {
		t.Error("Response 'dispatch_duration' can not be empty")
	}
	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
}

func TestUpdateDeliveryOrder(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Error("UnExpect request method")
		}

		if r.URL.EscapedPath() != "/cgi-bin/express/local/delivery/update_order" {
			t.Error("Unexpected path")
		}

		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}

		if r.Form.Get("access_token") == "" {
			t.Fatalf("Query 'access_token' can not be empty")
		}

		params := struct {
			WxToken     string `json:"wx_token"`
			ShopID      string `json:"shopid"`
			ShopOrderID string `json:"shop_order_id"`
			ShopNo      string `json:"shop_no"`
			WaybillID   string `json:"waybill_id"`
			ActionTime  uint   `json:"action_time"`
			OrderStatus int    `json:"order_status"`
			ActionMsg   string `json:"action_msg"`
			WxaPath     string `json:"wxa_path"`
			Agent       struct {
				Name             string `json:"name"`
				Phone            string `json:"phone"`
				IsPhoneEncrypted uint8  `json:"is_phone_encrypted"`
			} `json:"agent"`
			ExpectedDeliveryTime uint `json:"expected_delivery_time"`
		}{}
		if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
			t.Fatal(err)
		}
		if params.WxToken == "" {
			t.Error("Response column wx_token can not be empty")
		}
		if params.Agent.Name == "" {
			t.Error("Response column agent.name can not be empty")
		}
		if params.Agent.Phone == "" {
			t.Error("Response column agent.phone can not be empty")
		}
		if params.Agent.IsPhoneEncrypted == 0 {
			t.Error("Response column agent.is_phone_encrypted can not be empty")
		}
		if params.ShopID == "" {
			t.Error("Response column shopid can not be empty")
		}
		if params.ShopNo == "" {
			t.Error("Response column shop_no can not be empty")
		}
		if params.WaybillID == "" {
			t.Error("Response column waybill_id can not be empty")
		}
		if params.ShopOrderID == "" {
			t.Error("Response column expected_delivery_time can not be empty")
		}
		if params.ExpectedDeliveryTime == 0 {
			t.Error("Response column action_time can not be empty")
		}
		if params.ActionTime == 0 {
			t.Error("Response column action_time can not be empty")
		}
		if params.OrderStatus == 0 {
			t.Error("Response column order_status can not be empty")
		}
		if params.ActionMsg == "" {
			t.Error("Response column action_msg can not be empty")
		}

		w.WriteHeader(http.StatusOK)

		raw := `{
			"resultcode": 1,
			"resultmsg": "ok"
		  }`
		if _, err := w.Write([]byte(raw)); err != nil {
			t.Fatal(err)
		}
	}))

	raw := `{
		"wx_token": "xxxxxx",
		"shopid": "test_shop_id",
		"shop_no": "test_shop_id",
		"shop_order_id": "xxxxxxxxxxx",
		"waybill_id": "xxxxxxxxxxxxx",
		"action_time": 12345678,
		"order_status": 101,
		"action_msg": "xxxxxx",
		"wxa_path": "xxxxxx",
		"expected_delivery_time": 123456,
		"agent": {
			"name": "xxxxxx",
			"phone": "xxxxxx",
			"is_phone_encrypted": 1
		}
	 }`

	updater := new(DeliveryOrderUpdater)
	err := json.Unmarshal([]byte(raw), updater)
	if err != nil {
		t.Fatal(err)
	}

	res, err := updater.update(ts.URL+apiUpdateDeliveryOrder, "mock-access-token")
	if err != nil {
		t.Fatal(err)
	}
	if res.ResultCode == 0 {
		t.Error("Response 'resultcode' can not be empty")
	}
	if res.ResultMsg == "" {
		t.Error("Response 'resultmsg' can not be empty")
	}
}

func TestOnDeliveryOrderStatusUpdate(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		aesKey := base64.StdEncoding.EncodeToString([]byte("mock-aes-key"))
		srv, err := NewServer("mock-app-id", "mock-access-token", aesKey, "mock-mch-id", "mock-api-key", false)
		if err != nil {
			t.Fatal(err)
		}

		srv.OnDeliveryOrderStatusUpdate(func(mix *DeliveryOrderStatusUpdateResult) *DeliveryOrderStatusUpdateReturn {
			if mix.ToUserName == "" {
				t.Error("ToUserName can not be empty")
			}

			if mix.FromUserName == "" {
				t.Error("FromUserName can not be empty")
			}
			if mix.CreateTime == 0 {
				t.Error("CreateTime can not be zero")
			}
			if mix.MsgType != "event" {
				t.Error("Unexpected message type")
			}

			if mix.Event != "update_waybill_status" {
				t.Error("Unexpected message event")
			}

			if mix.ShopID == "" {
				t.Error("Result 'shopid' can not be zero")
			}

			if mix.ShopOrderID == "" {
				t.Error("Result 'shop_order_id' can not be zero")
			}

			if mix.ShopNo == "" {
				t.Error("Result 'shop_no' can not be zero")
			}

			if mix.WaybillID == "" {
				t.Error("Result 'waybill_id' can not be zero")
			}

			if mix.ActionTime == 0 {
				t.Error("Result 'action_time' can not be zero")
			}

			if mix.OrderStatus == 0 {
				t.Error("Result 'order_status' can not be zero")
			}

			if mix.ActionMsg == "" {
				t.Error("Result 'action_msg' can not be zero")
			}

			if mix.Agent.Name == "" {
				t.Error("Result 'agent.name' can not be zero")
			}

			if mix.Agent.Phone == "" {
				t.Error("Result 'agent.phone' can not be zero")
			}

			return &DeliveryOrderStatusUpdateReturn{
				"mock-to-user-name",
				"mock-from-user-name",
				20060102150405,
				"mock-message-type",
				"mock-event",
				0,
				"mock-result-message",
			}
		})

		if err := srv.Serve(w, r); err != nil {
			t.Fatal(err)
		}
	}))
	defer ts.Close()

	jsonData := `{
		"ToUserName": "toUser",
		"FromUserName": "fromUser",
		"CreateTime": 1546924844,
		"MsgType": "event",
		"Event": "update_waybill_status",
		"shopid": "123456",
		"shop_order_id": "123456",
		"waybill_id": "123456",
		"action_time": 1546924844,
		"order_status": 102,
		"action_msg": "xxx",
		"shop_no": "123456",
		"agent": {
		   "name": "xxx",
		   "phone": "1234567"
		}
	  }`

	res, err := http.Post(ts.URL, "application/json", strings.NewReader(jsonData))
	if err != nil {
		t.Fatal(err)
	}
	defer res.Body.Close()
}