######################################################################
#
# File: test/unit/v1/test_raw_api.py
#
# Copyright 2019 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
import pytest

from ..test_base import TestBase

from .deps import EncryptionAlgorithm
from .deps import EncryptionKey
from .deps import EncryptionMode
from .deps import EncryptionSetting
from .deps import B2RawHTTPApi
from .deps import B2Http
from .deps import BucketRetentionSetting, RetentionPeriod, RetentionMode
from .deps_exception import UnusableFileName, WrongEncryptionModeForBucketDefault

# Unicode characters for testing filenames.  (0x0394 is a letter Delta.)
TWO_BYTE_UNICHR = chr(0x0394)
CHAR_UNDER_32 = chr(31)
DEL_CHAR = chr(127)


class TestRawAPIFilenames(TestBase):
    """Test that the filename checker passes conforming names and rejects those that don't."""

    def setUp(self):
        self.raw_api = B2RawHTTPApi(B2Http())

    def _should_be_ok(self, filename):
        """Call with test filenames that follow the filename rules.

        :param filename: unicode (or str) that follows the rules
        """
        print(u"Filename \"{0}\" should be OK".format(filename))
        self.assertTrue(self.raw_api.check_b2_filename(filename) is None)

    def _should_raise(self, filename, exception_message):
        """Call with filenames that don't follow the rules (so the rule checker should raise).

        :param filename: unicode (or str) that doesn't follow the rules
        :param exception_message: regexp that matches the exception's detailed message
        """
        print(
            u"Filename \"{0}\" should raise UnusableFileName(\".*{1}.*\").".format(
                filename, exception_message
            )
        )
        with self.assertRaisesRegexp(UnusableFileName, exception_message):
            self.raw_api.check_b2_filename(filename)

    def test_b2_filename_checker(self):
        """Test a conforming and non-conforming filename for each rule.

        From the B2 docs (https://www.backblaze.com/b2/docs/files.html):
        - Names can be pretty much any UTF-8 string up to 1024 bytes long.
        - No character codes below 32 are allowed.
        - Backslashes are not allowed.
        - DEL characters (127) are not allowed.
        - File names cannot start with "/", end with "/", or contain "//".
        - Maximum of 250 bytes of UTF-8 in each segment (part between slashes) of a file name.
        """
        print("test b2 filename rules")

        # Examples from doc:
        self._should_be_ok('Kitten Videos')
        self._should_be_ok(u'\u81ea\u7531.txt')

        # Check length
        # 1024 bytes is ok if the segments are at most 250 chars.
        s_1024 = 4 * (250 * 'x' + '/') + 20 * 'y'
        self._should_be_ok(s_1024)
        # 1025 is too long.
        self._should_raise(s_1024 + u'x', "too long")
        # 1024 bytes with two byte characters should also work.
        s_1024_two_byte = 4 * (125 * TWO_BYTE_UNICHR + u'/') + 20 * u'y'
        self._should_be_ok(s_1024_two_byte)
        # But 1025 bytes is too long.
        self._should_raise(s_1024_two_byte + u'x', "too long")

        # Names with unicode values < 32, and DEL aren't allowed.
        self._should_raise(u'hey' + CHAR_UNDER_32, "contains code.*less than 32")
        # Unicode in the filename shouldn't break the exception message.
        self._should_raise(TWO_BYTE_UNICHR + CHAR_UNDER_32, "contains code.*less than 32")
        self._should_raise(DEL_CHAR, "DEL.*not allowed")

        # Names can't start or end with '/' or contain '//'
        self._should_raise(u'/hey', "not start.*/")
        self._should_raise(u'hey/', "not .*end.*/")
        self._should_raise(u'not//allowed', "contain.*//")

        # Reject segments longer than 250 bytes
        self._should_raise(u'foo/' + 251 * u'x', "segment too long")
        # So a segment of 125 two-byte chars plus one should also fail.
        self._should_raise(u'foo/' + 125 * TWO_BYTE_UNICHR + u'x', "segment too long")


class BucketTestBase:
    @pytest.fixture(autouse=True)
    def init(self, mocker):
        b2_http = mocker.MagicMock()
        self.raw_api = B2RawHTTPApi(b2_http)


class TestUpdateBucket(BucketTestBase):
    """Test updating bucket."""

    def test_assertion_raises(self):
        with pytest.raises(AssertionError):
            self.raw_api.update_bucket('test', 'account_auth_token', 'account_id', 'bucket_id')

    @pytest.mark.parametrize(
        'bucket_type,bucket_info,default_retention', (
            (None, {}, None),
            (
                'allPublic', None,
                BucketRetentionSetting(RetentionMode.COMPLIANCE, RetentionPeriod(years=1))
            ),
        )
    )
    def test_assertion_not_raises(self, bucket_type, bucket_info, default_retention):
        self.raw_api.update_bucket(
            'test',
            'account_auth_token',
            'account_id',
            'bucket_id',
            bucket_type=bucket_type,
            bucket_info=bucket_info,
            default_retention=default_retention,
        )

    @pytest.mark.parametrize(
        'encryption_setting,', (
            EncryptionSetting(
                mode=EncryptionMode.SSE_C,
                algorithm=EncryptionAlgorithm.AES256,
                key=EncryptionKey(b'key', 'key-id')
            ),
            EncryptionSetting(mode=EncryptionMode.UNKNOWN,),
        )
    )
    def test_update_bucket_wrong_encryption(self, encryption_setting):
        with pytest.raises(WrongEncryptionModeForBucketDefault):
            self.raw_api.update_bucket(
                'test',
                'account_auth_token',
                'account_id',
                'bucket_id',
                default_server_side_encryption=encryption_setting,
                bucket_type='allPublic',
            )


class TestCreateBucket(BucketTestBase):
    """Test creating bucket."""

    @pytest.mark.parametrize(
        'encryption_setting,', (
            EncryptionSetting(
                mode=EncryptionMode.SSE_C,
                algorithm=EncryptionAlgorithm.AES256,
                key=EncryptionKey(b'key', 'key-id')
            ),
            EncryptionSetting(mode=EncryptionMode.UNKNOWN,),
        )
    )
    def test_create_bucket_wrong_encryption(self, encryption_setting):

        with pytest.raises(WrongEncryptionModeForBucketDefault):
            self.raw_api.create_bucket(
                'test',
                'account_auth_token',
                'account_id',
                'bucket_id',
                bucket_type='allPrivate',
                bucket_info={},
                default_server_side_encryption=encryption_setting,
            )
