#!/usr/bin/env python

# Copyright (c) 2016 Orange and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0

import logging
import os
import time
import unittest

from git.exc import NoSuchPathError
import mock
import requests
from six.moves import urllib

from functest.tests.unit import test_utils
from functest.utils import functest_utils


class FunctestUtilsTesting(unittest.TestCase):

    def setUp(self):
        self.url = 'http://www.opnfv.org/'
        self.timeout = 5
        self.dest_path = 'test_path'
        self.repo_path = 'test_repo_path'
        self.installer = 'test_installer'
        self.scenario = 'test_scenario'
        self.build_tag = 'jenkins-functest-fuel-opnfv-jump-2-daily-master-190'
        self.build_tag_week = 'jenkins-functest-fuel-baremetal-weekly-master-8'
        self.version = 'master'
        self.node_name = 'test_node_name'
        self.project = 'test_project'
        self.case_name = 'test_case_name'
        self.status = 'test_status'
        self.details = 'test_details'
        self.db_url = 'test_db_url'
        self.criteria = 50
        self.result = 75
        self.start_date = 1482624000
        self.stop_date = 1482624000
        self.start_time = time.time()
        self.stop_time = time.time()
        self.readline = -1
        self.test_ip = ['10.1.23.4', '10.1.14.15', '10.1.16.15']
        self.test_file = 'test_file'
        self.error_msg = 'test_error_msg'
        self.cmd = 'test_cmd'
        self.output_file = 'test_output_file'
        self.testname = 'testname'
        self.testcase_dict = {'case_name': 'testname',
                              'criteria': self.criteria}
        self.parameter = 'general.openstack.image_name'
        self.config_yaml = 'test_config_yaml-'
        self.db_url_env = 'http://foo/testdb'
        self.file_yaml = {'general': {'openstack': {'image_name':
                                                    'test_image_name'}}}

    @mock.patch('six.moves.urllib.request.urlopen',
                side_effect=urllib.error.URLError('no host given'))
    def test_check_internet_connectivity_failed(self, mock_method):
        self.assertFalse(functest_utils.check_internet_connectivity())
        mock_method.assert_called_once_with(self.url, timeout=self.timeout)

    @mock.patch('six.moves.urllib.request.urlopen')
    def test_check_internet_connectivity_default(self, mock_method):
        self.assertTrue(functest_utils.check_internet_connectivity())
        mock_method.assert_called_once_with(self.url, timeout=self.timeout)

    @mock.patch('six.moves.urllib.request.urlopen')
    def test_check_internet_connectivity_debian(self, mock_method):
        self.url = "https://www.debian.org/"
        self.assertTrue(functest_utils.check_internet_connectivity(self.url))
        mock_method.assert_called_once_with(self.url, timeout=self.timeout)

    @mock.patch('six.moves.urllib.request.urlopen',
                side_effect=urllib.error.URLError('no host given'))
    def test_download_url_failed(self, mock_url):
        self.assertFalse(functest_utils.download_url(self.url, self.dest_path))

    @mock.patch('six.moves.urllib.request.urlopen')
    def test_download_url_default(self, mock_url):
        with mock.patch("six.moves.builtins.open", mock.mock_open()) as m, \
                mock.patch('functest.utils.functest_utils.shutil.copyfileobj')\
                as mock_sh:
            name = self.url.rsplit('/')[-1]
            dest = self.dest_path + "/" + name
            self.assertTrue(functest_utils.download_url(self.url,
                                                        self.dest_path))
            m.assert_called_once_with(dest, 'wb')
            self.assertTrue(mock_sh.called)

    def test_get_git_branch(self):
        with mock.patch('functest.utils.functest_utils.Repo') as mock_repo:
            mock_obj2 = mock.Mock()
            attrs = {'name': 'test_branch'}
            mock_obj2.configure_mock(**attrs)

            mock_obj = mock.Mock()
            attrs = {'active_branch': mock_obj2}
            mock_obj.configure_mock(**attrs)

            mock_repo.return_value = mock_obj
            self.assertEqual(functest_utils.get_git_branch(self.repo_path),
                             'test_branch')

    @mock.patch('functest.utils.functest_utils.Repo',
                side_effect=NoSuchPathError)
    def test_get_git_branch_failed(self, mock_repo):
        self.assertRaises(NoSuchPathError,
                          lambda: functest_utils.get_git_branch(self.repo_path
                                                                ))

    @mock.patch('functest.utils.functest_utils.logger.error')
    def test_get_installer_type_failed(self, mock_logger_error):
        with mock.patch.dict(os.environ,
                             {},
                             clear=True):
            self.assertEqual(functest_utils.get_installer_type(),
                             "Unknown_installer")
            mock_logger_error.assert_called_once_with("Impossible to retrieve"
                                                      " the installer type")

    def test_get_installer_type_default(self):
        with mock.patch.dict(os.environ,
                             {'INSTALLER_TYPE': 'test_installer'},
                             clear=True):
            self.assertEqual(functest_utils.get_installer_type(),
                             self.installer)

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_get_scenario_failed(self, mock_logger_info):
        with mock.patch.dict(os.environ,
                             {},
                             clear=True):
            self.assertEqual(functest_utils.get_scenario(),
                             "os-nosdn-nofeature-noha")
            mock_logger_info.assert_called_once_with("Impossible to retrieve "
                                                     "the scenario.Use "
                                                     "default "
                                                     "os-nosdn-nofeature-noha")

    def test_get_scenario_default(self):
        with mock.patch.dict(os.environ,
                             {'DEPLOY_SCENARIO': 'test_scenario'},
                             clear=True):
            self.assertEqual(functest_utils.get_scenario(),
                             self.scenario)

    @mock.patch('functest.utils.functest_utils.get_build_tag')
    def test_get_version_daily_job(self, mock_get_build_tag):
        mock_get_build_tag.return_value = self.build_tag
        self.assertEqual(functest_utils.get_version(), self.version)

    @mock.patch('functest.utils.functest_utils.get_build_tag')
    def test_get_version_weekly_job(self, mock_get_build_tag):
        mock_get_build_tag.return_value = self.build_tag_week
        self.assertEqual(functest_utils.get_version(), self.version)

    @mock.patch('functest.utils.functest_utils.get_build_tag')
    def test_get_version_with_dummy_build_tag(self, mock_get_build_tag):
        mock_get_build_tag.return_value = 'whatever'
        self.assertEqual(functest_utils.get_version(), 'unknown')

    @mock.patch('functest.utils.functest_utils.get_build_tag')
    def test_get_version_unknown(self, mock_get_build_tag):
        mock_get_build_tag.return_value = "unknown_build_tag"
        self.assertEqual(functest_utils.get_version(), "unknown")

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_get_pod_name_failed(self, mock_logger_info):
        with mock.patch.dict(os.environ,
                             {},
                             clear=True):
            self.assertEqual(functest_utils.get_pod_name(),
                             "unknown-pod")
            mock_logger_info.assert_called_once_with("Unable to retrieve "
                                                     "the POD name from "
                                                     "environment. Using "
                                                     "pod name 'unknown-pod'")

    def test_get_pod_name_default(self):
        with mock.patch.dict(os.environ,
                             {'NODE_NAME': 'test_node_name'},
                             clear=True):
            self.assertEqual(functest_utils.get_pod_name(),
                             self.node_name)

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_get_build_tag_failed(self, mock_logger_info):
        with mock.patch.dict(os.environ,
                             {},
                             clear=True):
            self.assertEqual(functest_utils.get_build_tag(),
                             "none")
            mock_logger_info.assert_called_once_with("Impossible to retrieve"
                                                     " the build tag")

    def test_get_build_tag_default(self):
        with mock.patch.dict(os.environ,
                             {'BUILD_TAG': self.build_tag},
                             clear=True):
            self.assertEqual(functest_utils.get_build_tag(),
                             self.build_tag)

    def test_get_db_url_env_var(self):
        with mock.patch.dict(os.environ,
                             {'TEST_DB_URL': self.db_url_env,
                              'CONFIG_FUNCTEST_YAML':
                              "./functest/ci/config_functest.yaml"},
                             clear=True):
            self.assertEqual(functest_utils.get_db_url(),
                             self.db_url_env)

    @mock.patch('functest.utils.functest_utils.get_functest_config')
    def test_get_db_url_default(self, mock_get_functest_config):
        mock_get_functest_config.return_value = self.db_url
        self.assertEqual(functest_utils.get_db_url(), self.db_url)
        mock_get_functest_config.assert_called_once_with('results.test_db_url')

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_logger_test_results(self, mock_logger_info):
        with mock.patch('functest.utils.functest_utils.get_pod_name',
                        return_value=self.node_name), \
                mock.patch('functest.utils.functest_utils.get_scenario',
                           return_value=self.scenario), \
                mock.patch('functest.utils.functest_utils.get_version',
                           return_value=self.version), \
                mock.patch('functest.utils.functest_utils.get_build_tag',
                           return_value=self.build_tag), \
                mock.patch('functest.utils.functest_utils.get_db_url',
                           return_value=self.db_url):
            functest_utils.logger_test_results(self.project, self.case_name,
                                               self.status, self.details)
            mock_logger_info.assert_called_once_with(
                "\n"
                "****************************************\n"
                "\t %(p)s/%(n)s results \n\n"
                "****************************************\n"
                "DB:\t%(db)s\n"
                "pod:\t%(pod)s\n"
                "version:\t%(v)s\n"
                "scenario:\t%(s)s\n"
                "status:\t%(c)s\n"
                "build tag:\t%(b)s\n"
                "details:\t%(d)s\n"
                % {'p': self.project,
                    'n': self.case_name,
                    'db': self.db_url,
                    'pod': self.node_name,
                    'v': self.version,
                    's': self.scenario,
                    'c': self.status,
                    'b': self.build_tag,
                    'd': self.details})

    def _get_env_dict(self, var):
        dic = {'INSTALLER_TYPE': self.installer,
               'DEPLOY_SCENARIO': self.scenario,
               'NODE_NAME': self.node_name,
               'BUILD_TAG': self.build_tag}
        dic.pop(var, None)
        return dic

    def _test_push_results_to_db_missing_env(self, env_var):
        dic = self._get_env_dict(env_var)
        with mock.patch('functest.utils.functest_utils.get_db_url',
                        return_value=self.db_url), \
                mock.patch.dict(os.environ,
                                dic,
                                clear=True), \
                mock.patch('functest.utils.functest_utils.logger.error') \
                as mock_logger_error:
            functest_utils.push_results_to_db(self.project, self.case_name,
                                              self.start_date, self.stop_date,
                                              self.result, self.details)
            mock_logger_error.assert_called_once_with("Please set env var: " +
                                                      str("\'" + env_var +
                                                          "\'"))

    def test_push_results_to_db_missing_installer(self):
        self._test_push_results_to_db_missing_env('INSTALLER_TYPE')

    def test_push_results_to_db_missing_scenario(self):
        self._test_push_results_to_db_missing_env('DEPLOY_SCENARIO')

    def test_push_results_to_db_missing_nodename(self):
        self._test_push_results_to_db_missing_env('NODE_NAME')

    def test_push_results_to_db_missing_buildtag(self):
        self._test_push_results_to_db_missing_env('BUILD_TAG')

    def test_push_results_to_db_request_post_failed(self):
        dic = self._get_env_dict(None)
        with mock.patch('functest.utils.functest_utils.get_db_url',
                        return_value=self.db_url), \
                mock.patch.dict(os.environ,
                                dic,
                                clear=True), \
                mock.patch('functest.utils.functest_utils.logger.error') \
                as mock_logger_error, \
                mock.patch('functest.utils.functest_utils.requests.post',
                           side_effect=requests.RequestException):
            self.assertFalse(functest_utils.
                             push_results_to_db(self.project, self.case_name,
                                                self.start_date,
                                                self.stop_date,
                                                self.result, self.details))
            mock_logger_error.assert_called_once_with(test_utils.
                                                      RegexMatch("Pushing "
                                                                 "Result to"
                                                                 " DB"
                                                                 "(\S+\s*) "
                                                                 "failed:"))

    def test_push_results_to_db_request_post_exception(self):
        dic = self._get_env_dict(None)
        with mock.patch('functest.utils.functest_utils.get_db_url',
                        return_value=self.db_url), \
                mock.patch.dict(os.environ,
                                dic,
                                clear=True), \
                mock.patch('functest.utils.functest_utils.logger.error') \
                as mock_logger_error, \
                mock.patch('functest.utils.functest_utils.requests.post',
                           side_effect=Exception):
            self.assertFalse(functest_utils.
                             push_results_to_db(self.project, self.case_name,
                                                self.start_date,
                                                self.stop_date,
                                                self.result, self.details))
            self.assertTrue(mock_logger_error.called)

    def test_push_results_to_db_default(self):
        dic = self._get_env_dict(None)
        with mock.patch('functest.utils.functest_utils.get_db_url',
                        return_value=self.db_url), \
                mock.patch.dict(os.environ,
                                dic,
                                clear=True), \
                mock.patch('functest.utils.functest_utils.requests.post'):
            self.assertTrue(functest_utils.
                            push_results_to_db(self.project, self.case_name,
                                               self.start_date,
                                               self.stop_date,
                                               self.result, self.details))
    readline = 0
    test_ip = ['10.1.23.4', '10.1.14.15', '10.1.16.15']

    @staticmethod
    def readline_side():
        if FunctestUtilsTesting.readline == \
                len(FunctestUtilsTesting.test_ip) - 1:
            return False
        FunctestUtilsTesting.readline += 1
        return FunctestUtilsTesting.test_ip[FunctestUtilsTesting.readline]

    # TODO: get_resolvconf_ns
    @mock.patch('functest.utils.functest_utils.dns.resolver.Resolver')
    def test_get_resolvconf_ns_default(self, mock_dns_resolve):
        attrs = {'query.return_value': ["test"]}
        mock_dns_resolve.configure_mock(**attrs)

        m = mock.Mock()
        attrs = {'readline.side_effect': self.readline_side}
        m.configure_mock(**attrs)

        with mock.patch("six.moves.builtins.open") as mo:
            mo.return_value = m
            self.assertEqual(functest_utils.get_resolvconf_ns(),
                             self.test_ip[1:])

    def _get_environ(self, var):
        if var == 'INSTALLER_TYPE':
            return self.installer
        elif var == 'DEPLOY_SCENARIO':
            return self.scenario
        return var

    def test_get_ci_envvars_default(self):
        with mock.patch('os.environ.get',
                        side_effect=self._get_environ):
            dic = {"installer": self.installer,
                   "scenario": self.scenario}
            self.assertDictEqual(functest_utils.get_ci_envvars(), dic)

    def cmd_readline(self):
        return 'test_value\n'

    @mock.patch('functest.utils.functest_utils.logger.error')
    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_execute_command_args_present_with_error(self, mock_logger_info,
                                                     mock_logger_error):
        with mock.patch('functest.utils.functest_utils.subprocess.Popen') \
                as mock_subproc_open, \
                mock.patch('six.moves.builtins.open',
                           mock.mock_open()) as mopen:

            FunctestUtilsTesting.readline = 0

            mock_obj = mock.Mock()
            attrs = {'readline.side_effect': self.cmd_readline()}
            mock_obj.configure_mock(**attrs)

            mock_obj2 = mock.Mock()
            attrs = {'stdout': mock_obj, 'wait.return_value': 1}
            mock_obj2.configure_mock(**attrs)

            mock_subproc_open.return_value = mock_obj2

            resp = functest_utils.execute_command(self.cmd, info=True,
                                                  error_msg=self.error_msg,
                                                  verbose=True,
                                                  output_file=self.output_file)
            self.assertEqual(resp, 1)
            msg_exec = ("Executing command: '%s'" % self.cmd)
            mock_logger_info.assert_called_once_with(msg_exec)
            mopen.assert_called_once_with(self.output_file, "w")
            mock_logger_error.assert_called_once_with(self.error_msg)

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_execute_command_args_present_with_success(self, mock_logger_info,
                                                       ):
        with mock.patch('functest.utils.functest_utils.subprocess.Popen') \
                as mock_subproc_open, \
                mock.patch('six.moves.builtins.open',
                           mock.mock_open()) as mopen:

            FunctestUtilsTesting.readline = 0

            mock_obj = mock.Mock()
            attrs = {'readline.side_effect': self.cmd_readline()}
            mock_obj.configure_mock(**attrs)

            mock_obj2 = mock.Mock()
            attrs = {'stdout': mock_obj, 'wait.return_value': 0}
            mock_obj2.configure_mock(**attrs)

            mock_subproc_open.return_value = mock_obj2

            resp = functest_utils.execute_command(self.cmd, info=True,
                                                  error_msg=self.error_msg,
                                                  verbose=True,
                                                  output_file=self.output_file)
            self.assertEqual(resp, 0)
            msg_exec = ("Executing command: '%s'" % self.cmd)
            mock_logger_info.assert_called_once_with(msg_exec)
            mopen.assert_called_once_with(self.output_file, "w")

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_execute_command_args_missing_with_success(self, mock_logger_info,
                                                       ):
        with mock.patch('functest.utils.functest_utils.subprocess.Popen') \
                as mock_subproc_open:

            FunctestUtilsTesting.readline = 2

            mock_obj = mock.Mock()
            attrs = {'readline.side_effect': self.cmd_readline()}
            mock_obj.configure_mock(**attrs)

            mock_obj2 = mock.Mock()
            attrs = {'stdout': mock_obj, 'wait.return_value': 0}
            mock_obj2.configure_mock(**attrs)

            mock_subproc_open.return_value = mock_obj2

            resp = functest_utils.execute_command(self.cmd, info=False,
                                                  error_msg="",
                                                  verbose=False,
                                                  output_file=None)
            self.assertEqual(resp, 0)

    @mock.patch('functest.utils.functest_utils.logger.error')
    def test_execute_command_args_missing_with_error(self, mock_logger_error,
                                                     ):
        with mock.patch('functest.utils.functest_utils.subprocess.Popen') \
                as mock_subproc_open:

            FunctestUtilsTesting.readline = 2
            mock_obj = mock.Mock()
            attrs = {'readline.side_effect': self.cmd_readline()}
            mock_obj.configure_mock(**attrs)

            mock_obj2 = mock.Mock()
            attrs = {'stdout': mock_obj, 'wait.return_value': 1}
            mock_obj2.configure_mock(**attrs)

            mock_subproc_open.return_value = mock_obj2

            resp = functest_utils.execute_command(self.cmd, info=False,
                                                  error_msg="",
                                                  verbose=False,
                                                  output_file=None)
            self.assertEqual(resp, 1)

    def _get_functest_config(self, var):
        return var

    @mock.patch('functest.utils.functest_utils.logger.error')
    def test_get_dict_by_test(self, mock_logger_error):
        with mock.patch('six.moves.builtins.open', mock.mock_open()), \
                mock.patch('functest.utils.functest_utils.yaml.safe_load') \
                as mock_yaml, \
                mock.patch('functest.utils.functest_utils.get_testcases_'
                           'file_dir'):
            mock_obj = mock.Mock()
            attrs = {'get.return_value': [{'testcases': [self.testcase_dict]}]}
            mock_obj.configure_mock(**attrs)

            mock_yaml.return_value = mock_obj

            self.assertDictEqual(functest_utils.
                                 get_dict_by_test(self.testname),
                                 self.testcase_dict)

    @mock.patch('functest.utils.functest_utils.get_dict_by_test')
    def test_get_criteria_by_test_default(self, mock_get_dict_by_test):
        mock_get_dict_by_test.return_value = self.testcase_dict
        self.assertEqual(functest_utils.get_criteria_by_test(self.testname),
                         self.criteria)

    @mock.patch('functest.utils.functest_utils.get_dict_by_test')
    def test_get_criteria_by_test_failed(self, mock_get_dict_by_test):
        mock_get_dict_by_test.return_value = None
        self.assertIsNone(functest_utils.get_criteria_by_test(self.testname))

    def test_get_parameter_from_yaml_failed(self):
        self.file_yaml['general'] = None
        with mock.patch('six.moves.builtins.open', mock.mock_open()), \
                mock.patch('functest.utils.functest_utils.yaml.safe_load') \
                as mock_yaml, \
                self.assertRaises(ValueError) as excep:
            mock_yaml.return_value = self.file_yaml
            functest_utils.get_parameter_from_yaml(self.parameter,
                                                   self.test_file)
            self.assertTrue(("The parameter %s is not"
                             " defined in config_functest.yaml" %
                             self.parameter) in excep.exception)

    def test_get_parameter_from_yaml_default(self):
        with mock.patch('six.moves.builtins.open', mock.mock_open()), \
                mock.patch('functest.utils.functest_utils.yaml.safe_load') \
                as mock_yaml:
            mock_yaml.return_value = self.file_yaml
            self.assertEqual(functest_utils.
                             get_parameter_from_yaml(self.parameter,
                                                     self.test_file),
                             'test_image_name')

    @mock.patch('functest.utils.functest_utils.get_parameter_from_yaml')
    def test_get_functest_config_default(self, mock_get_parameter_from_yaml):
        with mock.patch.dict(os.environ,
                             {'CONFIG_FUNCTEST_YAML': self.config_yaml}):
            functest_utils.get_functest_config(self.parameter)
            mock_get_parameter_from_yaml. \
                assert_called_once_with(self.parameter,
                                        self.config_yaml)

    # TODO: merge_dicts

    def test_get_testcases_file_dir(self):
        resp = functest_utils.get_testcases_file_dir()
        self.assertEqual(resp,
                         "/home/opnfv/repos/functest/"
                         "functest/ci/testcases.yaml")

    def test_get_functest_yaml(self):
        with mock.patch('six.moves.builtins.open', mock.mock_open()), \
                mock.patch('functest.utils.functest_utils.yaml.safe_load') \
                as mock_yaml:
            mock_yaml.return_value = self.file_yaml
            resp = functest_utils.get_functest_yaml()
            self.assertEqual(resp, self.file_yaml)

    @mock.patch('functest.utils.functest_utils.logger.info')
    def test_print_separator(self, mock_logger_info):
        functest_utils.print_separator()
        mock_logger_info.assert_called_once_with("======================="
                                                 "=======================")


if __name__ == "__main__":
    logging.disable(logging.CRITICAL)
    unittest.main(verbosity=2)