import base64 import os import random import string import tempfile from typing import Tuple, Any, Generator import pytest from flask import Flask from flask.testing import FlaskClient, FlaskCliRunner from werkzeug.test import Client from server import create_app, register_blueprints, register_error_handlers from server.db import init_db from server.model import User from server.service import user_service def add_test_user() -> Tuple[str, str]: test_username = 'test_' + ''.join( random.choices(string.ascii_letters + string.digits, k=17)).strip() test_password = ''.join( random.choices(string.ascii_letters + string.digits, k=32)).strip() user_service.register(test_username, test_password, User.ROLE_ADMIN, False) return test_username, test_password @pytest.fixture def app() -> Generator[Flask, Any, Any]: """Create and configure a new server_app instance for each test.""" # create a temporary file to isolate the database for each test db_fd, db_path = tempfile.mkstemp(suffix='.db') test_database_uri = 'sqlite:///{}'.format(db_path) # create the server_app with common test config server_app = create_app({ 'TESTING': True, 'SQLALCHEMY_DATABASE_URI': test_database_uri, }) register_blueprints(server_app) register_error_handlers(server_app) # create the database and load test data with server_app.app_context(): init_db() test_username, test_password = add_test_user() server_app.config['test_username'] = test_username server_app.config['test_password'] = test_password # get_db().executescript(_data_sql) yield server_app # close and remove the temporary database os.close(db_fd) os.unlink(db_path) @pytest.fixture def client(app: Flask) -> FlaskClient: """A test client for the app.""" return app.test_client() @pytest.fixture def runner(app: Flask) -> FlaskCliRunner: """A test runner for the app's Click commands.""" return app.test_cli_runner() class AuthActions(object): def __init__(self, client: Client, username: str = "", password: str = "") -> None: self._client = client self.username: str = username self.password: str = password self.token: str = "" def configure(self, username: str, password: str) -> Any: self.username = username self.password = password return self def login(self) -> Any: auth_header = self.get_authorization_header_basic() result = self._client.post( '/auth/login', headers={ auth_header[0]: auth_header[1] } ) self.token = result.json['token'] return result def bump(self) -> Any: auth_header = self.get_authorization_header_token() return self._client.post( '/auth/bump', headers={ auth_header[0]: auth_header[1] } ) def logout(self) -> Any: auth_header = self.get_authorization_header_token() return self._client.post( '/auth/logout', headers={ auth_header[0]: auth_header[1] } ) def get_authorization_header_basic(self) -> Tuple[str, str]: credentials = base64.b64encode( '{}:{}'.format(self.username, self.password).encode('utf8')) \ .decode('utf8').strip() return 'Authorization', 'Basic {}'.format(credentials) def get_authorization_header_token(self) -> Tuple[str, str]: credentials = base64.b64encode( '{}:{}'.format(self.username, self.token).encode('utf8')) \ .decode('utf8').strip() return 'X-Auth-Token', '{}'.format(credentials) def __enter__(self) -> 'AuthActions': return self.login() def __exit__(self, type: Any, value: Any, traceback: Any) -> None: self.logout() @pytest.fixture def auth(client: Client) -> AuthActions: return AuthActions(client, client.application.config.get('test_username'), client.application.config.get('test_password'))