import test from 'brittle'
import { Unit } from '../lib/unit.js'
import { FakeUnit, waitFor, sleep } from './helpers.js'
const DEAD = 'cthuloid-test-nonexistent.local'
function unitFor (fake, opts = {}) {
return new Unit({ id: fake.name, host: '127.0.0.1', port: fake.port, backoffStartMs: 100, ...opts })
}
test('open connects, reads the greeting, delivers the desired state', async (t) => {
const fake = await new FakeUnit('alpha').listen()
const u = unitFor(fake)
u.set('pwm 1\n')
u.open()
t.ok(await waitFor(() => fake.lines.includes('pwm 1')), 'payload delivered')
t.is(u.status, 'connected')
t.is(u.greetingName, 'alpha')
u.close()
await fake.close()
})
test('a burst of sets coalesces to the latest, and repeats are skipped', async (t) => {
const fake = await new FakeUnit().listen()
const u = unitFor(fake, { minIntervalMs: 40 })
u.open()
await waitFor(() => u.status === 'connected')
for (let i = 0; i < 50; i++) u.set('pwm ' + i + '\n')
t.ok(await waitFor(() => fake.lines.includes('pwm 49')), 'latest arrives')
const pwm = fake.lines.filter((l) => l.startsWith('pwm'))
t.ok(pwm.length <= 3, 'intermediate frames dropped, sent ' + pwm.length)
u.set('pwm 49\n')
await sleep(150)
t.is(fake.lines.filter((l) => l === 'pwm 49').length, 1, 'identical frame not resent')
u.close()
await fake.close()
})
test('a dead unit never delays a live one', async (t) => {
const fake = await new FakeUnit().listen()
const dead = new Unit({ id: 'dead', host: DEAD, port: 1337 })
const live = unitFor(fake)
dead.set('pwm 0\n')
dead.open()
live.open()
await waitFor(() => live.status === 'connected')
const t0 = Date.now()
live.set('pwm 7\n')
t.ok(await waitFor(() => fake.lines.includes('pwm 7'), 500), 'live unit served while dead one resolves')
t.ok(Date.now() - t0 < 500, 'within ' + (Date.now() - t0) + ' ms')
dead.close()
live.close()
await fake.close()
})
test('a dropped connection reconnects and re-asserts the look', async (t) => {
const fake = await new FakeUnit().listen()
const u = unitFor(fake)
u.set('pwm 3\n')
u.open()
await waitFor(() => fake.lines.includes('pwm 3'))
fake.lines.length = 0
fake.dropClients()
t.ok(await waitFor(() => fake.lines.includes('pwm 3'), 3000), 're-asserted after reconnect')
t.is(fake.accepted, 2)
u.close()
await fake.close()
})
test('close() disconnects and stops retrying', async (t) => {
const fake = await new FakeUnit().listen()
const u = unitFor(fake)
u.open()
await waitFor(() => fake.conns.size === 1)
u.close()
t.ok(await waitFor(() => fake.conns.size === 0), 'socket closed')
await sleep(400)
t.is(fake.accepted, 1, 'no reconnect after close')
t.is(u.status, 'off')
await fake.close()
})
test('an unreachable unit reports it and keeps retrying', async (t) => {
const fake = await new FakeUnit().listen()
const port = fake.port
await fake.close()
const u = new Unit({ id: 'gone', host: '127.0.0.1', port, backoffStartMs: 100 })
u.open()
t.ok(await waitFor(() => u.status === 'unreachable'), 'reported unreachable')
u.close()
})
test('sendOnce delivers without staying connected', async (t) => {
const fake = await new FakeUnit().listen()
const u = unitFor(fake)
u.sendOnce('stopfade\npwm 0\n')
t.ok(await waitFor(() => fake.lines.includes('pwm 0')), 'delivered')
t.ok(await waitFor(() => fake.conns.size === 0), 'connection released')
await sleep(300)
t.is(fake.accepted, 1, 'never reconnects')
await fake.close()
})
test('state readback is parsed and emitted', async (t) => {
const fake = await new FakeUnit().listen()
fake.values[2] = 30000
const u = unitFor(fake)
let got = null
u.on('state', (s) => { got = s })
u.open()
await waitFor(() => u.status === 'connected')
u.requestState()
t.ok(await waitFor(() => got !== null), 'state emitted')
t.is(got.values[2], 30000)
t.alike(u.actual.values, got.values, 'kept as unit.actual')
u.close()
await fake.close()
})