Merge pull request #17728 from overleaf/jel-fix-member-view-update

[web] Fix user view on frontend after unlinking group SSO member

GitOrigin-RevId: 375b798d23ed622453465661ced604c7068c1986
This commit is contained in:
Jessica Lawshe 2024-04-04 07:29:28 -05:00 committed by Copybot
parent f02248e1e5
commit 5fcc7f63cd
3 changed files with 209 additions and 5 deletions

View file

@ -32,10 +32,11 @@ export default function UnlinkUserModal({
if (!user.enrollment?.sso) {
return
}
const enrollment = Object.assign({}, user.enrollment, {
sso: user.enrollment.sso.filter(sso => sso.groupId !== groupId),
})
const updatedUser = Object.assign({}, user, {
enrollment: {
sso: user.enrollment.sso.filter(sso => sso.groupId !== groupId),
},
enrollment,
})
updateMemberView(user._id, updatedUser)
}, [groupId, updateMemberView, user])

View file

@ -1,5 +1,6 @@
import MembersList from '@/features/group-management/components/members-table/members-list'
import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
import { User } from '../../../../../../types/group-management/user'
const groupId = 'somegroup'
@ -110,4 +111,169 @@ describe('MembersList', function () {
.should('have.length', 0)
})
})
describe('SSO unlinking', function () {
const USER_PENDING_INVITE: User = {
_id: 'abc123def456',
first_name: 'John',
last_name: 'Doe',
email: 'john.doe@test.com',
last_active_at: new Date('2023-01-15'),
invite: true,
}
const USER_NOT_LINKED: User = {
_id: 'bcd234efa567',
first_name: 'Bobby',
last_name: 'Lapointe',
email: 'bobby.lapointe@test.com',
last_active_at: new Date('2023-01-02'),
invite: false,
}
const USER_LINKED: User = {
_id: 'defabc231453',
first_name: 'Claire',
last_name: 'Jennings',
email: 'claire.jennings@test.com',
last_active_at: new Date('2023-01-03'),
invite: false,
enrollment: {
sso: [
{
groupId,
linkedAt: new Date('2023-01-03'),
primary: true,
},
],
},
}
const USER_LINKED_AND_MANAGED: User = {
_id: 'defabc231453',
first_name: 'Jean-Luc',
last_name: 'Picard',
email: 'picard@test.com',
last_active_at: new Date('2023-01-03'),
invite: false,
enrollment: {
managedBy: groupId,
enrolledAt: new Date('2023-01-03'),
sso: [
{
groupId,
linkedAt: new Date('2023-01-03'),
primary: true,
},
],
},
}
const users = [
USER_PENDING_INVITE,
USER_NOT_LINKED,
USER_LINKED,
USER_LINKED_AND_MANAGED,
]
beforeEach(function () {
cy.window().then(win => {
win.metaAttributesCache.set('ol-groupId', groupId)
win.metaAttributesCache.set('ol-users', users)
win.metaAttributesCache.set('ol-groupSSOActive', true)
})
cy.intercept('POST', `manage/groups/${groupId}/unlink-user/*`, {
statusCode: 200,
})
})
describe('unlinking user', function () {
beforeEach(function () {
mountManagedUsersList()
cy.get('ul.managed-users-list table > tbody').within(() => {
cy.get('tr:nth-child(3)').within(() => {
cy.get('.sr-only').contains('SSO active')
cy.get('.action-btn').click()
cy.findByTestId('unlink-user-action').click()
})
})
})
it('should show successs notification and update the user row after unlinking', function () {
cy.get('.modal').within(() => {
cy.get('.btn-danger').click()
})
cy.get('.notification').contains(
`SSO reauthentication request has been sent to ${USER_LINKED.email}`
)
cy.get('ul.managed-users-list table > tbody').within(() => {
cy.get('tr:nth-child(3)').within(() => {
cy.get('.sr-only').contains('SSO not active')
})
})
})
})
describe('managed users enabled', function () {
beforeEach(function () {
cy.window().then(win => {
win.metaAttributesCache.set('ol-managedUsersActive', true)
})
mountManagedUsersList()
})
describe('when user is not managed', function () {
beforeEach(function () {
cy.get('ul.managed-users-list table > tbody').within(() => {
cy.get('tr:nth-child(3)').within(() => {
cy.get('.sr-only').contains('SSO active')
cy.get('.sr-only').contains('Not managed')
cy.get('.action-btn').click()
cy.findByTestId('unlink-user-action').click()
})
})
})
it('should show successs notification and update the user row after unlinking', function () {
cy.get('.modal').within(() => {
cy.get('.btn-danger').click()
})
cy.get('.notification').contains(
`SSO reauthentication request has been sent to ${USER_LINKED.email}`
)
cy.get('ul.managed-users-list table > tbody').within(() => {
cy.get('tr:nth-child(3)').within(() => {
cy.get('.sr-only').contains('SSO not active')
cy.get('.sr-only').contains('Not managed')
})
})
})
})
describe('when user is managed', function () {
beforeEach(function () {
cy.get('ul.managed-users-list table > tbody').within(() => {
cy.get('tr:nth-child(4)').within(() => {
cy.get('.sr-only').contains('SSO active')
cy.get('.sr-only').contains('Managed')
cy.get('.action-btn').click()
cy.findByTestId('unlink-user-action').click()
})
})
})
it('should show successs notification and update the user row after unlinking', function () {
cy.get('.modal').within(() => {
cy.get('.btn-danger').click()
})
cy.get('.notification').contains(
`SSO reauthentication request has been sent to ${USER_LINKED_AND_MANAGED.email}`
)
cy.get('ul.managed-users-list table > tbody').within(() => {
cy.get('tr:nth-child(4)').within(() => {
cy.get('.sr-only').contains('SSO not active')
cy.get('.sr-only').contains('Managed')
})
})
})
})
})
})
})

View file

@ -1,9 +1,10 @@
import { render, screen } from '@testing-library/react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { ReactElement } from 'react'
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import UnlinkUserModal from '@/features/group-management/components/members-table/unlink-user-modal'
import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
import { expect } from 'chai'
export function renderWithContext(component: ReactElement, props = {}) {
const GroupMembersProviderWrapper = ({
@ -17,16 +18,21 @@ export function renderWithContext(component: ReactElement, props = {}) {
describe('<UnlinkUserModal />', function () {
let defaultProps: any
const groupId = 'group123'
const userId = 'user123'
beforeEach(function () {
window.metaAttributesCache = new Map()
defaultProps = {
onClose: sinon.stub(),
user: {},
user: { _id: userId },
setGroupUserAlert: sinon.stub(),
}
window.metaAttributesCache.set('ol-groupId', groupId)
})
afterEach(function () {
window.metaAttributesCache = new Map()
fetchMock.reset()
})
@ -35,5 +41,36 @@ describe('<UnlinkUserModal />', function () {
await screen.findByRole('heading', {
name: 'Unlink user',
})
screen.getByText('Youre about to remove the SSO login option for', {
exact: false,
})
})
it('closes the modal on success', async function () {
fetchMock.post(`/manage/groups/${groupId}/unlink-user/${userId}`, 200)
renderWithContext(<UnlinkUserModal {...defaultProps} />)
await screen.findByRole('heading', {
name: 'Unlink user',
})
const confirmButton = screen.getByRole('button', { name: 'Unlink user' })
fireEvent.click(confirmButton)
await waitFor(() => expect(defaultProps.onClose).to.have.been.called)
})
it('handles errors', async function () {
fetchMock.post(`/manage/groups/${groupId}/unlink-user/${userId}`, 500)
renderWithContext(<UnlinkUserModal {...defaultProps} />)
await screen.findByRole('heading', {
name: 'Unlink user',
})
const confirmButton = screen.getByRole('button', { name: 'Unlink user' })
fireEvent.click(confirmButton)
await waitFor(() => screen.findByText('Sorry, something went wrong'))
})
})