diff --git a/amanuensis/backend/__init__.py b/amanuensis/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amanuensis/backend/user.py b/amanuensis/backend/user.py new file mode 100644 index 0000000..9a28e67 --- /dev/null +++ b/amanuensis/backend/user.py @@ -0,0 +1,45 @@ +""" +User query interface +""" + +import re +import uuid + +from amanuensis.db import DbContext, User +from amanuensis.errors import ArgumentError + + +def create_user( + db: DbContext, + username: str, + password: str, + display_name: str, + email: str, + is_site_admin: bool) -> User: + """ + Create a new user. + """ + # Verify username + if len(username) < 3 or len(username) > 32: + raise ArgumentError('Username must be between 3 and 32 characters') + if re.match(r'^[0-9-_]*$', username): + raise ArgumentError('Username must contain a letter') + if not re.match(r'^[A-Za-z0-9-_]*$', username): + raise ArgumentError('Username may only contain alphanumerics, dash, and underscore') + # Verify password + if not password: + raise ArgumentError('Password must be provided') + # If display name is not provided, use the username + if not display_name.strip(): + display_name = username + + new_user = User( + username=username, + password=password, + display_name=display_name, + email=email, + is_site_admin=is_site_admin, + ) + db.session.add(new_user) + db.session.commit() + return new_user diff --git a/amanuensis/errors.py b/amanuensis/errors.py index 861d26b..dca7c6f 100644 --- a/amanuensis/errors.py +++ b/amanuensis/errors.py @@ -1,32 +1,10 @@ +""" +Submodule of custom exception types +""" + class AmanuensisError(Exception): - """Base class for exceptions in amanuensis""" - - -class MissingConfigError(AmanuensisError): - """A config file is missing that was expected to be present""" - def __init__(self, path): - super().__init__("A config file or directory was expected to " - f"exist, but could not be found: {path}") - - -class ConfigAlreadyExistsError(AmanuensisError): - """Attempted to create a config, but it already exists""" - def __init__(self, path): - super().__init__("Attempted to create a config, but it already " - f"exists: {path}") - - -class MalformedConfigError(AmanuensisError): - """A config file could not be read and parsed""" - - -class ReadOnlyError(AmanuensisError): - """A config was edited in readonly mode""" + """Base class for exceptions in amanuensis""" class ArgumentError(AmanuensisError): - """An internal call was made with invalid arguments""" - - -class IndexMismatchError(AmanuensisError): - """An id was obtained from an index, but the object doesn't exist""" + """An internal call was made with invalid arguments""" diff --git a/tests/test_db.py b/tests/test_db.py index bd5d1a0..9e551b0 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -2,23 +2,47 @@ import pytest from sqlalchemy import func from amanuensis.db import * +import amanuensis.backend.user as userq +from amanuensis.errors import ArgumentError @pytest.fixture -def session(): +def db(): db = DbContext('sqlite:///:memory:', debug=True) db.create_all() - return db.session + return db -def test_create(session): +def test_create(db): """Simple test that the database creates fine from scratch.""" - assert session.query(func.count(User.id)).scalar() == 0 - assert session.query(func.count(Lexicon.id)).scalar() == 0 - assert session.query(func.count(Membership.id)).scalar() == 0 - assert session.query(func.count(Character.id)).scalar() == 0 - assert session.query(func.count(Article.id)).scalar() == 0 - assert session.query(func.count(ArticleIndex.id)).scalar() == 0 - assert session.query(func.count(ArticleIndexRule.id)).scalar() == 0 - assert session.query(func.count(ArticleContentRule.id)).scalar() == 0 - assert session.query(func.count(Post.id)).scalar() == 0 + assert db.session.query(func.count(User.id)).scalar() == 0 + assert db.session.query(func.count(Lexicon.id)).scalar() == 0 + assert db.session.query(func.count(Membership.id)).scalar() == 0 + assert db.session.query(func.count(Character.id)).scalar() == 0 + assert db.session.query(func.count(Article.id)).scalar() == 0 + assert db.session.query(func.count(ArticleIndex.id)).scalar() == 0 + assert db.session.query(func.count(ArticleIndexRule.id)).scalar() == 0 + assert db.session.query(func.count(ArticleContentRule.id)).scalar() == 0 + assert db.session.query(func.count(Post.id)).scalar() == 0 + + +def test_create_user(db): + """New user creation""" + kwargs = { + 'username': 'username', + 'password': 'password', + 'display_name': 'User Name', + 'email': 'user@example.com', + 'is_site_admin': False + } + + with pytest.raises(ArgumentError): + userq.create_user(db, **{**kwargs, 'username': 'user name'}) + + with pytest.raises(ArgumentError): + userq.create_user(db, **{**kwargs, 'password': None}) + + new_user = userq.create_user(db, **kwargs) + assert new_user + assert new_user.id is not None + assert new_user.created is not None