import sys
import unittest
import base64
from flask import Flask, g
from flask_httpauth import HTTPBasicAuth
import pytest


@pytest.mark.skipif(sys.version_info < (3, 7), reason='requires python3.7')
class HTTPAuthTestCase(unittest.TestCase):
    def setUp(self):
        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'my secret'

        roles_auth = HTTPBasicAuth()

        @roles_auth.verify_password
        async def roles_auth_verify_password(username, password):
            g.anon = False
            if username == 'john':
                return password == 'hello'
            elif username == 'susan':
                return password == 'bye'
            elif username == 'cindy':
                return password == 'byebye'
            elif username == '':
                g.anon = True
                return True
            return False

        @roles_auth.get_user_roles
        async def get_user_roles(auth):
            username = auth.username
            if username == 'john':
                return 'normal'
            elif username == 'susan':
                return ('normal', 'special')
            elif username == 'cindy':
                return None

        @roles_auth.error_handler
        async def error_handler():
            return 'error', 403  # use a custom error status

        @app.route('/')
        async def index():
            return 'index'

        @app.route('/normal')
        @roles_auth.login_required(role='normal')
        async def roles_auth_route_normal():
            return 'normal:' + roles_auth.username()

        @app.route('/special')
        @roles_auth.login_required(role='special')
        async def roles_auth_route_special():
            return 'special:' + roles_auth.username()

        @app.route('/normal-or-special')
        @roles_auth.login_required(role=('normal', 'special'))
        async def roles_auth_route_normal_or_special():
            return 'normal_or_special:' + roles_auth.username()

        @app.route('/normal-and-special')
        @roles_auth.login_required(role=(('normal', 'special'),))
        async def roles_auth_route_normal_and_special():
            return 'normal_and_special:' + roles_auth.username()

        self.app = app
        self.roles_auth = roles_auth
        self.client = app.test_client()

    def test_verify_roles_valid_normal_1(self):
        creds = base64.b64encode(b'susan:bye').decode('utf-8')
        response = self.client.get(
            '/normal', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.data, b'normal:susan')

    def test_verify_roles_valid_normal_2(self):
        creds = base64.b64encode(b'john:hello').decode('utf-8')
        response = self.client.get(
            '/normal', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.data, b'normal:john')

    def test_verify_auth_login_valid_special(self):
        creds = base64.b64encode(b'susan:bye').decode('utf-8')
        response = self.client.get(
            '/special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.data, b'special:susan')

    def test_verify_auth_login_invalid_special_1(self):
        creds = base64.b64encode(b'john:hello').decode('utf-8')
        response = self.client.get(
            '/special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.status_code, 403)
        self.assertTrue('WWW-Authenticate' in response.headers)

    def test_verify_auth_login_invalid_special_2(self):
        creds = base64.b64encode(b'cindy:byebye').decode('utf-8')
        response = self.client.get(
            '/special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.status_code, 403)
        self.assertTrue('WWW-Authenticate' in response.headers)

    def test_verify_auth_login_valid_normal_or_special_1(self):
        creds = base64.b64encode(b'susan:bye').decode('utf-8')
        response = self.client.get(
            '/normal-or-special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.data, b'normal_or_special:susan')

    def test_verify_auth_login_valid_normal_or_special_2(self):
        creds = base64.b64encode(b'john:hello').decode('utf-8')
        response = self.client.get(
            '/normal-or-special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.data, b'normal_or_special:john')

    def test_verify_auth_login_valid_normal_and_special_1(self):
        creds = base64.b64encode(b'susan:bye').decode('utf-8')
        response = self.client.get(
            '/normal-and-special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.data, b'normal_and_special:susan')

    def test_verify_auth_login_valid_normal_and_special_2(self):
        creds = base64.b64encode(b'john:hello').decode('utf-8')
        response = self.client.get(
            '/normal-and-special', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.status_code, 403)
        self.assertTrue('WWW-Authenticate' in response.headers)

    def test_verify_auth_login_invalid_password(self):
        creds = base64.b64encode(b'john:bye').decode('utf-8')
        response = self.client.get(
            '/normal', headers={'Authorization': 'Basic ' + creds})
        self.assertEqual(response.status_code, 403)
        self.assertTrue('WWW-Authenticate' in response.headers)
