# -*- coding: utf-8 -*-

# FOGLAMP_BEGIN
# See: http://foglamp.readthedocs.io/
# FOGLAMP_END

import os
import asyncio
import json

from unittest.mock import MagicMock, patch
from collections import Counter
from aiohttp import web
import pytest
from foglamp.services.core import routes
from foglamp.services.core import connect

from foglamp.plugins.storage.common.backup import Backup
from foglamp.plugins.storage.common.restore import Restore
from foglamp.plugins.storage.common import exceptions

from foglamp.services.core.api import backup_restore
from foglamp.common.storage_client.storage_client import StorageClientAsync

__author__ = "Vaibhav Singhal"
__copyright__ = "Copyright (c) 2017 OSIsoft, LLC"
__license__ = "Apache 2.0"
__version__ = "${VERSION}"


@asyncio.coroutine
def mock_coro(*args, **kwargs):
    if len(args) > 0:
        return args[0]
    else:
        return ""


@pytest.allure.feature("unit")
@pytest.allure.story("api", "backup")
class TestBackup:
    """Unit test the Backup functionality
    """
    @pytest.fixture
    def client(self, loop, test_client):
        app = web.Application(loop=loop)
        # fill the routes table
        routes.setup(app)
        return loop.run_until_complete(test_client(app))

    @pytest.mark.parametrize("input_data, expected", [
        (1, "RUNNING"),
        (2, "COMPLETED"),
        (3, "CANCELED"),
        (4, "INTERRUPTED"),
        (5, "FAILED"),
        (6, "RESTORED"),
        (7, "UNKNOWN")
    ])
    def test_get_status(self, input_data, expected):
        assert expected == backup_restore._get_status(input_data)

    @pytest.mark.parametrize("request_params", [
        '',
        '?limit=1',
        '?skip=1',
        '?status=completed',
        '?status=failed',
        '?status=restored&skip=10',
        '?status=running&limit=1',
        '?status=canceled&limit=10&skip=0',
        '?status=interrupted&limit=&skip=',
        '?status=&limit=&skip='
    ])
    async def test_get_backups(self, client, request_params):
        storage_client_mock = MagicMock(StorageClientAsync)
        response = [{'file_name': '1.dump',
                     'id': 1, 'type': '1', 'status': '2',
                     'ts': '2018-02-15 15:18:41.821978+05:30',
                     'exit_code': '0'}]
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'get_all_backups', return_value=mock_coro(response)):
                resp = await client.get('/foglamp/backup{}'.format(request_params))
                assert 200 == resp.status
                result = await resp.text()
                json_response = json.loads(result)
                assert 1 == len(json_response['backups'])
                assert Counter({"id", "date", "status"}) == Counter(json_response['backups'][0].keys())

    @pytest.mark.parametrize("request_params, response_code, response_message", [
        ('?limit=invalid', 400, "Limit must be a positive integer"),
        ('?limit=-1', 400, "Limit must be a positive integer"),
        ('?skip=invalid', 400, "Skip/Offset must be a positive integer"),
        ('?skip=-1', 400, "Skip/Offset must be a positive integer"),
        ('?status=BLA', 400, "'BLA' is not a valid status")
    ])
    async def test_get_backups_bad_data(self, client, request_params, response_code, response_message):
        resp = await client.get('/foglamp/backup{}'.format(request_params))
        assert response_code == resp.status
        assert response_message == resp.reason

    async def test_get_backups_exceptions(self, client):
        with patch.object(connect, 'get_storage_async', return_value=Exception):
            resp = await client.get('/foglamp/backup')
            assert 500 == resp.status
            assert "Internal Server Error" == resp.reason

    async def test_create_backup(self, client):
        async def mock_create():
            return "running_or_failed"

        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'create_backup', return_value=mock_create()):
                resp = await client.post('/foglamp/backup')
                assert 200 == resp.status
                assert '{"status": "running_or_failed"}' == await resp.text()

    async def test_create_backup_exception(self, client):
        with patch.object(connect, 'get_storage_async', return_value=Exception):
            with patch.object(Backup, 'create_backup', return_value=Exception):
                resp = await client.post('/foglamp/backup')
                assert 500 == resp.status
                assert "Internal Server Error" == resp.reason

    async def test_get_backup_details(self, client):
        storage_client_mock = MagicMock(StorageClientAsync)
        response = {'id': 1, 'file_name': '1.dump', 'ts': '2018-02-15 15:18:41.821978+05:30',
                    'status': '2', 'type': '1', 'exit_code': '0'}
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'get_backup_details', return_value=mock_coro(response)):
                resp = await client.get('/foglamp/backup/{}'.format(1))
                assert 200 == resp.status
                result = await resp.text()
                json_response = json.loads(result)
                assert 3 == len(json_response)
                assert Counter({"id", "date", "status"}) == Counter(json_response.keys())

    @pytest.mark.parametrize("input_exception, response_code, response_message", [
        (exceptions.DoesNotExist, 404, "Backup id 8 does not exist"),
        (Exception, 500, "Internal Server Error")
    ])
    async def test_get_backup_details_exceptions(self, client, input_exception, response_code, response_message):
        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'get_backup_details', side_effect=input_exception):
                resp = await client.get('/foglamp/backup/{}'.format(8))
                assert response_code == resp.status
                assert response_message == resp.reason

    async def test_get_backup_details_bad_data(self, client):
        resp = await client.get('/foglamp/backup/{}'.format('BLA'))
        assert 400 == resp.status
        assert "Invalid backup id" == resp.reason

    async def test_delete_backup(self, client):
        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'delete_backup', return_value=mock_coro(None)):
                resp = await client.delete('/foglamp/backup/{}'.format(1))
                assert 200 == resp.status
                result = await resp.text()
                json_response = json.loads(result)
                assert {'message': 'Backup deleted successfully'} == json_response

    @pytest.mark.parametrize("input_exception, response_code, response_message", [
        (exceptions.DoesNotExist, 404, "Backup id 8 does not exist"),
        (Exception, 500, "Internal Server Error")
    ])
    async def test_delete_backup_exceptions(self, client, input_exception, response_code, response_message):
        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'delete_backup', side_effect=input_exception):
                resp = await client.delete('/foglamp/backup/{}'.format(8))
                assert response_code == resp.status
                assert response_message == resp.reason

    async def test_delete_backup_bad_data(self, client):
        resp = await client.delete('/foglamp/backup/{}'.format('BLA'))
        assert 400 == resp.status
        assert "Invalid backup id" == resp.reason

    async def test_get_backup_status(self, client):
        resp = await client.get('/foglamp/backup/status')
        assert 200 == resp.status
        result = await resp.text()
        json_response = json.loads(result)
        assert {'backupStatus': [{'index': 1, 'name': 'RUNNING'},
                                 {'index': 2, 'name': 'COMPLETED'},
                                 {'index': 3, 'name': 'CANCELED'},
                                 {'index': 4, 'name': 'INTERRUPTED'},
                                 {'index': 5, 'name': 'FAILED'},
                                 {'index': 6, 'name': 'RESTORED'}]} == json_response

    @pytest.mark.parametrize("input_exception, response_code, response_message", [
        (ValueError, 400, "Invalid backup id"),
        (exceptions.DoesNotExist, 404, "Backup id 8 does not exist"),
        (Exception, 500, "Internal Server Error")
    ])
    async def test_get_backup_download_exceptions(self, client, input_exception, response_code, response_message):
        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Backup, 'get_backup_details', side_effect=input_exception):
                resp = await client.get('/foglamp/backup/{}/download'.format(8))
                assert response_code == resp.status
                assert response_message == resp.reason

    async def test_get_backup_download(self, client):
        storage_client_mock = MagicMock(StorageClientAsync)
        response = {'id': 1, 'file_name': '/usr/local/foglamp/data/backup/foglamp.db', 'ts': '2018-02-15 15:18:41',
                    'status': '2', 'type': '1'}

        with patch("aiohttp.web.FileResponse", return_value=web.FileResponse(path=os.path.realpath(__file__))) as file_res:
            with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
                with patch.object(Backup, 'get_backup_details', return_value=mock_coro(response)) as patch_backup_detail:
                    with patch('tarfile.open'):
                        resp = await client.get('/foglamp/backup/{}/download'.format(1))
                        assert 200 == resp.status
                        assert 'OK' == resp.reason
                patch_backup_detail.assert_called_once_with(1)
        assert 1 == file_res.call_count


@pytest.allure.feature("unit")
@pytest.allure.story("api", "restore")
class TestRestore:
    """Unit test the Restore functionality"""

    @pytest.fixture
    def client(self, loop, test_client):
        app = web.Application(loop=loop)
        # fill the routes table
        routes.setup(app)
        return loop.run_until_complete(test_client(app))

    async def test_restore_backup(self, client):
        async def mock_restore():
            return "running"

        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Restore, 'restore_backup', return_value=mock_restore()):
                resp = await client.put('/foglamp/backup/{}/restore'.format(1))
                assert 200 == resp.status
                r = await resp.text()
                assert {'status': 'running'} == json.loads(r)

    @pytest.mark.parametrize("backup_id, input_exception, code, message", [
        (8, exceptions.DoesNotExist, 404, "Backup with 8 does not exist"),
        (2, Exception, 500, "Internal Server Error"),
        ('blah', ValueError, 400, 'Invalid backup id')
    ])
    async def test_restore_backup_exceptions(self, client, backup_id, input_exception, code, message):
        storage_client_mock = MagicMock(StorageClientAsync)
        with patch.object(connect, 'get_storage_async', return_value=storage_client_mock):
            with patch.object(Restore, 'restore_backup', side_effect=input_exception):
                resp = await client.put('/foglamp/backup/{}/restore'.format(backup_id))
                assert code == resp.status
                assert message == resp.reason

[((39, 1, 39, 30), 'pytest.allure.feature', 'pytest.allure.feature', ({(39, 23, 39, 29): '"""unit"""'}, {}), "('unit')", False, 'import pytest\n'), ((40, 1, 40, 37), 'pytest.allure.story', 'pytest.allure.story', ({(40, 21, 40, 26): '"""api"""', (40, 28, 40, 36): '"""backup"""'}, {}), "('api', 'backup')", False, 'import pytest\n'), ((224, 1, 224, 30), 'pytest.allure.feature', 'pytest.allure.feature', ({(224, 23, 224, 29): '"""unit"""'}, {}), "('unit')", False, 'import pytest\n'), ((225, 1, 225, 38), 'pytest.allure.story', 'pytest.allure.story', ({(225, 21, 225, 26): '"""api"""', (225, 28, 225, 37): '"""restore"""'}, {}), "('api', 'restore')", False, 'import pytest\n'), ((51, 5, 59, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(51, 29, 51, 51): '"""input_data, expected"""', (51, 53, 59, 5): "[(1, 'RUNNING'), (2, 'COMPLETED'), (3, 'CANCELED'), (4, 'INTERRUPTED'), (5,\n    'FAILED'), (6, 'RESTORED'), (7, 'UNKNOWN')]"}, {}), "('input_data, expected', [(1, 'RUNNING'), (2,\n    'COMPLETED'), (3, 'CANCELED'), (4, 'INTERRUPTED'), (5, 'FAILED'), (6,\n    'RESTORED'), (7, 'UNKNOWN')])", False, 'import pytest\n'), ((63, 5, 74, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(63, 29, 63, 45): '"""request_params"""', (63, 47, 74, 5): "['', '?limit=1', '?skip=1', '?status=completed', '?status=failed',\n    '?status=restored&skip=10', '?status=running&limit=1',\n    '?status=canceled&limit=10&skip=0', '?status=interrupted&limit=&skip=',\n    '?status=&limit=&skip=']"}, {}), "('request_params', ['', '?limit=1', '?skip=1',\n    '?status=completed', '?status=failed', '?status=restored&skip=10',\n    '?status=running&limit=1', '?status=canceled&limit=10&skip=0',\n    '?status=interrupted&limit=&skip=', '?status=&limit=&skip='])", False, 'import pytest\n'), ((90, 5, 96, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(90, 29, 90, 78): '"""request_params, response_code, response_message"""', (90, 80, 96, 5): '[(\'?limit=invalid\', 400, \'Limit must be a positive integer\'), (\'?limit=-1\',\n    400, \'Limit must be a positive integer\'), (\'?skip=invalid\', 400,\n    \'Skip/Offset must be a positive integer\'), (\'?skip=-1\', 400,\n    \'Skip/Offset must be a positive integer\'), (\'?status=BLA\', 400,\n    "\'BLA\' is not a valid status")]'}, {}), '(\'request_params, response_code, response_message\',\n    [(\'?limit=invalid\', 400, \'Limit must be a positive integer\'), (\n    \'?limit=-1\', 400, \'Limit must be a positive integer\'), (\'?skip=invalid\',\n    400, \'Skip/Offset must be a positive integer\'), (\'?skip=-1\', 400,\n    \'Skip/Offset must be a positive integer\'), (\'?status=BLA\', 400,\n    "\'BLA\' is not a valid status")])', False, 'import pytest\n'), ((139, 5, 142, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(139, 29, 139, 79): '"""input_exception, response_code, response_message"""', (139, 81, 142, 5): "[(exceptions.DoesNotExist, 404, 'Backup id 8 does not exist'), (Exception, \n    500, 'Internal Server Error')]"}, {}), "('input_exception, response_code, response_message',\n    [(exceptions.DoesNotExist, 404, 'Backup id 8 does not exist'), (\n    Exception, 500, 'Internal Server Error')])", False, 'import pytest\n'), ((166, 5, 169, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(166, 29, 166, 79): '"""input_exception, response_code, response_message"""', (166, 81, 169, 5): "[(exceptions.DoesNotExist, 404, 'Backup id 8 does not exist'), (Exception, \n    500, 'Internal Server Error')]"}, {}), "('input_exception, response_code, response_message',\n    [(exceptions.DoesNotExist, 404, 'Backup id 8 does not exist'), (\n    Exception, 500, 'Internal Server Error')])", False, 'import pytest\n'), ((195, 5, 199, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(195, 29, 195, 79): '"""input_exception, response_code, response_message"""', (195, 81, 199, 5): "[(ValueError, 400, 'Invalid backup id'), (exceptions.DoesNotExist, 404,\n    'Backup id 8 does not exist'), (Exception, 500, 'Internal Server Error')]"}, {}), "('input_exception, response_code, response_message',\n    [(ValueError, 400, 'Invalid backup id'), (exceptions.DoesNotExist, 404,\n    'Backup id 8 does not exist'), (Exception, 500, 'Internal Server Error')])", False, 'import pytest\n'), ((248, 5, 252, 6), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(248, 29, 248, 72): '"""backup_id, input_exception, code, message"""', (248, 74, 252, 5): "[(8, exceptions.DoesNotExist, 404, 'Backup with 8 does not exist'), (2,\n    Exception, 500, 'Internal Server Error'), ('blah', ValueError, 400,\n    'Invalid backup id')]"}, {}), "('backup_id, input_exception, code, message', [(8,\n    exceptions.DoesNotExist, 404, 'Backup with 8 does not exist'), (2,\n    Exception, 500, 'Internal Server Error'), ('blah', ValueError, 400,\n    'Invalid backup id')])", False, 'import pytest\n'), ((46, 14, 46, 40), 'aiohttp.web.Application', 'web.Application', (), '', False, 'from aiohttp import web\n'), ((48, 8, 48, 25), 'foglamp.services.core.routes.setup', 'routes.setup', ({(48, 21, 48, 24): 'app'}, {}), '(app)', False, 'from foglamp.services.core import routes\n'), ((76, 30, 76, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(76, 40, 76, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((112, 30, 112, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(112, 40, 112, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((127, 30, 127, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(127, 40, 127, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((144, 30, 144, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(144, 40, 144, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((157, 30, 157, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(157, 40, 157, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((171, 30, 171, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(171, 40, 171, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((187, 24, 187, 42), 'json.loads', 'json.loads', ({(187, 35, 187, 41): 'result'}, {}), '(result)', False, 'import json\n'), ((201, 30, 201, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(201, 40, 201, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((209, 30, 209, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(209, 40, 209, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((231, 14, 231, 40), 'aiohttp.web.Application', 'web.Application', (), '', False, 'from aiohttp import web\n'), ((233, 8, 233, 25), 'foglamp.services.core.routes.setup', 'routes.setup', ({(233, 21, 233, 24): 'app'}, {}), '(app)', False, 'from foglamp.services.core import routes\n'), ((240, 30, 240, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(240, 40, 240, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((254, 30, 254, 59), 'unittest.mock.MagicMock', 'MagicMock', ({(254, 40, 254, 58): 'StorageClientAsync'}, {}), '(StorageClientAsync)', False, 'from unittest.mock import MagicMock, patch\n'), ((61, 27, 61, 65), 'foglamp.services.core.api.backup_restore._get_status', 'backup_restore._get_status', ({(61, 54, 61, 64): 'input_data'}, {}), '(input_data)', False, 'from foglamp.services.core.api import backup_restore\n'), ((81, 13, 81, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((103, 13, 103, 79), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((113, 13, 113, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((120, 13, 120, 79), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((130, 13, 130, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((145, 13, 145, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((158, 13, 158, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((172, 13, 172, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((202, 13, 202, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((241, 13, 241, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((255, 13, 255, 89), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((86, 32, 86, 50), 'json.loads', 'json.loads', ({(86, 43, 86, 49): 'result'}, {}), '(result)', False, 'import json\n'), ((121, 17, 121, 78), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((135, 32, 135, 50), 'json.loads', 'json.loads', ({(135, 43, 135, 49): 'result'}, {}), '(result)', False, 'import json\n'), ((146, 17, 146, 88), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((163, 32, 163, 50), 'json.loads', 'json.loads', ({(163, 43, 163, 49): 'result'}, {}), '(result)', False, 'import json\n'), ((173, 17, 173, 83), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((203, 17, 203, 88), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((214, 17, 214, 93), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((256, 17, 256, 85), 'unittest.mock.patch.object', 'patch.object', (), '', False, 'from unittest.mock import MagicMock, patch\n'), ((88, 23, 88, 56), 'collections.Counter', 'Counter', ({(88, 31, 88, 55): "{'id', 'date', 'status'}"}, {}), "({'id', 'date', 'status'})", False, 'from collections import Counter\n'), ((137, 23, 137, 56), 'collections.Counter', 'Counter', ({(137, 31, 137, 55): "{'id', 'date', 'status'}"}, {}), "({'id', 'date', 'status'})", False, 'from collections import Counter\n'), ((246, 48, 246, 61), 'json.loads', 'json.loads', ({(246, 59, 246, 60): 'r'}, {}), '(r)', False, 'import json\n'), ((216, 25, 216, 46), 'unittest.mock.patch', 'patch', ({(216, 31, 216, 45): '"""tarfile.open"""'}, {}), "('tarfile.open')", False, 'from unittest.mock import MagicMock, patch\n'), ((213, 82, 213, 108), 'os.path.realpath', 'os.path.realpath', ({(213, 99, 213, 107): '__file__'}, {}), '(__file__)', False, 'import os\n')]