diff --git a/src/iam/router.py b/src/iam/router.py index 32ad7ee..d428e80 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -272,7 +272,6 @@ async def create_new_permission( ): raise ConflictException(message="Permission already exists") response = { - "id": perm_model.id, "service_name": perm_model.service_name, "resource": perm_model.resource, "action": perm_model.action, diff --git a/src/iam/schemas.py b/src/iam/schemas.py index e46587b..fca76de 100644 --- a/src/iam/schemas.py +++ b/src/iam/schemas.py @@ -3,6 +3,7 @@ Pydantic models for the IAM module Models follow the nomenclature of: - Sub-models: "Schema" +- Mixins: "Mixin" - Models: "" ie "IAMGetGroupPermissionsResponse" """ @@ -10,14 +11,10 @@ from typing import Optional, Annotated from pydantic import EmailStr, ConfigDict, Field -from src.schemas import ( - CustomBaseModel, - ServiceIDMixin, - OrgIDMixin, - UserIDMixin, - PermIDMixin, - GroupIDMixin, -) +from src.service.schemas import ServiceIDMixin +from src.organisation.schemas import OrgIDMixin +from src.schemas import CustomBaseModel +from src.user.schemas import UserIDMixin class UserSchema(CustomBaseModel): @@ -32,7 +29,6 @@ class UserSchema(CustomBaseModel): class PermissionSchema(CustomBaseModel): model_config = ConfigDict(from_attributes=True, extra="ignore") - id: int service_name: str resource: str action: str @@ -43,6 +39,14 @@ class GroupSchema(CustomBaseModel): name: str +class GroupIDMixin(CustomBaseModel): + group_id: int = Field(gt=0) + + +class PermIDMixin(CustomBaseModel): + permission_id: int = Field(gt=0) + + class IAMGetGroupPermissionsResponse(CustomBaseModel): permissions: list[PermissionSchema] diff --git a/src/organisation/router.py b/src/organisation/router.py index 8b09544..3f4fcbc 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -265,7 +265,7 @@ async def get_users(org_model: org_model_root_claim_query_dependency): Returns a list of the email addresses of all users of the organisation. """ return { - "users": [{"email": user.email, "id": user.id} for user in org_model.user_rel], + "users": [user.email for user in org_model.user_rel], "organisation": org_model, } @@ -292,9 +292,8 @@ async def get_users(org_model: org_model_root_claim_query_dependency): ) async def add_user_to_org( db: db_dependency, - org_model: org_model_body_dependency, + org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, - su: super_admin_dependency, request_model: OrgPostUserRequest, ): """ diff --git a/src/organisation/schemas.py b/src/organisation/schemas.py index 2d8c1b9..9f120f3 100644 --- a/src/organisation/schemas.py +++ b/src/organisation/schemas.py @@ -3,19 +3,25 @@ Pydantic models for organisation module Models follow the nomenclature of: - Sub-models: "Schema" +- Mixins: "Mixin" - Models: "" ie "OrgPostOrgRequest" """ from typing import Optional -from pydantic import EmailStr, ConfigDict +from pydantic import EmailStr, ConfigDict, Field -from src.schemas import CustomBaseModel, OrgIDMixin, UserIDMixin +from src.schemas import CustomBaseModel from src.contact.schemas import ContactModel +from src.user.schemas import UserIDMixin from src.organisation.constants import Status, ContactType +class OrgIDMixin(CustomBaseModel): + organisation_id: int = Field(gt=0) + + class Questionnaire(CustomBaseModel): question_one: Optional[str] = None question_two: Optional[str] = None @@ -92,7 +98,7 @@ class OrgPatchRootResponse(CustomBaseModel): class OrgGetUserResponse(CustomBaseModel): - users: list[dict[str, str | int]] + users: list[str] organisation: OrgSchema diff --git a/src/schemas.py b/src/schemas.py index e281fd5..484031b 100644 --- a/src/schemas.py +++ b/src/schemas.py @@ -6,7 +6,7 @@ Exports: - ResourceName """ -from pydantic import BaseModel, Field +from pydantic import BaseModel from typing import Optional @@ -19,24 +19,3 @@ class ResourceName(CustomBaseModel): organisation: str resource: str instance: Optional[str] = None - - -### Mixins ### -class OrgIDMixin(CustomBaseModel): - organisation_id: int = Field(gt=0) - - -class GroupIDMixin(CustomBaseModel): - group_id: int = Field(gt=0) - - -class PermIDMixin(CustomBaseModel): - permission_id: int = Field(gt=0) - - -class ServiceIDMixin(CustomBaseModel): - service_id: int = Field(gt=0) - - -class UserIDMixin(CustomBaseModel): - user_id: int = Field(gt=0) diff --git a/src/service/schemas.py b/src/service/schemas.py index 531951f..47d47f5 100644 --- a/src/service/schemas.py +++ b/src/service/schemas.py @@ -3,12 +3,17 @@ Pydantic models for service module Models follow the nomenclature of: - Sub-models: "Schema" +- Mixins: "Mixin" - Models: "" ie "ServiceGetServiceResponse" """ -from pydantic import ConfigDict +from pydantic import ConfigDict, Field -from src.schemas import CustomBaseModel, ServiceIDMixin +from src.schemas import CustomBaseModel + + +class ServiceIDMixin(CustomBaseModel): + service_id: int = Field(gt=0) class ServiceSchema(CustomBaseModel): diff --git a/src/user/dependencies.py b/src/user/dependencies.py index b2f2152..9d2fe02 100644 --- a/src/user/dependencies.py +++ b/src/user/dependencies.py @@ -15,7 +15,7 @@ from src.user.models import User from src.auth.service import claims_dependency from src.database import db_dependency -from src.schemas import UserIDMixin +from src.user.schemas import UserIDMixin async def get_user_model_claims(claims: claims_dependency, db: db_dependency): diff --git a/src/user/schemas.py b/src/user/schemas.py index 114239f..74b58dd 100644 --- a/src/user/schemas.py +++ b/src/user/schemas.py @@ -3,9 +3,13 @@ Pydantic models for the user module """ from typing import Optional -from pydantic import EmailStr +from pydantic import Field, EmailStr -from src.schemas import CustomBaseModel, OrgIDMixin +from src.schemas import CustomBaseModel + + +class UserIDMixin(CustomBaseModel): + user_id: int = Field(gt=0) class OIDCClaims(CustomBaseModel): @@ -48,7 +52,13 @@ class UserResponse(CustomBaseModel): groups: Optional[dict[str, list[dict[str, str | int]]]] = None -class UserPostInvitationRequest(OrgIDMixin): +class OrgResponse(CustomBaseModel): + org_id: int + name: str + + +class UserPostInvitationRequest(CustomBaseModel): + organisation_id: int user_email: EmailStr diff --git a/test/pytest.toml b/test/pytest.toml deleted file mode 100644 index 4b2b03b..0000000 --- a/test/pytest.toml +++ /dev/null @@ -1,12 +0,0 @@ -[tool.pytest] -markers = [ - "iam_module", - "org_module", - "service_module", - "user_module", - "auth", - "root_user", - "super_admin", - "user", - "preapproval" -] \ No newline at end of file diff --git a/test/test_auth_approval.py b/test/test_auth_approval.py index 8bc73d9..69c8a25 100644 --- a/test/test_auth_approval.py +++ b/test/test_auth_approval.py @@ -12,12 +12,6 @@ from src.user.models import User from src.iam.models import Group -pytestmark = [ - pytest.mark.auth, - pytest.mark.preapproval, -] - - @pytest.fixture(autouse=True) def set_org_partial(db_session): org_model = db_session.get(Org, 1) diff --git a/test/test_auth_general.py b/test/test_auth_general.py index 7ee2070..0547824 100644 --- a/test/test_auth_general.py +++ b/test/test_auth_general.py @@ -7,11 +7,6 @@ from src.organisation.models import Organisation as Org from src.user.models import User -pytestmark = [ - pytest.mark.auth, -] - - @pytest.mark.anyio async def test_get_org_auth_root_su(default_client: AsyncClient, db_session): # If a super admin can access a resource when not the root user diff --git a/test/test_auth_root.py b/test/test_auth_root.py index c53b42c..e67bc6a 100644 --- a/test/test_auth_root.py +++ b/test/test_auth_root.py @@ -11,12 +11,6 @@ from src.user.models import User from src.iam.models import Group -pytestmark = [ - pytest.mark.auth, - pytest.mark.root_user, -] - - @pytest.fixture(autouse=True) def add_second_org(db_session): db_session.add( @@ -77,6 +71,26 @@ async def test_get_org_users_auth_root(no_su_client: AsyncClient): assert "Must be the org's root user" in resp.json()["detail"] +@pytest.mark.anyio +async def test_post_org_user_auth_root(no_su_client: AsyncClient, db_session): + db_session.add( + User( + email="user@test.org", + first_name="User", + last_name="Test", + oidc_id="abcd-efgh-ijkl-1234", + ) + ) + db_session.flush() + + resp = await no_su_client.post( + "/org/user", json={"organisation_id": 2, "user_id": 2} + ) + assert resp.status_code != 422 + assert resp.status_code == 401 + assert "Must be the org's root user" in resp.json()["detail"] + + @pytest.mark.anyio async def test_get_org_groups_auth_root(no_su_client: AsyncClient): resp = await no_su_client.get("/org/groups?org_id=2") diff --git a/test/test_auth_su.py b/test/test_auth_su.py index 2e438fb..18319a4 100644 --- a/test/test_auth_su.py +++ b/test/test_auth_su.py @@ -10,12 +10,6 @@ from src.organisation.models import OrgUsers from src.user.models import User -pytestmark = [ - pytest.mark.auth, - pytest.mark.super_admin, -] - - @pytest.mark.anyio async def test_get_user_auth_su(no_su_client: AsyncClient): resp = await no_su_client.get("/user/?user_id=1") @@ -73,7 +67,7 @@ async def test_post_service_auth_su(no_su_client: AsyncClient): @pytest.mark.anyio -async def test_post_perm_auth_su(no_su_client: AsyncClient, db_session): +async def test_post_perm_success(no_su_client: AsyncClient, db_session): resp = await no_su_client.post( "/iam/permission", json={"service_id": 1, "resource": "test_resource", "action": "create"}, @@ -81,23 +75,3 @@ async def test_post_perm_auth_su(no_su_client: AsyncClient, db_session): assert resp.status_code != 422 assert resp.status_code == 401 assert resp.json()["detail"] == "Must be super admin" - - -@pytest.mark.anyio -async def test_post_org_user_auth_su(no_su_client: AsyncClient, db_session): - db_session.add( - User( - email="user@test.org", - first_name="User", - last_name="Test", - oidc_id="abcd-efgh-ijkl-1234", - ) - ) - db_session.flush() - - resp = await no_su_client.post( - "/org/user", json={"organisation_id": 1, "user_id": 2} - ) - assert resp.status_code != 422 - assert resp.status_code == 401 - assert "Must be super admin" in resp.json()["detail"] diff --git a/test/test_auth_user.py b/test/test_auth_user.py index 99f5579..3cd66d0 100644 --- a/test/test_auth_user.py +++ b/test/test_auth_user.py @@ -6,12 +6,6 @@ import pytest from httpx import AsyncClient -pytestmark = [ - pytest.mark.auth, - pytest.mark.user, -] - - @pytest.mark.anyio async def test_get_self_db_auth_user(no_user_client: AsyncClient): resp = await no_user_client.get("/user/self/db") diff --git a/test/test_iam.py b/test/test_iam.py index 591e789..1599d7e 100644 --- a/test/test_iam.py +++ b/test/test_iam.py @@ -10,11 +10,6 @@ from src.iam.models import Group from .conftest import generate_query_and_status -pytestmark = [ - pytest.mark.iam_module, -] - - @pytest.mark.anyio async def test_post_act_on_resource_endpoint_success(default_client: AsyncClient): body = { @@ -137,18 +132,14 @@ async def test_act_on_resource_logic( @pytest.mark.anyio async def test_get_group_permissions_success(default_client: AsyncClient): resp = await default_client.get("/iam/group/permissions?org_id=1&group_id=1") - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "permissions" in data assert isinstance(data["permissions"], list) - - permission = data["permissions"][0] - assert permission["id"] == 1 - assert permission["service_name"] == "Test Service" - assert permission["resource"] == "test_resource" - assert permission["action"] == "read" + assert data["permissions"][0]["service_name"] == "Test Service" + assert data["permissions"][0]["resource"] == "test_resource" + assert data["permissions"][0]["action"] == "read" @pytest.mark.parametrize( @@ -195,18 +186,15 @@ async def test_get_group_permissions_mismatch( @pytest.mark.anyio async def test_get_group_users_success(default_client: AsyncClient): resp = await default_client.get("/iam/group/users?org_id=1&group_id=1") - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "users" in data assert isinstance(data["users"], list) - - user = data["users"][0] - assert user["id"] == 1 - assert user["first_name"] == "Admin" - assert user["last_name"] == "Test" - assert user["email"] == "admin@test.com" + assert data["users"][0]["id"] == 1 + assert data["users"][0]["first_name"] == "Admin" + assert data["users"][0]["last_name"] == "Test" + assert data["users"][0]["email"] == "admin@test.com" @pytest.mark.parametrize( @@ -255,10 +243,9 @@ async def test_post_group_success(default_client: AsyncClient): resp = await default_client.post( "/iam/group", json={"name": "New Group", "organisation_id": 1} ) - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "group" in data assert isinstance(data["group"], dict) assert data["group"]["name"] == "New Group" @@ -311,10 +298,9 @@ async def test_put_group_perm_success(default_client: AsyncClient, db_session): "/iam/group/permission", json={"permission_id": 1, "group_id": 2, "organisation_id": 1}, ) - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "group" in data assert isinstance(data["group"], dict) assert data["group"]["name"] == "Test Group Two" @@ -322,12 +308,9 @@ async def test_put_group_perm_success(default_client: AsyncClient, db_session): assert "permissions" in data assert isinstance(data["permissions"], list) - - permission = data["permissions"][0] - assert permission["id"] == 1 - assert permission["service_name"] == "Test Service" - assert permission["resource"] == "test_resource" - assert permission["action"] == "read" + assert data["permissions"][0]["service_name"] == "Test Service" + assert data["permissions"][0]["resource"] == "test_resource" + assert data["permissions"][0]["action"] == "read" @pytest.mark.parametrize( @@ -447,10 +430,9 @@ async def test_put_group_user_success(default_client: AsyncClient, db_session): resp = await default_client.put( "/iam/group/user", json={"user_id": 2, "group_id": 1, "organisation_id": 1} ) - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "group" in data assert isinstance(data["group"], dict) assert data["group"]["name"] == "Test Group" @@ -458,12 +440,10 @@ async def test_put_group_user_success(default_client: AsyncClient, db_session): assert "users" in data assert isinstance(data["users"], list) - - user = data["users"][1] - assert user["id"] == 2 - assert user["first_name"] == "User" - assert user["last_name"] == "Test" - assert user["email"] == "user@test.org" + assert data["users"][1]["id"] == 2 + assert data["users"][1]["first_name"] == "User" + assert data["users"][1]["last_name"] == "Test" + assert data["users"][1]["email"] == "user@test.org" @pytest.mark.parametrize( @@ -534,12 +514,9 @@ async def test_get_permissions_success(default_client: AsyncClient): assert resp.status_code == 200 assert "permissions" in data assert isinstance(data["permissions"], list) - - permission = data["permissions"][0] - assert permission["id"] == 1 - assert permission["service_name"] == "Test Service" - assert permission["resource"] == "test_resource" - assert permission["action"] == "read" + assert data["permissions"][0]["service_name"] == "Test Service" + assert data["permissions"][0]["resource"] == "test_resource" + assert data["permissions"][0]["action"] == "read" @pytest.mark.parametrize( @@ -560,14 +537,10 @@ async def test_post_perm_success(default_client: AsyncClient, db_session): "/iam/permission", json={"service_id": 1, "resource": "test_resource", "action": "create"}, ) - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "permission" in data - assert isinstance(data["permission"], dict) - - assert data["permission"]["id"] == 2 assert data["permission"]["service_name"] == "Test Service" assert data["permission"]["resource"] == "test_resource" assert data["permission"]["action"] == "create" @@ -651,12 +624,9 @@ async def test_post_perm_search_success(default_client: AsyncClient, db_session, assert resp.status_code == 200 assert "permissions" in data assert isinstance(data["permissions"], list) - - permission = data["permissions"][0] - assert permission["id"] == 1 - assert permission["service_name"] == "Test Service" - assert permission["resource"] == "test_resource" - assert permission["action"] == "read" + assert data["permissions"][0]["service_name"] == "Test Service" + assert data["permissions"][0]["resource"] == "test_resource" + assert data["permissions"][0]["action"] == "read" @pytest.mark.parametrize( diff --git a/test/test_organisation.py b/test/test_organisation.py index f852100..a8708ea 100644 --- a/test/test_organisation.py +++ b/test/test_organisation.py @@ -10,26 +10,18 @@ from src.user.models import User from .conftest import generate_query_and_status -pytestmark = [ - pytest.mark.org_module, -] - - @pytest.mark.anyio async def test_get_org_success(default_client: AsyncClient): resp = await default_client.get("/org?org_id=1") data = resp.json() assert resp.status_code == 200 - assert data["id"] == 1 assert data["name"] == "Test Org" - assert data["status"] == "approved" assert data["root_user"] == "admin@test.com" assert data["billing_contact"] == "billing@test.org" assert data["owner_contact"] == "owner@test.org" assert data["security_contact"] == "security@test.org" - assert "intake_questionnaire" in data - assert isinstance(data["intake_questionnaire"], dict) + assert data["status"] == "approved" @pytest.mark.parametrize( @@ -94,11 +86,10 @@ async def test_patch_org_questionnaire_partial_success( data = resp.json() assert resp.status_code == 200 - assert data["name"] == "Test Org" - assert data["status"] == "partial" assert "intake_questionnaire" in data - assert isinstance(data["intake_questionnaire"], dict) + assert data["name"] == "Test Org" assert data["intake_questionnaire"]["question_one"] == "new answer one" + assert data["status"] == "partial" assert data["intake_questionnaire"]["question_two"] == "answer two" assert data["intake_questionnaire"]["question_three"] is None @@ -163,11 +154,10 @@ async def test_patch_org_questionnaire_submit_success( data = resp.json() assert resp.status_code == 200 - assert data["name"] == "Test Org" - assert data["status"] == "submitted" assert "intake_questionnaire" in data - assert isinstance(data["intake_questionnaire"], dict) + assert data["name"] == "Test Org" assert data["intake_questionnaire"]["question_one"] == "new answer one" + assert data["status"] == "submitted" assert data["intake_questionnaire"]["question_two"] == "answer two" assert data["intake_questionnaire"]["question_three"] is None @@ -213,15 +203,10 @@ async def test_get_org_users_success(default_client: AsyncClient): data = resp.json() assert resp.status_code == 200 - assert "users" in data assert isinstance(data["users"], list) assert len(data["users"]) == 1 - - user = data["users"][0] - assert isinstance(user, dict) - assert user["email"] == "admin@test.com" - assert user["id"] == 1 + assert data["users"][0] == "admin@test.com" assert "organisation" in data assert data["organisation"]["name"] == "Test Org" @@ -255,11 +240,9 @@ async def test_post_org_user_success(default_client: AsyncClient, db_session): resp = await default_client.post( "/org/user", json={"organisation_id": 1, "user_id": 2} ) - - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "users" in data assert isinstance(data["users"], list) assert "user@test.org" in data["users"] @@ -312,10 +295,9 @@ async def test_patch_org_root_user_success(default_client: AsyncClient, db_sessi resp = await default_client.patch( "/org/root_user", json={"organisation_id": 1, "user_id": 2} ) - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert data["name"] == "Test Org" assert data["root_user_email"] == "user@test.org" @@ -377,10 +359,9 @@ async def test_patch_org_root_user_non_member(default_client: AsyncClient, db_se @pytest.mark.anyio async def test_get_org_groups_success(default_client: AsyncClient): resp = await default_client.get("/org/groups?org_id=1") - assert resp.status_code == 200 - data = resp.json() + assert resp.status_code == 200 assert "groups" in data assert isinstance(data["groups"], list) assert "Test Group" in data["groups"] @@ -408,10 +389,6 @@ async def test_get_org_contact_success(default_client: AsyncClient, contact_type assert resp.status_code == 200 - assert "organisation" in data - assert data["organisation"]["id"] == 1 - assert data["organisation"]["name"] == "Test Org" - attributes = [ "email", "first_name", @@ -482,39 +459,9 @@ async def test_patch_org_contact_success( "/org/contact", json={"organisation_id": 1, "contact_type": "billing", key: value}, ) - assert resp.status_code == 200 - data = resp.json() - assert "organisation" in data - assert data["organisation"]["id"] == 1 - assert data["organisation"]["name"] == "Test Org" - - attributes = [ - "email", - "first_name", - "last_name", - "phonenumber", - "vat_number", - "address", - ] - - for attribute in attributes: - assert attribute in data["contact"] - - address_attributes = [ - "post_office_box_number", - "street_address", - "street_address_line_2", - "locality", - "address_region", - "country_code", - "postal_code", - ] - - for attribute in address_attributes: - assert attribute in data["contact"]["address"] - + assert resp.status_code == 200 if key in data["contact"]: assert data["contact"][key] == value elif key in data["contact"]["address"]: diff --git a/test/test_service.py b/test/test_service.py index 1e19a40..bd8fe88 100644 --- a/test/test_service.py +++ b/test/test_service.py @@ -8,11 +8,6 @@ from httpx import AsyncClient from .conftest import generate_query_and_status -pytestmark = [ - pytest.mark.service_module, -] - - @pytest.mark.anyio async def test_get_services_success(default_client: AsyncClient): resp = await default_client.get("/service/?org_id=1") @@ -20,7 +15,6 @@ async def test_get_services_success(default_client: AsyncClient): assert resp.status_code == 200 assert "services" in data - assert isinstance(data["services"], list) assert data["services"][0]["id"] == 1 assert data["services"][0]["name"] == "Test Service" @@ -44,7 +38,6 @@ async def test_post_service_success(default_client: AsyncClient): assert resp.status_code == 200 assert "service" in data - assert isinstance(data["service"], dict) assert data["service"]["name"] == "New Test Service" assert data["service"]["id"] == 2 assert isinstance(data["service"]["api_key"], str) @@ -74,7 +67,6 @@ async def test_patch_service_success(default_client: AsyncClient): assert resp.status_code == 200 assert "service" in data - assert isinstance(data["service"], dict) assert data["service"]["name"] == "Test Service" assert data["service"]["id"] == 1 assert isinstance(data["service"]["api_key"], str) diff --git a/test/test_user.py b/test/test_user.py index 31fd2bb..0266c7a 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -9,11 +9,6 @@ from httpx import AsyncClient from .conftest import generate_query_and_status -pytestmark = [ - pytest.mark.user_module, -] - - @pytest.mark.anyio async def test_get_self_db_success(default_client: AsyncClient): resp = await default_client.get("/user/self/db") @@ -24,9 +19,7 @@ async def test_get_self_db_success(default_client: AsyncClient): assert data["last_name"] == "Test" assert data["email"] == "admin@test.com" assert "organisations" in data - assert isinstance(data["organisations"], list) assert "groups" in data - assert isinstance(data["groups"], dict) @pytest.mark.anyio @@ -39,9 +32,7 @@ async def test_get_user_success(default_client: AsyncClient): assert data["last_name"] == "Test" assert data["email"] == "admin@test.com" assert "organisations" in data - assert isinstance(data["organisations"], list) assert "groups" in data - assert isinstance(data["groups"], dict) @pytest.mark.anyio