Three minutes of wave sessions with stream starts, play events and next batches alongside a steady suggest load; thresholds follow the documented p95 targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
7.8 KiB
Markdown
77 lines
7.8 KiB
Markdown
# Тестирование
|
||
|
||
## Пирамида
|
||
|
||
| Уровень | Где | Инструменты | Что покрываем |
|
||
|---|---|---|---|
|
||
| Unit | Go | `testing`, `testify`, табличные тесты | Политики прав (вся матрица из auth-and-roles.md), парсинг шаблонов путей и слияние с тегами, нормализация имён, подпись ссылок, расчёт `implicit_score`, рендер `write_template` и очистка имён, обратимость шаблон ↔ регэксп, учёт квоты при конкурентных резервах, переходы статусов загрузки |
|
||
| Unit | Python | `pytest`, `hypothesis` | Формулы скоринга, context_fit, MMR, бандит, обновление сессионного вектора, правила переранжирования |
|
||
| Unit | TS | `vitest` | `packages/core`: очередь, события (подсчёт `played_ms`, засчитанное прослушивание), single-flight refresh, офлайн-очередь |
|
||
| Компоненты | Web | `vitest` + Testing Library | Поиск (клавиатура, Enter / выбор), плеер, MoodPicker, формы админки |
|
||
| Компоненты | Mobile | `jest` + RNTL | Экраны входа, коллекции, диалог обновления |
|
||
| Интеграция | Go | `testcontainers-go` (Postgres + pgvector, Redis, Meilisearch) | Репозитории, миграции, сканер на тестовой библиотеке, API-хендлеры через `httptest` |
|
||
| Интеграция | Python | `testcontainers` | Чтение событий из Redis Stream, kNN в pgvector, ночной пайплайн на синтетических данных |
|
||
| Контракт | — | `schemathesis` против запущенного `api` | Ответы соответствуют `openapi.yaml`, нет 500 на фаззинге |
|
||
| E2E | Web | Playwright (Chromium + Firefox) | Сценарии ниже, на `deploy/compose.test.yaml` |
|
||
| E2E | Mobile | Maestro (эмулятор) | Вход, волна, фоновое воспроизведение, скачивание, офлайн — ночной прогон, не на каждый PR |
|
||
| Качество рекомендаций | Python | офлайн-оценка | NDCG / Recall на синтетическом датасете с известными «вкусами»; регрессия качества ломает тест |
|
||
|
||
## Тестовая музыка
|
||
|
||
Реальную музыку в репозиторий не кладём. `tests/fixtures/music/generate.sh` генерирует через `ffmpeg`
|
||
~200 коротких (5–20 с) файлов разных форматов (FLAC, MP3, Opus, M4A) с синусами / шумом разного темпа и громкости,
|
||
с полными, частичными и отсутствующими тегами, в нескольких структурах папок — для проверки шаблонов,
|
||
дублей, переименований, кириллицы в путях. Генерация детерминирована (фиксированные параметры), результат кэшируется в CI.
|
||
|
||
Для аудиоанализа — отдельный набор из нескольких клипов с известным BPM (метроном 90 / 128 / 170).
|
||
|
||
## E2E-сценарии (обязательные)
|
||
|
||
1. Первый запуск: вход bootstrap-админа → принудительная смена логина / пароля → главная.
|
||
2. Админ создаёт модератора; модератор создаёт пользователя; модератор **не может** создать модератора (UI и API).
|
||
3. Админ добавляет `local`-источник, задаёт два шаблона, предпросмотр, скан → треки появились в поиске.
|
||
4. Поиск: выпадающий список → переход к исполнителю; Enter → страница поиска.
|
||
5. Воспроизведение: запуск трека, перемотка, перезагрузка страницы → трек и позиция восстановлены.
|
||
6. Моя волна: запуск с занятием «Бегаю» → в выдаче треки с BPM из профиля; дизлайк → следующий трек, трек в «Дизлайках».
|
||
7. Коллекция: лайк → в «Лайках»; история содержит прослушанное.
|
||
8. Смена пароля пользователем → старые сессии разлогинены.
|
||
9. Переключение языка ru ↔ en.
|
||
10. Сброс пароля пользователю → обязательная смена при входе.
|
||
11. Очередь на добавление: модератор загружает папку из 5 файлов, где 1 — точная копия трека из библиотеки,
|
||
1 — тот же трек в другом формате → первый отброшен на предпроверке, второй — по отпечатку; оба не учтены в квоте;
|
||
оставшиеся 3 — `pending_review`. Админ правит альбом, одобряет → файлы появились в хранилище по `write_template`,
|
||
трек найден поиском; квота освобождена.
|
||
12. Квота: лимит 50 МБ, загрузка 80 МБ → часть файлов отклонена `uploads.queue_full`; модератор не может
|
||
одобрить собственную загрузку, а обычный пользователь — открыть загрузку вообще (UI и API).
|
||
|
||
## Правила
|
||
|
||
- Покрытие: Go и Python ≥ 70% по строкам для `internal/*/service` и `pipu_rec/*`; для `auth/policy` — 100% веток.
|
||
- Тест на каждый исправленный баг.
|
||
- Flaky-тесты помечаются и чинятся в течение недели, а не перезапускаются бесконечно.
|
||
- Нагрузочный smoke (k6, вручную перед релизом) — см. ниже.
|
||
|
||
## Нагрузочный smoke
|
||
|
||
`tests/load/smoke.js` (k6 ≥ 0.50), 3 минуты: 100 одновременных слушателей волны (старт волны, начало потока
|
||
каждого трека — первые 64 КБ, события прослушивания, следующая пачка с отзывами) и 20 запросов подсказок поиска
|
||
в секунду. Пороги — цели из [api.md](api.md) и [recommendations.md](recommendations.md#10-требования-к-производительности):
|
||
|
||
| Запрос | p95 |
|
||
|---|---|
|
||
| `GET /search/suggest` | < 50 мс |
|
||
| `POST /wave/sessions/{id}/next` | < 150 мс |
|
||
| `POST /wave/sessions` | < 300 мс |
|
||
| начало потока (`Range: bytes=0-65535`) | < 300 мс |
|
||
| `POST /events` | < 100 мс |
|
||
|
||
Ошибок < 1%. k6 завершается с ненулевым кодом, если порог не выполнен.
|
||
|
||
```bash
|
||
k6 run -e BASE_URL=https://music.example.com -e PIPU_USER=loadtest -e PIPU_PASSWORD=… tests/load/smoke.js
|
||
```
|
||
|
||
Пользователь — отдельный обычный (`role=user`): тест наполняет его историю и лайки. Нагрузку можно менять:
|
||
`-e LISTENERS=20 -e SUGGEST_RPS=5 -e DURATION=1m`. Запускать с машины в той же сети, что и сервер, —
|
||
иначе в цифрах будет задержка канала.
|