import { describe, expect, it } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import { dispatchGatewayEvent } from '@/stores/gateway'; import { apiError, installFetch, installFailingFetch, json, makeUser, renderApp } from './helpers'; describe('защита маршрутов', () => { it('неавторизованного на /app отправляет на /login', async () => { installFetch([ { match: '/api/v1/users/@me', response: () => apiError('auth.unauthorized', 401) }, ]); const { router } = renderApp('/app'); await waitFor(() => { expect(router.state.location.pathname).toBe('/login'); }); expect(await screen.findByLabelText('Почта')).toBeVisible(); }); it('отзыв сессии на открытом сервере уводит на /login', async () => { const user = makeUser(); installFetch([ { match: '/api/v1/users/@me', response: () => json({ user }) }, { match: '/api/v1/users/@me/guilds', response: () => json({ guilds: [] }) }, ]); const { router } = renderApp('/app/g-1/c-1'); await waitFor(() => { expect(screen.getByTestId('guild-rail')).toBeVisible(); }); // Сервер отозвал сессии (logout-all, смена пароля): приходит INVALID_SESSION, // и уход на экран входа не зависит от ответа запроса профиля (AGENT.md 11.6). dispatchGatewayEvent({ op: 0, t: 'SESSION_INVALIDATED', s: 1, d: { reason: 'logout_all' } }); await waitFor(() => { expect(router.state.location.pathname).toBe('/login'); }); expect(await screen.findByLabelText('Почта')).toBeVisible(); }); it('пользователя с onboarding_completed=false отправляет на /onboarding', async () => { const user = makeUser({ onboarding_completed: false }); installFetch([{ match: '/api/v1/users/@me', response: () => json({ user }) }]); const { router } = renderApp('/app'); await waitFor(() => { expect(router.state.location.pathname).toBe('/onboarding'); }); expect(await screen.findByText('Привет, Alice!')).toBeVisible(); }); it('онбординг доступен при незавершённой настройке (без цикла редиректов)', async () => { const user = makeUser({ onboarding_completed: false }); installFetch([ { match: '/api/v1/users/@me', response: () => json({ user }) }, { match: '/api/v1/users/@me/onboarding/complete', method: 'POST', response: () => json({ user }), }, ]); const { router } = renderApp('/onboarding'); expect(await screen.findByLabelText('Как вас показывать?')).toBeVisible(); expect(router.state.location.pathname).toBe('/onboarding'); }); it('/ без сессии ведёт на /login, а /status доступен без авторизации', async () => { installFetch([ { match: '/api/v1/users/@me', response: () => apiError('auth.unauthorized', 401) }, { match: '/api/v1/meta', response: () => apiError('internal.error', 500) }, { match: '/api/v1/readyz', response: () => apiError('internal.error', 500) }, ]); const { router } = renderApp('/'); await waitFor(() => { expect(router.state.location.pathname).toBe('/login'); }); const status = renderApp('/status'); expect(await status.findByTestId('connection-state')).toHaveTextContent('Сервер недоступен'); }); it('сетевую ошибку показывает как сообщение, а не редирект', async () => { installFailingFetch(); const { router } = renderApp('/app'); expect(await screen.findByRole('alert')).toHaveTextContent('Сервер не отвечает'); expect(router.state.location.pathname).toBe('/app'); }); });