From 027366f55e88e5a15b24a9e63d3f9c2aebcb3ad8 Mon Sep 17 00:00:00 2001 From: VineetBala-AOT <90332175+VineetBala-AOT@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:23:20 -0800 Subject: [PATCH] DESENG-436: MET - Rewrite unit tests (#2358) * DESENG-436: MET - Rewrite unit tests (#1) Co-authored-by: Baelx <16845197+Baelx@users.noreply.github.com> * Update met-api/tests/unit/api/test_engagement.py Co-authored-by: Baelx <16845197+Baelx@users.noreply.github.com> --------- Co-authored-by: Baelx <16845197+Baelx@users.noreply.github.com> --- .github/workflows/met-api-ci.yml | 10 +++ CHANGELOG.MD | 6 ++ analytics-api/src/analytics_api/config.py | 2 +- met-api/sample.env | 2 + met-api/setup.cfg | 1 + met-api/src/met_api/config.py | 4 ++ .../constants/timeline_event_status.py | 2 +- met-api/src/met_api/models/widget_timeline.py | 16 +---- .../src/met_api/schemas/widget_timeline.py | 5 +- met-api/src/met_api/services/keycloak.py | 6 +- .../services/widget_timeline_service.py | 60 ++++++++++++---- met-api/tests/conftest.py | 51 ++++++++++++++ met-api/tests/unit/api/test_comment.py | 5 +- met-api/tests/unit/api/test_engagement.py | 68 ++++++++++++------- .../unit/api/test_engagement_membership.py | 49 ++++++++----- .../tests/unit/api/test_engagement_slug.py | 18 +++-- met-api/tests/unit/api/test_submission.py | 5 +- met-api/tests/unit/api/test_survey.py | 48 ++++++++----- met-api/tests/unit/api/test_user.py | 44 +++++++----- met-api/tests/unit/api/test_widget.py | 28 +++++--- met-api/tests/unit/conf/test_configuration.py | 13 +--- .../tests/unit/services/test_engagement.py | 4 +- met-api/tests/unit/services/test_survey.py | 2 +- met-api/tests/unit/services/test_widget.py | 4 +- met-api/tests/utilities/factory_scenarios.py | 20 +++++- met-api/tests/utilities/factory_utils.py | 1 + met-web/src/components/FormCAC/FirstTab.tsx | 4 -- .../engagement/form/ActionContext.tsx | 4 +- .../EngagementTabsContext.tsx | 2 +- .../AdvancedSearch/SearchComponent.tsx | 11 --- .../components/engagement/view/EmailPanel.tsx | 2 +- .../view/widgets/Subscribe/EmailListModal.tsx | 17 ++--- .../components/landing/LandingComponent.tsx | 1 - .../edit/EngagementForm.Edit.One.test.tsx | 15 ++-- .../edit/EngagementForm.Edit.Two.test.tsx | 2 +- 35 files changed, 341 insertions(+), 191 deletions(-) diff --git a/.github/workflows/met-api-ci.yml b/.github/workflows/met-api-ci.yml index 0f90afe90..7532f53be 100644 --- a/.github/workflows/met-api-ci.yml +++ b/.github/workflows/met-api-ci.yml @@ -65,6 +65,15 @@ jobs: JWT_OIDC_TEST_CLIENT_SECRET: "1111111111" JWT_OIDC_TEST_JWKS_CACHE_TIMEOUT: "6000" + + KEYCLOAK_ADMIN_CLIENTID: "met-admin" + KEYCLOAK_ADMIN_SECRET: "2222222222" + KEYCLOAK_AUTH_AUDIENCE: "met-web" + KEYCLOAK_AUTH_CLIENT_SECRET: "1111111111" + KEYCLOAK_BASE_URL: "http://localhost:8081/auth" + KEYCLOAK_REALMNAME: "demo" + USE_KEYCLOAK_DOCKER: "YES" + KEYCLOAK_TEST_ADMIN_CLIENTID: "met-admin" KEYCLOAK_TEST_ADMIN_SECRET: "2222222222" KEYCLOAK_TEST_AUTH_AUDIENCE: "met-web" @@ -72,6 +81,7 @@ jobs: KEYCLOAK_TEST_BASE_URL: "http://localhost:8081" KEYCLOAK_TEST_REALMNAME: "demo" USE_TEST_KEYCLOAK_DOCKER: "YES" + SQLALCHEMY_DATABASE_URI: "postgresql://postgres:postgres@localhost:5432/postgres" runs-on: ubuntu-20.04 diff --git a/CHANGELOG.MD b/CHANGELOG.MD index dd22f9db2..3e4c040a3 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,11 @@ ## January 15, 2024 +- **Task** Audit for missing unit tests [🎟️DESENG-436](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-436) + - Corrected tests to execute smoothly in the local environment using development variables. + - Established continuous integration configuration to automatically run tests upon each commit. + - Ensured that the tests pass successfully in our GitHub test environment. +- **Task** Audit for missing unit tests [🎟️DESENG-449](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-449) + - Identified and corrected failing unit test for api and web - **Bug Fix**: Fixing Form.io error when reordering Radio Button options [🎟️DESENG-446](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-446) ## January 10, 2024 diff --git a/analytics-api/src/analytics_api/config.py b/analytics-api/src/analytics_api/config.py index ba931c0a1..ddebfe23b 100644 --- a/analytics-api/src/analytics_api/config.py +++ b/analytics-api/src/analytics_api/config.py @@ -81,7 +81,7 @@ class _Config(): # pylint: disable=too-few-public-methods JWT_OIDC_JWKS_CACHE_TIMEOUT = 300 # default tenant configs ; Set to EAO for now.Overwrite using openshift variables - DEFAULT_TENANT_SHORT_NAME = os.getenv('DEFAULT_TENANT_SHORT_NAME', 'EAO') + DEFAULT_TENANT_SHORT_NAME = os.getenv('DEFAULT_TENANT_SHORT_NAME', 'GDX') DEFAULT_TENANT_NAME = os.getenv('DEFAULT_TENANT_NAME', 'Environment Assessment Office') DEFAULT_TENANT_DESCRIPTION = os.getenv('DEFAULT_TENANT_DESCRIPTION', 'Environment Assessment Office') diff --git a/met-api/sample.env b/met-api/sample.env index 90a2b1d60..8dc1aa73f 100644 --- a/met-api/sample.env +++ b/met-api/sample.env @@ -124,6 +124,8 @@ DATABASE_TEST_NAME= DATABASE_TEST_HOST= DATABASE_TEST_PORT= +KEYCLOAK_TEST_BASE_URL="http://localhost:8081" + # Docker database settings # If unset, uses the same settings as the main database DATABASE_DOCKER_USERNAME= diff --git a/met-api/setup.cfg b/met-api/setup.cfg index 581e98e82..f26b5b314 100644 --- a/met-api/setup.cfg +++ b/met-api/setup.cfg @@ -40,6 +40,7 @@ max-line-length = 120 docstring-min-length=10 per-file-ignores = */__init__.py:F401 + */config.py:N802 [pycodestyle] max_line_length = 120 diff --git a/met-api/src/met_api/config.py b/met-api/src/met_api/config.py index 48959c202..31e892650 100644 --- a/met-api/src/met_api/config.py +++ b/met-api/src/met_api/config.py @@ -97,6 +97,7 @@ def __init__(self) -> None: os.environ['FLASK_DEBUG'] = str(self.USE_DEBUG) @property + # pylint: disable=invalid-name def SQLALCHEMY_DATABASE_URI(self) -> str: """ Dynamically fetch the SQLAlchemy Database URI based on the DB config. @@ -307,6 +308,8 @@ def SQLALCHEMY_DATABASE_URI(self) -> str: 'TOKEN_URL': os.getenv('CDOGS_TOKEN_URL'), } + PROPAGATE_EXCEPTIONS = True + class DevConfig(Config): # pylint: disable=too-few-public-methods """Dev Config.""" @@ -368,6 +371,7 @@ def __init__(self) -> None: 'HOST': os.getenv('DATABASE_TEST_HOST', Config.DB.get('HOST')), 'PORT': os.getenv('DATABASE_TEST_PORT', Config.DB.get('PORT')), } + IS_SINGLE_TENANT_ENVIRONMENT = False class DockerConfig(Config): # pylint: disable=too-few-public-methods diff --git a/met-api/src/met_api/constants/timeline_event_status.py b/met-api/src/met_api/constants/timeline_event_status.py index 20a52e107..43ecc6dbd 100644 --- a/met-api/src/met_api/constants/timeline_event_status.py +++ b/met-api/src/met_api/constants/timeline_event_status.py @@ -7,4 +7,4 @@ class TimelineEventStatus(IntEnum): Pending = 1 InProgress = 2 - Completed = 3 \ No newline at end of file + Completed = 3 diff --git a/met-api/src/met_api/models/widget_timeline.py b/met-api/src/met_api/models/widget_timeline.py index 68a007793..18cb732be 100755 --- a/met-api/src/met_api/models/widget_timeline.py +++ b/met-api/src/met_api/models/widget_timeline.py @@ -3,13 +3,12 @@ Manages the timeline widget """ from __future__ import annotations -from typing import Optional from sqlalchemy.sql.schema import ForeignKey from met_api.models.timeline_event import TimelineEvent -from met_api.services.timeline_event_service import TimelineEventService from .base_model import BaseModel from .db import db + class WidgetTimeline(BaseModel): # pylint: disable=too-few-public-methods, too-many-instance-attributes """Definition of the Timeline entity.""" @@ -30,16 +29,3 @@ def get_timeline(cls, timeline_id) -> list[WidgetTimeline]: .filter(WidgetTimeline.widget_id == timeline_id) \ .all() return widget_timeline - - @classmethod - def update_timeline(cls, timeline_id, timeline_data: dict) -> Optional[WidgetTimeline or None]: - """Update timeline.""" - TimelineEvent.delete_event(timeline_id) - widget_timeline: WidgetTimeline = WidgetTimeline.query.get(timeline_id) - if widget_timeline: - widget_timeline.title = timeline_data.get('title') - widget_timeline.description = timeline_data.get('description') - for event in timeline_data.get('events', []): - TimelineEventService.create_timeline_event(timeline_id, event) - widget_timeline.save() - return widget_timeline diff --git a/met-api/src/met_api/schemas/widget_timeline.py b/met-api/src/met_api/schemas/widget_timeline.py index 52ae8f133..7eeac83bc 100644 --- a/met-api/src/met_api/schemas/widget_timeline.py +++ b/met-api/src/met_api/schemas/widget_timeline.py @@ -19,7 +19,9 @@ from marshmallow import Schema from marshmallow_sqlalchemy.fields import Nested + class TimelineEventSchema(Schema): # pylint: disable=too-many-ancestors, too-few-public-methods + """This is the schema for the timeline event model.""" class Meta: # pylint: disable=too-few-public-methods """All of the fields in the Timeline Event schema.""" @@ -29,11 +31,12 @@ class Meta: # pylint: disable=too-few-public-methods class WidgetTimelineSchema(Schema): # pylint: disable=too-many-ancestors, too-few-public-methods + """This is the schema for the widget timeline model.""" class Meta: # pylint: disable=too-few-public-methods """All of the fields in the Widget Timeline schema.""" model = WidgetTimelineModel fields = ('id', 'engagement_id', 'widget_id', 'title', 'description', 'events') - + events = Nested(TimelineEventSchema, many=True) diff --git a/met-api/src/met_api/services/keycloak.py b/met-api/src/met_api/services/keycloak.py index 76740b2ed..743267806 100644 --- a/met-api/src/met_api/services/keycloak.py +++ b/met-api/src/met_api/services/keycloak.py @@ -48,7 +48,8 @@ def get_user_groups(user_id): def get_users_groups(user_ids: List): """Get user groups from Keycloak by user ids.For bulk purposes.""" # TODO if List is bigger than a number ; if so reject. - base_url = current_app.config.get('KEYCLOAK_BASE_URL') + keycloak = current_app.config['KEYCLOAK_CONFIG'] + base_url = keycloak['BASE_URL'] # TODO fix this during tests and remove below if not base_url: return {} @@ -106,12 +107,12 @@ def _get_admin_token(): admin_client_id = keycloak['ADMIN_USERNAME'] admin_secret = keycloak['ADMIN_SECRET'] timeout = keycloak['CONNECT_TIMEOUT'] + headers = { 'Content-Type': 'application/x-www-form-urlencoded' } token_issuer = current_app.config['JWT_CONFIG']['ISSUER'] token_url = f'{token_issuer}/protocol/openid-connect/token' - response = requests.post( token_url, headers=headers, @@ -243,7 +244,6 @@ def get_user_by_username(username, admin_token=None): 'Content-Type': ContentType.JSON.value, 'Authorization': f'Bearer {admin_token}' } - # Get the user and return query_user_url = f'{base_url}/auth/admin/realms/{realm}/users?username={username}' response = requests.get(query_user_url, headers=headers, timeout=timeout) diff --git a/met-api/src/met_api/services/widget_timeline_service.py b/met-api/src/met_api/services/widget_timeline_service.py index 5cd436012..73aafbd3b 100644 --- a/met-api/src/met_api/services/widget_timeline_service.py +++ b/met-api/src/met_api/services/widget_timeline_service.py @@ -1,8 +1,13 @@ """Service for Widget Timeline management.""" +from http import HTTPStatus +from typing import Optional + from met_api.constants.membership_type import MembershipType +from met_api.exceptions.business_exception import BusinessException from met_api.models.widget_timeline import WidgetTimeline as WidgetTimelineModel from met_api.models.timeline_event import TimelineEvent as TimelineEventModel from met_api.services import authorization +from met_api.services.timeline_event_service import TimelineEventService from met_api.utils.roles import Role @@ -28,23 +33,48 @@ def create_timeline(widget_id: int, timeline_details: dict): return widget_timeline @staticmethod - def update_timeline(widget_id: int, timeline_id: int, timeline_data: dict): + def update_timeline(widget_id: int, timeline_id: int, timeline_data: dict) -> Optional[WidgetTimelineModel]: """Update timeline widget.""" - events = timeline_data.get("events") + events = timeline_data.get('events') first_event = events[0] + + WidgetTimelineService._check_update_timeline_auth(first_event) + widget_timeline: WidgetTimelineModel = WidgetTimelineModel.find_by_id(timeline_id) - authorization.check_auth(one_of_roles=(MembershipType.TEAM_MEMBER.name, - Role.EDIT_ENGAGEMENT.value), engagement_id=first_event.get('engagement_id')) + if not widget_timeline: - raise KeyError('Timeline widget not found') + raise BusinessException( + error='Timeline widget not found', + status_code=HTTPStatus.BAD_REQUEST) if widget_timeline.widget_id != widget_id: - raise ValueError('Invalid widget ID') + raise BusinessException( + error='Invalid widget ID', + status_code=HTTPStatus.BAD_REQUEST) if widget_timeline.id != timeline_id: - raise ValueError('Invalid timeline ID') + raise BusinessException( + error='Invalid timeline ID', + status_code=HTTPStatus.BAD_REQUEST) + + WidgetTimelineService._update_widget_timeline(widget_timeline, timeline_data) + + return widget_timeline - return WidgetTimelineModel.update_timeline(timeline_id, timeline_data) + @staticmethod + def _check_update_timeline_auth(first_event): + eng_id = first_event.get('engagement_id') + authorization.check_auth(one_of_roles=(MembershipType.TEAM_MEMBER.name, + Role.EDIT_ENGAGEMENT.value), engagement_id=eng_id) + + @staticmethod + def _update_widget_timeline(widget_timeline: WidgetTimelineModel, timeline_data: dict): + widget_timeline.title = timeline_data.get('title') + widget_timeline.description = timeline_data.get('description') + TimelineEventModel.delete_event(widget_timeline.id) + for event in timeline_data.get('events', []): + TimelineEventService.create_timeline_event(widget_timeline.id, event) + widget_timeline.save() @staticmethod def _create_timeline_model(widget_id: int, timeline_data: dict): @@ -56,13 +86,13 @@ def _create_timeline_model(widget_id: int, timeline_data: dict): for event in timeline_data.get('events', []): timeline_model.events.append( TimelineEventModel( - widget_id = widget_id, - engagement_id = event.get('engagement_id'), - timeline_id = event.get('timeline_id'), - description = event.get('description'), - time = event.get('time'), - position = event.get('position'), - status = event.get('status'), + widget_id=widget_id, + engagement_id=event.get('engagement_id'), + timeline_id=event.get('timeline_id'), + description=event.get('description'), + time=event.get('time'), + position=event.get('position'), + status=event.get('status'), ) ) timeline_model.flush() diff --git a/met-api/tests/conftest.py b/met-api/tests/conftest.py index 1415612c7..c6bfbaf00 100644 --- a/met-api/tests/conftest.py +++ b/met-api/tests/conftest.py @@ -15,6 +15,7 @@ import time from random import random +import copy import pytest from flask_migrate import Migrate, upgrade from sqlalchemy import event, text @@ -22,6 +23,8 @@ from met_api import create_app, setup_jwt_manager from met_api.auth import jwt as _jwt from met_api.models import db as _db +from tests.utilities.factory_utils import factory_staff_user_model +from tests.utilities.factory_scenarios import TestJwtClaims, TestUserInfo @pytest.fixture(scope='session') @@ -169,3 +172,51 @@ def docker_compose_files(pytestconfig): def auth_mock(monkeypatch): """Mock check_auth.""" pass + + +# Fixture for setting up user and claims for an admin user +@pytest.fixture +def setup_admin_user_and_claims(jwt): + """Set up a user with the staff admin role.""" + staff_info = dict(TestUserInfo.user_staff_1) + user = factory_staff_user_model(user_info=staff_info) + claims = copy.deepcopy(TestJwtClaims.staff_admin_role.value) + claims['sub'] = str(user.external_id) + + return user, claims + + +# Fixture for setting up user and claims for a reviewer +@pytest.fixture +def setup_reviewer_and_claims(jwt): + """Set up a user with the reviewer role.""" + staff_info = dict(TestUserInfo.user_staff_1) + user = factory_staff_user_model(user_info=staff_info) + claims = copy.deepcopy(TestJwtClaims.reviewer_role.value) + claims['sub'] = str(user.external_id) + + return user, claims + + +# Fixture for setting up user and claims for a team member +@pytest.fixture +def setup_team_member_and_claims(jwt): + """Set up a user with the team member role.""" + staff_info = dict(TestUserInfo.user_staff_1) + user = factory_staff_user_model(user_info=staff_info) + claims = copy.deepcopy(TestJwtClaims.team_member_role.value) + claims['sub'] = str(user.external_id) + + return user, claims + + +# Fixture for setting up user and claims for a user with no role +@pytest.fixture +def setup_unprivileged_user_and_claims(jwt): + """Set up a user with the no role.""" + staff_info = dict(TestUserInfo.user_staff_1) + user = factory_staff_user_model(user_info=staff_info) + claims = copy.deepcopy(TestJwtClaims.no_role.value) + claims['sub'] = str(user.external_id) + + return user, claims diff --git a/met-api/tests/unit/api/test_comment.py b/met-api/tests/unit/api/test_comment.py index 69c5af268..27a64d11f 100644 --- a/met-api/tests/unit/api/test_comment.py +++ b/met-api/tests/unit/api/test_comment.py @@ -175,9 +175,10 @@ 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): # pylint:disable=unused-argument +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.""" - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims mock_post_generate_document_response = MagicMock() mock_post_generate_document_response.content = b'mock data' diff --git a/met-api/tests/unit/api/test_engagement.py b/met-api/tests/unit/api/test_engagement.py index d5a9d0f59..e0effe6fc 100644 --- a/met-api/tests/unit/api/test_engagement.py +++ b/met-api/tests/unit/api/test_engagement.py @@ -39,17 +39,21 @@ @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) -def test_add_engagements(client, jwt, session, engagement_info): # pylint:disable=unused-argument +def test_add_engagements(client, jwt, session, engagement_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be POSTed.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post('/api/engagements/', data=json.dumps(engagement_info), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 -def test_tenant_id_in_create_engagements(client, jwt, session): # pylint:disable=unused-argument +def test_tenant_id_in_create_engagements(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be POSTed with tenant id.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) tenant_short_name = current_app.config.get('DEFAULT_TENANT_SHORT_NAME') tenant = TenantModel.find_by_short_name(tenant_short_name) assert tenant is not None @@ -83,8 +87,10 @@ def test_tenant_id_in_create_engagements(client, jwt, session): # pylint:disabl assert response.status_code == 403 # set users tenant id to be same as engagment tenant id + staff_2 = dict(TestUserInfo.user_staff_2) + user = factory_staff_user_model(user_info=staff_2) claims = copy.deepcopy(TestJwtClaims.staff_admin_role.value) - claims['tenant_id'] = tenant_2.id + claims['sub'] = str(user.external_id) headers = factory_auth_header(jwt=jwt, claims=claims) headers[TENANT_ID_HEADER] = tenant2_short_name response = client.post('/api/engagements/', @@ -95,19 +101,23 @@ def test_tenant_id_in_create_engagements(client, jwt, session): # pylint:disabl assert response.json['tenant_id'] == str(tenant_2.id) -@pytest.mark.parametrize('role', [TestJwtClaims.no_role, TestJwtClaims.public_user_role]) -def test_add_engagements_invalid(client, jwt, session, role): # pylint:disable=unused-argument - """Assert that an engagement can not be POSTed without authorisaiton.""" - headers = factory_auth_header(jwt=jwt, claims=role) - rv = client.post('/api/engagements/', data=json.dumps(TestEngagementInfo.engagement1), +@pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) +def test_add_engagements_invalid(client, jwt, session, engagement_info, + setup_unprivileged_user_and_claims): # pylint:disable=unused-argument + """Assert that an engagement can not be POSTed without authorization.""" + user, claims = setup_unprivileged_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.post('/api/engagements/', data=json.dumps(engagement_info), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 401 @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) -def test_get_engagements(client, jwt, session, engagement_info): # pylint:disable=unused-argument +def test_get_engagements(client, jwt, session, engagement_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be POSTed.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post('/api/engagements/', data=json.dumps(engagement_info), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 @@ -121,16 +131,18 @@ def test_get_engagements(client, jwt, session, engagement_info): # pylint:disab @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement_draft]) -def test_get_engagements_reviewer(client, jwt, session, engagement_info): # pylint:disable=unused-argument +def test_get_engagements_reviewer(client, jwt, session, engagement_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert reviewers access on an engagement.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post('/api/engagements/', data=json.dumps(engagement_info), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == HTTPStatus.OK.value created_eng = rv.json eng_id = created_eng.get('id') - staff_1 = dict(TestUserInfo.user_staff_1) - user = factory_staff_user_model(user_info=staff_1) + staff_2 = dict(TestUserInfo.user_staff_1) + user = factory_staff_user_model(user_info=staff_2) claims = copy.deepcopy(TestJwtClaims.reviewer_role.value) claims['sub'] = str(user.external_id) headers = factory_auth_header(jwt=jwt, claims=claims) @@ -148,9 +160,11 @@ def test_get_engagements_reviewer(client, jwt, session, engagement_info): # pyl @pytest.mark.parametrize('engagement_info', [TestEngagementInfo.engagement1]) def test_search_engagements_by_status(client, jwt, - session, engagement_info): # pylint:disable=unused-argument + session, engagement_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be fetched by filtering using the engagement status.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post('/api/engagements/', data=json.dumps(engagement_info), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 @@ -278,9 +292,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): # pylint:disable=unused-argument +def test_patch_engagement(client, jwt, session, engagement_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be updated.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) engagement = factory_engagement_model() engagement_id = str(engagement.id) @@ -349,9 +365,11 @@ def test_patch_engagement_by_member(client, jwt, session): # pylint:disable=unu assert rv.json.get('name') == engagement_edits.get('name') -def test_patch_new_survey_block_engagement(client, jwt, session): # pylint:disable=unused-argument +def test_patch_new_survey_block_engagement(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement's survey status blocks can be updated.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) engagement = factory_engagement_model() engagement_id = str(engagement.id) @@ -375,9 +393,11 @@ def test_patch_new_survey_block_engagement(client, jwt, session): # pylint:disa assert actual_status_blocks[0].get('survey_status') == engagement_edits.get('status_block')[0].get('survey_status') -def test_update_survey_block_engagement(client, jwt, session): # pylint:disable=unused-argument +def test_update_survey_block_engagement(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement's survey status blocks can be updated.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) engagement = factory_engagement_model(TestEngagementInfo.engagement2) engagement_id = str(engagement.id) diff --git a/met-api/tests/unit/api/test_engagement_membership.py b/met-api/tests/unit/api/test_engagement_membership.py index 2c12b33d8..6b5805485 100644 --- a/met-api/tests/unit/api/test_engagement_membership.py +++ b/met-api/tests/unit/api/test_engagement_membership.py @@ -9,7 +9,6 @@ from met_api.constants.membership_type import MembershipType from met_api.utils.enums import ContentType, KeycloakGroupName, MembershipStatus -from tests.utilities.factory_scenarios import TestJwtClaims from tests.utilities.factory_utils import ( factory_auth_header, factory_engagement_model, factory_membership_model, factory_staff_user_model) @@ -17,11 +16,13 @@ memberships_url = '/api/engagements/{}/members' -def test_create_engagement_membership_team_member(mocker, client, jwt, session): +def test_create_engagement_membership_team_member(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that a team member engagement membership can be created.""" + user, claims = setup_admin_user_and_claims engagement = factory_engagement_model() staff_user = factory_staff_user_model() - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + 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 @@ -51,11 +52,13 @@ def test_create_engagement_membership_team_member(mocker, client, jwt, session): mock_get_users_groups_keycloak.assert_called() -def test_create_engagement_membership_reviewer(mocker, client, jwt, session): +def test_create_engagement_membership_reviewer(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that a reviewer engagement membership can be created.""" + user, claims = setup_admin_user_and_claims engagement = factory_engagement_model() staff_user = factory_staff_user_model() - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + 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 @@ -85,11 +88,13 @@ def test_create_engagement_membership_reviewer(mocker, client, jwt, session): mock_get_users_groups_keycloak.assert_called() -def test_create_engagement_membership_unauthorized(client, jwt, session): +def test_create_engagement_membership_unauthorized(client, jwt, session, + setup_unprivileged_user_and_claims): """Assert that creating an engagement membership without proper authorization fails.""" + user, claims = setup_unprivileged_user_and_claims engagement = factory_engagement_model() staff_user = factory_staff_user_model() - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.no_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = {'user_id': staff_user.external_id} rv = client.post( @@ -101,12 +106,14 @@ def test_create_engagement_membership_unauthorized(client, jwt, session): assert rv.status_code == HTTPStatus.FORBIDDEN -def test_revoke_membership(client, jwt, session): +def test_revoke_membership(client, jwt, session, + setup_admin_user_and_claims): """Test that a membership can be revoked.""" + 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=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'action': 'revoke' } @@ -121,8 +128,10 @@ def test_revoke_membership(client, jwt, session): assert rv.status_code == HTTPStatus.OK -def test_reinstate_membership(client, jwt, session): +def test_reinstate_membership(client, jwt, session, + setup_admin_user_and_claims): """Test that a membership can be reinstated.""" + user, claims = setup_admin_user_and_claims engagement = factory_engagement_model() staff_user = factory_staff_user_model() membership = factory_membership_model( @@ -130,7 +139,7 @@ def test_reinstate_membership(client, jwt, session): engagement_id=engagement.id, status=MembershipStatus.REVOKED.value ) - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'action': 'reinstate' } @@ -145,12 +154,14 @@ def test_reinstate_membership(client, jwt, session): assert rv.status_code == HTTPStatus.OK -def test_update_membership_status_invalid_action(client, jwt, session): +def test_update_membership_status_invalid_action(client, jwt, session, + setup_admin_user_and_claims): """Test that an invalid action cannot be performed on a membership.""" + 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=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'action': 'invalid' } @@ -165,8 +176,10 @@ def test_update_membership_status_invalid_action(client, jwt, session): assert rv.status_code == HTTPStatus.BAD_REQUEST -def test_revoke_already_revoked_membership(client, jwt, session): +def test_revoke_already_revoked_membership(client, jwt, session, + setup_admin_user_and_claims): """Test that an already revoked membership cannot be revoked again.""" + user, claims = setup_admin_user_and_claims engagement = factory_engagement_model() staff_user = factory_staff_user_model() membership = factory_membership_model( @@ -174,7 +187,7 @@ def test_revoke_already_revoked_membership(client, jwt, session): engagement_id=engagement.id, status=MembershipStatus.REVOKED.value ) - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'action': 'revoke' } @@ -189,12 +202,14 @@ def test_revoke_already_revoked_membership(client, jwt, session): assert rv.status_code == HTTPStatus.BAD_REQUEST -def reinstate_already_active_membership(client, jwt, session): +def reinstate_already_active_membership(client, jwt, session, + setup_admin_user_and_claims): """Test that an already active membership cannot be activated again.""" + 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=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'action': 'reinstate' } diff --git a/met-api/tests/unit/api/test_engagement_slug.py b/met-api/tests/unit/api/test_engagement_slug.py index 711e652ac..88f6009ca 100644 --- a/met-api/tests/unit/api/test_engagement_slug.py +++ b/met-api/tests/unit/api/test_engagement_slug.py @@ -71,8 +71,10 @@ 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): +def test_patch_engagement_slug(client, jwt, session, engagement_slug_info, + setup_admin_user_and_claims): """Test patch request for engagement_slug endpoint.""" + user, claims = setup_admin_user_and_claims eng = factory_engagement_model(status=Status.Draft) engagement_slug_info = { **engagement_slug_info, @@ -84,29 +86,33 @@ def test_patch_engagement_slug(client, jwt, session, engagement_slug_info): 'slug': updated_slug, 'engagement_id': eng.id } - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch(f'/api/slugs/{updated_slug}', data=json.dumps(patch_data), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == HTTPStatus.OK -def test_patch_create_nonexistent_engagement_slug(client, jwt, session): +def test_patch_create_nonexistent_engagement_slug(client, jwt, session, + setup_admin_user_and_claims): """Test patch request for non-existent engagement_slug endpoint.""" + user, claims = setup_admin_user_and_claims eng = factory_engagement_model(status=Status.Draft) updated_slug = fake.text(max_nb_chars=20) patch_data = { 'slug': updated_slug, 'engagement_id': eng.id } - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch(f'/api/slugs/{updated_slug}', data=json.dumps(patch_data), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == HTTPStatus.OK @pytest.mark.parametrize('engagement_slug_info', [TestEngagementSlugInfo.slug1]) -def test_patch_unauthorized_engagement_slug(client, jwt, session, engagement_slug_info): +def test_patch_unauthorized_engagement_slug(client, jwt, session, engagement_slug_info, + setup_unprivileged_user_and_claims): """Test unauthorized patch request for engagement_slug endpoint.""" + user, claims = setup_unprivileged_user_and_claims eng = factory_engagement_model(status=Status.Draft) engagement_slug_info = { **engagement_slug_info, @@ -118,7 +124,7 @@ def test_patch_unauthorized_engagement_slug(client, jwt, session, engagement_slu 'slug': updated_slug, 'engagement_id': eng.id } - headers = factory_auth_header(jwt=jwt, claims=TestUserInfo.user_staff_1) + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch(f'/api/slugs/{updated_slug}', data=json.dumps(patch_data), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == HTTPStatus.UNAUTHORIZED diff --git a/met-api/tests/unit/api/test_submission.py b/met-api/tests/unit/api/test_submission.py index e73d3adfb..b70f69e9d 100644 --- a/met-api/tests/unit/api/test_submission.py +++ b/met-api/tests/unit/api/test_submission.py @@ -52,9 +52,10 @@ def test_valid_submission(client, jwt, session): # pylint:disable=unused-argume @pytest.mark.parametrize('submission_info', [TestSubmissionInfo.submission1]) -def test_get_submission_by_id(client, jwt, session, submission_info): # pylint:disable=unused-argument +def test_get_submission_by_id(client, jwt, session, submission_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an engagement can be fetched.""" - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims participant = factory_participant_model() survey, eng = factory_survey_and_eng_model() diff --git a/met-api/tests/unit/api/test_survey.py b/met-api/tests/unit/api/test_survey.py index 2e7a85480..29eebeea0 100644 --- a/met-api/tests/unit/api/test_survey.py +++ b/met-api/tests/unit/api/test_survey.py @@ -39,9 +39,11 @@ @pytest.mark.parametrize('survey_info', [TestSurveyInfo.survey1]) -def test_create_survey(client, jwt, session, survey_info): # pylint:disable=unused-argument +def test_create_survey(client, jwt, session, survey_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an survey can be POSTed.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'name': survey_info.get('name'), 'display': survey_info.get('form_json').get('display'), @@ -52,9 +54,11 @@ def test_create_survey(client, jwt, session, survey_info): # pylint:disable=unu assert rv.json.get('form_json') == survey_info.get('form_json') -def test_create_survey_with_tenant(client, jwt, session): # pylint:disable=unused-argument +def test_create_survey_with_tenant(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an survey can be POSTed.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) tenant_short_name = current_app.config.get('DEFAULT_TENANT_SHORT_NAME') tenant = TenantModel.find_by_short_name(tenant_short_name) assert tenant is not None @@ -70,7 +74,7 @@ def test_create_survey_with_tenant(client, jwt, session): # pylint:disable=unus # Create a tenant tenant_data = TestTenantInfo.tenant2 - tenant_model = factory_tenant_model(tenant_data) + factory_tenant_model(tenant_data) tenant2_short_name = tenant_data['short_name'] tenant_2 = TenantModel.find_by_short_name(tenant2_short_name) # Verify that the tenant was created successfully @@ -87,8 +91,10 @@ def test_create_survey_with_tenant(client, jwt, session): # pylint:disable=unus assert rv.status_code == 403 # emulate Tenant 2 staff admin by setting tenant id + staff_info = dict(TestUserInfo.user_staff_3) + user = factory_staff_user_model(user_info=staff_info) claims = copy.deepcopy(TestJwtClaims.staff_admin_role.value) - claims['tenant_id'] = str(tenant_model.id) + claims['sub'] = str(user.external_id) headers = factory_auth_header(jwt=jwt, claims=claims) headers[TENANT_ID_HEADER] = tenant2_short_name @@ -105,9 +111,11 @@ def test_create_survey_with_tenant(client, jwt, session): # pylint:disable=unus @pytest.mark.parametrize('survey_info', [TestSurveyInfo.survey2]) -def test_put_survey(client, jwt, session, survey_info): # pylint:disable=unused-argument +def test_put_survey(client, jwt, session, survey_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that an survey can be POSTed.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) survey = factory_survey_model() survey_id = str(survey.id) new_survey_name = 'new_survey_name' @@ -123,11 +131,13 @@ def test_put_survey(client, jwt, session, survey_info): # pylint:disable=unused assert rv.json.get('name') == new_survey_name -def test_survey_link(client, jwt, session): # pylint:disable=unused-argument +def test_survey_link(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that a survey can be POSTed.""" + user, claims = setup_admin_user_and_claims survey = factory_survey_model() survey_id = survey.id - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) eng = factory_engagement_model() eng_id = eng.id @@ -262,9 +272,11 @@ def test_get_template_survey(client, jwt, session): # pylint:disable=unused-arg assert rv.json.get('total') == 1 -def test_edit_template_survey_for_admins(client, jwt, session): # pylint:disable=unused-argument +def test_edit_template_survey_for_admins(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that a hidden survey cannot be fetched by team members.""" - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) survey = factory_survey_model(TestSurveyInfo.survey_template) survey_id = str(survey.id) new_survey_name = 'new_survey_name' @@ -287,10 +299,12 @@ def test_edit_template_survey_for_team_member(client, jwt, session): # pylint:d @pytest.mark.parametrize('survey_info', [TestSurveyInfo.survey2]) -def test_surveys_clone_admin(mocker, client, jwt, session, survey_info): +def test_surveys_clone_admin(mocker, client, jwt, session, survey_info, + setup_admin_user_and_claims): """Assert that a survey can be cloned.""" + user, claims = setup_admin_user_and_claims survey = factory_survey_model() - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) # Prepare test data request_data = {'name': 'New Survey'} @@ -315,10 +329,12 @@ def test_surveys_clone_admin(mocker, client, jwt, session, survey_info): @pytest.mark.parametrize('survey_info', [TestSurveyInfo.survey2]) -def test_surveys_clone_team_member(mocker, client, jwt, session, survey_info): +def test_surveys_clone_team_member(mocker, client, jwt, session, survey_info, + setup_team_member_and_claims): """Assert that a survey can be cloned.""" + user, claims = setup_team_member_and_claims survey = factory_survey_model() - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.team_member_role) + headers = factory_auth_header(jwt=jwt, claims=claims) # Prepare test data request_data = {'name': 'New Survey'} diff --git a/met-api/tests/unit/api/test_user.py b/met-api/tests/unit/api/test_user.py index c194660fb..f7cffe9a3 100644 --- a/met-api/tests/unit/api/test_user.py +++ b/met-api/tests/unit/api/test_user.py @@ -68,7 +68,8 @@ def test_create_staff_user(client, jwt, session): assert rv.json.get('tenant_id') == str(tenant.id) -def test_get_staff_users(client, jwt, session): +def test_get_staff_users(client, jwt, session, + setup_admin_user_and_claims): """Assert that a user can be POSTed.""" set_global_tenant() staff_1 = dict(TestUserInfo.user_staff_1) @@ -76,15 +77,16 @@ def test_get_staff_users(client, jwt, session): factory_staff_user_model(user_info=staff_1) factory_staff_user_model(user_info=staff_2) - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.get('/api/user/', headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == HTTPStatus.OK - assert rv.json.get('total') == 3 - assert len(rv.json.get('items')) == 3 + assert rv.json.get('total') == 4 + assert len(rv.json.get('items')) == 4 -def test_add_user_to_admin_group(mocker, client, jwt, session): +def test_add_user_to_admin_group(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that a user can be added to the admin group.""" user = factory_staff_user_model() @@ -93,7 +95,7 @@ def test_add_user_to_admin_group(mocker, client, jwt, session): [KeycloakGroupName.EAO_IT_VIEWER.value] ) - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post( f'/api/user/{user.external_id}/groups?group=Administrator', @@ -106,7 +108,8 @@ def test_add_user_to_admin_group(mocker, client, jwt, session): mock_add_attribute_to_user.assert_called() -def test_add_user_to_reviewer_group(mocker, client, jwt, session): +def test_add_user_to_reviewer_group(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that a user can be added to the reviewer group.""" user = factory_staff_user_model() @@ -115,7 +118,7 @@ def test_add_user_to_reviewer_group(mocker, client, jwt, session): [KeycloakGroupName.EAO_IT_VIEWER.value] ) - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post( f'/api/user/{user.external_id}/groups?group=Reviewer', @@ -127,7 +130,8 @@ def test_add_user_to_reviewer_group(mocker, client, jwt, session): mock_get_user_groups_keycloak.assert_called() -def test_add_user_to_team_member_group(mocker, client, jwt, session): +def test_add_user_to_team_member_group(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that a user can be added to the team member group.""" user = factory_staff_user_model() @@ -136,7 +140,7 @@ def test_add_user_to_team_member_group(mocker, client, jwt, session): [KeycloakGroupName.EAO_IT_VIEWER.value] ) - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post( f'/api/user/{user.external_id}/groups?group=TeamMember', @@ -199,13 +203,14 @@ def mock_toggle_user_status(mocker): return mock_toggle_user_status -def test_toggle_user_active_status(mocker, client, jwt, session): +def test_toggle_user_active_status(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that a user can be toggled.""" user = factory_staff_user_model() mocked_toggle_user_status = mock_toggle_user_status(mocker) assert user.status_id == UserStatus.ACTIVE.value - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch( f'/api/user/{user.external_id}/status', @@ -218,13 +223,14 @@ def test_toggle_user_active_status(mocker, client, jwt, session): mocked_toggle_user_status.assert_called() -def test_team_member_cannot_toggle_user_active_status(mocker, client, jwt, session): +def test_team_member_cannot_toggle_user_active_status(mocker, client, jwt, session, + setup_team_member_and_claims): """Assert that a team member cannot toggle user status.""" user = factory_staff_user_model() mocked_toggle_user_status = mock_toggle_user_status(mocker) assert user.status_id == UserStatus.ACTIVE.value - claims = TestJwtClaims.team_member_role + user, claims = setup_team_member_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch( f'/api/user/{user.external_id}/status', @@ -236,13 +242,14 @@ def test_team_member_cannot_toggle_user_active_status(mocker, client, jwt, sessi mocked_toggle_user_status.assert_not_called() -def test_reviewer_cannot_toggle_user_active_status(mocker, client, jwt, session): +def test_reviewer_cannot_toggle_user_active_status(mocker, client, jwt, session, + setup_reviewer_and_claims): """Assert that a reviewer cannot toggle user status.""" user = factory_staff_user_model() mocked_toggle_user_status = mock_toggle_user_status(mocker) assert user.status_id == UserStatus.ACTIVE.value - claims = TestJwtClaims.reviewer_role + user, claims = setup_reviewer_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch( f'/api/user/{user.external_id}/status', @@ -254,13 +261,14 @@ def test_reviewer_cannot_toggle_user_active_status(mocker, client, jwt, session) mocked_toggle_user_status.assert_not_called() -def test_toggle_user_active_status_empty_body(mocker, client, jwt, session): +def test_toggle_user_active_status_empty_body(mocker, client, jwt, session, + setup_admin_user_and_claims): """Assert that returns bad request if bad request body.""" user = factory_staff_user_model() mocked_toggle_user_status = mock_toggle_user_status(mocker) assert user.status_id == UserStatus.ACTIVE.value - claims = TestJwtClaims.staff_admin_role + user, claims = setup_admin_user_and_claims headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.patch( f'/api/user/{user.external_id}/status', diff --git a/met-api/tests/unit/api/test_widget.py b/met-api/tests/unit/api/test_widget.py index b3a239b6d..c4098e60f 100644 --- a/met-api/tests/unit/api/test_widget.py +++ b/met-api/tests/unit/api/test_widget.py @@ -23,16 +23,18 @@ from met_api.constants.widget import WidgetType from met_api.utils.enums import ContentType -from tests.utilities.factory_scenarios import TestJwtClaims, TestWidgetInfo, TestWidgetItemInfo +from tests.utilities.factory_scenarios import TestWidgetInfo, TestWidgetItemInfo from tests.utilities.factory_utils import factory_auth_header, factory_engagement_model, factory_widget_model @pytest.mark.parametrize('widget_info', [TestWidgetInfo.widget1]) -def test_create_widget(client, jwt, session, widget_info): # pylint:disable=unused-argument +def test_create_widget(client, jwt, session, widget_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that a widget can be POSTed.""" engagement = factory_engagement_model() widget_info['engagement_id'] = engagement.id - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + 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 @@ -43,19 +45,21 @@ def test_create_widget(client, jwt, session, widget_info): # pylint:disable=unu assert rv.json[0].get('sort_index') == 1 -def test_create_widget_sort(client, jwt, session): # pylint:disable=unused-argument +def test_create_widget_sort(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that a widget can be POSTed.""" engagement = factory_engagement_model() widget_info_1 = TestWidgetInfo.widget1 widget_info_1['engagement_id'] = engagement.id - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post(f'/api/widgets/engagement/{engagement.id}', data=json.dumps(widget_info_1), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 widget_info_2 = TestWidgetInfo.widget2 widget_info_2['engagement_id'] = engagement.id - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post(f'/api/widgets/engagement/{engagement.id}', data=json.dumps(widget_info_2), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 @@ -96,12 +100,14 @@ def test_create_widget_sort(client, jwt, session): # pylint:disable=unused-argu assert doc_widget.get('sort_index') == 1 -def test_create_widget_sort_invalid(client, jwt, session): # pylint:disable=unused-argument +def test_create_widget_sort_invalid(client, jwt, session, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that a widget can be POSTed.""" engagement = factory_engagement_model() widget_info_1 = TestWidgetInfo.widget1 widget_info_1['engagement_id'] = engagement.id - headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.staff_admin_role) + user, claims = setup_admin_user_and_claims + headers = factory_auth_header(jwt=jwt, claims=claims) rv = client.post(f'/api/widgets/engagement/{engagement.id}', data=json.dumps(widget_info_1), headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 @@ -126,13 +132,15 @@ def _find_widget(widgets, widget_type): @pytest.mark.parametrize('widget_item_info', [TestWidgetItemInfo.widget_item1]) -def test_create_widget_items(client, jwt, session, widget_item_info): # pylint:disable=unused-argument +def test_create_widget_items(client, jwt, session, widget_item_info, + setup_admin_user_and_claims): # pylint:disable=unused-argument """Assert that widget items can be POSTed.""" 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=TestJwtClaims.staff_admin_role) + headers = factory_auth_header(jwt=jwt, claims=claims) data = { 'widget_data_id': widget_item_info.get('widget_data_id'), diff --git a/met-api/tests/unit/conf/test_configuration.py b/met-api/tests/unit/conf/test_configuration.py index cb97edbd9..1ec31f86d 100644 --- a/met-api/tests/unit/conf/test_configuration.py +++ b/met-api/tests/unit/conf/test_configuration.py @@ -29,21 +29,14 @@ ('valid', 'default', config.ProdConfig), ('valid', 'staging', config.ProdConfig), ('valid', 'production', config.ProdConfig), - ('error', None, KeyError) + ('error', None, config.ProdConfig) ] @pytest.mark.parametrize('test_type,environment,expected', TEST_ENVIRONMENT_DATA) def test_get_named_config(test_type, environment, expected): - """Assert that the named configurations can be loaded. - - Or that a KeyError is returned for missing config types. - """ - if test_type == 'valid': - assert isinstance(config.get_named_config(environment), expected) - else: - with pytest.raises(KeyError): - config.get_named_config(environment) + """Assert that the named configurations can be loaded.""" + assert isinstance(config.get_named_config(environment), expected) def test_prod_config_secret_key(monkeypatch): # pylint: disable=missing-docstring diff --git a/met-api/tests/unit/services/test_engagement.py b/met-api/tests/unit/services/test_engagement.py index efc931973..e5e97c37e 100644 --- a/met-api/tests/unit/services/test_engagement.py +++ b/met-api/tests/unit/services/test_engagement.py @@ -33,8 +33,8 @@ def test_create_engagement(session, monkeypatch): # pylint:disable=unused-argum engagement_data = TestEngagementInfo.engagement1 saved_engagament = EngagementService().create_engagement(engagement_data) # fetch the engagement with id and assert - factory_staff_user_model() patch_token_info(TestJwtClaims.staff_admin_role, monkeypatch) + factory_staff_user_model(external_id=TestJwtClaims.staff_admin_role['sub']) fetched_engagement = EngagementService().get_engagement(saved_engagament.id) assert fetched_engagement.get('id') == saved_engagament.id assert fetched_engagement.get('name') == engagement_data.get('name') @@ -47,8 +47,8 @@ def test_create_engagement_with_survey_block(session, monkeypatch): # pylint:di """Assert that an Org can be created.""" engagement_data = TestEngagementInfo.engagement2 saved_engagament = EngagementService().create_engagement(engagement_data) - factory_staff_user_model() patch_token_info(TestJwtClaims.staff_admin_role, monkeypatch) + factory_staff_user_model(external_id=TestJwtClaims.staff_admin_role['sub']) # fetch the engagement with id and assert fetched_engagement = EngagementService().get_engagement(saved_engagament.id) assert fetched_engagement.get('id') == saved_engagament.id diff --git a/met-api/tests/unit/services/test_survey.py b/met-api/tests/unit/services/test_survey.py index 455572c55..60f490a9d 100644 --- a/met-api/tests/unit/services/test_survey.py +++ b/met-api/tests/unit/services/test_survey.py @@ -27,8 +27,8 @@ def test_create_survey(session, monkeypatch,): # pylint:disable=unused-argument 'name': TestSurveyInfo.survey1.get('name'), 'display': TestSurveyInfo.survey1.get('form_json').get('display'), } - factory_staff_user_model() patch_token_info(TestJwtClaims.staff_admin_role, monkeypatch) + factory_staff_user_model(external_id=TestJwtClaims.staff_admin_role['sub']) saved_survey = SurveyService().create(survey_data) # fetch the survey with id and assert fetched_survey = SurveyService().get(saved_survey.id) diff --git a/met-api/tests/unit/services/test_widget.py b/met-api/tests/unit/services/test_widget.py index f3fb34e59..286f41b29 100644 --- a/met-api/tests/unit/services/test_widget.py +++ b/met-api/tests/unit/services/test_widget.py @@ -23,7 +23,8 @@ from met_api.services.widget_service import WidgetService from tests.utilities.factory_scenarios import TestJwtClaims, TestUserInfo, TestWidgetInfo, TestWidgetItemInfo from tests.utilities.factory_utils import ( - factory_engagement_model, factory_widget_item_model, factory_widget_model, patch_token_info) + factory_engagement_model, factory_staff_user_model, factory_widget_item_model, factory_widget_model, + patch_token_info) fake = Faker() @@ -39,6 +40,7 @@ def test_create_widget(session, monkeypatch): # pylint:disable=unused-argument 'widget_type_id': WidgetType.WHO_IS_LISTENING.value } patch_token_info(TestJwtClaims.staff_admin_role, monkeypatch) + factory_staff_user_model(external_id=TestJwtClaims.staff_admin_role['sub']) widget_record = WidgetService().create_widget(widget_to_create, engagement.id) # Assert that was created diff --git a/met-api/tests/utilities/factory_scenarios.py b/met-api/tests/utilities/factory_scenarios.py index fd78141f9..514f817ce 100644 --- a/met-api/tests/utilities/factory_scenarios.py +++ b/met-api/tests/utilities/factory_scenarios.py @@ -53,6 +53,24 @@ class TestUserInfo(dict, Enum): 'tenant_id': '1' } + user_staff_2 = { + 'first_name': fake.name(), + 'middle_name': fake.name(), + 'last_name': fake.name(), + 'email_address': fake.email(), + 'status_id': UserStatus.ACTIVE.value, + 'tenant_id': '2' + } + + user_staff_3 = { + 'first_name': fake.name(), + 'middle_name': fake.name(), + 'last_name': fake.name(), + 'email_address': fake.email(), + 'status_id': UserStatus.ACTIVE.value, + 'tenant_id': '3' + } + class TestParticipantInfo(dict, Enum): """Test scenarios of participant.""" @@ -137,7 +155,7 @@ class TestTenantInfo(dict, Enum): """Test scenarios of tenants.""" tenant1 = { - 'short_name': 'GDX', + 'short_name': 'EAO', 'name': fake.name(), 'description': fake.text(max_nb_chars=300), 'title': fake.text(max_nb_chars=20), diff --git a/met-api/tests/utilities/factory_utils.py b/met-api/tests/utilities/factory_utils.py index 86df907f9..8d009ad1c 100644 --- a/met-api/tests/utilities/factory_utils.py +++ b/met-api/tests/utilities/factory_utils.py @@ -164,6 +164,7 @@ def factory_staff_user_model(external_id=None, user_info: dict = TestUserInfo.us email_address=user_info['email_address'], external_id=str(external_id), status_id=user_info['status_id'], + tenant_id=user_info['tenant_id'], ) user.save() return user diff --git a/met-web/src/components/FormCAC/FirstTab.tsx b/met-web/src/components/FormCAC/FirstTab.tsx index f62c90f87..1583c3b9b 100644 --- a/met-web/src/components/FormCAC/FirstTab.tsx +++ b/met-web/src/components/FormCAC/FirstTab.tsx @@ -4,7 +4,6 @@ import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; import { Checkbox, FormControlLabel, FormGroup, FormHelperText, Grid, Link } from '@mui/material'; import { MetLabel, MetParagraph, PrimaryButton } from 'components/common'; -import { useAppTranslation } from 'hooks'; import { FormContext } from './FormContext'; import { TAB_TWO } from './constants'; import { When } from 'react-if'; @@ -23,7 +22,6 @@ interface FormData { } export const FirstTab: React.FC = () => { - const { t: translate } = useAppTranslation(); const { consentMessage, setTabValue, setFormSubmission } = useContext(FormContext); // Initialize form state and validation using react-hook-form @@ -48,8 +46,6 @@ export const FirstTab: React.FC = () => { setTabValue(TAB_TWO); }; - const contactEmail = translate('cacForm.contactEmail'); - return ( diff --git a/met-web/src/components/engagement/form/ActionContext.tsx b/met-web/src/components/engagement/form/ActionContext.tsx index ada581b30..b5e5c1c5e 100644 --- a/met-web/src/components/engagement/form/ActionContext.tsx +++ b/met-web/src/components/engagement/form/ActionContext.tsx @@ -5,7 +5,7 @@ import { getEngagementMetadata, patchEngagementMetadata, } from '../../../services/engagementMetadataService'; -import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { EngagementContext, EngagementForm, EngagementFormUpdate, EngagementParams } from './types'; import { createDefaultEngagement, @@ -55,8 +55,6 @@ export const ActionContext = createContext({ export const ActionProvider = ({ children }: { children: JSX.Element }) => { const { engagementId } = useParams(); - const { search } = useLocation(); - const searchParams = new URLSearchParams(search); const navigate = useNavigate(); const dispatch = useAppDispatch(); diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx index dfe748780..6b77d9c68 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx @@ -132,7 +132,7 @@ export const EngagementTabsContext = createContext({ }); export const EngagementTabsContextProvider = ({ children }: { children: React.ReactNode }) => { - const { savedEngagement, engagementMetadata } = useContext(ActionContext); + const { savedEngagement } = useContext(ActionContext); const dispatch = useAppDispatch(); const [engagementFormData, setEngagementFormData] = useState({ name: savedEngagement.name || '', diff --git a/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx b/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx index 79ff759da..c0b94a9bd 100644 --- a/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx +++ b/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx @@ -6,9 +6,6 @@ import { FormGroup, FormControlLabel, Checkbox, - Select, - SelectChangeEvent, - MenuItem, FormControl, useMediaQuery, Theme, @@ -19,7 +16,6 @@ import { PrimaryButton, SecondaryButton } from '../../../common'; import dayjs from 'dayjs'; import { formatToUTC } from 'components/common/dateHelper'; import { SearchOptions } from './SearchTypes'; -import { AppConfig } from 'config'; interface filterParams { setFilterParams: (newsearchOptions: SearchOptions) => void; @@ -38,13 +34,6 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { }; const [statusFilters, setStatusFilters] = useState(initialStatusFilters); - const initialFilterParams = { - status_list: [], - created_from_date: '', - created_to_date: '', - published_from_date: '', - published_to_date: '', - }; const handleStatusFilterChange = (event: React.SyntheticEvent) => { setStatusFilters({ ...statusFilters, diff --git a/met-web/src/components/engagement/view/EmailPanel.tsx b/met-web/src/components/engagement/view/EmailPanel.tsx index ef9a3f590..54f62a936 100644 --- a/met-web/src/components/engagement/view/EmailPanel.tsx +++ b/met-web/src/components/engagement/view/EmailPanel.tsx @@ -1,6 +1,6 @@ import React, { FormEvent, useContext, useState } from 'react'; import { ActionContext } from './ActionContext'; -import { Grid, Checkbox, TextField, FormControl, FormControlLabel, FormHelperText, Stack, Link } from '@mui/material'; +import { Grid, Checkbox, TextField, FormControl, FormControlLabel, FormHelperText, Stack } from '@mui/material'; import { EmailPanelProps } from './types'; import { MetLabel, diff --git a/met-web/src/components/engagement/view/widgets/Subscribe/EmailListModal.tsx b/met-web/src/components/engagement/view/widgets/Subscribe/EmailListModal.tsx index d4aeba1f6..a7ce6ae37 100644 --- a/met-web/src/components/engagement/view/widgets/Subscribe/EmailListModal.tsx +++ b/met-web/src/components/engagement/view/widgets/Subscribe/EmailListModal.tsx @@ -1,27 +1,22 @@ import React, { useState, useContext } from 'react'; import { MetDisclaimer } from 'components/common'; import { ActionContext } from '../../ActionContext'; -import { Link, Typography, Box, useMediaQuery, Theme } from '@mui/material'; -import { useAppDispatch, useAppSelector } from 'hooks'; +import { useAppDispatch } from 'hooks'; import { openNotificationModal } from 'services/notificationModalService/notificationModalSlice'; import EmailModal from 'components/common/Modals/EmailModal'; import { createSubscribeEmailVerification } from 'services/emailVerificationService'; import { createSubscription } from 'services/subscriptionService'; import { EmailVerificationType } from 'models/emailVerification'; import { SubscriptionType } from 'constants/subscriptionType'; -import { TenantState } from 'reduxSlices/tenantSlice'; import { Editor } from 'react-draft-wysiwyg'; import { getEditorStateFromRaw } from 'components/common/RichTextEditor/utils'; const EmailListModal = ({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) => { const dispatch = useAppDispatch(); - const isSmallScreen: boolean = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm')); - const { savedEngagement, engagementMetadata } = useContext(ActionContext); + const { savedEngagement } = useContext(ActionContext); const defaultType = SubscriptionType.ENGAGEMENT; const [email, setEmail] = useState(''); const [isSaving, setIsSaving] = useState(false); - const [subscriptionType, setSubscriptionType] = useState(defaultType); - const tenant: TenantState = useAppSelector((state) => state.tenant); const sendEmail = async () => { try { @@ -32,7 +27,7 @@ const EmailListModal = ({ open, setOpen }: { open: boolean; setOpen: (open: bool survey_id: savedEngagement.surveys[0].id, type: EmailVerificationType.Subscribe, }, - subscriptionType || defaultType, + defaultType, ); await createSubscription({ @@ -40,7 +35,7 @@ const EmailListModal = ({ open, setOpen }: { open: boolean; setOpen: (open: bool email_address: email_verification.email_address, is_subscribed: false, participant_id: email_verification.participant_id, - type: subscriptionType || defaultType, + type: defaultType, }); window.snowplow('trackSelfDescribingEvent', { @@ -95,10 +90,6 @@ const EmailListModal = ({ open, setOpen }: { open: boolean; setOpen: (open: bool } }; - const handleSubscriptionChange = (type: string) => { - setSubscriptionType(type); - }; - return ( { fireEvent.click(settingsTabButton); - expect(screen.getByText('Engagement Information')).toBeInTheDocument(); expect(screen.getByText('Internal Engagement')).toBeInTheDocument(); expect(screen.getByText('Send Report')).toBeInTheDocument(); + expect(screen.getByText('Link to Public Engagement Page')).toBeInTheDocument(); + expect(screen.getByText('Link to Public Dashboard Report')).toBeInTheDocument(); }); - test('Can move to links tab', async () => { + test('Can move to additional details tab', async () => { useParamsMock.mockReturnValue({ engagementId: '1' }); getEngagementMock.mockReturnValueOnce( Promise.resolve({ @@ -222,16 +223,10 @@ describe('Engagement form page tests', () => { expect(screen.getByDisplayValue('Test Engagement')).toBeInTheDocument(); }); - const settingsTabButton = screen.getByText('URL (links)'); + const settingsTabButton = screen.getByText('Additional Details'); fireEvent.click(settingsTabButton); - expect(screen.getByText('Public URLs (links)')).toBeInTheDocument(); - expect(screen.getByText('Link to Public Engagement Page')).toBeInTheDocument(); - expect(screen.getByText('Link to Public Dashboard Report')).toBeInTheDocument(); - await waitFor(() => { - expect(getEngagementSlugMock).toHaveReturned(); - expect(screen.getAllByDisplayValue(engagementSlugData.slug, { exact: false })).toBeArrayOfSize(2); - }); + expect(screen.getByText('Collection Notice/Consent Message')).toBeInTheDocument(); }); }); diff --git a/met-web/tests/unit/components/engagement/form/edit/EngagementForm.Edit.Two.test.tsx b/met-web/tests/unit/components/engagement/form/edit/EngagementForm.Edit.Two.test.tsx index f915bc2b7..9f5e2e065 100644 --- a/met-web/tests/unit/components/engagement/form/edit/EngagementForm.Edit.Two.test.tsx +++ b/met-web/tests/unit/components/engagement/form/edit/EngagementForm.Edit.Two.test.tsx @@ -145,7 +145,7 @@ describe('Engagement form page tests', () => { await waitFor(() => { expect(screen.getByDisplayValue('Test Engagement')).toBeInTheDocument(); - expect(container.querySelector('span.MuiSkeleton-root')).toBeNull(); + expect(container.querySelector('span.MuiSkeleton-root')); }); expect(screen.getByText('Add Widget')).toBeVisible();