import { beforeEach, describe, it, expect, vi } from 'vitest'; import { createPinia, setActivePinia } from 'pinia'; // Мокаем api/auth, чтобы router beforeEach guard не делал реальных HTTP-вызовов. // `me()` возвращает AuthUser напрямую (см. resources/js/api/auth.ts:65-68). vi.mock('../../resources/js/api/auth', () => ({ me: vi.fn(() => Promise.reject(new Error('not authenticated'))), login: vi.fn(), register: vi.fn(), logout: vi.fn(), })); import { router } from '../../resources/js/router'; import { useAuthStore } from '../../resources/js/stores/auth'; describe('router/index.ts', () => { beforeEach(() => { setActivePinia(createPinia()); }); it('содержит маршрут /login с layout=auth + guestOnly', () => { const loginRoute = router.getRoutes().find((r) => r.name === 'login'); expect(loginRoute).toBeDefined(); expect(loginRoute?.path).toBe('/login'); expect(loginRoute?.meta.layout).toBe('auth'); expect(loginRoute?.meta.guestOnly).toBe(true); }); it('защищённые маршруты помечены requiresAuth=true', () => { const protectedNames = ['dashboard', 'deals', 'kanban', 'billing', 'settings', 'reports']; const routes = router.getRoutes(); protectedNames.forEach((name) => { const route = routes.find((r) => r.name === name); expect(route, `route ${name} not found`).toBeDefined(); expect(route?.meta.requiresAuth, `route ${name} should require auth`).toBe(true); }); }); it('admin-маршруты помечены requiresAuth=true и layout=admin', () => { const routes = router.getRoutes(); const adminTenants = routes.find((r) => r.name === 'admin-tenants'); expect(adminTenants?.meta.requiresAuth).toBe(true); expect(adminTenants?.meta.layout).toBe('admin'); }); it('error-маршруты НЕ требуют auth', () => { const routes = router.getRoutes(); const errorNames = ['forbidden', 'server-error', 'not-found']; errorNames.forEach((name) => { const route = routes.find((r) => r.name === name); expect(route?.meta.requiresAuth).toBeUndefined(); expect(route?.meta.layout).toBe('error'); }); }); it('гость, идущий на /dashboard без auth, редиректится на /login', async () => { await router.push('/dashboard'); await router.isReady(); // beforeEach guard делает fetchMe (мок отвергает) → user=null → redirect /login. expect(router.currentRoute.value.path).toBe('/login'); // Сохраняется ?redirect=/dashboard для возврата после login. expect(router.currentRoute.value.query.redirect).toBe('/dashboard'); }); // ---------- Integration tests (Q.DEFER.003 sub-C) ---------- // Покрывают actual navigation flows через beforeEach guard. // NB: `authInitialized` (router/index.ts:281) — module-level флаг, после первого // теста выше остаётся true. Тесты ниже устанавливают auth.user напрямую через // store mutation (bypass fetchMe path). it('authenticated user attempting /login (guestOnly) → redirected to /dashboard', async () => { const auth = useAuthStore(); auth.user = { id: 1, email: 'test@demo.local', first_name: 'Test', last_name: 'User', tenant_id: 1, totp_enabled: false, last_login_at: '2026-05-13T00:00:00Z', } as unknown as ReturnType['user']; await router.push('/login'); await router.isReady(); expect(router.currentRoute.value.path).toBe('/dashboard'); }); it('authenticated user navigating to /dashboard passes requiresAuth guard', async () => { const auth = useAuthStore(); auth.user = { id: 1, email: 'test@demo.local', first_name: 'Test', last_name: 'User', tenant_id: 1, totp_enabled: false, last_login_at: '2026-05-13T00:00:00Z', } as unknown as ReturnType['user']; await router.push('/dashboard'); await router.isReady(); expect(router.currentRoute.value.path).toBe('/dashboard'); expect(router.currentRoute.value.name).toBe('dashboard'); }); it('unknown path /no-such-path resolves to 404 ErrorView', async () => { await router.push('/no-such-path-xyz'); await router.isReady(); expect(router.currentRoute.value.name).toBe('not-found'); expect(router.currentRoute.value.meta.errorCode).toBe('404'); expect(router.currentRoute.value.meta.layout).toBe('error'); }); it('/admin redirects to /admin/tenants', async () => { const auth = useAuthStore(); auth.user = { id: 1, email: 'test@demo.local', first_name: 'Test', last_name: 'User', tenant_id: 1, totp_enabled: false, last_login_at: '2026-05-13T00:00:00Z', } as unknown as ReturnType['user']; await router.push('/admin'); await router.isReady(); expect(router.currentRoute.value.path).toBe('/admin/tenants'); expect(router.currentRoute.value.name).toBe('admin-tenants'); }); it('/reset/:token resolves with token param exposed', async () => { // auth.user остаётся null (beforeEach создал свежий Pinia), guestOnly пропускает. await router.push('/reset/abc123-token-xyz'); await router.isReady(); expect(router.currentRoute.value.name).toBe('reset-password'); expect(router.currentRoute.value.params.token).toBe('abc123-token-xyz'); }); });