This commit is contained in:
2026-10-18 04:32:58 +07:00
parent e85d899fd7
commit c36d3fd314
14 changed files with 446 additions and 50 deletions
+68 -1
View File
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { ApiError, SessionChangedError } from './request-error.js'
import { buildScreenPackage, prepareScreenPackage } from './screen-package.js'
const server = 'https://example.test'
@@ -200,7 +201,7 @@ test('отказы API и некорректный JSON не выдают час
prepareScreenPackage({ server, gameId: 7, token: 'SECRET' }, async () => {
throw new Error('SECRET')
}),
/^Error: Сервер недоступен$/,
/^Error: Описание игры: Сервер недоступен$/,
)
})
@@ -236,3 +237,69 @@ test('отмена и недопустимые адреса обрабатыва
)
await assert.rejects(prepareScreenPackage({ server, gameId: 7, token: '' }), /токен/)
})
test('сетевая ошибка называет запрос и журналирует только безопасную причину', async () => {
for (const suffix of ['', '/screen-plan', '/music']) {
const failures: { path: string; reason: string }[] = []
const data = fixture()
await assert.rejects(
prepareScreenPackage(
{ server, gameId: 7, token: 'SECRET', onRequestFailure: (f) => failures.push(f) },
async (input) => {
const url = String(input)
if (url === `${server}/api/games/7${suffix}`)
throw new TypeError('SECRET', {
cause: Object.assign(new Error('SECRET'), { code: 'ENOTFOUND' }),
})
return Response.json(
url.endsWith('/music')
? data.music
: url.endsWith('/screen-plan')
? data.plan
: data.game,
)
},
),
new RegExp(
`^Error: ${suffix === '/music' ? 'Музыка игры' : suffix === '/screen-plan' ? 'План экранов' : 'Описание игры'}: Не удалось определить адрес сервера$`,
),
)
assert.deepEqual(failures, [{ path: `/api/games/{id}${suffix}`, reason: 'ENOTFOUND' }])
assert.ok(!JSON.stringify(failures).includes('SECRET'))
}
})
test('подготовка сохраняет ошибки авторизации и смены сессии', async () => {
for (const error of [new ApiError(401), new ApiError(403), new SessionChangedError()]) {
const data = fixture()
await assert.rejects(
prepareScreenPackage({ server, gameId: 7, token: 'SECRET' }, async (input) => {
const url = String(input)
if (url.endsWith('/screen-plan')) throw error
return Response.json(url.endsWith('/music') ? data.music : data.game)
}),
(e) => e === error,
)
}
})
test('таймаут и отмена подготовки различаются без утечки причины', async () => {
for (const [name, message, reason] of [
['TimeoutError', 'Превышено время ожидания ответа сервера', 'TIMEOUT'],
['AbortError', 'Подготовка отменена', 'ABORTED'],
]) {
const signal = AbortSignal.abort(new DOMException('SECRET', name))
const failures: { reason: string }[] = []
await assert.rejects(
prepareScreenPackage(
{ server, gameId: 7, token: 'SECRET', signal, onRequestFailure: (f) => failures.push(f) },
async (_input, init) => {
init?.signal?.throwIfAborted()
return Response.json({})
},
),
new RegExp(`^Error: Описание игры: ${message}$`),
)
assert.ok(failures.every((f) => f.reason === reason))
}
})