162 lines
4.9 KiB
Python
162 lines
4.9 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
|
|
|
|
import amanuensis.backend.character as charq
|
|
import amanuensis.backend.lexicon as lexiq
|
|
import amanuensis.backend.membership as memq
|
|
import amanuensis.backend.user as userq
|
|
from amanuensis.config import AmanuensisConfig
|
|
from amanuensis.db import DbContext, User, Lexicon, Membership, Character
|
|
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 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)
|