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

# Copyright (C) 2012-2013,2015 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, see <http://www.gnu.org/licenses/>


import functools
import json
import os
import os.path
import shutil
import subprocess
import tempfile
import unittest
from io import StringIO

from unittest.mock import Mock, patch, ANY

import mock
from xivo_sysconf.modules.ha import HAConfigManager, _PostgresConfigUpdater, \
    _CronFileInstaller, HaNodeType, HAConfig, KAMAILIO_PJSIP_TEMPLATE


def new_master_ha_config(slave_ip_address, master_ip_address=''):
    return HAConfig(node_type=HaNodeType.MASTER, remote_address=slave_ip_address, local_address=master_ip_address, automatic_failover=False, active=True)


def new_slave_ha_config(master_ip_address, slave_ip_address='', active=False):
    return HAConfig(node_type=HaNodeType.SLAVE, remote_address=master_ip_address, local_address=slave_ip_address, automatic_failover=False, active=active)


def new_disabled_ha_config() -> HAConfig:
    return HAConfig(node_type=HaNodeType.DISABLED)

def mock_subprocess_check_call(f):
    @functools.wraps(f)
    def decorator(*kargs):
        old_subprocess_check_call = subprocess.check_call
        subprocess.check_call = Mock()

        try:
            f(*kargs)
        finally:
            subprocess.check_call = old_subprocess_check_call

    return decorator


class TestHA(unittest.TestCase):
    def setUp(self):
        self._tmp_dir = tempfile.mkdtemp()
        self._ha_conf_file = os.path.join(self._tmp_dir, 'test_ha.conf')
        self._kamailio_pjsip_file = os.path.join(self._tmp_dir, '10-xivo-edge-kamailio.conf')
        self._ha_config_mgr = HAConfigManager(Mock(),
                                              Mock(),
                                              ha_conf_file=self._ha_conf_file,
                                              kamailio_pjsip_file=self._kamailio_pjsip_file)

    def tearDown(self):
        shutil.rmtree(self._tmp_dir)

    def _create_tmp_conf_file(self, content):
        with open(self._ha_conf_file, 'wb') as fobj:
            fobj.write(content.encode('utf-8'))

    def test_read_conf(self):
        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": false
}
"""
        expected_ha_config = new_master_ha_config('10.0.0.1',"10.0.10.1")
        fobj = StringIO(content)

        ha_config = self._ha_config_mgr._read_ha_config_from_fobj(fobj)
        self.assertEqual(expected_ha_config, ha_config)

    def test_read_partial_conf(self):
        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1"
}
"""
        expected_ha_config = HAConfig(node_type=HaNodeType.MASTER, remote_address='10.0.0.1', local_address='', automatic_failover=True, active=True)
        fobj = StringIO(content)

        ha_config = self._ha_config_mgr._read_ha_config_from_fobj(fobj)
        self.assertEqual(expected_ha_config, ha_config)

    def test_read_conf_without_node_type(self):
        content = """\
{
    "remote_address" : "10.0.0.1"
}
"""
        expected_ha_config = HAConfig(node_type=HaNodeType.DISABLED, remote_address='10.0.0.1', automatic_failover=True, active=False)
        fobj = StringIO(content)

        ha_config = self._ha_config_mgr._read_ha_config_from_fobj(fobj)
        self.assertEqual(expected_ha_config, ha_config)

    def test_write_conf(self):
        ha_config = new_master_ha_config('10.0.0.1','10.0.10.1')
        expected_content = '{"node_type": "master", "remote_address": "10.0.0.1", "local_address": "10.0.10.1", "automatic_failover": false, "active": true}'
        fobj = StringIO()

        self._ha_config_mgr._write_ha_config_to_fobj(ha_config, fobj)

        content = fobj.getvalue()
        self.assertEqual(json.loads(expected_content), json.loads(content))

    def test_get_ha_config_no_file(self):
        expected_ha_config = HAConfig(node_type=HaNodeType.DISABLED, automatic_failover=False, active=False)

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self.assertEqual(expected_ha_config, ha_config)

    def test_get_ha_config(self):
        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": false
}
"""
        expected_ha_config = new_master_ha_config('10.0.0.1',"10.0.10.1").dict()
        self._create_tmp_conf_file(content)

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self.assertEqual(expected_ha_config, ha_config)

    def test_get_ha_config_without_automatic_failover(self):
        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1"
}
"""
        expected_ha_config = HAConfig(node_type=HaNodeType.MASTER, remote_address='10.0.0.1', local_address='10.0.10.1', automatic_failover=True, active=True)
        self._create_tmp_conf_file(content)

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self.assertEqual(expected_ha_config, ha_config)

    def test_get_ha_config_without_active(self):
        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": true
}
"""
        expected_ha_config = HAConfig(node_type=HaNodeType.MASTER, remote_address='10.0.0.1', local_address='10.0.10.1', automatic_failover=True, active=True)
        self._create_tmp_conf_file(content)

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self.assertEqual(expected_ha_config, ha_config)

    def test_update_ha_config(self):
        ha_config = dict(node_type='master', remote_address='10.181.25.2', local_address='10.181.181.2', automatic_failover=True, active=True)
        self._ha_config_mgr._manage_services = Mock()

        self._ha_config_mgr.update_ha_config(ha_config, None)

        expected_ha_config = self._ha_config_mgr._read_ha_config()
        self.assertEqual(expected_ha_config, HAConfig(node_type=HaNodeType.MASTER, remote_address='10.181.25.2', local_address='10.181.181.2', automatic_failover=True, active=True))

    def test_update_ha_config_without_automatic_failover(self):
        ha_config = dict(node_type='master', remote_address='10.181.25.2', local_address='10.181.181.2', active=True)
        self._ha_config_mgr._manage_services = Mock()

        self._ha_config_mgr.update_ha_config(ha_config, None)

        expected_ha_config = self._ha_config_mgr._read_ha_config()
        self.assertEqual(expected_ha_config, HAConfig(node_type=HaNodeType.MASTER, remote_address='10.181.25.2', local_address='10.181.181.2', automatic_failover=False, active=True))

    def test_update_ha_config_raise_error_on_extra_arg(self):
        ha_config = dict(node_type='master', unused_argument='foo')
        self._ha_config_mgr._manage_services = Mock()
        self.assertRaises(ValueError, self._ha_config_mgr.update_ha_config, ha_config, None)

    @patch('xivo_sysconf.modules.ha.HAConfigManager.search_voip_address', return_value='10.181.181.2')
    def test_update_ha_config_fetches_local_address(self, mock_search_voip_address):
        ha_config = dict(node_type='master', remote_address='10.181.25.2')
        self._ha_config_mgr._manage_services = Mock()

        self._ha_config_mgr.update_ha_config(ha_config, None)

        expected_ha_config = self._ha_config_mgr._read_ha_config()
        mock_search_voip_address.assert_called_once()
        self.assertEqual(expected_ha_config, HAConfig(node_type=HaNodeType.MASTER, remote_address='10.181.25.2', local_address='10.181.181.2'))

    @patch('xivo_sysconf.modules.ha.HAConfigManager.search_voip_address', side_effect=Exception("Something went wrong"))
    def test_update_ha_config_fetches_local_address_error_handling(self, mock_search_voip_address):
        ha_config = dict(node_type='master', remote_address='10.181.25.2')
        self._ha_config_mgr._manage_services = Mock()

        self._ha_config_mgr.update_ha_config(ha_config, None)

        expected_ha_config = self._ha_config_mgr._read_ha_config()
        mock_search_voip_address.assert_called_once()
        self.assertEqual(expected_ha_config, HAConfig(node_type=HaNodeType.MASTER, remote_address='10.181.25.2', local_address=''))

    def test_update_ha_config_to_disable_it(self):
        ha_config_1 = dict(node_type='master', remote_address='10.181.25.2', local_address='10.181.181.2')
        ha_config_2 = dict(node_type='disabled')
        self._ha_config_mgr._manage_services = Mock()

        self._ha_config_mgr.update_ha_config(ha_config_1, None)
        self._ha_config_mgr.update_ha_config(ha_config_2, None)
        expected_ha_config = self._ha_config_mgr._read_ha_config()
        self.assertEqual(expected_ha_config, new_disabled_ha_config().dict())

    @mock_subprocess_check_call
    def test_manage_services_disabled(self):
        ha_config = new_disabled_ha_config()

        self._ha_config_mgr._manage_services(ha_config)

        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'start'], close_fds=True)

    @mock_subprocess_check_call
    def test_manage_services_master(self):
        ha_config = new_master_ha_config('10.0.0.1')

        self._ha_config_mgr._manage_services(ha_config)

        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'start'], close_fds=True)

    @mock_subprocess_check_call
    def test_manage_services_slave(self):
        ha_config = new_slave_ha_config('10.0.0.1')

        self._ha_config_mgr._manage_services(ha_config)

        self.assertFalse(subprocess.check_call.called)

    def test_update_cronfiles_disabled_node(self):
        ha_config = new_disabled_ha_config()
        self._ha_config_mgr._cronfile_installer = Mock()

        self._ha_config_mgr._update_cronfiles(ha_config)

        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_MASTER)
        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_SLAVE)
        self._ha_config_mgr._cronfile_installer.add_cronfile.assert_not_called()

    def test_update_cronfiles_master_node(self):
        ha_config = new_master_ha_config('10.0.0.1')
        self._ha_config_mgr._cronfile_installer = Mock()

        self._ha_config_mgr._update_cronfiles(ha_config)

        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_MASTER)
        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_SLAVE)
        self._ha_config_mgr._cronfile_installer.add_cronfile.assert_called_once_with(HAConfigManager.CRONFILE_MASTER, ANY)

    def test_update_cronfiles_slave_node_with_automatic_failover(self):
        ha_config = new_slave_ha_config('10.0.0.1')
        ha_config.automatic_failover = True
        self._ha_config_mgr._cronfile_installer = Mock()

        self._ha_config_mgr._update_cronfiles(ha_config)

        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_MASTER)
        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_SLAVE)
        self._ha_config_mgr._cronfile_installer.add_cronfile.assert_called_once_with(HAConfigManager.CRONFILE_SLAVE, ANY)

    def test_update_cronfiles_slave_node_without_automatic_failover(self):
        ha_config = new_slave_ha_config('10.0.0.1')
        ha_config.automatic_failover = False
        self._ha_config_mgr._cronfile_installer = Mock()

        self._ha_config_mgr._update_cronfiles(ha_config)

        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_MASTER)
        self._ha_config_mgr._cronfile_installer.remove_cronfile.assert_any_call(HAConfigManager.CRONFILE_SLAVE)
        self._ha_config_mgr._cronfile_installer.add_cronfile.assert_not_called()

    def test_update_ha_config_calls_update_cronfiles(self):
        ha_config = dict(node_type='master', remote_address='10.181.25.2', local_address='10.181.181.2')
        self._ha_config_mgr._manage_services = Mock()
        self._ha_config_mgr._update_cronfiles = Mock()

        self._ha_config_mgr.update_ha_config(ha_config, None)

        self._ha_config_mgr._update_cronfiles.assert_called_once()

    @mock_subprocess_check_call
    def test_update_ha_state_activate_master(self):
        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": false,
    "active": true
}
"""
        self._create_tmp_conf_file(content)
        self._ha_config_mgr._update_postgres = Mock()

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self._ha_config_mgr.update_ha_state({'active': True}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'start'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_not_called()

        subprocess.check_call.reset_mock()

        self._ha_config_mgr.update_ha_state({'active': False}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'stop'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_not_called()

        content = """\
{
    "node_type" : "master",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": false,
    "active": false
}
"""
        self._create_tmp_conf_file(content)
        self._ha_config_mgr._update_postgres.reset_mock()
        subprocess.check_call.reset_mock()

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self._ha_config_mgr.update_ha_state({'active': True}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'start'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_not_called()

        subprocess.check_call.reset_mock()

        self._ha_config_mgr.update_ha_state({'active': False}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'stop'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_not_called()

    @mock_subprocess_check_call
    def test_update_ha_state_activate_slave(self):
        content = """\
{
    "node_type" : "slave",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": false,
    "active": false
}
"""
        self._create_tmp_conf_file(content)
        self._ha_config_mgr._update_postgres = Mock()

        ha_config = self._ha_config_mgr.get_ha_config(None, None)

        self._ha_config_mgr.update_ha_state({'active': True}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'start'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_called_once()

        subprocess.check_call.reset_mock()
        self._ha_config_mgr._update_postgres.reset_mock()

        self._ha_config_mgr.update_ha_state({'active': False}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'stop'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_called_once()

        content = """\
{
    "node_type" : "slave",
    "remote_address" : "10.0.0.1",
    "local_address" : "10.0.10.1",
    "automatic_failover": false,
    "active": true
}
"""
        self._create_tmp_conf_file(content)
        self._ha_config_mgr._update_postgres.reset_mock()

        ha_config = self._ha_config_mgr.get_ha_config(None, None)
        self._ha_config_mgr.update_ha_state({'active': True}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'start'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_called_once()

        subprocess.check_call.reset_mock()
        self._ha_config_mgr._update_postgres.reset_mock()

        self._ha_config_mgr.update_ha_state({'active': False}, None)
        subprocess.check_call.assert_called_with(['/usr/sbin/xivo-manage-slave-services', 'stop'], close_fds=True)
        self._ha_config_mgr._update_postgres.assert_called_once()

    def test_update_kamailio_pjsip_config_disabled_ha(self):
        ha_config = new_disabled_ha_config()

        self._ha_config_mgr._update_kamailio_pjsip_config(ha_config)

        self.assertTrue(os.path.exists(self._kamailio_pjsip_file))
        with open(self._kamailio_pjsip_file) as f:
            content = f.read()
        self.assertEqual(KAMAILIO_PJSIP_TEMPLATE, content)

    def test_update_kamailio_pjsip_config_file_already_exists(self):
        ha_config = new_master_ha_config('10.0.0.1')
        existing_content = 'existing config\n'
        with open(self._kamailio_pjsip_file, 'w') as f:
            f.write(existing_content)

        self._ha_config_mgr._update_kamailio_pjsip_config(ha_config)

        with open(self._kamailio_pjsip_file) as f:
            content = f.read()
        self.assertEqual(existing_content, content)

    def test_update_kamailio_pjsip_config_master_creates_file(self):
        ha_config = new_master_ha_config('10.0.0.1')

        self._ha_config_mgr._update_kamailio_pjsip_config(ha_config)

        self.assertTrue(os.path.exists(self._kamailio_pjsip_file))
        with open(self._kamailio_pjsip_file) as f:
            content = f.read()
        self.assertEqual(KAMAILIO_PJSIP_TEMPLATE, content)

    def test_update_kamailio_pjsip_config_slave_creates_file(self):
        ha_config = new_slave_ha_config('10.0.0.1')

        self._ha_config_mgr._update_kamailio_pjsip_config(ha_config)

        self.assertTrue(os.path.exists(self._kamailio_pjsip_file))
        with open(self._kamailio_pjsip_file) as f:
            content = f.read()
        self.assertEqual(KAMAILIO_PJSIP_TEMPLATE, content)

    def test_update_ha_config_calls_update_kamailio_pjsip_config(self):
        ha_config = dict(node_type='master', remote_address='10.181.25.2', local_address='10.181.181.2')
        self._ha_config_mgr._manage_services = Mock()
        self._ha_config_mgr._update_kamailio_pjsip_config = Mock()

        self._ha_config_mgr.update_ha_config(ha_config, None)

        self._ha_config_mgr._update_kamailio_pjsip_config.assert_called_once()


class TestPostgresConfigUpdater(unittest.TestCase):
    def setUp(self):
        self._tmp_dir = tempfile.mkdtemp()
        self._pg_hba_filename = os.path.join(self._tmp_dir, _PostgresConfigUpdater.PG_HBA_FILE)

    def tearDown(self):
        shutil.rmtree(self._tmp_dir)

    def test_update_pg_hba_file_from_non_slave_to_slave(self):
        pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
"""
        expected_pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
host asterisk postgres 10.0.0.1/32 trust
"""
        self._write_pg_hba_file(pg_hba_content)
        ha_config = new_slave_ha_config('10.0.0.1')
        postgres_updater = self._new_postgres_updater(ha_config)

        postgres_updater.update_pg_hba_file()

        self.assertEqual(expected_pg_hba_content, self._read_pg_hba_file())

    def _new_postgres_updater(self, ha_config):
        return _PostgresConfigUpdater(ha_config, self._tmp_dir)

    def _write_pg_hba_file(self, content):
        self._write_file(self._pg_hba_filename, content)

    def _write_file(self, filename, content):
        with open(filename, 'w') as fobj:
            fobj.write(content)

    def _read_pg_hba_file(self):
        return self._read_file(self._pg_hba_filename)

    def _read_file(self, filename):
        with open(filename) as fobj:
            return fobj.read()

    def test_update_pg_hba_file_from_slave_to_slave(self):
        pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
host asterisk postgres 10.0.0.1/32 trust
"""
        inactive_expected_pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
host asterisk postgres 10.0.0.2/32 trust
"""

        active_expected_pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
"""

        self._write_pg_hba_file(pg_hba_content)
        ha_config = new_slave_ha_config('10.0.0.2')
        postgres_updater = self._new_postgres_updater(ha_config)

        postgres_updater.update_pg_hba_file()

        self.assertEqual(inactive_expected_pg_hba_content, self._read_pg_hba_file())

        self._write_pg_hba_file(pg_hba_content)
        ha_config = new_slave_ha_config('10.0.0.2', '', False)
        postgres_updater = self._new_postgres_updater(ha_config)

        postgres_updater.update_pg_hba_file()

        self.assertEqual(inactive_expected_pg_hba_content, self._read_pg_hba_file())

        self._write_pg_hba_file(pg_hba_content)
        ha_config = new_slave_ha_config('10.0.0.2', '', True)
        postgres_updater = self._new_postgres_updater(ha_config)

        postgres_updater.update_pg_hba_file()

        self.assertEqual(active_expected_pg_hba_content, self._read_pg_hba_file())

    def test_update_pg_hba_file_from_slave_to_non_slave(self):
        pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
host asterisk postgres 10.0.0.1/32 trust
"""
        expected_pg_hba_content = """\
# PostgreSQL Client Authentication Configuration File
local   all         postgres                          ident
host    all             all             127.0.0.1/32            md5
"""
        self._write_pg_hba_file(pg_hba_content)
        ha_config = new_disabled_ha_config()
        postgres_updater = self._new_postgres_updater(ha_config)

        postgres_updater.update_pg_hba_file()

        self.assertEqual(expected_pg_hba_content, self._read_pg_hba_file())

    @mock_subprocess_check_call
    def test_reload_postgres_conf_called_with_sighup_signal(self):
        ha_config = new_slave_ha_config('10.0.0.1')
        postgres_updater = self._new_postgres_updater(ha_config)

        postgres_updater.reload_postgres()

        subprocess.check_call.assert_called_with(['/usr/bin/xivo-dcomp', 'kill', '-s', 'SIGHUP', 'db'], close_fds=True)


class TestCronFileInstaller(unittest.TestCase):
    def setUp(self):
        self._tmp_dir = tempfile.mkdtemp()

    def tearDown(self):
        shutil.rmtree(self._tmp_dir)

    def test_add_cronfile(self):
        filename = 'test-cronfile'
        content = 'foo bar\n'
        cronfile_installer = self._new_cronfile_installer()

        cronfile_installer.add_cronfile(filename, content)

        self.assertEqual(content, self._read_cronfile(filename))

    def _new_cronfile_installer(self):
        return _CronFileInstaller(self._tmp_dir)

    def _read_cronfile(self, filename):
        abs_filename = os.path.join(self._tmp_dir, filename)
        with open(abs_filename) as fobj:
            return fobj.read()

    def test_remove_cronfile_when_cronfile_present(self):
        filename = 'test-cronfile'
        cronfile_installer = self._new_cronfile_installer()
        cronfile_installer.add_cronfile(filename, '')

        cronfile_installer.remove_cronfile(filename)

        self.assertEqual([], self._list_crondir())

    def _list_crondir(self):
        return os.listdir(self._tmp_dir)

    def test_remove_cronfile_when_cronfile_absent(self):
        filename = 'test-cronfile'
        cronfile_installer = self._new_cronfile_installer()

        cronfile_installer.remove_cronfile(filename)

        self.assertEqual([], self._list_crondir())
