Skip to content

Commit

Permalink
Remove reinstate memberships functionality (#2098)
Browse files Browse the repository at this point in the history
* Remove reinstate memberships functionality

* Add test to verify user membership reassign

* fix lint issues
  • Loading branch information
jadmsaadaot authored Aug 29, 2023
1 parent 9844eff commit 59ed61f
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 62 deletions.
35 changes: 0 additions & 35 deletions met-api/src/met_api/models/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,38 +168,3 @@ def revoke_memberships_bulk(cls, user_id: int):
db.session.commit()

return new_memberships

@classmethod
def reinstate_memberships_bulk(cls, user_id: int, membership_type: int = None):
"""Create reinstated memberships versions in bulk."""
# Get all latest memberships by engagement ids
current_memberships = db.session.query(Membership) \
.filter(and_(
Membership.user_id == user_id,
Membership.is_latest.is_(True)
)) \
.all()

if not current_memberships:
return []

# Create new versions with the desired changes
new_memberships = []
for current_membership in current_memberships:
current_membership.is_latest = False
db.session.add(current_membership)
new_membership = Membership(
engagement_id=current_membership.engagement_id,
user_id=user_id,
status=MembershipStatus.ACTIVE.value,
type=membership_type if membership_type else current_membership.type,
revoked_date=datetime.utcnow(),
is_latest=True,
version=current_membership.version + 1
)
new_memberships.append(new_membership)

# Bulk insert new versions
db.session.bulk_save_objects(new_memberships)
db.session.commit()
return new_memberships
2 changes: 1 addition & 1 deletion met-api/src/met_api/resources/staff_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def patch(user_id):
return str(err), HTTPStatus.BAD_REQUEST


@cors_preflight('POST')
@cors_preflight('POST, PUT')
@API.route('/<user_id>/groups')
class UserGroup(Resource):
"""Add user to group."""
Expand Down
12 changes: 4 additions & 8 deletions met-api/src/met_api/services/membership_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,7 @@ def reinstate_membership(membership: MembershipModel):
return new_membership

@staticmethod
def reassign_memberships(user_id: int, membership_type: int):
"""Update memberships type."""
MembershipModel.revoke_memberships_bulk(user_id)
new_memberships = []
if membership_type in [MembershipType.TEAM_MEMBER.value, MembershipType.REVIEWER.value]:
new_memberships = MembershipModel.reinstate_memberships_bulk(user_id, membership_type)

return new_memberships
def revoke_memberships_bulk(user_id: int):
"""Revoke memberships in bulk."""
revoked_memberships = MembershipModel.revoke_memberships_bulk(user_id)
return revoked_memberships
17 changes: 2 additions & 15 deletions met-api/src/met_api/services/staff_user_membership_service.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
"""Service for membership."""
from http import HTTPStatus

from met_api.constants.membership_type import MembershipType
from met_api.exceptions.business_exception import BusinessException
from met_api.schemas.staff_user import StaffUserSchema
from met_api.services.staff_user_service import StaffUserService
from met_api.services.membership_service import MembershipService
from met_api.services.staff_user_service import StaffUserService
from met_api.utils.constants import Groups
from met_api.utils.enums import KeycloakGroups


class StaffUserMembershipService:
"""Staff User Membership management service."""

@staticmethod
def _get_membership_type_from_group_name(group_name):
"""Get membership type from group name."""
if group_name == KeycloakGroups.EAO_TEAM_MEMBER.name:
return MembershipType.TEAM_MEMBER
if group_name == KeycloakGroups.EAO_REVIEWER.name:
return MembershipType.REVIEWER
return None

@classmethod
def reassign_user(cls, user_id, group_name):
"""Add user to a new group and reassign memberships."""
Expand Down Expand Up @@ -51,8 +40,6 @@ def reassign_user(cls, user_id, group_name):

StaffUserService.remove_user_from_group(external_id, Groups.get_name_by_value(main_group))
StaffUserService.add_user_to_group(external_id, group_name)
membership_type = StaffUserMembershipService._get_membership_type_from_group_name(group_name)
MembershipService.reassign_memberships(user_id, membership_type)

MembershipService.revoke_memberships_bulk(user_id)
new_user = StaffUserService.get_user_by_id(user_id, include_groups=True)
return StaffUserSchema().dump(new_user)
3 changes: 1 addition & 2 deletions met-api/src/met_api/services/staff_user_service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Service for user management."""
from http import HTTPStatus
from typing import List

from flask import current_app, g

Expand Down Expand Up @@ -103,7 +102,7 @@ def _render_email_template(user: StaffUserModel):
@staticmethod
def attach_groups(user_collection):
"""Attach keycloak groups to user object."""
group_user_details: List = KEYCLOAK_SERVICE.get_users_groups(
group_user_details = KEYCLOAK_SERVICE.get_users_groups(
[user.get('external_id') for user in user_collection])

for user in user_collection:
Expand Down
108 changes: 108 additions & 0 deletions met-api/tests/unit/api/test_user_membership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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 membership operations.
Test-Suite to ensure that the user membership endpoints are working as expected.
"""
from http import HTTPStatus
from unittest.mock import MagicMock

from met_api.models.membership import Membership as MembershipModel
from met_api.utils.enums import ContentType, KeycloakGroupName, MembershipStatus, UserStatus
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)


KEYCLOAK_SERVICE_MODULE = 'met_api.services.keycloak.KeycloakService'


def mock_keycloak_methods(mocker, mock_group_names):
"""Mock the KeycloakService.add_user_to_group method."""
mock_response = MagicMock()
mock_response.status_code = HTTPStatus.NO_CONTENT

mock_add_user_to_group_keycloak = mocker.patch(
f'{KEYCLOAK_SERVICE_MODULE}.add_user_to_group',
return_value=mock_response
)

mock_get_user_groups_keycloak = mocker.patch(
f'{KEYCLOAK_SERVICE_MODULE}.get_user_groups',
return_value=[{'name': group_name} for group_name in mock_group_names]
)

mock_add_attribute_to_user = mocker.patch(
f'{KEYCLOAK_SERVICE_MODULE}.add_attribute_to_user',
return_value=mock_response
)

mock_remove_user_from_group_keycloak = mocker.patch(
f'{KEYCLOAK_SERVICE_MODULE}.remove_user_from_group',
return_value=mock_response
)

return (
mock_add_user_to_group_keycloak,
mock_get_user_groups_keycloak,
mock_add_attribute_to_user,
mock_remove_user_from_group_keycloak
)


def test_reassign_user_reviewer_team_member(mocker, client, jwt, session):
"""Assert that returns bad request if bad request body."""
user = factory_staff_user_model()
eng = factory_engagement_model()
current_membership = factory_membership_model(user_id=user.id, engagement_id=eng.id)
assert current_membership.status == MembershipStatus.ACTIVE.value
mock_response = MagicMock()
mock_response.status_code = HTTPStatus.NO_CONTENT

(
mock_add_user_to_group_keycloak,
mock_get_user_groups_keycloak,
mock_add_attribute_to_user,
mock_remove_user_from_group_keycloak
) = mock_keycloak_methods(
mocker,
[KeycloakGroupName.EAO_REVIEWER.value]
)

mock_get_users_groups_keycloak = mocker.patch(
f'{KEYCLOAK_SERVICE_MODULE}.get_users_groups',
return_value={user.external_id: [KeycloakGroupName.EAO_REVIEWER.value]}
)

assert user.status_id == UserStatus.ACTIVE.value
claims = TestJwtClaims.staff_admin_role
headers = factory_auth_header(jwt=jwt, claims=claims)

rv = client.put(
f'/api/user/{user.id}/groups?group=EAO_TEAM_MEMBER',
headers=headers,
content_type=ContentType.JSON.value
)

assert rv.status_code == HTTPStatus.OK
mock_add_user_to_group_keycloak.assert_called()
mock_get_user_groups_keycloak.assert_called()
mock_add_attribute_to_user.assert_called()
mock_remove_user_from_group_keycloak.assert_called()
mock_get_users_groups_keycloak.assert_called()

memberships = MembershipModel.find_by_user_id(user.id)
assert len(memberships) == 1
assert memberships[0].status == MembershipStatus.REVOKED.value
1 change: 1 addition & 0 deletions met-api/tests/utilities/factory_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ class TestJwtClaims(dict, Enum):
'view_all_engagements',
'toggle_user_status',
'export_to_csv',
'update_user_group'
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const ActionsDropDown = ({ selectedUser }: { selectedUser: User }) => {
},
{
value: 3,
label: 'ReassignRole',
label: 'Reassign Role',
action: () => {
setUser(selectedUser);
setReassignRoleModalOpen(true);
Expand Down

0 comments on commit 59ed61f

Please sign in to comment.