class TestToggleRundeckLock(asynctest.TestCase):

    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])

        self.rd_lock.rundeck_jobs = [RundeckJob(friendly_name="job1")]
        self.rd_lock.populate_slack_user_object = CoroutineMock()
        self.rd_lock.slack.send_channel_message = CoroutineMock()
        self.rd_lock.lock_or_unlock_rundeck_job = CoroutineMock()
        self.rd_lock.set_channel_topic = CoroutineMock()
        self.rd_lock.print_lock_status = CoroutineMock()
        self.sm = SlackMessage(user="******", channel="C001")
        self.su = SlackUser(name="suser", is_admin=True)

    def test_unauthorized_user(self):
        self.su.is_admin = False
        self.rd_lock.populate_slack_user_object.side_effect = [self.su]
        yield from self.rd_lock.toggle_rundeck_lock(self.sm, True)
        calls = [
            call("C001", "Sorry <@suser>, you are not allowed to lock Rundeck executions.")  # NOQA
        ]
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         calls)

    def test_authorized_user_unlock(self):
        self.rd_lock.populate_slack_user_object.side_effect = [self.su]
        yield from self.rd_lock.toggle_rundeck_lock(self.sm, False)
        self.assertEqual(self.rd_lock.set_channel_topic.mock_calls,
                         [call(False)])
        send_channel_msg_calls = [
            call("C001", ":white_check_mark: Rundeck executions unlocked! :white_check_mark:")  # NOQA
        ]
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         send_channel_msg_calls)
        self.assertEqual(self.rd_lock.print_lock_status.mock_calls,
                         [call(self.sm)])

    def test_authorized_user_lock(self):
        self.rd_lock.populate_slack_user_object.side_effect = [self.su]
        yield from self.rd_lock.toggle_rundeck_lock(self.sm, True)
        self.assertEqual(self.rd_lock.set_channel_topic.mock_calls,
                         [call(True)])
        send_channel_msg_calls = [
            call("C001", ":lock: Rundeck executions locked by <@suser> :lock:")
        ]
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         send_channel_msg_calls)
        self.assertEqual(self.rd_lock.print_lock_status.mock_calls,
                         [call(self.sm)])
class TestLockUnlockRundeckJob(asynctest.TestCase):

    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot_rundeck.rundeck_lock.http_post_request')
        self.addCleanup(patcher2.stop)
        self.mock_http_post_request = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])
        self.rd_job = RundeckJob(execution_enabled=False,
                                 href="job.com")
        self.headers = {
            "Accept": "application/json",
            "X-Rundeck-Auth-Token": "token",
        }

    def test_lock_invalid_response(self):
        self.mock_http_post_request.side_effect = [""]
        yield from self.rd_lock.lock_or_unlock_rundeck_job(self.rd_job, True)
        calls = [
            call("job.com/execution/disable", self.headers)
        ]
        self.assertFalse(self.rd_job.execution_enabled)
        self.assertEqual(self.mock_http_post_request.mock_calls, calls)

    def test_lock_valid_response(self):
        self.mock_http_post_request.side_effect = ["200"]
        yield from self.rd_lock.lock_or_unlock_rundeck_job(self.rd_job, True)
        calls = [
            call("job.com/execution/disable", self.headers)
        ]
        self.assertTrue(self.rd_job.execution_enabled)
        self.assertEqual(self.mock_http_post_request.mock_calls, calls)

    def test_unlock_valid_response(self):
        self.rd_job.execution_enabled = True
        self.mock_http_post_request.side_effect = ["200"]
        yield from self.rd_lock.lock_or_unlock_rundeck_job(self.rd_job, False)
        calls = [
            call("job.com/execution/enable", self.headers)
        ]
        self.assertFalse(self.rd_job.execution_enabled)
        self.assertEqual(self.mock_http_post_request.mock_calls, calls)
class Rundeck(BasePlugin):

    def __init__(self):
        super().__init__("Rundeck")
        self.load_config()

    def load_config(self):  # pragma: no cover
        config_dict = configuration.get()
        self.rundeck_token = config_dict['rundeck']['token']
        self.rundeck_url = config_dict['rundeck']['url']
        try:
            # It's okay if this key isn't set
            self.topic_channel = config_dict['rundeck']['deployment_status_channel']  # NOQA
        except KeyError:
            self.topic_channel = None
        self.rd_jobs_raw_list = config_dict['rundeck']['lock_jobs']
        self.rundeck_lock = RundeckLock(self.rundeck_token,
                                        self.rundeck_url,
                                        self.topic_channel,
                                        self.rd_jobs_raw_list)

    def get_help_message(self):
        help_msg = []
        help_msg.append("!lock status - Prints the status of the Rundeck deployment lock")  # NOQA
        help_msg.append("!lock acquire - Acquires the Rundeck deployment lock (only available to Slack admins)")  # NOQA
        help_msg.append("!lock release - Releases the Rundeck deployment lock (only available to Slack admins)")  # NOQA
        return "\n".join(help_msg)

    @asyncio.coroutine
    def process_message(self, message):
        """
        Main method that handles all messages sent to this plugin
        """
        if not type(message) is SlackMessage:
            return

        parsed_message = parse_msg_with_prefix("!lock", message.text)
        if not parsed_message:
            return

        if does_msg_contain_prefix("acquire", parsed_message):
            yield from self.rundeck_lock.toggle_rundeck_lock(message,
                                                             lock_job=True)
        elif does_msg_contain_prefix("release", parsed_message):
            yield from self.rundeck_lock.toggle_rundeck_lock(message,
                                                             lock_job=False)
        elif does_msg_contain_prefix("status", parsed_message):
            yield from self.rundeck_lock.print_lock_status(message)
    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])
        self.rd_lock.slack.send_channel_message = CoroutineMock()
        self.rd_lock.trigger_rundeck_executions_allowed_update = CoroutineMock()  # NOQA
        self.sm = SlackMessage(channel="sixchan")
    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot_rundeck.rundeck_lock.http_get_request')
        self.addCleanup(patcher2.stop)
        self.mock_http_get_request = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])
 def load_config(self):  # pragma: no cover
     config_dict = configuration.get()
     self.rundeck_token = config_dict['rundeck']['token']
     self.rundeck_url = config_dict['rundeck']['url']
     try:
         # It's okay if this key isn't set
         self.topic_channel = config_dict['rundeck']['deployment_status_channel']  # NOQA
     except KeyError:
         self.topic_channel = None
     self.rd_jobs_raw_list = config_dict['rundeck']['lock_jobs']
     self.rundeck_lock = RundeckLock(self.rundeck_token,
                                     self.rundeck_url,
                                     self.topic_channel,
                                     self.rd_jobs_raw_list)
    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot.slack.slack_connection.SlackConnection.api_call')  # NOQA
        self.addCleanup(patcher2.stop)
        self.mock_api_call = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])
    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])

        self.rd_lock.rundeck_jobs = [RundeckJob(friendly_name="job1")]
        self.rd_lock.populate_slack_user_object = CoroutineMock()
        self.rd_lock.slack.send_channel_message = CoroutineMock()
        self.rd_lock.lock_or_unlock_rundeck_job = CoroutineMock()
        self.rd_lock.set_channel_topic = CoroutineMock()
        self.rd_lock.print_lock_status = CoroutineMock()
        self.sm = SlackMessage(user="******", channel="C001")
        self.su = SlackUser(name="suser", is_admin=True)
    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot_rundeck.rundeck_lock.http_post_request')
        self.addCleanup(patcher2.stop)
        self.mock_http_post_request = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])
        self.rd_job = RundeckJob(execution_enabled=False,
                                 href="job.com")
        self.headers = {
            "Accept": "application/json",
            "X-Rundeck-Auth-Token": "token",
        }
class TestUpdateExecutionEnabledStatus(asynctest.TestCase):

    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot_rundeck.rundeck_lock.http_get_request')
        self.addCleanup(patcher2.stop)
        self.mock_http_get_request = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])

    def test_empty_task_list(self):
        self.rd_lock.rundeck_jobs = []
        yield from self.rd_lock.trigger_rundeck_executions_allowed_update()
        self.assertEqual(self.mock_http_get_request.mock_calls, [])

    def test_multiple_tasks_execution_disabled(self):
        rd_job1 = RundeckJob(friendly_name="job1")
        rd_job2 = RundeckJob(friendly_name="job2")
        self.rd_lock.rundeck_jobs = [
            rd_job1,
            rd_job2
        ]
        side_effect = [
            "<joblist><job><description></description><executionEnabled>false</executionEnabled></job></joblist>",  # NOQA
            "<joblist><job><description></description><executionEnabled>false</executionEnabled></job></joblist>",  # NOQA
        ]
        self.mock_http_get_request.side_effect = side_effect
        yield from self.rd_lock.trigger_rundeck_executions_allowed_update()
        self.assertEqual({False, False}, {rd_job1.execution_enabled, rd_job2.execution_enabled})  # NOQA

    def test_multiple_tasks_execution_enabled(self):
        rd_job1 = RundeckJob(friendly_name="job1")
        rd_job2 = RundeckJob(friendly_name="job2")
        self.rd_lock.rundeck_jobs = [
            rd_job1,
            rd_job2
        ]
        side_effect = [
            "<joblist><job><description></description><executionEnabled>true</executionEnabled></job></joblist>",  # NOQA
            "<joblist><job><description></description><executionEnabled>true</executionEnabled></job></joblist>",  # NOQA
        ]
        self.mock_http_get_request.side_effect = side_effect
        yield from self.rd_lock.trigger_rundeck_executions_allowed_update()
        self.assertEqual({True, True}, {rd_job1.execution_enabled, rd_job2.execution_enabled})  # NOQA

    def test_multiple_tasks_mixed_execution_states(self):
        rd_job1 = RundeckJob(friendly_name="job1")
        rd_job2 = RundeckJob(friendly_name="job2")
        self.rd_lock.rundeck_jobs = [
            rd_job1,
            rd_job2,
        ]
        side_effect = [
            "<joblist><job><description></description><executionEnabled>true</executionEnabled></job></joblist>",  # NOQA
            "<joblist><job><description></description><executionEnabled>false</executionEnabled></job></joblist>",  # NOQA
        ]
        self.mock_http_get_request.side_effect = side_effect
        yield from self.rd_lock.trigger_rundeck_executions_allowed_update()
        self.assertEqual({True, False}, {rd_job1.execution_enabled, rd_job2.execution_enabled})  # NOQA
class TestPrintLockStatus(asynctest.TestCase):

    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])
        self.rd_lock.slack.send_channel_message = CoroutineMock()
        self.rd_lock.trigger_rundeck_executions_allowed_update = CoroutineMock()  # NOQA
        self.sm = SlackMessage(channel="sixchan")

    def test_empty_job_list(self):
        self.rd_lock.rundeck_jobs = []
        expected_slack_msg_raw = []
        expected_slack_msg_raw.append("*Rundeck Job Lock Report*")
        expected_slack_msg_raw.append("```")
        expected_slack_msg_raw.append("```")
        expected_slack_msg = "\n".join(expected_slack_msg_raw)
        expected_call = call("sixchan", expected_slack_msg)
        yield from self.rd_lock.print_lock_status(self.sm)
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         [expected_call])

    def test_single_enabled_job(self):
        self.rd_lock.rundeck_jobs = [
            RundeckJob(friendly_name="job1", execution_enabled=True)
        ]
        expected_slack_msg_raw = []
        expected_slack_msg_raw.append("*Rundeck Job Lock Report*")
        expected_slack_msg_raw.append("```")
        expected_slack_msg_raw.append("job1: unlocked")
        expected_slack_msg_raw.append("```")
        expected_slack_msg = "\n".join(expected_slack_msg_raw)
        expected_call = call("sixchan", expected_slack_msg)
        yield from self.rd_lock.print_lock_status(self.sm)
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         [expected_call])

    def test_single_disabled_job(self):
        self.rd_lock.rundeck_jobs = [
            RundeckJob(friendly_name="job1", execution_enabled=False)
        ]
        expected_slack_msg_raw = []
        expected_slack_msg_raw.append("*Rundeck Job Lock Report*")
        expected_slack_msg_raw.append("```")
        expected_slack_msg_raw.append("job1: locked")
        expected_slack_msg_raw.append("```")
        expected_slack_msg = "\n".join(expected_slack_msg_raw)
        expected_call = call("sixchan", expected_slack_msg)
        yield from self.rd_lock.print_lock_status(self.sm)
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         [expected_call])

    def test_multiple_mixed_jobs(self):
        self.rd_lock.rundeck_jobs = [
            RundeckJob(friendly_name="job1", execution_enabled=False),
            RundeckJob(friendly_name="job3", execution_enabled=True),
            RundeckJob(friendly_name="job2", execution_enabled=False)
        ]
        expected_slack_msg_raw = []
        expected_slack_msg_raw.append("*Rundeck Job Lock Report*")
        expected_slack_msg_raw.append("```")
        expected_slack_msg_raw.append("job1: locked")
        expected_slack_msg_raw.append("job3: unlocked")
        expected_slack_msg_raw.append("job2: locked")
        expected_slack_msg_raw.append("```")
        expected_slack_msg = "\n".join(expected_slack_msg_raw)
        expected_call = call("sixchan", expected_slack_msg)
        yield from self.rd_lock.print_lock_status(self.sm)
        self.assertEqual(self.rd_lock.slack.send_channel_message.mock_calls,
                         [expected_call])
class TestLoadRundeckJobs(asynctest.TestCase):

    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot_rundeck.rundeck_lock.RundeckJob.retrieve_rundeck_job_info')  # NOQA
        self.addCleanup(patcher2.stop)
        self.mock_rundeck_job = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])

    def test_empty_raw_job_list(self):
        self.rd_lock.rd_jobs_raw_list = []
        yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(self.rd_lock.rundeck_jobs, [])

    def test_friendly_name_key_not_present(self):
        self.rd_lock.rd_jobs_raw_list = [
            {
                "key1": "val1",
            }
        ]
        with self.assertRaises(KeyError):
            yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(self.rd_lock.rundeck_jobs, [])

    def test_single_unsuccessful_job(self):
        self.rd_lock.rd_jobs_raw_list = [
            {
                "friendly_name": "fname1",
                "project": "project1",
                "name": "name1",
            }
        ]
        self.mock_rundeck_job.side_effect = [False]
        yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(self.rd_lock.rundeck_jobs, [])

    def test_multiple_mixed_success_job_1(self):
        self.rd_lock.rd_jobs_raw_list = [
            {
                "friendly_name": "fname1",
                "project": "project1",
                "name": "name1",
            },
            {
                "friendly_name": "fname2",
                "project": "project2",
                "name": "name2",
            },
            {
                "friendly_name": "fname3",
                "project": "project3",
                "name": "name3",
            },
        ]
        self.mock_rundeck_job.side_effect = [True, False, True]
        yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(len(self.rd_lock.rundeck_jobs), 2)

    def test_multiple_mixed_success_job_2(self):
        self.rd_lock.rd_jobs_raw_list = [
            {
                "friendly_name": "fname1",
                "project": "project1",
                "name": "name1",
            },
            {
                "friendly_name": "fname2",
                "project": "project2",
                "name": "name2",
            },
            {
                "friendly_name": "fname3",
                "project": "project3",
                "name": "name3",
            },
        ]
        self.mock_rundeck_job.side_effect = [False, True, True]
        yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(len(self.rd_lock.rundeck_jobs), 2)

    def test_multiple_mixed_success_job_3(self):
        self.rd_lock.rd_jobs_raw_list = [
            {
                "friendly_name": "fname1",
                "project": "project1",
                "name": "name1",
            },
            {
                "friendly_name": "fname2",
                "project": "project2",
                "name": "name2",
            },
            {
                "friendly_name": "fname3",
                "project": "project3",
                "name": "name3",
            },
        ]
        self.mock_rundeck_job.side_effect = [True, True, False]
        yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(len(self.rd_lock.rundeck_jobs), 2)

    def test_multiple_successful_jobs(self):
        self.rd_lock.rd_jobs_raw_list = [
            {
                "friendly_name": "fname1",
                "project": "project1",
                "name": "name1",
            },
            {
                "friendly_name": "fname2",
                "project": "project2",
                "name": "name2",
            },
            {
                "friendly_name": "fname3",
                "project": "project3",
                "name": "name3",
            },
        ]
        self.mock_rundeck_job.side_effect = [True, True, True]
        yield from self.rd_lock.load_rundeck_jobs()
        self.assertEqual(len(self.rd_lock.rundeck_jobs), 3)
class TestSetChannelTopic(asynctest.TestCase):

    def setUp(self):
        patcher1 = patch('charlesbot_rundeck.rundeck_lock.RundeckLock.seed_job_list')  # NOQA
        self.addCleanup(patcher1.stop)
        self.mock_seed_job_list = patcher1.start()

        patcher2 = patch('charlesbot.slack.slack_connection.SlackConnection.api_call')  # NOQA
        self.addCleanup(patcher2.stop)
        self.mock_api_call = patcher2.start()

        from charlesbot_rundeck.rundeck_lock import RundeckLock
        self.rd_lock = RundeckLock("token",
                                   "url",
                                   "channel",
                                   [])

    def test_topic_channel_not_set(self):
        self.rd_lock.topic_channel = None
        yield from self.rd_lock.set_channel_topic(True)
        self.assertEqual(self.mock_api_call.mock_calls, [])

    def test_topic_channel_id_already_set(self):
        self.rd_lock.locked_by_user = "******"
        self.rd_lock.topic_channel = "chan1"
        self.rd_lock.topic_channel_id = "C1234"
        yield from self.rd_lock.set_channel_topic(True)
        calls = [
            call("channels.setTopic",
                 channel="C1234",
                 topic=":lock: Rundeck executions locked by @bob :lock:")
        ]
        self.assertEqual(self.mock_api_call.mock_calls, calls)

    def test_topic_channel_not_found(self):
        channels = {
            "ok": True,
            "channels": [
                {
                    "name": "chan1",
                },
                {
                    "name": "chan2",
                },
                {
                    "name": "chan3",
                },
            ]
        }
        self.rd_lock.locked_by_user = "******"
        self.rd_lock.topic_channel = "chan4"
        self.mock_api_call.side_effect = [json.dumps(channels), None]
        yield from self.rd_lock.set_channel_topic(True)
        calls = [
            call("channels.list", exclude_archived=1),
        ]
        self.assertEqual(self.mock_api_call.mock_calls, calls)

    def test_topic_channel_found(self):
        channels = {
            "ok": True,
            "channels": [
                {
                    "name": "chan1",
                    "id": "C1",
                },
                {
                    "name": "chan2",
                    "id": "C2",
                },
                {
                    "name": "chan3",
                    "id": "C3",
                },
            ]
        }
        self.rd_lock.locked_by_user = "******"
        self.rd_lock.topic_channel = "chan2"
        self.mock_api_call.side_effect = [json.dumps(channels), None]
        yield from self.rd_lock.set_channel_topic(False)
        calls = [
            call("channels.list", exclude_archived=1),
            call("channels.setTopic",
                 channel="C2",
                 topic="")
        ]
        self.assertEqual(self.mock_api_call.mock_calls, calls)