diff --git a/amanuensis/backend/character.py b/amanuensis/backend/character.py new file mode 100644 index 0000000..c8f04da --- /dev/null +++ b/amanuensis/backend/character.py @@ -0,0 +1,64 @@ +""" +Character query interface +""" + +from sqlalchemy import select, func + +from amanuensis.db import * +from amanuensis.errors import ArgumentError + + +def create( + db: DbContext, + lexicon_id: int, + user_id: int, + name: str, + signature: str) -> Character: + """ + Create a new character for a user. + """ + # 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 not isinstance(name, str): + raise ArgumentError('name') + if signature is not None and not isinstance(signature, str): + raise ArgumentError('signature') + + # Verify character name is valid + if not name.strip(): + raise ArgumentError('Character name cannot be blank') + + # If no signature is provided, use a default signature + if not signature or not signature.strip(): + signature = f'~{name}' + + # 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') + + # Check that this user is below the limit for creating characters + num_user_chars = db( + select(func.count(Character.id)) + .where(Character.lexicon_id == lexicon_id) + .where(Character.user_id == user_id) + ).scalar() + if mem.lexicon.character_limit is not None and num_user_chars >= mem.lexicon.character_limit: + raise ArgumentError('User is at character limit') + + new_character = Character( + lexicon_id=lexicon_id, + user_id=user_id, + name=name, + signature=signature, + ) + db.session.add(new_character) + db.session.commit() + return new_character diff --git a/tests/test_character.py b/tests/test_character.py new file mode 100644 index 0000000..26134ed --- /dev/null +++ b/tests/test_character.py @@ -0,0 +1,73 @@ +import pytest + +from amanuensis.db import * +import amanuensis.backend.character as charq +from amanuensis.errors import ArgumentError + + +def test_create_character(db: DbContext, lexicon_with_editor, make_user): + """Test creating a character.""" + lexicon, user = lexicon_with_editor + kwargs = { + 'db': db, + 'user_id': user.id, + 'lexicon_id': lexicon.id, + 'name': 'Character Name', + 'signature': 'Signature', + } + + # Bad argument types + with pytest.raises(ArgumentError): + charq.create(**{**kwargs, 'name': b'bytestring'}) + with pytest.raises(ArgumentError): + charq.create(**{**kwargs, 'name': None}) + with pytest.raises(ArgumentError): + charq.create(**{**kwargs, 'signature': b'bytestring'}) + + # Bad character name + with pytest.raises(ArgumentError): + charq.create(**{**kwargs, 'name': ' '}) + + # Signature is auto-populated + char = charq.create(**{**kwargs, 'signature': None}) + assert char.signature is not None + + # User must be in lexicon + new_user = make_user() + with pytest.raises(ArgumentError): + charq.create(**{**kwargs, 'user_id': new_user.id}) + + +def test_character_limits(db: DbContext, lexicon_with_editor): + """Test lexicon settings limiting character creation.""" + lexicon: Lexicon + user: User + lexicon, user = lexicon_with_editor + + # Set character limit to one and create a character + lexicon.character_limit = 1 + db.session.commit() + char1 = charq.create(db, lexicon.id, user.id, 'Test Character 1', signature=None) + assert char1.id, 'Failed to create character 1' + + # Creating a second character should fail + with pytest.raises(ArgumentError): + char2 = charq.create(db, lexicon.id, user.id, 'Test Character 2', signature=None) + assert char2 + + # Raising the limit to 2 should allow a second character + lexicon.character_limit = 2 + db.session.commit() + char2 = charq.create(db, lexicon.id, user.id, 'Test Character 2', signature=None) + assert char2.id, 'Failed to create character 2' + + # Creating a third character should fail + with pytest.raises(ArgumentError): + char3 = charq.create(db, lexicon.id, user.id, 'Test Character 3', signature=None) + assert char3 + + # Setting the limit to null should allow a third character + lexicon.character_limit = None + db.session.commit() + char3 = charq.create(db, lexicon.id, user.id, 'Test Character 3', signature=None) + assert char3.id, 'Failed to create character 3'