Add article backend #5

Merged
Jaculabilis merged 2 commits from tvb/article-backend into develop 2021-06-02 02:02:50 +00:00
5 changed files with 165 additions and 9 deletions

View File

@ -0,0 +1,62 @@
"""
Article query interface
"""
from sqlalchemy import select
from amanuensis.db import *
from amanuensis.errors import ArgumentError
def create(
db: DbContext,
lexicon_id: int,
user_id: int,
character_id: int) -> Article:
"""
Create a new article in a lexicon.
"""
# Verify argument types are correct
if not isinstance(lexicon_id, int):
raise ArgumentError('lexicon_id')
if not isinstance(user_id, int):
raise ArgumentError('user_id')
if character_id is not None and not isinstance(character_id, int):
raise ArgumentError('character_id')
# Check that the user is a member of this lexicon
mem: Membership = db(
select(Membership)
.where(Membership.user_id == user_id)
.where(Membership.lexicon_id == lexicon_id)
).scalar_one_or_none()
if not mem:
raise ArgumentError('User is not a member of lexicon')
# If the character id is provided, check that the user owns the character
# and the character belongs to the lexicon
if character_id is not None:
character: Character = db(
select(Character)
.where(Character.id == character_id)
).scalar_one_or_none()
if not character:
raise ArgumentError('Character does not exist')
if character.user.id != user_id:
raise ArgumentError('Character is owned by the wrong player')
if character.lexicon.id != lexicon_id:
raise ArgumentError('Character belongs to the wrong lexicon')
signature = character.signature
else:
signature = '~Ersatz Scrivener'
new_article = Article(
lexicon_id=lexicon_id,
user_id=user_id,
character_id=character_id,
title='Article title',
body=f'\n\n{signature}',
)
db.session.add(new_article)
db.session.commit()
return new_article

View File

@ -4,6 +4,7 @@ pytest test fixtures
import pytest import pytest
from amanuensis.db import DbContext from amanuensis.db import DbContext
import amanuensis.backend.character as charq
import amanuensis.backend.lexicon as lexiq import amanuensis.backend.lexicon as lexiq
import amanuensis.backend.membership as memq import amanuensis.backend.membership as memq
import amanuensis.backend.user as userq import amanuensis.backend.user as userq
@ -62,12 +63,52 @@ def make_membership(db: DbContext):
@pytest.fixture @pytest.fixture
def lexicon_with_editor(make_user, make_lexicon, make_membership): def make_character(db: DbContext):
"""Provides a factory function for creating characters, with valid default values."""
def character_factory(state={'nonce': 1}, **kwargs):
default_kwargs = {
'name': f'Character {state["nonce"]}',
'signature': None,
}
state['nonce'] += 1
updated_kwargs = {**default_kwargs, **kwargs}
return charq.create(db, **updated_kwargs)
return character_factory
class TestFactory:
def __init__(self, db, **factories):
self.db = db
self.factories = factories
def __getattr__(self, name):
return self.factories[name]
@pytest.fixture
def make(
db: DbContext,
make_user,
make_lexicon,
make_membership,
make_character) -> TestFactory:
"""Fixture that groups all factory fixtures together."""
return TestFactory(
db,
user=make_user,
lexicon=make_lexicon,
membership=make_membership,
character=make_character,
)
@pytest.fixture
def lexicon_with_editor(make):
"""Shortcut setup for a lexicon game with an editor.""" """Shortcut setup for a lexicon game with an editor."""
editor = make_user() editor = make.user()
assert editor assert editor
lexicon = make_lexicon() lexicon = make.lexicon()
assert lexicon assert lexicon
membership = make_membership(user_id=editor.id, lexicon_id=lexicon.id, is_editor=True) membership = make.membership(user_id=editor.id, lexicon_id=lexicon.id, is_editor=True)
assert membership assert membership
return (lexicon, editor) return (lexicon, editor)

53
tests/test_article.py Normal file
View File

@ -0,0 +1,53 @@
import pytest
from amanuensis.db import DbContext
import amanuensis.backend.article as artiq
from amanuensis.errors import ArgumentError
def test_create_article(db: DbContext, make):
"""Test new article creation"""
# Create two users in a shared lexicon
user1 = make.user()
user2 = make.user()
lexicon1 = make.lexicon()
make.membership(user_id=user1.id, lexicon_id=lexicon1.id)
make.membership(user_id=user2.id, lexicon_id=lexicon1.id)
char1_1 = make.character(lexicon_id=lexicon1.id, user_id=user1.id)
char1_2 = make.character(lexicon_id=lexicon1.id, user_id=user2.id)
# Create a lexicon that only one user is in
lexicon2 = make.lexicon()
make.membership(user_id=user2.id, lexicon_id=lexicon2.id)
char2_2 = make.character(lexicon_id=lexicon2.id, user_id=user2.id)
# User cannot create article for another user's character
with pytest.raises(ArgumentError):
artiq.create(db, lexicon1.id, user1.id, char1_2.id)
with pytest.raises(ArgumentError):
artiq.create(db, lexicon1.id, user2.id, char1_1.id)
# User cannot create article for their character in the wrong lexicon
with pytest.raises(ArgumentError):
artiq.create(db, lexicon1.id, user2.id, char2_2.id)
with pytest.raises(ArgumentError):
artiq.create(db, lexicon2.id, user2.id, char1_2.id)
# User cannot create article in a lexicon they aren't in
with pytest.raises(ArgumentError):
artiq.create(db, lexicon2.id, user1.id, char1_1.id)
# User cannot create anonymous articles in a lexicon they aren't in
with pytest.raises(ArgumentError):
artiq.create(db, lexicon2.id, user1.id, character_id=None)
# Users can create character-owned articles
assert artiq.create(db, lexicon1.id, user1.id, char1_1.id)
assert artiq.create(db, lexicon1.id, user2.id, char1_2.id)
assert artiq.create(db, lexicon2.id, user2.id, char2_2.id)
# Users can create anonymous articles
assert artiq.create(db, lexicon1.id, user1.id, character_id=None)
assert artiq.create(db, lexicon1.id, user2.id, character_id=None)
assert artiq.create(db, lexicon2.id, user2.id, character_id=None)

View File

@ -5,7 +5,7 @@ import amanuensis.backend.character as charq
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
def test_create_character(db: DbContext, lexicon_with_editor, make_user): def test_create_character(db: DbContext, lexicon_with_editor, make):
"""Test creating a character.""" """Test creating a character."""
lexicon, user = lexicon_with_editor lexicon, user = lexicon_with_editor
kwargs = { kwargs = {
@ -33,7 +33,7 @@ def test_create_character(db: DbContext, lexicon_with_editor, make_user):
assert char.signature is not None assert char.signature is not None
# User must be in lexicon # User must be in lexicon
new_user = make_user() new_user = make.user()
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, 'user_id': new_user.id}) charq.create(**{**kwargs, 'user_id': new_user.id})

View File

@ -7,12 +7,12 @@ from amanuensis.errors import ArgumentError
import amanuensis.backend.membership as memq import amanuensis.backend.membership as memq
def test_create_membership(db: DbContext, make_user, make_lexicon): def test_create_membership(db: DbContext, make):
"""Test joining a game.""" """Test joining a game."""
# Set up a user and a lexicon # Set up a user and a lexicon
new_user = make_user() new_user = make.user()
assert new_user.id, 'Failed to create user' assert new_user.id, 'Failed to create user'
new_lexicon = make_lexicon() new_lexicon = make.lexicon()
assert new_lexicon.id, 'Failed to create lexicon' assert new_lexicon.id, 'Failed to create lexicon'
# Add the user to the lexicon as an editor # Add the user to the lexicon as an editor