diff --git a/strr-api/src/strr_api/models/rental.py b/strr-api/src/strr_api/models/rental.py index 0e8edd4a..68314652 100644 --- a/strr-api/src/strr_api/models/rental.py +++ b/strr-api/src/strr_api/models/rental.py @@ -128,7 +128,7 @@ class Document(BaseModel): eligibility_id = db.Column(db.Integer, db.ForeignKey("eligibilities.id"), nullable=False) file_name = db.Column(db.String, nullable=False) file_type = db.Column(db.String, nullable=False) # e.g., 'pdf', 'jpeg', etc. - path = db.Column(db.String, nullable=False) # e.g., 'pdf', 'jpeg', etc. + path = db.Column(db.String, nullable=False) eligibility = relationship("Eligibility", back_populates="documents") diff --git a/strr-api/src/strr_api/requests/RegistrationRequest.py b/strr-api/src/strr_api/requests/RegistrationRequest.py index b02119c3..0719a2e6 100644 --- a/strr-api/src/strr_api/requests/RegistrationRequest.py +++ b/strr-api/src/strr_api/requests/RegistrationRequest.py @@ -24,7 +24,14 @@ class Registration: """Registration payload object.""" def __init__( - self, primaryContact, unitAddress, unitDetails, listingDetails, principalResidence, secondaryContact=None + self, + primaryContact, + unitAddress, + unitDetails, + listingDetails, + principalResidence, + secondaryContact=None, + documents=[], ): self.primaryContact = Contact(**primaryContact) self.secondaryContact = None @@ -34,6 +41,7 @@ def __init__( self.unitDetails = UnitDetails(**unitDetails) self.listingDetails = [ListingDetails(**item) for item in listingDetails] self.principalResidence = PrincipalResidence(**principalResidence) + self.documents = [Document(**document) for document in documents] class PrincipalResidence: @@ -121,3 +129,10 @@ def __init__(self, name, dateOfBirth, details, mailingAddress, socialInsuranceNu self.businessNumber = businessNumber self.details = ContactDetails(**details) self.mailingAddress = MailingAddress(**mailingAddress) + + +class Document: + def __init__(self, fileName: str, fileType: str, fileKey: str): + self.fileName = fileName + self.fileKey = fileKey + self.fileType = fileType diff --git a/strr-api/src/strr_api/resources/application.py b/strr-api/src/strr_api/resources/application.py index 630d5354..5d6468e6 100644 --- a/strr-api/src/strr_api/resources/application.py +++ b/strr-api/src/strr_api/resources/application.py @@ -42,6 +42,7 @@ from flasgger import swag_from from flask import Blueprint, g, jsonify, request from flask_cors import cross_origin +from werkzeug.utils import secure_filename from strr_api.common.auth import jwt from strr_api.enums.enum import ErrorMessage, Role @@ -56,8 +57,17 @@ from strr_api.requests import RegistrationRequest from strr_api.responses import AutoApprovalRecord, Events, LTSARecord from strr_api.schemas.utils import validate -from strr_api.services import ApplicationService, ApprovalService, EventsService, LtsaService, UserService, strr_pay +from strr_api.services import ( + ApplicationService, + ApprovalService, + DocumentService, + EventsService, + LtsaService, + UserService, + strr_pay, +) from strr_api.services.application_service import APPLICATION_STATES_STAFF_ACTION, APPLICATION_TERMINAL_STATES +from strr_api.validators.DocumentUploadValidator import validate_document_upload from strr_api.validators.RegistrationRequestValidator import validate_registration_request logger = logging.getLogger("api") @@ -193,7 +203,7 @@ def update_application_payment_details(application_id): if not application: raise AuthException() invoice_details = strr_pay.get_payment_details_by_invoice_id( - jwt, application.paymentAccount, application.invoice_id + jwt, application.payment_account, application.invoice_id ) application = ApplicationService.update_application_payment_details_and_status(application, invoice_details) return jsonify(ApplicationService.serialize(application)), HTTPStatus.OK @@ -379,3 +389,106 @@ def update_application_status(application_id): except Exception as exception: logger.error(exception) return error_response(ErrorMessage.PROCESSING_ERROR.value, HTTPStatus.INTERNAL_SERVER_ERROR) + + +@bp.route("//documents", methods=("POST",)) +@swag_from({"security": [{"Bearer": []}]}) +@cross_origin(origin="*") +@jwt.requires_auth +def upload_registration_supporting_document(application_id): + """ + Upload a supporting document for a STRR application. + --- + tags: + - application + parameters: + - in: path + name: application_id + type: integer + required: true + description: Application ID + - name: file + in: formData + type: file + required: true + description: The file to upload + consumes: + - multipart/form-data + responses: + 201: + description: + 400: + description: + 401: + description: + 403: + description: + 502: + description: + """ + + try: + account_id = request.headers.get("Account-Id", None) + file = validate_document_upload(request.files) + + # only allow upload for registrations that belong to the user + application = ApplicationService.get_application(application_id=application_id, account_id=account_id) + if not application: + raise AuthException() + + filename = secure_filename(file.filename) + + document = DocumentService.upload_document(filename, file.content_type, file.read()) + return document, HTTPStatus.CREATED + except AuthException as auth_exception: + return exception_response(auth_exception) + except ValidationException as auth_exception: + return exception_response(auth_exception) + except ExternalServiceException as service_exception: + return exception_response(service_exception) + + +@bp.route("//documents/", methods=("DELETE",)) +@swag_from({"security": [{"Bearer": []}]}) +@cross_origin(origin="*") +@jwt.requires_auth +def delete_document(application_id, document_key): + """ + Delete document. + --- + tags: + - application + parameters: + - in: path + name: application_id + type: integer + required: true + description: Application Id + - in: path + name: file_key + type: string + required: true + description: File key from the upload document response + responses: + 204: + description: + 401: + description: + 403: + description: + 502: + description: + """ + + try: + # only allow upload for registrations that belong to the user + application = ApplicationService.get_application(application_id=application_id) + if not application: + raise AuthException() + + DocumentService.delete_document(document_key) + return "", HTTPStatus.NO_CONTENT + except AuthException as auth_exception: + return exception_response(auth_exception) + except ExternalServiceException as external_exception: + return exception_response(external_exception) diff --git a/strr-api/src/strr_api/resources/registrations.py b/strr-api/src/strr_api/resources/registrations.py index 00943252..3c8af828 100644 --- a/strr-api/src/strr_api/resources/registrations.py +++ b/strr-api/src/strr_api/resources/registrations.py @@ -43,21 +43,13 @@ from flasgger import swag_from from flask import Blueprint, current_app, g, jsonify, request, send_file from flask_cors import cross_origin -from werkzeug.utils import secure_filename from strr_api.common.auth import jwt -from strr_api.enums.enum import RegistrationSortBy, RegistrationStatus -from strr_api.exceptions import ( - AuthException, - ExternalServiceException, - ValidationException, - error_response, - exception_response, -) +from strr_api.enums.enum import RegistrationSortBy, RegistrationStatus, Role +from strr_api.exceptions import AuthException, ExternalServiceException, error_response, exception_response from strr_api.models import User from strr_api.responses import Document, Events, Pagination, Registration -from strr_api.services import EventsService, GCPStorageService, RegistrationService -from strr_api.validators.DocumentUploadValidator import validate_document_upload +from strr_api.services import DocumentService, EventsService, GCPStorageService, RegistrationService logger = logging.getLogger("api") bp = Blueprint("registrations", __name__) @@ -227,69 +219,11 @@ def get_registration(registration_id): return exception_response(auth_exception) -@bp.route("//documents", methods=("POST",)) -@swag_from({"security": [{"Bearer": []}]}) -@cross_origin(origin="*") -@jwt.requires_auth -def upload_registration_supporting_document(registration_id): - """ - Upload a supporting document for a STRR registration. - --- - tags: - - registration - parameters: - - in: path - name: registration_id - type: integer - required: true - description: ID of the registration - - name: file - in: formData - type: file - required: true - description: The file to upload - consumes: - - multipart/form-data - responses: - 201: - description: - 400: - description: - 401: - description: - 403: - description: - 502: - description: - """ - - try: - file = validate_document_upload(request.files) - - # only allow upload for registrations that belong to the user - registration = RegistrationService.get_registration(g.jwt_oidc_token_info, registration_id) - if not registration: - raise AuthException() - - filename = secure_filename(file.filename) - - document = RegistrationService.save_registration_document( - registration.eligibility.id, filename, file.content_type, file.read() - ) - return jsonify(Document.from_db(document).model_dump(mode="json")), HTTPStatus.CREATED - except AuthException as auth_exception: - return exception_response(auth_exception) - except ValidationException as auth_exception: - return exception_response(auth_exception) - except ExternalServiceException as service_exception: - return exception_response(service_exception) - - @bp.route("//documents", methods=("GET",)) @swag_from({"security": [{"Bearer": []}]}) @cross_origin(origin="*") @jwt.requires_auth -def get_registration_supporting_documents(registration_id): +def get_registration_documents(registration_id): """ Get registration supporting documents for given registration id. --- @@ -316,7 +250,7 @@ def get_registration_supporting_documents(registration_id): if not registration: raise AuthException() - documents = RegistrationService.get_registration_documents(registration_id) + documents = DocumentService.get_registration_documents(registration_id) return ( jsonify([Document.from_db(document).model_dump(mode="json") for document in documents]), HTTPStatus.OK, @@ -363,7 +297,7 @@ def get_registration_supporting_document_by_id(registration_id, document_id): if not registration: raise AuthException() - document = RegistrationService.get_registration_document(registration_id, document_id) + document = DocumentService.get_registration_document(registration_id, document_id) if not document: return error_response(HTTPStatus.NOT_FOUND, "Document not found") @@ -412,7 +346,7 @@ def get_registration_file_by_id(registration_id, document_id): if not registration: raise AuthException() - document = RegistrationService.get_registration_document(registration_id, document_id) + document = DocumentService.get_registration_document(registration_id, document_id) if not document: return error_response(HTTPStatus.NOT_FOUND, "Document not found") @@ -426,52 +360,6 @@ def get_registration_file_by_id(registration_id, document_id): return exception_response(external_exception) -@bp.route("//documents/", methods=("DELETE",)) -@swag_from({"security": [{"Bearer": []}]}) -@cross_origin(origin="*") -@jwt.requires_auth -def delete_registration_supporting_document_by_id(registration_id, document_id): - """ - Delete registration supporting document for given registration id and document id. - --- - tags: - - registration - parameters: - - in: path - name: registration_id - type: integer - required: true - description: ID of the registration - - in: path - name: document_id - type: integer - required: true - description: ID of the document - responses: - 204: - description: - 401: - description: - 403: - description: - 502: - description: - """ - - try: - # only allow upload for registrations that belong to the user - registration = RegistrationService.get_registration(g.jwt_oidc_token_info, registration_id) - if not registration: - raise AuthException() - - RegistrationService.delete_registration_document(registration_id, document_id) - return "", HTTPStatus.NO_CONTENT - except AuthException as auth_exception: - return exception_response(auth_exception) - except ExternalServiceException as external_exception: - return exception_response(external_exception) - - @bp.route("//events", methods=("GET",)) @swag_from({"security": [{"Bearer": []}]}) @cross_origin(origin="*") @@ -520,6 +408,7 @@ def get_registration_events(registration_id): @swag_from({"security": [{"Bearer": []}]}) @cross_origin(origin="*") @jwt.requires_auth +@jwt.has_one_of_roles([Role.STAFF.value]) def issue_registration_certificate(registration_id): """ Manually generate and issue a STRR registration certificate. diff --git a/strr-api/src/strr_api/responses/DocumentResponse.py b/strr-api/src/strr_api/responses/DocumentResponse.py index dfb8c178..8f8e8ffa 100644 --- a/strr-api/src/strr_api/responses/DocumentResponse.py +++ b/strr-api/src/strr_api/responses/DocumentResponse.py @@ -9,17 +9,17 @@ class Document(BaseModel): """Document response object.""" - registration_id: int - document_id: int - file_name: str - file_type: str + registrationId: int + documentId: int + fileName: str + fileType: str @classmethod def from_db(cls, source: models.Document): """Return a Document object from a database model.""" return cls( - registration_id=source.eligibility.registration_id, - document_id=source.id, - file_name=source.file_name, - file_type=source.file_type, + registrationId=source.eligibility.registration_id, + documentId=source.id, + fileName=source.file_name, + fileType=source.file_type, ) diff --git a/strr-api/src/strr_api/services/__init__.py b/strr-api/src/strr_api/services/__init__.py index d7cfe83e..c767fc58 100644 --- a/strr-api/src/strr_api/services/__init__.py +++ b/strr-api/src/strr_api/services/__init__.py @@ -35,6 +35,7 @@ from .user_service import UserService # isort: skip from .application_service import ApplicationService from .auth_service import AuthService +from .document_service import DocumentService from .events_service import EventsService from .gcp_storage_service import GCPStorageService from .geocoder_service import GeoCoderService diff --git a/strr-api/src/strr_api/services/document_service.py b/strr-api/src/strr_api/services/document_service.py new file mode 100644 index 00000000..06d54630 --- /dev/null +++ b/strr-api/src/strr_api/services/document_service.py @@ -0,0 +1,80 @@ +# Copyright © 2024 Province of British Columbia +# +# Licensed under the BSD 3 Clause License, (the "License"); +# you may not use this file except in compliance with the License. +# The template for the license can be found here +# https://opensource.org/license/bsd-3-clause/ +# +# Redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# pylint: disable=R0913 +# pylint: disable=E1102 +"""Manages registration model interactions.""" +from strr_api.models import Document, Eligibility +from strr_api.services.gcp_storage_service import GCPStorageService + + +class DocumentService: + """Service to manage documents.""" + + @classmethod + def upload_document(cls, file_name, file_type, file_contents): + """Uploads the document to GCP.""" + + blob_name = GCPStorageService.upload_registration_document(file_type, file_contents) + file_key = blob_name + response = { + "fileName": file_name, + "fileType": file_type, + "fileKey": file_key, + } + return response + + @classmethod + def delete_document(cls, document_path): + """Delete document using document path.""" + GCPStorageService.delete_registration_document(document_path) + return True + + @classmethod + def get_registration_documents(cls, registration_id): + """Get registration documents by registration id.""" + return ( + Document.query.join(Eligibility, Eligibility.id == Document.eligibility_id) + .filter(Eligibility.registration_id == registration_id) + .all() + ) + + @classmethod + def get_registration_document(cls, registration_id, document_id): + """Get registration document by id.""" + return ( + Document.query.join(Eligibility, Eligibility.id == Document.eligibility_id) + .filter(Eligibility.registration_id == registration_id) + .filter(Document.id == document_id) + .one_or_none() + ) diff --git a/strr-api/src/strr_api/services/registration_service.py b/strr-api/src/strr_api/services/registration_service.py index f3de30d1..49fe3e84 100644 --- a/strr-api/src/strr_api/services/registration_service.py +++ b/strr-api/src/strr_api/services/registration_service.py @@ -58,7 +58,6 @@ db, ) from strr_api.services.events_service import EventsService -from strr_api.services.gcp_storage_service import GCPStorageService from strr_api.utils.user_context import UserContext, user_context @@ -129,6 +128,21 @@ def create_registration(cls, user_id, sbc_account_id, registration_request: requ db.session.flush() db.session.refresh(property_manager) + eligibility = Eligibility( + is_principal_residence=registration_request.principalResidence.isPrincipalResidence, + agreed_to_rental_act=registration_request.principalResidence.agreedToRentalAct, + non_principal_option=registration_request.principalResidence.nonPrincipalOption, + specified_service_provider=registration_request.principalResidence.specifiedServiceProvider, + agreed_to_submit=registration_request.principalResidence.agreedToSubmit, + ) + + documents = [] + for doc in registration_request.documents: + document = Document(file_name=doc.fileName, file_type=doc.fileType, path=doc.fileKey) + documents.append(document) + if documents: + eligibility.documents = documents + start_date = datetime.utcnow() registration_number = RegistrationService._get_registration_number() registration = Registration( @@ -155,13 +169,7 @@ def create_registration(cls, user_id, sbc_account_id, registration_request: requ ownership_type=registration_request.unitDetails.ownershipType, rental_platforms=[RentalPlatform(url=listing.url) for listing in registration_request.listingDetails], ), - eligibility=Eligibility( - is_principal_residence=registration_request.principalResidence.isPrincipalResidence, - agreed_to_rental_act=registration_request.principalResidence.agreedToRentalAct, - non_principal_option=registration_request.principalResidence.nonPrincipalOption, - specified_service_provider=registration_request.principalResidence.specifiedServiceProvider, - agreed_to_submit=registration_request.principalResidence.agreedToSubmit, - ), + eligibility=eligibility, ) registration.save() return registration @@ -258,54 +266,6 @@ def get_registration(cls, jwt_oidc_token_info, registration_id): query = query.filter_by(user_id=user.id) return query.one_or_none() - @classmethod - def save_registration_document(cls, eligibility_id, file_name, file_type, file_contents): - """Save STRR uploaded document to database.""" - - blob_name = GCPStorageService.upload_registration_document(file_type, file_contents) - path = blob_name - - registration_document = Document( - eligibility_id=eligibility_id, - file_name=file_name, - file_type=file_type, - path=path, - ) - db.session.add(registration_document) - db.session.commit() - db.session.refresh(registration_document) - return registration_document - - @classmethod - def get_registration_documents(cls, registration_id): - """Get registration documents by registration id.""" - return ( - Document.query.join(Eligibility, Eligibility.id == Document.eligibility_id) - .filter(Eligibility.registration_id == registration_id) - .all() - ) - - @classmethod - def get_registration_document(cls, registration_id, document_id): - """Get registration document by id.""" - return ( - Document.query.join(Eligibility, Eligibility.id == Document.eligibility_id) - .filter(Eligibility.registration_id == registration_id) - .filter(Document.id == document_id) - .one_or_none() - ) - - @classmethod - def delete_registration_document(cls, registration_id, document_id): - """Delete registration document by id.""" - document = RegistrationService.get_registration_document(registration_id, document_id) - if not document: - return False - GCPStorageService.delete_registration_document(document.path) - db.session.delete(document) - db.session.commit() - return True - @classmethod @user_context def generate_registration_certificate(cls, registration: Registration, **kwargs): diff --git a/strr-api/tests/unit/resources/test_registration_applications.py b/strr-api/tests/unit/resources/test_registration_applications.py index 78b96911..24cec62f 100644 --- a/strr-api/tests/unit/resources/test_registration_applications.py +++ b/strr-api/tests/unit/resources/test_registration_applications.py @@ -1,8 +1,10 @@ import json import os +from datetime import datetime from http import HTTPStatus from unittest.mock import patch +from strr_api.enums.enum import PaymentStatus from strr_api.models import Application, Events from tests.unit.utils.auth_helpers import PUBLIC_USER, STAFF_ROLE, create_header @@ -16,6 +18,13 @@ ACCOUNT_ID = 1234 MOCK_INVOICE_RESPONSE = {"id": 123, "statusCode": "CREATED", "paymentAccount": {"accountId": ACCOUNT_ID}} +MOCK_PAYMENT_COMPLETED_RESPONSE = { + "id": 123, + "statusCode": "COMPLETED", + "paymentAccount": {"accountId": ACCOUNT_ID}, + "paymentDate": datetime.now().isoformat(), +} +MOCK_DOCUMENT_UPLOAD = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../mocks/file/document_upload.txt") @patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE) @@ -134,3 +143,102 @@ def test_get_application_events(session, client, jwt): events_response = rv.json assert events_response[0].get("eventName") == Events.EventName.APPLICATION_SUBMITTED assert events_response[0].get("eventType") == Events.EventType.APPLICATION + + +def test_update_application_payment(session, client, jwt): + with open(CREATE_REGISTRATION_REQUEST) as f: + headers = create_header(jwt, [PUBLIC_USER], "Account-Id") + headers["Account-Id"] = ACCOUNT_ID + with patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE): + json_data = json.load(f) + rv = client.post("/applications", json=json_data, headers=headers) + response_json = rv.json + application_id = response_json.get("header").get("id") + with patch( + "strr_api.services.strr_pay.get_payment_details_by_invoice_id", return_value=MOCK_PAYMENT_COMPLETED_RESPONSE + ): + rv = client.put(f"/applications/{application_id}/payment-details", json={}, headers=headers) + assert HTTPStatus.OK == rv.status_code + response_json = rv.json + assert response_json.get("header").get("status") == Application.Status.PAID + + +@patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE) +def test_examiner_reject_application(session, client, jwt): + with open(CREATE_REGISTRATION_REQUEST) as f: + headers = create_header(jwt, [PUBLIC_USER], "Account-Id") + headers["Account-Id"] = ACCOUNT_ID + json_data = json.load(f) + rv = client.post("/applications", json=json_data, headers=headers) + response_json = rv.json + application_id = response_json.get("header").get("id") + + application = Application.find_by_id(application_id=application_id) + application.payment_status = PaymentStatus.COMPLETED.value + application.save() + + staff_headers = create_header(jwt, [STAFF_ROLE], "Account-Id") + status_update_request = {"status": "rejected"} + rv = client.put(f"/applications/{application_id}/status", json=status_update_request, headers=staff_headers) + assert HTTPStatus.OK == rv.status_code + response_json = rv.json + assert response_json.get("header").get("status") == Application.Status.REJECTED + assert response_json.get("header").get("reviewer").get("username") is not None + + +@patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE) +def test_examiner_approve_application(session, client, jwt): + with open(CREATE_REGISTRATION_REQUEST) as f: + headers = create_header(jwt, [PUBLIC_USER], "Account-Id") + headers["Account-Id"] = ACCOUNT_ID + json_data = json.load(f) + rv = client.post("/applications", json=json_data, headers=headers) + response_json = rv.json + application_id = response_json.get("header").get("id") + + application = Application.find_by_id(application_id=application_id) + application.payment_status = PaymentStatus.COMPLETED.value + application.save() + + staff_headers = create_header(jwt, [STAFF_ROLE], "Account-Id") + status_update_request = {"status": "approved"} + rv = client.put(f"/applications/{application_id}/status", json=status_update_request, headers=staff_headers) + assert HTTPStatus.OK == rv.status_code + response_json = rv.json + assert response_json.get("header").get("status") == Application.Status.APPROVED + assert response_json.get("header").get("reviewer").get("username") is not None + assert response_json.get("header").get("registrationId") is not None + assert response_json.get("header").get("registrationNumber") is not None + + +def test_post_and_delete_registration_documents(session, client, jwt): + with patch("strr_api.services.strr_pay.create_invoice", return_value=MOCK_INVOICE_RESPONSE): + headers = create_header(jwt, [PUBLIC_USER], "Account-Id") + headers["Account-Id"] = ACCOUNT_ID + with open(CREATE_REGISTRATION_REQUEST) as f: + json_data = json.load(f) + rv = client.post("/applications", json=json_data, headers=headers) + response_json = rv.json + application_id = response_json.get("header").get("id") + with patch( + "strr_api.services.gcp_storage_service.GCPStorageService.upload_registration_document", + return_value="Test Key", + ): + with open(MOCK_DOCUMENT_UPLOAD, "rb") as df: + data = {"file": (df, MOCK_DOCUMENT_UPLOAD)} + rv = client.post( + f"/applications/{application_id}/documents", + content_type="multipart/form-data", + data=data, + headers=headers, + ) + + assert rv.status_code == HTTPStatus.CREATED + fileKey = rv.json.get("fileKey") + assert fileKey == "Test Key" + with patch( + "strr_api.services.gcp_storage_service.GCPStorageService.delete_registration_document", + return_value="Test Key", + ): + rv = client.delete(f"/applications/{application_id}/documents/{fileKey}", headers=headers) + assert rv.status_code == HTTPStatus.NO_CONTENT diff --git a/strr-api/tests/unit/resources/test_registrations.py b/strr-api/tests/unit/resources/test_registrations.py index c5577252..bcb0c3ad 100644 --- a/strr-api/tests/unit/resources/test_registrations.py +++ b/strr-api/tests/unit/resources/test_registrations.py @@ -24,7 +24,6 @@ MOCK_ACCOUNT_MINIMUM_FIELDS_REQUEST = os.path.join( os.path.dirname(os.path.realpath(__file__)), f"../../mocks/json/{REGISTRATION_MINIMUM_FIELDS}.json" ) -MOCK_DOCUMENT_UPLOAD = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../mocks/file/document_upload.txt") @patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) @@ -55,102 +54,7 @@ def test_get_registration_documents_401(client): assert rv.status_code == HTTPStatus.UNAUTHORIZED -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_get_registration_documents_403(client): - rv = client.get("/registrations/1/documents") - assert rv.status_code == HTTPStatus.FORBIDDEN - - -@patch("strr_api.services.registration_service.RegistrationService.save_registration_document", new=fake_document) -@patch("strr_api.services.registration_service.RegistrationService.get_registration", new=fake_registration) -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_post_registration_documents_201(client): - with open(MOCK_DOCUMENT_UPLOAD, "rb") as f: - data = {"file": (f, MOCK_DOCUMENT_UPLOAD)} - rv = client.post("/registrations/1/documents", content_type="multipart/form-data", data=data) - assert rv.status_code == HTTPStatus.CREATED - - -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_post_registration_documents_400(client): - rv = client.post("/registrations/1/documents", content_type="multipart/form-data", data={}) - assert rv.status_code == HTTPStatus.BAD_REQUEST - - -def test_post_registration_documents_401(client): - with open(MOCK_DOCUMENT_UPLOAD, "rb") as f: - data = {"file": (f, MOCK_DOCUMENT_UPLOAD)} - rv = client.post("/registrations/1/documents", content_type="multipart/form-data", data=data) - assert rv.status_code == HTTPStatus.UNAUTHORIZED - - -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_post_registration_documents_403(client): - with open(MOCK_DOCUMENT_UPLOAD, "rb") as f: - data = {"file": (f, MOCK_DOCUMENT_UPLOAD)} - rv = client.post("/registrations/1/documents", content_type="multipart/form-data", data=data) - assert rv.status_code == HTTPStatus.FORBIDDEN - - -@patch("strr_api.services.registration_service.RegistrationService.get_registration", new=fake_registration) -@patch( - "strr_api.services.gcp_storage_service.GCPStorageService.registration_documents_bucket", - new=throw_external_service_exception, -) -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_post_registration_documents_502(client): - with open(MOCK_DOCUMENT_UPLOAD, "rb") as f: - data = {"file": (f, MOCK_DOCUMENT_UPLOAD)} - rv = client.post("/registrations/1/documents", content_type="multipart/form-data", data=data) - assert rv.status_code == HTTPStatus.BAD_GATEWAY - - -@patch("strr_api.services.registration_service.RegistrationService.get_registration", new=fake_registration) -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_delete_registration_documents_204(client): - rv = client.delete("/registrations/1/documents/1") - assert rv.status_code == HTTPStatus.NO_CONTENT - - -def test_delete_registration_documents_401(client): - rv = client.delete("/registrations/1/documents/1") - assert rv.status_code == HTTPStatus.UNAUTHORIZED - - -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_delete_registration_documents_403(client): - rv = client.delete("/registrations/1/documents/1") - assert rv.status_code == HTTPStatus.FORBIDDEN - - -@patch("strr_api.services.registration_service.RegistrationService.get_registration", new=fake_registration) -@patch("strr_api.services.registration_service.RegistrationService.get_registration_document", new=fake_document) -@patch( - "strr_api.services.gcp_storage_service.GCPStorageService.registration_documents_bucket", - new=throw_external_service_exception, -) -@patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) -@patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header) -@patch("flask_jwt_oidc.JwtManager._validate_token", new=no_op) -def test_delete_registration_documents_502(client): - rv = client.delete("/registrations/1/documents/1") - assert rv.status_code == HTTPStatus.BAD_GATEWAY - - -@patch("strr_api.services.registration_service.RegistrationService.get_registration_document", new=fake_document) +@patch("strr_api.services.DocumentService.get_registration_document", new=fake_document) @patch("strr_api.services.registration_service.RegistrationService.get_registration", new=fake_registration) @patch("strr_api.models.user.User.find_by_jwt_token", new=fake_user_from_token) @patch("flask_jwt_oidc.JwtManager.get_token_auth_header", new=fake_get_token_auth_header)