#
# Copyright 2012 OpenStack Foundation
# 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.


from unittest import mock

import fixtures

from heat.common import exception
from heat.common.i18n import _
from heat.tests import common


class TestException(exception.HeatException):
    msg_fmt = _("Testing message %(text)s")


class TestHeatException(common.HeatTestCase):

    def test_fatal_exception_error(self):
        self.useFixture(fixtures.MonkeyPatch(
            'heat.common.exception._FATAL_EXCEPTION_FORMAT_ERRORS',
            True))
        self.assertRaises(KeyError, TestException)

    def test_format_string_error_message(self):
        message = "This format %(message)s should work"
        err = exception.Error(message)
        self.assertEqual(message, str(err))


class TestStackValidationFailed(common.HeatTestCase):

    scenarios = [
        ('test_error_as_exception', dict(
            kwargs=dict(
                error=exception.StackValidationFailed(
                    error='Error',
                    path=['some', 'path'],
                    message='Some message')),
            expected='Error: some.path: Some message',
            called_error='Error',
            called_path=['some', 'path'],
            called_msg='Some message'
        )),
        ('test_full_exception', dict(
            kwargs=dict(
                error='Error',
                path=['some', 'path'],
                message='Some message'),
            expected='Error: some.path: Some message',
            called_error='Error',
            called_path=['some', 'path'],
            called_msg='Some message'
        )),
        ('test_no_error_exception', dict(
            kwargs=dict(
                path=['some', 'path'],
                message='Chain letter'),
            expected='some.path: Chain letter',
            called_error='',
            called_path=['some', 'path'],
            called_msg='Chain letter'
        )),
        ('test_no_path_exception', dict(
            kwargs=dict(
                error='Error',
                message='Just no.'),
            expected='Error: Just no.',
            called_error='Error',
            called_path=[],
            called_msg='Just no.'
        )),
        ('test_no_msg_exception', dict(
            kwargs=dict(
                error='Error',
                path=['we', 'lost', 'our', 'message']),
            expected='Error: we.lost.our.message: ',
            called_error='Error',
            called_path=['we', 'lost', 'our', 'message'],
            called_msg=''
        )),
        ('test_old_format_exception', dict(
            kwargs=dict(
                message='Wow. I think I am old error message format.'
            ),
            expected='Wow. I think I am old error message format.',
            called_error='',
            called_path=[],
            called_msg='Wow. I think I am old error message format.'
        )),
        ('test_int_path_item_exception', dict(
            kwargs=dict(
                path=['null', 0]
            ),
            expected='null[0]: ',
            called_error='',
            called_path=['null', 0],
            called_msg=''
        )),
        ('test_digit_path_item_exception', dict(
            kwargs=dict(
                path=['null', '0']
            ),
            expected='null[0]: ',
            called_error='',
            called_path=['null', '0'],
            called_msg=''
        )),
        ('test_string_path_exception', dict(
            kwargs=dict(
                path='null[0].not_null'
            ),
            expected='null[0].not_null: ',
            called_error='',
            called_path=['null[0].not_null'],
            called_msg=''
        ))
    ]

    def test_exception(self):
        try:
            raise exception.StackValidationFailed(**self.kwargs)
        except exception.StackValidationFailed as ex:
            self.assertIn(self.expected, str(ex))
            self.assertIn(self.called_error, ex.error)
            self.assertEqual(self.called_path, ex.path)
            self.assertEqual(self.called_msg, ex.error_message)


class TestResourceFailure(common.HeatTestCase):
    def test_status_reason_resource(self):
        reason = ('Resource CREATE failed: ValueError: resources.oops: '
                  'Test Resource failed oops')

        exc = exception.ResourceFailure(reason, None, action='CREATE')
        self.assertEqual('ValueError', exc.error)
        self.assertEqual(['resources', 'oops'], exc.path)
        self.assertEqual('Test Resource failed oops', exc.error_message)

    def test_status_reason_general(self):
        reason = ('something strange happened')
        exc = exception.ResourceFailure(reason, None, action='CREATE')
        self.assertEqual('', exc.error)
        self.assertEqual([], exc.path)
        self.assertEqual('something strange happened', exc.error_message)

    def test_status_reason_general_res(self):
        res = mock.Mock()
        res.name = 'fred'
        res.stack.t.get_section_name.return_value = 'Resources'

        reason = ('something strange happened')
        exc = exception.ResourceFailure(reason, res, action='CREATE')
        self.assertEqual('', exc.error)
        self.assertEqual(['Resources', 'fred'], exc.path)
        self.assertEqual('something strange happened', exc.error_message)

    def test_std_exception(self):
        base_exc = ValueError('sorry mom')
        exc = exception.ResourceFailure(base_exc, None, action='UPDATE')
        self.assertEqual('ValueError', exc.error)
        self.assertEqual([], exc.path)
        self.assertEqual('sorry mom', exc.error_message)

    def test_std_exception_with_resource(self):
        base_exc = ValueError('sorry mom')
        res = mock.Mock()
        res.name = 'fred'
        res.stack.t.get_section_name.return_value = 'Resources'
        exc = exception.ResourceFailure(base_exc, res, action='UPDATE')
        self.assertEqual('ValueError', exc.error)
        self.assertEqual(['Resources', 'fred'], exc.path)
        self.assertEqual('sorry mom', exc.error_message)

    def test_heat_exception(self):
        base_exc = ValueError('sorry mom')
        heat_exc = exception.ResourceFailure(base_exc, None, action='UPDATE')
        exc = exception.ResourceFailure(heat_exc, None, action='UPDATE')
        self.assertEqual('ValueError', exc.error)
        self.assertEqual([], exc.path)
        self.assertEqual('sorry mom', exc.error_message)

    def test_nested_exceptions(self):
        res = mock.Mock()
        res.name = 'frodo'
        res.stack.t.get_section_name.return_value = 'Resources'

        reason = ('Resource UPDATE failed: ValueError: resources.oops: '
                  'Test Resource failed oops')
        base_exc = exception.ResourceFailure(reason, res, action='UPDATE')
        exc = exception.ResourceFailure(base_exc, res, action='UPDATE')
        self.assertEqual(['Resources', 'frodo', 'resources', 'oops'], exc.path)
        self.assertEqual('ValueError', exc.error)
        self.assertEqual('Test Resource failed oops', exc.error_message)
