diff --git a/amanuensis/backend/lexicon.py b/amanuensis/backend/lexicon.py new file mode 100644 index 0000000..089b8e3 --- /dev/null +++ b/amanuensis/backend/lexicon.py @@ -0,0 +1,51 @@ +""" +Lexicon query interface +""" + +import re + +from sqlalchemy import select, func + +from amanuensis.db import DbContext, Lexicon +from amanuensis.errors import ArgumentError + + +RE_ALPHANUM_DASH_UNDER = re.compile(r'^[A-Za-z0-9-_]*$') + + +def create_lexicon( + db: DbContext, + name: str, + title: str, + prompt: str) -> Lexicon: + """ + Create a new lexicon. + """ + # Verify name + if not isinstance(name, str): + raise ArgumentError('Lexicon name must be a string') + if not name.strip(): + raise ArgumentError('Lexicon name must not be blank') + if not RE_ALPHANUM_DASH_UNDER.match(name): + raise ArgumentError('Lexicon name may only contain alphanumerics, dash, and underscore') + + # Verify title + if title is not None and not isinstance(name, str): + raise ArgumentError('Lexicon name must be a string') + + # Verify prompt + if not isinstance(prompt, str): + raise ArgumentError('Lexicon prompt must be a string') + + # Query the db to make sure the lexicon name isn't taken + if db.session.query(func.count(Lexicon.id)).filter(Lexicon.name == name).scalar() > 0: + raise ArgumentError('Lexicon name is already taken') + + new_lexicon = Lexicon( + name=name, + title=title, + prompt=prompt, + ) + db.session.add(new_lexicon) + db.session.commit() + return new_lexicon diff --git a/tests/test_db.py b/tests/test_db.py index 866e408..68ae2c1 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,7 +1,10 @@ +import datetime + import pytest from sqlalchemy import func from amanuensis.db import * +import amanuensis.backend.lexicon as lexiq import amanuensis.backend.user as userq from amanuensis.errors import ArgumentError @@ -62,3 +65,39 @@ def test_create_user(db): user2_kw = {**kwargs, 'username': 'user2', 'display_name': None} user2 = userq.create_user(db, **user2_kw) assert user2.display_name is not None + + +def test_create_lexicon(db): + """Test new game creation.""" + kwargs = { + 'name': 'Test', + 'title': None, + 'prompt': 'A test Lexicon game' + } + # Test name constraints + with pytest.raises(ArgumentError): + lexiq.create_lexicon(db, **{**kwargs, 'name': None}) + with pytest.raises(ArgumentError): + lexiq.create_lexicon(db, **{**kwargs, 'name': ''}) + with pytest.raises(ArgumentError): + lexiq.create_lexicon(db, **{**kwargs, 'name': ' '}) + with pytest.raises(ArgumentError): + lexiq.create_lexicon(db, **{**kwargs, 'name': '..'}) + with pytest.raises(ArgumentError): + lexiq.create_lexicon(db, **{**kwargs, 'name': '\x00'}) + with pytest.raises(ArgumentError): + lexiq.create_lexicon(db, **{**kwargs, 'name': 'space in name'}) + + # Validate that creation populates fields, including timestamps + before = datetime.datetime.utcnow() - datetime.timedelta(seconds=1) + new_lexicon = lexiq.create_lexicon(db, **kwargs) + after = datetime.datetime.utcnow() + datetime.timedelta(seconds=1) + assert new_lexicon + assert new_lexicon.id is not None + assert new_lexicon.created is not None + assert before < new_lexicon.created + assert new_lexicon.created < after + + # No duplicate lexicon names + with pytest.raises(ArgumentError): + duplicate = lexiq.create_lexicon(db, **kwargs)