# -*- coding: utf-8 -*-
import json
import os
import urllib.parse
from io import BytesIO

import multipart
import pytest
import requests
import responses

from zgrobot import ZgRoBot
from zgrobot.client import Client, check_error, ClientException
from zgrobot.config import Config
from zgrobot.utils import cached_property

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
GOD_PIC = os.path.join(
    os.path.dirname(__file__), os.path.join("media", "123.png")
)
GOD2_PIC = os.path.join(
    os.path.dirname(__file__), os.path.join("media", "234.pngw")
)
GOD_MP4 = os.path.join(
    os.path.dirname(__file__), os.path.join("media", "456.mp4")
)
TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
JSON_HEADER = {'content-type': 'application/json'}


def token_callback(request):
    return 200, JSON_HEADER, json.dumps(
        {
            "access_token": "ACCESS_TOKEN",
            "expires_in": 7200
        }
    )


def add_token_response(method):
    def wrapped_func(self, *args, **kwargs):
        responses.add_callback(
            responses.GET, TOKEN_URL, callback=token_callback
        )
        method(self, *args, **kwargs)

    return wrapped_func


def create_pic_file(func):
    def wrapped_func(self, *args, **kwargs):
        with open(GOD_PIC, 'a') as f:
            f.write("just a test")
        try:
            func(self, *args, **kwargs)
        finally:
            pass

    return wrapped_func


class BaseTestClass:
    @cached_property
    def client(self):
        config = Config()
        config.from_pyfile(os.path.join(BASE_DIR, "client_config.py"))
        return Client(config)

    @staticmethod
    def callback_without_check(request):
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})


class TestClientBaseClass(BaseTestClass):
    def test_id_and_secret(self):
        assert self.client.appid == "123"
        assert self.client.appsecret == "321"

    def test_robot_client(self):
        robot = ZgRoBot()
        assert robot.client.config == robot.config

    def test_robot_reuse_client(self):
        robot = ZgRoBot()
        client_1 = robot.client
        client_2 = robot.client
        assert client_1 is client_2

    def test_check_error(self):
        error_json = dict(error_code=0)
        assert error_json == check_error(error_json)

        error_json = dict(errcode=1, errmsg="test")
        with pytest.raises(ClientException) as err:
            check_error(error_json)
        assert err.value.args[0] == "1: test"

    @responses.activate
    @add_token_response
    def test_grant_token(self):
        # responses.add_callback(responses.GET, TOKEN_URL, callback=token_callback)
        self.client.grant_token()
        assert self.client.token == "ACCESS_TOKEN"

    @responses.activate
    @add_token_response
    def test_client_request(self):
        EMPTY_PARAMS_URL = "http://empty-params.zgrobot.com/"
        DATA_EXISTS_URL = "http://data-exists.zgrobot.com/"

        def empty_params_callback(request):
            params = urllib.parse.parse_qs(
                urllib.parse.urlparse(request.url).query
            )
            assert params["access_token"][0] == self.client.token
            return 200, JSON_HEADER, json.dumps({"test": "test"})

        def data_exists_url(request):
            assert json.loads(request.body.decode('utf-8')) == {"test": "test"}
            return 200, JSON_HEADER, json.dumps({"test": "test"})

        responses.add_callback(
            responses.POST, DATA_EXISTS_URL, callback=data_exists_url
        )
        responses.add_callback(
            responses.GET, EMPTY_PARAMS_URL, callback=empty_params_callback
        )
        responses.add_callback(
            responses.GET, TOKEN_URL, callback=token_callback
        )

        r = self.client.get(url=EMPTY_PARAMS_URL)
        assert r == {"test": "test"}

        r = self.client.post(url=DATA_EXISTS_URL, data={"test": "test"})
        assert r == {"test": "test"}


class TestClientBaseClassPost(TestClientBaseClass):
    @pytest.fixture(autouse=True)
    def mock_request(self, mocker):
        self.mocked_request = mocker.spy(self.client, 'request')

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_post_with_unittest(self):
        POST_FILE_URL = "http://post_file.zgrobot.com/"

        def empty_post_file_callback(request):
            return 200, JSON_HEADER, json.dumps({"test": "test"})

        responses.add_callback(
            responses.POST, POST_FILE_URL, callback=empty_post_file_callback
        )

        with open(GOD_PIC, 'rb') as f:
            self.client.post(url=POST_FILE_URL, files={"media": f})
            self.mocked_request.assert_any_call(
                method='post',
                url='http://post_file.zgrobot.com/',
                files=dict(media=(urllib.parse.quote(GOD_PIC), f))
            )

        # Test another attribute
        with open(GOD_PIC, 'rb') as f:
            self.client.post(url=POST_FILE_URL, files={"gugugu": f})
            self.mocked_request.assert_any_call(
                method='post',
                url='http://post_file.zgrobot.com/',
                files=dict(gugugu=(urllib.parse.quote(GOD_PIC), f))
            )

    @responses.activate
    @add_token_response
    def test_post_with_file_object_without_name(self):
        POST_FILE_URL = "http://post_file.zgrobot.com/"

        def empty_post_file_callback(request):
            return 200, JSON_HEADER, json.dumps({"test": "test"})

        responses.add_callback(
            responses.POST, POST_FILE_URL, callback=empty_post_file_callback
        )

        f = BytesIO(b'gugugu')
        self.client.post(url=POST_FILE_URL, files={"media": f})
        self.mocked_request.assert_any_call(
            method='post',
            url='http://post_file.zgrobot.com/',
            files=dict(media=f)
        )

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_post_with_integration_test(self):
        POST_FILE_URL = "http://post_file.zgrobot.com/"

        def post_file_callback(request):
            s = request.body.split(b"\r")[0][2:]
            p = list(
                multipart.MultipartParser(
                    BytesIO(multipart.tob(request.body)), s
                )
            )[0]
            assert "filename" in p.options
            return 200, JSON_HEADER, json.dumps({"test": "test"})

        responses.add_callback(
            responses.POST, POST_FILE_URL, callback=post_file_callback
        )

        with open(GOD_PIC, 'rb') as f:
            self.client.post(url=POST_FILE_URL, files={"media": f})


class TestClientMenuClass(BaseTestClass):
    CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create"
    GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get"
    DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete"

    menu_data = {
        "button": [
            {
                "type": "click",
                "name": u"今日歌曲",
                "key": "V1001_TODAY_MUSIC"
            }, {
                "type": "click",
                "name": u"歌手简介",
                "key": "V1001_TODAY_SINGER"
            }, {
                "name": u"菜单",
                "sub_button": [
                    {
                        "type": "view",
                        "name": u"搜索",
                        "url": "http://www.soso.com/"
                    }, {
                        "type": "view",
                        "name": u"视频",
                        "url": "http://v.qq.com/"
                    }, {
                        "type": "click",
                        "name": u"赞一下我们",
                        "key": "V1001_GOOD"
                    }
                ]
            }
        ]
    }

    @staticmethod
    def create_menu_callback(request):
        def check_menu_data(item):
            keys = item.keys()
            assert "name" in keys
            if "sub_button" in keys:
                for button in item["sub_button"]:
                    check_menu_data(button)
                return
            assert "type" in keys
            if "type" == "click":
                assert "key" in keys
            elif "type" == "view":
                assert "url" in keys
            elif "type" == "media_id" or "type" == "view_limited":
                assert "media_id" in keys

        try:
            body = json.loads(request.body.decode("utf-8"))["button"]
        except KeyError:
            return 200, JSON_HEADER, json.dumps(
                {
                    "errcode": 1,
                    "errmsg": "error"
                }
            )
        try:
            for item in body:
                check_menu_data(item)
        except AssertionError:
            return 200, JSON_HEADER, json.dumps(
                {
                    "errcode": 1,
                    "errmsg": "error"
                }
            )
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_create_menu(self):
        responses.add_callback(
            responses.POST,
            self.CREATE_URL,
            callback=self.create_menu_callback
        )
        r = self.client.create_menu(self.menu_data)
        assert r == {"errcode": 0, "errmsg": "ok"}
        with pytest.raises(ClientException) as err:
            self.client.create_menu({"error": "error"})
        assert err.value.args[0] == "1: error"

    @responses.activate
    @add_token_response
    def test_get_menu(self):
        responses.add_callback(
            responses.GET, self.GET_URL, callback=self.callback_without_check
        )
        r = self.client.get_menu()
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_delete_menu(self):
        responses.add_callback(
            responses.GET,
            self.DELETE_URL,
            callback=self.callback_without_check
        )
        r = self.client.delete_menu()
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientGroupClass(BaseTestClass):
    CREATE_URL = "https://api.weixin.qq.com/cgi-bin/groups/create"
    GET_URL = "https://api.weixin.qq.com/cgi-bin/groups/get"
    GET_WITH_ID_URL = "https://api.weixin.qq.com/cgi-bin/groups/getid"
    UPDATE_URL = "https://api.weixin.qq.com/cgi-bin/groups/update"
    MOVE_URL = "https://api.weixin.qq.com/cgi-bin/groups/members/update"
    MOVE_USERS_URL = "https://api.weixin.qq.com/cgi-bin/groups/members/batchupdate"
    DELETE_URL = "https://api.weixin.qq.com/cgi-bin/groups/delete"

    @staticmethod
    def create_group_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "group" in body.keys()
        assert "name" in body["group"].keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def get_groups_with_id_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "openid" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def update_group_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "group" in body.keys()
        assert "id" in body["group"].keys()
        assert "name" in body["group"].keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def move_user_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "openid" in body.keys()
        assert "to_groupid" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def move_users_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "openid_list" in body.keys()
        assert "to_groupid" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def delete_group_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "group" in body.keys()
        assert "id" in body["group"].keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_create_group(self):
        responses.add_callback(
            responses.POST,
            self.CREATE_URL,
            callback=self.create_group_callback
        )
        r = self.client.create_group("test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_get_group(self):
        responses.add_callback(
            responses.GET, self.GET_URL, callback=self.callback_without_check
        )
        r = self.client.get_groups()
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_get_group_with_id(self):
        responses.add_callback(
            responses.POST,
            self.GET_WITH_ID_URL,
            callback=self.get_groups_with_id_callback
        )
        r = self.client.get_group_by_id("test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_update_group(self):
        responses.add_callback(
            responses.POST,
            self.UPDATE_URL,
            callback=self.update_group_callback
        )
        r = self.client.update_group("0", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_move_user(self):
        responses.add_callback(
            responses.POST, self.MOVE_URL, callback=self.move_user_callback
        )
        r = self.client.move_user("test", "0")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_move_users(self):
        responses.add_callback(
            responses.POST,
            self.MOVE_USERS_URL,
            callback=self.move_users_callback
        )
        r = self.client.move_users("test", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_delete_group(self):
        responses.add_callback(
            responses.POST,
            self.DELETE_URL,
            callback=self.delete_group_callback
        )
        r = self.client.delete_group("test")
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientRemarkClass(BaseTestClass):
    REMARK_URL = "https://api.weixin.qq.com/cgi-bin/user/info/updateremark"

    @staticmethod
    def remark_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "openid" in body.keys()
        assert "remark" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_client_remark(self):
        responses.add_callback(
            responses.POST, self.REMARK_URL, callback=self.remark_callback
        )
        r = self.client.remark_user("test", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientUserInfo(BaseTestClass):
    SINGLE_USER_URL = "https://api.weixin.qq.com/cgi-bin/user/info"
    MULTI_USER_URL = "https://api.weixin.qq.com/cgi-bin/user/info/batchget"

    @staticmethod
    def single_user_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        assert "openid" in params.keys()
        assert "lang" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def multi_user_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "user_list" in body.keys()
        for user in body["user_list"]:
            assert "openid" in user.keys()
            assert "lang" in user.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_single_user(self):
        responses.add_callback(
            responses.GET,
            self.SINGLE_USER_URL,
            callback=self.single_user_callback
        )
        r = self.client.get_user_info("test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_multi_user(self):
        responses.add_callback(
            responses.POST,
            self.MULTI_USER_URL,
            callback=self.multi_user_callback
        )
        r = self.client.get_users_info(["test1", "test2"])
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientGetFollowersClass(BaseTestClass):
    FOLLOWER_URL = "https://api.weixin.qq.com/cgi-bin/user/get"

    @staticmethod
    def get_followers_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        assert "next_openid" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_get_followers(self):
        responses.add_callback(
            responses.GET,
            self.FOLLOWER_URL,
            callback=self.get_followers_callback
        )
        r = self.client.get_followers("test")
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientCustomMenuClass(BaseTestClass):
    GET_URL = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info"
    CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/addconditional"
    DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delconditional"
    MATCH_URL = "https://api.weixin.qq.com/cgi-bin/menu/trymatch"

    custom_data = {
        "menu_data": [
            {
                "type": "click",
                "name": u"今日歌曲",
                "key": "V1001_TODAY_MUSIC"
            }, {
                "name": u"菜单",
                "sub_button": [
                    {
                        "type": "view",
                        "name": u"搜索",
                        "url": "http://www.soso.com/"
                    }, {
                        "type": "view",
                        "name": u"视频",
                        "url": "http://v.qq.com/"
                    }, {
                        "type": "click",
                        "name": u"赞一下我们",
                        "key": "V1001_GOOD"
                    }
                ]
            }
        ],
        "matchrule": {
            "group_id": "2",
            "sex": "1",
            "country": u"中国",
            "province": u"广东",
            "city": u"广州",
            "client_platform_type": "2",
            "language": "zh_CN"
        }
    }

    @staticmethod
    def get_custom_menu_callback(request):
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def create_custom_menu_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "button" in body.keys()
        assert "matchrule" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def delete_custom_menu_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "menuid" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def match_custom_menu(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "user_id" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_get_custom_menu(self):
        responses.add_callback(
            responses.GET,
            self.GET_URL,
            callback=self.get_custom_menu_callback
        )
        r = self.client.get_custom_menu_config()
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_create_custom_menu(self):
        responses.add_callback(
            responses.POST,
            self.CREATE_URL,
            callback=self.create_custom_menu_callback
        )
        r = self.client.create_custom_menu(**self.custom_data)
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_delete_custom_menu(self):
        responses.add_callback(
            responses.POST,
            self.DELETE_URL,
            callback=self.delete_custom_menu_callback
        )
        r = self.client.delete_custom_menu("test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_march_custom_menu(self):
        responses.add_callback(
            responses.POST, self.MATCH_URL, callback=self.match_custom_menu
        )
        r = self.client.match_custom_menu("test")
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientResourceClass(BaseTestClass):
    UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/media/upload"
    DOWNLOAD_URL = "https://api.weixin.qq.com/cgi-bin/media/get"
    ADD_NEWS_URL = "https://api.weixin.qq.com/cgi-bin/material/add_news"
    UPLOAD_PICTURE_URL = "https://api.weixin.qq.com/cgi-bin/media/uploadimg"
    UPLOAD_P_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material"
    DOWNLOAD_P_URL = "https://api.weixin.qq.com/cgi-bin/material/get_material"
    DELETE_P_URL = "https://api.weixin.qq.com/cgi-bin/material/del_material"
    UPDATE_NEWS_URL = "https://api.weixin.qq.com/cgi-bin/material/update_news"
    add_news_data = [
        {
            "title": "test_title",
            "thumb_media_id": "test",
            "author": "test",
            "digest": "test",
            "show_cover_pic": 1,
            "content": "test",
            "content_source_url": "test"
        }
    ]
    update_data = {
        "media_id": "test",
        "index": "test",
        "articles": {
            "title": "test",
            "thumb_media_id": "test",
            "author": "test",
            "digest": "test",
            "show_cover_pic": 1,
            "content": "test",
            "content_source_url": "test"
        }
    }

    @staticmethod
    def upload_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "type" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def download_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "media_id" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def add_news_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "articles" in body.keys()
        for article in body["articles"]:
            assert "title" in article.keys()
            assert "thumb_media_id" in article.keys()
            assert "author" in article.keys()
            assert "digest" in article.keys()
            assert "show_cover_pic" in article.keys()
            assert "content" in article.keys()
            assert "content_source_url" in article.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def upload_picture_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def upload_p_media_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        assert "type" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def download_p_media_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert "media_id" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def delete_p_media_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "media_id" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def update_news_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "media_id" in body.keys()
        assert "index" in body.keys()
        assert "articles" in body.keys()
        articles = body["articles"]
        assert "title" in articles.keys()
        assert "thumb_media_id" in articles.keys()
        assert "author" in articles.keys()
        assert "digest" in articles.keys()
        assert "show_cover_pic" in articles.keys()
        assert "content" in articles.keys()
        assert "content_source_url" in articles.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_upload_media(self):
        responses.add_callback(
            responses.POST, self.UPLOAD_URL, callback=self.upload_callback
        )
        with open(GOD_PIC, 'rb') as f:
            r = self.client.upload_media('image', f)
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_download_media(self):
        responses.add_callback(
            responses.GET, self.DOWNLOAD_URL, callback=self.download_callback
        )
        r = self.client.download_media("test")
        assert isinstance(r, requests.Response)

    @responses.activate
    @add_token_response
    def test_add_news(self):
        responses.add_callback(
            responses.POST, self.ADD_NEWS_URL, callback=self.add_news_callback
        )
        r = self.client.add_news(self.add_news_data)
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_upload_news_picture(self):
        responses.add_callback(
            responses.POST,
            self.UPLOAD_PICTURE_URL,
            callback=self.upload_picture_callback
        )
        with open(GOD_PIC, 'rb') as f:
            r = self.client.upload_news_picture(f)
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_upload_permanent_media(self):
        responses.add_callback(
            responses.POST,
            self.UPLOAD_P_URL,
            callback=self.upload_p_media_callback
        )
        with open(GOD_PIC, 'rb') as f:
            r = self.client.upload_permanent_media('image', f)
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_download_permanent_media(self):
        responses.add_callback(
            responses.POST,
            self.DOWNLOAD_P_URL,
            callback=self.download_p_media_callback
        )
        r = self.client.download_permanent_media("test")
        assert isinstance(r, requests.Response)

    @responses.activate
    @add_token_response
    def test_delete_permanent_media(self):
        responses.add_callback(
            responses.POST,
            self.DELETE_P_URL,
            callback=self.delete_p_media_callback
        )
        r = self.client.delete_permanent_media("test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_update_news(self):
        responses.add_callback(
            responses.POST,
            self.UPDATE_NEWS_URL,
            callback=self.update_news_callback
        )
        r = self.client.update_news(self.update_data)
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestUploadVideoClass(BaseTestClass):
    UPLOAD_VIDEO_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material"

    @staticmethod
    def upload_video_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "type" in params.keys()
        assert params["type"][0] == "video"
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_upload_video(self):
        responses.add_callback(
            responses.POST,
            self.UPLOAD_VIDEO_URL,
            callback=self.upload_video_callback
        )
        with open(GOD_PIC, 'rb') as f:
            r = self.client.upload_permanent_video("test", "test", f)
        assert isinstance(r, requests.Response)


class TestMediaClass(BaseTestClass):
    GET_URL = "https://api.weixin.qq.com/cgi-bin/material/get_materialcount"
    GET_LIST_URL = "https://api.weixin.qq.com/cgi-bin/material/batchget_material"

    @staticmethod
    def get_media_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def get_media_list_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "type" in body.keys()
        assert "offset" in body.keys()
        assert "count" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_get_media(self):
        responses.add_callback(
            responses.GET, self.GET_URL, callback=self.get_media_callback
        )
        r = self.client.get_media_count()
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_get_media_list(self):
        responses.add_callback(
            responses.POST,
            self.GET_LIST_URL,
            callback=self.get_media_list_callback
        )
        r = self.client.get_media_list("test", "test", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestGetIpListClass(BaseTestClass):
    GET_URL = "https://api.weixin.qq.com/cgi-bin/getcallbackip"

    @staticmethod
    def get_ip_list_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_get_ip_list(self):
        responses.add_callback(
            responses.GET, self.GET_URL, callback=self.get_ip_list_callback
        )
        r = self.client.get_ip_list()
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestCustomService(BaseTestClass):
    ADD_URL = "https://api.weixin.qq.com/customservice/kfaccount/add"
    UPDATE_URL = "https://api.weixin.qq.com/customservice/kfaccount/update"
    DELETE_URL = "https://api.weixin.qq.com/customservice/kfaccount/del"
    UPLOAD_URL = "https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg"
    GET_URL = "https://api.weixin.qq.com/cgi-bin/customservice/getkflist"
    GET_ONLINE_URL = "https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist"

    @staticmethod
    def add_update_delete_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "kf_account" in body.keys()
        assert "nickname" in body.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def upload_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def get_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_add_custom_service_account(self):
        responses.add_callback(
            responses.POST,
            self.ADD_URL,
            callback=self.add_update_delete_callback
        )
        r = self.client.add_custom_service_account("test", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_update_custom_service_account(self):
        responses.add_callback(
            responses.POST,
            self.UPDATE_URL,
            callback=self.add_update_delete_callback
        )
        r = self.client.update_custom_service_account("test", "test", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_delete_custom_service_account(self):
        responses.add_callback(
            responses.POST,
            self.DELETE_URL,
            callback=self.add_update_delete_callback
        )
        r = self.client.delete_custom_service_account("test", "test", "test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    @create_pic_file
    def test_upload_custom_service_account_avatar(self):
        responses.add_callback(
            responses.POST, self.UPLOAD_URL, callback=self.upload_callback
        )
        with open(GOD_PIC, 'rb') as f:
            r = self.client.upload_custom_service_account_avatar("image", f)
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_get_custom_service_account_list(self):
        responses.add_callback(
            responses.GET, self.GET_URL, callback=self.get_callback
        )
        r = self.client.get_custom_service_account_list()
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_get_online_custom_service_account_list(self):
        responses.add_callback(
            responses.GET, self.GET_ONLINE_URL, callback=self.get_callback
        )
        r = self.client.get_online_custom_service_account_list()
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestQrcodeClass(BaseTestClass):
    CREATE_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create"
    SHOW_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode"

    @staticmethod
    def create_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @staticmethod
    def show_callback(request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "ticket" in params.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_create_qrcode(self):
        responses.add_callback(
            responses.POST, self.CREATE_URL, callback=self.create_callback
        )
        r = self.client.create_qrcode("test")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @responses.activate
    @add_token_response
    def test_show_qrcode(self):
        responses.add_callback(
            responses.GET, self.SHOW_URL, callback=self.show_callback
        )
        r = self.client.show_qrcode("test")
        assert isinstance(r, requests.Response)


class TestSendArticleMessagesClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def article_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "news"
        assert "news" in body.keys()
        for article in body["news"]["articles"]:
            assert "title" in article.keys()
            assert "description" in article.keys()
            assert "url" in article.keys()
            assert "picurl" in article.keys()
        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_article_messages(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.article_callback
        )

        from zgrobot.replies import Article
        articles = []
        for _ in range(0, 8):
            articles.append(
                Article(
                    *[
                        "test_title",
                        "test_description",
                        "test_img",
                        "test_url",
                    ]
                )
            )

        r = self.client.send_article_message("test_id", articles)
        assert r == {"errcode": 0, "errmsg": "ok"}

        articles = []
        for _ in range(0, 8):
            articles.append(
                {
                    "title": "test_title",
                    "description": "test_description",
                    "url": "test_url",
                    "picurl": "test_pic_url"
                }
            )

        r = self.client.send_article_message("test_id", articles)
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestSendTextMessageClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def text_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "text"
        assert "text" in body.keys()
        assert "content" in body["text"].keys()
        assert "customservice" not in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_text_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.text_callback
        )

        r = self.client.send_text_message("test_id", "test_message")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @staticmethod
    def text_with_kf_account_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "text"
        assert "text" in body.keys()
        assert "content" in body["text"].keys()
        assert "customservice" in body.keys()
        assert "kf_account" in body["customservice"].keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_text_message_with_kf_account(self):
        responses.add_callback(
            responses.POST,
            self.URL,
            callback=self.text_with_kf_account_callback
        )
        r = self.client.send_text_message("test_id", "test_message", "233@233")
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestSendImageMessageClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def image_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "image"
        assert "image" in body.keys()
        assert "media_id" in body["image"].keys()
        assert "customservice" not in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_image_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.image_callback
        )

        r = self.client.send_image_message("test_id", "test_media_id")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @staticmethod
    def image_with_kf_account_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "image"
        assert "image" in body.keys()
        assert "media_id" in body["image"].keys()
        assert "customservice" in body.keys()
        assert "kf_account" in body["customservice"].keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_image_message_with_kf_account(self):
        responses.add_callback(
            responses.POST,
            self.URL,
            callback=self.image_with_kf_account_callback
        )

        r = self.client.send_image_message(
            "test_id", "test_media_id", "233@233"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestSendVoiceMessageClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def voice_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "voice"
        assert "voice" in body.keys()
        assert "media_id" in body["voice"].keys()
        assert "customservice" not in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_voice_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.voice_callback
        )

        r = self.client.send_voice_message("test_id", "test_media_id")
        assert r == {"errcode": 0, "errmsg": "ok"}

    @staticmethod
    def voice_with_kf_account_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "voice"
        assert "voice" in body.keys()
        assert "media_id" in body["voice"].keys()
        assert "customservice" in body.keys()
        assert "kf_account" in body["customservice"].keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_voice_message_with_kf_account(self):
        responses.add_callback(
            responses.POST,
            self.URL,
            callback=self.voice_with_kf_account_callback
        )

        r = self.client.send_voice_message(
            "test_id", "test_media_id", "233@233"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestMusicMessageClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def music_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "music"
        assert "music" in body.keys()
        assert "musicurl" in body["music"].keys()
        assert "hqmusicurl" in body["music"].keys()
        assert "thumb_media_id" in body["music"].keys()
        assert "customservice" not in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_music_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.music_callback
        )

        r = self.client.send_music_message(
            user_id="test_id",
            url="test_url",
            hq_url="test_hq_url",
            thumb_media_id="test_media_id",
            title="test_title",
            description="test_description"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}

    @staticmethod
    def music_with_kf_account_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "music"
        assert "music" in body.keys()
        assert "musicurl" in body["music"].keys()
        assert "hqmusicurl" in body["music"].keys()
        assert "thumb_media_id" in body["music"].keys()
        assert "customservice" in body.keys()
        assert "kf_account" in body["customservice"].keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_music_message_with_kf_account(self):
        responses.add_callback(
            responses.POST,
            self.URL,
            callback=self.music_with_kf_account_callback
        )

        r = self.client.send_music_message(
            user_id="test_id",
            url="test_url",
            hq_url="test_hq_url",
            thumb_media_id="test_media_id",
            title="test_title",
            description="test_description",
            kf_account="233@233"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestVideoMessageClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def video_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "video"
        assert "video" in body.keys()
        assert "media_id" in body["video"].keys()
        assert "customservice" not in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_video_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.video_callback
        )
        r = self.client.send_video_message(
            user_id="test_id",
            media_id="test_media_id",
            title="test_title",
            description="test_description",
        )
        assert r == {"errcode": 0, "errmsg": "ok"}

    @staticmethod
    def video_with_kf_account_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "video"
        assert "video" in body.keys()
        assert "media_id" in body["video"].keys()
        assert "customservice" in body.keys()
        assert "kf_account" in body["customservice"].keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_video_message_with_kf_account(self):
        responses.add_callback(
            responses.POST,
            self.URL,
            callback=self.video_with_kf_account_callback
        )
        r = self.client.send_video_message(
            user_id="test_id",
            media_id="test_media_id",
            title="test_title",
            description="test_description",
            kf_account="233@233"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestNewsMessageClass(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def news_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "mpnews"
        assert "mpnews" in body.keys()
        assert "media_id" in body["mpnews"].keys()
        assert "customservice" not in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_news_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.news_callback
        )

        r = self.client.send_news_message(
            user_id="test_id", media_id="test_media_id"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}

    @staticmethod
    def news_with_kf_account_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "mpnews"
        assert "mpnews" in body.keys()
        assert "media_id" in body["mpnews"].keys()
        assert "customservice" in body.keys()
        assert "kf_account" in body["customservice"].keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_news_message_with_kf_account(self):
        responses.add_callback(
            responses.POST,
            self.URL,
            callback=self.news_with_kf_account_callback
        )

        r = self.client.send_news_message(
            user_id="test_id", media_id="test_media_id", kf_account="233@233"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestTemplateMessage(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/template/send"

    @staticmethod
    def template_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "template_id" in body.keys()
        assert "url" in body.keys()
        assert "data" in body.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_template_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.template_callback
        )

        r = self.client.send_template_message(
            user_id="test_id",
            template_id="test_template_id",
            data="test_data",
            url="test_url"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestMiniprogrampageMessage(BaseTestClass):
    URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"

    @staticmethod
    def miniprogrampage_callback(request):
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body.keys()
        assert "msgtype" in body.keys()
        assert body["msgtype"] == "miniprogrampage"
        assert "miniprogrampage" in body.keys()
        miniprogrampage = body["miniprogrampage"]
        assert "title" in miniprogrampage.keys()
        assert "appid" in miniprogrampage.keys()
        assert "pagepath" in miniprogrampage.keys()
        assert "thumb_media_id" in miniprogrampage.keys()

        return 200, JSON_HEADER, json.dumps({"errcode": 0, "errmsg": "ok"})

    @responses.activate
    @add_token_response
    def test_send_miniprogrampage_message(self):
        responses.add_callback(
            responses.POST, self.URL, callback=self.miniprogrampage_callback
        )

        r = self.client.send_miniprogrampage_message(
            user_id="test_id",
            title="test_title",
            appid="test_appid",
            pagepath="test_pagepath",
            thumb_media_id="test_id"
        )
        assert r == {"errcode": 0, "errmsg": "ok"}


class TestClientTagManageClass(BaseTestClass):
    CREATE_TAG_URL = "https://api.weixin.qq.com/cgi-bin/tags/create"
    GET_TAGS_URL = "https://api.weixin.qq.com/cgi-bin/tags/get"
    UPDATE_TAG_URL = "https://api.weixin.qq.com/cgi-bin/tags/update"
    DELETE_TAG_URL = "https://api.weixin.qq.com/cgi-bin/tags/delete"
    GET_USERS_BY_TAG_URL = "https://api.weixin.qq.com/cgi-bin/user/tag/get"

    create_tag_name = u"喵喵喵"
    create_tag_id = 100

    update_tag_name = "nyanya"
    update_tag_id = 100

    get_tags_response = {
        'tags': [
            {
                'id': 2,
                'name': u'星标组',
                'count': 0
            }, {
                'id': 100,
                'name': update_tag_name,
                'count': 0
            }
        ]
    }

    get_users_by_tag_id = 100
    get_users_by_tag_response = {
        'count': 1,
        'data': {
            'openid': ['testopenid']
        },
        'next_openid': 'testopenid'
    }

    delete_tag_id = 100

    def create_tag_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {"tag": {"name": self.create_tag_name}}
        return 200, JSON_HEADER, json.dumps(
            {"tag": {
                "id": self.create_tag_id,
                "name": self.create_tag_name
            }}
        )

    def update_tag_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {
            "tag": {
                "id": self.update_tag_id,
                "name": self.update_tag_name
            }
        }
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def get_tags_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps(self.get_tags_response)

    def get_users_by_tag_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {"tagid": self.get_users_by_tag_id, "next_openid": ""}
        return 200, JSON_HEADER, json.dumps(self.get_users_by_tag_response)

    def delete_tag_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {
            "tag": {
                "id": self.delete_tag_id,
            }
        }
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    @responses.activate
    @add_token_response
    def test_create_tag(self):
        responses.add_callback(
            responses.POST,
            self.CREATE_TAG_URL,
            callback=self.create_tag_callback
        )
        r = self.client.create_tag(self.create_tag_name)
        assert r == {
            "tag": {
                "id": self.create_tag_id,
                "name": self.create_tag_name
            }
        }

    @responses.activate
    @add_token_response
    def test_update_tag(self):
        responses.add_callback(
            responses.POST,
            self.UPDATE_TAG_URL,
            callback=self.update_tag_callback
        )
        r = self.client.update_tag(self.update_tag_id, self.update_tag_name)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_get_tags(self):
        responses.add_callback(
            responses.GET, self.GET_TAGS_URL, callback=self.get_tags_callback
        )
        r = self.client.get_tags()
        assert r == self.get_tags_response

    @responses.activate
    @add_token_response
    def test_get_users_by_tag(self):
        responses.add_callback(
            responses.POST,
            self.GET_USERS_BY_TAG_URL,
            callback=self.get_users_by_tag_callback
        )
        r = self.client.get_users_by_tag(self.get_users_by_tag_id)
        assert r == self.get_users_by_tag_response

    @responses.activate
    @add_token_response
    def test_delete_tag(self):
        responses.add_callback(
            responses.POST,
            self.DELETE_TAG_URL,
            callback=self.delete_tag_callback
        )
        r = self.client.delete_tag(self.delete_tag_id)
        assert r == {'errcode': 0, 'errmsg': 'ok'}


class TestClientMembersTagClass(BaseTestClass):
    TAG_USER_URL = "https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging"
    UNTAG_USER_URL = "https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging"
    GET_TAGS_BY_USER_URL = "https://api.weixin.qq.com/cgi-bin/tags/getidlist"

    tag_id = 100
    user_a_open_id = 'a'
    user_b_open_id = 'b'
    user_c_open_id = 'c'
    users_list = [user_a_open_id, user_b_open_id, user_c_open_id]

    get_tags_by_user_response = {'tagid_list': [tag_id]}

    def tag_user_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {"openid_list": self.users_list, "tagid": self.tag_id}
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def untag_user_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {"openid_list": self.users_list, "tagid": self.tag_id}
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def get_tags_by_user_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {
            "openid": self.user_a_open_id,
        }
        return 200, JSON_HEADER, json.dumps(self.get_tags_by_user_response)

    @responses.activate
    @add_token_response
    def test_tag_users(self):
        responses.add_callback(
            responses.POST, self.TAG_USER_URL, callback=self.tag_user_callback
        )
        r = self.client.tag_users(self.tag_id, self.users_list)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_untag_users(self):
        responses.add_callback(
            responses.POST,
            self.UNTAG_USER_URL,
            callback=self.untag_user_callback
        )
        r = self.client.untag_users(self.tag_id, self.users_list)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_get_tags_by_user(self):
        responses.add_callback(
            responses.POST,
            self.GET_TAGS_BY_USER_URL,
            callback=self.get_tags_by_user_callback
        )
        r = self.client.get_tags_by_user(self.user_a_open_id)
        assert r == self.get_tags_by_user_response


class TestClientMass(BaseTestClass):
    UP_NEWS_URL = 'https://api.weixin.qq.com/cgi-bin/media/uploadnews'
    SEND_ALL_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/sendall'
    SEND_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/send'
    DELETE_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/delete'
    PREVIEW_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/preview'
    GET_STATUS_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/get'
    GET_SPEED_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/speed/get'
    SET_SPEED_URL = 'https://api.weixin.qq.com/cgi-bin/message/mass/speed/set'

    articles = [
        {
            "thumb_media_id": "qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
            "author": "xxx",
            "title": "Happy Day",
            "content_source_url": "www.qq.com",
            "content": "content",
            "digest": "digest",
            "show_cover_pic": 1,
            "need_open_comment": 1,
            "only_fans_can_comment": 1
        }, {
            "thumb_media_id": "qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
            "author": "xxx",
            "title": "Happy Day",
            "content_source_url": "www.qq.com",
            "content": "content",
            "digest": "digest",
            "show_cover_pic": 0,
            "need_open_comment": 1,
            "only_fans_can_comment": 1
        }
    ]

    def up_news_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert body == {"articles": self.articles}
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def send_all_openid_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert 'touser' in body
        count = len(body['touser'])
        assert count >= 2
        assert count <= 10000
        assert 'msgtype' in body
        types = {
            'mpnews': 'media_id',
            'mpvideo': 'media_id',
            'music': 'media_id',
            'image': 'media_id',
            'video': 'media_id',
            'wxcard': 'card_id',
            'text': 'content',
        }
        assert types[body['msgtype']] in body[body['msgtype']]
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def send_all_tagid_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert 'filter' in body
        assert 'msgtype' in body
        types = {
            'mpnews': 'media_id',
            'mpvideo': 'media_id',
            'music': 'media_id',
            'image': 'media_id',
            'video': 'media_id',
            'wxcard': 'card_id',
            'text': 'content',
        }
        assert types[body['msgtype']] in body[body['msgtype']]
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def delete_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert "msg_id" in body
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def preview_openid_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert "touser" in body
        types = {
            'mpnews': 'media_id',
            'mpvideo': 'media_id',
            'music': 'media_id',
            'image': 'media_id',
            'video': 'media_id',
            'wxcard': 'card_id',
            'text': 'content',
        }
        assert types[body['msgtype']] in body[body['msgtype']]
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def preview_wxname_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert "towxname" in body
        types = {
            'mpnews': 'media_id',
            'mpvideo': 'media_id',
            'music': 'media_id',
            'image': 'media_id',
            'video': 'media_id',
            'wxcard': 'card_id',
            'text': 'content',
            'voice': 'media_id'
        }
        assert types[body['msgtype']] in body[body['msgtype']]
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    def get_status_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert "msg_id" in body
        return 200, JSON_HEADER, json.dumps(
            {
                "msg_id": body['msg_id'],
                "msg_status": "SEND_SUCCESS"
            }
        )

    def get_news_speed_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        return 200, JSON_HEADER, json.dumps({"speed": 3, "realspeed": 15})

    def set_news_speed_callback(self, request):
        params = urllib.parse.parse_qs(
            urllib.parse.urlparse(request.url).query
        )
        assert "access_token" in params.keys()
        body = json.loads(request.body.decode("utf-8"))
        assert "speed" in body
        return 200, JSON_HEADER, json.dumps({'errcode': 0, 'errmsg': 'ok'})

    @responses.activate
    @add_token_response
    def test_upload_news(self):
        responses.add_callback(
            responses.POST, self.UP_NEWS_URL, callback=self.up_news_callback
        )
        r = self.client.upload_news(self.articles)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_send_by_openid(self):
        responses.add_callback(
            responses.POST,
            self.SEND_URL,
            callback=self.send_all_openid_callback
        )
        r = self.client.send_mass_msg('mpnews', 'hgbkjnlkmlkn', ['1', '2'], 0)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_send_by_tagid(self):
        responses.add_callback(
            responses.POST,
            self.SEND_ALL_URL,
            callback=self.send_all_tagid_callback
        )
        r = self.client.send_mass_msg('mpnews', 'hgbkjnlkmlkn', 2, 0)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_send_all(self):
        responses.add_callback(
            responses.POST,
            self.SEND_ALL_URL,
            callback=self.send_all_tagid_callback
        )
        r = self.client.send_mass_msg('mpnews', 'hgbkjnlkmlkn', 0, 0)
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_preview_openid(self):
        responses.add_callback(
            responses.POST,
            self.PREVIEW_URL,
            callback=self.preview_openid_callback
        )
        r = self.client.send_mass_preview_to_user(
            type, 'hgbkjnlkmlkn', 'asdasdasd', 'openid'
        )
        assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_preview_wxname(self):
        responses.add_callback(
            responses.POST,
            self.PREVIEW_URL,
            callback=self.preview_wxname_callback
        )
        type_list = [
            'mpnews', 'text', 'voice', 'music', 'image', 'video', 'wxcard'
        ]
        for type in type_list:
            r = self.client.send_mass_preview_to_user(
                type, 'hgbkjnlkmlkn', 'asdasdasd', 'wxname'
            )
            assert r == {'errcode': 0, 'errmsg': 'ok'}

    @responses.activate
    @add_token_response
    def test_get_status(self):
        responses.add_callback(
            responses.POST,
            self.GET_STATUS_URL,
            callback=self.get_status_callback
        )
        r = self.client.get_mass_msg_status('mpnews')
        assert 'msg_id' in r
        assert 'msg_status' in r

    @responses.activate
    @add_token_response
    def test_get_speed(self):
        responses.add_callback(
            responses.POST,
            self.GET_SPEED_URL,
            callback=self.get_news_speed_callback
        )
        r = self.client.get_mass_msg_speed()
        assert 'speed' in r
        assert 'realspeed' in r

    @responses.activate
    @add_token_response
    def test_set_speed(self):
        responses.add_callback(
            responses.POST,
            self.SET_SPEED_URL,
            callback=self.set_news_speed_callback
        )
        r = self.client.set_mass_msg_speed(1)
        assert r == {'errcode': 0, 'errmsg': 'ok'}
