amanuensis/tests/conftest.py

172 lines
5.2 KiB
Python

"""
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)