From 8cf6bedf5887bfe71fe1e2f88e73daefb3be0dec Mon Sep 17 00:00:00 2001 From: grendervill Date: Sat, 26 Sep 2026 18:40:46 +0300 Subject: [PATCH] =?UTF-8?q?fix(cli):=20=D0=BD=D0=B5=20=D0=BE=D1=82=D0=B4?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=D1=82=D1=8C=20stdin=20=D0=BE=D0=B1=D1=91?= =?UTF-8?q?=D1=80=D1=82=D0=BA=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=D0=B0=D0=BC=20=D0=BE=D0=B1=D1=81=D0=BB=D1=83=D0=B6=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker compose exec -T подключает stdin и съедал его у скрипта, поданного на stdin (ssh … bash -s): обёртка обрывала такой скрипт после первой команды. Найдено живой проверкой build/cli-live-check.sh (create-user выполнялся, дальше скрипт молча заканчивался). Командам обслуживания stdin не нужен: { + 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(); + } + }); +});