[+!] all but small

some small modifications, tests added
This commit is contained in:
2026-10-07 22:42:46 +03:00
parent fac12b2ea8
commit c16ed7915a
21 changed files with 1190 additions and 100 deletions

View File

@@ -1,70 +1,150 @@
# Клиент и тесты сервера
# Клиент и тесты SIRMENT/core
`src/.test/client_test.py` используется и для ручного запуска, и в интеграционных
тестах. Он ожидает `token_ask`, отвечает `token_answer`, ожидает `handshake_ask`,
отвечает `handshake_answer` и подтверждает состояние `air` ответом на heartbeat.
Затем ручной запуск отправляет десять пакетов телеметрии с heartbeat между ними.
Каждое ожидание ответа имеет таймаут; импорт файла не запускает клиент.
Общий клиент `examples/client.py` используется для ручного запуска и настоящих
TCP-интеграционных тестов. Fixture загружает его по пути относительно
репозитория, без запуска CLI при импорте. Папка `src/.test` игнорируется Git и
не нужна для выполнения тестов из чистого клона.
После запуска сервера:
## Установка и запуск
```bat
src\.test\start_client.bat rover --count 10 --interval 1
Нужен Python 3.11 и зависимости тестов:
```sh
python -m pip install -r req.txt
python -m pip install -r req-dev.txt
python -m pytest -q --cov=src --cov-branch --cov-report=term-missing --cov-report=xml
```
Для запроса восстановления укажите старые значения из вывода клиента:
Для окружения sites на Windows, из корня репозитория:
```bat
src\.test\start_client.bat rover --old-token 123 --old-upf 456
D:\_Programs\anaconda\envs\sites\python.exe -m pip install -r req-dev.txt
D:\_Programs\anaconda\envs\sites\python.exe -m pytest -q --cov=src --cov-branch --cov-report=term-missing --cov-report=xml
tests\start_tests.bat
```
Сейчас сервер не сообщает результат восстановления отдельным ответом.
Клиент сообщает только факт отправки запроса; heartbeat подтверждает новый
handshake, но сам по себе не доказывает успешное восстановление.
Запуск всех тестов из корня `server`:
Батник переходит в корень репозитория независимо от текущей папки. Без аргументов
он запускает весь набор с покрытием; с аргументами передаёт их pytest:
```bat
D:\_Programs\anaconda\envs\sites\python.exe -m pytest -q
tests\start_tests.bat tests/test_router.py -q
```
Также можно запустить `tests/start_tests.bat`. Только проверки самого клиента:
Отдельные группы:
```bat
tests\start_tests.bat tests/test_client_protocol.py -q
```sh
python -m pytest tests/test_client_protocol.py -q
python -m pytest tests/test_session.py tests/test_session_manager.py tests/test_router.py -q
python -m pytest tests/test_transport_boundary.py -q
python -m pytest tests/test_entrypoint.py -q --cov=src --cov-branch
```
Интеграционные тесты проверяют подключение, метаданные портов, телеметрию,
heartbeat, немедленное закрытие транспорта и сохранение отключённой сессии,
отключение после пяти команд в неправильном состоянии протокола,
перенос данных и маршрутов в обе стороны, отказ для активной/отсутствующей
сессии и неверного `upf`, а также однократность восстановления.
Новые регрессионные проверки намеренно не помечены `xfail` и не отключены.
С текущими ошибками реализации весь набор возвращает ненулевой код. После
исправления соответствующей ошибки тот же тест должен пройти без изменения
ожидаемого поведения. Результаты и пробелы проверки — в [REPORT.md](REPORT.md).
Дополнительные проверки:
## Ручной клиент
- `test_session.py`: все этапы подключения, истечение и продление дедлайна,
остановка воркера, ошибки последовательности и повторный handshake.
- `test_session_manager.py`: обработка событий, истечение окна восстановления,
однократность переноса и безопасность старой отложенной очистки.
- `test_router.py`: перенос ссылок в обе стороны, несвязанные маршруты,
окончательное удаление связей и данные отправителя при пересылке.
- `test_source_validation.py`: синтаксис исходников и docstring каждого теста.
После запуска сервера через `python src/main.py`:
В `test_client_run.py` также проверяются настоящие TCP-таймауты в состояниях
`new/token/ready/air` и сохранение соединения после продления heartbeat.
Краткое описание каждой проверки находится в docstring её функции.
```sh
python examples/client.py rover --host 127.0.0.1 --port 9000 --count 10 --interval 1
python examples/client.py rover --old-token 123 --old-upf 456
```
Интеграционные тесты запускают настоящий TCP-обработчик, сессии и роутер на
свободном локальном порту. Маршруты задаются явно внутри теста: правила API/GUI
не предполагаются. Изолированные проверки менеджера используют наблюдатель
вызовов роутера; настоящий роутер дополнительно проверяется отдельно.
На Windows: `examples\start_client.bat rover --count 10 --interval 1`.
Клиент ждёт `token_ask`, отвечает `token_answer`, ждёт `handshake_ask`, отвечает
`handshake_answer` и подтверждает состояние `air` ответом на heartbeat.
Ручной запуск отправляет телеметрию с heartbeat между пакетами. Ожидания ответов
ограничены таймаутом. Сервер пока не сообщает результат восстановления отдельным
пакетом; клиент сообщает только факт отправки запроса.
В сценариях с таймерами подменяются только часы и ожидания времени в памяти.
Тест явно продвигает время и освобождает нужное ожидание, поэтому реальные
паузы в 5/60 секунд не нужны. Методы сессии, её воркер, обработчики менеджера
и маршрутизация не подменяются. Неопределённые имена часов не добавляются
тестами: такие ошибки остаются видимыми. Фоновые задачи завершаются или
отменяются при выходе из каждого сценария; ошибки воркеров не скрываются.
## Что проверяется
`fixtures/client_run_expected.json` оставлен как исторический эталон старого
сценария. Новые тесты проверяют ответы протокола и состояние сервера, а не логи.
| Файл | Сценарии |
| --- | --- |
| `test_client_protocol.py` | Порядок handshake клиента, буферизация данных до heartbeat, таймауты и ошибочные ответы |
| `test_client_run.py` | Настоящий TCP, метаданные портов, телеметрия, heartbeat, EOF, таймауты всех этапов, восстановление в обе стороны, отказ переноса и однократность |
| `test_session.py` | Дедлайны 60/5 секунд, собственный watcher, переходы протокола, retry handshake, последние данные, запрет новых отправок в stop/migrated, обработка данных во время фоновой отправки |
| `test_session_manager.py` | События, срок восстановления 60 секунд, истечение окна, повторный close после удаления, ошибка writer.wait_closed через настоящий Connection.close, безопасность старой очистки после реконекта |
| `test_router.py` | Исходящие/входящие связи, самоссылки, несвязанные узлы, удаление ссылок и данные отправителя |
| `test_transport_boundary.py` | Отключение/очистка плохих пакетов, лимит неверных команд, status True/False/Other, auth робота, произвольные JSON-данные без автокаста, heartbeat, TCP-фрейминг, watcher/EOF, фоновые сетевые ошибки |
| `test_entrypoint.py` | Настоящие main.py/TCP.run в отдельном процессе, handshake, heartbeat и завершение процесса |
| `test_source_validation.py` | Синтаксис src/examples и краткие docstring каждого теста |
Некорректные клиенты проверяются одновременно с исправным соединением:
закрытие одного транспорта не должно мешать heartbeat и телеметрии другого.
Локальная ошибка разбора пакета допускается, если транспорт закрывается,
запись и маршруты удаляются после окна восстановления, а другой клиент работает.
Ожидаемое исключение разрешается только для проверенного обработчика плохого
клиента; ошибки остальных задач продолжают проваливать тест.
`connection.payload.token` хранится в метаданных `auth` как токен робота.
Отсутствующий/null токен не мешает подключению; whitelist роутера пока не
реализован. Это поле отдельно от UID/upf восстановления в `token_answer`.
По согласованному протоколу отказ handshake означает только JSON `false`:
`0`, `null`, пустые строка/массив/объект означают успех. Тесты не требуют boolean.
Для status оставлены три представителя: True, False и Other = 0. Значение 0
отличает буквальный false от ложного значения Python без перебора всех типов.
Payload команд, читающих поля объекта, проверяется на безопасное отклонение,
если его нельзя разобрать. Содержимое heartbeat не используется, поэтому
необъектное значение разрешено. Для heartbeat без поля payload допустимы
обработка или локальное отключение с очисткой. Полезная нагрузка данных
сохраняется и маршрутизируется без преобразования JSON-значения в словарь.
## Метод проверки и завершение задач
Интеграционные тесты запускают настоящие TCP-обработчик, Session, SessionManager
и Router на локальном порту, выбранном ОС. Моки сервера не используются.
Маршруты задаются явно в тестах: правила будущих API/GUI не предполагаются.
Изолированные тесты менеджера используют наблюдатель вызовов роутера, а сам
Router проверяется отдельно.
В проверках таймаутов подменяются только монотонные часы и ожидания времени в
памяти. Тест освобождает выбранный `sleep` явно; реальные паузы в 5/60 секунд
не нужны. Основные методы сервера не подменяются. Ошибки транспорта вводятся
локально на одном реальном connection. Искусственная передача Python set в
сериализацию исключена: она не воспроизводит текущий путь данных из JSON.
Гонка watcher/TCP-finally проверяет одно закрытие и одну очистку и допускает
как ожидание очистки вызывающей корутиной, так и отдельную задачу очистки.
Ошибка закрытия воспроизводится на `writer.wait_closed` и проходит через
настоящий `Connection.close`; удаление записи и маршрутов должно состояться.
Уже существующий тест старой очистки после реконекта сохранён.
Фоновые сетевые исключения должны быть видны с исходным исключением; стандартное
логирование asyncio, включая `Task exception was never retrieved`, допускается.
Тест не требует немедленного disconnect от одной ошибки отдельной задачи:
закрытие проверяется через настоящий EOF. При разрыве во время отправки
допускаются завершение с сетевой ошибкой или отмена задачи; транспорт закрывается
один раз, другое соединение работает. Отмена уже запланированных отправок при
смене состояния не вводится как отдельное обязательное правило.
Фикстуры и контекст сервера завершают или отменяют созданные задачи, закрывают
клиентов и listener и проверяют исключения задач. Ожидания ограничены временем;
ошибка не превращается в зависший тест.
## Точка входа и покрытие дочернего процесса
В текущем `main.py` адрес/порт заданы напрямую, поэтому процессный тест запускает
`tests/entrypoint_runner.py`, который выполняет настоящий файл через `runpy`.
Обёртка перенаправляет только адрес и порт вызова `asyncio.start_server` на
`127.0.0.1:0` и вызывает оригинальную функцию. Код main.py, TCP.run, handshake,
сессий, маршрутизации и фоновых отправок не заменяется.
Процесс запускается из другой текущей папки. После проверки клиент закрывается,
а команда по stdin отменяет главную корутину. Это позволяет завершить процесс
штатно и сохранить покрытие, включая Windows. При зависании есть принудительное
завершение. Тест не доказывает настраиваемость порта самим production CLI:
такого интерфейса пока нет.
`pytest-cov` и `coverage` находятся в `req-dev.txt`. `.coveragerc` включает
покрытие ветвей и `patch = subprocess`, чтобы учитывать main.py/TCP.run из
дочернего процесса. Это [рекомендованный механизм pytest-cov 7](https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html).
Порог 100% не установлен; ошибки поведения не скрываются высоким процентом.
`coverage.xml` и `.coverage*` — генерируемые, игнорируемые Git результаты.
`fixtures/client_run_expected.json` сохранён как исторический эталон старого
сценария. Нынешние тесты проверяют протокол и состояние сервера, а не этот лог.