fix: Implement user API enhancements with masked API keys and updated invite link handling
This commit is contained in:
@@ -216,8 +216,8 @@ describe('UsersPage', () => {
|
||||
uuid: 'new-user',
|
||||
email: 'new@example.com',
|
||||
role: 'user',
|
||||
invite_token: 'test-token-123',
|
||||
invite_url: 'https://charon.example.com/accept-invite?token=test-token-123',
|
||||
invite_token_masked: '********',
|
||||
invite_url: '[REDACTED]',
|
||||
email_sent: false,
|
||||
expires_at: '2024-01-03T00:00:00Z',
|
||||
})
|
||||
@@ -319,26 +319,19 @@ describe('UsersPage', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('shows manual invite link flow when email is not sent and allows copy', async () => {
|
||||
it('hides invite link when backend returns a redacted URL', async () => {
|
||||
vi.mocked(usersApi.listUsers).mockResolvedValue(mockUsers)
|
||||
vi.mocked(usersApi.inviteUser).mockResolvedValue({
|
||||
id: 5,
|
||||
uuid: 'invitee',
|
||||
email: 'manual@example.com',
|
||||
role: 'user',
|
||||
invite_token: 'token-123',
|
||||
invite_url: 'https://charon.example.com/accept-invite?token=token-123',
|
||||
invite_token_masked: '********',
|
||||
invite_url: '[REDACTED]',
|
||||
email_sent: false,
|
||||
expires_at: '2025-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
const writeText = vi.fn().mockResolvedValue(undefined)
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(navigator, 'clipboard')
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
get: () => ({ writeText }),
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
renderWithQueryClient(<UsersPage />)
|
||||
|
||||
const user = userEvent.setup()
|
||||
@@ -347,127 +340,10 @@ describe('UsersPage', () => {
|
||||
await user.type(screen.getByPlaceholderText('user@example.com'), 'manual@example.com')
|
||||
await user.click(screen.getByRole('button', { name: /^Send Invite$/i }))
|
||||
|
||||
await screen.findByDisplayValue(/accept-invite\?token=token-123/)
|
||||
const copyButton = await screen.findByRole('button', { name: /copy invite link/i })
|
||||
|
||||
await user.click(copyButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Invite link copied to clipboard')
|
||||
expect(screen.queryByRole('button', { name: /copy invite link/i })).not.toBeInTheDocument()
|
||||
expect(screen.queryByDisplayValue('[REDACTED]')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
if (originalDescriptor) {
|
||||
Object.defineProperty(navigator, 'clipboard', originalDescriptor)
|
||||
} else {
|
||||
delete (navigator as unknown as { clipboard?: unknown }).clipboard
|
||||
}
|
||||
})
|
||||
|
||||
it('uses textarea fallback copy when clipboard API fails', async () => {
|
||||
vi.mocked(usersApi.listUsers).mockResolvedValue(mockUsers)
|
||||
vi.mocked(usersApi.inviteUser).mockResolvedValue({
|
||||
id: 6,
|
||||
uuid: 'invitee-fallback',
|
||||
email: 'fallback@example.com',
|
||||
role: 'user',
|
||||
invite_token: 'token-fallback',
|
||||
invite_url: 'https://charon.example.com/accept-invite?token=token-fallback',
|
||||
email_sent: false,
|
||||
expires_at: '2025-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(navigator, 'clipboard')
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
get: () => undefined,
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
const appendSpy = vi.spyOn(document.body, 'appendChild')
|
||||
const removeSpy = vi.spyOn(document.body, 'removeChild')
|
||||
Object.defineProperty(document, 'execCommand', {
|
||||
value: vi.fn(),
|
||||
configurable: true,
|
||||
writable: true,
|
||||
})
|
||||
|
||||
renderWithQueryClient(<UsersPage />)
|
||||
|
||||
const user = userEvent.setup()
|
||||
await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument())
|
||||
await user.click(screen.getByRole('button', { name: /Invite User/i }))
|
||||
await user.type(screen.getByPlaceholderText('user@example.com'), 'fallback@example.com')
|
||||
await user.click(screen.getByRole('button', { name: /^Send Invite$/i }))
|
||||
|
||||
await screen.findByDisplayValue(/accept-invite\?token=token-fallback/)
|
||||
await user.click(screen.getByRole('button', { name: /copy invite link/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appendSpy).toHaveBeenCalled()
|
||||
expect(toast.success).toHaveBeenCalledWith('Invite link copied to clipboard')
|
||||
})
|
||||
|
||||
appendSpy.mockRestore()
|
||||
removeSpy.mockRestore()
|
||||
|
||||
if (originalDescriptor) {
|
||||
Object.defineProperty(navigator, 'clipboard', originalDescriptor)
|
||||
} else {
|
||||
delete (navigator as unknown as { clipboard?: unknown }).clipboard
|
||||
}
|
||||
})
|
||||
|
||||
it('uses textarea fallback copy when clipboard writeText rejects', async () => {
|
||||
vi.mocked(usersApi.listUsers).mockResolvedValue(mockUsers)
|
||||
vi.mocked(usersApi.inviteUser).mockResolvedValue({
|
||||
id: 7,
|
||||
uuid: 'invitee-reject',
|
||||
email: 'reject@example.com',
|
||||
role: 'user',
|
||||
invite_token: 'token-reject',
|
||||
invite_url: 'https://charon.example.com/accept-invite?token=token-reject',
|
||||
email_sent: false,
|
||||
expires_at: '2025-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
const writeText = vi.fn().mockRejectedValue(new Error('clipboard denied'))
|
||||
const originalDescriptor = Object.getOwnPropertyDescriptor(navigator, 'clipboard')
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
get: () => ({ writeText }),
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
const appendSpy = vi.spyOn(document.body, 'appendChild')
|
||||
const removeSpy = vi.spyOn(document.body, 'removeChild')
|
||||
Object.defineProperty(document, 'execCommand', {
|
||||
value: vi.fn().mockReturnValue(true),
|
||||
configurable: true,
|
||||
writable: true,
|
||||
})
|
||||
|
||||
renderWithQueryClient(<UsersPage />)
|
||||
|
||||
const user = userEvent.setup()
|
||||
await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument())
|
||||
await user.click(screen.getByRole('button', { name: /Invite User/i }))
|
||||
await user.type(screen.getByPlaceholderText('user@example.com'), 'reject@example.com')
|
||||
await user.click(screen.getByRole('button', { name: /^Send Invite$/i }))
|
||||
|
||||
await screen.findByDisplayValue(/accept-invite\?token=token-reject/)
|
||||
await user.click(screen.getByRole('button', { name: /copy invite link/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appendSpy).toHaveBeenCalled()
|
||||
expect(toast.success).toHaveBeenCalledWith('Invite link copied to clipboard')
|
||||
})
|
||||
|
||||
appendSpy.mockRestore()
|
||||
removeSpy.mockRestore()
|
||||
|
||||
if (originalDescriptor) {
|
||||
Object.defineProperty(navigator, 'clipboard', originalDescriptor)
|
||||
} else {
|
||||
delete (navigator as unknown as { clipboard?: unknown }).clipboard
|
||||
}
|
||||
})
|
||||
|
||||
describe('URL Preview in InviteModal', () => {
|
||||
|
||||
Reference in New Issue
Block a user