From 43033a6c6a3ba6aed3d143ef425f6813d2283ad9 Mon Sep 17 00:00:00 2001 From: Odysseus Chiu Date: Tue, 10 Dec 2024 13:14:28 -0800 Subject: [PATCH] 24491 - Pay documents route (#1855) --- pay-api/src/pay_api/dtos/documents.py | 21 ++++++++ pay-api/src/pay_api/resources/v1/__init__.py | 2 + pay-api/src/pay_api/resources/v1/documents.py | 47 ++++++++++++++++ .../src/pay_api/services/documents_service.py | 53 +++++++++++++++++++ .../src/pay_api/services/report_service.py | 5 +- pay-api/src/pay_api/utils/enums.py | 12 +++++ pay-api/src/pay_api/utils/errors.py | 2 + pay-api/src/pay_api/version.py | 2 +- pay-api/tests/unit/api/test_cors_preflight.py | 10 ++++ pay-api/tests/unit/api/test_documents.py | 43 +++++++++++++++ 10 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 pay-api/src/pay_api/dtos/documents.py create mode 100644 pay-api/src/pay_api/resources/v1/documents.py create mode 100644 pay-api/src/pay_api/services/documents_service.py create mode 100644 pay-api/tests/unit/api/test_documents.py diff --git a/pay-api/src/pay_api/dtos/documents.py b/pay-api/src/pay_api/dtos/documents.py new file mode 100644 index 000000000..313d0a1d8 --- /dev/null +++ b/pay-api/src/pay_api/dtos/documents.py @@ -0,0 +1,21 @@ +"""Rationale behind creating these DTOS below. + +1. To ensure that the request and response payloads are validated before they are passed to the service layer. +2. To ensure that the request and response payloads are consistent across the application. +3. To ensure that the request and response payloads are consistent with the API documentation. + +In the near future, will find a library that generates our API spec based off of these DTOs. +""" + +from typing import Optional + +from attrs import define + +from pay_api.utils.serializable import Serializable + + +@define +class DocumentsGetRequest(Serializable): + """Retrieve documents DTO.""" + + document_type: Optional[str] = None diff --git a/pay-api/src/pay_api/resources/v1/__init__.py b/pay-api/src/pay_api/resources/v1/__init__.py index 20235de62..6751f822e 100644 --- a/pay-api/src/pay_api/resources/v1/__init__.py +++ b/pay-api/src/pay_api/resources/v1/__init__.py @@ -24,6 +24,7 @@ from .bank_accounts import bp as bank_accounts_bp from .code import bp as code_bp from .distributions import bp as distributions_bp +from .documents import bp as documents_bp from .eft_short_names import bp as eft_short_names_bp from .fas import fas_refund_bp, fas_routing_slip_bp from .fee import bp as fee_bp @@ -58,6 +59,7 @@ def init_app(self, app): self.app.register_blueprint(bank_accounts_bp) self.app.register_blueprint(code_bp) self.app.register_blueprint(distributions_bp) + self.app.register_blueprint(documents_bp) self.app.register_blueprint(eft_short_names_bp) self.app.register_blueprint(fas_refund_bp) self.app.register_blueprint(fas_routing_slip_bp) diff --git a/pay-api/src/pay_api/resources/v1/documents.py b/pay-api/src/pay_api/resources/v1/documents.py new file mode 100644 index 000000000..b49d3cb9e --- /dev/null +++ b/pay-api/src/pay_api/resources/v1/documents.py @@ -0,0 +1,47 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Resource for documents endpoints.""" +from http import HTTPStatus + +from flask import Blueprint, Response, current_app, request +from flask_cors import cross_origin + +from pay_api.dtos.documents import DocumentsGetRequest +from pay_api.exceptions import BusinessException +from pay_api.services.documents_service import DocumentsService +from pay_api.utils.auth import jwt as _jwt +from pay_api.utils.endpoints_enums import EndpointEnum +from pay_api.utils.enums import ContentType + +bp = Blueprint("DOCUMENTS", __name__, url_prefix=f"{EndpointEnum.API_V1.value}/documents") + + +@bp.route("", methods=["GET", "OPTIONS"]) +@cross_origin(origins="*", methods=["GET"]) +@_jwt.requires_auth +def get_documents(): + """Get Pay documents.""" + current_app.logger.info("get_documents") + return response + except BusinessException as exception: + return exception.response() diff --git a/pay-api/src/pay_api/services/documents_service.py b/pay-api/src/pay_api/services/documents_service.py new file mode 100644 index 000000000..bbc04f84a --- /dev/null +++ b/pay-api/src/pay_api/services/documents_service.py @@ -0,0 +1,53 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This manages the retrieval of report-api documents.""" +from flask import current_app + +from pay_api.exceptions import BusinessException +from pay_api.services import ReportService +from pay_api.services.report_service import ReportRequest +from pay_api.utils.enums import ContentType, DocumentTemplate, DocumentType +from pay_api.utils.errors import Error + + +class DocumentsService: + """Service to manage document retrieval.""" + + @classmethod + def get_document(cls, document_type: str): + """Get document file.""" + current_app.logger.debug("get_document") + return report_response, report_name + + @classmethod + def _get_document_report_params(cls, document_type: str): + """Get document report parameters.""" + if document_type == DocumentType.EFT_INSTRUCTIONS.value: + return "bcrs_eft_instructions.pdf", DocumentTemplate.EFT_INSTRUCTIONS.value + return None, None diff --git a/pay-api/src/pay_api/services/report_service.py b/pay-api/src/pay_api/services/report_service.py index fdfc22e1c..54b94117c 100644 --- a/pay-api/src/pay_api/services/report_service.py +++ b/pay-api/src/pay_api/services/report_service.py @@ -13,6 +13,7 @@ # limitations under the License. """Service to manage report generation.""" from dataclasses import dataclass +from typing import Optional from flask import current_app @@ -29,8 +30,8 @@ class ReportRequest: content_type: str report_name: str template_name: str - template_vars: dict populate_page_number: bool + template_vars: Optional[dict] = None class ReportService: @@ -42,7 +43,7 @@ def get_request_payload(request: ReportRequest) -> dict: return { "reportName": request.report_name, "templateName": request.template_name, - "templateVars": request.template_vars, + "templateVars": request.template_vars or {}, "populatePageNumber": request.populate_page_number, } diff --git a/pay-api/src/pay_api/utils/enums.py b/pay-api/src/pay_api/utils/enums.py index d6821de6d..8c7733c4a 100644 --- a/pay-api/src/pay_api/utils/enums.py +++ b/pay-api/src/pay_api/utils/enums.py @@ -450,3 +450,15 @@ class SuspensionReasonCodes(Enum): OVERDUE_EFT = "OVERDUE_EFT" PAD_NSF = "PAD_NSF" + + +class DocumentTemplate(Enum): + """Document Templates.""" + + EFT_INSTRUCTIONS = "eft_instructions" + + +class DocumentType(Enum): + """Document Types.""" + + EFT_INSTRUCTIONS = "eftInstructions" diff --git a/pay-api/src/pay_api/utils/errors.py b/pay-api/src/pay_api/utils/errors.py index 775605005..0713f3469 100644 --- a/pay-api/src/pay_api/utils/errors.py +++ b/pay-api/src/pay_api/utils/errors.py @@ -82,6 +82,8 @@ class Error(Enum): CFS_INVOICES_MISMATCH = "CFS_INVOICES_MISMATCH", HTTPStatus.BAD_REQUEST + DOCUMENT_TYPE_INVALID = "DOCUMENT_TYPE_INVALID", HTTPStatus.BAD_REQUEST + EFT_PARTIAL_REFUND = "EFT_PARTIAL_REFUND", HTTPStatus.BAD_REQUEST EFT_CREDIT_AMOUNT_UNEXPECTED = ( "EFT_CREDIT_AMOUNT_UNEXPECTED", diff --git a/pay-api/src/pay_api/version.py b/pay-api/src/pay_api/version.py index 98239652f..02f786cf3 100644 --- a/pay-api/src/pay_api/version.py +++ b/pay-api/src/pay_api/version.py @@ -22,4 +22,4 @@ Development release segment: .devN """ -__version__ = "1.22.10" # pylint: disable=invalid-name +__version__ = "1.22.11" # pylint: disable=invalid-name diff --git a/pay-api/tests/unit/api/test_cors_preflight.py b/pay-api/tests/unit/api/test_cors_preflight.py index e1cebc1be..73c6dc341 100644 --- a/pay-api/tests/unit/api/test_cors_preflight.py +++ b/pay-api/tests/unit/api/test_cors_preflight.py @@ -297,6 +297,16 @@ def test_preflight_eft_shortnames(app, client, jwt, session): assert_access_control_headers(rv, "*", "POST") +def test_preflight_documents(app, client, jwt, session): + """Assert preflight responses for documents are correct.""" + rv = client.options( + "/api/v1/documents", + headers={"Access-Control-Request-Method": "GET"}, + ) + assert rv.status_code == 200 + assert_access_control_headers(rv, "*", "GET") + + def assert_access_control_headers(rv, origins: str, methods: str): """Assert access control headers are correct.""" assert rv.headers["Access-Control-Allow-Origin"] == origins diff --git a/pay-api/tests/unit/api/test_documents.py b/pay-api/tests/unit/api/test_documents.py new file mode 100644 index 000000000..1be514d34 --- /dev/null +++ b/pay-api/tests/unit/api/test_documents.py @@ -0,0 +1,43 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests to assure the documents end-point. + +Test-Suite to ensure that the /documents endpoint is working as expected. +""" +import pytest + +from pay_api.utils.enums import DocumentType +from pay_api.utils.errors import Error +from tests.utilities.base_test import get_claims, token_header + + +@pytest.mark.parametrize("document_type", [None, "ABC"]) +def test_documents_invalid(session, client, jwt, app, document_type): + """Assert that the endpoint returns 400 for invalid documents.""" + token = jwt.create_jwt(get_claims(), token_header) + headers = {"Authorization": f"Bearer {token}", "content-type": "application/json"} + document_url = "/api/v1/documents" if document_type is None else f"/api/v1/documents?documentType={document_type}" + rv = client.get(document_url, headers=headers) + assert rv.status_code == 400 + assert rv.json["type"] == Error.DOCUMENT_TYPE_INVALID.name + + +@pytest.mark.parametrize("document_type", [DocumentType.EFT_INSTRUCTIONS.value]) +def test_documents(session, client, jwt, app, document_type): + """Assert that the endpoint returns 200 for valid documents.""" + token = jwt.create_jwt(get_claims(), token_header) + headers = {"Authorization": f"Bearer {token}", "content-type": "application/json"} + rv = client.get(f"/api/v1/documents?documentType={document_type}", headers=headers) + assert rv.status_code == 200