diff --git a/docs/sprint8-progress.md b/docs/sprint8-progress.md index 808e550..f0fbcfe 100644 --- a/docs/sprint8-progress.md +++ b/docs/sprint8-progress.md @@ -15,7 +15,7 @@ ## Чек-лист - [x] **1. P2-7 SignalR real-time** — Hub `/hubs/notifications` с группами per-org. События `SalePosted`/`SupplyPosted`/`LowStock`. JWT через query `?access_token=` (для WebSocket). Дашборд: live-индикатор Wifi, оптимистическое приращение «Выручка сегодня», toast.info на SupplyPosted, toast.error на LowStock. Тесты: SignalRNotificationsTests (multi-tenant 1/1) + `stage-ui-signalr.spec.ts` (1/1 ✓). Nginx `/hubs/` с upgrade-хедерами и 24h read_timeout. -- [ ] **2. P2-14 Telegram-бот владельца** — Hangfire `OwnerDailySummaryJob` 09:00 МСК, ежедневная сводка (выручка вчера, продажи, топ-3, low-stock). Привязка через deep-link → `Organization.OwnerTelegramChatId`. UI в OrgSettings. +- [x] **2. P2-14 Telegram-бот владельца** — миграция `Phase9a_OwnerTelegramChatId`. `TelegramBotClient` (sendMessage HTML), Disabled-mode когда `Telegram__BotToken` пустой. `OwnerDailySummaryJob.RunAsync` — cron `0 6 * * *` UTC = 09:00 МСК. Сводка: выручка вчера, чеков, средний чек, топ-3 по выручке, low-stock 5. `TelegramBindingController` (status/bind/unbind). UI секция в OrganizationSettings с deep-link + пошаговой инструкцией. Тесты: `TelegramOwnerSummaryTests` (рендер ✓), `stage-ui-telegram.spec.ts` (3/3 ✓ на стейдже). - [ ] **3. P2-6a Локализация UI (en)** — react-i18next, `ru.json` + `en.json`, language switcher в header. kz TODO. На стейдже smoke — все страницы переключаются. - [ ] **4. P2-15 MinIO/S3 для uploads** — `Minio` SDK, bucket `food-market-uploads`, авто-создание на старте, миграция existing volume. `Storage:Type=Local|Minio` с fallback на Local. Тесты + UI upload картинки. diff --git a/tests/e2e/scenarios/stage-ui-telegram.spec.ts b/tests/e2e/scenarios/stage-ui-telegram.spec.ts new file mode 100644 index 0000000..816a4fe --- /dev/null +++ b/tests/e2e/scenarios/stage-ui-telegram.spec.ts @@ -0,0 +1,64 @@ +/** + * Sprint 8 пункт 2 — Telegram binding endpoints на стейдже. + * Бот может быть отключён (Telegram__BotToken не задан) — это нормально, + * UI должен показать сообщение «бот не настроен». Тестируем endpoint и UI. + */ +import { test, expect, request as apiRequest } from '@playwright/test' +import { apiSignup, attachSession, watchPage, expectNoErrors } from '../lib/ui.js' + +const BASE = process.env.E2E_ADMIN_URL ?? 'https://test.admin.food-market.kz' + +test.describe('Telegram binding', () => { + test('S8-2.1 GET /api/organization/telegram/status работает', async () => { + const sess = await apiSignup('tg21') + const ctx = await apiRequest.newContext({ + baseURL: BASE, ignoreHTTPSErrors: true, + extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` }, + }) + const r = await ctx.get('/api/organization/telegram/status') + expect(r.status()).toBe(200) + const body = await r.json() as { botEnabled: boolean; chatId: number | null } + // С стейджа токен не задан — бот disabled. + expect(typeof body.botEnabled).toBe('boolean') + expect(body.chatId).toBeNull() + await ctx.dispose() + }) + + test('S8-2.2 UI секция Telegram рендерится в OrganizationSettings', async ({ page }) => { + const sess = await apiSignup('tg22') + const errs = watchPage(page) + await attachSession(page, sess, '/settings/organization') + await page.waitForLoadState('networkidle') + // Заголовок секции + await expect(page.getByText(/Telegram владельца/i).first()).toBeVisible({ timeout: 8_000 }) + // Если бот отключён — показывается соответствующее сообщение + const botDisabled = await page.locator('text=/бот не настроен|нет токена/i').count() + const showsBindForm = await page.locator('text=/Ваш chat_id/i').count() + // Один из двух режимов должен быть видим + expect(botDisabled + showsBindForm).toBeGreaterThan(0) + expectNoErrors(errs, 'telegram section') + }) + + test('S8-2.3 PUT bind с bot disabled → 400 с читаемым сообщением', async () => { + const sess = await apiSignup('tg23') + const ctx = await apiRequest.newContext({ + baseURL: BASE, ignoreHTTPSErrors: true, + extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` }, + }) + const r = await ctx.put('/api/organization/telegram/bind', { + data: { chatId: 123456 }, + failOnStatusCode: false, + }) + // Бот выключен → 400 + if (r.status() === 400) { + const body = await r.json() as { error: string } + expect(body.error).toMatch(/Telegram|бот.*не настроен|токен/i) + } else if (r.status() === 204) { + // если бот включён и chatId валидный — тоже OK + expect(r.status()).toBe(204) + } else { + throw new Error(`unexpected status ${r.status()}`) + } + await ctx.dispose() + }) +})