From 5e051e7e8919aaaa36c6e303e614ebc39f269b95 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 31 May 2021 12:13:37 -0700 Subject: [PATCH] Add check for duplicate memberships --- amanuensis/backend/membership.py | 12 +++++++++++- amanuensis/db/models.py | 7 ++++++- tests/test_membership.py | 29 +++++++++++++++++++++++------ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/amanuensis/backend/membership.py b/amanuensis/backend/membership.py index 279b14b..c112b01 100644 --- a/amanuensis/backend/membership.py +++ b/amanuensis/backend/membership.py @@ -2,6 +2,8 @@ Membership query interface """ +from sqlalchemy import select, func + from amanuensis.db import DbContext, Membership from amanuensis.errors import ArgumentError @@ -14,7 +16,7 @@ def create( """ Create a new user membership in a lexicon. """ - # Quick argument verification + # Verify argument types are correct if not isinstance(user_id, int): raise ArgumentError('user_id') if not isinstance(lexicon_id, int): @@ -22,6 +24,14 @@ def create( if not isinstance(is_editor, bool): raise ArgumentError('is_editor') + # Verify user has not already joined lexicon + if db( + select(func.count(Membership.id)) + .where(Membership.user_id == user_id) + .where(Membership.lexicon_id == lexicon_id) + ).scalar() > 0: + raise ArgumentError('User is already a member of lexicon') + new_membership = Membership( user_id=user_id, lexicon_id=lexicon_id, diff --git a/amanuensis/db/models.py b/amanuensis/db/models.py index 3f8597d..1aca951 100644 --- a/amanuensis/db/models.py +++ b/amanuensis/db/models.py @@ -2,6 +2,8 @@ Data model SQL definitions """ import enum +import uuid + from sqlalchemy import ( Boolean, Column, @@ -17,7 +19,7 @@ from sqlalchemy import ( TypeDecorator, ) from sqlalchemy.orm import relationship, backref -import uuid +from sqlalchemy.sql.schema import UniqueConstraint from .database import ModelBase @@ -233,6 +235,9 @@ class Membership(ModelBase): Represents a user's participation in a Lexicon game. """ __tablename__ = 'membership' + __table_args__ = ( + UniqueConstraint('user_id', 'lexicon_id'), + ) ################### # Membership keys # diff --git a/tests/test_membership.py b/tests/test_membership.py index 13e64a5..3e76c2a 100644 --- a/tests/test_membership.py +++ b/tests/test_membership.py @@ -1,4 +1,9 @@ -from amanuensis.db import DbContext +import pytest + +from sqlalchemy import select + +from amanuensis.db import * +from amanuensis.errors import ArgumentError import amanuensis.backend.membership as memq @@ -15,10 +20,22 @@ def test_create_membership(db: DbContext, make_user, make_lexicon): assert mem, 'Failed to create membership' # Check that the user and lexicon are mutually visible in the ORM relationships - assert new_user.memberships, 'User memberships not updated' - assert new_lexicon.memberships, 'Lexicon memberships not updated' - assert new_user.memberships[0].lexicon_id == new_lexicon.id - assert new_lexicon.memberships[0].user_id == new_user.id + assert any(map(lambda mem: mem.lexicon == new_lexicon, new_user.memberships)) + assert any(map(lambda mem: mem.user == new_user, new_lexicon.memberships)) # Check that the editor flag was set properly - assert new_lexicon.memberships + editor = db( + select(User) + .join(User.memberships) + .join(Membership.lexicon) + .where(Lexicon.id == new_lexicon.id) + .where(Membership.is_editor == True) + ).scalar_one() + assert editor is not None + assert isinstance(editor, User) + assert editor.id == new_user.id + + # Check that joining twice is not allowed + with pytest.raises(ArgumentError): + mem2 = memq.create(db, new_user.id, new_lexicon.id, False) + assert mem2