datajunction-ui
Version:
DataJunction UI
199 lines (164 loc) • 5.3 kB
JSX
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { ServiceAccountsSection } from '../ServiceAccountsSection';
describe('ServiceAccountsSection', () => {
const mockOnCreate = vi.fn();
const mockOnDelete = vi.fn();
const mockAccounts = [
{
id: 1,
name: 'my-pipeline',
client_id: 'abc-123-xyz',
created_at: '2024-12-01T00:00:00Z',
},
{
id: 2,
name: 'etl-job',
client_id: 'def-456-uvw',
created_at: '2024-12-02T00:00:00Z',
},
];
beforeEach(() => {
vi.clearAllMocks();
});
it('renders empty state when no accounts', () => {
render(
<ServiceAccountsSection
accounts={[]}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
expect(screen.getByText(/No service accounts yet/i)).toBeInTheDocument();
});
it('renders accounts list', () => {
render(
<ServiceAccountsSection
accounts={mockAccounts}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
expect(screen.getByText('my-pipeline')).toBeInTheDocument();
expect(screen.getByText('etl-job')).toBeInTheDocument();
expect(screen.getByText('abc-123-xyz')).toBeInTheDocument();
expect(screen.getByText('def-456-uvw')).toBeInTheDocument();
});
it('renders section title and create button', () => {
render(
<ServiceAccountsSection
accounts={[]}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
expect(screen.getByText('Service Accounts')).toBeInTheDocument();
expect(screen.getByText('+ Create')).toBeInTheDocument();
});
it('opens create modal when create button is clicked', () => {
render(
<ServiceAccountsSection
accounts={[]}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
fireEvent.click(screen.getByText('+ Create'));
expect(screen.getByText('Create Service Account')).toBeInTheDocument();
});
it('calls onDelete when delete is confirmed', async () => {
window.confirm = vi.fn().mockReturnValue(true);
mockOnDelete.mockResolvedValue();
render(
<ServiceAccountsSection
accounts={mockAccounts}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
const deleteButtons = screen.getAllByTitle('Delete service account');
fireEvent.click(deleteButtons[0]);
expect(window.confirm).toHaveBeenCalledWith(
'Delete service account "my-pipeline"?\n\nThis will revoke all access for this account and cannot be undone.',
);
await waitFor(() => {
expect(mockOnDelete).toHaveBeenCalledWith('abc-123-xyz');
});
});
it('does not call onDelete when delete is cancelled', () => {
window.confirm = vi.fn().mockReturnValue(false);
render(
<ServiceAccountsSection
accounts={mockAccounts}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
const deleteButtons = screen.getAllByTitle('Delete service account');
fireEvent.click(deleteButtons[0]);
expect(mockOnDelete).not.toHaveBeenCalled();
});
it('renders description text', () => {
render(
<ServiceAccountsSection
accounts={[]}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
expect(
screen.getByText(/Service accounts allow programmatic access/i),
).toBeInTheDocument();
});
it('shows table headers when accounts exist', () => {
render(
<ServiceAccountsSection
accounts={mockAccounts}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByText('Client ID')).toBeInTheDocument();
expect(screen.getByText('Created')).toBeInTheDocument();
});
it('handles delete error gracefully', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation();
const alertSpy = vi.spyOn(window, 'alert').mockImplementation();
window.confirm = vi.fn().mockReturnValue(true);
mockOnDelete.mockRejectedValue(new Error('Delete failed'));
render(
<ServiceAccountsSection
accounts={mockAccounts}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
const deleteButtons = screen.getAllByTitle('Delete service account');
fireEvent.click(deleteButtons[0]);
await waitFor(() => {
expect(alertSpy).toHaveBeenCalledWith('Failed to delete service account');
});
consoleSpy.mockRestore();
alertSpy.mockRestore();
});
it('closes modal when close button is clicked', () => {
render(
<ServiceAccountsSection
accounts={[]}
onCreate={mockOnCreate}
onDelete={mockOnDelete}
/>,
);
// Open modal
fireEvent.click(screen.getByText('+ Create'));
expect(screen.getByText('Create Service Account')).toBeInTheDocument();
// Close modal
fireEvent.click(screen.getByTitle('Close'));
// Modal should no longer be visible (it stays in DOM but should be hidden)
// The modal content should be hidden
expect(
screen.queryByText('Create Service Account'),
).not.toBeInTheDocument();
});
});