diff --git a/deploy/glchat-cli.sh b/deploy/glchat-cli.sh index 53f411a..31d7e70 100755 --- a/deploy/glchat-cli.sh +++ b/deploy/glchat-cli.sh @@ -228,10 +228,13 @@ cmd_verify_backup() { # app_cli выполняет подкоманду обслуживания внутри контейнера приложения # (AGENT.md 10.7): команды, которым нужна БД, живут в Go-бинарнике, а shell-обёртка -# доставляет их с хоста. +# доставляет их с хоста. ` { + test.skip(!enabled, 'нужен развёрнутый инстанс: GLCHAT_E2E_REALTIME=1'); + + test.beforeAll(async ({ playwright }) => { + const ownerApi = await playwright.request.newContext({ baseURL: process.env.GLCHAT_URL }); + try { + const probe = await ensureProbe(ownerApi, OWNER_LOGIN.username, OWNER_LOGIN.password); + const name = await displayName(ownerApi); + await stepUp(ownerApi, OWNER_LOGIN.password); + await dropStaleGuilds(ownerApi, PREFIX); + owner = { + ...probe, + displayName: name, + state: await ownerApi.storageState(), + browserState: await ownerApi.storageState(), + }; + } finally { + await ownerApi.dispose(); + } + }); + + test('429 на списке участников: раздел заполняется сам', async ({ browser, playwright }) => { + const ownerApi = await apiAs(playwright, owner.state); + let session: Session | null = null; + let guildId = ''; + + try { + const guild = await createGuild(ownerApi, `${PREFIX}${String(Date.now() % 100_000)}`); + guildId = guild.id; + session = await openApp(browser, owner.browserState); + const page = session.page; + + // Перехват ставим до первого запроса участников: его делает уже список + // участников в ленте сервера, и без перехвата данные легли бы в кэш + // раньше проверки. Запрос обязан получить 429 дважды и уйти на стенд + // только с третьей попытки. + let blocked = 0; + await page.route('**/api/v1/guilds/*/members*', async (route) => { + if (blocked >= 2) { + await route.continue(); + return; + } + blocked += 1; + await route.fulfill({ + status: 429, + contentType: 'application/json', + body: JSON.stringify({ + error: { + code: 'rate_limited', + message: 'Слишком много запросов. Подождите немного.', + retry_after_ms: 400, + }, + }), + }); + }); + + // Открываем настройки сервера так же, как пользователь: выбрать сервер в + // рейке и нажать ⚙. Адресный переход `/settings/servers/` здесь не + // годится: он на мгновение монтирует вкладку «Мои серверы», та заводит + // запрос участников по тому же ключу и, размонтируясь, обрывает его + // повтор — запрос остаётся в состоянии ошибки. + await openGuild(page, guild.name); + await page.getByRole('button', { name: 'Настройки сервера' }).click(); + await expect(page.getByTestId('settings-content')).toBeVisible({ timeout: 30_000 }); + + // Кнопка «Повторить» не нажимается: строку даёт автоматический повтор. + const row = page + .getByTestId('guild-members') + .locator('li', { hasText: `@${owner.username}` }); + await expect(row).toContainText(owner.displayName, { timeout: 30_000 }); + expect(blocked, 'оба ответа 429 были отданы перехватчиком').toBe(2); + } finally { + await session?.context.close(); + if (guildId !== '') { + await stepUp(ownerApi, OWNER_LOGIN.password).catch(() => undefined); + await ownerApi.delete(`${API}/guilds/${guildId}`).catch(() => undefined); + } + await ownerApi.dispose(); + } + }); +});