From 54998e7161083fe5479b37967d071d2e785b4133 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Mon, 11 Nov 2024 17:03:21 -0800 Subject: [PATCH] user-groups: Remove deactivated users from groups As newly required to support Zulip Server 10+; see #5899. Fixes: #5899 --- .../__tests__/userGroupsReducer-test.js | 42 +++++++++++++++++++ src/user-groups/userGroupsReducer.js | 28 +++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/user-groups/__tests__/userGroupsReducer-test.js b/src/user-groups/__tests__/userGroupsReducer-test.js index 37aebe9bf85..b51abb4e00c 100644 --- a/src/user-groups/__tests__/userGroupsReducer-test.js +++ b/src/user-groups/__tests__/userGroupsReducer-test.js @@ -1,6 +1,7 @@ /* @flow strict-local */ import deepFreeze from 'deep-freeze'; +import invariant from 'invariant'; import * as eg from '../../__tests__/lib/exampleData'; import { @@ -11,6 +12,7 @@ import { EVENT_USER_GROUP_REMOVE_MEMBERS, } from '../../actionConstants'; import userGroupsReducer from '../userGroupsReducer'; +import eventToAction from '../../events/eventToAction'; describe('userGroupsReducer', () => { describe('REGISTER_COMPLETE', () => { @@ -192,4 +194,44 @@ describe('userGroupsReducer', () => { ).toEqual([{ ...group1, members: [user1.user_id, user4.user_id] }, group2]); }); }); + + describe('realm_user op: update', () => { + test('a user is deactivated', () => { + const user1 = eg.makeUser(); + const user2 = eg.makeUser(); + const user3 = eg.makeUser(); + + const group1 = eg.makeUserGroup({ members: [user1.user_id, user2.user_id, user3.user_id] }); + const group2 = eg.makeUserGroup({ members: [user2.user_id, user1.user_id] }); + const group3 = eg.makeUserGroup({ members: [user1.user_id] }); + + const event = { + id: 0, + type: 'realm_user', + op: 'update', + person: { user_id: user1.user_id, is_active: false }, + }; + + const prevUserGroupsState = deepFreeze([group1, group2, group3]); + const prevPerAccountState = eg.reduxStatePlus({ + users: [user1, user2, user3], + userGroups: prevUserGroupsState, + }); + const action = eventToAction(prevPerAccountState, event); + + expect(action).not.toBeNull(); + invariant(action != null, 'action not null'); + + const actualState = userGroupsReducer(prevUserGroupsState, action); + + expect(actualState).toEqual([ + { ...group1, members: [user2.user_id, user3.user_id] }, + { ...group2, members: [user2.user_id] }, + + // A newly-empty group is not pruned; when a group is deactivated, + // we expect a user_group/remove event. + { ...group3, members: [] }, + ]); + }); + }); }); diff --git a/src/user-groups/userGroupsReducer.js b/src/user-groups/userGroupsReducer.js index 7ee86da4c7f..ecb7b054b6c 100644 --- a/src/user-groups/userGroupsReducer.js +++ b/src/user-groups/userGroupsReducer.js @@ -2,6 +2,7 @@ import type { UserGroupsState, PerAccountApplicableAction } from '../types'; import { REGISTER_COMPLETE, + EVENT, EVENT_USER_GROUP_ADD, EVENT_USER_GROUP_REMOVE, EVENT_USER_GROUP_UPDATE, @@ -9,6 +10,7 @@ import { EVENT_USER_GROUP_REMOVE_MEMBERS, RESET_ACCOUNT_DATA, } from '../actionConstants'; +import { EventTypes } from '../api/eventTypes'; import { NULL_ARRAY } from '../nullObjects'; const initialState: UserGroupsState = NULL_ARRAY; @@ -69,6 +71,32 @@ export default ( case EVENT_USER_GROUP_REMOVE_MEMBERS: return eventUserGroupRemoveMembers(state, action); + case EVENT: { + const { event } = action; + switch (event.type) { + case EventTypes.realm_user: { + switch (event.op) { + case 'update': { + const { person } = event; + // TODO(flow) teach Flow that the `person.existingUser != null` is redundant + if (person.is_active === false && person.existingUser != null) { + const userId = person.existingUser.user_id; + return state.map(g => ({ ...g, members: g.members.filter(m => m !== userId) })); + } + + return state; + } + + default: + return state; + } + } + + default: + return state; + } + } + default: return state; }