OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / webui / test / server-v3.test.js

75 lines · javascript ref:
// 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()
})