diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 8b95e4989..8087bb30e 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -8,6 +8,8 @@ - **Bug Fix**: MET - Engagement tab does not revert the filtered out data [DESENG-525](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-525) - Resetting search filter values invoke the list to reload +- **Task**: Add missing unit tests for analytics api [DESENG-482](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-482) + - Added unit tests for analytics API models and API end points ## March 26, 2024 diff --git a/analytics-api/tests/unit/api/test_aggregator.py b/analytics-api/tests/unit/api/test_aggregator.py index 9fbc2f2cc..4745a9d18 100644 --- a/analytics-api/tests/unit/api/test_aggregator.py +++ b/analytics-api/tests/unit/api/test_aggregator.py @@ -16,13 +16,23 @@ Test-Suite to ensure that the aggregator endpoint is working as expected. """ +import pytest +from http import HTTPStatus +from unittest.mock import patch + +from analytics_api.services.aggregator_service import AggregatorService from analytics_api.utils.util import ContentType from tests.utilities.factory_utils import factory_engagement_model, factory_email_verification_model -def test_get_aggregator_data(client, session): # pylint:disable=unused-argument +@pytest.mark.parametrize("exception_type", [KeyError, ValueError]) +def test_get_aggregator_data(client, exception_type, session): # pylint:disable=unused-argument """Assert that aggregator data for an engagement can be fetched.""" - engagement = factory_engagement_model() - emailverification = factory_email_verification_model() - rv = client.get(f'/api/counts/', content_type=ContentType.JSON.value) - assert rv.status_code == 200 + factory_engagement_model() + factory_email_verification_model() + rv = client.get('/api/counts/', content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + with patch.object(AggregatorService, 'get_count', side_effect=exception_type('Test error')): + rv = client.get('/api/counts/', content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/analytics-api/tests/unit/api/test_engagement.py b/analytics-api/tests/unit/api/test_engagement.py index d656a3c18..e0008331a 100644 --- a/analytics-api/tests/unit/api/test_engagement.py +++ b/analytics-api/tests/unit/api/test_engagement.py @@ -16,12 +16,36 @@ Test-Suite to ensure that the engagement endpoint is working as expected. """ +import pytest +from http import HTTPStatus +from unittest.mock import patch + +from analytics_api.services.engagement_service import EngagementService from analytics_api.utils.util import ContentType from tests.utilities.factory_utils import factory_engagement_model -def test_get_engagement(client, session): # pylint:disable=unused-argument +@pytest.mark.parametrize("exception_type", [KeyError, ValueError]) +def test_get_engagement(client, exception_type, session): # pylint:disable=unused-argument """Assert that engagement can be fetched.""" engagement = factory_engagement_model() rv = client.get(f'/api/engagements/{engagement.id}', content_type=ContentType.JSON.value) - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK + + with patch.object(EngagementService, 'get_engagement', side_effect=exception_type('Test error')): + rv = client.get(f'/api/engagements/{engagement.id}', content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize("exception_type", [KeyError, ValueError]) +def test_get_engagement_map(client, exception_type, session): # pylint:disable=unused-argument + """Assert that engagement can be fetched.""" + engagement = factory_engagement_model() + rv = client.get(f'/api/engagements/map/{engagement.source_engagement_id}', + content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + with patch.object(EngagementService, 'get_engagement_map_data', side_effect=exception_type('Test error')): + rv = client.get(f'/api/engagements/map/{engagement.source_engagement_id}', + content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/analytics-api/tests/unit/api/test_survey_result.py b/analytics-api/tests/unit/api/test_survey_result.py new file mode 100644 index 000000000..42d6ae02f --- /dev/null +++ b/analytics-api/tests/unit/api/test_survey_result.py @@ -0,0 +1,95 @@ +# 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 survey result API end-point. + +Test-Suite to ensure that the survey result endpoint is working as expected. +""" +import json +from http import HTTPStatus +from faker import Faker +from unittest.mock import patch + +from analytics_api.services.survey_result import SurveyResultService +from analytics_api.utils.util import ContentType +from tests.utilities.factory_scenarios import TestJwtClaims, TestRequestTypeOptionInfo +from tests.utilities.factory_utils import ( + factory_available_response_option_model, factory_engagement_model, factory_request_type_option_model, + factory_response_type_option_model, factory_survey_model) + +fake = Faker() + + +def test_get_survey_result_internal(client, mocker, session): # pylint:disable=unused-argument + """Assert that survey result can be fetched.""" + engagement = factory_engagement_model() + survey = factory_survey_model(engagement) + available_response_option = factory_available_response_option_model(survey) + factory_request_type_option_model(survey, available_response_option.request_key, + TestRequestTypeOptionInfo.request_type_option3) + factory_response_type_option_model(survey, available_response_option.request_key, + available_response_option.value) + + # Mock the return value of get_survey_result method + mocked_survey_result = {"data": "mocked_survey_result"} + mocker.patch.object(SurveyResultService, 'get_survey_result', return_value=mocked_survey_result) + + token_str = json.dumps(TestJwtClaims.staff_admin_role.value) + + with patch("analytics_api.resources.survey_result._jwt.has_one_of_roles", return_value=True), \ + patch("analytics_api.resources.survey_result.SurveyResultInternal.get") as mock_get: + + # Mock the get method of SurveyResultInternal to directly return the mocked data + def mock_get(engagement_id): + return mocked_survey_result + + mocker.patch("analytics_api.resources.survey_result.SurveyResultInternal.get", side_effect=mock_get) + + # Call the endpoint directly without involving authentication decorators + rv = client.get(f'/api/surveyresult/{engagement.source_engagement_id}/internal', + headers={'Authorization': 'Bearer ' + token_str}) + + # Check if the response status code is HTTPStatus.OK + assert rv.status_code == HTTPStatus.OK + + +def test_get_survey_result_public(client, session): # pylint:disable=unused-argument + """Assert that survey result can be fetched.""" + engagement = factory_engagement_model() + survey = factory_survey_model(engagement) + available_response_option = factory_available_response_option_model(survey) + factory_request_type_option_model(survey, available_response_option.request_key, + TestRequestTypeOptionInfo.request_type_option2) + factory_response_type_option_model(survey, available_response_option.request_key, + available_response_option.value) + + rv = client.get(f'/api/surveyresult/{engagement.source_engagement_id}/public', + content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.OK + + rv = client.get(f'/api/surveyresult/{fake.random_int(min=11, max=99)}/public', + content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.NOT_FOUND + + with patch.object(SurveyResultService, 'get_survey_result', + side_effect=KeyError('Test error')): + rv = client.get(f'/api/surveyresult/{engagement.source_engagement_id}/public', + content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + with patch.object(SurveyResultService, 'get_survey_result', + side_effect=ValueError('Test error')): + rv = client.get(f'/api/surveyresult/{engagement.source_engagement_id}/public', + content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/analytics-api/tests/unit/api/test_user_response_detail.py b/analytics-api/tests/unit/api/test_user_response_detail.py index 3c8bd886c..97cd2aa58 100644 --- a/analytics-api/tests/unit/api/test_user_response_detail.py +++ b/analytics-api/tests/unit/api/test_user_response_detail.py @@ -16,30 +16,52 @@ Test-Suite to ensure that the user response detail endpoint is working as expected. """ +import pytest +from unittest.mock import patch +from http import HTTPStatus + +from analytics_api.services.user_response_detail import UserResponseDetailService from analytics_api.utils.util import ContentType from datetime import datetime, timedelta -from tests.utilities.factory_utils import factory_user_response_detail_model, factory_survey_model +from tests.utilities.factory_utils import ( + factory_engagement_model, factory_user_response_detail_model, factory_survey_model) -def test_get_user_responses_by_month(client, session): # pylint:disable=unused-argument +@pytest.mark.parametrize("exception_type", [KeyError, ValueError]) +def test_get_user_responses_by_month(client, exception_type, session): # pylint:disable=unused-argument """Assert that user response detail by month can be fetched.""" - survey_data = factory_survey_model() + eng = factory_engagement_model() + survey_data = factory_survey_model(eng) user_response_detail = factory_user_response_detail_model(survey_data.id) from_date = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d') to_date = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d') rv = client.get(f'/api/responses/month/{user_response_detail.engagement_id}\ ?&from_date={from_date}&to_date={to_date}', content_type=ContentType.JSON.value) assert rv.json[0].get('responses') == 1 - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK + + with patch.object(UserResponseDetailService, 'get_response_count_by_created_month', + side_effect=exception_type('Test error')): + rv = client.get(f'/api/responses/month/{user_response_detail.engagement_id}\ + ?&from_date={from_date}&to_date={to_date}', content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR -def test_get_user_responses_by_week(client, session): # pylint:disable=unused-argument +@pytest.mark.parametrize("exception_type", [KeyError, ValueError]) +def test_get_user_responses_by_week(client, exception_type, session): # pylint:disable=unused-argument """Assert that user response detail by week can be fetched.""" - survey_data = factory_survey_model() + eng = factory_engagement_model() + survey_data = factory_survey_model(eng) user_response_detail = factory_user_response_detail_model(survey_data.id) from_date = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d') to_date = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d') rv = client.get(f'/api/responses/week/{user_response_detail.engagement_id}\ ?&from_date={from_date}&to_date={to_date}', content_type=ContentType.JSON.value) assert rv.json[0].get('responses') == 1 - assert rv.status_code == 200 + assert rv.status_code == HTTPStatus.OK + + with patch.object(UserResponseDetailService, 'get_response_count_by_created_week', + side_effect=exception_type('Test error')): + rv = client.get(f'/api/responses/week/{user_response_detail.engagement_id}\ + ?&from_date={from_date}&to_date={to_date}', content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/analytics-api/tests/unit/models/test_available_response_option.py b/analytics-api/tests/unit/models/test_available_response_option.py new file mode 100644 index 000000000..3d16ac7a8 --- /dev/null +++ b/analytics-api/tests/unit/models/test_available_response_option.py @@ -0,0 +1,36 @@ +# 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 User response detail API end-point. + +Test-Suite to ensure that the user response detail endpoint is working as expected. +""" +from http import HTTPStatus +from analytics_api.utils.util import ContentType +from datetime import datetime, timedelta +from tests.utilities.factory_utils import ( + factory_engagement_model, factory_user_response_detail_model, factory_survey_model) + + +def test_get_user_responses_by_month(client, session): # pylint:disable=unused-argument + """Assert that user response detail by month can be fetched.""" + eng = factory_engagement_model() + survey_data = factory_survey_model(eng) + user_response_detail = factory_user_response_detail_model(survey_data.id) + from_date = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d') + to_date = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d') + rv = client.get(f'/api/responses/month/{user_response_detail.engagement_id}\ + ?&from_date={from_date}&to_date={to_date}', content_type=ContentType.JSON.value) + assert rv.json[0].get('responses') == 1 + assert rv.status_code == HTTPStatus.OK diff --git a/analytics-api/tests/unit/models/test_email_verification.py b/analytics-api/tests/unit/models/test_email_verification.py new file mode 100644 index 000000000..ff882adcf --- /dev/null +++ b/analytics-api/tests/unit/models/test_email_verification.py @@ -0,0 +1,36 @@ +# 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 for the Engagement model. + +Test suite to ensure that the Engagement model routines are working as expected. +""" + +from analytics_api.models import EmailVerification as EmailVerificationModel +from tests.utilities.factory_utils import factory_email_verification_model + + +def test_get_email_verification_data_by_id(session): + """Assert that an email verification data can be created and fetched.""" + email_verification = factory_email_verification_model() + assert email_verification.id is not None + retrieved_email_verification = EmailVerificationModel.find_by_id(email_verification.id) + assert email_verification.participant_id == retrieved_email_verification.participant_id + + +def test_get_email_verification_count(session): + """Test get count for email verification.""" + email_verification = factory_email_verification_model() + assert email_verification.id is not None + email_verification_count = EmailVerificationModel.get_email_verification_count(email_verification.engagement_id) + assert email_verification_count == 1 diff --git a/analytics-api/tests/unit/models/test_engagement.py b/analytics-api/tests/unit/models/test_engagement.py index 4b924b463..c2046980b 100644 --- a/analytics-api/tests/unit/models/test_engagement.py +++ b/analytics-api/tests/unit/models/test_engagement.py @@ -20,9 +20,35 @@ from tests.utilities.factory_utils import factory_engagement_model -def test_engagement_map_data(session): +def test_get_engagement_map_data(session): """Assert that an map data related to an engagement can be created and fetched.""" eng = factory_engagement_model() assert eng.id is not None eng_existing = EngagementModel.find_by_id(eng.id) assert eng.latitude == eng_existing.latitude + + +def test_create_engagement_by_source_id(session): + """Test creating an engagement by source identifier.""" + eng = factory_engagement_model() + assert eng.id is not None + + # Check if the engagement can be retrieved by source identifier + eng_by_source_id = EngagementModel.find_by_source_id(eng.source_engagement_id) + assert len(eng_by_source_id) == 1 + assert eng_by_source_id[0].source_engagement_id == eng.source_engagement_id + + +def test_deactivate_engagement_by_source_id(session): + """Test deactivating an engagement by source identifier.""" + eng = factory_engagement_model() + assert eng.id is not None + + # Deactivate the engagement by source identifier + num_deactivated = EngagementModel.deactivate_by_source_id(eng.source_engagement_id) + assert num_deactivated == 1 + + # Check if the deactivated engagement is not active anymore + eng_by_source_id = EngagementModel.find_by_source_id(eng.source_engagement_id) + assert len(eng_by_source_id) == 1 + assert not eng_by_source_id[0].is_active diff --git a/analytics-api/tests/unit/models/test_request_type_option.py b/analytics-api/tests/unit/models/test_request_type_option.py new file mode 100644 index 000000000..99937bc6c --- /dev/null +++ b/analytics-api/tests/unit/models/test_request_type_option.py @@ -0,0 +1,94 @@ +# 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 for the Request Type Option model. + +Test suite to ensure that the Request Type Option model routines are working as expected. +""" + +from analytics_api.models.request_type_option import RequestTypeOption as RequestTypeOptionModel +from faker import Faker +from tests.utilities.factory_scenarios import TestRequestTypeOptionInfo +from tests.utilities.factory_utils import ( + factory_available_response_option_model, factory_engagement_model, factory_request_type_option_model, + factory_response_type_option_model, factory_survey_model) + +fake = Faker() + + +def test_request_type_option_data(session): + """Assert that an request type option data can be created and fetched.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + available_response_option = factory_available_response_option_model(survey) + request_type_option = factory_request_type_option_model(survey, available_response_option.request_key) + assert request_type_option.id is not None + retrieved_options = RequestTypeOptionModel.find_by_id(request_type_option.id) + assert retrieved_options.request_id == request_type_option.request_id + + +def test_request_type_option_data_by_engagement_id_admin(session): + """Assert that an request type option with display true/false data can be fetched by users with access.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + available_response_option = factory_available_response_option_model(survey) + request_type_option = factory_request_type_option_model(survey, available_response_option.request_key) + assert request_type_option.id is not None + + request_type_option = factory_request_type_option_model(survey, available_response_option.request_key, + TestRequestTypeOptionInfo.request_type_option3) + assert request_type_option.id is not None + + response_type_option = factory_response_type_option_model(survey, available_response_option.request_key, + available_response_option.value) + assert response_type_option.id is not None + + retrieved_options = RequestTypeOptionModel.get_survey_result(eng.source_engagement_id, True) + assert len(retrieved_options) == 2 + + +def test_request_type_option_data_by_engagement_id_non_admin(session): + """Assert that an request type option with display true/false data cannot be fetched by users with access.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + available_response_option = factory_available_response_option_model(survey) + request_type_option = factory_request_type_option_model(survey, available_response_option.request_key, + TestRequestTypeOptionInfo.request_type_option2) + assert request_type_option.id is not None + + request_type_option = factory_request_type_option_model(survey, available_response_option.request_key, + TestRequestTypeOptionInfo.request_type_option3) + assert request_type_option.id is not None + + response_type_option = factory_response_type_option_model(survey, available_response_option.request_key, + available_response_option.value) + assert response_type_option.id is not None + + retrieved_options = RequestTypeOptionModel.get_survey_result(eng.source_engagement_id, False) + assert len(retrieved_options) == 1 + + +def test_request_type_option_data_by_engagement_id(session): + """Assert that an request type option data can be fetched by even without having available responses.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + request_key = fake.word() + request_type_option = factory_request_type_option_model(survey, request_key, + TestRequestTypeOptionInfo.request_type_option2) + assert request_type_option.id is not None + + response_type_option = factory_response_type_option_model(survey, request_key, fake.word()) + assert response_type_option.id is not None + + retrieved_options = RequestTypeOptionModel.get_survey_result(eng.source_engagement_id, False) + assert len(retrieved_options) == 1 diff --git a/analytics-api/tests/unit/models/test_response_type_option.py b/analytics-api/tests/unit/models/test_response_type_option.py new file mode 100644 index 000000000..4cff81c47 --- /dev/null +++ b/analytics-api/tests/unit/models/test_response_type_option.py @@ -0,0 +1,38 @@ +# 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 for the Response Type Option model. + +Test suite to ensure that the Response Type Option model routines are working as expected. +""" + +from analytics_api.models.response_type_option import ResponseTypeOption as ResponseTypeOptionModel +from tests.utilities.factory_utils import ( + factory_available_response_option_model, factory_engagement_model, factory_response_type_option_model, + factory_survey_model) + + +def test_response_type_option_data(session): + """Assert that a response type option data can be created and fetched.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + available_response_option = factory_available_response_option_model(survey) + response_type_option = factory_response_type_option_model(survey, available_response_option.request_key, + available_response_option.value) + assert response_type_option.id is not None + + # Assuming that the primary key for ResponseTypeOptionModel is a tuple + # containing the ID and the request key + primary_key = (response_type_option.id, available_response_option.request_key) + retrieved_options = ResponseTypeOptionModel.find_by_id(primary_key) # NOSONAR # for this table the primary key is a tuple + assert available_response_option.request_key == retrieved_options.request_key diff --git a/analytics-api/tests/unit/models/test_survey.py b/analytics-api/tests/unit/models/test_survey.py new file mode 100644 index 000000000..71d8dddf8 --- /dev/null +++ b/analytics-api/tests/unit/models/test_survey.py @@ -0,0 +1,58 @@ +# 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 for the Survey model. + +Test suite to ensure that the Survey model routines are working as expected. +""" + +from analytics_api.models import Survey as SurveyModel +from tests.utilities.factory_utils import ( + factory_engagement_model, factory_survey_model) + + +def test_get_survey_data(session): + """Assert that an survey can be created and fetched.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + assert survey.id is not None + survey_existing = SurveyModel.find_by_id(survey.id) + assert survey.id == survey_existing.id + + +def test_create_engagement_by_source_id(session): + """Test creating an survey by source identifier.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + assert survey.id is not None + + # Check if the survey can be retrieved by source identifier + survey_by_source_id = SurveyModel.find_by_source_id(survey.source_survey_id) + assert len(survey_by_source_id) == 1 + assert survey_by_source_id[0].source_survey_id == survey.source_survey_id + + +def test_deactivate_survey_by_source_id(session): + """Test deactivating an survey by source identifier.""" + eng = factory_engagement_model() + survey = factory_survey_model(eng) + assert survey.id is not None + + # Deactivate the survey by source identifier + num_deactivated = SurveyModel.deactivate_by_source_id(survey.source_survey_id) + assert num_deactivated == 1 + + # Check if the deactivated survey is not active anymore + eng_by_source_id = SurveyModel.find_by_source_id(survey.source_survey_id) + assert len(eng_by_source_id) == 1 + assert not eng_by_source_id[0].is_active diff --git a/analytics-api/tests/unit/models/test_user_response_detail.py b/analytics-api/tests/unit/models/test_user_response_detail.py index c2c012de0..7336c502b 100644 --- a/analytics-api/tests/unit/models/test_user_response_detail.py +++ b/analytics-api/tests/unit/models/test_user_response_detail.py @@ -17,13 +17,15 @@ """ from analytics_api.models.user_response_detail import UserResponseDetail as UserResponseDetailModel -from tests.utilities.factory_utils import factory_user_response_detail_model, factory_survey_model +from tests.utilities.factory_utils import ( + factory_engagement_model, factory_survey_model, factory_user_response_detail_model) def test_user_response_detail_by_month(session): """Assert that an user response detail data related to an engagement can be fetched by month.""" - survey_data = factory_survey_model() - user_response_detail = factory_user_response_detail_model(survey_data.id) + eng = factory_engagement_model() + survey = factory_survey_model(eng) + user_response_detail = factory_user_response_detail_model(survey.id) assert user_response_detail.id is not None user_response_detail_by_month = UserResponseDetailModel.get_response_count_by_created_month( user_response_detail.engagement_id) @@ -32,8 +34,9 @@ def test_user_response_detail_by_month(session): def test_user_response_detail_by_week(session): """Assert that an user response detail data related to an engagement can be fetched by week.""" - survey_data = factory_survey_model() - user_response_detail = factory_user_response_detail_model(survey_data.id) + eng = factory_engagement_model() + survey = factory_survey_model(eng) + user_response_detail = factory_user_response_detail_model(survey.id) assert user_response_detail.id is not None user_response_detail_by_week = UserResponseDetailModel.get_response_count_by_created_week( user_response_detail.engagement_id) diff --git a/analytics-api/tests/unit/utils/test_token_info.py b/analytics-api/tests/unit/utils/test_token_info.py new file mode 100644 index 000000000..0f74e06ac --- /dev/null +++ b/analytics-api/tests/unit/utils/test_token_info.py @@ -0,0 +1,12 @@ +from faker import Faker +from analytics_api.utils.token_info import TokenInfo + +fake = Faker() + + +def test_get_id_without_user_context(): + # Call the get_id method without user context + user_id = TokenInfo.get_id() + + # Assert that the user ID is None + assert user_id is None diff --git a/analytics-api/tests/unit/utils/test_util.py b/analytics-api/tests/unit/utils/test_util.py new file mode 100644 index 000000000..8e9acf8ef --- /dev/null +++ b/analytics-api/tests/unit/utils/test_util.py @@ -0,0 +1,55 @@ +# 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 assure the CORS utilities. + +Test-Suite to ensure that the CORS decorator is working as expected. +""" +import base64 +import pytest +from urllib.parse import unquote + +from analytics_api.utils.util import cors_preflight, escape_wam_friendly_url + + +TEST_CORS_METHODS_DATA = [ + ('GET'), + ('PUT'), + ('POST'), + ('GET,PUT'), + ('GET,POST'), + ('PUT,POST'), + ('GET,PUT,POST'), +] + + +@pytest.mark.parametrize('methods', TEST_CORS_METHODS_DATA) +def test_cors_preflight_post(methods): + """Assert that the options methos is added to the class and that the correct access controls are set.""" + @cors_preflight(methods) # pylint: disable=too-few-public-methods + class TestCors(): + pass + + rv = TestCors().options() # pylint: disable=no-member + assert rv[0]['Access-Control-Allow-Origin'] == '*' + assert rv[0]['Access-Control-Allow-Methods'] == methods + + +def test_escape_wam_friendly_url(): + """Assert conversion back yields same string.""" + org_name = 'foo-bar helo ..' + org_name_encoded = escape_wam_friendly_url(org_name) + param1 = unquote(org_name_encoded) + org_name_actual = base64.b64decode(bytes(param1, encoding='utf-8')).decode('utf-8') + assert org_name_actual == org_name diff --git a/analytics-api/tests/utilities/factory_scenarios.py b/analytics-api/tests/utilities/factory_scenarios.py index 4ad141b40..174ef09ae 100644 --- a/analytics-api/tests/utilities/factory_scenarios.py +++ b/analytics-api/tests/utilities/factory_scenarios.py @@ -28,6 +28,36 @@ CONFIG = get_named_config('testing') +class TestAvailableResponseOptionInfo(dict, Enum): + """Test scenarios of available response option.""" + + available_response_option1 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'participant_id': 1, + 'request_key': fake.word(), + 'value': fake.sentence(), + 'request_id': fake.word() + } + + +class TestEmailVerificationInfo(dict, Enum): + """Test scenarios of email verification.""" + + emailverification1 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'source_email_ver_id': 1, + 'participant_id': 1, + 'engagement_id': 1, + 'survey_id': 1, + } + + class TestEngagementInfo(dict, Enum): """Test scenarios of engagement.""" @@ -45,39 +75,81 @@ class TestEngagementInfo(dict, Enum): 'longitude': -120.5725377787922, 'geojson': '', 'marker_label': 'Project Location', + 'status_name': 'Published', } -class TestEmailVerificationInfo(dict, Enum): - """Test scenarios of email verification.""" +class TestRequestTypeOptionInfo(dict, Enum): + """Test scenarios of request type option.""" - emailverification1 = { + request_type_option1 = { 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), 'is_active': True, 'runcycle_id': 1, - 'source_email_ver_id': 1, - 'participant_id': 1, - 'engagement_id': 1, - 'survey_id': 1, + 'key': fake.word(), + 'type': fake.word(), + 'label': fake.sentence(), + 'request_id': fake.word(), + 'position': 1, + 'display': True + } + + request_type_option2 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'key': fake.word(), + 'type': fake.word(), + 'label': fake.sentence(), + 'request_id': fake.word(), + 'position': 1, + 'display': True + } + + request_type_option3 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'key': fake.word(), + 'type': fake.word(), + 'label': fake.sentence(), + 'request_id': fake.word(), + 'position': 1, + 'display': False } -class TestUserResponseDetailInfo(dict, Enum): - """Test scenarios of user response detail verification.""" +class TestResponseTypeOptionInfo(dict, Enum): + """Test scenarios of response type option.""" - userresponsedetail1 = { + response_type_option1 = { 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), 'is_active': True, 'runcycle_id': 1, 'participant_id': 1, - 'engagement_id': 1, + 'request_key': fake.word(), + 'value': fake.sentence(), + 'request_id': fake.word() + } + + response_type_option2 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'participant_id': 2, + 'request_key': fake.word(), + 'value': fake.sentence(), + 'request_id': fake.word() } class TestSurveyInfo(dict, Enum): - """Test scenarios of survey verification.""" + """Test scenarios of survey.""" survey1 = { 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), @@ -85,5 +157,98 @@ class TestSurveyInfo(dict, Enum): 'is_active': True, 'runcycle_id': 1, 'source_survey_id': 1, + 'name': fake.name(), + 'generate_dashboard': True + } + + +class TestUserDetailsInfo(dict, Enum): + """Test scenarios of user details.""" + + user1 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'name': fake.name() + } + + user2 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': False, + 'runcycle_id': 1, + 'name': fake.name() + } + + +class TestUserFeedbackInfo(dict, Enum): + """Test scenarios of user feedback.""" + + feedback1 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'survey_id': 1, + 'user_id': 1, + 'comment': fake.paragraph(), + 'sentiment_analysis': 'Positive', + 'label': 'Positive Feedback', + 'source_comment_id': 12345 + } + + feedback2 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': False, + 'runcycle_id': 1, + 'survey_id': 2, + 'user_id': 2, + 'comment': fake.paragraph(), + 'sentiment_analysis': 'Negative', + 'label': 'Negative Feedback', + 'source_comment_id': 67890 + } + + +class TestUserResponseDetailInfo(dict, Enum): + """Test scenarios of user response detail.""" + + response1 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': True, + 'runcycle_id': 1, + 'survey_id': 1, 'engagement_id': 1, + 'participant_id': 1 + } + + response2 = { + 'created_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'updated_date': (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d'), + 'is_active': False, + 'runcycle_id': 1, + 'survey_id': 2, + 'engagement_id': 2, + 'participant_id': 2 } + + +class TestJwtClaims(dict, Enum): + """Test scenarios of jwt claims.""" + + staff_admin_role = { + 'sub': 'f7a4a1d3-73a8-4cbc-a40f-bb1145302064', + 'idp_userid': 'f7a4a1d3-73a8-4cbc-a40f-bb1145302064', + 'preferred_username': f'{fake.user_name()}@idir', + 'given_name': fake.first_name(), + 'family_name': fake.last_name(), + 'tenant_id': 1, + 'email': 'staff@gov.bc.ca', + 'identity_provider': 'idir', + 'client_roles': [ + 'view_all_survey_results' + ] +} diff --git a/analytics-api/tests/utilities/factory_utils.py b/analytics-api/tests/utilities/factory_utils.py index 7699dc83d..4b41be227 100644 --- a/analytics-api/tests/utilities/factory_utils.py +++ b/analytics-api/tests/utilities/factory_utils.py @@ -19,17 +19,61 @@ from analytics_api import db from analytics_api.config import get_named_config +from analytics_api.models.available_response_option import AvailableResponseOption as AvailableResponseOptionModel +from analytics_api.models.email_verification import EmailVerification as EmailVerificationModel from analytics_api.models.engagement import Engagement as EngagementModel +from analytics_api.models.request_type_option import RequestTypeOption as RequestTypeOptionModel +from analytics_api.models.response_type_option import ResponseTypeOption as ResponseTypeOptionModel from analytics_api.models.survey import Survey as SurveyModel -from analytics_api.models.email_verification import EmailVerification as EmailVerificationModel +from analytics_api.models.user_details import UserDetails as UserDetailsModel +from analytics_api.models.user_feedback import UserFeedback as UserFeedbackModel from analytics_api.models.user_response_detail import UserResponseDetail as UserResponseDetailModel -from tests.utilities.factory_scenarios import (TestEngagementInfo, TestEmailVerificationInfo, - TestUserResponseDetailInfo, TestSurveyInfo) +from tests.utilities.factory_scenarios import (TestAvailableResponseOptionInfo, TestEmailVerificationInfo, + TestEngagementInfo, TestRequestTypeOptionInfo, + TestResponseTypeOptionInfo, TestSurveyInfo, TestUserDetailsInfo, + TestUserFeedbackInfo, TestUserResponseDetailInfo) + CONFIG = get_named_config('testing') fake = Faker() +def factory_available_response_option_model(survey, + info: dict = TestAvailableResponseOptionInfo.available_response_option1): + """Produce an available response option model.""" + available_response_option = AvailableResponseOptionModel( + created_date=info.get('created_date'), + updated_date=info.get('updated_date'), + is_active=info.get('is_active'), + runcycle_id=info.get('runcycle_id'), + survey_id=survey.id, + participant_id=info.get('participant_id'), + request_key=info.get('request_key'), + value=info.get('value'), + request_id=info.get('request_id'), + ) + db.session.add(available_response_option) + db.session.commit() + return available_response_option + + +def factory_email_verification_model(emailinfo: dict = TestEmailVerificationInfo.emailverification1): + """Produce a email verification model.""" + emailverification = EmailVerificationModel( + created_date=emailinfo.get('created_date'), + updated_date=emailinfo.get('updated_date'), + is_active=emailinfo.get('is_active'), + runcycle_id=emailinfo.get('runcycle_id'), + source_email_ver_id=emailinfo.get('source_email_ver_id'), + participant_id=emailinfo.get('participant_id'), + engagement_id=emailinfo.get('engagement_id'), + survey_id=emailinfo.get('survey_id'), + ) + db.session.add(emailverification) + db.session.commit() + return emailverification + + def factory_engagement_model(eng_info: dict = TestEngagementInfo.engagement1): """Produce a engagement model.""" engagement = EngagementModel( @@ -52,41 +96,47 @@ def factory_engagement_model(eng_info: dict = TestEngagementInfo.engagement1): return engagement -def factory_email_verification_model(emailinfo: dict = TestEmailVerificationInfo.emailverification1): - """Produce a email verification model.""" - emailverification = EmailVerificationModel( - created_date=emailinfo.get('created_date'), - updated_date=emailinfo.get('updated_date'), - is_active=emailinfo.get('is_active'), - runcycle_id=emailinfo.get('runcycle_id'), - source_email_ver_id=emailinfo.get('source_email_ver_id'), - participant_id=emailinfo.get('participant_id'), - engagement_id=emailinfo.get('engagement_id'), - survey_id=emailinfo.get('survey_id'), +def factory_request_type_option_model(survey, request_key, + info: dict = TestRequestTypeOptionInfo.request_type_option1): + """Produce a request type option model.""" + request_type_option = RequestTypeOptionModel( + created_date=info.get('created_date'), + updated_date=info.get('updated_date'), + is_active=info.get('is_active'), + runcycle_id=info.get('runcycle_id'), + survey_id=survey.id, + key=request_key, + type=info.get('type'), + label=info.get('label'), + request_id=info.get('request_id'), + position=info.get('position'), + display=info.get('display'), ) - db.session.add(emailverification) + db.session.add(request_type_option) db.session.commit() - return emailverification + return request_type_option -def factory_user_response_detail_model(survey_id, - userresponseinfo: dict = TestUserResponseDetailInfo.userresponsedetail1): - """Produce a user response detail verification model.""" - user_response_detail = UserResponseDetailModel( - created_date=userresponseinfo.get('created_date'), - updated_date=userresponseinfo.get('updated_date'), - is_active=userresponseinfo.get('is_active'), - runcycle_id=userresponseinfo.get('runcycle_id'), - participant_id=userresponseinfo.get('participant_id'), - engagement_id=userresponseinfo.get('engagement_id'), - survey_id=survey_id, +def factory_response_type_option_model(survey, request_key, value, + info: dict = TestResponseTypeOptionInfo.response_type_option1): + """Produce a response type option model.""" + response_type_option = ResponseTypeOptionModel( + created_date=info.get('created_date'), + updated_date=info.get('updated_date'), + is_active=info.get('is_active'), + runcycle_id=info.get('runcycle_id'), + survey_id=survey.id, + participant_id=info.get('participant_id'), + request_key=request_key, + value=value, + request_id=info.get('request_id'), ) - db.session.add(user_response_detail) + db.session.add(response_type_option) db.session.commit() - return user_response_detail + return response_type_option -def factory_survey_model(surveyinfo: dict = TestSurveyInfo.survey1): +def factory_survey_model(engagement, surveyinfo: dict = TestSurveyInfo.survey1): """Produce a survey verification model.""" survey = SurveyModel( created_date=surveyinfo.get('created_date'), @@ -94,9 +144,58 @@ def factory_survey_model(surveyinfo: dict = TestSurveyInfo.survey1): is_active=surveyinfo.get('is_active'), runcycle_id=surveyinfo.get('runcycle_id'), source_survey_id=surveyinfo.get('source_survey_id'), - engagement_id=surveyinfo.get('engagement_id'), + engagement_id=engagement.source_engagement_id, name=fake.name(), ) db.session.add(survey) db.session.commit() return survey + + +def factory_user_details_model(info: dict = TestUserDetailsInfo.user1): + """Produce a user details model.""" + user_details = UserDetailsModel( + created_date=info.get('created_date'), + updated_date=info.get('updated_date'), + is_active=info.get('is_active'), + runcycle_id=info.get('runcycle_id'), + name=info.get('name'), + ) + db.session.add(user_details) + db.session.commit() + return user_details + +def factory_user_feedback_model(info: dict = TestUserFeedbackInfo.feedback1): + """Produce a user feedback model.""" + user_feedback = UserFeedbackModel( + created_date=info.get('created_date'), + updated_date=info.get('updated_date'), + is_active=info.get('is_active'), + runcycle_id=info.get('runcycle_id'), + survey_id=info.get('survey_id'), + user_id=info.get('user_id'), + comment=info.get('comment'), + sentiment_analysis=info.get('sentiment_analysis'), + label=info.get('label'), + source_comment_id=info.get('source_comment_id'), + ) + db.session.add(user_feedback) + db.session.commit() + return user_feedback + + +def factory_user_response_detail_model(survey_id, + userresponseinfo: dict = TestUserResponseDetailInfo.response1): + """Produce a user response detail verification model.""" + user_response_detail = UserResponseDetailModel( + created_date=userresponseinfo.get('created_date'), + updated_date=userresponseinfo.get('updated_date'), + is_active=userresponseinfo.get('is_active'), + runcycle_id=userresponseinfo.get('runcycle_id'), + participant_id=userresponseinfo.get('participant_id'), + engagement_id=userresponseinfo.get('engagement_id'), + survey_id=survey_id, + ) + db.session.add(user_response_detail) + db.session.commit() + return user_response_detail