154 lines
5.5 KiB
TypeScript
154 lines
5.5 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
vi.mock('./api/sessionApi', () => ({
|
|
getSessionMe: vi.fn(),
|
|
loginSession: vi.fn(),
|
|
refreshSession: vi.fn(),
|
|
logoutSession: vi.fn()
|
|
}));
|
|
|
|
vi.mock('./api/dashboardApi', () => ({
|
|
loadDashboard: vi.fn(),
|
|
loadRecentChanges: vi.fn(),
|
|
setServiceWindow: vi.fn()
|
|
}));
|
|
|
|
import { loadDashboard, loadRecentChanges, setServiceWindow } from './api/dashboardApi';
|
|
import { ApiError } from './api/client';
|
|
import { getSessionMe } from './api/sessionApi';
|
|
import App from './App';
|
|
|
|
describe('Restaurant Admin App', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(loadDashboard).mockReset();
|
|
vi.mocked(loadRecentChanges).mockReset();
|
|
vi.mocked(setServiceWindow).mockReset();
|
|
vi.mocked(getSessionMe).mockReset();
|
|
window.__APP_CONFIG__ = {
|
|
API_BASE_URL: 'http://localhost:8080',
|
|
THALOS_AUTH_BASE_URL: 'https://auth.dream-views.com',
|
|
THALOS_DEFAULT_RETURN_URL: 'https://restaurant-admin-demo.dream-views.com/config',
|
|
THALOS_DEFAULT_TENANT_ID: 'demo-tenant'
|
|
};
|
|
window.history.pushState({}, '', '/config');
|
|
});
|
|
|
|
it('shows central login action when session is missing', async () => {
|
|
vi.mocked(getSessionMe).mockResolvedValue({ isAuthenticated: false, subjectId: '', tenantId: '', provider: 0 });
|
|
|
|
render(<App />);
|
|
|
|
await waitFor(() => expect(screen.getByRole('link', { name: 'Continue with Google' })).toBeInTheDocument());
|
|
const link = screen.getByRole('link', { name: 'Continue with Google' }) as HTMLAnchorElement;
|
|
expect(link.href).toContain('/api/identity/oidc/google/start');
|
|
expect(link.href).toContain('tenantId=demo-tenant');
|
|
});
|
|
|
|
it('loads admin config and recent changes for authenticated users', async () => {
|
|
vi.mocked(getSessionMe).mockResolvedValue({
|
|
isAuthenticated: true,
|
|
subjectId: 'demo-user',
|
|
tenantId: 'demo-tenant',
|
|
provider: 2
|
|
});
|
|
vi.mocked(loadDashboard).mockResolvedValue({
|
|
contextId: 'demo-context',
|
|
summary: 'configured',
|
|
version: 'v2',
|
|
featureFlags: [{ key: 'pos.closeout.preview', enabled: true }],
|
|
serviceWindows: [{ day: 1, openAt: '08:00:00', closeAt: '22:00:00', isClosed: false }],
|
|
recentChanges: []
|
|
});
|
|
vi.mocked(loadRecentChanges).mockResolvedValue({
|
|
contextId: 'demo-context',
|
|
summary: 'changes',
|
|
version: 'v2',
|
|
recentChanges: [
|
|
{
|
|
changeId: 'CFG-100',
|
|
category: 'feature-flag',
|
|
description: 'Enabled POS closeout preview mode.',
|
|
updatedBy: 'ops-lead',
|
|
updatedAtUtc: '2026-03-31T12:00:00Z'
|
|
}
|
|
]
|
|
});
|
|
|
|
render(<App />);
|
|
|
|
await waitFor(() => expect(screen.getByRole('button', { name: /Load Config/ })).toBeInTheDocument());
|
|
fireEvent.click(screen.getByRole('button', { name: /Load Config/ }));
|
|
fireEvent.click(screen.getByRole('button', { name: /Load Recent Changes/ }));
|
|
|
|
await waitFor(() => expect(loadDashboard).toHaveBeenCalledWith('demo-context'));
|
|
await waitFor(() => expect(loadRecentChanges).toHaveBeenCalledWith('demo-context'));
|
|
expect(await screen.findByText('pos.closeout.preview')).toBeInTheDocument();
|
|
expect(await screen.findByText('CFG-100')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies service window and shows the latest applied snapshot', async () => {
|
|
vi.mocked(getSessionMe).mockResolvedValue({
|
|
isAuthenticated: true,
|
|
subjectId: 'demo-user',
|
|
tenantId: 'demo-tenant',
|
|
provider: 2
|
|
});
|
|
vi.mocked(loadDashboard).mockResolvedValue({
|
|
contextId: 'demo-context',
|
|
summary: 'configured',
|
|
version: 'v2',
|
|
featureFlags: [],
|
|
serviceWindows: [],
|
|
recentChanges: []
|
|
});
|
|
vi.mocked(loadRecentChanges).mockResolvedValue({
|
|
contextId: 'demo-context',
|
|
summary: 'changes',
|
|
version: 'v2',
|
|
recentChanges: []
|
|
});
|
|
vi.mocked(setServiceWindow).mockResolvedValue({
|
|
contextId: 'demo-context',
|
|
applied: true,
|
|
message: 'Service window updated by admin-operator.',
|
|
serviceWindow: {
|
|
day: 1,
|
|
openAt: '08:00:00',
|
|
closeAt: '22:00:00',
|
|
isClosed: false
|
|
}
|
|
});
|
|
|
|
render(<App />);
|
|
|
|
await waitFor(() => expect(screen.getByText('Service Window')).toBeInTheDocument());
|
|
fireEvent.click(screen.getByText('Service Window'));
|
|
fireEvent.click(screen.getByRole('button', { name: 'Apply Service Window' }));
|
|
|
|
await waitFor(() => expect(setServiceWindow).toHaveBeenCalledTimes(1));
|
|
expect(await screen.findAllByText('Service window updated by admin-operator.')).toHaveLength(2);
|
|
expect((await screen.findAllByText('Monday')).length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('shows reauthentication guidance when the session expires', async () => {
|
|
vi.mocked(getSessionMe)
|
|
.mockResolvedValueOnce({
|
|
isAuthenticated: true,
|
|
subjectId: 'demo-user',
|
|
tenantId: 'demo-tenant',
|
|
provider: 2
|
|
})
|
|
.mockResolvedValueOnce({ isAuthenticated: false, subjectId: '', tenantId: '', provider: 2 });
|
|
vi.mocked(loadDashboard).mockRejectedValue(new ApiError(401, 'Unauthorized request.', 'unauthorized', 'corr-401'));
|
|
|
|
render(<App />);
|
|
|
|
await waitFor(() => expect(screen.getByRole('button', { name: /Load Config/ })).toBeInTheDocument());
|
|
fireEvent.click(screen.getByRole('button', { name: /Load Config/ }));
|
|
|
|
await waitFor(() => expect(screen.getByText('Authentication Required')).toBeInTheDocument());
|
|
expect(screen.getByText('Session expired')).toBeInTheDocument();
|
|
});
|
|
});
|