# Backend unit-тесты

## Назначение

Unit- и service-тесты backend проверяют бизнес-логику без запуска HTTP API:

- доменные правила записи на лекции;
- преобразование сущностей в DTO;
- фильтрацию, пагинацию и связи курсов;
- дерево тегов;
- управление ролями и профилями пользователей.

Security-тесты авторизации находятся в том же тестовом проекте, но это отдельный интеграционный набор: они запускают API через `WebApplicationFactory` и проверяют HTTP-доступ к endpoint-ам.

## Где лежат файлы

- [EnrollmentSlotPolicyTests.cs](../backend/UniVerse.Api.Tests/Domain/EnrollmentSlotPolicyTests.cs) - правила лимита активных записей по уровню.
- [MappingExtensionsTests.cs](../backend/UniVerse.Api.Tests/Application/MappingExtensionsTests.cs) - маппинг доменных сущностей в DTO.
- [CourseServiceTests.cs](../backend/UniVerse.Api.Tests/Courses/CourseServiceTests.cs) - фильтры, пагинация и теги курсов.
- [TagServiceTests.cs](../backend/UniVerse.Api.Tests/Tags/TagServiceTests.cs) - фильтры тегов и построение дерева.
- [UserServiceTests.cs](../backend/UniVerse.Api.Tests/Users/UserServiceTests.cs) - статистика, роли, профили и список пользователей.
- [EndpointAuthorizationTests.cs](../backend/UniVerse.Api.Tests/Authorization/EndpointAuthorizationTests.cs) - security-тесты ролевого доступа к API.
- [ApiWebApplicationFactory.cs](../backend/UniVerse.Api.Tests/Helpers/ApiWebApplicationFactory.cs) - тестовый запуск API.
- [TestJwtFactory.cs](../backend/UniVerse.Api.Tests/Helpers/TestJwtFactory.cs) - генерация JWT для ролей в security-тестах.

Тестовый проект: [UniVerse.Api.Tests.csproj](../backend/UniVerse.Api.Tests/UniVerse.Api.Tests.csproj).

## Тестовый стек

- `xUnit` - test runner и assertions.
- `NSubstitute` - mock-объекты для сервисных зависимостей.
- `Microsoft.EntityFrameworkCore.InMemory` - изолированная InMemory БД для service-тестов.

Каждый service-тест создает отдельный `AppDbContext` с уникальным именем базы через `Guid.NewGuid()`, чтобы данные разных тестов не пересекались.

## Что покрыто

### EnrollmentSlotPolicy

Проверяется, что `GetLimitForLevel` выбирает последний подходящий threshold:

- уровни ниже первого правила получают базовый лимит;
- уровни между threshold используют предыдущий лимит;
- уровни выше последнего threshold используют максимальный лимит;
- публичный список `Rules` остается в ожидаемом порядке.

### MappingExtensions

Проверяется стабильность DTO-маппинга:

- роли пользователя сортируются одинаково в `UserDto`, `CurrentUserDto` и `UserAuthDto`;
- лекции корректно считают записи и используют fallback для отсутствующих navigation properties;
- отзывы переносят поля LLM-анализа;
- дерево тегов маппится рекурсивно.

### CourseService

Проверяется поведение без HTTP-слоя:

- совместная работа фильтров `Search`, `IsSynced`, `TagId`;
- корректные `TotalCount`, `Page`, `PageSize`, `TotalPages`;
- добавление связи курс-тег;
- ошибки при повторной связи или отсутствующем курсе/теге.

### TagService

Проверяется:

- фильтрация по `TagType` и `ParentId`;
- сортировка по имени;
- запрет создания дочернего тега без существующего родителя;
- построение вложенного дерева тегов.

### UserService

Проверяется:

- статистика пользователя, прогресс уровня и лимиты записей;
- `SetRolesAsync` удаляет дубли ролей;
- пустой набор ролей отклоняется;
- профили студента и преподавателя создаются и не дублируются;
- `GetAllAsync` фильтрует по поиску, активности и одиночной роли;
- пагинация пользователей идет в порядке `CreatedAt` по убыванию.

## Security-тесты авторизации

Security-тесты находятся в [EndpointAuthorizationTests.cs](../backend/UniVerse.Api.Tests/Authorization/EndpointAuthorizationTests.cs). Это интеграционные тесты, которые отправляют реальные HTTP-запросы в тестовый API через `ApiWebApplicationFactory`.

Они проверяют не бизнес-результат endpoint-а, а сам факт прохождения или блокировки авторизации:

- анонимный запрос к защищенному endpoint-у получает `401 Unauthorized`;
- запрос с неподходящей ролью получает `403 Forbidden`;
- запрос с подходящей ролью не получает `401` или `403`;
- публичные endpoint-ы из `AnonymousEndpoints` доступны без JWT и не возвращают `401` от middleware авторизации.

Таблица защищенных endpoint-ов задается в методе `AuthenticatedEndpoints`. Каждый кейс описывает:

- человекочитаемое имя сценария;
- HTTP-метод;
- URL;
- роль, которая должна пройти авторизацию;
- роли, которые должны получить `403`;
- опциональное JSON-тело запроса.

Для endpoint-ов, доступных любой авторизованной роли, используется обычная тестовая роль, чаще `Student`, и пустой список запрещенных ролей. Для endpoint-ов с несколькими разрешенными ролями добавляется отдельный кейс на каждую разрешенную роль, например `Admin` и `Teacher`.

JWT для ролей создаются через `TestJwtFactory.BearerHeader(role)`. Это позволяет проверять backend-авторизацию без Microsoft OAuth flow и без реального входа пользователя.

## Как обновлять security-тесты

При добавлении или изменении API endpoint-а нужно обновить `EndpointAuthorizationTests`:

1. Если endpoint требует авторизации, добавьте его в `AuthenticatedEndpoints`.
2. Укажите правильную роль или отдельные кейсы для нескольких ролей.
3. Для role-specific endpoint-а заполните `forbidden` ролями, которые должны получать `403`.
4. Если endpoint публичный, добавьте его в `AnonymousEndpoints`.
5. Для `POST`, `PUT`, `PATCH` endpoint-ов добавьте минимальное валидное тело запроса, чтобы тест дошел до авторизации и не падал на model binding раньше времени.

Security-тест считается успешным для правильной роли, если ответ не `401` и не `403`. Это намеренно: после авторизации endpoint может вернуть `404`, `400`, `409` или другой доменный ответ из-за тестовых данных, и это не является ошибкой проверки доступа.

## Как запускать

Из корня репозитория:

```bash
dotnet test backend/UniVerse.sln --no-restore
```

## Как добавлять новые unit/service-тесты

1. Размещайте тесты рядом с проверяемой областью внутри `backend/UniVerse.Api.Tests`.
2. Для сервисов с EF используйте InMemory `AppDbContext` с уникальным именем базы.
3. Мокайте только внешние зависимости и соседние сервисы через `NSubstitute`.
4. Не запускайте `WebApplicationFactory`, если проверяется не HTTP/auth behavior.
5. Покрывайте не только успешный сценарий, но и доменные ошибки: `NotFoundException`, `ConflictException`, `ForbiddenException`.

## Текущий baseline

После добавления unit/service-тестов и с учетом существующих security-тестов полный backend test suite проходит:

```text
Passed: 303, Failed: 0, Skipped: 0
```
