Тестирование BirdLense Hub
Безопасность: если пароль настроек попал в лог или чат — смените его в Settings → General.
Placeholders:
YOUR_HOST— hostname или IP;YOUR_SSH_HOST— host из~/.ssh/config;YOUR_REMOTE_DIR— путь на сервере (например/opt/birdlense).
Тесты (разработка, CI)
На GitHub (PR/push в main и dev) workflow CI запускает:
ui-build—npm ci+ production-сборка SPAdocs— MkDocs--strict+ проверка версии в докахdocker-tests—docker compose build+make test+make test-web(как ниже)
Job docker-tests входит в required checks ruleset Protect на main (см. GITHUB_SETUP_GH.ru).
Unit-тесты (processor)
cd app && make test
Запускает unittest для processor в Docker (detection strategy, decision maker). Нужны ultralytics, ncnn — тесты выполняются в контейнере.
Память (RAM): тесты процессора поднимают YOLO/NCNN в контейнере и могут занять несколько ГБ ОЗУ. На слабом VPS или ноутбуке с лимитом памяти
make test(или jobdocker-testsв CI) может завершиться SIGKILL / код 137 (OOM). Лучше ≥8 ГБ свободно под Docker, закрыть тяжёлые приложения или гонять тесты на GitHub Actions, а не только локально.
API-тесты (web)
cd app && make test-web
Запускает pytest для web API в Docker (health, status, settings, feed, cameras).
Перед первым запуском: make build. Тесты выполняются в контейнере.
В том числе TestVerifyPasswordRateLimit: после пяти неверных паролей к POST /api/ui/settings/verify-password за 60 с — ответ 429 и заголовок Retry-After, отдельные счётчики по X-Real-IP, сброс при успешном входе (ACCESS_CONTROL.ru).
Реестр видов (species registry) — quality gate
В CI (job openapi-contract) запускается smoke-набор:
cd app
python -m pytest web/tests/test_species_registry.py -q
Проверяется:
- доступность API реестра (
seed,backfill,health, async status); - целостность покрытия (
coverage_percent = 100.0после backfill); - отсутствие регресса в базовой нормализации.
Операционный контроль на инстансе:
# health snapshot реестра (требует admin-доступ)
curl -s http://YOUR_HOST:8085/api/ui/system/species-registry/health
# ручной backfill (если нужно вне автозапуска)
curl -s -X POST http://YOUR_HOST:8085/api/ui/system/species-registry/backfill \
-H "Content-Type: application/json" \
-d '{"dry_run": false}'
Покрытие (coverage)
cd app && make test-coverage
Запускает processor + web тесты с измерением покрытия, выводит отчёт в консоль.
cd app && make test-report
То же + генерирует HTML-отчёт в app/htmlcov/index.html.
Конфигурация: app/.coveragerc (исключены tests, app_config, scripts).
Приоритет для расширения покрытия: ui_system_routes, retention_service, visit_processor, processor_routes.
E2E-тесты (Playwright)
E2E проверяют UI и API на работающем экземпляре.
Запуск:
-
Запустите приложение:
bash cd app && make start -
Запустите E2E:
bash cd app && make test-e2e -
Против сервера (YOUR_HOST:8085):
bash cd app && BASE_URL=http://YOUR_HOST:8085 make test-e2e -
Пароль — если включена защита настроек (
general.settings_password), задайтеE2E_SETTINGS_PASSWORD=xxxдля полного прогона. Без него тесты Settings иGET /api/ui/settingsпропускаются (skip).
Что проверяют E2E:
- smoke.spec.ts: загрузка главной, навигация, Settings, Live
- api.spec.ts:
/api/ui/health,/api/ui/status,/api/ui/settings,/api/ui/cameras,/api/ui/weather,/api/ui/feed/dispense - settings.spec.ts: форма настроек, секции Video/MQTT, Feed
- migration.spec.ts: фильтр «С года» / From year на странице Migration (и сброс на «Все годы»); при пустой БД без таблицы миграции тесты помечаются skip
Только API-тесты (без браузера):
cd app/e2e && npm test -- --grep @api
Отладка одного файла / UI mode:
cd app/e2e && npx playwright test tests/migration.spec.ts
cd app/e2e && npx playwright test --debug tests/migration.spec.ts
CI по расписанию: workflow E2E (Playwright) (.github/workflows/e2e-scheduled.yml) — раз в неделю и по workflow_dispatch; в ruleset не required — для отлова регрессий без Playwright на каждом PR.
Расширение покрытия: дополнительные сценарии (полный логин, углубление в таймлайн, коррекция видов) добавляются по мере необходимости вместе с фичей; фиксируйте в PR и в этом документе — отдельный «зонтичный» issue не обязателен.
Статус MQTT и ESPHome
Эндпоинт /api/ui/status возвращает:
mqtt:ok|error|not_configured|not_usedesphome:ok|error|not_configured|not_used
Когда feed.source = mqtt, проверяется подключение к MQTT-брокеру.
Когда feed.source = esphome, проверяется доступность URL кормушки.
Индикаторы отображаются в навигации (StatusIndicator).
Проверка после деплоя
Проверка работоспособности распознавания без птиц (например, после ночного деплоя).
Важно: распознавание могло ломаться из‑за
PROCESSOR_SECRET(токен не подставлялся в.env). Исправление в deploy.sh — проверка обязательна после деплоя (см. раздел «403 (PROCESSOR_SECRET)»).
1. Сразу после деплоя (перед сном)
API и статус:
curl -s http://YOUR_HOST:8085/api/ui/health
curl -s http://YOUR_HOST:8085/api/ui/status
Ожидаемо: processor: ok, web: ok, motion_source: opencv или frigate. При motion_source: frigate и feed.source ≠ mqtt поле mqtt показывает статус aggregator (ok при processor ok).
Логи процессора:
- UI: System → Логи процессора (последние 100–200 строк)
- SSH:
bash ssh YOUR_SSH_HOST "tail -100 YOUR_REMOTE_DIR/app/data/processor.log"
Проверить:
- модели загружены (NCNN, YOLO);
- Motion: Frigate или Motion: OpenCV;
- нет API request failed, 403, Connection refused;
- нет Traceback, Error, Exception.
403 (PROCESSOR_SECRET)
Если в логах есть API request failed ... 403 — процессор не проходит проверку API.
Проверка: на сервере cat YOUR_REMOTE_DIR/app/.env | grep PROCESSOR_SECRET — должно быть PROCESSOR_SECRET=hex... (32 символа), а не PROCESSOR_SECRET=${PROCESSOR_SECRET}. Если буквально — баг в deploy.sh (исправлено: двойные кавычки при записи).
Серые иконки (Video, MQTT, YOLO)
Если статус серый при работающем подключении — heartbeat не доходит до API.
Диагностика: /api/ui/status/debug теперь требует админскую сессию настроек. Проверяйте его из браузера после входа администратором и разблокировки Settings либо передавайте cookie сессии в HTTP-клиенте.
last_heartbeat: null— процессор не шлёт heartbeat (проверить логи:Heartbeat failed,403)last_heartbeat.updated_atстарый — процессор упал или heartbeat не проходитprocessor_secret_configured: false— PROCESSOR_SECRET не задан в контейнере- повторяющиеся
403на heartbeat в логах процессора — несовпадение PROCESSOR_SECRET
2. Провокация события (тест полного пайплайна)
Запуск процессора на существующей записи с птицей — симулирует детекцию и создаёт новую запись в UI.
Автоматический скрипт:
./scripts/test-deploy-recognition.sh
Скрипт:
1. Находит последнюю запись video.mp4 в app/data/recordings/ (или берёт путь по VIDEO_ID через API);
2. Запускает процессор с --fake-motion true и MQTT_CLIENT_ID=birdlense_aggregator_test (избегает конфликта с основным процессором);
3. Обрабатывает видео, запускает YOLO;
4. При обнаружении птицы — создаёт новую запись и сохраняет в API.
Тест на конкретном видео по ID:
VIDEO_ID=37 ./scripts/test-deploy-recognition.sh
Ручной запуск:
# Найти запись (на сервере)
ssh YOUR_SSH_HOST "find YOUR_REMOTE_DIR/app/data/recordings -name 'video.mp4' | tail -1"
# Запустить процессор (подставить путь)
ssh YOUR_SSH_HOST "docker exec birdlense python /app/processor/src/main.py /app/data/recordings/YYYY/MM/DD/HHMMSS/video.mp4 --fake-motion true"
Или локально (если контейнер запущен):
cd app
VIDEO=$(find data/recordings -name 'video.mp4' 2>/dev/null | tail -1)
docker exec birdlense python /app/processor/src/main.py "/app/$VIDEO" --fake-motion true
Результат: В UI должна появиться новая запись (дата/время = момент запуска). В логах: Detection ... has N track frames, create_video без ошибок.
Примечание: Скрипт запускает процессор в том же контейнере (docker exec). При args.input (видеофайл) процессор не создаёт Go2RTC-стримы — конфликта портов нет. Используется отдельный MQTT client_id (birdlense_aggregator_test), чтобы не конфликтовать с основным процессором.
Ограничение: Скрипт проверяет только YOLO и цепочку процессор → API. Не проверяет: Frigate MQTT (триггер записи), BirdNET MQTT (аудио-детекции), Go2RTC (подключение к потоку).
3. Тест MQTT (Frigate, BirdNET)
Проверка приёма событий от Frigate и BirdNET. Требует: процессор запущен, MQTT брокер доступен.
Что смотреть в логах:
| Событие | Строка в processor.log |
|---|---|
| MQTT подключён | MQTT aggregator connected |
| Frigate триггер | Frigate trigger: camera=X label=Y sub_label=Z -> recording |
| Frigate пропущен | Frigate event skipped (no trigger): camera=... \| camera_filter=... label_filter=... |
| Frigate не подключён | Frigate MQTT not connected, falling back to OpenCV |
Ручная публикация тестового события:
Frigate (триггер записи — процессор начнёт запись с Go2RTC):
# Брокер и порт — из mqtt.broker в конфиге или MQTT_BROKER
# Топик по умолчанию: frigate/events
# camera — должен совпадать с video.cameras[].id или frigate_camera_filter (пусто = любая)
# label/sub_label — bird или Bird (frigate_label_filter)
mosquitto_pub -h BROKER -p 1883 -t "frigate/events" -m '{"after":{"camera":"birdbox","label":"bird","sub_label":"Bird","top_score":0.95,"frame_time":'$(date +%s)'}}'
Подставьте BROKER (IP брокера) и birdbox — имя камеры из video.cameras[].id или motion.frigate_camera_filter. Если фильтр пуст — подойдёт любая камера.
После публикации в логах должно появиться Frigate trigger: camera=... label=bird sub_label=Bird -> recording, и начнётся запись.
BirdNET (событие для слияния с YOLO; само по себе запись не запускает):
mosquitto_pub -h BROKER -p 1883 -t "birdnet" -m '{"Common_Name":"Northern Cardinal","Confidence_Score":0.85}'
При аутентификации MQTT добавьте -u USER -P PASS.
Проверка статуса MQTT:
curl -s http://YOUR_HOST:8085/api/ui/status
При motion.source: frigate поле mqtt должно быть ok. Если not_used — MQTT не используется для motion.
4. Утром (если ночью не было птиц)
Если записей нет — это нормально. Проверить:
- Status:
processor: ok(heartbeat есть); - Логи: нет ошибок, падений, 403;
- Activity: в System → Activity есть активность за ночь.
5. Диагностика по логам
| Симптом | Что искать |
|---|---|
| 403 | API request failed + 403 |
| Падение | Traceback, Error, Exception в processor.log |
| Модели не загружаются | Loading ... ncnn_model + Error |
| Нет Go2RTC | video.go2rtc_url не задан, waiting_cameras |
| Нет motion | Frigate MQTT not connected, falling back to OpenCV |
| Frigate не триггерит | Frigate event skipped — проверить camera_filter, label_filter |
| MQTT отключён | MQTT aggregator connect failed, disconnected |
6. Минимальный чеклист перед сном
curl http://YOUR_HOST:8085/api/ui/status→processor: ok,mqtt: ok(если motion.source: frigate)- PROCESSOR_SECRET:
ssh YOUR_SSH_HOST "grep PROCESSOR_SECRET YOUR_REMOTE_DIR/app/.env"→ hex-значение, не${PROCESSOR_SECRET}. DEPLOY_URL в deploy.local.sh — URL сервера (не localhost), иначе health check FAIL. - System → Логи процессора → последние 50 строк без ошибок, нет 403
./scripts/test-deploy-recognition.sh→ новая запись в UI (проверка YOLO)- При Frigate:
mosquitto_pubтестового события → в логахFrigate trigger: ... -> recording
Если всё зелёное — можно оставлять на ночь.
7. Telegram: детекции не приходят, перезагрузка — приходит
Причина: Сообщение «App is UP!» отправляет web при старте. Детекции отправляет processor через POST /api/processor/notify/detections. Если processor не доходит до API или получает 403 — уведомления не уйдут.
Проверки:
- Тест Telegram — Настройки → Уведомления → «Тест Telegram». Если приходит — token, chat_id, enable_notifications в порядке. Проблема в цепочке processor → API.
- PROCESSOR_SECRET — в логах процессора:
Notify species failed 403 (check PROCESSOR_SECRET in app/.env). На сервере:cat YOUR_REMOTE_DIR/app/.env | grep PROCESSOR_SECRET— должно быть hex-значение. - Детекции есть? — В логах:
Processing stopped. Video Result: [...]— если пусто, YOLO/MQTT не нашли птиц. Уведомления шлются только приvideo_detections > 0. - Исключения —
general.notification_excluded_species— виды из списка не уходят в Telegram.
8. Публичная галерея (opt-in)
- Локальный приёмник: из корня репозитория
docker compose -f docker/gallery-test/docker-compose.yml up -d→http://localhost:8086/api/upload(см. CONFIGURATION.ru → Gallery). - BirdLense + тестовая галерея в одной сети Docker:
cd app && docker compose -f docker-compose.yml -f docker-compose.gallery-test.yml up -d, в настройках Gallery указатьupload_url:http://gallery-test:5000/api/upload(имя сервиса, неlocalhostхоста). - Триггер: после обработки клипа с video-детекциями и frames в треке web шлёт POST в фоне. Логи:
Gallery upload:или ошибкиGallery upload failed/Gallery upload thread failed. - Автотесты:
app/web/tests/test_gallery_upload.py— в CI черезmake test-web.
См. также: INSTALL, CONFIGURATION.