# Copyright 2016 Cloudbase Solutions Srl
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
    Unit tests for the common functions used by different CLI interfaces.
"""

import sys

import fixtures
import mock
from six.moves import StringIO

from nova.cmd import common as cmd_common
from nova.db import api
from nova import exception
from nova import test


class TestCmdCommon(test.NoDBTestCase):

    @mock.patch.object(cmd_common, 'LOG')
    @mock.patch.object(api, 'IMPL')
    def test_block_db_access(self, mock_db_IMPL, mock_LOG):
        cmd_common.block_db_access('unit-tests')

        self.assertEqual(api.IMPL, api.IMPL.foo)
        self.assertRaises(exception.DBNotAllowed, api.IMPL)
        self.assertEqual('unit-tests',
                         mock_LOG.error.call_args[0][1]['service_name'])

    def test_args_decorator(self):
        @cmd_common.args(bar='<bar>')
        @cmd_common.args('foo')
        def f():
            pass

        f_args = f.__dict__['args']
        bar_args = ((), {'bar': '<bar>'})
        foo_args = (('foo', ), {})
        self.assertEqual(bar_args, f_args[0])
        self.assertEqual(foo_args, f_args[1])

    def test_methods_of(self):
        class foo(object):
            foo = 'bar'

            def public(self):
                pass

            def _private(self):
                pass

        methods = cmd_common.methods_of(foo())

        method_names = [method_name for method_name, method in methods]
        self.assertIn('public', method_names)
        self.assertNotIn('_private', method_names)
        self.assertNotIn('foo', method_names)

    @mock.patch.object(cmd_common, 'CONF')
    def test_print_bash_completion_no_query_category(self, mock_CONF):
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
        mock_CONF.category.query_category = None
        categories = {'foo': mock.sentinel.foo, 'bar': mock.sentinel.bar}

        cmd_common.print_bash_completion(categories)

        self.assertEqual(' '.join(categories.keys()) + '\n',
                         sys.stdout.getvalue())

    @mock.patch.object(cmd_common, 'CONF')
    def test_print_bash_completion_mismatch(self, mock_CONF):
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
        mock_CONF.category.query_category = 'bar'
        categories = {'foo': mock.sentinel.foo}

        cmd_common.print_bash_completion(categories)
        self.assertEqual('', sys.stdout.getvalue())

    @mock.patch.object(cmd_common, 'methods_of')
    @mock.patch.object(cmd_common, 'CONF')
    def test_print_bash_completion(self, mock_CONF, mock_method_of):
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
        mock_CONF.category.query_category = 'foo'
        actions = [('f1', mock.sentinel.f1), ('f2', mock.sentinel.f2)]
        mock_method_of.return_value = actions
        mock_fn = mock.Mock()
        categories = {'foo': mock_fn}

        cmd_common.print_bash_completion(categories)

        mock_fn.assert_called_once_with()
        mock_method_of.assert_called_once_with(mock_fn.return_value)
        self.assertEqual(' '.join([k for k, v in actions]) + '\n',
                         sys.stdout.getvalue())

    @mock.patch.object(cmd_common, 'validate_args')
    @mock.patch.object(cmd_common, 'CONF')
    def test_get_action_fn(self, mock_CONF, mock_validate_args):
        mock_validate_args.return_value = None
        action_args = [u'arg']
        action_kwargs = ['missing', 'foo', 'bar']

        mock_CONF.category.action_fn = mock.sentinel.action_fn
        mock_CONF.category.action_args = action_args
        mock_CONF.category.action_kwargs = action_kwargs
        mock_CONF.category.action_kwarg_foo = u'foo_val'
        mock_CONF.category.action_kwarg_bar = True
        mock_CONF.category.action_kwarg_missing = None

        actual_fn, actual_args, actual_kwargs = cmd_common.get_action_fn()

        self.assertEqual(mock.sentinel.action_fn, actual_fn)
        self.assertEqual(action_args, actual_args)
        self.assertEqual(u'foo_val', actual_kwargs['foo'])
        self.assertTrue(actual_kwargs['bar'])
        self.assertNotIn('missing', actual_kwargs)

    @mock.patch.object(cmd_common, 'validate_args')
    @mock.patch.object(cmd_common, 'CONF')
    def test_get_action_fn_missing_args(self, mock_CONF, mock_validate_args):
        # Don't leak the actual print call
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
        mock_validate_args.return_value = ['foo']
        mock_CONF.category.action_fn = mock.sentinel.action_fn
        mock_CONF.category.action_args = []
        mock_CONF.category.action_kwargs = []

        self.assertRaises(exception.Invalid, cmd_common.get_action_fn)
        mock_CONF.print_help.assert_called_once_with()
