test(s8-2): telegram stage e2e
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
3088237ea7
commit
749829c12f
|
|
@ -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.
|
- [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 — все страницы переключаются.
|
- [ ] **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 картинки.
|
- [ ] **4. P2-15 MinIO/S3 для uploads** — `Minio` SDK, bucket `food-market-uploads`, авто-создание на старте, миграция existing volume. `Storage:Type=Local|Minio` с fallback на Local. Тесты + UI upload картинки.
|
||||||
|
|
||||||
|
|
|
||||||
64
tests/e2e/scenarios/stage-ui-telegram.spec.ts
Normal file
64
tests/e2e/scenarios/stage-ui-telegram.spec.ts
Normal file
|
|
@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue