// Real routes, proto-3 unit: grab / slot / release by browser session.
import test from 'brittle'
import http from 'bare-http1'
import EventEmitter from 'bare-events'
import { Rig } from '../lib/rig.js'
import { createServer } from '../lib/server.js'
import { FakeUnitV3, waitFor } from './helpers.js'
class FakeDiscovery extends EventEmitter {
constructor () { super(); this.available = true; this.reason = '' }
start () {}
stop () {}
}
async function setup () {
const fake = await new FakeUnitV3('u3').listen()
const discovery = new FakeDiscovery()
const rig = new Rig({ store: { load: () => ({}), save: () => {} }, discovery, unitOpts: { backoffStartMs: 100 } })
rig.start()
discovery.emit('up', { name: 'u3', host: 'u3.local', addr: '127.0.0.1', port: fake.port, proto: 3 })
rig.updateUnit('u3', { enabled: true, fadeMs: 0 })
rig.setConnected(true)
await waitFor(() => rig.snapshot().units[0].slots !== null)
const server = createServer(rig)
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
const port = server.address().port
return {
fake, rig, port,
teardown: async () => { rig.stop(); await new Promise((resolve) => server.close(resolve)); await fake.close() }
}
}
function post (port, path, obj) {
return new Promise((resolve, reject) => {
const req = http.request({ host: '127.0.0.1', port, method: 'POST', path, headers: { 'content-type': 'application/json', 'x-cthuloid': '1' } }, (res) => {
let data = ''
res.on('data', (d) => { data += d })
res.on('end', () => resolve({ status: res.statusCode, body: data ? JSON.parse(data) : null }))
})
req.on('error', reject)
req.end(JSON.stringify(obj))
})
}
test('grab, drag, release over HTTP; a second session is refused', async (t) => {
const s = await setup()
const a = await post(s.port, '/api/units/u3/grab', { index: 0, session: 'A', name: 'alice' })
t.is(a.status, 200)
t.alike(a.body, { granted: true })
const b = await post(s.port, '/api/units/u3/grab', { index: 0, session: 'B', name: 'bob' })
t.is(b.status, 409)
t.alike(b.body, { busy: 'alice' })
const notMine = await post(s.port, '/api/units/u3/slot', { index: 0, session: 'B', r: 1, g: 0, b: 0, level: 1 })
t.is(notMine.status, 409, 'setting a slot you do not hold is a conflict')
const set = await post(s.port, '/api/units/u3/slot', { index: 0, session: 'A', r: 0, g: 1, b: 0, level: 1 })
t.is(set.status, 200)
t.ok(await waitFor(() => s.fake.values[1] === 65535), 'green reached the unit')
const rel = await post(s.port, '/api/units/u3/release', { index: 0, session: 'A' })
t.is(rel.status, 200)
t.ok(await waitFor(() => s.rig.snapshot().units[0].holds[0] === null), 'released')
await s.teardown()
})
test('look recall reports units it had to skip', async (t) => {
const s = await setup()
s.rig.updateUnit('u3', { slots: [{ r: 1, g: 1, b: 1, level: 1 }, null, null, null] })
s.rig.saveLook('white')
const ext = await s.fake.externalGrab(1, 'blender')
const r = await post(s.port, '/api/looks/white/recall', { fadeMs: 0 })
t.is(r.status, 200)
t.alike(r.body.skipped, [{ unit: 'u3', holder: 'blender' }])
ext.destroy()
await s.teardown()
})