Delete endpoints do not fully support bodies. Queries used instead. Tests added. Resolves #20
This commit is contained in:
parent
e9b272811f
commit
c452c6c0d5
13 changed files with 114 additions and 57 deletions
|
|
@ -61,3 +61,16 @@ def get_perm_model_body(
|
||||||
|
|
||||||
|
|
||||||
perm_model_body_dependency = Annotated[type[Permission], Depends(get_perm_model_body)]
|
perm_model_body_dependency = Annotated[type[Permission], Depends(get_perm_model_body)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_perm_model_query(
|
||||||
|
db: db_dependency, perm_id: Annotated[int, Query(gt=0)]
|
||||||
|
) -> type[Permission]:
|
||||||
|
perm_model = db.get(Permission, perm_id)
|
||||||
|
if perm_model is None:
|
||||||
|
raise PermNotFoundException(perm_id)
|
||||||
|
|
||||||
|
return perm_model
|
||||||
|
|
||||||
|
|
||||||
|
perm_model_query_dependency = Annotated[type[Permission], Depends(get_perm_model_query)]
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ from src.auth.dependencies import (
|
||||||
super_admin_dependency,
|
super_admin_dependency,
|
||||||
)
|
)
|
||||||
from src.user.models import User
|
from src.user.models import User
|
||||||
from src.user.dependencies import user_model_body_dependency
|
from src.user.dependencies import (
|
||||||
|
user_model_body_dependency,
|
||||||
|
user_model_query_dependency,
|
||||||
|
)
|
||||||
from src.organisation.models import Organisation as Org
|
from src.organisation.models import Organisation as Org
|
||||||
from src.service.models import Service
|
from src.service.models import Service
|
||||||
|
|
||||||
|
|
@ -46,6 +49,7 @@ from src.iam.dependencies import (
|
||||||
group_model_query_dependency,
|
group_model_query_dependency,
|
||||||
group_model_body_dependency,
|
group_model_body_dependency,
|
||||||
perm_model_body_dependency,
|
perm_model_body_dependency,
|
||||||
|
perm_model_query_dependency,
|
||||||
)
|
)
|
||||||
from src.iam.schemas import (
|
from src.iam.schemas import (
|
||||||
IAMGetGroupPermissionsResponse,
|
IAMGetGroupPermissionsResponse,
|
||||||
|
|
@ -57,14 +61,11 @@ from src.iam.schemas import (
|
||||||
IAMPutGroupPermissionResponse,
|
IAMPutGroupPermissionResponse,
|
||||||
IAMPutGroupUserRequest,
|
IAMPutGroupUserRequest,
|
||||||
IAMPutGroupUserResponse,
|
IAMPutGroupUserResponse,
|
||||||
IAMDeleteGroupPermissionRequest,
|
|
||||||
IAMDeleteGroupPermissionResponse,
|
IAMDeleteGroupPermissionResponse,
|
||||||
IAMDeleteGroupUserRequest,
|
|
||||||
IAMDeleteGroupUserResponse,
|
IAMDeleteGroupUserResponse,
|
||||||
IAMGetPermissionsResponse,
|
IAMGetPermissionsResponse,
|
||||||
IAMPostPermissionRequest,
|
IAMPostPermissionRequest,
|
||||||
IAMPostPermissionResponse,
|
IAMPostPermissionResponse,
|
||||||
IAMDeletePermissionRequest,
|
|
||||||
IAMGetPermissionsSearchRequest,
|
IAMGetPermissionsSearchRequest,
|
||||||
IAMGetPermissionsSearchResponse,
|
IAMGetPermissionsSearchResponse,
|
||||||
)
|
)
|
||||||
|
|
@ -205,10 +206,9 @@ async def add_group_user(
|
||||||
@router.delete("/group/permissions")
|
@router.delete("/group/permissions")
|
||||||
async def remove_group_permissions(
|
async def remove_group_permissions(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
group_model: group_model_body_dependency,
|
group_model: group_model_query_dependency,
|
||||||
perm_model: perm_model_body_dependency,
|
perm_model: perm_model_query_dependency,
|
||||||
org_model: org_model_root_claim_body_dependency,
|
org_model: org_model_root_claim_query_dependency,
|
||||||
request_model: IAMDeleteGroupPermissionRequest,
|
|
||||||
):
|
):
|
||||||
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")
|
||||||
|
|
@ -226,10 +226,9 @@ async def remove_group_permissions(
|
||||||
@router.delete("/group/user")
|
@router.delete("/group/user")
|
||||||
async def remove_group_user(
|
async def remove_group_user(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
group_model: group_model_body_dependency,
|
group_model: group_model_query_dependency,
|
||||||
user_model: user_model_body_dependency,
|
user_model: user_model_query_dependency,
|
||||||
org_model: org_model_root_claim_body_dependency,
|
org_model: org_model_root_claim_query_dependency,
|
||||||
request_model: IAMDeleteGroupUserRequest,
|
|
||||||
):
|
):
|
||||||
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")
|
||||||
|
|
@ -285,8 +284,7 @@ async def create_new_permission(
|
||||||
async def delete_permission(
|
async def delete_permission(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
su: super_admin_dependency,
|
su: super_admin_dependency,
|
||||||
perm_model: perm_model_body_dependency,
|
perm_model: perm_model_query_dependency,
|
||||||
request_model: IAMDeletePermissionRequest,
|
|
||||||
):
|
):
|
||||||
db.delete(perm_model)
|
db.delete(perm_model)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
|
||||||
|
|
@ -81,19 +81,11 @@ class IAMPutGroupUserResponse(CustomBaseModel):
|
||||||
users: list[UserSchema]
|
users: list[UserSchema]
|
||||||
|
|
||||||
|
|
||||||
class IAMDeleteGroupPermissionRequest(GroupIDMixin, PermIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class IAMDeleteGroupPermissionResponse(CustomBaseModel):
|
class IAMDeleteGroupPermissionResponse(CustomBaseModel):
|
||||||
group: GroupSchema
|
group: GroupSchema
|
||||||
permissions: list[PermissionSchema]
|
permissions: list[PermissionSchema]
|
||||||
|
|
||||||
|
|
||||||
class IAMDeleteGroupUserRequest(GroupIDMixin, UserIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class IAMDeleteGroupUserResponse(CustomBaseModel):
|
class IAMDeleteGroupUserResponse(CustomBaseModel):
|
||||||
group: GroupSchema
|
group: GroupSchema
|
||||||
users: list[UserSchema]
|
users: list[UserSchema]
|
||||||
|
|
@ -112,10 +104,6 @@ class IAMPostPermissionResponse(CustomBaseModel):
|
||||||
permission: PermissionSchema
|
permission: PermissionSchema
|
||||||
|
|
||||||
|
|
||||||
class IAMDeletePermissionRequest(PermIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class IAMGetPermissionsSearchRequest(OrgIDMixin):
|
class IAMGetPermissionsSearchRequest(OrgIDMixin):
|
||||||
service_id: Annotated[int | None, Field(gt=0)] = None
|
service_id: Annotated[int | None, Field(gt=0)] = None
|
||||||
resource: Optional[str] = None
|
resource: Optional[str] = None
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ from src.database import db_dependency
|
||||||
from src.user.dependencies import (
|
from src.user.dependencies import (
|
||||||
user_model_body_dependency,
|
user_model_body_dependency,
|
||||||
user_model_claims_dependency,
|
user_model_claims_dependency,
|
||||||
|
user_model_query_dependency,
|
||||||
)
|
)
|
||||||
from src.auth.dependencies import (
|
from src.auth.dependencies import (
|
||||||
super_admin_dependency,
|
super_admin_dependency,
|
||||||
|
|
@ -38,7 +39,10 @@ from src.auth.dependencies import (
|
||||||
org_model_root_claim_body_dependency,
|
org_model_root_claim_body_dependency,
|
||||||
)
|
)
|
||||||
|
|
||||||
from src.organisation.dependencies import org_model_body_dependency
|
from src.organisation.dependencies import (
|
||||||
|
org_model_body_dependency,
|
||||||
|
org_model_query_dependency,
|
||||||
|
)
|
||||||
from src.organisation.constants import ContactType
|
from src.organisation.constants import ContactType
|
||||||
from src.organisation.models import Organisation as Org
|
from src.organisation.models import Organisation as Org
|
||||||
from src.organisation.schemas import (
|
from src.organisation.schemas import (
|
||||||
|
|
@ -52,8 +56,6 @@ from src.organisation.schemas import (
|
||||||
OrgGetOrgResponse,
|
OrgGetOrgResponse,
|
||||||
OrgPatchRootRequest,
|
OrgPatchRootRequest,
|
||||||
OrgGetGroupResponse,
|
OrgGetGroupResponse,
|
||||||
OrgDeleteUserRequest,
|
|
||||||
OrgDeleteOrgRequest,
|
|
||||||
OrgPostOrgResponse,
|
OrgPostOrgResponse,
|
||||||
OrgPatchQuestionnaireResponse,
|
OrgPatchQuestionnaireResponse,
|
||||||
OrgPatchStatusResponse,
|
OrgPatchStatusResponse,
|
||||||
|
|
@ -324,9 +326,8 @@ async def add_user_to_org(
|
||||||
)
|
)
|
||||||
async def delete_organisation_by_id(
|
async def delete_organisation_by_id(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
org_model: org_model_body_dependency,
|
org_model: org_model_query_dependency,
|
||||||
su: super_admin_dependency,
|
su: super_admin_dependency,
|
||||||
request_model: OrgDeleteOrgRequest,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Removes an organisation from the hub.
|
Removes an organisation from the hub.
|
||||||
|
|
@ -411,9 +412,8 @@ async def get_org_groups(org_model: org_model_root_claim_query_dependency):
|
||||||
)
|
)
|
||||||
async def remove_user_from_org(
|
async def remove_user_from_org(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
org_model: org_model_root_claim_body_dependency,
|
org_model: org_model_root_claim_query_dependency,
|
||||||
user_model: user_model_body_dependency,
|
user_model: user_model_query_dependency,
|
||||||
request_model: OrgDeleteUserRequest,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Revokes a user's membership in an organisation.
|
Revokes a user's membership in an organisation.
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,6 @@ class OrgPostUserResponse(CustomBaseModel):
|
||||||
users: list[str]
|
users: list[str]
|
||||||
|
|
||||||
|
|
||||||
class OrgDeleteUserRequest(OrgIDMixin, UserIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OrgPatchRootRequest(OrgIDMixin, UserIDMixin):
|
class OrgPatchRootRequest(OrgIDMixin, UserIDMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -133,7 +129,3 @@ class OrgGetOrgResponse(CustomBaseModel):
|
||||||
billing_contact: Optional[str] = None
|
billing_contact: Optional[str] = None
|
||||||
security_contact: Optional[str] = None
|
security_contact: Optional[str] = None
|
||||||
intake_questionnaire: Optional[Questionnaire] = None
|
intake_questionnaire: Optional[Questionnaire] = None
|
||||||
|
|
||||||
|
|
||||||
class OrgDeleteOrgRequest(OrgIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ from src.auth.dependencies import (
|
||||||
|
|
||||||
from src.service.models import Service
|
from src.service.models import Service
|
||||||
from src.service.utils import generate_api_key
|
from src.service.utils import generate_api_key
|
||||||
from src.service.dependencies import service_model_body_dependency
|
from src.service.dependencies import (
|
||||||
|
service_model_body_dependency,
|
||||||
|
service_model_query_dependency,
|
||||||
|
)
|
||||||
from src.service.schemas import (
|
from src.service.schemas import (
|
||||||
ServiceGetServiceResponse,
|
ServiceGetServiceResponse,
|
||||||
ServicePostServiceRequest,
|
ServicePostServiceRequest,
|
||||||
|
|
@ -28,7 +31,6 @@ from src.service.schemas import (
|
||||||
ServiceWithKeySchema,
|
ServiceWithKeySchema,
|
||||||
ServicePatchKeyResponse,
|
ServicePatchKeyResponse,
|
||||||
ServicePatchKeyRequest,
|
ServicePatchKeyRequest,
|
||||||
ServiceDeleteServiceRequest,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
|
|
@ -137,9 +139,8 @@ async def regenerate_api_key(
|
||||||
)
|
)
|
||||||
async def remove_service(
|
async def remove_service(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
service_model: service_model_body_dependency,
|
service_model: service_model_query_dependency,
|
||||||
su: super_admin_dependency,
|
su: super_admin_dependency,
|
||||||
request_model: ServiceDeleteServiceRequest,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Removes a service from the hub.
|
Removes a service from the hub.
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,3 @@ class ServicePatchKeyRequest(ServiceIDMixin):
|
||||||
|
|
||||||
class ServicePatchKeyResponse(CustomBaseModel):
|
class ServicePatchKeyResponse(CustomBaseModel):
|
||||||
service: ServiceWithKeySchema
|
service: ServiceWithKeySchema
|
||||||
|
|
||||||
|
|
||||||
class ServiceDeleteServiceRequest(ServiceIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Endpoints:
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from starlette import status
|
from starlette import status
|
||||||
|
|
||||||
from src.user.schemas import UserResponse, OIDCClaims, UserDeleteUserRequest
|
from src.user.schemas import UserResponse, OIDCClaims
|
||||||
from src.user.dependencies import (
|
from src.user.dependencies import (
|
||||||
user_model_claims_dependency,
|
user_model_claims_dependency,
|
||||||
user_model_query_dependency,
|
user_model_query_dependency,
|
||||||
|
|
@ -92,9 +92,8 @@ async def get_user_by_id(
|
||||||
)
|
)
|
||||||
async def delete_user_by_id(
|
async def delete_user_by_id(
|
||||||
db: db_dependency,
|
db: db_dependency,
|
||||||
user_model: user_model_body_dependency,
|
user_model: user_model_query_dependency,
|
||||||
su: super_admin_dependency,
|
su: super_admin_dependency,
|
||||||
request_model: UserDeleteUserRequest,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Deletes the user with the provided ID from the database. This will not remove them from OIDC, and they will be automatically readded on next login.
|
Deletes the user with the provided ID from the database. This will not remove them from OIDC, and they will be automatically readded on next login.
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,3 @@ class UserResponse(CustomBaseModel):
|
||||||
class OrgResponse(CustomBaseModel):
|
class OrgResponse(CustomBaseModel):
|
||||||
org_id: int
|
org_id: int
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteUserRequest(UserIDMixin):
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
|
|
@ -723,3 +723,40 @@ async def test_post_perm_search_status_checks(
|
||||||
resp = await default_client.post("/iam/permissions/search", json=body)
|
resp = await default_client.post("/iam/permissions/search", json=body)
|
||||||
|
|
||||||
assert resp.status_code == expected_status
|
assert resp.status_code == expected_status
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_group_permissions_success(default_client: AsyncClient):
|
||||||
|
resp = await default_client.delete(
|
||||||
|
"/iam/group/permissions?org_id=1&group_id=1&perm_id=1"
|
||||||
|
)
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert "permissions" in data
|
||||||
|
assert isinstance(data["permissions"], list)
|
||||||
|
assert len(data["permissions"]) == 0
|
||||||
|
assert "group" in data
|
||||||
|
assert data["group"]["id"] == 1
|
||||||
|
assert data["group"]["name"] == "Test Group"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_permissions_success(default_client: AsyncClient):
|
||||||
|
resp = await default_client.delete("/iam/permission?perm_id=1")
|
||||||
|
|
||||||
|
assert resp.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_group_users_success(default_client: AsyncClient):
|
||||||
|
resp = await default_client.delete("/iam/group/user?org_id=1&group_id=1&user_id=1")
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert "users" in data
|
||||||
|
assert isinstance(data["users"], list)
|
||||||
|
assert len(data["users"]) == 0
|
||||||
|
assert "group" in data
|
||||||
|
assert data["group"]["id"] == 1
|
||||||
|
assert data["group"]["name"] == "Test Group"
|
||||||
|
|
|
||||||
|
|
@ -491,3 +491,26 @@ async def test_patch_org_contact_status_checks(
|
||||||
resp = await default_client.patch("/org/contact", json=body)
|
resp = await default_client.patch("/org/contact", json=body)
|
||||||
|
|
||||||
assert resp.status_code == expected_status
|
assert resp.status_code == expected_status
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_org_success(default_client: AsyncClient):
|
||||||
|
resp = await default_client.delete("/org?org_id=1")
|
||||||
|
|
||||||
|
assert resp.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_org_users_success(db_session, default_client: AsyncClient):
|
||||||
|
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 default_client.delete("/org/user?org_id=1&user_id=2")
|
||||||
|
|
||||||
|
assert resp.status_code == 204
|
||||||
|
|
|
||||||
|
|
@ -88,3 +88,10 @@ async def test_patch_services_status_checks(
|
||||||
resp = await default_client.patch("/service/key", json=body)
|
resp = await default_client.patch("/service/key", json=body)
|
||||||
|
|
||||||
assert resp.status_code == expected_status
|
assert resp.status_code == expected_status
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_service_success(default_client: AsyncClient):
|
||||||
|
resp = await default_client.delete("/service/?service_id=1")
|
||||||
|
|
||||||
|
assert resp.status_code == 204
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,10 @@ async def test_get_user_status_checks(
|
||||||
resp = await default_client.get(f"/user/?{query}")
|
resp = await default_client.get(f"/user/?{query}")
|
||||||
|
|
||||||
assert resp.status_code == expected_status
|
assert resp.status_code == expected_status
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_user_success(default_client: AsyncClient):
|
||||||
|
resp = await default_client.delete("/user/?user_id=1")
|
||||||
|
|
||||||
|
assert resp.status_code == 204
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue