diff --git a/services/web/frontend/js/features/group-management/components/members-table/unlink-user-modal.tsx b/services/web/frontend/js/features/group-management/components/members-table/unlink-user-modal.tsx
index 13462e8947..73b0e39a67 100644
--- a/services/web/frontend/js/features/group-management/components/members-table/unlink-user-modal.tsx
+++ b/services/web/frontend/js/features/group-management/components/members-table/unlink-user-modal.tsx
@@ -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])
diff --git a/services/web/test/frontend/features/group-management/components/members-table/members-list.spec.tsx b/services/web/test/frontend/features/group-management/components/members-table/members-list.spec.tsx
index 206d8ed204..abcc50ab67 100644
--- a/services/web/test/frontend/features/group-management/components/members-table/members-list.spec.tsx
+++ b/services/web/test/frontend/features/group-management/components/members-table/members-list.spec.tsx
@@ -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')
+ })
+ })
+ })
+ })
+ })
+ })
})
diff --git a/services/web/test/frontend/features/group-management/components/members-table/unlink-user-modal.test.tsx b/services/web/test/frontend/features/group-management/components/members-table/unlink-user-modal.test.tsx
index d02fedcd35..4eea219475 100644
--- a/services/web/test/frontend/features/group-management/components/members-table/unlink-user-modal.test.tsx
+++ b/services/web/test/frontend/features/group-management/components/members-table/unlink-user-modal.test.tsx
@@ -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('', 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('', function () {
await screen.findByRole('heading', {
name: 'Unlink user',
})
+ screen.getByText('You’re 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()
+ 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()
+ 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'))
})
})