# -*- coding: utf-8 -*-

# Copyright (C) 2015-2016 Avencall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


from hamcrest import assert_that, equal_to, has_entries, has_entry, has_item, has_items, has_length, \
    is_not, contains_exactly, none, not_none, greater_than
from test_api import associations as a
from test_api import confd
from test_api import config
from test_api import db
from test_api import errors as e
from test_api import fixtures
from test_api import mocks
from test_api import scenarios as s
from test_api.bus import BusClient
import unittest

FULL_USER = {"firstname": "Jôhn",
             "lastname": "Smêth",
             "username": "jsmeth",
             "email": "jsmeth@smeth.com",
             "mobile_phone_number": "+4185551234*2",
             "userfield": "userfield",
             "caller_id": '"Jôhnny Smith" <4185551234>',
             "outgoing_caller_id": '"Johnny" <123>',
             "music_on_hold": "default",
             "language": "fr_FR",
             "timezone": "America/Montreal",
             "preprocess_subroutine": "preprocess_subroutine",
             "password": "password",
             "description": "John's description",
             "supervision_enabled": False,
             "call_transfer_enabled": False,
             "call_record_enabled": True,
             "online_call_record_enabled": True,
             "call_permission_password": '1234',
             "enabled": False,
             "ring_seconds": 60,
             "agent_number": None,
             "agent_group_id": None,
             "simultaneous_calls": 10,
             "labels": []}

AGENT_USER = {"firstname": "James",
              "lastname": "Bond",
              "username": "jbond",
              "email": "jbond@gov.uk",
              "mobile_phone_number": "+44123456789",
              "music_on_hold": "default",
              "language": "fr_FR",
              "timezone": "Europe/London",
              "password": "password",
              "agent_number": "2000",
              "agent_group_id": 2,
              "simultaneous_calls": 10}

NULL_USER = {"firstname": "Jôhn",
             "lastname": None,
             "username": None,
             "email": None,
             "mobile_phone_number": None,
             "userfield": None,
             "outgoing_caller_id": None,
             "music_on_hold": None,
             "language": None,
             "timezone": None,
             "preprocess_subroutine": None,
             "password": None,
             "description": None,
             "supervision_enabled": True,
             "call_transfer_enabled": True,
             "call_record_enabled": False,
             "online_call_record_enabled": False,
             "call_permission_password": None,
             "enabled": True,
             "ring_seconds": 20,
             "agent_number": None,
             "agent_group_id": None,
             "simultaneous_calls": 2,
             "labels": []}

class TestUsers(unittest.TestCase):

    def test_get_errors(self):
        fake_get = confd.users(999999).get
        yield s.check_resource_not_found, fake_get, 'User'


    def test_post_errors(self):
        empty_post = confd.users.post
        user_post = confd.users(firstname="Jôhn").post

        yield s.check_missing_required_field_returns_error, empty_post, 'firstname'
        for check in self.error_checks(user_post):
            yield check


    def error_checks(self, url):
        yield s.check_bogus_field_returns_error, url, 'firstname', 123
        yield s.check_bogus_field_returns_error, url, 'firstname', None
        yield s.check_bogus_field_returns_error, url, 'lastname', 123
        yield s.check_bogus_field_returns_error, url, 'email', 123
        yield s.check_bogus_field_returns_error, url, 'timezone', 123
        yield s.check_bogus_field_returns_error, url, 'language', 123
        yield s.check_bogus_field_returns_error, url, 'description', 123
        yield s.check_bogus_field_returns_error, url, 'caller_id', 123
        yield s.check_bogus_field_returns_error, url, 'outgoing_caller_id', 123
        yield s.check_bogus_field_returns_error, url, 'mobile_phone_number', 123
        yield s.check_bogus_field_returns_error, url, 'username', 123
        yield s.check_bogus_field_returns_error, url, 'password', 123
        yield s.check_bogus_field_returns_error, url, 'music_on_hold', 123
        yield s.check_bogus_field_returns_error, url, 'preprocess_subroutine', 123
        yield s.check_bogus_field_returns_error, url, 'userfield', 123
        yield s.check_bogus_field_returns_error, url, 'caller_id', 'callerid'
        yield s.check_bogus_field_returns_error, url, 'mobile_phone_number', '123abcd'
        yield s.check_bogus_field_returns_error, url, 'call_transfer_enabled', 'yeah'
        yield s.check_bogus_field_returns_error, url, 'call_record_enabled', 'yeah'
        yield s.check_bogus_field_returns_error, url, 'online_call_record_enabled', 'yeah'
        yield s.check_bogus_field_returns_error, url, 'supervision_enabled', 'yeah'
        yield s.check_bogus_field_returns_error, url, 'ring_seconds', 'ten'
        yield s.check_bogus_field_returns_error, url, 'simultaneous_calls', 'sixty'
        yield s.check_bogus_field_returns_error, url, 'simultaneous_calls', -1
        yield s.check_bogus_field_returns_error, url, 'simultaneous_calls', 21
        yield s.check_bogus_field_returns_error, url, 'username', 'ûsername',
        yield s.check_bogus_field_returns_error, url, 'ring_seconds', 6
        yield s.check_bogus_field_returns_error, url, 'ring_seconds', -1
        yield s.check_bogus_field_returns_error, url, 'ring_seconds', 65
        yield s.check_bogus_field_returns_error, url, 'language', 'klingon'
        yield s.check_bogus_field_returns_error, url, 'call_permission_password', 1234
        yield s.check_bogus_field_returns_error, url, 'call_permission_password', 'invalid_char'
        yield s.check_bogus_field_returns_error, url, 'enabled', 'yeah'


    def put_error_checks(self, url):
        yield s.check_bogus_field_returns_error, url, 'caller_id', None
        yield s.check_bogus_field_returns_error, url, 'call_transfer_enabled', None
        yield s.check_bogus_field_returns_error, url, 'call_record_enabled', None
        yield s.check_bogus_field_returns_error, url, 'online_call_record_enabled', None
        yield s.check_bogus_field_returns_error, url, 'supervision_enabled', None
        yield s.check_bogus_field_returns_error, url, 'ring_seconds', None
        yield s.check_bogus_field_returns_error, url, 'simultaneous_calls', None
        yield s.check_bogus_field_returns_error, url, 'ring_seconds', None
        yield s.check_bogus_field_returns_error, url, 'enabled', None


    @fixtures.user()
    def test_put_errors(self, user):
        user_put = confd.users(user['id']).put

        for check in self.error_checks(user_put):
            yield check
        for check in self.put_error_checks(user_put):
            yield check


    @fixtures.user(firstname='user1', username='unique_username', email='unique@email')
    @fixtures.user()
    def test_unique_errors(self, user1, user2):
        url = confd.users(user2['id']).put
        for check in self.unique_error_checks(url, user1):
            yield check

        required_body = {'firstname': 'user2'}
        url = confd.users.post
        for check in self.unique_error_checks(url, user1, required_body):
            yield check


    def unique_error_checks(self, url, existing_resource, required_body=None):
        yield s.check_bogus_field_returns_error, url, 'username', existing_resource['username'], required_body
        yield s.check_bogus_field_returns_error, url, 'email', existing_resource['email'], required_body


    @fixtures.user()
    def test_delete_errors(self, user):
        user_url = confd.users(user['id'])
        user_url.delete()
        yield s.check_resource_not_found, user_url.get, 'User'


    @fixtures.user(firstname='ÉricDir')
    def test_that_the_directory_view_works_with_unicode_characters(self, user):
        response = confd.users.get(view='directory', search='éricdir')
        response.assert_ok()
        assert_that(response.items[0]['id'], equal_to(user['id']))

        response = confd.users.get(view='directory', search_ng='éricdir')
        response.assert_ok()
        assert_that(response.items[0]['id'], equal_to(user['id']))


    @fixtures.user()
    @fixtures.line()
    @fixtures.sip()
    @fixtures.extension()
    def test_summary_view_on_sip_endpoint(self, user, line, sip, extension):
        expected = has_entries(id=user['id'],
                            uuid=user['uuid'],
                            firstname=user['firstname'],
                            lastname=user['lastname'],
                            provisioning_code=line['provisioning_code'],
                            extension=extension['exten'],
                            context=extension['context'],
                            entity=config.ENTITY_NAME,
                            enabled=True,
                            protocol='sip',
                            configregistrar='default',
                            line_type='sip')

        with a.line_endpoint_sip(line, sip), a.line_extension(line, extension), \
                a.user_line(user, line):
            response = confd.users.get(view='summary', id=user['id'])
            assert_that(response.items, contains_exactly(expected))


    @fixtures.user()
    @fixtures.line()
    @fixtures.sip(options=[["webrtc", "yes"]])
    @fixtures.extension()
    def test_summary_view_on_sip_endpoint_with_ua(self, user, line, sip, extension):
        expected = has_entries(id=user['id'],
                            uuid=user['uuid'],
                            firstname=user['firstname'],
                            lastname=user['lastname'],
                            provisioning_code=line['provisioning_code'],
                            extension=extension['exten'],
                            context=extension['context'],
                            entity=config.ENTITY_NAME,
                            enabled=True,
                            protocol='sip',
                            configregistrar='default',
                            line_type='webrtc')

        with a.line_endpoint_sip(line, sip), a.line_extension(line, extension), \
                a.user_line(user, line):
            response = confd.users.get(view='summary', id=user['id'])
            assert_that(response.items, contains_exactly(expected))


    @fixtures.user()
    @fixtures.line()
    @fixtures.sip(options=[["webrtc", "ua"]])
    @fixtures.extension()
    def test_summary_view_on_sip_endpoint_with_ua(self, user, line, sip, extension):
        expected = has_entries(id=user['id'],
                            uuid=user['uuid'],
                            firstname=user['firstname'],
                            lastname=user['lastname'],
                            provisioning_code=line['provisioning_code'],
                            extension=extension['exten'],
                            context=extension['context'],
                            entity=config.ENTITY_NAME,
                            enabled=True,
                            protocol='sip',
                            configregistrar='default',
                            line_type='ua')

        with a.line_endpoint_sip(line, sip), a.line_extension(line, extension), \
                a.user_line(user, line):
            response = confd.users.get(view='summary', id=user['id'])
            assert_that(response.items, contains_exactly(expected))


    @fixtures.user()
    @fixtures.line()
    @fixtures.sip()
    @fixtures.extension()
    @fixtures.registrar()
    def test_summary_view_on_sip_endpoint_with_registrar(self, user, line, sip, extension, registrar):
        expected = has_entries(provisioning_code='243546',
                            configregistrar=registrar['id'],
                            line_type='sip')

        with a.line_endpoint_sip(line, sip), a.line_extension(line, extension), \
                a.user_line(user, line):
            confd.lines(line['id']).put(registrar=registrar['id'], provisioning_code='243546')

            response = confd.users.get(view='summary', id=user['id'])
            assert_that(response.items, contains_exactly(expected))


    @fixtures.user()
    @fixtures.line()
    @fixtures.sccp()
    @fixtures.extension()
    def test_summary_view_on_sccp_endpoint(self, user, line, sccp, extension):
        expected = has_entries(id=user['id'],
                            uuid=user['uuid'],
                            firstname=user['firstname'],
                            lastname=user['lastname'],
                            provisioning_code=none(),
                            extension=extension['exten'],
                            context=extension['context'],
                            entity=config.ENTITY_NAME,
                            enabled=True,
                            protocol='sccp',
                            line_type='sccp')

        with a.line_endpoint_sccp(line, sccp), a.line_extension(line, extension), \
                a.user_line(user, line):
            response = confd.users.get(view='summary', id=user['id'])
            assert_that(response.items, contains_exactly(expected))


    @fixtures.user()
    @fixtures.line()
    @fixtures.custom()
    @fixtures.extension()
    def test_summary_view_on_custom_endpoint(self, user, line, custom, extension):
        expected = has_entries(id=user['id'],
                            uuid=user['uuid'],
                            firstname=user['firstname'],
                            lastname=user['lastname'],
                            provisioning_code=none(),
                            extension=extension['exten'],
                            context=extension['context'],
                            entity=config.ENTITY_NAME,
                            enabled=True,
                            protocol='custom',
                            line_type='custom')

        with a.line_endpoint_custom(line, custom), a.line_extension(line, extension), \
                a.user_line(user, line):
            response = confd.users.get(view='summary', id=user['id'])
            assert_that(response.items, contains_exactly(expected))


    @fixtures.user()
    def test_summary_view_on_user_without_line(self, user):
        expected = has_entries(id=user['id'],
                            uuid=user['uuid'],
                            firstname=user['firstname'],
                            lastname=user['lastname'],
                            provisioning_code=none(),
                            extension=none(),
                            context=none(),
                            entity=config.ENTITY_NAME,
                            enabled=True,
                            protocol=none(),
                            line_type=none())

        response = confd.users.get(view='summary', id=user['id'])
        assert_that(response.items, contains_exactly(expected))


    @fixtures.user(firstname="Lègacy", lastname="Usér")
    @fixtures.user(firstname="Hîde", lastname="Mé")
    def test_search_using_legacy_parameter(self, user1, user2):
        expected_found = has_item(has_entries(firstname="Lègacy", lastname="Usér"))
        expected_hidden = has_item(has_entries(firstname="Hîde", lastname="Mé"))

        response = confd.users.get(q="lègacy usér")

        assert_that(response.items, expected_found)
        assert_that(response.items, is_not(expected_hidden))


    @fixtures.user(firstname="Léeroy",
                lastname="Jénkins",
                email="jenkins@leeroy.com",
                outgoing_caller_id='"Mystery Man" <5551234567>',
                username="leeroyjenkins",
                music_on_hold="leeroy_music_on_hold",
                mobile_phone_number="5552423232",
                userfield="leeroy jenkins userfield",
                description="Léeroy Jénkin's bio",
                enabled=False,
                preprocess_subroutine="leeroy_preprocess")
    def test_search_on_user_view(self, user):
        url = confd.users
        searches = {
            'firstname': 'léeroy',
            'lastname': 'jénkins',
            'email': 'jenkins@',
            'music_on_hold': 'leeroy_music',
            'outgoing_caller_id': '5551234567',
            'mobile_phone_number': '2423232',
            'userfield': 'jenkins userfield',
            'description': "jénkin's bio",
            'preprocess_subroutine': 'roy_preprocess',
            'enabled': False,
        }

        for field, term in searches.items():
            yield self.check_search, url, field, term, user[field]

        allowed = [
            'exten', 'firstname', 'lastname', 'userfield', 'email', 'description', 'mobile_phone_number',
            'voicemail_number'
        ]
        for field, term in [_ for _ in searches.items() if _ in allowed]:
            yield self.check_search_ng, url, field, term, user[field]


    @fixtures.user(firstname='Tame', lastname='Tiger')
    @fixtures.user(firstname='Wild', lastname='Tiger')
    @fixtures.user(firstname='Wíld Bláck', lastname='Tígér')
    @fixtures.user(firstname='Tame', lastname='Tager')
    def test_that_inaccurate_search_returns_appropriate_result(self, *users):
        tiger1 = {'firstname': 'Tame', 'lastname': 'Tiger'}
        tiger2 = {'firstname': 'Wild', 'lastname': 'Tiger'}
        tiger3 = {'firstname': 'Wíld Bláck', 'lastname': 'Tígér'}
        tiger4 = {'firstname': 'Tame', 'lastname': 'Tager'}
        url = confd.users

        response = url.get(search_ng='TiGeR')
        assert_that(response.items, has_items(has_entries(tiger1), has_entries(tiger2), has_entries(tiger3)))

        response = url.get(search_ng='wild tiger')
        assert_that(response.items, has_items(has_entries(tiger2), has_entries(tiger3)))

        response = url.get(search_ng='ta ti')
        assert_that(response.items, has_item(has_entries(tiger1)))

        response = url.get(search_ng='tuger')
        assert_that(
            response.items, has_items(has_entries(tiger1), has_entries(tiger2), has_entries(tiger4)))


    @fixtures.user(firstname="Môustapha",
                lastname="Bângoura",
                email="moustapha@bangoura.com",
                mobile_phone_number="+5559284759",
                userfield="Moustapha userfield",
                description="Moustapha the greatest dancer")
    def test_search_on_directory_view(self, user):
        url = confd.users(view='directory')

        searches = {
            'firstname': 'môustapha',
            'lastname': 'bângoura',
            'email': 'moustapha@',
            'mobile_phone_number': '928475',
            'userfield': 'moustapha userfield',
            'description': "greatest dancer",
        }

        for field, term in searches.items():
            yield self.check_search, url, field, term, user[field]


    @fixtures.user(firstname='John', lastname='Agent')
    @fixtures.agent(number='8001', numgroup=1)
    @fixtures.user(firstname='Jean-Michel', lastname='Not Agent')
    @fixtures.user(firstname='Secret', lastname='Agent')
    @fixtures.agent(number='8002', numgroup=2)
    def test_list_users_with_agent(self, user1, agent1, user2, user3, agent3):
        with db.queries() as queries:
            queries.associate_user_agent(user_id=user1['id'], agent_id=agent1['id'])
            queries.associate_user_agent(user_id=user3['id'], agent_id=agent3['id'])

        expected_for_user1 = has_item(has_entries(id=user1['id'],
                                                uuid=user1['uuid'],
                                                firstname=user1['firstname'],
                                                lastname=user1['lastname'],
                                                agent_number=agent1['number'],
                                                agent_group_id=agent1['numgroup']))

        expected_for_user2 = has_item(has_entries(id=user2['id'],
                                                uuid=user2['uuid'],
                                                firstname=user2['firstname'],
                                                lastname=user2['lastname'],
                                                agent_number=None,
                                                agent_group_id=None))

        expected_for_user3 = has_item(has_entries(id=user3['id'],
                                                uuid=user3['uuid'],
                                                firstname=user3['firstname'],
                                                lastname=user3['lastname'],
                                                agent_number=agent3['number'],
                                                agent_group_id=agent3['numgroup']))

        response = confd.users.get()

        assert_that(response.items, expected_for_user1)
        assert_that(response.items, expected_for_user2)
        assert_that(response.items, expected_for_user3)


    @fixtures.user(firstname='John', lastname='Agent')
    @fixtures.agent(number='8001', numgroup=1)
    @fixtures.label()
    def test_list_users_with_label(self, user1, agent1, label1):
        with db.queries() as queries:
            queries.associate_user_agent(user_id=user1['id'], agent_id=agent1['id'])
            queries.associate_user_label(user_id=user1['id'], label_id=label1['id'])

        expected_for_user1 = has_item(has_entries(id=user1['id'],
                                                uuid=user1['uuid'],
                                                firstname=user1['firstname'],
                                                lastname=user1['lastname'],
                                                agent_number=agent1['number'],
                                                agent_group_id=agent1['numgroup'],
                                                labels=[label1['id']]))

        response = confd.users.get()

        assert_that(response.items, expected_for_user1)


    @fixtures.user()
    @fixtures.line_sip()
    @fixtures.extension()
    def test_search_on_users_extension(self, user, line, extension):
        with a.user_line(user, line), a.line_extension(line, extension):
            response = confd.users.get(search=extension['exten'], view='directory')
            assert_that(response.items, has_item(has_entry('exten', extension['exten'])))

            response = confd.users.get(search_ng=extension['exten'], view='directory')
            assert_that(response.items, has_item(has_entry('exten', extension['exten'])))


    @fixtures.user(firstname='Alicé')
    @fixtures.line_sip()
    @fixtures.extension()
    def test_search_on_users_with_context_filter(self, user, line, extension):
        with a.user_line(user, line), a.line_extension(line, extension):
            response = confd.users.get(search='alic', view='directory', context='default')
            assert_that(response.total, equal_to(1))
            assert_that(response.items, has_item(has_entry('exten', extension['exten'])))

            response = confd.users.get(search='alic', view='directory', context='other')
            assert_that(response.total, equal_to(0))

            response = confd.users.get(search_ng='alic', view='directory', context='default')
            assert_that(response.total, equal_to(1))
            assert_that(response.items, has_item(has_entry('exten', extension['exten'])))

            response = confd.users.get(search_ng='alic', view='directory', context='other')
            assert_that(response.total, equal_to(0))


    @fixtures.user(firstname="Âboubacar",
                lastname="Manè",
                description="Âboubacar le grand danseur")
    @fixtures.line()
    @fixtures.sip()
    @fixtures.extension()
    def test_search_on_summary_view(self, user, line, sip, extension):
        url = confd.users(view='summary')

        with a.line_endpoint_sip(line, sip), a.user_line(user, line), a.line_extension(line, extension):
            yield self.check_search, url, 'firstname', 'âbou', user['firstname']
            yield self.check_search, url, 'lastname', 'man', user['lastname']
            yield self.check_search, url, 'provisioning_code', line['provisioning_code'], line['provisioning_code']
            yield self.check_search, url, 'extension', extension['exten'], extension['exten']
            yield self.check_search_ng, url, 'firstname', 'âbou', user['firstname']
            yield self.check_search_ng, url, 'lastname', 'man', user['lastname']
            yield self.check_search_ng, url, 'extension', extension['exten'], extension['exten']


    def check_search(self, url, field, term, value):
        expected = has_item(has_entry(field, value))
        response = url.get(search=term)
        assert_that(response.items, expected)


    def check_search_ng(self, url, field, term, value):
        expected = has_item(has_entry(field, value))
        response = url.get(search_ng=term)
        assert_that(response.items, expected)


    @fixtures.user(**FULL_USER)
    def test_get_user(self, user):
        response = confd.users(user['id']).get()
        assert_that(response.item, has_entries(FULL_USER))


    @fixtures.user(firstname="Snôm", lastname="Whîte")
    @fixtures.user()
    @fixtures.user()
    def test_that_get_works_with_a_uuid(self, user_1, user_2_, user_3):
        result = confd.users(user_1['uuid']).get()

        assert_that(result.item, has_entries(firstname='Snôm', lastname='Whîte'))


    @fixtures.user(firstname="Snôw", lastname="Whîte", username='snow.white+dwarves@disney.example.com')
    def test_that_the_username_can_be_an_email(self, user):
        result = confd.users(user['id']).get()

        assert_that(result.item, has_entries(firstname='Snôw',
                                            lastname='Whîte',
                                            username='snow.white+dwarves@disney.example.com'))


    def test_create_minimal_parameters(self):
        response = confd.users.post(firstname="Roger")

        response.assert_created('users')
        assert_that(response.item, has_entry("firstname", "Roger"))


    def test_create_with_null_parameters_fills_default_values(self):
        response = confd.users.post(firstname="Charlie")
        response.assert_created('users')

        assert_that(response.item, has_entries(caller_id='"Charlie"',
                                            call_transfer_enabled=False,
                                            call_record_enabled=False,
                                            online_call_record_enabled=False,
                                            supervision_enabled=True,
                                            ring_seconds=20,
                                            simultaneous_calls=2))


    def test_create_user_with_all_parameters(self):
        response = confd.users.post(**FULL_USER)

        response.assert_created('users')
        assert_that(response.item, has_entries(FULL_USER))

        # We need to clean this user as it is used in test_get_users
        user_id = response.item['id']
        confd.users(user_id).delete()


    def test_create_user_with_all_parameters_null(self):
        response = confd.users.post(**NULL_USER)
        assert_that(response.item, has_entries(NULL_USER))


    def test_bus_reload_event_when_create_user(self):
        BusClient.listen_delayed_events('config.ipbx.reload')
        expected_reload_commands = ['dialplan reload',
                                    'module reload chan_sccp.so',
                                    'module reload app_queue.so',
                                    'sip reload']

        confd.users.post(firstname='John', lastname='Smith')

        reload_events = BusClient.drain_events()

        assert_that(reload_events, has_length(1))
        assert_that(reload_events[0]['data']['commands'], equal_to(expected_reload_commands))


    def assert_sysconfd_add_user_with_agent(self, sysconfd, user_id):
        with db.queries() as queries:
            agent_id = queries.get_agent_id_by_user_id(user_id)

        agent_handler = {'ctibus': ['xivo[queuemember,update]',
                                    'xivo[user,add,{}]'.format(user_id),
                                    'xivo[user,edit,{}]'.format(user_id),
                                    'xivo[agent,add,{}]'.format(agent_id)],
                        'agentbus': ['agent.add.{}'.format(agent_id)]}

        sysconfd.assert_request('/exec_request_handlers',
                                method='POST',
                                body_entries=agent_handler)


    def assert_sysconfd_update_user_with_agent(self, sysconfd, user_id):
        with db.queries() as queries:
            agent_id = queries.get_agent_id_by_user_id(user_id)

        agent_handler = {'ctibus': ['xivo[agent,add,{}]'.format(agent_id),
                                    'xivo[user,edit,{}]'.format(user_id),
                                    'xivo[queuemember,update]'],
                        'agentbus': ['agent.add.{}'.format(agent_id)]}

        sysconfd.assert_request('/exec_request_handlers',
                                method='POST',
                                body_entries=agent_handler)


    @mocks.sysconfd()
    def test_create_user_with_agent(self, sysconfd):
        response = confd.users.post(**AGENT_USER)
        response.assert_created('users')
        assert_that(response.item, has_entries(AGENT_USER))
        user_id = response.item['id']

        self.assert_sysconfd_add_user_with_agent(sysconfd, user_id)


    def test_create_user_generates_appropriate_caller_id(self):
        expected_caller_id = '"Jôhn"'
        response = confd.users.post(firstname='Jôhn')
        assert_that(response.item, has_entry('caller_id', expected_caller_id))

        expected_caller_id = '"Jôhn Doe"'
        response = confd.users.post(firstname='Jôhn', lastname='Doe')
        assert_that(response.item['caller_id'], equal_to(expected_caller_id))


    def test_create_user_fix_cti_profile(self):
        response = confd.users.post(firstname='CTI', lastname='User', username='ucti', password='pwdcti')
        with db.queries() as queries:
            user = queries.get_user_by_id(response.item['id'])
            assert_that(user.cti_profile_id, not_none())
            assert_that(user.cti_profile_id, greater_than(0))
            assert_that(user.enableclient, equal_to(1))


    @fixtures.user(firstname="Léeroy",
                lastname="Jénkins",
                email="leeroy@jenkins.com",
                outgoing_caller_id='"Mystery Man" <5551234567>',
                username="leeroyjenkins",
                music_on_hold="leeroy_music_on_hold",
                mobile_phone_number="5552423232",
                userfield="leeroy jenkins userfield",
                description="Léeroy Jénkin's bio",
                preprocess_subroutine="leeroy_preprocess")
    def test_update_user_with_all_parameters(self, user):
        user_url = confd.users(user['id'])

        response = user_url.put(**FULL_USER)
        response.assert_updated()

        response = user_url.get()
        assert_that(response.item, has_entries(FULL_USER))


    @fixtures.user(firstname="Léeroy",
                lastname="Jénkins")
    def test_bus_reload_event_when_update_user(self, user):
        BusClient.listen_delayed_events('config.ipbx.reload')
        expected_reload_commands = ['dialplan reload',
                                    'module reload chan_sccp.so',
                                    'module reload app_queue.so',
                                    'sip reload']

        user_url = confd.users(user['id'])
        user_url.put(firstname="Mystery", lastname="Man")

        reload_events = BusClient.drain_events()

        assert_that(reload_events, has_length(1))
        assert_that(reload_events[0]['data']['commands'], equal_to(expected_reload_commands))


    @mocks.sysconfd()
    @fixtures.user(firstname="Jack",
                lastname="Bauer")
    def test_update_user_add_agent_info(self, sysconfd, user):
        user_url = confd.users(user['id'])
        sysconfd.clear_requests()
        response = user_url.put(agent_number='2001')
        response.assert_updated()

        response = user_url.get()
        assert_that(response.item, has_entry('agent_number', '2001'))
        self.assert_sysconfd_update_user_with_agent(sysconfd, user['id'])


    @fixtures.user(firstname="Jack",
                lastname="Bauer")
    def test_update_user_add_agent_info_and_group(self, user):
        user_url = confd.users(user['id'])

        response = user_url.put(agent_number='2002', agent_group_id=2)
        response.assert_updated()

        response = user_url.get()
        assert_that(response.item, has_entries(agent_number='2002', agent_group_id=2))


    @fixtures.user(firstname="Saul",
                lastname="Berenson",
                agent_number="2003",
                agent_group_id=1)
    def test_update_user_change_agent_group(self, user):
        user_url = confd.users(user['id'])

        response = user_url.put(agent_number="2003", agent_group_id=2)
        response.assert_updated()

        response = user_url.get()
        assert_that(response.item, has_entries(agent_number='2003', agent_group_id=2))


    @fixtures.user(firstname="Agent",
                lastname="Secret",
                agent_number="007",
                agent_group_id=1)
    def test_update_user_change_agent_group_only(self, user):
        user_url = confd.users(user['id'])

        response = user_url.put(agent_group_id=2)
        response.assert_updated()

        response = user_url.get()
        assert_that(response.item, has_entries(agent_group_id=2))


    @fixtures.user(firstname="Carrie",
                lastname="Mathison",
                agent_number="2004")
    def test_update_user_remove_agent_info(self, user):
        user_url = confd.users(user['id'])

        response = user_url.put(agent_number=None)
        response.assert_updated()

        response = user_url.get()
        assert_that(response.item, has_entry('agent_number', None))


    @fixtures.user()
    def test_update_user_with_all_parameters_null(self, user):
        response = confd.users(user['id']).put(**NULL_USER)
        response.assert_updated()

        response = confd.users(user['id']).get()
        assert_that(response.item, has_entries(**NULL_USER))


    @fixtures.user(firstname='Not', lastname='Anagent')
    def test_get_user_without_agent(self, user):
        result = confd.users(user['id']).get()

        assert_that(result.item, has_entries(firstname='Not',
                                            lastname='Anagent',
                                            agent_number=None))


    @fixtures.user(firstname='John', lastname='Agent')
    @fixtures.agent(number='8008')
    def test_get_user_with_its_agentnumber(self, user, agent):
        with db.queries() as queries:
            queries.associate_user_agent(user_id=user['id'], agent_id=agent['id'])

        result = confd.users(user['id']).get()

        assert_that(result.item, has_entries(firstname='John',
                                            lastname='Agent',
                                            agent_number=agent['number']))


    @fixtures.user()
    def test_that_users_can_be_edited_by_uuid(self, user):
        response = confd.users(user['uuid']).put({'firstname': 'Fôo',
                                                'lastname': 'Bâr'})
        response.assert_updated()

        response = confd.users(user['uuid']).get()
        assert_that(response.item, has_entries(firstname='Fôo', lastname='Bâr'))


    @fixtures.user()
    @fixtures.label()
    @fixtures.label()
    def test_edit_user_label_association(self, user, label1, label2):
        with db.queries() as queries:
            queries.associate_user_label(user_id=user['id'], label_id=label1['id'])

        response = confd.users(user['uuid']).put({'labels': [label2['id']]})
        response.assert_updated()

        response = confd.users(user['uuid']).get()
        assert_that(response.item, has_entries(labels=[label2['id']]))


    @fixtures.user()
    def test_delete(self, user):
        response = confd.users(user['id']).delete()
        response.assert_deleted()


    @fixtures.user()
    @fixtures.label()
    def test_delete_with_label(self, user, label):
        with db.queries() as queries:
            queries.associate_user_label(user_id=user['id'], label_id=label['id'])

        response = confd.users(user['id']).delete()
        response.assert_deleted()


    @fixtures.user()
    def test_bus_reload_event_when_delete_user(self, user):
        BusClient.listen_delayed_events('config.ipbx.reload')
        expected_reload_commands = ['dialplan reload',
                                    'module reload chan_sccp.so',
                                    'module reload app_queue.so',
                                    'sip reload']

        confd.users(user['id']).delete()

        reload_events = BusClient.drain_events()

        assert_that(reload_events, has_length(1))
        assert_that(reload_events[0]['data']['commands'], equal_to(expected_reload_commands))


    @fixtures.user()
    def test_that_users_can_be_deleted_by_uuid(self, user):
        response = confd.users(user['uuid']).delete()
        response.assert_deleted()

        response = confd.users(user['uuid']).get()
        response.assert_status(404)


    @fixtures.user(email='common@email.com')
    def test_post_email_already_exists_then_error_raised(self, user):
        response = confd.users.post(firstname='bob', email='common@email.com')
        response.assert_match(400, e.resource_exists('User'))


    @fixtures.user(email='common@email.com')
    @fixtures.user(email='other@email.com')
    def test_put_email_already_exists_then_error_raised(self, _, user):
        response = confd.users(user['uuid']).put(email='common@email.com')
        response.assert_match(400, e.resource_exists('User'))
