test(ui): проверить поздний фрагмент открытого потока
This commit is contained in:
1 parent
53bca894bb
commit
7318c5286b
3 files changed
+73
-3
No files matched your search
+2
-2
@@ -84,11 +84,11 @@ flowchart LR
|
||||
|
||||
Подробные планы: [этап 6](ux-ui-stage-6.md), [этап 7](ux-ui-stage-7.md), [этап 8](ux-ui-stage-8.md).
|
||||
|
||||
Для этапа 8 уже выполнены блоки периодов/URL статистики, ошибок и гонок трафика, месячного отчёта, адаптивности, неизвестных метрик, пустой истории и разрывов графиков. Проверены смена flow-сервера, отмена SSE до ответа и смена backend через настройки. Точный состав тестов и оговорки — в журнале, блоки 8.1–8.4М.
|
||||
Для этапа 8 уже выполнены блоки периодов/URL статистики, ошибок и гонок трафика, месячного отчёта, адаптивности, неизвестных метрик, пустой истории и разрывов графиков. Проверены смена flow-сервера, отмена SSE до ответа, позднее чтение открытого SSE на управляемом ReadableStream и смена backend через настройки. Точный состав тестов и оговорки — в журнале, блоки 8.1–8.4Н.
|
||||
|
||||
Ближайший оставшийся объём:
|
||||
|
||||
1. Проверка задержанного чтения внутри уже открытого SSE-потока.
|
||||
1. Задержанное чтение открытого SSE проверено в блоке 8.4Н; продолжить со свежести данных ниже.
|
||||
2. Свежесть данных трафика: ясное различие времени получения, истории и текущего live-измерения.
|
||||
3. Сводная приёмка этапа 8 и оставшихся ограничений (включая выбор архивных серверов месячного отчёта), профильная регрессия и ReUI checklist.
|
||||
4. После фиксации результата этапа 8 — этап 9.
|
||||
|
||||
@@ -24,13 +24,20 @@
|
||||
| 5 | Состояния данных, свежесть, частичные сбои, гонки polling | Завершён 2026-09-20 для дашборда; границы проверок ниже |
|
||||
| 6 | Навигация и дашборд | Завершён 2026-09-20 в границах локальной приёмки; ограничения ниже |
|
||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||
| 8 | Трафик и статистика | В работе: выполнены блоки 8.1–8.4М; далее задержка внутри SSE, свежесть и приёмка |
|
||||
| 8 | Трафик и статистика | В работе: выполнены блоки 8.1–8.4Н; далее свежесть данных и приёмка |
|
||||
| 9 | VPN, Firewall и Filters | Не начат |
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Не начат |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
## Блок 8.4Н — поздний фрагмент открытого SSE (2026-09-28)
|
||||
|
||||
- Добавлен браузерный сценарий с управляемым ReadableStream: первый sample отображается, второй reader.read остаётся ожидающим, пользователь выбирает второй сервер. Проверены abort старой подписки, получение нового sample и завершение старого read после abort. Поздний фрагмент не заменяет данные второго сервера.
|
||||
- Граница: fetch/ReadableStream подменены в браузере только для flow/live; поток намеренно игнорирует отмену, чтобы проверить защиту после await reader.read. Остальные HTTP-запросы используют прежние route-моки. Это не проверка реального сетевого транспорта или IPFIX/RouterOS.
|
||||
- Все 20 flow-E2E прошли; ESLint теста, TypeScript (tsc --noEmit) и diff-check чистые. В первом прогоне исправлена ошибка тестового макета: Response должен создаваться до блокировки stream через getReader. Production-код не менялся, build не повторялся.
|
||||
- Общий план синхронизирован. Следующий шаг: свежесть REST/SSE данных (разделять время получения и время измерения), затем сводная приёмка этапа 8. Этап остаётся открытым. На контрольной точке использовано 24% пятичасового и 4% недельного лимита; локальный коммит без push.
|
||||
|
||||
## Блок 8.4М — разрывы основного графика (2026-09-24)
|
||||
|
||||
- Существующий E2E пропусков дополнен проверкой SVG основного RX/TX: при [1, 2, null, 3, 4] каждая линия содержит два самостоятельных сегмента. Проверка прошла вместе со Sparkline; lint/diff-check чистые. Production-код не менялся.
|
||||
|
||||
@@ -222,3 +222,66 @@ test('Flow: смена сервера отменяет старую SSE-подп
|
||||
await expect(page.getByText('SSE текущего сервера', { exact: true }).filter({ visible: true })).toBeVisible()
|
||||
await expect(page.getByLabel('Скорость потоков')).toContainText('7.00 Мбит/с')
|
||||
})
|
||||
|
||||
test('Flow: позднее чтение открытого SSE не заменяет данные нового сервера', async ({ page }) => {
|
||||
await setup(page)
|
||||
await page.getByRole('button', { name: '15м', exact: true }).click()
|
||||
await expect(page.getByText('Категория 15m', { exact: true }).filter({ visible: true })).toBeVisible()
|
||||
// Deliberately let the mock stream deliver a chunk after abort. This exercises
|
||||
// the post-read guard, independently of the browser transport's cancellation.
|
||||
await page.evaluate(() => {
|
||||
const originalFetch = window.fetch.bind(window)
|
||||
const state = { pending: false, aborted: false, delivered: false, release: () => {} }
|
||||
Object.assign(window, { delayedFlowStream: state })
|
||||
window.fetch = async (input, init) => {
|
||||
const url = new URL(input instanceof Request ? input.url : String(input), location.href)
|
||||
if (url.pathname !== '/api/traffic/flow/live') return originalFetch(input, init)
|
||||
const old = url.searchParams.get('serverId') === '1'
|
||||
const sample = (late = false) => ({
|
||||
bpsNow: old ? (late ? 99000000 : 1000000) : 7000000,
|
||||
bytes: 200, packets: 2, conversations: 1, uniqueSrc: 1, uniqueDst: 1,
|
||||
topProto: 'TCP', topCategory: old ? (late ? 'Поздний фрагмент' : 'Открытый поток первого') : 'Открытый поток второго',
|
||||
rxSeries: [1], txSeries: [2], applications: [], protocols: [], sources: [],
|
||||
destinations: [], interfaces: [], conversationsList: [], ifaces: [], live: true,
|
||||
})
|
||||
const encode = (late = false) => new TextEncoder().encode('event: sample\ndata: ' + JSON.stringify(sample(late)) + '\n\n')
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(encode())
|
||||
if (old) state.release = () => controller.enqueue(encode(true))
|
||||
},
|
||||
})
|
||||
const response = new Response(stream, { headers: { 'Content-Type': 'text/event-stream' } })
|
||||
if (old) {
|
||||
init?.signal?.addEventListener('abort', () => { state.aborted = true })
|
||||
const reader = stream.getReader()
|
||||
const read = reader.read.bind(reader)
|
||||
let calls = 0
|
||||
reader.read = async () => {
|
||||
const delayed = ++calls === 2
|
||||
if (delayed) state.pending = true
|
||||
const result = await read()
|
||||
if (delayed) { state.pending = false; state.delivered = true }
|
||||
return result
|
||||
}
|
||||
stream.getReader = (() => reader) as typeof stream.getReader
|
||||
}
|
||||
return response
|
||||
}
|
||||
})
|
||||
await page.getByRole('button', { name: '5м', exact: true }).click()
|
||||
await expect(page.getByText('Открытый поток первого', { exact: true }).filter({ visible: true })).toBeVisible()
|
||||
const streamState = () => page.evaluate(() => {
|
||||
const state = (window as unknown as { delayedFlowStream: { pending: boolean; aborted: boolean; delivered: boolean } }).delayedFlowStream
|
||||
return { pending: state.pending, aborted: state.aborted, delivered: state.delivered }
|
||||
})
|
||||
await expect.poll(streamState).toEqual({ pending: true, aborted: false, delivered: false })
|
||||
await page.getByRole('button').filter({ hasText: 'Экспортёр второй' }).click()
|
||||
await expect(page.getByText('Открытый поток второго', { exact: true }).filter({ visible: true })).toBeVisible()
|
||||
await expect.poll(streamState).toEqual({ pending: true, aborted: true, delivered: false })
|
||||
await page.evaluate(() => (window as unknown as { delayedFlowStream: { release: () => void } }).delayedFlowStream.release())
|
||||
await expect.poll(streamState).toEqual({ pending: false, aborted: true, delivered: true })
|
||||
await expect(page.getByText('Поздний фрагмент', { exact: true })).toHaveCount(0)
|
||||
await expect(page.getByText('Открытый поток второго', { exact: true }).filter({ visible: true })).toBeVisible()
|
||||
await expect(page.getByLabel('Скорость потоков')).toContainText('7.00 Мбит/с')
|
||||
})
|
||||
Reference in new issue
Block a user