From caa2e1e8a457963cb033e9eb52d367016ee17413 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 31 May 2021 15:09:26 -0700 Subject: [PATCH 1/3] Add character backend --- amanuensis/backend/character.py | 64 +++++++++++++++++++++++++++++ tests/test_character.py | 73 +++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 amanuensis/backend/character.py create mode 100644 tests/test_character.py 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' -- 2.44.1 From 98898b98fc15e8bfc8fa92a116611839e6c8b229 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 31 May 2021 15:12:33 -0700 Subject: [PATCH 2/3] Reduce test output --- pyproject.toml | 3 +++ tests/conftest.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 44f64a3..8cd59b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,9 @@ SQLAlchemy = "^1.4.12" [tool.poetry.dev-dependencies] pytest = "^5.2" +[tool.pytest.ini_options] +addopts = "--show-capture=log" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/tests/conftest.py b/tests/conftest.py index 3b6ae46..c80c7f8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,7 @@ import amanuensis.backend.user as userq @pytest.fixture def db(): """Provides an initialized database in memory.""" - db = DbContext('sqlite:///:memory:', debug=True) + db = DbContext('sqlite:///:memory:', debug=False) db.create_all() return db -- 2.44.1 From cda4523b666c2649dbd880b6024d4b927a31378b Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 31 May 2021 15:12:43 -0700 Subject: [PATCH 3/3] Add stopgap pytest.ini --- pytest.ini | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..73414ad --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +addopts = --show-capture=log +; pytest should be able to read the pyproject.toml file, but for some reason it +; doesn't seem to be working here. This file is a temporary fix until that gets +; resolved. \ No newline at end of file -- 2.44.1