diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 3e4c040a3..2f9bcbcc6 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,3 +1,8 @@ +## January 22, 2024 +- **Task** Add missing unit tests for met api [🎟️DESENG-481](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-481) + - Added missing unit tests for met api + - Added unit tests for error handling for met api + ## January 15, 2024 - **Task** Audit for missing unit tests [🎟️DESENG-436](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-436) diff --git a/met-api/sample.env b/met-api/sample.env index 8dc1aa73f..61b7167ca 100644 --- a/met-api/sample.env +++ b/met-api/sample.env @@ -46,8 +46,8 @@ JWT_OIDC_ISSUER="" # default: constructed from base url and realm name JWT_OIDC_WELL_KNOWN_CONFIG="" # default: constructed from issuer JWT_OIDC_JWKS_URI="" # default: constructed from issuer # Object path to access roles from JWT token -JWT_OIDC_ROLE_CLAIM=realm_access.roles # SSO schema -# JWT_OIDC_ROLE_CLAIM=client_roles # Keycloak schema +# JWT_OIDC_ROLE_CLAIM=realm_access.roles # SSO schema +JWT_OIDC_ROLE_CLAIM=client_roles # Keycloak schema JWT_OIDC_CACHING_ENABLED=true # Enable caching of JWKS. JWT_OIDC_JWKS_CACHE_TIMEOUT=300 # Timeout for JWKS cache in seconds. @@ -124,6 +124,7 @@ DATABASE_TEST_NAME= DATABASE_TEST_HOST= DATABASE_TEST_PORT= +# A keycloak server is started automatically by Pytest; there is no need to start your own instance. KEYCLOAK_TEST_BASE_URL="http://localhost:8081" # Docker database settings diff --git a/met-api/src/met_api/config.py b/met-api/src/met_api/config.py index 31e892650..84c3da562 100644 --- a/met-api/src/met_api/config.py +++ b/met-api/src/met_api/config.py @@ -200,7 +200,7 @@ def SQLALCHEMY_DATABASE_URI(self) -> str: 'AUDIENCE': os.getenv('JWT_OIDC_AUDIENCE', 'account'), 'CACHING_ENABLED': str(env_truthy('JWT_OIDC_CACHING_ENABLED', 'True')), 'JWKS_CACHE_TIMEOUT': int(os.getenv('JWT_OIDC_JWKS_CACHE_TIMEOUT', '300')), - 'ROLE_CLAIM': os.getenv('JWT_OIDC_ROLE_CLAIM', 'realm_access.roles'), + 'ROLE_CLAIM': os.getenv('JWT_OIDC_ROLE_CLAIM', 'client_roles'), } # PostgreSQL configuration diff --git a/met-api/src/met_api/constants/widget.py b/met-api/src/met_api/constants/widget.py index a5ae6da25..5f05073c0 100644 --- a/met-api/src/met_api/constants/widget.py +++ b/met-api/src/met_api/constants/widget.py @@ -25,3 +25,4 @@ class WidgetType(IntEnum): EVENTS = 5 Map = 6 Video = 7 + Timeline = 9 diff --git a/met-api/src/met_api/models/widget_map.py b/met-api/src/met_api/models/widget_map.py index ea70ae00e..d384501c0 100644 --- a/met-api/src/met_api/models/widget_map.py +++ b/met-api/src/met_api/models/widget_map.py @@ -34,7 +34,7 @@ def get_map(cls, widget_id) -> list[WidgetMap]: @classmethod def update_map(cls, widget_id, map_data: dict) -> WidgetMap: """Update map.""" - query = WidgetMap.query.filter_by(WidgetMap.widget_id == widget_id) + query = WidgetMap.query.filter_by(widget_id=widget_id) widget_map: WidgetMap = query.first() if not widget_map: return map_data diff --git a/met-api/src/met_api/services/engagement_service.py b/met-api/src/met_api/services/engagement_service.py index 4392da86b..cb05061cf 100644 --- a/met-api/src/met_api/services/engagement_service.py +++ b/met-api/src/met_api/services/engagement_service.py @@ -56,7 +56,8 @@ def get_engagement(self, engagement_id) -> EngagementSchema: engagement = EngagementSchema().dump(engagement_model) engagement['banner_url'] = self.object_storage.get_url(engagement['banner_filename']) - return engagement + return engagement + return None def get_engagements_paginated( self, diff --git a/met-api/tests/unit/api/test_cac_form.py b/met-api/tests/unit/api/test_cac_form.py new file mode 100644 index 000000000..b1aac1345 --- /dev/null +++ b/met-api/tests/unit/api/test_cac_form.py @@ -0,0 +1,156 @@ +# Copyright © 2019 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 verify the CAC Form API end-point. + +Test-Suite to ensure that the /cacform endpoint is working as expected. +""" +import json +from http import HTTPStatus +from unittest.mock import MagicMock, patch +import pytest + +from met_api.exceptions.business_exception import BusinessException +from met_api.services.cac_form_service import CACFormService +from met_api.utils.enums import ContentType +from tests.utilities.factory_scenarios import TestCACForm, TestJwtClaims, TestSubscribeInfo, TestWidgetInfo +from tests.utilities.factory_utils import factory_auth_header, factory_engagement_model, factory_widget_model + + +def create_widget_subscription(client, jwt, widget_id, headers): + """Add subscribe widget to the engagement.""" + data = { + **TestSubscribeInfo.subscribe_info_2.value, + 'widget_id': widget_id, + } + return client.post( + f'/api/widgets/{widget_id}/subscribe', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value, + ) + + +def create_cac_form_submission(client, jwt, engagement_id, widget_id, form_data, headers): + """Create CAC Form data.""" + return client.post( + f'/api/engagements/{engagement_id}/cacform/{widget_id}', + data=json.dumps(form_data), + headers=headers, + content_type=ContentType.JSON.value, + ) + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.BAD_REQUEST), + (ValueError('Test error'), HTTPStatus.BAD_REQUEST), +]) +def test_create_form_submission(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that cac form submission can be POSTed.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget_subscribe['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_subscribe) + form_data = TestCACForm.form_data.value + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + rv = create_widget_subscription(client, jwt, widget.id, headers) + # Checking response + assert rv.status_code == HTTPStatus.OK.value + + # Sending POST request for CAC + rv = create_cac_form_submission(client, jwt, engagement.id, widget.id, form_data, headers) + # Checking response + assert rv.status_code == HTTPStatus.OK.value + response_data = json.loads(rv.data) + assert response_data.get('engagement_id') == engagement.id + + with patch.object(CACFormService, 'create_form_submission', side_effect=side_effect): + rv = client.post( + f'/api/engagements/{engagement.id}/cacform/{widget.id}', + data=json.dumps(form_data), + headers=headers, + content_type=ContentType.JSON.value, + ) + assert rv.status_code == expected_status + + with patch.object(CACFormService, 'create_form_submission', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.post( + f'/api/engagements/{engagement.id}/cacform/{widget.id}', + data=json.dumps(form_data), + headers=headers, + content_type=ContentType.JSON.value, + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_cac_form_spreadsheet(mocker, client, jwt, session, side_effect, expected_status, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that cac form submissions sheet can be fetched.""" + user, claims = setup_admin_user_and_claims + mock_post_generate_document_response = MagicMock() + mock_post_generate_document_response.content = b'mock data' + mock_post_generate_document_response.headers = {} + mock_post_generate_document_response.status_code = 200 + mock_post_generate_document = mocker.patch( + 'met_api.services.cdogs_api_service.CdogsApiService._post_generate_document', + return_value=mock_post_generate_document_response + ) + mock_get_access_token = mocker.patch( + 'met_api.services.cdogs_api_service.CdogsApiService._get_access_token', + return_value='token' + ) + + mock_upload_template_response = MagicMock() + mock_upload_template_response.headers = { + 'X-Template-Hash': 'hash_code' + } + mock_upload_template_response.status_code = 200 + mock_post_upload_template = mocker.patch( + 'met_api.services.cdogs_api_service.CdogsApiService._post_upload_template', + return_value=mock_upload_template_response + ) + + engagement = factory_engagement_model() + TestWidgetInfo.widget_subscribe['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_subscribe) + form_data = TestCACForm.form_data.value + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + rv = create_widget_subscription(client, jwt, widget.id, headers) + assert rv.status_code == HTTPStatus.OK.value + + # Sending POST request for CAC + rv = create_cac_form_submission(client, jwt, engagement.id, widget.id, form_data, headers) + assert rv.status_code == HTTPStatus.OK.value + + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.get(f'/api/engagements/{engagement.id}/cacform/sheet', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + mock_post_generate_document.assert_called() + mock_get_access_token.assert_called() + mock_post_upload_template.assert_called() + + with patch.object(CACFormService, 'export_cac_form_submissions_to_spread_sheet', side_effect=side_effect): + rv = client.get(f'/api/engagements/{engagement.id}/cacform/sheet', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_comment.py b/met-api/tests/unit/api/test_comment.py index 27a64d11f..114c9567d 100644 --- a/met-api/tests/unit/api/test_comment.py +++ b/met-api/tests/unit/api/test_comment.py @@ -24,6 +24,7 @@ from met_api.constants.membership_type import MembershipType from met_api.constants.staff_note_type import StaffNoteType +from met_api.services.comment_service import CommentService from met_api.utils import notification from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims @@ -47,6 +48,11 @@ def test_get_comments(client, jwt, session): # pylint:disable=unused-argument rv = client.get(f'/api/comments/survey/{survey.id}', headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 + with patch.object(CommentService, 'get_comments_paginated', side_effect=ValueError('Test error')): + rv = client.get(f'/api/comments/survey/{survey.id}', headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_review_comment(client, jwt, session): # pylint:disable=unused-argument """Assert that a comment can be reviewed.""" @@ -175,9 +181,9 @@ def test_review_comment_review_note(client, jwt, session): # pylint:disable=unu mock_mail.assert_called() -def test_get_comments_spreadsheet(mocker, client, jwt, session, - setup_admin_user_and_claims): # pylint:disable=unused-argument - """Assert that comments sheet can be fetched.""" +def test_get_comments_spreadsheet_staff(mocker, client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that staff comments sheet can be fetched.""" user, claims = setup_admin_user_and_claims mock_post_generate_document_response = MagicMock() @@ -214,3 +220,79 @@ def test_get_comments_spreadsheet(mocker, client, jwt, session, mock_post_generate_document.assert_called() mock_get_access_token.assert_called() mock_post_upload_template.assert_called() + + with patch.object(CommentService, 'export_comments_to_spread_sheet_staff', + side_effect=ValueError('Test error')): + rv = client.get(f'/api/comments/survey/{survey.id}/sheet/staff', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +def test_get_comments_spreadsheet_proponent(mocker, client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that proponent comments sheet can be fetched.""" + user, claims = setup_admin_user_and_claims + + mock_post_generate_document_response = MagicMock() + mock_post_generate_document_response.content = b'mock data' + mock_post_generate_document_response.headers = {} + mock_post_generate_document_response.status_code = 200 + mock_post_generate_document = mocker.patch( + 'met_api.services.cdogs_api_service.CdogsApiService._post_generate_document', + return_value=mock_post_generate_document_response + ) + mock_get_access_token = mocker.patch( + 'met_api.services.cdogs_api_service.CdogsApiService._get_access_token', + return_value='token' + ) + + mock_upload_template_response = MagicMock() + mock_upload_template_response.headers = { + 'X-Template-Hash': 'hash_code' + } + mock_upload_template_response.status_code = 200 + mock_post_upload_template = mocker.patch( + 'met_api.services.cdogs_api_service.CdogsApiService._post_upload_template', + return_value=mock_upload_template_response + ) + + participant = factory_participant_model() + survey, eng = factory_survey_and_eng_model() + submission = factory_submission_model(survey.id, eng.id, participant.id) + factory_comment_model(survey.id, submission.id) + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.get(f'/api/comments/survey/{survey.id}/sheet/proponent', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + mock_post_generate_document.assert_called() + mock_get_access_token.assert_called() + mock_post_upload_template.assert_called() + + with patch.object(CommentService, 'export_comments_to_spread_sheet_proponent', + side_effect=ValueError('Test error')): + rv = client.get(f'/api/comments/survey/{survey.id}/sheet/proponent', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +def test_get_comments_spreadsheet_without_role(mocker, client, jwt, session): # pylint:disable=unused-argument + """Assert that proponent comments sheet can be fetched.""" + mock_post_generate_document_response = MagicMock() + mock_post_generate_document_response.content = b'mock data' + mock_post_generate_document_response.headers = {} + mock_post_generate_document_response.status_code = 200 + + mock_upload_template_response = MagicMock() + mock_upload_template_response.headers = { + 'X-Template-Hash': 'hash_code' + } + mock_upload_template_response.status_code = 200 + + participant = factory_participant_model() + survey, eng = factory_survey_and_eng_model() + submission = factory_submission_model(survey.id, eng.id, participant.id) + factory_comment_model(survey.id, submission.id) + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) + rv = client.get(f'/api/comments/survey/{survey.id}/sheet/staff', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.UNAUTHORIZED, 'Not a team member.So throws exception.' diff --git a/met-api/tests/unit/api/test_contact.py b/met-api/tests/unit/api/test_contact.py index 28886a828..17b884f43 100644 --- a/met-api/tests/unit/api/test_contact.py +++ b/met-api/tests/unit/api/test_contact.py @@ -17,24 +17,136 @@ Test-Suite to ensure that the Contact endpoint is working as expected. """ import json +from http import HTTPStatus +from faker import Faker +from marshmallow import ValidationError +from unittest.mock import patch import pytest +from met_api.services.contact_service import ContactService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestContactInfo, TestJwtClaims from tests.utilities.factory_utils import factory_auth_header +fake = Faker() + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) @pytest.mark.parametrize('contact_info', [TestContactInfo.contact1]) -def test_create_contact(client, jwt, session, contact_info): # pylint:disable=unused-argument +def test_create_contact(client, jwt, session, contact_info, side_effect, + expected_status): # pylint:disable=unused-argument """Assert that a contact can be POSTed.""" headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) rv = client.post('/api/contacts/', data=json.dumps(contact_info), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK assert rv.json.get('name') == contact_info.get('name') assert rv.json.get('title') == contact_info.get('title') assert rv.json.get('phone_number') == contact_info.get('phone_number') assert rv.json.get('email') == contact_info.get('email') assert rv.json.get('address') == contact_info.get('address') assert rv.json.get('bio') == contact_info.get('bio') + + with patch.object(ContactService, 'create_contact', side_effect=side_effect): + rv = client.post('/api/contacts/', data=json.dumps(contact_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + with patch.object(ContactService, 'create_contact', side_effect=ValidationError('Test error')): + rv = client.post('/api/contacts/', data=json.dumps(contact_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +@pytest.mark.parametrize('contact_info', [TestContactInfo.contact1]) +def test_get_contact(client, jwt, session, contact_info, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that a contact can be fetched.""" + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + rv = client.post('/api/contacts/', data=json.dumps(contact_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + rv = client.get('/api/contacts/', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('name') == contact_info.get('name') + + with patch.object(ContactService, 'get_contacts', side_effect=side_effect): + rv = client.get('/api/contacts/', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +@pytest.mark.parametrize('contact_info', [TestContactInfo.contact1]) +def test_patch_contact(client, jwt, session, contact_info, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that a contact can be PATCHed.""" + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + rv = client.post('/api/contacts/', data=json.dumps(contact_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + rv = client.get('/api/contacts/', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('name') == contact_info.get('name') + + contact_edits = { + 'id': rv.json[0].get('id'), + 'name': fake.name(), + 'title': fake.job(), + } + + rv = client.patch('/api/contacts/', data=json.dumps(contact_edits), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK + + rv = client.get('/api/contacts/', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('name') == contact_edits.get('name') + + with patch.object(ContactService, 'update_contact', side_effect=side_effect): + rv = client.patch('/api/contacts/', data=json.dumps(contact_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + with patch.object(ContactService, 'update_contact', side_effect=ValidationError('Test error')): + rv = client.patch('/api/contacts/', data=json.dumps(contact_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +@pytest.mark.parametrize('contact_info', [TestContactInfo.contact1]) +def test_get_contact_by_id(client, jwt, session, contact_info, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that a contact can be POSTed.""" + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + rv = client.post('/api/contacts/', data=json.dumps(contact_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + contact_id = rv.json.get('id') + name = rv.json.get('name') + + rv = client.get(f'/api/contacts/{contact_id}', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + assert rv.json.get('name') == name + + with patch.object(ContactService, 'get_contact_by_id', side_effect=side_effect): + rv = client.get(f'/api/contacts/{contact_id}', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_email_verification_service.py b/met-api/tests/unit/api/test_email_verification_service.py index 2898232c1..87bef2173 100644 --- a/met-api/tests/unit/api/test_email_verification_service.py +++ b/met-api/tests/unit/api/test_email_verification_service.py @@ -18,10 +18,18 @@ """ import json +from http import HTTPStatus from faker import Faker +from unittest.mock import patch +import pytest + +from met_api.constants.email_verification import EmailVerificationType +from met_api.constants.subscription_type import SubscriptionTypes +from met_api.services.email_verification_service import EmailVerificationService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims -from tests.utilities.factory_utils import factory_auth_header, factory_survey_and_eng_model, set_global_tenant +from tests.utilities.factory_utils import ( + factory_auth_header, factory_email_verification, factory_survey_and_eng_model, set_global_tenant) fake = Faker() @@ -40,3 +48,103 @@ def test_email_verification(client, jwt, session, notify_mock, ): # pylint:disa headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_email_verification_by_token(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that an email verification can be fetched.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + email_verification = factory_email_verification(survey.id) + + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.get(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == 200 + assert rv.json.get('verification_token') == email_verification.verification_token + assert rv.json.get('is_active') is True + + with patch.object(EmailVerificationService, 'get_active', side_effect=side_effect): + rv = client.get(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + # test email verification not found + email_verification_token = fake.text(max_nb_chars=20) + rv = client.get(f'/api/email_verification/{email_verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +def test_patch_email_verification_by_token(client, jwt, session): # pylint:disable=unused-argument + """Assert that an email verification can be fetched.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + email_verification = factory_email_verification(survey.id) + + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.put(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == 200 + assert rv.json.get('verification_token') == email_verification.verification_token + assert rv.json.get('is_active') is False + + with patch.object(EmailVerificationService, 'verify', side_effect=KeyError('Test error')): + rv = client.put(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.NOT_FOUND + + with patch.object(EmailVerificationService, 'verify', side_effect=ValueError('Test error')): + rv = client.put(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + # test email verification not found to update the data + email_verification_token = fake.text(max_nb_chars=20) + rv = client.put(f'/api/email_verification/{email_verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_post_subscription_email_verification(client, jwt, session, notify_mock, + side_effect, expected_status): # pylint:disable=unused-argument + """Assert that an Subscription Email can be sent.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + to_dict = { + 'email_address': fake.email(), + 'survey_id': survey.id, + 'type': EmailVerificationType.Subscribe + } + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.post(f'/api/email_verification/{SubscriptionTypes.PROJECT.value}/subscribe', + data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == 200 + verification_token = rv.json.get('verification_token') + + rv = client.get(f'/api/email_verification/{verification_token}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == 200 + assert rv.json.get('type') == EmailVerificationType.Subscribe + + with patch.object(EmailVerificationService, 'create', side_effect=side_effect): + rv = client.post(f'/api/email_verification/{SubscriptionTypes.PROJECT.value}/subscribe', + data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_engagement.py b/met-api/tests/unit/api/test_engagement.py index e0effe6fc..7b56d6f35 100644 --- a/met-api/tests/unit/api/test_engagement.py +++ b/met-api/tests/unit/api/test_engagement.py @@ -21,11 +21,14 @@ from http import HTTPStatus import pytest +from unittest.mock import patch from faker import Faker +from marshmallow import ValidationError from flask import current_app from met_api.constants.engagement_status import EngagementDisplayStatus, SubmissionStatus from met_api.models.tenant import Tenant as TenantModel +from met_api.services.engagement_service import EngagementService from met_api.utils.constants import TENANT_ID_HEADER from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import ( @@ -39,7 +42,11 @@ @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) -def test_add_engagements(client, jwt, session, engagement_info, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_add_engagements(client, jwt, session, engagement_info, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be POSTed.""" user, claims = setup_admin_user_and_claims @@ -48,6 +55,16 @@ def test_add_engagements(client, jwt, session, engagement_info, headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 + with patch.object(EngagementService, 'create_engagement', side_effect=side_effect): + rv = client.post('/api/engagements/', data=json.dumps(engagement_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + with patch.object(EngagementService, 'create_engagement', side_effect=ValidationError('Test error')): + rv = client.post('/api/engagements/', data=json.dumps(engagement_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_tenant_id_in_create_engagements(client, jwt, session, setup_admin_user_and_claims): # pylint:disable=unused-argument @@ -113,7 +130,11 @@ def test_add_engagements_invalid(client, jwt, session, engagement_info, @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) -def test_get_engagements(client, jwt, session, engagement_info, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_engagements(client, jwt, session, engagement_info, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be POSTed.""" user, claims = setup_admin_user_and_claims @@ -129,6 +150,16 @@ def test_get_engagements(client, jwt, session, engagement_info, assert created_eng.get('name') == rv.json.get('name') assert created_eng.get('content') == rv.json.get('content') + with patch.object(EngagementService, 'get_engagement', side_effect=side_effect): + rv = client.get(f'/api/engagements/{created_eng.get("id")}', data=json.dumps(engagement_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + engagement_id = fake.pyint() + rv = client.get(f'/api/engagements/{engagement_id}', data=json.dumps(engagement_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement_draft]) def test_get_engagements_reviewer(client, jwt, session, engagement_info, @@ -159,8 +190,11 @@ def test_get_engagements_reviewer(client, jwt, session, engagement_info, @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) +@pytest.mark.parametrize('side_effect, expected_status', [ + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) def test_search_engagements_by_status(client, jwt, - session, engagement_info, + session, engagement_info, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be fetched by filtering using the engagement status.""" user, claims = setup_admin_user_and_claims @@ -182,6 +216,13 @@ def test_search_engagements_by_status(client, jwt, assert rv.json.get('total') == 1 + with patch.object(EngagementService, 'get_engagements_paginated', side_effect=side_effect): + rv = client.get(f'/api/engagements/?page={page}&size={page_size}&sort_key={sort_key}\ + &sort_order={sort_order}&engagement_status={[engagement_status]}', + data=json.dumps(engagement_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + def test_search_engagements(client, jwt, session): # pylint:disable=unused-argument """Verify the functionality of searching engagements with different access levels.""" @@ -292,7 +333,11 @@ def test_search_engagements_not_logged_in(client, session): # pylint:disable=un @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) -def test_patch_engagement(client, jwt, session, engagement_info, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_patch_engagement(client, jwt, session, engagement_info, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be updated.""" user, claims = setup_admin_user_and_claims @@ -325,6 +370,16 @@ def test_patch_engagement(client, jwt, session, engagement_info, assert rv.json.get('content') == engagement_edits.get('content') assert engagement_edits.get('created_date') in rv.json.get('created_date') + with patch.object(EngagementService, 'edit_engagement', side_effect=side_effect): + rv = client.patch('/api/engagements/', data=json.dumps(engagement_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + with patch.object(EngagementService, 'edit_engagement', side_effect=ValidationError('Test error')): + rv = client.patch('/api/engagements/', data=json.dumps(engagement_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_patch_engagement_by_member(client, jwt, session): # pylint:disable=unused-argument """Assert that an engagement can be updated.""" diff --git a/met-api/tests/unit/api/test_engagement_membership.py b/met-api/tests/unit/api/test_engagement_membership.py index 6b5805485..711ab3bd8 100644 --- a/met-api/tests/unit/api/test_engagement_membership.py +++ b/met-api/tests/unit/api/test_engagement_membership.py @@ -5,9 +5,12 @@ """ import json from http import HTTPStatus -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch +import pytest from met_api.constants.membership_type import MembershipType +from met_api.exceptions.business_exception import BusinessException +from met_api.services.membership_service import MembershipService from met_api.utils.enums import ContentType, KeycloakGroupName, MembershipStatus from tests.utilities.factory_utils import ( factory_auth_header, factory_engagement_model, factory_membership_model, factory_staff_user_model) @@ -51,6 +54,16 @@ def test_create_engagement_membership_team_member(mocker, client, jwt, session, mock_add_user_to_group_keycloak.assert_called() mock_get_users_groups_keycloak.assert_called() + with patch.object(MembershipService, 'create_membership', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.post( + memberships_url.format(engagement.id), + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_create_engagement_membership_reviewer(mocker, client, jwt, session, setup_admin_user_and_claims): @@ -222,3 +235,73 @@ def reinstate_already_active_membership(client, jwt, session, ) assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_get_membership(client, jwt, session, + setup_admin_user_and_claims): + """Test that a membership can be fetched.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + staff_user = factory_staff_user_model() + membership = factory_membership_model(user_id=staff_user.id, engagement_id=engagement.id) + headers = factory_auth_header(jwt=jwt, claims=claims) + + rv = client.get( + f'/api/engagements/{engagement.id}/members', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('engagement_id') == membership.engagement_id + + with patch.object(MembershipService, 'get_memberships', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.get( + f'/api/engagements/{engagement.id}/members', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (ValueError('Test error'), HTTPStatus.BAD_REQUEST), +]) +def test_get_all_engagements_by_user(mocker, client, jwt, session, side_effect, expected_status, + setup_admin_user_and_claims): + """Test that all engagements can be fetched for a member.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + staff_user = factory_staff_user_model() + headers = factory_auth_header(jwt=jwt, claims=claims) + + mock_add_user_to_group_keycloak_response = MagicMock() + mock_add_user_to_group_keycloak_response.status_code = HTTPStatus.NO_CONTENT + mocker.patch( + 'met_api.services.keycloak.KeycloakService.add_user_to_group', + return_value=mock_add_user_to_group_keycloak_response + ) + mocker.patch( + 'met_api.services.keycloak.KeycloakService.get_users_groups', + return_value={staff_user.external_id: [KeycloakGroupName.EAO_TEAM_MEMBER.value]} + ) + + data = {'user_id': staff_user.external_id} + + rv = client.post( + memberships_url.format(engagement.id), + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'/api/engagements/all/members/{staff_user.external_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('engagement_id') == engagement.id diff --git a/met-api/tests/unit/api/test_engagement_settings.py b/met-api/tests/unit/api/test_engagement_settings.py new file mode 100644 index 000000000..f0b0932d6 --- /dev/null +++ b/met-api/tests/unit/api/test_engagement_settings.py @@ -0,0 +1,152 @@ +# Copyright © 2019 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 verify the Engagement settings API end-point. + +Test-Suite to ensure that the Engagement settings API endpoint +is working as expected. +""" +import json +from http import HTTPStatus + +from faker import Faker +from unittest.mock import patch +import pytest +from marshmallow import ValidationError + +from met_api.constants.engagement_status import Status +from met_api.services.engagement_settings_service import EngagementSettingsService +from met_api.utils.enums import ContentType +from tests.utilities.factory_scenarios import TestJwtClaims +from tests.utilities.factory_utils import ( + factory_auth_header, factory_engagement_model, factory_engagement_setting_model) + + +fake = Faker() + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_engagement_settings(client, jwt, session, side_effect, expected_status, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that engagement settings can be fetched.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + engagement_settings = factory_engagement_setting_model(engagement.id) + + headers = factory_auth_header(jwt=jwt, claims=claims) + + rv = client.get( + f'/api/engagementsettings/{engagement.id}', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + response_data = rv.json + assert response_data.get('send_report') == engagement_settings.send_report + + with patch.object(EngagementSettingsService, 'get', side_effect=side_effect): + rv = client.get( + f'/api/engagementsettings/{engagement.id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + +def test_get_engagement_settings_for_draft_engagement(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that an engagement setting can not be fetched without authorization.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model(status=Status.Draft) + factory_engagement_setting_model(engagement.id) + + headers = factory_auth_header(jwt=jwt, claims=claims) + + rv = client.get( + f'/api/engagementsettings/{engagement.id}', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK, 'User with proper role is able to fetch the data.' + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) + + rv = client.get( + f'/api/engagementsettings/{engagement.id}', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.FORBIDDEN, 'Not a team member.So throws exception.' + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_patch_engagement_settings(client, jwt, session, side_effect, expected_status, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that engagement settings can be PATCHed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model(status=Status.Draft) + factory_engagement_setting_model(engagement.id) + + headers = factory_auth_header(jwt=jwt, claims=claims) + + rv = client.get( + f'/api/engagementsettings/{engagement.id}', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + + patch_data = { + 'send_report': True, + 'engagement_id': engagement.id + } + + rv = client.patch( + '/api/engagementsettings/', + data=json.dumps(patch_data), + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + response_data = rv.json + assert response_data.get('send_report') == patch_data.get('send_report') + + with patch.object(EngagementSettingsService, 'update_settings', side_effect=side_effect): + rv = client.patch( + '/api/engagementsettings/', + data=json.dumps(patch_data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + with patch.object(EngagementSettingsService, 'update_settings', side_effect=ValidationError('Test error')): + rv = client.patch( + '/api/engagementsettings/', + data=json.dumps(patch_data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/met-api/tests/unit/api/test_engagement_slug.py b/met-api/tests/unit/api/test_engagement_slug.py index 88f6009ca..7ae220e2d 100644 --- a/met-api/tests/unit/api/test_engagement_slug.py +++ b/met-api/tests/unit/api/test_engagement_slug.py @@ -21,8 +21,10 @@ import pytest from faker import Faker +from unittest.mock import patch from met_api.constants.engagement_status import Status +from met_api.services.engagement_slug_service import EngagementSlugService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestEngagementSlugInfo, TestJwtClaims, TestUserInfo from tests.utilities.factory_utils import factory_auth_header, factory_engagement_model, factory_engagement_slug_model @@ -32,7 +34,12 @@ @pytest.mark.parametrize('engagement_slug_info', [TestEngagementSlugInfo.slug1]) -def test_get_engagement_slug(client, jwt, session, engagement_slug_info): +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.BAD_REQUEST), + (ValueError('Test error'), HTTPStatus.BAD_REQUEST), +]) +def test_get_engagement_slug(client, jwt, session, side_effect, expected_status, + engagement_slug_info): """Test get request for engagement_slug endpoint.""" eng = factory_engagement_model() engagement_slug_info = { @@ -46,9 +53,18 @@ def test_get_engagement_slug(client, jwt, session, engagement_slug_info): assert rv.json.get('slug') == eng_slug.slug assert rv.json.get('engagement_id') == eng_slug.engagement_id + with patch.object(EngagementSlugService, 'get_engagement_slug', side_effect=side_effect): + rv = client.get(f'/api/slugs/{eng_slug.slug}', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + @pytest.mark.parametrize('engagement_slug_info', [TestEngagementSlugInfo.slug1]) -def test_get_engagement_slug_by_engagement_id(client, jwt, session, engagement_slug_info): +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.BAD_REQUEST), + (ValueError('Test error'), HTTPStatus.BAD_REQUEST), +]) +def test_get_engagement_slug_by_engagement_id(client, jwt, session, side_effect, expected_status, + engagement_slug_info): """Test get request for engagement_slug endpoint.""" eng = factory_engagement_model() engagement_slug_info = { @@ -62,6 +78,11 @@ def test_get_engagement_slug_by_engagement_id(client, jwt, session, engagement_s assert rv.json.get('slug') == eng_slug.slug assert rv.json.get('engagement_id') == eng_slug.engagement_id + with patch.object(EngagementSlugService, 'get_engagement_slug_by_engagement_id', side_effect=side_effect): + rv = client.get(f'/api/slugs/engagements/{eng.id}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + def test_get_nonexistent_engagement_slug(client, jwt, session): """Test get request for non-existent engagement_slug endpoint.""" @@ -71,7 +92,11 @@ def test_get_nonexistent_engagement_slug(client, jwt, session): @pytest.mark.parametrize('engagement_slug_info', [TestEngagementSlugInfo.slug1]) -def test_patch_engagement_slug(client, jwt, session, engagement_slug_info, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.BAD_REQUEST), + (ValueError('Test error'), HTTPStatus.BAD_REQUEST), +]) +def test_patch_engagement_slug(client, jwt, session, engagement_slug_info, side_effect, expected_status, setup_admin_user_and_claims): """Test patch request for engagement_slug endpoint.""" user, claims = setup_admin_user_and_claims @@ -91,6 +116,11 @@ def test_patch_engagement_slug(client, jwt, session, engagement_slug_info, headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == HTTPStatus.OK + with patch.object(EngagementSlugService, 'update_engagement_slug', side_effect=side_effect): + rv = client.patch(f'/api/slugs/{updated_slug}', data=json.dumps(patch_data), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + def test_patch_create_nonexistent_engagement_slug(client, jwt, session, setup_admin_user_and_claims): diff --git a/met-api/tests/unit/api/test_feedback.py b/met-api/tests/unit/api/test_feedback.py index f86823fb8..66cc49aea 100644 --- a/met-api/tests/unit/api/test_feedback.py +++ b/met-api/tests/unit/api/test_feedback.py @@ -17,15 +17,25 @@ Test-Suite to ensure that the /Feedbacks endpoint is working as expected. """ import json +from http import HTTPStatus +from unittest.mock import patch +from faker import Faker +import pytest from met_api.constants.feedback import FeedbackSourceType, FeedbackStatusType +from met_api.services.feedback_service import FeedbackService from met_api.utils.enums import ContentType - from tests.utilities.factory_scenarios import TestJwtClaims from tests.utilities.factory_utils import factory_auth_header, factory_feedback_model +fake = Faker() + -def test_feedback(client, jwt, session): # pylint:disable=unused-argument +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_feedback(client, jwt, session, side_effect, expected_status,): # pylint:disable=unused-argument """Assert that an feedback can be POSTed.""" claims = TestJwtClaims.public_user_role @@ -50,6 +60,11 @@ def test_feedback(client, jwt, session): # pylint:disable=unused-argument assert result.get('comment') == feedback.comment assert result.get('source') == FeedbackSourceType.Internal + with patch.object(FeedbackService, 'create_feedback', side_effect=side_effect): + rv = client.post('/api/feedbacks/', data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + def test_invalid_feedback(client, jwt, session): # pylint:disable=unused-argument """Assert that an invalid feedback can not be POSTed.""" @@ -103,8 +118,18 @@ def test_patch_feedback(client, jwt, session): # pylint:disable=unused-argument # Check if the status is update assert rv.json.get('status') == FeedbackStatusType.Archived.value + # Patching a non existing feedback should give an error + feedback_id = fake.pyint() + rv = client.patch(f'/api/feedbacks/{feedback_id}', data=json.dumps(feedback_creation), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.NOT_FOUND + -def test_delete_feedback(client, jwt, session): # pylint:disable=unused-argument +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_delete_feedback(client, jwt, session, side_effect, expected_status,): # pylint:disable=unused-argument """Assert that feedback can be deleted.""" # Setup: Create a new feedback first claims = TestJwtClaims.public_user_role @@ -123,3 +148,44 @@ def test_delete_feedback(client, jwt, session): # pylint:disable=unused-argumen # Now, delete this feedback rv = client.delete(f'/api/feedbacks/{feedback_id}', headers=headers) assert rv.status_code == 200 + + # Delete a non existing feedback + rv = client.delete(f'/api/feedbacks/{feedback_id}', headers=headers) + assert rv.status_code == HTTPStatus.NOT_FOUND + + rv = client.post('/api/feedbacks/', data=json.dumps(feedback_creation), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + with patch.object(FeedbackService, 'delete_feedback', side_effect=side_effect): + rv = client.delete(f'/api/feedbacks/{feedback_id}', headers=headers) + assert rv.status_code == expected_status + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_feedback(client, jwt, session, side_effect, expected_status,): # pylint:disable=unused-argument + """Assert that feedback can be fetched.""" + # Setup: Create a new feedback first + claims = TestJwtClaims.public_user_role + feedback = factory_feedback_model() + headers = factory_auth_header(jwt=jwt, claims=claims) + + page = 1 + page_size = 10 + sort_key = 'created_date' + sort_order = 'desc' + feedback_status = feedback.status + + rv = client.get(f'/api/feedbacks/?page={page}&size={page_size}&sort_key={sort_key}\ + &sort_order={sort_order}&engagement_status={[feedback_status]}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == 200 + assert rv.json.get('total') == 1 + + with patch.object(FeedbackService, 'get_feedback_paginated', side_effect=side_effect): + rv = client.get(f'/api/feedbacks/?page={page}&size={page_size}&sort_key={sort_key}\ + &sort_order={sort_order}&engagement_status={[feedback_status]}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_report_setting.py b/met-api/tests/unit/api/test_report_setting.py index 00c655a17..8f8738ed4 100644 --- a/met-api/tests/unit/api/test_report_setting.py +++ b/met-api/tests/unit/api/test_report_setting.py @@ -16,13 +16,24 @@ Test-Suite to ensure that the Report setting endpoint is working as expected. """ +import json +from http import HTTPStatus +from unittest.mock import patch +import pytest +from marshmallow import ValidationError + +from met_api.services.report_setting_service import ReportSettingService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims, TestReportSettingInfo, TestSurveyInfo from tests.utilities.factory_utils import ( factory_auth_header, factory_survey_and_eng_model, factory_survey_report_setting_model) -def test_get_report_setting(client, jwt, session): # pylint:disable=unused-argument +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_report_setting(client, jwt, session, side_effect, expected_status): # pylint:disable=unused-argument """Assert that report setting can be fetched.""" headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) survey, _ = factory_survey_and_eng_model(TestSurveyInfo.survey3) @@ -39,4 +50,75 @@ def test_get_report_setting(client, jwt, session): # pylint:disable=unused-argu content_type=ContentType.JSON.value ) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK + + with patch.object(ReportSettingService, 'get_report_setting', side_effect=side_effect): + rv = client.get( + f'/api/surveys/{survey.id}/reportsettings', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_patch_report_setting(client, jwt, session, side_effect, expected_status): # pylint:disable=unused-argument + """Assert that report setting can be PATCHed.""" + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + survey, _ = factory_survey_and_eng_model(TestSurveyInfo.survey3) + + report_setting_data = { + **TestReportSettingInfo.report_setting_1, + 'survey_id': survey.id, + } + factory_survey_report_setting_model(report_setting_data) + + rv = client.get( + f'/api/surveys/{survey.id}/reportsettings', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('display') is True + + report_setting_edits = { + 'id': rv.json[0].get('id'), + 'display': False, + } + + rv = client.patch( + f'/api/surveys/{survey.id}/reportsettings', + data=json.dumps([report_setting_edits]), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'/api/surveys/{survey.id}/reportsettings', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('display') is False + + with patch.object(ReportSettingService, 'update_report_setting', side_effect=side_effect): + rv = client.patch( + f'/api/surveys/{survey.id}/reportsettings', + data=json.dumps([report_setting_edits]), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + with patch.object(ReportSettingService, 'update_report_setting', side_effect=ValidationError('Test error')): + rv = client.patch( + f'/api/surveys/{survey.id}/reportsettings', + data=json.dumps([report_setting_edits]), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/met-api/tests/unit/api/test_submission.py b/met-api/tests/unit/api/test_submission.py index b70f69e9d..724a55902 100644 --- a/met-api/tests/unit/api/test_submission.py +++ b/met-api/tests/unit/api/test_submission.py @@ -18,10 +18,14 @@ """ import copy import json +from http import HTTPStatus +from unittest.mock import patch import pytest +from faker import Faker from met_api.constants.membership_type import MembershipType +from met_api.services.submission_service import SubmissionService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims, TestSubmissionInfo from tests.utilities.factory_utils import ( @@ -31,9 +35,14 @@ DATE_FORMAT = '%Y-%m-%d %H:%M:%S' +fake = Faker() -def test_valid_submission(client, jwt, session): # pylint:disable=unused-argument +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_valid_submission(client, jwt, session, side_effect, expected_status): # pylint:disable=unused-argument """Assert that an engagement can be POSTed.""" claims = TestJwtClaims.public_user_role @@ -50,6 +59,12 @@ def test_valid_submission(client, jwt, session): # pylint:disable=unused-argume headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 + with patch.object(SubmissionService, 'create', side_effect=side_effect): + rv = client.post(f'/api/submissions/public/{email_verification.verification_token}', + data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + @pytest.mark.parametrize('submission_info', [TestSubmissionInfo.submission1]) def test_get_submission_by_id(client, jwt, session, submission_info, diff --git a/met-api/tests/unit/api/test_subscription.py b/met-api/tests/unit/api/test_subscription.py index ddc502f9c..034df4e64 100644 --- a/met-api/tests/unit/api/test_subscription.py +++ b/met-api/tests/unit/api/test_subscription.py @@ -18,14 +18,27 @@ """ import json +from http import HTTPStatus +from faker import Faker +from unittest.mock import patch +import pytest + +from met_api.services.subscription_service import SubscriptionService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims from tests.utilities.factory_utils import ( factory_auth_header, factory_participant_model, factory_subscription_model, factory_survey_and_eng_model, set_global_tenant) +fake = Faker() + -def test_create_subscription(client, jwt, session): # pylint:disable=unused-argument +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_create_subscription(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument """Assert that an Email can be sent.""" claims = TestJwtClaims.public_user_role set_global_tenant() @@ -40,11 +53,21 @@ def test_create_subscription(client, jwt, session): # pylint:disable=unused-arg rv = client.post('/api/subscription/', data=json.dumps(to_dict), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value + with patch.object(SubscriptionService, 'create_subscription', side_effect=side_effect): + rv = client.post('/api/subscription/', data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status -def test_update_subscription(client, jwt, session): # pylint:disable=unused-argument - """Assert that an subscription can be updated.""" + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_update_subscription(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that a subscription can be updated.""" headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) subscription = factory_subscription_model() subscription_participant_id = str(subscription.participant_id) @@ -57,4 +80,97 @@ def test_update_subscription(client, jwt, session): # pylint:disable=unused-arg rv = client.patch('/api/subscription/', data=json.dumps(subscription_edits), headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK.value + + with patch.object(SubscriptionService, 'update_subscription_for_participant', side_effect=side_effect): + rv = client.patch('/api/subscription/', data=json.dumps(subscription_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_subscription(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that a subscription can be fetched.""" + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) + subscription = factory_subscription_model() + subscription_participant_id = str(subscription.participant_id) + + rv = client.get(f'/api/subscription/{subscription_participant_id}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK.value + assert rv.json.get('participant_id') == subscription.participant_id + + with patch.object(SubscriptionService, 'get', side_effect=side_effect): + rv = client.get(f'/api/subscription/{subscription_participant_id}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + # test subscription not found + subscription_participant_id = fake.pyint() + rv = client.get(f'/api/subscription/{subscription_participant_id}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.NOT_FOUND.value + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_confirm_subscription(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that a subscription can be confirmed or unsubscribed.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + participant = factory_participant_model() + to_dict = { + 'engagement_id': eng.id, + 'participant_id': participant.id, + 'is_subscribed': True, + } + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.post('/api/subscription/manage', data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK.value + + subscription_participant_id = str(participant.id) + + rv = client.get(f'/api/subscription/{subscription_participant_id}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK.value + assert rv.json.get('is_subscribed') is True + + with patch.object(SubscriptionService, 'create_or_update_subscription', side_effect=side_effect): + rv = client.post('/api/subscription/manage', data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + subscription_edits = { + 'engagement_id': eng.id, + 'participant_id': participant.id, + 'is_subscribed': False, + } + + rv = client.patch('/api/subscription/manage', data=json.dumps(subscription_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + + rv = client.get(f'/api/subscription/{subscription_participant_id}', + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK.value + assert rv.json.get('is_subscribed') is False + + with patch.object(SubscriptionService, 'update_subscription_for_participant_eng', side_effect=side_effect): + rv = client.patch('/api/subscription/manage', data=json.dumps(subscription_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_survey.py b/met-api/tests/unit/api/test_survey.py index 29eebeea0..1f47fa918 100644 --- a/met-api/tests/unit/api/test_survey.py +++ b/met-api/tests/unit/api/test_survey.py @@ -20,16 +20,20 @@ import json from http import HTTPStatus +from unittest.mock import patch import pytest from flask import current_app from met_api.constants.engagement_status import Status +from met_api.exceptions.business_exception import BusinessException from met_api.models.engagement import Engagement as EngagementModel from met_api.models.membership import Membership as MembershipModel from met_api.models.tenant import Tenant as TenantModel +from met_api.services.survey_service import SurveyService from met_api.utils.constants import TENANT_ID_HEADER from met_api.utils.enums import ContentType, MembershipStatus -from tests.utilities.factory_scenarios import TestJwtClaims, TestSurveyInfo, TestTenantInfo, TestUserInfo +from tests.utilities.factory_scenarios import ( + TestEngagementInfo, TestJwtClaims, TestSurveyInfo, TestTenantInfo, TestUserInfo) from tests.utilities.factory_utils import ( factory_auth_header, factory_engagement_model, factory_membership_model, factory_staff_user_model, factory_survey_model, factory_tenant_model, set_global_tenant) @@ -39,7 +43,11 @@ @pytest.mark.parametrize('survey_info', [TestSurveyInfo.survey1]) -def test_create_survey(client, jwt, session, survey_info, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_create_survey(client, jwt, session, survey_info, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an survey can be POSTed.""" user, claims = setup_admin_user_and_claims @@ -50,9 +58,14 @@ def test_create_survey(client, jwt, session, survey_info, } rv = client.post(surveys_url, data=json.dumps(data), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('form_json') == survey_info.get('form_json') + with patch.object(SurveyService, 'create', side_effect=side_effect): + rv = client.post(surveys_url, data=json.dumps(data), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + def test_create_survey_with_tenant(client, jwt, session, setup_admin_user_and_claims): # pylint:disable=unused-argument @@ -68,7 +81,7 @@ def test_create_survey_with_tenant(client, jwt, session, 'name': TestSurveyInfo.survey1.get('name'), 'display': TestSurveyInfo.survey1.get('form_json').get('display'), }), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value survey_tenant_id = rv.json.get('tenant_id') assert survey_tenant_id == str(tenant.id) @@ -103,7 +116,7 @@ def test_create_survey_with_tenant(client, jwt, session, 'name': TestSurveyInfo.survey2.get('name'), 'display': TestSurveyInfo.survey2.get('form_json').get('display'), }), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value # Verify that the new survey belongs to the correct tenant survey_tenant_id = rv.json.get('tenant_id') @@ -111,7 +124,11 @@ def test_create_survey_with_tenant(client, jwt, session, @pytest.mark.parametrize('survey_info', [TestSurveyInfo.survey2]) -def test_put_survey(client, jwt, session, survey_info, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_put_survey(client, jwt, session, survey_info, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an survey can be POSTed.""" user, claims = setup_admin_user_and_claims @@ -122,16 +139,31 @@ def test_put_survey(client, jwt, session, survey_info, rv = client.put(surveys_url, data=json.dumps({'id': survey_id, 'name': new_survey_name}), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value rv = client.get(f'{surveys_url}{survey_id}', headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('form_json') == survey_info.get('form_json') assert rv.json.get('name') == new_survey_name + with patch.object(SurveyService, 'update', side_effect=side_effect): + rv = client.put(surveys_url, data=json.dumps({'id': survey_id, 'name': new_survey_name}), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + + with patch.object(SurveyService, 'update', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.put(surveys_url, data=json.dumps({'id': survey_id, 'name': new_survey_name}), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR -def test_survey_link(client, jwt, session, + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_survey_link(client, jwt, session, side_effect, expected_status, setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that a survey can be POSTed.""" user, claims = setup_admin_user_and_claims @@ -157,6 +189,14 @@ def test_survey_link(client, jwt, session, content_type=ContentType.JSON.value ) + with patch.object(SurveyService, 'link', side_effect=side_effect): + rv = client.put( + f'{surveys_url}{survey_id}/link/engagement/{eng_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + rv = client.get( f'{surveys_url}{survey_id}', headers=headers, @@ -164,6 +204,22 @@ def test_survey_link(client, jwt, session, ) assert rv.json.get('engagement_id') == str(eng_id) + with patch.object(SurveyService, 'get', side_effect=side_effect): + rv = client.get( + f'{surveys_url}{survey_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + # test if public user can fetch open surveys + rv = client.get( + f'{surveys_url}{survey_id}', + headers=factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role), + content_type=ContentType.JSON.value + ) + assert rv.json.get('engagement_id') == str(eng_id) + def test_get_hidden_survey_for_admins(client, jwt, session): # pylint:disable=unused-argument """Assert that a hidden survey can be fetched by admins.""" @@ -179,7 +235,7 @@ def test_get_hidden_survey_for_admins(client, jwt, session): # pylint:disable=u rv = client.get(f'{surveys_url}?page={page}&size={page_size}&sort_key={sort_key}\ &sort_order={sort_order}&search_text=', headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('total') == 1 @@ -214,7 +270,7 @@ def test_get_survey_for_reviewer(client, jwt, session): # pylint:disable=unused # Assert Reviewer can see the survey since he is added to the team. rv = client.get(f'{surveys_url}{survey1.id}', headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value # Deactivate membership membership_model: MembershipModel = MembershipModel.find_by_engagement_and_user_id(eng.id, user.id) @@ -233,7 +289,7 @@ def test_get_survey_for_reviewer(client, jwt, session): # pylint:disable=unused headers=headers, content_type=ContentType.JSON.value) # Assert user can access the survey even when he is removed from the team since its published. - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value def test_get_hidden_survey_for_team_member(client, jwt, session): # pylint:disable=unused-argument @@ -250,7 +306,7 @@ def test_get_hidden_survey_for_team_member(client, jwt, session): # pylint:disa rv = client.get(f'{surveys_url}?page={page}&size={page_size}&sort_key={sort_key}\ &sort_order={sort_order}&search_text=', headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('total') == 0 @@ -268,7 +324,7 @@ def test_get_template_survey(client, jwt, session): # pylint:disable=unused-arg rv = client.get(f'{surveys_url}?page={page}&size={page_size}&sort_key={sort_key}\ &sort_order={sort_order}&search_text=', headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('total') == 1 @@ -283,7 +339,7 @@ def test_edit_template_survey_for_admins(client, jwt, session, rv = client.put(surveys_url, data=json.dumps({'id': survey_id, 'name': new_survey_name}), headers=headers, content_type=ContentType.JSON.value) - assert rv.status_code == 200, 'Admins are able to edit template surveys' + assert rv.status_code == HTTPStatus.OK.value, 'Admins are able to edit template surveys' def test_edit_template_survey_for_team_member(client, jwt, session): # pylint:disable=unused-argument @@ -356,3 +412,65 @@ def test_surveys_clone_team_member(mocker, client, jwt, session, survey_info, # Assert the response status code and data assert response.status_code == HTTPStatus.OK assert response.get_json().get('form_json') == survey.form_json + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_survey_unlink(client, jwt, session, side_effect, expected_status, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that a survey can be unlinked from an engagement.""" + user, claims = setup_admin_user_and_claims + survey = factory_survey_model() + survey_id = survey.id + headers = factory_auth_header(jwt=jwt, claims=claims) + + eng = factory_engagement_model(TestEngagementInfo.engagement_draft) + eng_id = eng.id + + # assert eng id is none in GET Survey + rv = client.get( + f'{surveys_url}{survey_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.json.get('engagement_id') is None + + # link them together + rv = client.put( + f'{surveys_url}{survey_id}/link/engagement/{eng_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'{surveys_url}{survey_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.json.get('engagement_id') == str(eng_id) + + # unlink the survey + rv = client.delete( + f'{surveys_url}{survey_id}/unlink/engagement/{eng_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'{surveys_url}{survey_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.json.get('engagement_id') is None + + with patch.object(SurveyService, 'unlink', side_effect=side_effect): + rv = client.delete( + f'{surveys_url}{survey_id}/unlink/engagement/{eng_id}', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_tenant.py b/met-api/tests/unit/api/test_tenant.py new file mode 100644 index 000000000..b395612be --- /dev/null +++ b/met-api/tests/unit/api/test_tenant.py @@ -0,0 +1,45 @@ +# Copyright © 2019 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 verify the tenant API end-point. + +Test-Suite to ensure that the tenant endpoint is working as expected. +""" +from http import HTTPStatus +from unittest.mock import patch + +from met_api.services.tenant_service import TenantService +from met_api.utils.enums import ContentType +from tests.utilities.factory_scenarios import TestJwtClaims, TestTenantInfo +from tests.utilities.factory_utils import factory_auth_header, factory_tenant_model + + +def test_get_tenant(client, jwt, session): # pylint:disable=unused-argument + """Assert that a tenant can be fetched.""" + claims = TestJwtClaims.public_user_role + + tenant_data = TestTenantInfo.tenant1 + factory_tenant_model(tenant_data) + tenant_short_name = tenant_data['short_name'] + + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.get(f'/api/tenants/{tenant_short_name}', headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK.value + assert rv.json.get('name') == tenant_data['name'] + + with patch.object(TenantService, 'get', side_effect=ValueError('Test error')): + rv = client.get(f'/api/tenants/{tenant_short_name}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/met-api/tests/unit/api/test_user.py b/met-api/tests/unit/api/test_user.py index f7cffe9a3..d95e29556 100644 --- a/met-api/tests/unit/api/test_user.py +++ b/met-api/tests/unit/api/test_user.py @@ -19,11 +19,14 @@ """ import copy from http import HTTPStatus -from unittest.mock import MagicMock - +from unittest.mock import MagicMock, patch +import pytest from flask import current_app +from met_api.exceptions.business_exception import BusinessException from met_api.models import Tenant as TenantModel +from met_api.services.staff_user_membership_service import StaffUserMembershipService +from met_api.services.staff_user_service import StaffUserService from met_api.utils.enums import ContentType, KeycloakGroupName, UserStatus from tests.utilities.factory_scenarios import TestJwtClaims, TestUserInfo from tests.utilities.factory_utils import factory_auth_header, factory_staff_user_model, set_global_tenant @@ -56,7 +59,11 @@ def mock_add_user_to_group(mocker, mock_group_names): return mock_add_user_to_group_keycloak, mock_get_user_groups_keycloak, mock_add_attribute_to_user -def test_create_staff_user(client, jwt, session): +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_create_staff_user(client, jwt, session, side_effect, expected_status): """Assert that a user can be POSTed.""" claims = TestJwtClaims.staff_admin_role headers = factory_auth_header(jwt=jwt, claims=claims) @@ -67,6 +74,10 @@ def test_create_staff_user(client, jwt, session): tenant = TenantModel.find_by_short_name(tenant_short_name) assert rv.json.get('tenant_id') == str(tenant.id) + with patch.object(StaffUserService, 'create_or_update_user', side_effect=side_effect): + rv = client.put('/api/user/', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + def test_get_staff_users(client, jwt, session, setup_admin_user_and_claims): @@ -85,7 +96,11 @@ def test_get_staff_users(client, jwt, session, assert len(rv.json.get('items')) == 4 -def test_add_user_to_admin_group(mocker, client, jwt, session, +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_add_user_to_admin_group(mocker, client, jwt, session, side_effect, expected_status, setup_admin_user_and_claims): """Assert that a user can be added to the admin group.""" user = factory_staff_user_model() @@ -107,6 +122,23 @@ def test_add_user_to_admin_group(mocker, client, jwt, session, mock_get_user_groups_keycloak.assert_called() mock_add_attribute_to_user.assert_called() + with patch.object(StaffUserService, 'add_user_to_group', side_effect=side_effect): + rv = client.post( + f'/api/user/{user.external_id}/groups?group=Administrator', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + with patch.object(StaffUserService, 'add_user_to_group', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.post( + f'/api/user/{user.external_id}/groups?group=Administrator', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_add_user_to_reviewer_group(mocker, client, jwt, session, setup_admin_user_and_claims): @@ -277,3 +309,40 @@ def test_toggle_user_active_status_empty_body(mocker, client, jwt, session, ) assert rv.status_code == HTTPStatus.BAD_REQUEST mocked_toggle_user_status.assert_not_called() + + +def test_get_staff_users_by_id(client, jwt, session, + setup_admin_user_and_claims): + """Assert that a user can be fetched.""" + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.put('/api/user/', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + user_id = rv.json.get('id') + rv = client.get(f'/api/user/{user_id}', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + assert rv.json.get('id') == user_id + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.BAD_REQUEST), + (ValueError('Test error'), HTTPStatus.BAD_REQUEST), +]) +def test_errors_on_toggle_user_active_status(client, jwt, session, side_effect, expected_status, + setup_admin_user_and_claims): + """Assert that a user can be toggled.""" + user = factory_staff_user_model() + + assert user.status_id == UserStatus.ACTIVE.value + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) + + with patch.object(StaffUserMembershipService, 'reactivate_deactivate_user', side_effect=side_effect): + rv = client.patch( + f'/api/user/{user.external_id}/status', + headers=headers, + json={'active': False}, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status diff --git a/met-api/tests/unit/api/test_user_membership.py b/met-api/tests/unit/api/test_user_membership.py index a4edd90e5..f91fb16c8 100644 --- a/met-api/tests/unit/api/test_user_membership.py +++ b/met-api/tests/unit/api/test_user_membership.py @@ -17,9 +17,12 @@ Test-Suite to ensure that the user membership endpoints are working as expected. """ from http import HTTPStatus -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch +import pytest +from met_api.exceptions.business_exception import BusinessException from met_api.models.membership import Membership as MembershipModel +from met_api.services.staff_user_membership_service import StaffUserMembershipService from met_api.utils.enums import ContentType, KeycloakGroupName, MembershipStatus, UserStatus from tests.utilities.factory_scenarios import TestJwtClaims from tests.utilities.factory_utils import ( @@ -62,7 +65,11 @@ def mock_keycloak_methods(mocker, mock_group_names): ) -def test_reassign_user_reviewer_team_member(mocker, client, jwt, session): +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_reassign_user_reviewer_team_member(mocker, client, jwt, session, side_effect, expected_status): """Assert that returns bad request if bad request body.""" user = factory_staff_user_model() eng = factory_engagement_model() @@ -106,3 +113,20 @@ def test_reassign_user_reviewer_team_member(mocker, client, jwt, session): memberships = MembershipModel.find_by_user_id(user.id) assert len(memberships) == 1 assert memberships[0].status == MembershipStatus.REVOKED.value + + with patch.object(StaffUserMembershipService, 'reassign_user', side_effect=side_effect): + rv = client.put( + f'/api/user/{user.id}/groups?group=EAO_TEAM_MEMBER', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == expected_status + + with patch.object(StaffUserMembershipService, 'reassign_user', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.put( + f'/api/user/{user.id}/groups?group=EAO_TEAM_MEMBER', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/met-api/tests/unit/api/test_widget.py b/met-api/tests/unit/api/test_widget.py index c4098e60f..8b16bf74a 100644 --- a/met-api/tests/unit/api/test_widget.py +++ b/met-api/tests/unit/api/test_widget.py @@ -18,15 +18,22 @@ """ import json from http import HTTPStatus +from marshmallow import ValidationError +from unittest.mock import patch import pytest +from faker import Faker from met_api.constants.widget import WidgetType +from met_api.services.widget_service import WidgetService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestWidgetInfo, TestWidgetItemInfo from tests.utilities.factory_utils import factory_auth_header, factory_engagement_model, factory_widget_model +fake = Faker() + + @pytest.mark.parametrize('widget_info', [TestWidgetInfo.widget1]) def test_create_widget(client, jwt, session, widget_info, setup_admin_user_and_claims): # pylint:disable=unused-argument @@ -44,6 +51,39 @@ def test_create_widget(client, jwt, session, widget_info, assert rv.status_code == 200 assert rv.json[0].get('sort_index') == 1 + with patch.object(WidgetService, 'create_widget', side_effect=ValueError('Test error')): + rv = client.post('/api/widgets/engagement/' + str(engagement.id), data=json.dumps(widget_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + with patch.object(WidgetService, 'create_widget', side_effect=ValidationError('Test error')): + rv = client.post('/api/widgets/engagement/' + str(engagement.id), data=json.dumps(widget_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize('widget_info', [TestWidgetInfo.widget1]) +def test_get_widget(client, jwt, session, widget_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that a widget can be fetched.""" + engagement = factory_engagement_model() + widget_info['engagement_id'] = engagement.id + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.post('/api/widgets/engagement/' + str(engagement.id), data=json.dumps(widget_info), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + + rv = client.get('/api/widgets/engagement/' + str(engagement.id), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + assert rv.json[0].get('sort_index') == 1 + + with patch.object(WidgetService, 'get_widgets_by_engagement_id', side_effect=ValueError('Test error')): + rv = client.get('/api/widgets/engagement/' + str(engagement.id), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_create_widget_sort(client, jwt, session, setup_admin_user_and_claims): # pylint:disable=unused-argument @@ -149,3 +189,60 @@ def test_create_widget_items(client, jwt, session, widget_item_info, rv = client.post('/api/widgets/' + str(widget.id) + '/items', data=json.dumps([data]), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 + + with patch.object(WidgetService, 'save_widget_items_bulk', side_effect=ValueError('Test error')): + rv = client.post('/api/widgets/' + str(widget.id) + '/items', data=json.dumps([data]), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +def test_delete_widget(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that a widget can be deleted.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + user, claims = setup_admin_user_and_claims + + headers = factory_auth_header(jwt=jwt, claims=claims) + + rv = client.delete(f'/api/widgets/{widget.id}/engagements/' + str(engagement.id), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK + + widget = factory_widget_model(TestWidgetInfo.widget1) + with patch.object(WidgetService, 'delete_widget', side_effect=ValueError('Test error')): + rv = client.delete(f'/api/widgets/{widget.id}/engagements/' + str(engagement.id), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +def test_patch_widget(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that a widget can be PATCHed.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + user, claims = setup_admin_user_and_claims + + headers = factory_auth_header(jwt=jwt, claims=claims) + + data = { + 'title': fake.text(max_nb_chars=10), + } + rv = client.patch(f'/api/widgets/{widget.id}/engagements/' + str(engagement.id), data=json.dumps(data), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK + assert rv.json.get('title') == data.get('title') + + with patch.object(WidgetService, 'update_widget', side_effect=ValueError('Test error')): + rv = client.patch(f'/api/widgets/{widget.id}/engagements/' + str(engagement.id), data=json.dumps(data), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + with patch.object(WidgetService, 'update_widget', side_effect=ValidationError('Test error')): + rv = client.patch(f'/api/widgets/{widget.id}/engagements/' + str(engagement.id), data=json.dumps(data), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/met-api/tests/unit/api/test_widget_document.py b/met-api/tests/unit/api/test_widget_document.py index 609080240..08d612259 100644 --- a/met-api/tests/unit/api/test_widget_document.py +++ b/met-api/tests/unit/api/test_widget_document.py @@ -20,8 +20,11 @@ from http import HTTPStatus from faker import Faker +from unittest.mock import patch import pytest +from met_api.exceptions.business_exception import BusinessException +from met_api.services.widget_documents_service import WidgetDocumentService from met_api.utils.enums import ContentType, WidgetDocumentType from tests.utilities.factory_scenarios import TestJwtClaims, TestWidgetDocumentInfo, TestWidgetInfo from tests.utilities.factory_utils import ( @@ -52,6 +55,16 @@ def test_create_documents(client, jwt, session, document_info): # pylint:disabl ) assert rv.status_code == HTTPStatus.OK + with patch.object(WidgetDocumentService, 'create_document', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.post( + f'/api/widgets/{widget.id}/documents', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_get_document(client, jwt, session): # pylint:disable=unused-argument """Assert that widget items can be POSTed.""" @@ -75,6 +88,15 @@ def test_get_document(client, jwt, session): # pylint:disable=unused-argument assert rv.status_code == HTTPStatus.OK assert rv.json.get('children')[0].get('id') == document.id + with patch.object(WidgetDocumentService, 'get_documents_by_widget_id', side_effect=ValueError('Test error')): + rv = client.get( + f'/api/widgets/{widget.id}/documents', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_assert_tree_structure_invalid(client, jwt, session): # pylint:disable=unused-argument """Assert that widget items can be POSTed.""" @@ -98,7 +120,7 @@ def test_assert_tree_structure_invalid(client, jwt, session): # pylint:disable= content_type=ContentType.JSON.value ) # TODO once we remove action result , this should be HTTP 400 - assert rv.status_code == 500 + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR def test_assert_tree_structure(client, jwt, session): # pylint:disable=unused-argument @@ -163,6 +185,13 @@ def test_patch_documents(client, jwt, session): # pylint:disable=unused-argumen assert rv.status_code == HTTPStatus.OK assert rv.json.get('children')[0].get('title') == document_edits.get('title') + with patch.object(WidgetDocumentService, 'edit_document', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.patch(f'/api/widgets/{widget.id}/documents/{document.id}', + data=json.dumps(document_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_delete_documents(client, jwt, session): # pylint:disable=unused-argument """Assert that a document can be PATCHed.""" @@ -178,6 +207,12 @@ def test_delete_documents(client, jwt, session): # pylint:disable=unused-argume assert rv.status_code == HTTPStatus.OK + document = factory_document_model(TestWidgetDocumentInfo.document1) + with patch.object(WidgetDocumentService, 'delete_document', side_effect=ValueError('Test error')): + rv = client.delete(f'/api/widgets/{widget.id}/documents/{document.id}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_sort_folders(client, jwt, session): """Test sorting of folders.""" @@ -244,6 +279,13 @@ def test_sort_folders(client, jwt, session): reset_order = [doc['id'] for doc in rv.json['children'] if doc.get('type') == 'folder'] assert reset_order == initial_order + with patch.object(WidgetDocumentService, 'sort_documents', + side_effect=BusinessException('Test error', status_code=HTTPStatus.INTERNAL_SERVER_ERROR)): + rv = client.patch(f'/api/widgets/{widget.id}/documents/order', data=json.dumps({ + 'documents': reset_reorder_dict}), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + def test_sort_files(client, jwt, session): """Test sorting of documents within a folder.""" diff --git a/met-api/tests/unit/api/test_widget_event.py b/met-api/tests/unit/api/test_widget_event.py index 5b578fa1f..d7ac4b3e5 100644 --- a/met-api/tests/unit/api/test_widget_event.py +++ b/met-api/tests/unit/api/test_widget_event.py @@ -18,10 +18,14 @@ """ import json +from http import HTTPStatus from faker import Faker +from unittest.mock import patch from met_api.utils.enums import ContentType from met_api.constants.event_types import EventTypes +from met_api.exceptions.business_exception import BusinessException +from met_api.services.widget_events_service import WidgetEventsService from tests.utilities.factory_scenarios import TestEventnfo, TestJwtClaims, TestWidgetInfo from tests.utilities.factory_utils import factory_auth_header, factory_engagement_model, factory_widget_model @@ -48,12 +52,22 @@ def test_create_events(client, jwt, session): # pylint:disable=unused-argument headers=headers, content_type=ContentType.JSON.value ) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('title') == event_info.get('title') response_event_items = rv.json.get('event_items') assert len(response_event_items) == 1 assert response_event_items[0].get('description') == event_info.get('items')[0].get('description') + with patch.object(WidgetEventsService, 'create_event', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.post( + f'/api/widgets/{widget.id}/events', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + def test_widget_events_sort(client, jwt, session): # pylint:disable=unused-argument """Assert that a widget events can be sorted.""" @@ -73,7 +87,7 @@ def test_widget_events_sort(client, jwt, session): # pylint:disable=unused-argu headers=headers, content_type=ContentType.JSON.value ) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value event_info = TestEventnfo.event_virtual headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) @@ -87,14 +101,14 @@ def test_widget_events_sort(client, jwt, session): # pylint:disable=unused-argu headers=headers, content_type=ContentType.JSON.value ) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value rv = client.get( f'/api/widgets/{widget.id}/events', headers=headers, content_type=ContentType.JSON.value ) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert len(rv.json) == 2, 'Two Widget Events Should exist.' widget_events = rv.json open_house_event = _find_widget_events(widget_events, EventTypes.OPENHOUSE) @@ -119,7 +133,7 @@ def test_widget_events_sort(client, jwt, session): # pylint:disable=unused-argu headers=headers, content_type=ContentType.JSON.value ) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value rv = client.get( f'/api/widgets/{widget.id}/events', @@ -133,7 +147,116 @@ def test_widget_events_sort(client, jwt, session): # pylint:disable=unused-argu virtual_event = _find_widget_events(widget_events, EventTypes.VIRTUAL) assert virtual_event.get('sort_index') == 1 + with patch.object(WidgetEventsService, 'save_widget_events_bulk', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch( + f'/api/widgets/{widget.id}/events/sort_index', + data=json.dumps(reorder_dict), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + def _find_widget_events(widget_events, widget_event_type): _widget_event_type = next(x for x in widget_events if x.get('type') == widget_event_type.name) return _widget_event_type + + +def test_delete_events(client, jwt, session): # pylint:disable=unused-argument + """Assert that widget events can be deleted.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + event_info = TestEventnfo.event_meetup + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + data = { + **event_info, + 'widget_id': widget.id, + } + + rv = client.post( + f'/api/widgets/{widget.id}/events', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + + event_id = rv.json.get('id') + rv = client.delete( + f'/api/widgets/{widget.id}/events/{event_id}', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + + with patch.object(WidgetEventsService, 'delete_event', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.delete( + f'/api/widgets/{widget.id}/events/{event_id}', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_patch_events(client, jwt, session): # pylint:disable=unused-argument + """Assert that widget events can be PATCHed.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + event_info = TestEventnfo.event_meetup + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + data = { + **event_info, + 'widget_id': widget.id, + } + + rv = client.post( + f'/api/widgets/{widget.id}/events', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + response_event_items = rv.json.get('event_items') + item_id = response_event_items[0].get('id') + event_id = rv.json.get('id') + + event_edits = { + 'description': fake.text(max_nb_chars=20), + 'start_date': fake.date() + } + + rv = client.patch( + f'/api/widgets/{widget.id}/events/{event_id}/item/{item_id}', + data=json.dumps(event_edits), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + + rv = client.get( + f'/api/widgets/{widget.id}/events', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + event_items = rv.json[0].get('event_items', []) + description_value = event_items[0].get('description') + assert description_value == event_edits.get('description') + + with patch.object(WidgetEventsService, 'update_event_item', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch( + f'/api/widgets/{widget.id}/events/{event_id}/item/{item_id}', + data=json.dumps(event_edits), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST diff --git a/met-api/tests/unit/api/test_widget_map.py b/met-api/tests/unit/api/test_widget_map.py new file mode 100644 index 000000000..79b793ee6 --- /dev/null +++ b/met-api/tests/unit/api/test_widget_map.py @@ -0,0 +1,161 @@ +# Copyright © 2019 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 verify the Widget Map API end-point. + +Test-Suite to ensure that the Widget Map API endpoint +is working as expected. +""" +import json +from http import HTTPStatus + +from faker import Faker +from unittest.mock import patch + +from met_api.exceptions.business_exception import BusinessException +from met_api.services.widget_map_service import WidgetMapService +from met_api.utils.enums import ContentType +from tests.utilities.factory_scenarios import TestJwtClaims, TestWidgetInfo, TestWidgetMap +from tests.utilities.factory_utils import ( + factory_auth_header, factory_engagement_model, factory_widget_map_model, factory_widget_model) + + +fake = Faker() + + +def test_create_map_widget(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that widget map can be POSTed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + TestWidgetInfo.widget_map['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_map) + map_info = TestWidgetMap.map1 + headers = factory_auth_header(jwt=jwt, claims=claims) + + data = { + 'latitude': map_info['latitude'], + 'longitude': map_info['longitude'], + 'engagement_id': engagement.id, + 'marker_label': map_info['marker_label'], + } + + rv = client.post( + f'/api/widgets/{widget.id}/maps', + data=data, + headers=headers, + content_type=ContentType.FORM_URL_ENCODED.value + ) + + assert rv.status_code == HTTPStatus.OK.value + assert float(rv.json.get('longitude')) == float(map_info.get('longitude')) + + with patch.object(WidgetMapService, 'create_map', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.post( + f'/api/widgets/{widget.id}/maps', + data=data, + headers=headers, + content_type=ContentType.FORM_URL_ENCODED.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_get_map(client, jwt, session): # pylint:disable=unused-argument + """Assert that map can be fetched.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget_map['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_map) + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + map_widget_info = TestWidgetMap.map1 + + widget_map = factory_widget_map_model({ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'longitude': map_widget_info.get('longitude'), + 'latitude': map_widget_info.get('latitude'), + 'marker_label': map_widget_info.get('marker_label'), + }) + + rv = client.get( + f'/api/widgets/{widget.id}/maps', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('id') == widget_map.id + + with patch.object(WidgetMapService, 'get_map', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.get( + f'/api/widgets/{widget.id}/maps', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_patch_map(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that map can be PATCHed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + TestWidgetInfo.widget_map['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_map) + + headers = factory_auth_header(jwt=jwt, claims=claims) + + map_widget_info = TestWidgetMap.map1 + + factory_widget_map_model({ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'longitude': map_widget_info.get('longitude'), + 'latitude': map_widget_info.get('latitude'), + 'marker_label': map_widget_info.get('marker_label'), + }) + + map_edits = { + 'longitude': str(fake.longitude()) + } + + rv = client.patch( + f'/api/widgets/{widget.id}/maps', + data=json.dumps(map_edits), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'/api/widgets/{widget.id}/maps', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + assert round(float(rv.json[0].get('longitude')), 5) == round(float(map_edits.get('longitude')), 5) + + with patch.object(WidgetMapService, 'update_map', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch( + f'/api/widgets/{widget.id}/maps', + data=json.dumps(map_edits), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST diff --git a/met-api/tests/unit/api/test_widget_subscribe.py b/met-api/tests/unit/api/test_widget_subscribe.py index 9c4ab9018..4a2d32f9c 100644 --- a/met-api/tests/unit/api/test_widget_subscribe.py +++ b/met-api/tests/unit/api/test_widget_subscribe.py @@ -19,8 +19,13 @@ """ import json +from http import HTTPStatus from faker import Faker +from unittest.mock import patch +from met_api.constants.subscribe_types import SubscribeTypes +from met_api.exceptions.business_exception import BusinessException +from met_api.services.widget_subscribe_service import WidgetSubscribeService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims, TestSubscribeInfo, TestWidgetInfo from tests.utilities.factory_utils import factory_auth_header, factory_engagement_model, factory_widget_model @@ -54,9 +59,24 @@ def test_create_subscribe(client, jwt, session): # pylint:disable=unused-argume content_type=ContentType.JSON.value, ) # Checking response - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json.get('type') == subscribe_info.get('type') - # ... + + # test create subscribe exception + data = { + **subscribe_info, + 'widget_id': fake.pyint(), + } + + with patch.object(WidgetSubscribeService, 'create_subscribe', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.post( + f'/api/widgets/{widget.id}/subscribe', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value, + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST def test_get_subscribe(client, jwt, session): # pylint:disable=unused-argument @@ -85,7 +105,7 @@ def test_get_subscribe(client, jwt, session): # pylint:disable=unused-argument ) # Checking POST response - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value # Sending GET request rv = client.get( @@ -95,5 +115,182 @@ def test_get_subscribe(client, jwt, session): # pylint:disable=unused-argument ) # Checking GET response - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK.value assert rv.json[0].get('type') == subscribe_info.get('type') + + +def test_delete_subscribe(client, jwt, session): # pylint:disable=unused-argument + """Assert that widget subscribe can be deleted.""" + engagement = factory_engagement_model() + + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + + subscribe_info = TestSubscribeInfo.subscribe_info_1.value + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + # Preparing data + data = { + **subscribe_info, + 'widget_id': widget.id, + } + + # Sending POST request + rv = client.post( + f'/api/widgets/{widget.id}/subscribe', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value, + ) + # Checking response + assert rv.status_code == HTTPStatus.OK.value + subscribe_id = rv.json.get('id') + + # Sending DELETE request + rv = client.delete( + f'/api/widgets/{widget.id}/subscribe/{subscribe_id}', + headers=headers, + content_type=ContentType.JSON.value, + ) + # Checking response + assert rv.status_code == HTTPStatus.OK.value + + # test delete subscribe exception + widget_id = fake.pyint() + # Sending DELETE request + with patch.object(WidgetSubscribeService, 'delete_subscribe', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.delete( + f'/api/widgets/{widget_id}/subscribe/{subscribe_id}', + headers=headers, + content_type=ContentType.JSON.value, + ) + # Checking response + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_subscribe_sorting(client, jwt, session): # pylint:disable=unused-argument + """Assert that widget subscribe can be sorted.""" + engagement = factory_engagement_model() + + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + subscribe_info = TestSubscribeInfo.subscribe_info_1.value + data = { + **subscribe_info, + 'widget_id': widget.id, + } + rv = client.post(f'/api/widgets/{widget.id}/subscribe', data=json.dumps(data), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK.value + + subscribe_info = TestSubscribeInfo.subscribe_info_2.value + data = { + **subscribe_info, + 'widget_id': widget.id, + } + rv = client.post(f'/api/widgets/{widget.id}/subscribe', data=json.dumps(data), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK.value + + rv = client.get(f'/api/widgets/{widget.id}/subscribe', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK.value + assert len(rv.json) == 2, 'Two Items Should exist.' + widgets = rv.json + email_list = _find_item(widgets, SubscribeTypes.EMAIL_LIST.name) + assert email_list.get('sort_index') == 1 + + sign_up = _find_item(widgets, SubscribeTypes.SIGN_UP.name) + assert sign_up.get('sort_index') == 2 + + # Do reorder + reorder_dict = [ + { + 'id': sign_up.get('id'), + }, + { + 'id': email_list.get('id'), + } + ] + + rv = client.patch(f'/api/widgets/{widget.id}/subscribe/sort_index', data=json.dumps(reorder_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK.value + + rv = client.get(f'/api/widgets/{widget.id}/subscribe', headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK.value + widgets = rv.json + email_list = _find_item(widgets, SubscribeTypes.EMAIL_LIST.name) + assert email_list.get('sort_index') == 2 + + sign_up = _find_item(widgets, SubscribeTypes.SIGN_UP.name) + assert sign_up.get('sort_index') == 1 + + # test subscribe sorting exception + widget_id = fake.pyint() + with patch.object(WidgetSubscribeService, 'save_widget_subscribes_bulk', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch(f'/api/widgets/{widget_id}/subscribe/sort_index', data=json.dumps(reorder_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def _find_item(widgets, type): + widget_type = next(x for x in widgets if x.get('type') == type) + return widget_type + + +def test_update_subscribe(client, jwt, session): # pylint:disable=unused-argument + """Assert that widget subscribe can be PATCHed.""" + engagement = factory_engagement_model() + + TestWidgetInfo.widget1['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget1) + + subscribe_info = TestSubscribeInfo.subscribe_info_1.value + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + # Preparing data + data = { + **subscribe_info, + 'widget_id': widget.id, + } + # Sending POST request + rv = client.post( + f'/api/widgets/{widget.id}/subscribe', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value, + ) + # Checking response + assert rv.status_code == HTTPStatus.OK.value + subscribe_id = rv.json.get('id') + subscribe_items = rv.json.get('subscribe_items') + item_id = subscribe_items[0].get('id') + + subscribe_edits = { + 'description': fake.text(max_nb_chars=20), + 'call_to_action_text': fake.text(max_nb_chars=20) + } + # Sending PATCH request + rv = client.patch(f'/api/widgets/{widget.id}/subscribe/{subscribe_id}/item/{item_id}', + data=json.dumps(subscribe_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + rv = client.get(f'/api/widgets/{widget.id}/subscribe', headers=headers, content_type=ContentType.JSON.value) + subscribe_items = rv.json[0].get('subscribe_items') + assert subscribe_items[0].get('call_to_action_text') == subscribe_edits.get('call_to_action_text') + + # test update subscribe exception + widget_id = fake.pyint() + with patch.object(WidgetSubscribeService, 'update_subscribe_item', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch(f'/api/widgets/{widget_id}/subscribe/{subscribe_id}/item/{item_id}', + data=json.dumps(subscribe_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.BAD_REQUEST diff --git a/met-api/tests/unit/api/test_widget_timeline.py b/met-api/tests/unit/api/test_widget_timeline.py new file mode 100644 index 000000000..3b7e7308a --- /dev/null +++ b/met-api/tests/unit/api/test_widget_timeline.py @@ -0,0 +1,196 @@ +# Copyright © 2019 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 verify the Widget Timeline API end-point. + +Test-Suite to ensure that the Widget Timeline API endpoint +is working as expected. +""" +import json +from http import HTTPStatus +from unittest.mock import patch + +from faker import Faker + +from met_api.constants.timeline_event_status import TimelineEventStatus +from met_api.exceptions.business_exception import BusinessException +from met_api.services.widget_timeline_service import WidgetTimelineService +from met_api.utils.enums import ContentType +from tests.utilities.factory_scenarios import TestJwtClaims, TestTimelineInfo, TestWidgetInfo +from tests.utilities.factory_utils import ( + factory_auth_header, factory_engagement_model, factory_timeline_event_model, factory_widget_model, + factory_widget_timeline_model) + + +fake = Faker() + + +def test_create_timeline_widget(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that widget timeline can be POSTed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + TestWidgetInfo.widget_timeline['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_timeline) + timeline_info = TestTimelineInfo.timeline_event + headers = factory_auth_header(jwt=jwt, claims=claims) + + events = [{ + **timeline_info, + 'widget_id': widget.id, + 'engagement_id': engagement.id, + }] + + timeline_widget_info = TestTimelineInfo.widget_timeline + + data = { + **timeline_widget_info, + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'events': events + } + + rv = client.post( + f'/api/widgets/{widget.id}/timelines', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + assert rv.json.get('engagement_id') == engagement.id + + with patch.object(WidgetTimelineService, 'create_timeline', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.post( + f'/api/widgets/{widget.id}/timelines', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_get_timeline(client, jwt, session): # pylint:disable=unused-argument + """Assert that timeline can be fetched.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget_timeline['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_timeline) + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + timeline_widget_info = TestTimelineInfo.widget_timeline + timeline_event_info = TestTimelineInfo.timeline_event + + widget_timeline = factory_widget_timeline_model({ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'title': timeline_widget_info.get('title'), + 'description': timeline_widget_info.get('description'), + }) + + factory_timeline_event_model({ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'timeline_id': widget_timeline.id, + 'status': timeline_event_info.get('status'), + 'position': timeline_event_info.get('position'), + 'description': timeline_event_info.get('description'), + 'time': timeline_event_info.get('time'), + }) + + rv = client.get( + f'/api/widgets/{widget.id}/timelines', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('id') == widget_timeline.id + assert len(rv.json[0]['events']) == 1 + + with patch.object(WidgetTimelineService, 'get_timeline', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.get( + f'/api/widgets/{widget.id}/timelines', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_patch_timeline(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that a timeline can be PATCHed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + TestWidgetInfo.widget_video['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_video) + + timeline_widget_info = TestTimelineInfo.widget_timeline + timeline_event_info = TestTimelineInfo.timeline_event + + widget_timeline = factory_widget_timeline_model({ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'title': timeline_widget_info.get('title'), + 'description': timeline_widget_info.get('description'), + }) + + timeline_event = factory_timeline_event_model({ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'timeline_id': widget_timeline.id, + 'status': timeline_event_info.get('status'), + 'position': timeline_event_info.get('position'), + 'description': timeline_event_info.get('description'), + 'time': timeline_event_info.get('time'), + }) + + headers = factory_auth_header(jwt=jwt, claims=claims) + + timeline_edits = { + 'title': fake.name(), + 'description': fake.text(max_nb_chars=20), + 'events': [{ + 'widget_id': widget.id, + 'engagement_id': engagement.id, + 'timeline_id': widget_timeline.id, + 'status': timeline_event_info.get('status'), + 'position': TimelineEventStatus.Completed.value, + 'description': timeline_event_info.get('description'), + 'time': timeline_event_info.get('time'), + }] + } + + rv = client.patch(f'/api/widgets/{widget.id}/timelines/{timeline_event.id}', + data=json.dumps(timeline_edits), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'/api/widgets/{widget.id}/timelines', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('description') == timeline_edits.get('description') + assert len(rv.json[0]['events']) == 1 + + with patch.object(WidgetTimelineService, 'update_timeline', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch(f'/api/widgets/{widget.id}/timelines/{timeline_event.id}', + data=json.dumps(timeline_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.BAD_REQUEST diff --git a/met-api/tests/unit/api/test_widget_video.py b/met-api/tests/unit/api/test_widget_video.py new file mode 100644 index 000000000..d2c6aa10e --- /dev/null +++ b/met-api/tests/unit/api/test_widget_video.py @@ -0,0 +1,146 @@ +# Copyright © 2019 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 verify the Widget Video API end-point. + +Test-Suite to ensure that the Widget Video API endpoint +is working as expected. +""" +import json +from http import HTTPStatus +from unittest.mock import patch + +from faker import Faker + +from met_api.utils.enums import ContentType +from met_api.exceptions.business_exception import BusinessException +from met_api.services.widget_video_service import WidgetVideoService +from tests.utilities.factory_scenarios import TestJwtClaims, TestWidgetInfo, TestWidgetVideo +from tests.utilities.factory_utils import ( + factory_auth_header, factory_engagement_model, factory_video_model, factory_widget_model) + + +fake = Faker() + + +def test_create_video_widget(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that widget video can be POSTed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + TestWidgetInfo.widget_video['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_video) + video_info = TestWidgetVideo.video1 + headers = factory_auth_header(jwt=jwt, claims=claims) + + data = { + **video_info, + 'widget_id': widget.id, + 'engagement_id': engagement.id, + } + + rv = client.post( + f'/api/widgets/{widget.id}/videos', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK.value + assert rv.json.get('video_url') == video_info.get('video_url') + + with patch.object(WidgetVideoService, 'create_video', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.post( + f'/api/widgets/{widget.id}/videos', + data=json.dumps(data), + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_get_video(client, jwt, session): # pylint:disable=unused-argument + """Assert that video can be fetched.""" + engagement = factory_engagement_model() + TestWidgetInfo.widget_video['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_video) + + headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + + video = factory_video_model({ + **TestWidgetVideo.video1, + 'widget_id': widget.id, + 'engagement_id': engagement.id, + }) + + rv = client.get( + f'/api/widgets/{widget.id}/videos', + headers=headers, + content_type=ContentType.JSON.value + ) + + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('id') == video.id + + with patch.object(WidgetVideoService, 'get_video', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.get( + f'/api/widgets/{widget.id}/videos', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.BAD_REQUEST + + +def test_patch_video(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument + """Assert that a video can be PATCHed.""" + user, claims = setup_admin_user_and_claims + engagement = factory_engagement_model() + TestWidgetInfo.widget_video['engagement_id'] = engagement.id + widget = factory_widget_model(TestWidgetInfo.widget_video) + + video = factory_video_model({ + **TestWidgetVideo.video1, + 'widget_id': widget.id, + 'engagement_id': engagement.id, + }) + + headers = factory_auth_header(jwt=jwt, claims=claims) + + video_edits = { + 'description': fake.text(max_nb_chars=20), + 'video_url': fake.url(), + } + + rv = client.patch(f'/api/widgets/{widget.id}/videos/{video.id}', + data=json.dumps(video_edits), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == HTTPStatus.OK + + rv = client.get( + f'/api/widgets/{widget.id}/videos', + headers=headers, + content_type=ContentType.JSON.value + ) + assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('description') == video_edits.get('description') + + with patch.object(WidgetVideoService, 'update_video', + side_effect=BusinessException('Test error', status_code=HTTPStatus.BAD_REQUEST)): + rv = client.patch(f'/api/widgets/{widget.id}/videos/{video.id}', + data=json.dumps(video_edits), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.BAD_REQUEST diff --git a/met-api/tests/utilities/factory_scenarios.py b/met-api/tests/utilities/factory_scenarios.py index 514f817ce..4b15ca5a7 100644 --- a/met-api/tests/utilities/factory_scenarios.py +++ b/met-api/tests/utilities/factory_scenarios.py @@ -25,6 +25,7 @@ from met_api.constants.comment_status import Status as CommentStatus from met_api.constants.engagement_status import Status as EngagementStatus from met_api.constants.engagement_status import SubmissionStatus +from met_api.constants.timeline_event_status import TimelineEventStatus from met_api.constants.feedback import CommentType, FeedbackSourceType, FeedbackStatusType, RatingType from met_api.constants.widget import WidgetType from met_api.utils.enums import LoginSource, UserStatus @@ -270,10 +271,8 @@ class TestJwtClaims(dict, Enum): 'firstname': fake.first_name(), 'lastname': fake.last_name(), 'preferred_username': fake.user_name(), - 'realm_access': { - 'roles': [ - ] - } + 'client_roles': [ + ] } public_user_role = { @@ -284,11 +283,9 @@ class TestJwtClaims(dict, Enum): 'preferred_username': fake.user_name(), 'email': fake.email(), 'tenant_id': 1, - 'realm_access': { - 'roles': [ - 'public_user' - ] - } + 'client_roles': [ + 'public_user' + ] } met_admin_role = { @@ -301,20 +298,18 @@ class TestJwtClaims(dict, Enum): 'tenant_id': 1, 'email': 'staff@gov.bc.ca', 'identity_provider': LoginSource.IDIR.value, - 'realm_access': { - 'roles': [ - 'staff', - 'view_engagement', - 'create_survey', - 'view_users', - 'create_admin_user', - 'edit_members', - 'toggle_user_status', - 'export_to_csv', - 'update_user_group', - 'create_tenant' - ] - } + 'client_roles': [ + 'staff', + 'view_engagement', + 'create_survey', + 'view_users', + 'create_admin_user', + 'edit_members', + 'toggle_user_status', + 'export_to_csv', + 'update_user_group', + 'create_tenant' + ] } staff_admin_role = { @@ -327,33 +322,33 @@ class TestJwtClaims(dict, Enum): 'tenant_id': 1, 'email': 'staff@gov.bc.ca', 'identity_provider': LoginSource.IDIR.value, - 'realm_access': { - 'roles': [ - 'staff', - 'view_engagement', - 'create_engagement', - 'edit_engagement', - 'create_survey', - 'view_users', - 'view_private_engagements', - 'create_admin_user', - 'view_all_surveys', - 'view_surveys', - 'edit_all_surveys', - 'edit_survey', - 'view_unapproved_comments', - 'clone_survey', - 'edit_members', - 'review_comments', - 'review_all_comments', - 'view_all_engagements', - 'toggle_user_status', - 'export_all_to_csv', - 'update_user_group', - 'export_proponent_comment_sheet', - 'export_internal_comment_sheet' - ] - } + 'client_roles': [ + 'staff', + 'view_engagement', + 'create_engagement', + 'edit_engagement', + 'create_survey', + 'view_users', + 'view_private_engagements', + 'create_admin_user', + 'view_all_surveys', + 'view_surveys', + 'edit_all_surveys', + 'edit_survey', + 'view_unapproved_comments', + 'clone_survey', + 'edit_members', + 'review_comments', + 'review_all_comments', + 'view_all_engagements', + 'toggle_user_status', + 'export_all_to_csv', + 'update_user_group', + 'export_proponent_comment_sheet', + 'export_internal_comment_sheet', + 'export_cac_form_to_sheet', + 'view_members' + ] } team_member_role = { 'iss': CONFIG.JWT_OIDC_TEST_ISSUER, @@ -365,15 +360,13 @@ class TestJwtClaims(dict, Enum): 'email': 'staff@gov.bc.ca', 'identity_provider': LoginSource.IDIR.value, 'tenant_id': 1, - 'realm_access': { - 'roles': [ - 'staff', - 'view_engagement', - 'view_users', - 'clone_survey', - 'export_proponent_comment_sheet' - ] - } + 'client_roles': [ + 'staff', + 'view_engagement', + 'view_users', + 'clone_survey', + 'export_proponent_comment_sheet' + ] } reviewer_role = { @@ -386,12 +379,10 @@ class TestJwtClaims(dict, Enum): 'email': 'staff@gov.bc.ca', 'identity_provider': LoginSource.IDIR.value, 'tenant_id': 1, - 'realm_access': { - 'roles': [ - 'staff', - 'view_users', - ] - } + 'client_roles': [ + 'staff', + 'view_users', + ] } @@ -426,6 +417,27 @@ class TestWidgetInfo(dict, Enum): 'created_date': datetime.now().strftime('%Y-%m-%d'), 'updated_date': datetime.now().strftime('%Y-%m-%d'), } + widget_map = { + 'widget_type_id': WidgetType.Map.value, + 'created_by': '123', + 'updated_by': '123', + 'created_date': datetime.now().strftime('%Y-%m-%d'), + 'updated_date': datetime.now().strftime('%Y-%m-%d'), + } + widget_video = { + 'widget_type_id': WidgetType.Video.value, + 'created_by': '123', + 'updated_by': '123', + 'created_date': datetime.now().strftime('%Y-%m-%d'), + 'updated_date': datetime.now().strftime('%Y-%m-%d'), + } + widget_timeline = { + 'widget_type_id': WidgetType.Timeline.value, + 'created_by': '123', + 'updated_by': '123', + 'created_date': datetime.now().strftime('%Y-%m-%d'), + 'updated_date': datetime.now().strftime('%Y-%m-%d'), + } class TestWidgetItemInfo(dict, Enum): @@ -667,3 +679,75 @@ class TestSubscribeInfo(Enum): } ] } + + subscribe_info_2 = { + 'widget_id': 1, + 'type': 'SIGN_UP', + 'items': [ + { + 'description': '{\"blocks\":[{\"key\":\"2ku94\",\"text\":\ + "Rich Description Sample\",\"type\":\"unstyled\", \ + "depth\":0,\"inlineStyleRanges\":[],\ + "entityRanges\":[],\"data\":{}}],\"entityMap\":{}}', + 'call_to_action_type': 'link', + 'call_to_action_text': 'Click here to sign up', + 'form_type': 'SIGN_UP' + } + ] + } + + +class TestCACForm(dict, Enum): + """Test scenarios of cac form.""" + + form_data = { + 'understand': True, + 'terms_of_reference': True, + 'first_name': fake.name(), + 'last_name': fake.name(), + 'city': 'City', + 'email': fake.email(), + } + + +class TestWidgetMap(dict, Enum): + """Test scenarios of video widget.""" + + map1 = { + 'longitude': fake.longitude(), + 'latitude': fake.latitude(), + 'marker_label': fake.name() + } + + map2 = { + 'longitude': fake.longitude(), + 'latitude': fake.latitude(), + 'marker_label': fake.name() + } + + +class TestWidgetVideo(dict, Enum): + """Test scenarios of video widget.""" + + video1 = { + 'id': '1', + 'video_url': fake.url(), + 'description': fake.text(max_nb_chars=50), + } + + +class TestTimelineInfo(dict, Enum): + """Test scenarios of event.""" + + widget_timeline = { + 'title': fake.name(), + 'description': fake.text(max_nb_chars=50), + } + + timeline_event = { + 'timeline_id': '1', + 'description': fake.text(max_nb_chars=50), + 'time': datetime.now().strftime('%Y-%m-%d'), + 'position': 1, + 'status': TimelineEventStatus.Pending.value + } diff --git a/met-api/tests/utilities/factory_utils.py b/met-api/tests/utilities/factory_utils.py index 8d009ad1c..feeef81cb 100644 --- a/met-api/tests/utilities/factory_utils.py +++ b/met-api/tests/utilities/factory_utils.py @@ -35,15 +35,19 @@ from met_api.models.submission import Submission as SubmissionModel from met_api.models.subscription import Subscription as SubscriptionModel from met_api.models.survey import Survey as SurveyModel +from met_api.models.timeline_event import TimelineEvent as TimelineEventModel from met_api.models.widget import Widget as WidgetModal from met_api.models.widget_documents import WidgetDocuments as WidgetDocumentModel +from met_api.models.widget_map import WidgetMap as WidgetMapModel +from met_api.models.widget_timeline import WidgetTimeline as WidgetTimelineModel +from met_api.models.widget_video import WidgetVideo as WidgetVideoModel from met_api.models.widget_item import WidgetItem as WidgetItemModal from met_api.utils.constants import TENANT_ID_HEADER from met_api.utils.enums import MembershipStatus from tests.utilities.factory_scenarios import ( TestCommentInfo, TestEngagementInfo, TestEngagementSlugInfo, TestFeedbackInfo, TestParticipantInfo, - TestReportSettingInfo, TestSubmissionInfo, TestSurveyInfo, TestTenantInfo, TestUserInfo, TestWidgetDocumentInfo, - TestWidgetInfo, TestWidgetItemInfo) + TestReportSettingInfo, TestSubmissionInfo, TestSurveyInfo, TestTenantInfo, TestTimelineInfo, TestUserInfo, + TestWidgetDocumentInfo, TestWidgetInfo, TestWidgetItemInfo, TestWidgetMap, TestWidgetVideo) CONFIG = get_named_config('testing') fake = Faker() @@ -334,3 +338,55 @@ def factory_engagement_setting_model(engagement_id): ) setting.save() return setting + + +def factory_video_model(video_info: dict = TestWidgetVideo.video1): + """Produce a comment model.""" + video = WidgetVideoModel( + video_url=video_info.get('video_url'), + description=video_info.get('description'), + widget_id=video_info.get('widget_id'), + engagement_id=video_info.get('engagement_id'), + ) + video.save() + return video + + +def factory_widget_timeline_model(widget_timeline: dict = TestTimelineInfo.widget_timeline): + """Produce a widget timeline model.""" + widget_timeline = WidgetTimelineModel( + widget_id=widget_timeline.get('widget_id'), + engagement_id=widget_timeline.get('engagement_id'), + title=widget_timeline.get('title'), + description=widget_timeline.get('description'), + ) + widget_timeline.save() + return widget_timeline + + +def factory_timeline_event_model(timeline_event: dict = TestTimelineInfo.timeline_event): + """Produce a widget timeline model.""" + timeline_event = TimelineEventModel( + widget_id=timeline_event.get('widget_id'), + engagement_id=timeline_event.get('engagement_id'), + timeline_id=timeline_event.get('timeline_id'), + status=timeline_event.get('status'), + position=timeline_event.get('position'), + description=timeline_event.get('description'), + time=timeline_event.get('time'), + ) + timeline_event.save() + return timeline_event + + +def factory_widget_map_model(widget_map: dict = TestWidgetMap.map1): + """Produce a widget map model.""" + widget_map = WidgetMapModel( + widget_id=widget_map.get('widget_id'), + engagement_id=widget_map.get('engagement_id'), + longitude=widget_map.get('longitude'), + latitude=widget_map.get('latitude'), + marker_label=widget_map.get('marker_label'), + ) + widget_map.save() + return widget_map