""" pytest test fixtures """ import os import pytest import tempfile from typing import Optional from bs4 import BeautifulSoup from flask.testing import FlaskClient from sqlalchemy.orm.session import close_all_sessions from amanuensis.backend import * from amanuensis.config import AmanuensisConfig from amanuensis.db import * from amanuensis.server import get_app @pytest.fixture def db(request) -> DbContext: """Provides a fully-initialized ephemeral database.""" db_fd, db_path = tempfile.mkstemp() db = DbContext(path=db_path, echo=False) db.create_all() def db_teardown(): close_all_sessions() os.close(db_fd) os.unlink(db_path) request.addfinalizer(db_teardown) return db class UserClient: """Class encapsulating user web operations.""" def __init__(self, db: DbContext, user_id: int): self.db = db self.user_id = user_id def login(self, client: FlaskClient): """Log the user in.""" user: Optional[User] = userq.try_from_id(self.db, self.user_id) assert user is not None # Set the user's password so we know what it is later password = os.urandom(8).hex() userq.password_set(self.db, user.username, password) # Log in response = client.get("/auth/login/") assert response.status_code == 200 soup = BeautifulSoup(response.data, features="html.parser") csrf_token = soup.find(id="csrf_token") assert csrf_token is not None response = client.post( "/auth/login/", data={ "username": user.username, "password": password, "csrf_token": csrf_token["value"], }, ) assert 300 <= response.status_code <= 399 def logout(self, client: FlaskClient): """Log the user out.""" response = client.get("/auth/logout/") assert 300 <= response.status_code <= 399 class ObjectFactory: """Factory class.""" def __init__(self, db): self.db = db def user(self, state={"nonce": 1}, **kwargs) -> User: """Factory function for creating users, with valid default values.""" default_kwargs: dict = { "username": f'test_user_{state["nonce"]}', "password": "password", "display_name": None, "email": "user@example.com", "is_site_admin": False, } state["nonce"] += 1 updated_kwargs: dict = {**default_kwargs, **kwargs} return userq.create(self.db, **updated_kwargs) def lexicon(self, state={"nonce": 1}, **kwargs) -> Lexicon: """Factory function for creating lexicons, with valid default values.""" default_kwargs: dict = { "name": f'Test_{state["nonce"]}', "title": None, "prompt": f'Test Lexicon game {state["nonce"]}', } state["nonce"] += 1 updated_kwargs: dict = {**default_kwargs, **kwargs} lex = lexiq.create(self.db, **updated_kwargs) lex.joinable = True self.db.session.commit() return lex def membership(self, **kwargs) -> Membership: """Factory function for creating memberships, with valid default values.""" default_kwargs: dict = { "is_editor": False, } updated_kwargs: dict = {**default_kwargs, **kwargs} return memq.create(self.db, **updated_kwargs) def character(self, state={"nonce": 1}, **kwargs) -> Character: """Factory function for creating characters, with valid default values.""" default_kwargs: dict = { "name": f'Character {state["nonce"]}', "signature": None, } state["nonce"] += 1 updated_kwargs: dict = {**default_kwargs, **kwargs} return charq.create(self.db, **updated_kwargs) def index(self, state={"nonce": ord("A")}, **kwargs) -> ArticleIndex: """Factory function for creating indices, with valid defaut values.""" default_kwargs: dict = { "index_type": IndexType.CHAR, "pattern": chr(state["nonce"]), "logical_order": 0, "display_order": 0, "capacity": None, } state["nonce"] += 1 updated_kwargs = {**default_kwargs, **kwargs} return indq.create(self.db, **updated_kwargs) def client(self, user_id: int) -> UserClient: """Factory function for user test clients.""" return UserClient(self.db, user_id) @pytest.fixture def make(db: DbContext) -> ObjectFactory: """Fixture that provides a factory class.""" return ObjectFactory(db) @pytest.fixture def lexicon_with_editor(make: ObjectFactory): """Shortcut setup for a lexicon game with an editor.""" editor = make.user() assert editor lexicon = make.lexicon() assert lexicon membership = make.membership( user_id=editor.id, lexicon_id=lexicon.id, is_editor=True ) assert membership return (lexicon, editor) class TestConfig(AmanuensisConfig): TESTING = True SECRET_KEY = os.urandom(32).hex() @pytest.fixture def app(db: DbContext): """Provides an application running on top of the test database.""" return get_app(TestConfig(), db)