From e9b272811f883c767a28c396a796b9b7cbe42477 Mon Sep 17 00:00:00 2001 From: luxferre Date: Mon, 8 Jun 2026 16:05:20 +0100 Subject: [PATCH] feat: all unique constraints tested --- src/iam/models.py | 9 +++++++-- src/iam/router.py | 10 ++++++---- src/organisation/router.py | 6 ++++-- src/service/router.py | 6 ++++-- test/test_iam.py | 4 ++++ test/test_organisation.py | 1 + test/test_service.py | 3 ++- 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/iam/models.py b/src/iam/models.py index a35391f..0087abc 100644 --- a/src/iam/models.py +++ b/src/iam/models.py @@ -33,8 +33,13 @@ class Permission(Base): service_id = Column(Integer, ForeignKey("service.id", ondelete="CASCADE")) - UniqueConstraint( - "service_id", "resource", "action", name="uniq_permission_resource_and_action" + __table_args__ = ( + UniqueConstraint( + "service_id", + "resource", + "action", + name="uniq_permission_resource_and_action", + ), ) service_rel = relationship("Service", foreign_keys=[service_id]) diff --git a/src/iam/router.py b/src/iam/router.py index e6715e4..c946464 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -18,7 +18,6 @@ Endpoints: from fastapi import APIRouter, status from sqlalchemy.exc import IntegrityError -from psycopg import errors from src.service.exceptions import ServiceNotFoundException from src.exceptions import ConflictException @@ -264,12 +263,15 @@ async def create_new_permission( if service_model is None: raise ServiceNotFoundException(service_id=request_model.service_id) perm_model = Perm(**request_model.__dict__) + db.add(perm_model) try: - db.add(perm_model) + db.flush() except IntegrityError as e: - if isinstance(e.orig, errors.UniqueViolation): + if ( + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation + or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation + ): raise ConflictException(message="Permission already exists") - db.flush() response = { "service_name": perm_model.service_name, "resource": perm_model.resource, diff --git a/src/organisation/router.py b/src/organisation/router.py index 6d2bb69..498d282 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -20,7 +20,6 @@ from typing import Annotated from fastapi import APIRouter, status from fastapi.params import Query -from psycopg.errors import UniqueViolation from sqlalchemy.exc import IntegrityError from src.contact.schemas import ContactModel @@ -143,7 +142,10 @@ async def create_org( try: db.flush() except IntegrityError as e: - if isinstance(e.orig, UniqueViolation): + if ( + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation + or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation + ): raise ConflictException( message="Organisation with this name already exists" ) diff --git a/src/service/router.py b/src/service/router.py index 69a9369..6f98e7f 100644 --- a/src/service/router.py +++ b/src/service/router.py @@ -9,7 +9,6 @@ Endpoints: """ from fastapi import APIRouter, status -from psycopg.errors import UniqueViolation from sqlalchemy.exc import IntegrityError from src.exceptions import ConflictException @@ -87,7 +86,10 @@ async def register_service( try: db.flush() except IntegrityError as e: - if isinstance(e.orig, UniqueViolation): + if ( + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation + or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation + ): raise ConflictException(message="Service with this name already exists") response = ServiceWithKeySchema(**service_model.__dict__) db.commit() diff --git a/test/test_iam.py b/test/test_iam.py index 05cbabb..b61b4b7 100644 --- a/test/test_iam.py +++ b/test/test_iam.py @@ -549,6 +549,10 @@ async def test_post_perm_success(default_client: AsyncClient, db_session): @pytest.mark.parametrize( "body, expected_status", [ + ( + {"service_id": 1, "resource": "test_resource", "action": "read"}, + 409, + ), # service_id tests ( {"service_id": 42, "resource": "test_resource", "action": "read"}, diff --git a/test/test_organisation.py b/test/test_organisation.py index f0fdf5d..b179689 100644 --- a/test/test_organisation.py +++ b/test/test_organisation.py @@ -49,6 +49,7 @@ async def test_post_org_success(default_client: AsyncClient): @pytest.mark.parametrize( "body, expected_status", [ + ({"name": "Test Org"}, 409), ({"name": 42}, 422), ({}, 422), ({"name": "New Test Org", "intake_questionnaire": {"question_one": 42}}, 422), diff --git a/test/test_service.py b/test/test_service.py index 6bb28b3..a01d25b 100644 --- a/test/test_service.py +++ b/test/test_service.py @@ -46,12 +46,13 @@ async def test_post_service_success(default_client: AsyncClient): @pytest.mark.parametrize( "body, expected_status", [ + ({"name": "Test Service"}, 409), ({"name": 42}, 422), ({}, 422), ], ) @pytest.mark.anyio -async def test_post_services_status_checks( +async def test_post_service_status_checks( default_client: AsyncClient, body: dict[str, str], expected_status: int ): resp = await default_client.post("/service/", json=body)