Files
portal/app/tests/Frontend/router.spec.ts
T
Дмитрий 59299d3c2b phase2(auth-frontend): axios + Pinia + auth-store + auth-guards + form integration
- axios@^1.16 + pinia@^3.0 (--legacy-peer-deps).
- api/client.ts: axios с withCredentials+withXSRFToken (Sanctum SPA auto-XSRF).
  ensureCsrfCookie() + extractValidationErrors/Message helpers.
- api/auth.ts: типизированные login/register/me/logout с AuthUser interface.
- stores/auth.ts: Pinia composition-store (user/loading/requires2fa +
  isAuthenticated computed + login/register/fetchMe/logout actions).
  logout() catch-swallow - UI всегда выходит локально.
- LoginView/RegisterView: useAuthStore интеграция, real POST через store,
  errors из 422 на v-text-fields, redirect на /dashboard или /2fa,
  :loading на btn'ах.
- Auth-guard в router.beforeEach: meta.requiresAuth на 10 routes
  (6 app + 4 admin), meta.guestOnly на login/register/forgot. При первом
  переходе fetchMe() restore-session. Unauth → /login?redirect=<original>.
- / redirect → /dashboard (auth-guard перехватит если не залогинен).
- Pinia в app.ts через app.use(createPinia()).
- cspell-words.txt: мокаем.

Vitest +10 (всего 139/139 за 10.11s):
- auth-store 7 (initial state + login success/reject + register + fetchMe
  success/401 + logout swallow).
- router 5 переписан (login.guestOnly + 6 protected + admin layout +
  3 error без auth + unauth /dashboard → /login?redirect).
- LoginView/RegisterView/router тесты получили createPinia в plugins.
- vi.mock api/auth в router+auth-store specs.

Регресс: lint+type+format OK; vitest 139/139; vite build (main app-chunk
105→153.64 KB +axios+pinia+auth gzipped 54.54 KB) 806ms; story:build 21/28
за 31.73s; Pest 61/61 за 5.86s.

CLAUDE.md v1.32->v1.33, реестр Открытых_вопросов v1.41->v1.42.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:59:43 +03:00

63 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, it, expect, vi } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';
// Мокаем api/auth, чтобы router beforeEach guard не делал реальных HTTP-вызовов.
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';
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');
});
});