feat: group name unique per org

Instead of group names being wholly unique (enforced by the db), group names are unique within the org (enforced by endpoint logic).
This commit is contained in:
Chris Milne 2026-06-15 11:10:02 +01:00
parent 3f7abc5986
commit dad23733e8
3 changed files with 52 additions and 1 deletions

View file

@ -0,0 +1,34 @@
"""group name unique per org
Revision ID: 98e20aae555c
Revises: b6c8614ef799
Create Date: 2026-06-15 11:05:16.673658
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '98e20aae555c'
down_revision: Union[str, Sequence[str], None] = 'b6c8614ef799'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('group_name_key'), 'group', type_='unique')
op.create_unique_constraint('uniq_group_name_org_id', 'group', ['name', 'org_id'])
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('uniq_group_name_org_id', 'group', type_='unique')
op.create_unique_constraint(op.f('group_name_key'), 'group', ['name'], postgresql_nulls_not_distinct=False)
# ### end Alembic commands ###

View file

@ -56,10 +56,18 @@ class Permission(Base):
class Group(Base): class Group(Base):
__tablename__ = "group" __tablename__ = "group"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True) name = Column(String, nullable=False)
org_id = Column(Integer, ForeignKey("organisation.id", ondelete="CASCADE")) org_id = Column(Integer, ForeignKey("organisation.id", ondelete="CASCADE"))
__table_args__ = (
UniqueConstraint(
"name",
"org_id",
name="uniq_group_name_org_id",
),
)
user_rel = relationship("User", secondary="user_groups", back_populates="group_rel") user_rel = relationship("User", secondary="user_groups", back_populates="group_rel")
org_rel = relationship("Organisation", back_populates="group_rel") org_rel = relationship("Organisation", back_populates="group_rel")

View file

@ -283,6 +283,15 @@ async def test_post_group_conflict(default_client: AsyncClient):
assert resp.status_code == 409 assert resp.status_code == 409
@pytest.mark.anyio
async def test_post_group_non_conflict(default_client: AsyncClient):
resp = await default_client.post(
"/iam/group", json={"organisation_id": 2, "name": "Org One Group"}
)
assert resp.status_code == 201
@pytest.mark.anyio @pytest.mark.anyio
async def test_put_group_perm_success(default_client: AsyncClient): async def test_put_group_perm_success(default_client: AsyncClient):
resp = await default_client.put( resp = await default_client.put(