From c1afdea9fac7dee442384beefc449f1acf221d89 Mon Sep 17 00:00:00 2001 From: BrandonSharratt Date: Wed, 9 Oct 2024 14:16:39 -0700 Subject: [PATCH] Request Endpoint (#209) * fixes * Request to omit obscure section * remove errant nbsp * checkbox fixes * WIP - Add Request to omit endpoint, tests outstanding * add postman test * fix postman tests * add tests * fix lint * add id field to request * remove exception handling that was only removed to debug --- .../versions/20241009_1344_3b039edc1404_.py | 84 ++++++++ btr-api/src/btr_api/enums/__init__.py | 3 + btr-api/src/btr_api/enums/completing_party.py | 42 ++++ .../src/btr_api/enums/individual_at_risk.py | 42 ++++ .../btr_api/enums/information_to_omit_type.py | 44 +++++ btr-api/src/btr_api/models/__init__.py | 2 + btr-api/src/btr_api/models/request.py | 150 ++++++++++++++ btr-api/src/btr_api/resources/__init__.py | 6 + btr-api/src/btr_api/resources/request.py | 161 +++++++++++++++ .../schemas/btr-bods/btr-bods-request.json | 76 +++++++ btr-api/src/btr_api/services/__init__.py | 1 + btr-api/src/btr_api/services/auth.py | 2 +- btr-api/src/btr_api/services/request.py | 131 ++++++++++++ .../postman/btr-api.postman_collection.json | 141 +++++++++++++ btr-api/tests/unit/models/test_request.py | 23 +++ btr-api/tests/unit/resources/test_requests.py | 187 ++++++++++++++++++ btr-api/tests/unit/services/test_request.py | 57 ++++++ btr-api/tests/unit/utils/__init__.py | 1 + btr-api/tests/unit/utils/mock_data.py | 13 ++ 19 files changed, 1165 insertions(+), 1 deletion(-) create mode 100644 btr-api/migrations/versions/20241009_1344_3b039edc1404_.py create mode 100644 btr-api/src/btr_api/enums/completing_party.py create mode 100644 btr-api/src/btr_api/enums/individual_at_risk.py create mode 100644 btr-api/src/btr_api/enums/information_to_omit_type.py create mode 100644 btr-api/src/btr_api/models/request.py create mode 100644 btr-api/src/btr_api/resources/request.py create mode 100644 btr-api/src/btr_api/schemas/btr-bods/btr-bods-request.json create mode 100644 btr-api/src/btr_api/services/request.py create mode 100644 btr-api/tests/unit/models/test_request.py create mode 100644 btr-api/tests/unit/resources/test_requests.py create mode 100644 btr-api/tests/unit/services/test_request.py diff --git a/btr-api/migrations/versions/20241009_1344_3b039edc1404_.py b/btr-api/migrations/versions/20241009_1344_3b039edc1404_.py new file mode 100644 index 00000000..4c3303b4 --- /dev/null +++ b/btr-api/migrations/versions/20241009_1344_3b039edc1404_.py @@ -0,0 +1,84 @@ +"""empty message + +Revision ID: 3b039edc1404 +Revises: 8d8279a86def +Create Date: 2024-10-09 13:44:54.558111 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3b039edc1404' +down_revision = '8d8279a86def' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('request', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('uuid', sa.Uuid(), nullable=False), + sa.Column('full_name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('birthdate', sa.Date(), nullable=False), + sa.Column('business_identifier', sa.String(), nullable=False), + sa.Column('information_to_omit', sa.Enum('ALL', 'FULL_NAME', 'BIRTH_YEAR', 'CITIZENSHIP_PR', name='informationtoomittype'), nullable=False), + sa.Column('individual_at_risk', sa.Enum('SI', 'HOUSEHOLD', name='individualatrisk'), nullable=False), + sa.Column('reasons', sa.String(), nullable=False), + sa.Column('completing_party', sa.Enum('SI', 'REPRESENTATIVE', name='completingparty'), nullable=False), + sa.Column('completing_name', sa.String(), nullable=False), + sa.Column('completing_email', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.Column('version', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sqlite_autoincrement=True + ) + with op.batch_alter_table('request', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_request_business_identifier'), ['business_identifier'], unique=False) + batch_op.create_index(batch_op.f('ix_request_uuid'), ['uuid'], unique=False) + + op.create_table('request_history', + sa.Column('id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('uuid', sa.Uuid(), autoincrement=False, nullable=False), + sa.Column('full_name', sa.String(), autoincrement=False, nullable=False), + sa.Column('email', sa.String(), autoincrement=False, nullable=False), + sa.Column('birthdate', sa.Date(), autoincrement=False, nullable=False), + sa.Column('business_identifier', sa.String(), autoincrement=False, nullable=False), + sa.Column('information_to_omit', sa.Enum('ALL', 'FULL_NAME', 'BIRTH_YEAR', 'CITIZENSHIP_PR', name='informationtoomittype'), autoincrement=False, nullable=False), + sa.Column('individual_at_risk', sa.Enum('SI', 'HOUSEHOLD', name='individualatrisk'), autoincrement=False, nullable=False), + sa.Column('reasons', sa.String(), autoincrement=False, nullable=False), + sa.Column('completing_party', sa.Enum('SI', 'REPRESENTATIVE', name='completingparty'), autoincrement=False, nullable=False), + sa.Column('completing_name', sa.String(), autoincrement=False, nullable=False), + sa.Column('completing_email', sa.String(), autoincrement=False, nullable=False), + sa.Column('created_at', sa.DateTime(), autoincrement=False, nullable=False), + sa.Column('updated_at', sa.DateTime(), autoincrement=False, nullable=False), + sa.Column('version', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('changed', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id', 'version'), + sqlite_autoincrement=True + ) + with op.batch_alter_table('request_history', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_request_history_business_identifier'), ['business_identifier'], unique=False) + batch_op.create_index(batch_op.f('ix_request_history_uuid'), ['uuid'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + with op.batch_alter_table('request_history', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_request_history_uuid')) + batch_op.drop_index(batch_op.f('ix_request_history_business_identifier')) + + op.drop_table('request_history') + with op.batch_alter_table('request', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_request_uuid')) + batch_op.drop_index(batch_op.f('ix_request_business_identifier')) + + op.drop_table('request') + # ### end Alembic commands ### diff --git a/btr-api/src/btr_api/enums/__init__.py b/btr-api/src/btr_api/enums/__init__.py index a7bfa021..14a8356c 100644 --- a/btr-api/src/btr_api/enums/__init__.py +++ b/btr-api/src/btr_api/enums/__init__.py @@ -17,3 +17,6 @@ from .log_level import LogLevel from .redaction_types import RedactionType from .user_types import UserType +from .completing_party import CompletingParty +from .individual_at_risk import IndividualAtRisk +from .information_to_omit_type import InformationToOmitType diff --git a/btr-api/src/btr_api/enums/completing_party.py b/btr-api/src/btr_api/enums/completing_party.py new file mode 100644 index 00000000..976e81d3 --- /dev/null +++ b/btr-api/src/btr_api/enums/completing_party.py @@ -0,0 +1,42 @@ +# 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. +"""Enum for email type.""" +from btr_api.common.enum import BaseEnum, auto + + +class CompletingParty(BaseEnum): + """Enum for the email type.""" + + SI = auto() + REPRESENTATIVE = auto() diff --git a/btr-api/src/btr_api/enums/individual_at_risk.py b/btr-api/src/btr_api/enums/individual_at_risk.py new file mode 100644 index 00000000..c63bd8d2 --- /dev/null +++ b/btr-api/src/btr_api/enums/individual_at_risk.py @@ -0,0 +1,42 @@ +# 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. +"""Enum for email type.""" +from btr_api.common.enum import BaseEnum, auto + + +class IndividualAtRisk(BaseEnum): + """Enum for the email type.""" + + SI = auto() + HOUSEHOLD = auto() diff --git a/btr-api/src/btr_api/enums/information_to_omit_type.py b/btr-api/src/btr_api/enums/information_to_omit_type.py new file mode 100644 index 00000000..60eac46b --- /dev/null +++ b/btr-api/src/btr_api/enums/information_to_omit_type.py @@ -0,0 +1,44 @@ +# 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. +"""Enum for email type.""" +from btr_api.common.enum import BaseEnum, auto + + +class InformationToOmitType(BaseEnum): + """Enum for the email type.""" + + ALL = auto() + FULL_NAME = auto() + BIRTH_YEAR = auto() + CITIZENSHIP_PR = auto() diff --git a/btr-api/src/btr_api/models/__init__.py b/btr-api/src/btr_api/models/__init__.py index 91f9619b..6e4c6d1a 100644 --- a/btr-api/src/btr_api/models/__init__.py +++ b/btr-api/src/btr_api/models/__init__.py @@ -39,6 +39,7 @@ from .submission import SubmissionType from .user import User from .user import UserRoles +from .request import Request __all__ = ( @@ -49,4 +50,5 @@ "SubmissionType", "User", "UserRoles", + "Request", ) diff --git a/btr-api/src/btr_api/models/request.py b/btr-api/src/btr_api/models/request.py new file mode 100644 index 00000000..1439f976 --- /dev/null +++ b/btr-api/src/btr_api/models/request.py @@ -0,0 +1,150 @@ +# 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. +"""Manages Request (to omit) data.""" +from __future__ import annotations + +import uuid +from datetime import date, datetime + +from sqlalchemy.orm import Mapped, mapped_column +from sql_versioning import Versioned + +from btr_api.enums import InformationToOmitType +from btr_api.enums import IndividualAtRisk +from btr_api.enums import CompletingParty + +from .base import Base + + +class Request(Versioned, Base): + """Stores request (to omit) data.""" + + __tablename__ = 'request' + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + uuid: Mapped[uuid.UUID] = mapped_column(index=True) + full_name: Mapped[str] = mapped_column(nullable=False) + email: Mapped[str] = mapped_column(nullable=False) + birthdate: Mapped[date] = mapped_column(nullable=False) + business_identifier: Mapped[str] = mapped_column(nullable=False, index=True) + information_to_omit: Mapped[InformationToOmitType] = mapped_column(nullable=False) + individual_at_risk: Mapped[IndividualAtRisk] = mapped_column(nullable=False) + reasons: Mapped[str] = mapped_column(nullable=False) + completing_party: Mapped[CompletingParty] = mapped_column(nullable=False) + completing_name: Mapped[str] = mapped_column(nullable=False) + completing_email: Mapped[str] = mapped_column(nullable=False) + created_at: Mapped[datetime] = mapped_column(nullable=False, default=datetime.now()) + updated_at: Mapped[datetime] = mapped_column(nullable=False, default=datetime.now()) + + def __init__(self, data): + self.uuid = uuid.uuid4() + self.full_name = data['fullName'] + self.email = data['email'] + self.birthdate = data['birthdate'] + self.business_identifier = data['businessIdentifier'] + self.information_to_omit = data['informationToOmit'] + self.individual_at_risk = data['individualAtRisk'] + self.reasons = data['reasons'] + self.completing_party = data['completingParty'] + self.completing_name = data['completingName'] + self.completing_email = data['completingEmail'] + + @classmethod + def find_by_uuid(cls, request_id: int) -> Request | None: + """Return the person by id.""" + return cls.query.filter_by(uuid=request_id).one_or_none() + + @classmethod + def __setitem__(cls, key, value): + match key: + case 'id': + cls.id = value + case 'uuid': + cls.uuid = value + case 'email': + cls.email = value + case 'birthdate': + cls.birthdate = value + case 'reasons': + cls.reasons = value + case 'fullName': + print('hi') + print(value) + print(cls.full_name) + cls.full_name = value + print('hi2 ' + value + " " + cls.full_name) + case 'businessIdentifier': + cls.business_identifier = value + case 'informationToOmit': + cls.information_to_omit = value + case 'individualAtRisk': + cls.individual_at_risk = value + case 'completingParty': + cls.completing_party = value + case 'completingEmail': + cls.completing_email = value + case 'completingName': + cls.completing_name = value + case 'createdAt': + cls.created_at = value + case 'updatedAt': + cls.updated_at = value + + +class RequestSerializer: + """Serializer for requests. Can convert to dict, string from request model. """ + + @staticmethod + def to_str(request: Request): + """Return string representation of request model.""" + return str(RequestSerializer.to_dict(request)) + + @staticmethod + def to_dict(request: Request) -> dict: + """Return the request class as a dict for response purposes.""" + return { + 'id': request.id, + 'uuid': request.uuid, + 'fullName': request.full_name, + 'email': request.email, + 'birthdate': request.birthdate, + 'businessIdentifier': request.business_identifier, + 'informationToOmit': request.information_to_omit, + 'individualAtRisk': request.individual_at_risk, + 'reasons': request.reasons, + 'completingParty': request.completing_party, + 'completingEmail': request.completing_email, + 'completingName': request.completing_name, + 'createdAt': request.created_at.isoformat(), + 'updatedAt': request.updated_at.isoformat(), + } diff --git a/btr-api/src/btr_api/resources/__init__.py b/btr-api/src/btr_api/resources/__init__.py index 372e899a..66fb7552 100644 --- a/btr-api/src/btr_api/resources/__init__.py +++ b/btr-api/src/btr_api/resources/__init__.py @@ -43,6 +43,7 @@ from .ops import bp as ops_endpoint from .submission import bp as submission_endpoint from .json_schema import bp as json_schema_endpoint +from .request import bp as request_endpoint def register_endpoints(app: Flask): @@ -81,3 +82,8 @@ def register_endpoints(app: Flask): url_prefix="/json-schemas", blueprint=json_schema_endpoint, ) + + app.register_blueprint( + url_prefix="/requests", + blueprint=request_endpoint, + ) diff --git a/btr-api/src/btr_api/resources/request.py b/btr-api/src/btr_api/resources/request.py new file mode 100644 index 00000000..4a80ca87 --- /dev/null +++ b/btr-api/src/btr_api/resources/request.py @@ -0,0 +1,161 @@ +# Copyright © 2023 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. +""" +This module defines request-related API endpoints. + +It provides endpoints to create and retrieve request objects. +The 'get_request' and 'create_request' functions define the 'GET' and 'POST' methods respectively. + +""" +import uuid +from http import HTTPStatus +from flask import Blueprint +from flask import current_app +from flask import g +from flask import jsonify +from flask import request +from flask_cors import cross_origin + +from btr_api.common.auth import jwt +from btr_api.exceptions import AuthException +from btr_api.exceptions import error_request_response +from btr_api.exceptions import exception_response +from btr_api.models import Request as RequestModel +from btr_api.models import User as UserModel +from btr_api.models.request import RequestSerializer +from btr_api.services import btr_auth +from btr_api.services import SchemaService +from btr_api.services import RequestService + +bp = Blueprint('request', __name__) + + +@bp.route('/', methods=('GET',)) +@jwt.requires_auth +def get_request(id: uuid.UUID | None = None): # pylint: disable=redefined-builtin + """Get the request by id.""" + # try: + if req := RequestModel.find_by_uuid(id): + account_id = request.headers.get('Account-Id', None) + btr_auth.product_authorizations(request=request, account_id=account_id) + # if btr_auth.get_user_type() == UserType.USER_STAFF: + return jsonify(RequestSerializer.to_dict(req)), HTTPStatus.OK + + return {}, HTTPStatus.NOT_FOUND + + # except AuthException as aex: + # return exception_response(aex) + # except Exception as exception: # noqa: B902 + # return exception_response(exception) + + +@bp.route('/', methods=('POST',)) +@cross_origin(origin='*') +def create_request(): + """ + Create a new request. + + Returns: + A tuple containing the response JSON and the HTTP status code. + """ + try: + + json_input = request.get_json() + + # validate payload + schema_name = 'btr-bods-request.json' + schema_service = SchemaService() + [valid, errors] = schema_service.validate(schema_name, json_input) + if not valid: + return error_request_response('Invalid schema', HTTPStatus.BAD_REQUEST, errors) + + # create request + req = RequestService.create_request(json_input) + req.save() + + return jsonify(RequestSerializer.to_dict(req)), HTTPStatus.CREATED + + except AuthException as aex: + return exception_response(aex) + except Exception as exception: # noqa: B902 + current_app.logger.error(exception.with_traceback(None)) + return exception_response(exception) + + +@bp.route('/', methods=('PUT',)) +@cross_origin(origin='*') +@jwt.requires_auth +def update_request(req_id: str): + """ + Update an existing request. + Any fields that are not in the payload will be ignored. + To remove a field the payload explicitly pass null + + Returns: + A tuple containing the response JSON and the HTTP status code. + """ + try: + UserModel.get_or_create_user_by_jwt(g.jwt_oidc_token_info) + account_id = request.headers.get('Account-Id', None) + req = RequestModel.find_by_uuid(req_id) + if req: + btr_auth.product_authorizations(request, account_id) + + req_json = request.get_json() + req = RequestService.update_request(req, req_json) + + req_d = RequestSerializer.to_dict(req) + try: + req_d['birthdate'] = req_d['birthdate'].strftime('%Y-%m-%d') + except Exception: + pass + + # validate resulting full request + schema_name = 'btr-bods-request.json' + schema_service = SchemaService() + [valid, errors] = schema_service.validate(schema_name, req_d) + if not valid: + return error_request_response('Invalid schema', HTTPStatus.BAD_REQUEST, errors) + + req.save() + + return jsonify(RequestSerializer.to_dict(req)), HTTPStatus.OK + + return {}, HTTPStatus.NOT_FOUND + + except AuthException as aex: + return exception_response(aex) + except Exception as exception: # noqa: B902 + current_app.logger.error(exception.with_traceback(None)) + return exception_response(exception) diff --git a/btr-api/src/btr_api/schemas/btr-bods/btr-bods-request.json b/btr-api/src/btr_api/schemas/btr-bods/btr-bods-request.json new file mode 100644 index 00000000..5f686871 --- /dev/null +++ b/btr-api/src/btr_api/schemas/btr-bods/btr-bods-request.json @@ -0,0 +1,76 @@ +{ + "$id": "https://btr.gov.bc.ca/.well_known/schemas/btr-bods-request.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.1", + "type": "object", + "title": "Request (to omit)", + "description": "A request to omit data from public search.", + "properties": { + "fullName": { + "title": "Names", + "description": "One or more known names for this individual.", + "type": "string" + }, + "birthdate": { + "title": "Date of birth", + "description": "The date of birth for this individual. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "format": "date", + "propertyOrder": 35 + }, + "email": { + "type": "string", + "format": "email" + }, + "businessIdentifier": { + "type": "string" + }, + "informationToOmit": { + "type": "string", + "enum": [ + "ALL", + "FULL_NAME", + "BIRTH_YEAR", + "CITIZENSHIP_PR" + ] + }, + "individualAtRisk": { + "type": "string", + "enum": [ + "SI", + "HOUSEHOLD" + ] + }, + "reasons": { + "type": "string" + }, + "completingParty": { + "type": "string", + "enum": [ + "SI", + "REPRESENTATIVE" + ] + }, + "completingName": { + "title": "Names", + "description": "Name for the completing individual.", + "type": "string" + }, + "completingEmail": { + "type": "string", + "format": "email" + } + }, + "required": [ + "fullName", + "birthdate", + "email", + "businessIdentifier", + "informationToOmit", + "individualAtRisk", + "reasons", + "completingParty", + "completingName", + "completingEmail" + ] +} diff --git a/btr-api/src/btr_api/services/__init__.py b/btr-api/src/btr_api/services/__init__.py index 47cb931c..c782533c 100644 --- a/btr-api/src/btr_api/services/__init__.py +++ b/btr-api/src/btr_api/services/__init__.py @@ -40,6 +40,7 @@ from .pay import PayService from .registries_search import RegSearchService from .submission import SubmissionService +from .request import RequestService PAYMENT_REQUEST_TEMPLATE = { 'filingInfo': {'filingTypes': [{'filingTypeCode': 'REGSIGIN'}]}, diff --git a/btr-api/src/btr_api/services/auth.py b/btr-api/src/btr_api/services/auth.py index 42ede69f..78cafd9b 100644 --- a/btr-api/src/btr_api/services/auth.py +++ b/btr-api/src/btr_api/services/auth.py @@ -146,7 +146,7 @@ def get_authorization_header(self, request: Request) -> str: return authorization_header - @auth_cache.cached(timeout=600, make_cache_key=get_cache_key, cache_none=False) + # @auth_cache.cached(timeout=600, make_cache_key=get_cache_key, cache_none=False) def product_authorizations(self, request, account_id: str) -> None: """Get the products associated with the user and account_id.""" if not account_id: diff --git a/btr-api/src/btr_api/services/request.py b/btr-api/src/btr_api/services/request.py new file mode 100644 index 00000000..5b75f6b5 --- /dev/null +++ b/btr-api/src/btr_api/services/request.py @@ -0,0 +1,131 @@ +# Copyright © 2023 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. +""" +This module contains the services necessary for handling Submissions, +including creating a submission. Each service is encapsulated in its own class. + +The `SubmissionService` class provides the method `create_submission`, +which accepts a dictionary as an input and returns a SubmissionModel object. + +The individual services can be invoked as per the requirements. +""" +from datetime import datetime + +from btr_api.models import Request as RequestModel + + +class RequestService: # pylint: disable=too-few-public-methods + """ + The `RequestService` class is responsible for handling submissions and creating request models. + + Creates a request model based on the given request dictionary. The request dictionary should contain + the necessary information for creating a request. + """ + + @staticmethod + def create_request(request_dict: dict) -> RequestModel: + """ + This method creates the current request request dict. + + Parameters: + - request_dict (dict): A dictionary containing the submission data. It should have the following keys: + - 'effectiveDate': A string representing the effective date of the submission in ISO format (YYYY-MM-DD). + - 'businessIdentifier': A string representing the business identifier for the submission. + - Any other key-value pairs representing additional payload data. + + Returns: + - RequestnModel: A RequestModel object that represents the created request. + """ + + # init request + request = RequestModel(request_dict) + request.created_at = datetime.today().strftime('%Y-%m-%dT%H:%M:%S') + request.updated_at = datetime.today().strftime('%Y-%m-%dT%H:%M:%S') + request.save_to_session() + return request + + @staticmethod + def update_request(request: RequestModel, + request_dict: dict) -> RequestModel: + """ + This method replaces the current request for the business using the provided request dict. + + Parameters: + - request_dict (dict): A dictionary containing the submission data. It should have the following keys: + - 'effectiveDate': A string representing the effective date of the submission in ISO format (YYYY-MM-DD). + - 'businessIdentifier': A string representing the business identifier for the submission. + + Returns: + - RequestModel: A RequestModel object that represents the updated request + """ + + # I dislike that I can't use a for loop here but it doesn't like to access the model with [key] + # even if i override __setitems__ or add a set function + if 'fullName' in request_dict: + request.full_name = request_dict['fullName'] + + if 'email' in request_dict: + request.email = request_dict['email'] + + if 'birthdate' in request_dict: + request.birthdate = request_dict['birthdate'] + + if 'businessIdentifier' in request_dict: + request.business_identifier = request_dict['businessIdentifier'] + + if 'informationToOmit' in request_dict: + request.information_to_omit = request_dict['informationToOmit'] + + if 'individualAtRisk' in request_dict: + request.individual_at_risk = request_dict['individualAtRisk'] + + if 'reasons' in request_dict: + request.reasons = request_dict['reasons'] + + if 'completingParty' in request_dict: + request.completing_party = request_dict['completingParty'] + + if 'completingEmail' in request_dict: + request.completing_email = request_dict['completingEmail'] + + if 'completingName' in request_dict: + request.completing_name = request_dict['completingName'] + + # for key in dict_keys: + # if key in request_dict: + # request[key] = request_dict[key] + + request['updatedAt'] = datetime.today().strftime('%Y-%m-%dT%H:%M:%S') + + return request diff --git a/btr-api/tests/postman/btr-api.postman_collection.json b/btr-api/tests/postman/btr-api.postman_collection.json index ca3ce050..dc462fbe 100644 --- a/btr-api/tests/postman/btr-api.postman_collection.json +++ b/btr-api/tests/postman/btr-api.postman_collection.json @@ -410,6 +410,147 @@ "response": [] } ] + }, + { + "name": "Request (to omit)", + "item": [ + { + "name": "create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "pm.environment.set(\"request_id\", jsonData.uuid);", + "pm.environment.set(\"request_birthdate\", jsonData.birthate);", + "pm.environment.set(\"request_completingEmail\", jsonData.completingEmail);", + "pm.environment.set(\"request_completingParty\", jsonData.completingParty);", + "pm.environment.set(\"request_createdAt\", jsonData.createdAt);", + "pm.environment.set(\"request_email\", jsonData.email);", + "pm.environment.set(\"request_full_name\", jsonData.fullName);", + "pm.environment.set(\"request_atRisk\", jsonData.individualAtRisk);", + "pm.environment.set(\"request_omit\", jsonData.informationToOmit);", + "pm.environment.set(\"request_reasons\", jsonData.reasons);", + "pm.environment.set(\"request_updated\", jsonData.updatedAt);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"fullName\": \"John Doe\",\n \"email\": \"jdoe@gmail.com\",\n \"birthdate\": \"1970-03-31\",\n \"businessIdentifier\": \"BUSID\",\n \"informationToOmit\": \"ALL\",\n \"individualAtRisk\": \"SI\",\n \"reasons\": \"I have some\",\n \"completingParty\": \"SI\",\n \"completingName\": \"John Doe\",\n \"completingEmail\": \"jdoe@gmail.com\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{internal_url}}/requests", + "host": [ + "{{internal_url}}" + ], + "path": [ + "requests" + ] + } + }, + "response": [] + }, + { + "name": "fetch", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{internal_url}}/requests/{{request_id}}", + "host": [ + "{{internal_url}}" + ], + "path": [ + "requests", + "{{request_id}}" + ] + } + }, + "response": [] + }, + { + "name": "update", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "pm.expect(jsonData.uuid).to.eql(pm.collectionVariables.get(\"request_id\"));", + "pm.expect(jsonData.birthate).to.eql(pm.collectionVariables.get(\"request_birthdate\"));", + "pm.expect(jsonData.completingEmail).to.eql(pm.collectionVariables.get(\"request_completingEmail\"));", + "pm.expect(jsonData.completingParty).to.eql(pm.collectionVariables.get(\"request_completingParty\"));", + "pm.expect(jsonData.createdAt).to.eql(pm.collectionVariables.get(\"request_createdAt\"));", + "pm.expect(jsonData.email).to.eql(pm.collectionVariables.get(\"request_email\"));", + "pm.expect(jsonData.request_full_name).to.not.eql(pm.collectionVariables.get(\"fullName\"));", + "pm.expect(jsonData.request_full_name).to.eql(\"John Doe2\");", + "pm.expect(jsonData.individualAtRisk).to.eql(pm.collectionVariables.get(\"request_atRisk\"));", + "pm.expect(jsonData.informationToOmit).to.eql(pm.collectionVariables.get(\"request_omit\"));", + "pm.expect(jsonData.reasons).to.eql(pm.collectionVariables.get(\"request_reasons\"));", + "pm.expect(jsonData.updatedAt).to.not.eql(pm.collectionVariables.get(\"request_updated\"));" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"fullName\": \"John Doe2\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{internal_url}}/requests/{{request_id}}", + "host": [ + "{{internal_url}}" + ], + "path": [ + "requests", + "{{request_id}}" + ] + } + }, + "response": [] + } + ] } ] } \ No newline at end of file diff --git a/btr-api/tests/unit/models/test_request.py b/btr-api/tests/unit/models/test_request.py new file mode 100644 index 00000000..4c630a55 --- /dev/null +++ b/btr-api/tests/unit/models/test_request.py @@ -0,0 +1,23 @@ +import pytest +import datetime + +from sqlalchemy import text + +from btr_api.models.request import Request + +from tests.unit.utils.db_helpers import clear_db +from tests.unit.utils.mock_data import REQUEST_DICT + + +def test_find_by_uuid(session): + # Prepare data + clear_db(session) + request = Request(REQUEST_DICT) + session.add(request) + session.commit() + + # Do test + result = Request.find_by_uuid(request.uuid) + + # Verify result + assert result == request diff --git a/btr-api/tests/unit/resources/test_requests.py b/btr-api/tests/unit/resources/test_requests.py new file mode 100644 index 00000000..77a40444 --- /dev/null +++ b/btr-api/tests/unit/resources/test_requests.py @@ -0,0 +1,187 @@ +""" Tests to ensure that the submission based end-points work correctly. +""" +import json +import os +from datetime import datetime +from http import HTTPStatus + +import pytest +import requests +from dateutil.relativedelta import relativedelta + +from btr_api.enums import UserType +from btr_api.models import Request as RequestModel +from btr_api.models import User as UserModel +from btr_api.models.request import RequestSerializer +from btr_api.services import RequestService +from btr_api.services.auth import auth_cache +from btr_api.utils import redact_information + +from tests.unit import nested_session +from tests.unit.models.test_user import sample_user +from tests.unit.utils import create_header +from tests.unit.utils.db_helpers import clear_db +from tests.unit.utils.mock_data import REQUEST_DICT + +UPDATE_R_DICT = { + 'fullName': 'edited' +} + +@pytest.mark.parametrize( + 'test_name, user_type', + [('test_get', UserType.USER_COMPETENT_AUTHORITY)], +) +def test_get(app, client, session, jwt, requests_mock, sample_user, test_name, user_type): + """Get the plot submissions. + A parameterized set of tests that runs defined scenarios. + """ + with nested_session(session): + clear_db(session) + # Setup + id = '' + req = RequestModel(REQUEST_DICT) + session.add(req) + session.commit() + id = req.uuid + + requests_mock.get( + f"{app.config.get('AUTH_SVC_URL')}/orgs/1/products?include_hidden=true", + json=[{'code': 'CA_SEARCH', 'subscriptionStatus': 'ACTIVE'}]) + # Test + rv = client.get( + f'/requests/{id}', + headers=create_header( + jwt, ['basic'], **{'Accept-Version': 'v1', 'content-type': 'application/json', 'Account-Id': 1} + ), + ) + + # Confirm outcome + assert rv.status_code == HTTPStatus.OK + +@pytest.mark.parametrize( + 'test_name, user_type', + [('test_get_no_auth', UserType.USER_COMPETENT_AUTHORITY)], +) +def test_get_fail_no_auth(app, client, session, jwt, requests_mock, sample_user, test_name, user_type): + """Get the plot submissions. + A parameterized set of tests that runs defined scenarios. + """ + with nested_session(session): + clear_db(session) + # Setup + id = '' + req = RequestModel(REQUEST_DICT) + session.add(req) + session.commit() + id = req.uuid + + # Test + rv = client.get( + f'/requests/{id}', + ) + + # Confirm outcome + assert rv.status_code == HTTPStatus.UNAUTHORIZED + + +@pytest.mark.parametrize( + 'test_name, user_type', + [('test_post', UserType.USER_COMPETENT_AUTHORITY)], +) +def test_post(app, client, session, jwt, requests_mock, sample_user, test_name, user_type): + """Get the plot submissions. + A parameterized set of tests that runs defined scenarios. + """ + with nested_session(session): + clear_db(session) + # Setup + requests_mock.get( + f"{app.config.get('AUTH_SVC_URL')}/orgs/1/products?include_hidden=true", + json=[{'code': 'CA_SEARCH', 'subscriptionStatus': 'ACTIVE'}]) + # Test + rv = client.post( + f'/requests', + json=REQUEST_DICT, + headers=create_header( + jwt, ['basic'], **{'Accept-Version': 'v1', 'content-type': 'application/json', 'Account-Id': 1} + ), + ) + + # Confirm outcome + assert rv.status_code == HTTPStatus.CREATED + + +@pytest.mark.parametrize( + 'test_name, user_type', + [('test_post_unauthorized', UserType.USER_COMPETENT_AUTHORITY)], +) +def test_post_unauth(app, client, session, jwt, requests_mock, sample_user, test_name, user_type): + """Get the plot submissions. + A parameterized set of tests that runs defined scenarios. + """ + with nested_session(session): + clear_db(session) + # Test + rv = client.post( + f'/requests', + json=REQUEST_DICT, + ) + + # Confirm outcome + assert rv.status_code == HTTPStatus.CREATED + +@pytest.mark.parametrize( + 'test_name, user_type', + [('test_put', UserType.USER_COMPETENT_AUTHORITY)], +) +def test_put(app, client, session, jwt, requests_mock, sample_user, test_name, user_type): + """Get the plot submissions. + A parameterized set of tests that runs defined scenarios. + """ + with nested_session(session): + clear_db(session) + # Setup + requests_mock.get( + f"{app.config.get('AUTH_SVC_URL')}/orgs/1/products?include_hidden=true", + json=[{'code': 'CA_SEARCH', 'subscriptionStatus': 'ACTIVE'}]) + rv = client.post( + f'/requests', + json=REQUEST_DICT, + ) + id = rv.json['uuid'] + # Test + rv = client.put( + f'/requests/{id}', + json=UPDATE_R_DICT, + headers=create_header( + jwt, ['basic'], **{'Accept-Version': 'v1', 'content-type': 'application/json', 'Account-Id': 1} + ), + ) + + # Confirm outcome + assert rv.status_code == HTTPStatus.OK + +@pytest.mark.parametrize( + 'test_name, user_type', + [('test_put_no_auth', UserType.USER_COMPETENT_AUTHORITY)], +) +def test_put_no_auth(app, client, session, jwt, requests_mock, sample_user, test_name, user_type): + """Get the plot submissions. + A parameterized set of tests that runs defined scenarios. + """ + with nested_session(session): + clear_db(session) + # Setup + rv = client.post( + f'/requests', + json=REQUEST_DICT, + ) + id = rv.json['uuid'] + # Test + rv = client.put( + f'/requests/{id}', + json=UPDATE_R_DICT, + ) + + # Confirm outcome + assert rv.status_code == HTTPStatus.UNAUTHORIZED \ No newline at end of file diff --git a/btr-api/tests/unit/services/test_request.py b/btr-api/tests/unit/services/test_request.py new file mode 100644 index 00000000..4db638d1 --- /dev/null +++ b/btr-api/tests/unit/services/test_request.py @@ -0,0 +1,57 @@ +from datetime import date + +from btr_api.services.auth import auth_cache +from btr_api.models.request import Request +from btr_api.services.request import RequestService + +from tests.unit import nested_session +from tests.unit.utils import REQUEST_DICT +from tests.unit.utils.db_helpers import clear_db + + +def test_create_request(session, app, requests_mock): + """Assure the create request works as expected.""" + requests_mock.post(app.config.get('SSO_SVC_TOKEN_URL'), json={'access_token': 'token'}) + requests_mock.post(f"{app.config.get('NOTIFY_SVC_URL')}", json={}) + + with nested_session(session): + auth_cache.clear() + request = RequestService.create_request(REQUEST_DICT) + request.save() + # Assert the properties of the resulting SubmissionModel instance + assert request.birthdate == date.fromisoformat(REQUEST_DICT['birthdate']) + assert request.full_name == REQUEST_DICT['fullName'] + assert request.email == REQUEST_DICT['email'] + assert request.business_identifier == REQUEST_DICT['businessIdentifier'] + assert request.information_to_omit == REQUEST_DICT['informationToOmit'] + assert request.individual_at_risk == REQUEST_DICT['individualAtRisk'] + assert request.reasons == REQUEST_DICT['reasons'] + assert request.completing_party == REQUEST_DICT['completingParty'] + assert request.completing_name == REQUEST_DICT['completingName'] + assert request.completing_email == REQUEST_DICT['completingEmail'] + +def test_update_request(session, app, requests_mock): + """Assure the create request works as expected.""" + requests_mock.post(app.config.get('SSO_SVC_TOKEN_URL'), json={'access_token': 'token'}) + requests_mock.post(f"{app.config.get('NOTIFY_SVC_URL')}", json={}) + + with nested_session(session): + auth_cache.clear() + request_dict2={ + 'fullName': 'edited', + 'email': 'edited@email.ca' + } + req = Request(REQUEST_DICT) + request = RequestService.update_request(req, request_dict2) + request.save() + # Assert the properties of the resulting SubmissionModel instance + assert request.birthdate == date.fromisoformat(REQUEST_DICT['birthdate']) + assert request.full_name == request_dict2['fullName'] + assert request.email == request_dict2['email'] + assert request.business_identifier == REQUEST_DICT['businessIdentifier'] + assert request.information_to_omit == REQUEST_DICT['informationToOmit'] + assert request.individual_at_risk == REQUEST_DICT['individualAtRisk'] + assert request.reasons == REQUEST_DICT['reasons'] + assert request.completing_party == REQUEST_DICT['completingParty'] + assert request.completing_name == REQUEST_DICT['completingName'] + assert request.completing_email == REQUEST_DICT['completingEmail'] \ No newline at end of file diff --git a/btr-api/tests/unit/utils/__init__.py b/btr-api/tests/unit/utils/__init__.py index 9557bf3e..da851947 100644 --- a/btr-api/tests/unit/utils/__init__.py +++ b/btr-api/tests/unit/utils/__init__.py @@ -34,4 +34,5 @@ """Tests utils module.""" from .auth_helpers import create_header, create_header_account, create_jwt from .mock_data import SUBMISSION_DICT +from .mock_data import REQUEST_DICT from .test_deep_spread import * \ No newline at end of file diff --git a/btr-api/tests/unit/utils/mock_data.py b/btr-api/tests/unit/utils/mock_data.py index 1cf0f864..5b9af122 100644 --- a/btr-api/tests/unit/utils/mock_data.py +++ b/btr-api/tests/unit/utils/mock_data.py @@ -444,3 +444,16 @@ } ] } + +REQUEST_DICT = { + 'fullName': 'John Doe', + 'email': 'jdoe@gmail.com', + 'birthdate': '1970-03-31', + 'businessIdentifier': 'BUSID', + 'informationToOmit': 'ALL', + 'individualAtRisk': 'SI', + 'reasons': 'I have some', + 'completingParty': 'SI', + 'completingName': 'John Doe', + 'completingEmail': 'jdoe@gmail.com' +} \ No newline at end of file