Compare commits
No commits in common. "294baadcb71ef5c409df297b57df4af1f265abd4" and "bdba903db16aece22731b1855ca3bc8e6d25f44e" have entirely different histories.
294baadcb7
...
bdba903db1
9 changed files with 42 additions and 159 deletions
|
|
@ -21,7 +21,6 @@ from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from src.iam.exceptions import GroupNotFoundException
|
from src.iam.exceptions import GroupNotFoundException
|
||||||
from src.organisation.exceptions import OrgNotFoundException
|
from src.organisation.exceptions import OrgNotFoundException
|
||||||
from src.schemas import GroupSummary, OrgSummary
|
|
||||||
from src.service.exceptions import ServiceNotFoundException
|
from src.service.exceptions import ServiceNotFoundException
|
||||||
from src.exceptions import ConflictException
|
from src.exceptions import ConflictException
|
||||||
from src.database import db_dependency
|
from src.database import db_dependency
|
||||||
|
|
@ -128,11 +127,7 @@ async def get_group_permissions(
|
||||||
):
|
):
|
||||||
if group_model.org_id != org_model.id:
|
if group_model.org_id != org_model.id:
|
||||||
raise UnauthorizedException("Group does not belong to this organization")
|
raise UnauthorizedException("Group does not belong to this organization")
|
||||||
return {
|
return {"permissions": group_model.permission_rel}
|
||||||
"organisation": org_model,
|
|
||||||
"group": group_model,
|
|
||||||
"permissions": group_model.permission_rel,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/group/users", response_model=IAMGetGroupUsersResponse)
|
@router.get("/group/users", response_model=IAMGetGroupUsersResponse)
|
||||||
|
|
@ -142,11 +137,7 @@ async def get_group_users(
|
||||||
):
|
):
|
||||||
if group_model.org_id != org_model.id:
|
if group_model.org_id != org_model.id:
|
||||||
raise UnauthorizedException("Group does not belong to this organization")
|
raise UnauthorizedException("Group does not belong to this organization")
|
||||||
return {
|
return {"users": group_model.user_rel}
|
||||||
"organisation": org_model,
|
|
||||||
"group": group_model,
|
|
||||||
"users": group_model.user_rel,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/group", response_model=IAMPostGroupResponse)
|
@router.post("/group", response_model=IAMPostGroupResponse)
|
||||||
|
|
@ -189,8 +180,7 @@ async def add_group_permission(
|
||||||
|
|
||||||
db.flush()
|
db.flush()
|
||||||
response = IAMPutGroupPermissionResponse(
|
response = IAMPutGroupPermissionResponse(
|
||||||
organisation=OrgSummary(**org_model.__dict__),
|
group=GroupSchema(**group_model.__dict__),
|
||||||
group=GroupSummary(**group_model.__dict__),
|
|
||||||
permissions=group_model.permission_rel,
|
permissions=group_model.permission_rel,
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,6 @@ from src.schemas import (
|
||||||
UserIDMixin,
|
UserIDMixin,
|
||||||
PermIDMixin,
|
PermIDMixin,
|
||||||
GroupIDMixin,
|
GroupIDMixin,
|
||||||
GroupSummary,
|
|
||||||
OrgSummary,
|
|
||||||
UserSummary,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,15 +50,11 @@ class IAMCAoRRequest(CustomBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class IAMGetGroupPermissionsResponse(CustomBaseModel):
|
class IAMGetGroupPermissionsResponse(CustomBaseModel):
|
||||||
organisation: OrgSummary
|
|
||||||
group: GroupSummary
|
|
||||||
permissions: list[PermissionSchema]
|
permissions: list[PermissionSchema]
|
||||||
|
|
||||||
|
|
||||||
class IAMGetGroupUsersResponse(CustomBaseModel):
|
class IAMGetGroupUsersResponse(CustomBaseModel):
|
||||||
organisation: OrgSummary
|
users: list[UserSchema]
|
||||||
group: GroupSummary
|
|
||||||
users: list[UserSummary]
|
|
||||||
|
|
||||||
|
|
||||||
class IAMPostGroupRequest(OrgIDMixin):
|
class IAMPostGroupRequest(OrgIDMixin):
|
||||||
|
|
@ -77,8 +70,7 @@ class IAMPutGroupPermissionRequest(GroupIDMixin, PermIDMixin, OrgIDMixin):
|
||||||
|
|
||||||
|
|
||||||
class IAMPutGroupPermissionResponse(CustomBaseModel):
|
class IAMPutGroupPermissionResponse(CustomBaseModel):
|
||||||
organisation: OrgSummary
|
group: GroupSchema
|
||||||
group: GroupSummary
|
|
||||||
permissions: list[PermissionSchema]
|
permissions: list[PermissionSchema]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,10 +314,7 @@ async def add_user_to_org(
|
||||||
raise ConflictException(message="User already a part of this organisation")
|
raise ConflictException(message="User already a part of this organisation")
|
||||||
org_model.user_rel.append(user_model)
|
org_model.user_rel.append(user_model)
|
||||||
db.flush()
|
db.flush()
|
||||||
response = {
|
response = {"users": [user.email for user in org_model.user_rel]}
|
||||||
"organisation": org_model,
|
|
||||||
"users": [{"id": user.id, "email": user.email} for user in org_model.user_rel],
|
|
||||||
}
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
@ -440,12 +437,7 @@ async def get_org_groups(org_model: org_model_root_claim_query_dependency):
|
||||||
"""
|
"""
|
||||||
Returns a list of the names of all IAM groups created by the organisation.
|
Returns a list of the names of all IAM groups created by the organisation.
|
||||||
"""
|
"""
|
||||||
return {
|
return {"groups": [group.name for group in org_model.group_rel]}
|
||||||
"organisation": org_model,
|
|
||||||
"groups": [
|
|
||||||
{"id": group.id, "name": group.name} for group in org_model.group_rel
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,7 @@ from typing import Optional
|
||||||
|
|
||||||
from pydantic import EmailStr, ConfigDict
|
from pydantic import EmailStr, ConfigDict
|
||||||
|
|
||||||
from src.schemas import (
|
from src.schemas import CustomBaseModel, OrgIDMixin, UserIDMixin
|
||||||
CustomBaseModel,
|
|
||||||
OrgIDMixin,
|
|
||||||
UserIDMixin,
|
|
||||||
GroupSummary,
|
|
||||||
OrgSummary,
|
|
||||||
UserSummary,
|
|
||||||
)
|
|
||||||
from src.contact.schemas import ContactModel
|
from src.contact.schemas import ContactModel
|
||||||
|
|
||||||
from src.organisation.constants import Status, ContactType
|
from src.organisation.constants import Status, ContactType
|
||||||
|
|
@ -29,6 +22,11 @@ class Questionnaire(CustomBaseModel):
|
||||||
question_three: Optional[str] = None
|
question_three: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class OrgSummary(CustomBaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
class ContactSummary(CustomBaseModel):
|
class ContactSummary(CustomBaseModel):
|
||||||
id: int
|
id: int
|
||||||
email: Optional[EmailStr] = None
|
email: Optional[EmailStr] = None
|
||||||
|
|
@ -51,7 +49,6 @@ class OrgPostOrgRequest(CustomBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class OrgPostOrgResponse(CustomBaseModel):
|
class OrgPostOrgResponse(CustomBaseModel):
|
||||||
id: int
|
|
||||||
name: str
|
name: str
|
||||||
status: Status
|
status: Status
|
||||||
|
|
||||||
|
|
@ -62,7 +59,6 @@ class OrgPatchQuestionnaireRequest(OrgIDMixin):
|
||||||
|
|
||||||
|
|
||||||
class OrgPatchQuestionnaireResponse(CustomBaseModel):
|
class OrgPatchQuestionnaireResponse(CustomBaseModel):
|
||||||
id: int
|
|
||||||
name: str
|
name: str
|
||||||
intake_questionnaire: Questionnaire
|
intake_questionnaire: Questionnaire
|
||||||
status: Status
|
status: Status
|
||||||
|
|
@ -73,7 +69,6 @@ class OrgPatchStatusRequest(OrgIDMixin):
|
||||||
|
|
||||||
|
|
||||||
class OrgPatchStatusResponse(CustomBaseModel):
|
class OrgPatchStatusResponse(CustomBaseModel):
|
||||||
id: int
|
|
||||||
name: str
|
name: str
|
||||||
status: Status
|
status: Status
|
||||||
|
|
||||||
|
|
@ -100,8 +95,7 @@ class OrgPostUserRequest(OrgIDMixin, UserIDMixin):
|
||||||
|
|
||||||
|
|
||||||
class OrgPostUserResponse(CustomBaseModel):
|
class OrgPostUserResponse(CustomBaseModel):
|
||||||
organisation: OrgSummary
|
users: list[str]
|
||||||
users: list[UserSummary]
|
|
||||||
|
|
||||||
|
|
||||||
class OrgPatchRootRequest(OrgIDMixin, UserIDMixin):
|
class OrgPatchRootRequest(OrgIDMixin, UserIDMixin):
|
||||||
|
|
@ -119,8 +113,7 @@ class OrgGetUserResponse(CustomBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class OrgGetGroupResponse(CustomBaseModel):
|
class OrgGetGroupResponse(CustomBaseModel):
|
||||||
organisation: OrgSummary
|
groups: list[str]
|
||||||
groups: list[GroupSummary]
|
|
||||||
|
|
||||||
|
|
||||||
class OrgGetContactResponse(CustomBaseModel):
|
class OrgGetContactResponse(CustomBaseModel):
|
||||||
|
|
|
||||||
|
|
@ -40,18 +40,3 @@ class ServiceIDMixin(CustomBaseModel):
|
||||||
|
|
||||||
class UserIDMixin(CustomBaseModel):
|
class UserIDMixin(CustomBaseModel):
|
||||||
user_id: int = Field(gt=0)
|
user_id: int = Field(gt=0)
|
||||||
|
|
||||||
|
|
||||||
class OrgSummary(CustomBaseModel):
|
|
||||||
id: int
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
class GroupSummary(CustomBaseModel):
|
|
||||||
id: int
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
class UserSummary(CustomBaseModel):
|
|
||||||
id: int
|
|
||||||
email: str
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
from fastapi.routing import APIRoute
|
|
||||||
|
import pytest
|
||||||
from httpx import AsyncClient, ASGITransport
|
from httpx import AsyncClient, ASGITransport
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
|
@ -13,6 +12,7 @@ from src.contact.models import Contact
|
||||||
from src.iam.models import Group, Permission
|
from src.iam.models import Group, Permission
|
||||||
from src.auth.service import get_current_user, get_dev_user
|
from src.auth.service import get_current_user, get_dev_user
|
||||||
from src.auth.dependencies import empty_su_list, get_super_admin_list, testing_su_list
|
from src.auth.dependencies import empty_su_list, get_super_admin_list, testing_su_list
|
||||||
|
|
||||||
from src.main import app # inited FastAPI app
|
from src.main import app # inited FastAPI app
|
||||||
from src.database import engine, Base, get_db
|
from src.database import engine, Base, get_db
|
||||||
|
|
||||||
|
|
@ -156,22 +156,25 @@ def generate_query_and_status(params) -> list[tuple[str, int]]:
|
||||||
return query_and_status
|
return query_and_status
|
||||||
|
|
||||||
|
|
||||||
def get_testable_routes():
|
# # Produces a text file with method and path for every endpoint in the API
|
||||||
routes = []
|
# from fastapi.routing import APIRoute
|
||||||
|
#
|
||||||
for route in app.routes:
|
# def get_testable_routes():
|
||||||
if not isinstance(route, APIRoute):
|
# routes = []
|
||||||
continue
|
#
|
||||||
|
# for route in app.routes:
|
||||||
for method in route.methods:
|
# if not isinstance(route, APIRoute):
|
||||||
if method in {"HEAD", "OPTIONS"}:
|
# continue
|
||||||
continue
|
#
|
||||||
|
# for method in route.methods:
|
||||||
routes.append((method, route.path, route.status_code, route.response_model))
|
# if method in {"HEAD", "OPTIONS"}:
|
||||||
|
# continue
|
||||||
return routes
|
#
|
||||||
|
# routes.append((route.path, method))
|
||||||
|
#
|
||||||
|
# return routes
|
||||||
|
#
|
||||||
|
#
|
||||||
# with open("endpoints.txt", "w") as f:
|
# with open("endpoints.txt", "w") as f:
|
||||||
# for ep in get_testable_routes():
|
# for ep in get_testable_routes():
|
||||||
# f.write(f"[{ep[0]}]{ep[1]}({ep[2]}) -> {ep[2]}: {ep[3]}\n")
|
# f.write(f"{ep[1]} {ep[0]}\n")
|
||||||
|
|
|
||||||
|
|
@ -221,18 +221,10 @@ async def test_get_group_users_success(default_client: AsyncClient):
|
||||||
|
|
||||||
user = data["users"][0]
|
user = data["users"][0]
|
||||||
assert user["id"] == 1
|
assert user["id"] == 1
|
||||||
|
assert user["first_name"] == "Admin"
|
||||||
|
assert user["last_name"] == "Test"
|
||||||
assert user["email"] == "admin@test.com"
|
assert user["email"] == "admin@test.com"
|
||||||
|
|
||||||
assert "group" in data
|
|
||||||
assert isinstance(data["group"], dict)
|
|
||||||
assert data["group"]["id"] == 1
|
|
||||||
assert data["group"]["name"] == "Test Group"
|
|
||||||
|
|
||||||
assert "organisation" in data
|
|
||||||
assert isinstance(data["organisation"], dict)
|
|
||||||
assert data["organisation"]["id"] == 1
|
|
||||||
assert data["organisation"]["name"] == "Test Org"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"query, expected_status", generate_query_and_status(["group_id", "org_id"])
|
"query, expected_status", generate_query_and_status(["group_id", "org_id"])
|
||||||
|
|
|
||||||
|
|
@ -265,16 +265,9 @@ async def test_post_org_user_success(default_client: AsyncClient, db_session):
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|
||||||
assert "organisation" in data
|
|
||||||
assert isinstance(data["organisation"], dict)
|
|
||||||
assert data["organisation"]["id"] == 1
|
|
||||||
assert data["organisation"]["name"] == "Test Org"
|
|
||||||
|
|
||||||
assert "users" in data
|
assert "users" in data
|
||||||
assert isinstance(data["users"], list)
|
assert isinstance(data["users"], list)
|
||||||
assert (
|
assert "user@test.org" in data["users"]
|
||||||
len([user for user in data["users"] if user["email"] == "user@test.org"]) == 1
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -393,17 +386,9 @@ async def test_get_org_groups_success(default_client: AsyncClient):
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|
||||||
assert "organisation" in data
|
|
||||||
assert isinstance(data["organisation"], dict)
|
|
||||||
assert data["organisation"]["id"] == 1
|
|
||||||
assert data["organisation"]["name"] == "Test Org"
|
|
||||||
|
|
||||||
assert "groups" in data
|
assert "groups" in data
|
||||||
assert isinstance(data["groups"], list)
|
assert isinstance(data["groups"], list)
|
||||||
group = data["groups"][0]
|
assert "Test Group" in data["groups"]
|
||||||
assert isinstance(group, dict)
|
|
||||||
assert group["id"] == 1
|
|
||||||
assert group["name"] == "Test Group"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
from fastapi.routing import APIRoute
|
|
||||||
|
|
||||||
from .conftest import generate_query_and_status
|
from .conftest import generate_query_and_status
|
||||||
|
|
||||||
|
|
@ -144,51 +143,3 @@ async def test_get_self_orgs_success(default_client: AsyncClient):
|
||||||
assert isinstance(org["security_contact"], dict)
|
assert isinstance(org["security_contact"], dict)
|
||||||
assert org["security_contact"]["email"] == "security@test.org"
|
assert org["security_contact"]["email"] == "security@test.org"
|
||||||
assert org["security_contact"]["id"] == 3
|
assert org["security_contact"]["id"] == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_get_self_orgs_dynamic(default_client: AsyncClient):
|
|
||||||
method = "GET"
|
|
||||||
path = "/user/self/orgs"
|
|
||||||
expected_data = {
|
|
||||||
"organisations": [
|
|
||||||
{
|
|
||||||
"organisation_id": 1,
|
|
||||||
"name": "Test Org",
|
|
||||||
"status": "approved",
|
|
||||||
"root_user_email": "admin@test.com",
|
|
||||||
"owner_contact": {"email": "owner@test.org", "id": 2},
|
|
||||||
"security_contact": {"email": "security@test.org", "id": 3},
|
|
||||||
"billing_contact": {"email": "billing@test.org", "id": 1},
|
|
||||||
"intake_questionnaire": {
|
|
||||||
"question_one": None,
|
|
||||||
"question_three": None,
|
|
||||||
"question_two": "answer two",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = await default_client.get(path)
|
|
||||||
|
|
||||||
route = next(
|
|
||||||
route
|
|
||||||
for route in default_client._transport.app.routes
|
|
||||||
if isinstance(route, APIRoute)
|
|
||||||
and path in route.path
|
|
||||||
and method in route.methods
|
|
||||||
)
|
|
||||||
|
|
||||||
assert resp.status_code == route.status_code
|
|
||||||
if route.status_code == 204:
|
|
||||||
return
|
|
||||||
|
|
||||||
expected_response_schema = route.response_model
|
|
||||||
data = resp.json()
|
|
||||||
|
|
||||||
response_model = expected_response_schema(**data)
|
|
||||||
assert isinstance(response_model, expected_response_schema)
|
|
||||||
|
|
||||||
expected_response_model = expected_response_schema(**expected_data)
|
|
||||||
|
|
||||||
assert response_model == expected_response_model
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue