ef2fd39508
Backend CI / build-and-test (push) Successful in 48s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 5s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 24s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 2s
144 lines
9.5 KiB
Markdown
144 lines
9.5 KiB
Markdown
# 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
|
||
```
|