From 1e337864f5d2341db2a8062c1d365bca1f710086 Mon Sep 17 00:00:00 2001 From: Arnthorny Date: Thu, 8 Aug 2024 17:33:34 +0100 Subject: [PATCH] feat: Endpoint that allows a superadmin delete all invitations from the db --- api/v1/routes/invitations.py | 31 +++-- api/v1/services/invite.py | 34 +++--- .../invitation/test_delete_all_invitations.py | 69 +++++++++++ tests/v1/invitation/test_delete_invite.py | 110 ------------------ 4 files changed, 105 insertions(+), 139 deletions(-) create mode 100644 tests/v1/invitation/test_delete_all_invitations.py delete mode 100644 tests/v1/invitation/test_delete_invite.py diff --git a/api/v1/routes/invitations.py b/api/v1/routes/invitations.py index 6c2a3bac8..3206c07fc 100644 --- a/api/v1/routes/invitations.py +++ b/api/v1/routes/invitations.py @@ -41,6 +41,25 @@ async def add_user_to_organization( logging.info(f"Processing invitation ID: {invite_id}") return invite.InviteService.add_user_to_organization(invite_id, session) + + +@invites.delete("", status_code=status.HTTP_204_NO_CONTENT) +def delete_all_invite( + db: Session = Depends(get_session), + admin: User = Depends(user_service.get_current_super_admin) +): + """Delete all invitations from the database + + Args: + db (Session, optional): _description_. Defaults to Depends(get_session). + admin (User, optional): _description_. Defaults to Depends(user_service.get_current_super_admin). + """ + print("Deleting all invites") + invite.InviteService.delete_all(db) + + logging.info("Deleted all invites successfully") + + @invites.delete("/{invite_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_invite( invite_id: str, @@ -53,14 +72,4 @@ def delete_invite( if not invite_is_deleted: raise HTTPException(status_code=404, detail="Invalid invitation id") - logging.info(f"Deleted invite. ID: {invite_id}") - -@invites.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT,response_model=None,tags=["Invitation Management"]) -def delete_invitation( - id: str, - db: Session = Depends(get_session), - current_user: User = Depends(user_service.get_current_super_admin) - ): - '''Delete invitation from the database''' - invite.InviteService.delete_invitation(id, db) - + logging.info(f"Deleted invite. ID: {invite_id}") \ No newline at end of file diff --git a/api/v1/services/invite.py b/api/v1/services/invite.py index a208a3164..ba80d8a63 100644 --- a/api/v1/services/invite.py +++ b/api/v1/services/invite.py @@ -172,24 +172,6 @@ def add_user_to_organization(invite_id: str, session: Session): detail="An error occurred while adding the user to the organization" ) @staticmethod - def delete_invitation(id: str, db: Session): - """Function to delete an invitation by its id - - Args: - session(Session): The current ORM session object. - id(str): Invite id string - """ - invitation = db.query(Invitation).filter(Invitation.id == id).first() - if invitation: - try: - db.delete(invitation) - db.commit() - except Exception as e: - db.rollback() - raise HTTPException(status_code=500, detail="An unexpected error occurred: " + str(e)) - else: - raise HTTPException(status_code=404, detail="Invitation not found") - def delete(session: Session, id: str): """Function to delete invite link @@ -211,6 +193,22 @@ def delete(session: Session, id: str): session.commit() return True + @staticmethod + def delete_all(session: Session): + """Function to delete all invite links + + Args: + session(Session): The current ORM session object. + id(str): Invite id string + + """ + all_invites = session.query(Invitation).all() + + for invite in all_invites: + session.delete(invite) + + session.commit() + def fetch(self): pass diff --git a/tests/v1/invitation/test_delete_all_invitations.py b/tests/v1/invitation/test_delete_all_invitations.py new file mode 100644 index 000000000..acbc3bb94 --- /dev/null +++ b/tests/v1/invitation/test_delete_all_invitations.py @@ -0,0 +1,69 @@ +# Dependencies: +# pip install pytest-mock +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from main import app +from api.v1.services.user import user_service +from api.db.database import get_db +from sqlalchemy.orm import Session +from api.v1.services.user import oauth2_scheme + +from api.v1.services.invite import InviteService +from sqlalchemy.orm import Session + + +db_mck = MagicMock(spec=Session) +def mock_deps(): + return MagicMock(id="user_id") + +def mock_oauth(): + return 'access_token' + +def mock_db(): + return db_mck + +@pytest.fixture +def client(): + client = TestClient(app) + yield client + +DELETE_ENDPOINT = "/api/v1/invite" +class TestDeleteAllInvite: + @classmethod + def setup_class(cls): + app.dependency_overrides[user_service.get_current_super_admin] = mock_deps + app.dependency_overrides[get_db] = mock_db + + @classmethod + def teardown_class(cls): + app.dependency_overrides = {} + + + # Successfully delete all invitations when called by a super admin + def test_delete_all_invite_success(self, mocker, client): + + mocker.patch.object(InviteService, 'delete_all') + response = client.delete(DELETE_ENDPOINT) + + InviteService.delete_all.assert_called_once_with(db_mck) + assert response.status_code == 204 + + # Handling unauthorized request + def test_delete_all_invite_unauth(self, client): + app.dependency_overrides = {} + + response = client.delete(DELETE_ENDPOINT) + assert response.status_code == 401 + assert response.json()['message'] == 'Not authenticated' + + # Handling forbidden request + def test_delete_all_invite_forbidden(self, client): + app.dependency_overrides = {} + app.dependency_overrides[get_db] = mock_db + app.dependency_overrides[oauth2_scheme] = mock_oauth + + with patch('api.v1.services.user.user_service.get_current_user', return_value=MagicMock(is_super_admin=False)) as cu: + response = client.delete(DELETE_ENDPOINT) + assert response.status_code == 403 + assert response.json()['message'] == 'You do not have permission to access this resource' diff --git a/tests/v1/invitation/test_delete_invite.py b/tests/v1/invitation/test_delete_invite.py deleted file mode 100644 index c1ebd908a..000000000 --- a/tests/v1/invitation/test_delete_invite.py +++ /dev/null @@ -1,110 +0,0 @@ -import sys -import os -import warnings -from unittest.mock import patch, MagicMock -import pytest -from fastapi.testclient import TestClient -from datetime import datetime, timezone -from uuid_extensions import uuid7 - -warnings.filterwarnings("ignore", category=DeprecationWarning) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) - -from main import app -from api.v1.models.user import User -from api.v1.models.invitation import Invitation -from api.v1.models.organization import Organization -from api.v1.services.user import user_service -from api.db.database import get_db - -DELETE_INVITE_ENDPOINT = '/api/v1/invite/{id}' - -client = TestClient(app) - -@pytest.fixture -def mock_db_session(): - with patch("api.db.database.get_db", autospec=True) as mock_get_db: - mock_db = MagicMock() - app.dependency_overrides[get_db] = lambda: mock_db - yield mock_db - app.dependency_overrides = {} - -@pytest.fixture -def mock_invite_service(): - with patch("api.v1.services.invite.invite_service", autospec=True) as mock_service: - yield mock_service - -def create_mock_user(mock_db_session, user_id): - mock_user = User( - id=user_id, - email="testuser@gmail.com", - password="hashed_password", - first_name='Test', - last_name='User', - is_active=True, - created_at=datetime.now(timezone.utc), - updated_at=datetime.now(timezone.utc) - ) - mock_db_session.query(User).filter_by(id=user_id).first.return_value = mock_user - return mock_user - -def create_mock_organization(mock_db_session, org_id): - mock_org = Organization( - id=org_id, - name="Test Organization", - created_at=datetime.now(timezone.utc), - updated_at=datetime.now(timezone.utc) - ) - mock_db_session.query(Organization).filter_by(id=org_id).first.return_value = mock_org - return mock_org - -def create_mock_invitation(mock_db_session, user_id, org_id, invitation_id, expiration, is_valid): - mock_invitation = Invitation( - id=invitation_id, - user_id=user_id, - organization_id=org_id, - expires_at=expiration, - is_valid=is_valid - ) - mock_db_session.query(Invitation).filter_by(id=invitation_id, is_valid=True).first.return_value = mock_invitation - return mock_invitation - -@pytest.mark.usefixtures("mock_db_session", "mock_invite_service") -def test_delete_invite_invalid_id(mock_db_session, mock_invite_service): - user_id = str(uuid7()) - access_token = user_service.create_access_token(str(user_id)) - - response = client.delete('/api/v1/invite', headers={'Authorization': f'Bearer {access_token}'}) - assert response.status_code == 404 - -@pytest.mark.usefixtures("mock_db_session", "mock_invite_service") -def test_delete_invite_success(mock_db_session, mock_invite_service): - user_id = str(uuid7()) - org_id = str(uuid7()) - id = str(uuid7()) - - create_mock_user(mock_db_session, user_id) - create_mock_organization(mock_db_session, org_id) - create_mock_invitation(mock_db_session, user_id, org_id, id, datetime.now(timezone.utc), is_valid=True) - - access_token = user_service.create_access_token(str(user_id)) - - response = client.delete(DELETE_INVITE_ENDPOINT.format(id=id), headers={'Authorization': f'Bearer {access_token}'}) - assert response.status_code == 204 - - -@pytest.mark.usefixtures("mock_db_session", "mock_invite_service") -def test_delete_invite_no_authorization(mock_db_session, mock_invite_service): - user_id = str(uuid7()) - org_id = str(uuid7()) - id = str(uuid7()) - - create_mock_user(mock_db_session, user_id) - create_mock_organization(mock_db_session, org_id) - create_mock_invitation(mock_db_session, user_id, org_id, id, datetime.now(timezone.utc), is_valid=True) - - response = client.delete(DELETE_INVITE_ENDPOINT.format(id=id)) - assert response.status_code == 401 - -if __name__ == "__main__": - pytest.main()