diff --git a/compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py b/compliance-api/migrations/versions/6c3b6755e2dc_inspection_requirement_patches.py similarity index 82% rename from compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py rename to compliance-api/migrations/versions/6c3b6755e2dc_inspection_requirement_patches.py index a6db14a..cb9c5a9 100644 --- a/compliance-api/migrations/versions/791cc75df1c4_inspection_requirement_patches.py +++ b/compliance-api/migrations/versions/6c3b6755e2dc_inspection_requirement_patches.py @@ -1,8 +1,8 @@ -"""inspection_requirement patches +"""inspection_requirement_patches -Revision ID: 791cc75df1c4 +Revision ID: 6c3b6755e2dc Revises: a684c47ac3e8 -Create Date: 2025-01-02 15:02:27.066126 +Create Date: 2025-01-02 18:17:21.124175 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '791cc75df1c4' +revision = '6c3b6755e2dc' down_revision = 'a684c47ac3e8' branch_labels = None depends_on = None @@ -34,10 +34,12 @@ def upgrade(): existing_type=sa.INTEGER(), nullable=True, existing_comment='Compliance finding of the requirement') + batch_op.create_index(batch_op.f('ix_inspection_requirements_inspection_id'), ['inspection_id'], unique=False) with op.batch_alter_table('inspection_requirements_version', schema=None) as batch_op: batch_op.add_column(sa.Column('sort_order', sa.Integer(), autoincrement=False, nullable=True, comment='The order of requirements')) batch_op.add_column(sa.Column('sort_order_mod', sa.Boolean(), server_default=sa.text('false'), nullable=False)) + batch_op.create_index(batch_op.f('ix_inspection_requirements_version_inspection_id'), ['inspection_id'], unique=False) # ### end Alembic commands ### @@ -45,10 +47,12 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('inspection_requirements_version', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_inspection_requirements_version_inspection_id')) batch_op.drop_column('sort_order_mod') batch_op.drop_column('sort_order') with op.batch_alter_table('inspection_requirements', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_inspection_requirements_inspection_id')) batch_op.alter_column('compliance_finding_id', existing_type=sa.INTEGER(), nullable=False, diff --git a/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py b/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py index d732654..6df4367 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_req_detail_doc.py @@ -52,6 +52,7 @@ class InspectionReqDetailDocument(BaseModelVersioned): "InspectionReqSourceDetail", back_populates="documents", lazy="select", + uselist=False ) document_type = relationship( "DocumentType", foreign_keys=[document_type_id], lazy="select" diff --git a/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py b/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py index 95257b6..189d122 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_req_source_detail.py @@ -60,6 +60,7 @@ class InspectionReqSourceDetail(BaseModelVersioned): "InspectionRequirement", back_populates="requirement_source_details", lazy="select", + uselist=False ) requirement_source = relationship( "RequirementSource", foreign_keys=[requirement_source_id], lazy="joined" @@ -67,7 +68,7 @@ class InspectionReqSourceDetail(BaseModelVersioned): documents = relationship( "InspectionReqDetailDocument", back_populates="requirement_source_detail", - lazy="joined", + lazy="select", ) @classmethod diff --git a/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py b/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py index 892d6cd..37ee16e 100644 --- a/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py +++ b/compliance-api/src/compliance_api/models/inspection/inspection_requirement.py @@ -3,7 +3,7 @@ from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy.orm import relationship -from ..base_model import BaseModelVersioned +from ..base_model import BaseModelVersioned, db class InspectionRequirement(BaseModelVersioned): @@ -20,6 +20,7 @@ class InspectionRequirement(BaseModelVersioned): Integer, ForeignKey("inspections.id", name="inspection_requirements_inspection_id_fkey"), nullable=False, + index=True, comment="The unique identifier of the inspection", ) summary = Column(String, nullable=False, comment="The summary of the requirement") @@ -61,7 +62,7 @@ class InspectionRequirement(BaseModelVersioned): requirement_source_details = relationship( "InspectionReqSourceDetail", back_populates="inspection_requirement", - lazy="joined", + lazy="select" ) @classmethod @@ -74,3 +75,12 @@ def create_requirement(cls, requirement_obj, session=None): else: requirement.save() return requirement + + @classmethod + def get_by_inspection_id(cls, inspection_id): + """Get requirements by inspection id.""" + return db.session.query(InspectionRequirement).filter( + InspectionRequirement.inspection_id == inspection_id, + InspectionRequirement.is_deleted.is_(False), + InspectionRequirement.is_active.is_(True) + ).all() diff --git a/compliance-api/src/compliance_api/resources/inspection_requirement.py b/compliance-api/src/compliance_api/resources/inspection_requirement.py index 43f481b..057f3c5 100644 --- a/compliance-api/src/compliance_api/resources/inspection_requirement.py +++ b/compliance-api/src/compliance_api/resources/inspection_requirement.py @@ -27,6 +27,15 @@ class InspectionRequirements(Resource): """InspectionRequirements.""" + @staticmethod + @ApiHelper.swagger_decorators(API, endpoint_description="Get all requirements by inspection") + @auth.require + @API.response(code=200, description="Success", model=[inspection_requirement_list_model]) + def get(inspection_id): + """Get requirements by inspection id.""" + requirements = InspectionRequirementService.get_all(inspection_id) + return InspectionRequirementSchema(many=True).dump(requirements), HTTPStatus.OK + @staticmethod @ApiHelper.swagger_decorators(API, endpoint_description="Create an inspection") @API.expect(inspection_requirement_create_model) diff --git a/compliance-api/src/compliance_api/schemas/inspection_requirement.py b/compliance-api/src/compliance_api/schemas/inspection_requirement.py index 4411687..d7c71ef 100644 --- a/compliance-api/src/compliance_api/schemas/inspection_requirement.py +++ b/compliance-api/src/compliance_api/schemas/inspection_requirement.py @@ -125,8 +125,7 @@ class Meta(AutoSchemaBase.Meta): # pylint: disable=too-few-public-methods model = InspectionReqSourceDetail include_fk = True - -documents = fields.List(fields.Nested(InspectionReqDetailDocSchema)) + documents = fields.List(fields.Nested(InspectionReqDetailDocSchema)) class InspectionRequirementSchema(AutoSchemaBase): # pylint: disable=too-many-ancestors diff --git a/compliance-api/src/compliance_api/services/inspection_requirement.py b/compliance-api/src/compliance_api/services/inspection_requirement.py index d55f6ed..6489adb 100644 --- a/compliance-api/src/compliance_api/services/inspection_requirement.py +++ b/compliance-api/src/compliance_api/services/inspection_requirement.py @@ -9,6 +9,11 @@ class InspectionRequirementService: """InspectionRequirementService.""" + @classmethod + def get_all(cls, inspection_id): + """Get all requirements by inspection id.""" + return InspectionRequirementModel.get_by_inspection_id(inspection_id) + @classmethod def create(cls, inspection_id, requirement_data): """Create inspection requirement.""" diff --git a/compliance-api/tests/integration/api/test_case_file.py b/compliance-api/tests/integration/api/test_case_file.py index 4629cc3..85145a0 100644 --- a/compliance-api/tests/integration/api/test_case_file.py +++ b/compliance-api/tests/integration/api/test_case_file.py @@ -24,7 +24,7 @@ fake = Faker() -@pytest.fixture +@pytest.fixture(scope="session") def mock_auth_service(mocker): """Fixture to mock AuthService methods.""" mock_get_user_by_guid = mocker.patch( @@ -167,17 +167,22 @@ def test_get_case_files_by_project_id(client, auth_header): assert len(result.json) == 2 -def test_get_case_files(client, auth_header): +def test_get_case_files(client, auth_header, mocker): """Get all case files.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["project_id"] = 2 case_file_data["case_file_number"] = fake.word() - CaseFileModel.create_case_file(case_file_data) + created_case = CaseFileService.create(case_file_data) url = urljoin(API_BASE_URL, "case-files") result = client.get(url, headers=auth_header) assert result.status_code == HTTPStatus.OK - assert len(result.json) == 4 + filtered_case_file = next( + (case for case in result.json if case["id"] == created_case.id), None + ) + assert filtered_case_file is not None def test_get_case_file_by_id(client, auth_header, mocker): @@ -204,8 +209,10 @@ def test_get_case_file_by_id(client, auth_header, mocker): assert result.json["case_file_status"] == CaseFileStatusEnum.OPEN.value -def test_get_case_file_officers(client, auth_header_super_user): +def test_get_case_file_officers(client, auth_header_super_user, mocker): """Create case file with file number.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True user_data = StaffScenario.default_data.value auth_user_guid = fake.word() user_data["auth_user_guid"] = auth_user_guid @@ -214,13 +221,8 @@ def test_get_case_file_officers(client, auth_header_super_user): case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = new_user.id case_file_data["officer_ids"] = [new_user.id] - result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) - print(result.json) - url = urljoin(API_BASE_URL, f"case-files/{result.json.get('id')}/officers") + result = CaseFileService.create(case_file_data) + url = urljoin(API_BASE_URL, f"case-files/{result.id}/officers") result = client.get(url, headers=auth_header_super_user) assert result.status_code == HTTPStatus.OK assert len(result.json) == 1 @@ -240,11 +242,7 @@ def test_get_case_file_by_number(client, auth_header_super_user, mocker): } case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() - result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) + result = CaseFileModel.create_case_file(case_file_data) url = urljoin( API_BASE_URL, f"case-files/case-file-numbers/{case_file_data['case_file_number']}", @@ -272,11 +270,7 @@ def test_case_file_update(client, auth_header_super_user, created_staff, mocker) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id case_file_data["project_description"] = "sample description" - created_result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) + created_result = CaseFileModel.create_case_file(case_file_data) url = urljoin( API_BASE_URL, f"case-files/case-file-numbers/{case_file_data['case_file_number']}", @@ -295,7 +289,7 @@ def test_case_file_update(client, auth_header_super_user, created_staff, mocker) case_file_data["primary_officer_id"] = new_user.id case_file_data["officer_ids"] = [new_user.id] case_file_data["project_description"] = "changed description" - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}") + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}") result = client.patch( url, data=json.dumps(case_file_data), headers=auth_header_super_user ) @@ -309,7 +303,7 @@ def test_case_file_update(client, auth_header_super_user, created_staff, mocker) # update the payload by making the officer list empty case_file_data["primary_officer_id"] = new_user.id case_file_data["officer_ids"] = [] - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}") + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}") result = client.patch( url, data=json.dumps(case_file_data), headers=auth_header_super_user ) @@ -320,19 +314,13 @@ def test_case_file_update(client, auth_header_super_user, created_staff, mocker) assert len(officers) == 0 -def test_case_file_update_viewer_fails( - client, auth_header, auth_header_super_user, created_staff -): +def test_case_file_update_viewer_fails(client, auth_header, created_staff): """Update as Viewer.""" case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id - created_result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}") + created_result = CaseFileModel.create_case_file(case_file_data) + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}") result = client.patch(url, data=json.dumps(case_file_data), headers=auth_header) assert result.status_code == HTTPStatus.FORBIDDEN @@ -344,38 +332,31 @@ def test_case_file_update_with_primary( case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id - created_result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) - + created_result = CaseFileModel.create_case_file(case_file_data) header = TokenJWTClaims.default.value header["preferred_username"] = created_staff.auth_user_guid headers = factory_auth_header(jwt=jwt, claims=header) - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}") + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}") result = client.patch(url, data=json.dumps(case_file_data), headers=headers) assert result.status_code == HTTPStatus.OK -def test_case_file_close(client, jwt, created_staff, auth_header_super_user): +def test_case_file_close(client, jwt, created_staff, mocker): """Update as primary.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id - created_result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) - case_file_id = created_result.json.get("id") - case_file_number = created_result.json.get("case_file_number") + created_result = CaseFileService.create(case_file_data) + case_file_id = created_result.id + case_file_number = created_result.case_file_number header = TokenJWTClaims.default.value header["preferred_username"] = created_staff.auth_user_guid headers = factory_auth_header(jwt=jwt, claims=header) print(created_result) - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}/status") + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}/status") result = client.patch(url, data=json.dumps({"status": "OPEN"}), headers=headers) assert result.status_code == HTTPStatus.UNPROCESSABLE_ENTITY result = client.patch(url, data=json.dumps({"status": "CLOSED"}), headers=headers) @@ -432,48 +413,39 @@ def test_case_file_close(client, jwt, created_staff, auth_header_super_user): assert cr_key is not None -def test_case_file_delete(client, jwt, created_staff, auth_header_super_user): +def test_case_file_delete(client, jwt, created_staff, mocker, auth_header_super_user): """Update as primary.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id - created_result = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) + created_result = CaseFileService.create(case_file_data) - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}") + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}") result = client.delete(url, headers=auth_header_super_user) assert result.status_code == HTTPStatus.NO_CONTENT - url = urljoin(API_BASE_URL, f"case-files/{created_result.json.get('id')}") + url = urljoin(API_BASE_URL, f"case-files/{created_result.id}") result = client.get(url, headers=auth_header_super_user) assert result.status_code == HTTPStatus.NOT_FOUND -def test_case_file_linking(client, jwt, created_staff, auth_header_super_user): +def test_case_file_linking(client, jwt, created_staff, auth_header_super_user, mocker): """Link case file.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True # Create source case file case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id - source_case_file = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) + source_case_file = CaseFileService.create(case_file_data) # Create target case file case_file_data = copy.copy(CasefileScenario.default_value.value) case_file_data["case_file_number"] = fake.word() case_file_data["primary_officer_id"] = created_staff.id - target_case_file = client.post( - urljoin(API_BASE_URL, "case-files"), - data=json.dumps(case_file_data), - headers=auth_header_super_user, - ) - print(target_case_file.json.get("id")) - url = urljoin(API_BASE_URL, f"case-files/{source_case_file.json.get('id')}/links") - post_data = {"link_case_file_id": target_case_file.json.get("id")} + target_case_file = CaseFileService.create(case_file_data) + url = urljoin(API_BASE_URL, f"case-files/{source_case_file.id}/links") + post_data = {"link_case_file_id": target_case_file.id} result = client.post( url, data=json.dumps(post_data), diff --git a/compliance-api/tests/integration/api/test_complaint.py b/compliance-api/tests/integration/api/test_complaint.py index 4244755..efa951e 100644 --- a/compliance-api/tests/integration/api/test_complaint.py +++ b/compliance-api/tests/integration/api/test_complaint.py @@ -1,9 +1,55 @@ """test suit for complaint.""" + from http import HTTPStatus from urllib.parse import urljoin +import pytest +from faker import Faker + +from compliance_api.services import CaseFileService, ComplaintService +from tests.utilities.factory_scenario import CasefileScenario, ComplaintScenario, StaffScenario + API_BASE_URL = "/api/" +fake = Faker() + + +@pytest.fixture +def mock_track_service(mocker): + """Fixture to mock TrackService methods.""" + mock_get_project_by_id = mocker.patch( + "compliance_api.services.epic_track_service.track_service.TrackService.get_project_by_id" + ) + mock_get_project_by_id.return_value = { + "abbreviation": fake.word(), + "ea_certificate": "", + "type": {"name": ""}, + "sub_type": {"name": ""}, + "proponent": {"name": ""}, + } + + yield mock_get_project_by_id + + +@pytest.fixture +def created_staff(mocker): + """Create staff.""" + user_data = StaffScenario.default_data.value + auth_user_guid = fake.word() + user_data["auth_user_guid"] = auth_user_guid + new_user = StaffScenario.create(user_data) + return new_user + + +@pytest.fixture +def created_case_file(mocker, created_staff, mock_track_service): + """Create case file.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True + case_file_data = CasefileScenario.default_value.value + case_file_data["primary_officer_id"] = created_staff.id + created_case_file = CaseFileService.create(case_file_data) + return created_case_file def test_get_complaint_sources(client, auth_header): @@ -13,3 +59,19 @@ def test_get_complaint_sources(client, auth_header): result = client.get(url, headers=auth_header) assert len(result.json) == 4 assert result.status_code == HTTPStatus.OK + + +def test_get_case_files_without_case_file_id_passed( + client, auth_header, mocker, mock_track_service, created_staff, created_case_file +): + """Get case files.""" + contains_role = mocker.patch("compliance_api.auth.jwt.contains_role") + contains_role.return_value = True + complaint_data = ComplaintScenario.complaint_default.value + complaint_data["lead_officer_id"] = created_staff.id + complaint_data["case_file_id"] = created_case_file.id + ComplaintService.create(complaint_data) + + result = client.get(urljoin(API_BASE_URL, "complaints"), headers=auth_header) + + assert result.status_code == HTTPStatus.OK