Просмотр исходного кода

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
Jessica Lawshe 2 лет назад
Родитель
Сommit
5fcc7f63cd

+ 4 - 3
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) {
     if (!user.enrollment?.sso) {
       return
       return
     }
     }
+    const enrollment = Object.assign({}, user.enrollment, {
+      sso: user.enrollment.sso.filter(sso => sso.groupId !== groupId),
+    })
     const updatedUser = Object.assign({}, user, {
     const updatedUser = Object.assign({}, user, {
-      enrollment: {
-        sso: user.enrollment.sso.filter(sso => sso.groupId !== groupId),
-      },
+      enrollment,
     })
     })
     updateMemberView(user._id, updatedUser)
     updateMemberView(user._id, updatedUser)
   }, [groupId, updateMemberView, user])
   }, [groupId, updateMemberView, user])

+ 166 - 0
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 MembersList from '@/features/group-management/components/members-table/members-list'
 import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
 import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
+import { User } from '../../../../../../types/group-management/user'
 
 
 const groupId = 'somegroup'
 const groupId = 'somegroup'
 
 
@@ -110,4 +111,169 @@ describe('MembersList', function () {
         .should('have.length', 0)
         .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')
+            })
+          })
+        })
+      })
+    })
+  })
 })
 })

+ 39 - 2
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 { ReactElement } from 'react'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import fetchMock from 'fetch-mock'
 import fetchMock from 'fetch-mock'
 import UnlinkUserModal from '@/features/group-management/components/members-table/unlink-user-modal'
 import UnlinkUserModal from '@/features/group-management/components/members-table/unlink-user-modal'
 import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
 import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
+import { expect } from 'chai'
 
 
 export function renderWithContext(component: ReactElement, props = {}) {
 export function renderWithContext(component: ReactElement, props = {}) {
   const GroupMembersProviderWrapper = ({
   const GroupMembersProviderWrapper = ({
@@ -17,16 +18,21 @@ export function renderWithContext(component: ReactElement, props = {}) {
 
 
 describe('<UnlinkUserModal />', function () {
 describe('<UnlinkUserModal />', function () {
   let defaultProps: any
   let defaultProps: any
+  const groupId = 'group123'
+  const userId = 'user123'
 
 
   beforeEach(function () {
   beforeEach(function () {
+    window.metaAttributesCache = new Map()
     defaultProps = {
     defaultProps = {
       onClose: sinon.stub(),
       onClose: sinon.stub(),
-      user: {},
+      user: { _id: userId },
       setGroupUserAlert: sinon.stub(),
       setGroupUserAlert: sinon.stub(),
     }
     }
+    window.metaAttributesCache.set('ol-groupId', groupId)
   })
   })
 
 
   afterEach(function () {
   afterEach(function () {
+    window.metaAttributesCache = new Map()
     fetchMock.reset()
     fetchMock.reset()
   })
   })
 
 
@@ -35,5 +41,36 @@ describe('<UnlinkUserModal />', function () {
     await screen.findByRole('heading', {
     await screen.findByRole('heading', {
       name: 'Unlink user',
       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(<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'))
   })
   })
 })
 })