Пункты 3, 10 и 11 матрицы и тест саундборда падали не на продукте, а на
инфраструктуре: ICE через TURN на VPS не поднимается (STUN отвечает, но все
ICE-пары `failed` с `requestsReceived: 0`), и проверка трижды ждала кнопку
входа, после чего падал весь прогон.
В support.ts добавлен общий помощник joinVoiceIfPossible/joinVoiceOrSkip:
- три попытки, как раньше, но при уже показанной клиентом ошибке повтор не
делается, а ожидание ограничено 25 секундами;
- при неудаче проверяется серверная часть входа (POST .../voice/join →
токен LiveKit): живой сервер при мёртвом ICE — блокер среды, проверка
скипается с точной причиной; нерабочая ручка — дефект продукта, тест
падает. Проба ограничена по времени, а ответ 429 считается признаком
живого сервера (лимит частоты к медиапути отношения не имеет);
- п. 11 при недоступном голосе не отменяется: голосовой мьют пропускается
с аннотацией и предупреждением в лог, а тайм-аут, кик, возврат сервера в
рейку и бан по-прежнему проверяются.
Три локальные копии joinVoice заменены одним помощником.
Прогоны на стенде (образ с HEAD): матрица 18 passed / 0 failed / 3 skipped,
§11.5 — 8 passed, два прогона подряд совпали; без правки было 3 failed.
§11.5 требует, чтобы каждое мутирующее действие писалось в журнал сервера
с `actor.instance_admin=true`, но у PATCH /guilds/{id},
PATCH /guilds/{id}/members/{user_id} и у ручек комнат вызова `recordAudit`
не было: переименование сервера, смена ника, тайм-аут и создание, правка и
удаление комнаты не оставляли следа ни у владельца, ни у инстанс-админа.
- guild.update, member.update, member.timeout (в причине — срок тайм-аута),
channel.create, channel.update, channel.delete;
- Go-тест TestAuditCoversGuildMemberAndChannelChanges проверяет записи на
чужом сервере и флаг actor_instance_admin, а также что действие владельца
этим флагом не помечается;
- e2e 19b в instance-admin.spec.ts включён вместо test.fixme с диагностикой.
Проверки: go test -tags sqlite_fts5 -race -count=1 ./internal/... — ok;
make go-lint — 0 issues; живьём на стенде тест 19b проходит.
Проверки, которые в матрице 11.6 обходились по REST, теперь идут через
интерфейс (AGENT.md 7.13, 7.15, 11.6).
- 25: 🔗/⚙/+ видны владельцу и не видны обычному участнику, а после выдачи
роли с правами управления появляются без перезагрузки;
- 26: статус комнаты и медленный режим появляются в шапке по событию и
пропадают при снятии;
- 27: саундборд — панель открывается в голосовой комнате, проигрывание
уходит на сервер (200), звук играет локально (счётчик вызовов
`Audio.play`, подпись «включил звук»). Если медиапуть LiveKit не
поднялся, тест скипается с причиной: это внешняя зависимость;
- 28: счётчик непрочитанных в сайдбаре и явная фиксация того, что центра
уведомлений в клиенте нет (заглушка — только переключатель системных
уведомлений в настройках).
Сценариев личных серверов и личных бесед в матрице 11.6 не было
(AGENT.md 7.2, 7.8, 7.9). Новый спек идёт под тем же флагом
`GLCHAT_E2E_REALTIME=1` и тем же стилем: два браузера, `support.ts`,
`trackLoads`, уборка в `finally`.
- 21: личный (непубличный) сервер — вход по ссылке-приглашению в браузере,
сервера нет в каталоге, вход без приглашения 403, вошедший видит историю;
- 22: DM 1:1 — непрочитанное в списке бесед, история, «печатает», ответ из
интерфейса, правка и удаление без перезагрузки;
- 23: заявка в друзья и её принятие кнопкой в интерфейсе;
- 24: блокировка из списка друзей — писать нельзя в обе стороны
(`dm.blocked`), беседа не открывается, разблокировка возвращает переписку.
Приглашение (1 на прогон) создаёт владелец: суточная квота — 10 на
пользователя (AGENT.md 8.6).
Чек-лист §11.5 проверялся отдельными скриптами и живьём, но в спеках его
не было. Новый файл (`GLCHAT_E2E_INSTANCE=1`) работает на сервере, где
админ не участник: владелец — `rtprobe`, участник — `rtprobe2`.
- 14: видимость — чужой приватный сервер в `GET /instance/guilds` и в
списке серверов админа, комнаты, история, поиск, участники, роли, баны,
инвайты, эмодзи, звуки, вебхуки, аудит; чужие DM по-прежнему 404;
- 15: управление без 403 — имя сервера, ник владельца, роли (создание,
поднятие выше своей, выдача и снятие), комнаты; аудит помечает действия
`actor_instance_admin=true`;
- 16: модерация — бан с причиной и автором, разбан, кик, тайм-аут; попытки
владельца кикнуть/забанить/замутить/лишить роли админа дают 403
`instance.admin_protected`;
- 17: обход лимитов — slowmode, приватность комнаты, лимит участников
голосовой комнаты;
- 18: UI — админ входит в чужой приватный сервер из рейки без инвайта,
пишет в приватную комнату, а участник видит переименование и кик без
перезагрузки;
- 19: аудит — прежние записи не затираются; 19b (`test.fixme`) фиксирует
пробел: переименование сервера, ник и комнаты в аудит не пишутся;
- 20: безопасность не обходится — step-up на свежей сессии, чужой Origin,
лимит реакций 429.
Уборка тестовых серверов — в `finally` от имени админа (удаление чужого
сервера требует step-up).
Новые спеки (§11.5, личные серверы и шапка сервера) используют тот же
набор шагов, что матрица 11.6, но её помощники лежали внутри
`realtime.spec.ts`. Вынес в `support.ts`, чтобы не копировать их в третий
раз: сессии и лимит входов, повтор при 429, step-up (в том числе с 2FA
админа), подготовка сервера и уборка, комнаты, роли, сообщения, звуки,
личные беседы, друзья и блокировки.
Пункт 12 включён: клиент уходит на экран входа при отзыве сессий, в том числе
когда открыт сервер (дефект исправлен в 751ecc6), а смена пароля на сервере
больше не рвёт соединение текущего устройства (05409d0).
Пункт 11 теперь проверяет то, ради чего он написан: после кика и повторного
входа плитка сервера возвращается в рейку без перезагрузки (правка клиента в
ea1b4a7). Возврат пароля пробного участника перенесён в finally: падение теста
не оставляет постоянный аккаунт с временным паролем, а неудачный возврат
сообщается явной проверкой.
Прогон: 13 passed (2,9 мин), лог — build/e2e-realtime-run4.log.
Приглашения ограничены 10 на пользователя за 24 ч (AGENT.md 8.6), поэтому
проверка инвайтов создаёт отдельного свежего владельца сервера: у постоянных
пробных аккаунтов квота выгорает за серию прогонов. Прогон: 1 passed.
Два спектакля на общем модуле `web/e2e/support.ts` (запуск только с
`GLCHAT_E2E_REALTIME=1`):
- `realtime.spec.ts` — пункты 1–7: роли и права, приватная комната по роли,
профиль и ники, оформление сервера, эмодзи/звуки/косметика, инвайты, комнаты;
- `realtime-social.spec.ts` — пункты 8–13: сообщения, непрочитанные и
упоминания между устройствами, presence/typing/голос, модерация, два
устройства, RESUME после реконнекта.
Владелец действует по REST, второй браузер видит результат через Gateway;
полные загрузки документа запрещены и проверяются `trackLoads`. Матрица нашла
четыре дефекта продукта (исправлены в `d241714` и `7ec9dbf`).
- invalidateCurrentUser перечитывает профиль, а не только удаляет кэш: без
запроса AuthGuard не видит 401 и второе устройство остаётся в приложении
после logout-all или смены пароля (AGENT.md 11.6, пункт 12);
- сброс сессии и уход из последнего сервера инвалидируют список серверов:
пустой снапшот подставлял устаревший REST-ответ, и сервер возвращался в
рейку (видно и после кика);
- joinGuild проверяет бан сервера: публичный сервер больше не обходится
нажатием «Войти» вместо приглашения (AGENT.md 7.17, 7.20), тест расширен;
- e2e/voice-ten приведён к линту, console.log разрешён в e2e (диагностика
прогона), артефакты Playwright исключены из prettier.
- cmd/loadgen: держит заданное число WS-клиентов Gateway, шлёт сообщения с
нужной частотой, измеряет задержку доставки (p50/p95/p99/max), ответы API,
разрывы соединений и пишет отчёт в JSON;
- glchat create-user (CLI + обёртка): создаёт аккаунты в обход выключенной
регистрации, пакетно (--count/--prefix), при --sessions сразу выдаёт сессии
и складывает учётные данные в файл 600 — иначе сотня клиентов не сможет
войти из-за лимита попыток по IP;
- auth: CreateUserByOperator и IssueSessionForOperator с тестом;
- web/e2e/voice-ten.spec.ts: десять участников в одной голосовой комнате,
проверка плиток, входящего аудио и слоя 1080p60 у клиента; сессии берутся
из файла (без формы входа);
- .gitignore: каталоги вывода Playwright test-results*.
Сервер:
- store: оверрайды всех комнат сервера одним запросом (без N+1) и снятие
оверрайда с признаком «был ли он»;
- API: PUT/DELETE /guilds/{id}/channels/{cid}/overwrites/{role|user}/{tid}
(права именами через `|`, как у ролей), step-up на изменение, проверка что
роль принадлежит серверу, а участник состоит в нём;
- список комнат отдаёт permission_overwrites; после правки сбрасывается кэш
прав комнаты, пишется аудит (channel.overwrite_set/delete) и уходит событие
GUILD_CHANNELS_SYNC — видимость комнаты меняется у всех участников.
Клиент:
- редактор «Доступ к комнате»: приватность одним переключателем (запрет
VIEW_CHANNEL для @everyone), права просмотра/переписки/входа для ролей и
участников, подтверждение личности по требованию сервера;
- в настройках комнаты теперь и голосовые комнаты (фон и права), вебхуки —
только у текстовых; событие GUILD_CHANNELS_SYNC перечитывает список комнат.
Тесты: 2 Go-теста (скрытие и открытие комнаты ролями, проверка цели и прав) и
3 web-теста редактора (маски, step-up, скрытие без MANAGE_ROLES).
Постоянная подпись в плитке занимала место и была не нужна: задержка теперь
живёт в подсказке индикатора связи («Отличное соединение, 46 мс — задержка до
голосового сервера»), а плитка осталась компактной. Тесты и e2e проверяют
`title`/`aria-label` индикатора вместо видимого текста.
Два реальных браузера в одной комнате: у каждого в плитке появляется своя
задержка («N мс», измеряется по WebRTC-статистике), а второй участник видит
пинг первого, пришедший по data-каналу LiveKit.
Регистрация тестового пользователя выполнялась на любой неудачный вход, из-за
чего при `rate_limited` прогон падал на попытке регистрации. Теперь:
- 401 → пользователя нет, регистрируем; 429 → ждём `retry_after_ms` и повторяем;
- 409 при регистрации (аккаунт уже есть) → просто входим;
- число повторов увеличено до шести.
Прогон зелёный, аккаунт `voiceprobe` переиспользуется.
- постоянный аккаунт `voiceprobe` вместо регистрации нового на каждый прогон
(API удаления пользователей пока нет — это Фаза 5);
- `postWithRetry` и повтор входа в браузере учитывают `rate_limited` и
`retry_after_ms`: защита от флуда входа обязана работать, а тест не должен
падать из-за неё;
- проверено два прогона подряд: оба зелёные, в базе стенда остаются только
аккаунт администратора, тестовый пользователь и серверы владельца.
LiveKit принимает право `roomAdmin` только вместе с именем комнаты: без
`video.room` RoomService отвечает 401 `permissions denied` — серверный мьют и
отключение участника не доходили до SFU, хотя API отвечал 200 (AGENT.md 7.14).
- `voice.IssueAdmin(room)` кладёт в грахт `room` и `roomAdmin` и отклоняет
пустое имя комнаты;
- `AdminClient.call` выпускает токен под конкретную комнату каждого вызова
(GetParticipant, MutePublishedTrack, RemoveParticipant, ListParticipants);
- тест на состав грахта и отказ при пустой комнате;
- e2e: администратор и гость работают в отдельных api-контекстах (код 2FA
одноразовый), нажатие «Войти» в комнату не ждёт стабильности исчезающей
кнопки.
Проверка голоса двумя реальными браузерами (AGENT.md 11.2): два контекста с
фейковыми медиа-устройствами входят в одну комнату, инструментируется
`RTCPeerConnection` и по `inbound-rtp` подтверждается, что аудио реально
доходит; затем серверный мьют блокирует микрофон у гостя, а отключение
участника убирает его плитку у обоих клиентов.
- `playwright.config.ts`: прогон против развёрнутого инстанса (`GLCHAT_URL`),
трейс при падении, флаги фейковых медиа-устройств;
- `e2e/voice.spec.ts`: подготовка через REST (сервер, комната, инвайт, гость),
завершение онбординга, уборка тестовых серверов и включение только по
`GLCHAT_E2E_VOICE=1`;
- `tsconfig.e2e.json` и запись в eslint для новых файлов.