# Copyright 2016 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 mock import patch
import pecan
import unittest

from trio2o.cinder_apigw.controllers import volume_type
from trio2o.common import context
from trio2o.db import api as db_api
from trio2o.db import core


class FakeResponse(object):
    def __new__(cls, code=500):
        cls.status = code
        cls.status_code = code
        return super(FakeResponse, cls).__new__(cls)


class VolumeTypeTest(unittest.TestCase):
    def setUp(self):
        core.initialize()
        core.ModelBase.metadata.create_all(core.get_engine())
        self.context = context.get_admin_context()
        self.project_id = 'test_project'
        self.controller = volume_type.VolumeTypeController(self.project_id)

    def _validate_error_code(self, res, code):
        self.assertEqual(code, res[res.keys()[0]]['code'])

    @patch.object(pecan, 'response', new=FakeResponse)
    @patch.object(context, 'extract_context_from_environ')
    def test_post(self, mock_context):
        mock_context.return_value = self.context

        body = {'volume_type': {'name': 'vol-type-001',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        self.controller.post(**body)
        res = db_api.volume_type_get_by_name(self.context, 'vol-type-001')

        self.assertEqual('vol-type-001', res['name'])
        self.assertEqual('volume type 001', res['description'])
        capabilities = res['extra_specs']['capabilities']
        self.assertEqual('gpu', capabilities)

        # failure case, only admin can create volume type
        self.context.is_admin = False
        res = self.controller.post(**body)
        self._validate_error_code(res, 403)

        self.context.is_admin = True

        # failure case, volume_type body is required
        body = {'name': 'vol-type-002'}
        res = self.controller.post(**body)
        self._validate_error_code(res, 400)

        # failure case, volume type name is empty
        body = {'volume_type': {'name': '',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        res = self.controller.post(**body)
        self._validate_error_code(res, 400)

        # failure case, volume type name has more than 255 characters
        body = {'volume_type': {'name': ('a' * 500),
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu', }
                                }
                }
        res = self.controller.post(**body)
        self._validate_error_code(res, 400)

        # failure case, volume type description has more than 255 characters
        body = {'volume_type': {'name': 'vol-type-001',
                                'description': ('a' * 500),
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        self.controller.post(**body)
        self._validate_error_code(res, 400)

        # failure case, is_public is invalid input
        body = {'volume_type': {'name': 'vol-type-001',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': 'a',
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        res = self.controller.post(**body)
        self._validate_error_code(res, 400)

        # failure case, volume type name is unique
        body = {'volume_type': {'name': 'vol-type-001',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        res = self.controller.post(**body)
        self._validate_error_code(res, 409)

    @patch.object(pecan, 'response', new=FakeResponse)
    @patch.object(context, 'extract_context_from_environ')
    def test_get_one(self, mock_context):
        mock_context.return_value = self.context

        body = {'volume_type': {'name': 'vol-type-001',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        self.controller.post(**body)
        vtype = db_api.volume_type_get_by_name(self.context, 'vol-type-001')
        res = self.controller.get_one(vtype['id'])['volume_type']

        self.assertEqual('vol-type-001', res['name'])
        self.assertEqual('volume type 001', res['description'])
        capabilities = res['extra_specs']['capabilities']
        self.assertEqual('gpu', capabilities)

        # failure case, volume type is not exist.
        fake_id = "Fake_ID"
        res = self.controller.get_one(fake_id)
        self._validate_error_code(res, 404)

        # failure case, the volume type is private.
        body = {'volume_type': {'name': 'vol-type-002',
                                'description': 'volume type 002',
                                'os-volume-type-access:is_public': False,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        self.controller.post(**body)
        vtype = db_api.volume_type_get_by_name(self.context, 'vol-type-002')
        self.context.is_admin = False
        res = self.controller.get_one(vtype['id'])
        self._validate_error_code(res, 404)

    @patch.object(context, 'extract_context_from_environ')
    def test_get_all(self, mock_context):
        mock_context.return_value = self.context

        volume_type_001 = {'volume_type': {'name': 'vol-type-001',
                                           'description': 'volume type 001',
                                           'os-volume-'
                                           'type-access:is_public': True,
                                           'extra_specs': {
                                               'capabilities': 'gpu',
                                           }}}
        volume_type_002 = {'volume_type': {'name': 'vol-type-002',
                                           'description': 'volume type 002',
                                           'os-volume-'
                                           'type-access:is_public': True,
                                           'extra_specs': {
                                               'capabilities': 'gpu',
                                           }}}
        self.controller.post(**volume_type_001)
        self.controller.post(**volume_type_002)
        volume_types = self.controller.get_all()['volume_types']

        self.assertEqual('vol-type-001', volume_types[0]['name'])
        self.assertEqual('volume type 001', volume_types[0]['description'])
        capabilities_001 = volume_types[0]['extra_specs']['capabilities']
        self.assertEqual('gpu', capabilities_001)

        self.assertEqual('vol-type-002', volume_types[1]['name'])
        self.assertEqual('volume type 002', volume_types[1]['description'])
        capabilities_002 = volume_types[1]['extra_specs']['capabilities']
        self.assertEqual('gpu', capabilities_002)

    @patch.object(pecan, 'response', new=FakeResponse)
    @patch.object(context, 'extract_context_from_environ')
    def test_put(self, mock_context):
        mock_context.return_value = self.context

        body = {'volume_type': {'name': 'vol-type-001',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        body_update = {'volume_type': {'name': 'vol-type-002',
                                       'description': 'volume type 002',
                                       'os-volume-'
                                       'type-access:is_public': True,
                                       'extra_specs': {
                                           'capabilities': 'gpu',
                                       }}}
        self.controller.post(**body)
        vtype = db_api.volume_type_get_by_name(self.context, 'vol-type-001')
        res = self.controller.put(vtype['id'], **body_update)['volume_type']

        self.assertEqual('vol-type-002', res['name'])
        self.assertEqual('volume type 002', res['description'])
        capabilities = res['extra_specs']['capabilities']
        self.assertEqual('gpu', capabilities)

        # failure case, volume type name, description, is_public
        # not None at the same time
        body = {'volume_type': {'extra_specs': {
            'capabilities': 'gpu',
        }}}
        res = self.controller.put(vtype['id'], **body)
        self._validate_error_code(res, 400)
        # failure case, name exists in db
        body = {'volume_type': {'name': 'vol-type-003',
                                'description': 'volume type 003',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        self.controller.post(**body)
        res = self.controller.put(vtype['id'], **body)
        self._validate_error_code(res, 500)

    @patch.object(pecan, 'response', new=FakeResponse)
    @patch.object(db_api, 'volume_type_delete')
    @patch.object(context, 'extract_context_from_environ')
    def test_delete(self, mock_context, mock_delete):
        mock_context.return_value = self.context
        mock_delete.return_value = Exception()

        body = {'volume_type': {'name': 'vol-type-001',
                                'description': 'volume type 001',
                                'os-volume-type-access:is_public': True,
                                'extra_specs': {
                                    'capabilities': 'gpu',
                                }}}
        self.controller.post(**body)
        vtype = db_api.volume_type_get_by_name(self.context, 'vol-type-001')

        # failure case, only admin delete create volume type
        self.context.is_admin = False
        res = self.controller.delete(vtype['id'])
        self._validate_error_code(res, 403)

        # failure case, bad request
        self.context.is_admin = True
        res = self.controller.delete(_id=None)
        self._validate_error_code(res, 404)

        res = self.controller.delete(vtype['id'])
        self.assertEqual(res.status, 202)

    def tearDown(self):
        core.ModelBase.metadata.drop_all(core.get_engine())
