Files
evening_detective_screen/electron/lan.test.ts
T
2026-10-17 18:14:31 +07:00

107 lines
3.7 KiB
TypeScript

import assert from 'node:assert/strict'
import { once } from 'node:events'
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { test } from 'node:test'
import { WebSocket } from 'ws'
import { Controller } from './controller.js'
import { LanServer } from './lan.js'
test('LAN: код, подтверждение, cookie, WebSocket и восстановление без повтора', async () => {
const root = await mkdtemp(join(tmpdir(), 'screen-test-'))
await writeFile(join(root, 'index.html'), '<html>local</html>')
let count = 0
const controller = new Controller((state) => {
count++
server.broadcast(state)
})
const server = new LanServer(
root,
() => controller.state,
(input) => controller.command(input),
)
const sockets: WebSocket[] = []
try {
await server.start('127.0.0.1')
const origin = server.url
assert.equal((await fetch(origin)).status, 200)
assert.equal((await fetch(origin + '/package.json')).status, 404)
assert.equal(
(
await fetch(origin + '/pair', {
method: 'POST',
headers: { Origin: origin },
body: JSON.stringify({ code: 'wrong' }),
})
).status,
403,
)
assert.equal(
(
await fetch(origin + '/pair', {
method: 'POST',
headers: { Origin: 'http://evil.example' },
body: '{}',
})
).status,
403,
)
const denied = new WebSocket(origin.replace('http', 'ws') + '/ws', { origin })
sockets.push(denied)
await new Promise<void>((resolve) => denied.on('error', () => resolve()))
const paired = await fetch(origin + '/pair', {
method: 'POST',
headers: { Origin: origin },
body: JSON.stringify({ code: server.code }),
})
const { id } = (await paired.json()) as { id: string }
assert.equal((await fetch(origin + '/pair/' + id)).status, 202)
server.approve(id)
const approved = await fetch(origin + '/pair/' + id)
assert.equal(approved.status, 200)
const cookie = approved.headers.get('set-cookie')!.split(';')[0]
assert.equal((await fetch(origin + '/pair/' + id)).status, 404)
async function connect() {
const ws = new WebSocket(origin.replace('http', 'ws') + '/ws', {
origin,
headers: { Cookie: cookie },
})
sockets.push(ws)
const [data] = await once(ws, 'message')
return { ws, state: JSON.parse(data.toString()) }
}
const first = await connect()
assert.equal(first.state.revision, 0)
let received = once(first.ws, 'message')
const command = { id: 'phone-1', action: 'play', media: 'audio', position: 0 }
first.ws.send(JSON.stringify(command))
await received
first.ws.send(JSON.stringify(command))
// A fresh command is a barrier proving that the duplicate was processed first.
received = once(first.ws, 'message')
first.ws.send(JSON.stringify({ ...command, id: 'phone-2', action: 'pause' }))
await received
assert.equal(count, 2)
first.ws.close()
await once(first.ws, 'close')
const restored = await connect()
assert.equal(restored.state.revision, 2)
assert.equal(count, 2)
const closed = once(restored.ws, 'close')
restored.ws.send(JSON.stringify({ action: 'delete' }))
await closed
assert.equal(count, 2)
const oversized = await connect()
const oversizedClosed = once(oversized.ws, 'close')
oversized.ws.send('x'.repeat(3000))
await oversizedClosed
assert.equal(count, 2)
assert.equal((await fetch(origin)).status, 200)
} finally {
for (const ws of sockets) ws.terminate()
await server.close()
await rm(root, { recursive: true, force: true })
}
})