import assert from 'node:assert/strict'; import test from 'node:test'; import { requestJson, ApiRequestError } from '../react/shared/api-client.js'; import { loadAuthorizedSession } from '../react/shared/auth-client.js'; import { resolveSessionState, SESSION_PHASES } from '../react/shared/session-policy.js'; import { renderAsyncState, renderErrorBoundaryContent, renderFormAlert } from '../dist/tests/react-shared-renderer.mjs'; test('общая политика сессии различает разрешённую и запрещённую роль', () => { const ready = resolveSessionState({ role: 'STUDENT', userId: '7' }, ['STUDENT']); const denied = resolveSessionState({ role: 'TEACHER', userId: '8' }, ['STUDENT']); assert.equal(ready.phase, SESSION_PHASES.ready); assert.equal(ready.session.userId, '7'); assert.equal(denied.phase, SESSION_PHASES.unauthorized); assert.equal(denied.message, 'У вас нет доступа к этому разделу'); }); test('auth-адаптер возвращает безопасное состояние при ошибке восстановления', async () => { const state = await loadAuthorizedSession(['STUDENT'], { restoreSessionImpl: async () => { throw new Error('Техническая причина не должна попасть в UI'); } }); assert.equal(state.phase, SESSION_PHASES.error); assert.equal(state.message, 'Не удалось восстановить сессию'); assert.doesNotMatch(state.message, /Техническая причина/); }); test('общий API-клиент сериализует JSON и сохраняет same-origin auth-транспорт', async () => { const calls = []; const result = await requestJson('/api/example', { json: { value: 'данные' }, method: 'POST' }, { fetchImpl: async (endpoint, options) => { calls.push({ endpoint, options }); return new Response(JSON.stringify({ saved: true }), { headers: { 'Content-Type': 'application/json' }, status: 200 }); } }); assert.deepEqual(result, { saved: true }); assert.equal(calls[0].endpoint, '/api/example'); assert.equal(calls[0].options.method, 'POST'); assert.equal(new Headers(calls[0].options.headers).get('Accept'), 'application/json'); assert.equal(new Headers(calls[0].options.headers).get('Content-Type'), 'application/json'); assert.deepEqual(JSON.parse(calls[0].options.body), { value: 'данные' }); }); test('общий API-клиент не выводит технический ответ сервера как пользовательскую ошибку', async () => { await assert.rejects( requestJson('/api/example', { errorMessage: 'Не удалось загрузить пример' }, { fetchImpl: async () => new Response(JSON.stringify({ message: 'Internal failure' }), { headers: { 'Content-Type': 'application/json' }, status: 500 }) }), error => { assert.ok(error instanceof ApiRequestError); assert.equal(error.message, 'Не удалось загрузить пример'); assert.equal(error.status, 500); assert.equal(error.data.message, 'Internal failure'); return true; } ); }); test('общие React-состояния имеют русские тексты и доступную семантику', () => { const loading = renderAsyncState({ kind: 'loading' }); const failure = renderAsyncState({ kind: 'error', message: 'Не удалось получить расписание', onRetry: () => {} }); const alert = renderFormAlert({ alert: { message: 'Проверьте поля', type: 'error' }, id: 'form-alert' }); assert.match(loading, /role="status"/); assert.match(loading, /Загружаем данные/); assert.match(failure, /role="alert"/); assert.match(failure, /Не удалось получить расписание/); assert.match(failure, /Повторить/); assert.match(alert, /id="form-alert"/); assert.match(alert, /Проверьте поля/); assert.match(renderErrorBoundaryContent(), /Рабочее содержимое/); });