mock.ts was a static import and the mock switch was read from the query string at runtime, so the bundle that ships inside the daemon carried a complete fictional router and a link ending in ?dev rendered it: protected, 119 of 122 nodes alive, without a single request to the daemon. The only tell was a line in the footer. That is worse than any wrong number — there is no data at all and nothing says so. It is out of the production bundle now, which is 21 kB smaller for it. Unknown state stopped reading as good news in two more places. The kill-switch tile treated an absent plane as armed, because the check was "not none" and undefined satisfies it — the contract in the API types says the opposite. And the apply page announced "daemon auto-rolled back" from its own timer, while the daemon, seeing the state generation move, disarms and says it is NOT rolling back in the log only. Alerts moved to Settings. They are about the kill switch, apply failures, new devices and subscription expiry, and they lived at the bottom of the DNS page, while Settings mentioned them in prose with nothing to click. Findings truncation is visible now: the notice that says how many were suppressed arrives as info, and the attention list keeps only critical and warning, so past fifty findings the operator saw forty-nine and no hint of the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
254 lines
10 KiB
TypeScript
254 lines
10 KiB
TypeScript
// protectionState — the one sentence the whole panel shows about "am I protected".
|
||
//
|
||
// Run with `npm test` (node's built-in test runner + native TypeScript stripping;
|
||
// no test dependency is added to the SPA, which ships inside the daemon binary).
|
||
//
|
||
// The case this file was written for is "plane full, traffic direct": the exact
|
||
// state of a live router — one enabled rule, `default → direct`, no groups, no
|
||
// rule-sets — where every part of the data plane was installed and the readout
|
||
// therefore said "Protected — traffic from your network is going through the
|
||
// tunnel", under a green LED, while the whole LAN went out the plain WAN.
|
||
//
|
||
// planeState.ts has no runtime imports (both of its imports are `import type`),
|
||
// so this runs against the real module with nothing stubbed.
|
||
|
||
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
|
||
import { engineReadout, engineState, killSwitchReadout, protectionState } from './planeState.ts'
|
||
import type { Status, Traffic } from './api.ts'
|
||
|
||
/** A healthy, fully-installed router; `traffic` is what each case varies. */
|
||
function status(over: Partial<Status> = {}): Status {
|
||
return {
|
||
running: true,
|
||
enabled: true,
|
||
active: true,
|
||
table: true,
|
||
hash: 'abc',
|
||
version: '1.11.0-shater',
|
||
kill_switch: 'closed',
|
||
engine_running: true,
|
||
plane: 'full',
|
||
warnings: [],
|
||
...over,
|
||
}
|
||
}
|
||
|
||
function withTraffic(traffic: Traffic | undefined): Status {
|
||
return status({ traffic })
|
||
}
|
||
|
||
// --- the field case ---------------------------------------------------------
|
||
|
||
test('plane full + default direct is NOT reported as protected', () => {
|
||
const s = protectionState(withTraffic({ verdict: 'direct', default: 'direct', tunnel_rules: 0 }))
|
||
assert.notEqual(s.headline, 'Protected')
|
||
assert.equal(s.variant, 'crit')
|
||
assert.equal(s.alarm, true)
|
||
// The claim that was false must not survive anywhere in the copy.
|
||
assert.doesNotMatch(s.detail, /going through the tunnel/)
|
||
// ...and the honest consequence must be stated, not implied.
|
||
assert.match(s.detail, /real address/)
|
||
})
|
||
|
||
// --- the other verdicts under a full plane ----------------------------------
|
||
|
||
test('plane full + default into a tunnel is protected', () => {
|
||
const s = protectionState(withTraffic({ verdict: 'tunnel', default: 'auto', tunnel_rules: 1 }))
|
||
assert.equal(s.variant, 'on')
|
||
assert.equal(s.headline, 'Protected')
|
||
assert.equal(s.alarm, false)
|
||
})
|
||
|
||
test('plane full + direct default with tunnelling rules is split, not protected', () => {
|
||
const s = protectionState(withTraffic({ verdict: 'split', default: 'direct', tunnel_rules: 3 }))
|
||
assert.equal(s.variant, 'amber')
|
||
assert.notEqual(s.headline, 'Protected')
|
||
// Says how much is protected, and that the default is not.
|
||
assert.match(s.detail, /3 rules/)
|
||
assert.match(s.detail, /normal internet connection/)
|
||
// A working selective setup must not raise a banner on every other page.
|
||
assert.equal(s.alarm, false)
|
||
})
|
||
|
||
test('split names a single rule in the singular', () => {
|
||
const s = protectionState(withTraffic({ verdict: 'split', default: 'direct', tunnel_rules: 1 }))
|
||
assert.match(s.detail, /^One rule sends traffic/)
|
||
})
|
||
|
||
test('plane full + blocked default with rules leaks nothing and is never crit', () => {
|
||
const s = protectionState(withTraffic({ verdict: 'blocked', default: 'block', tunnel_rules: 2 }))
|
||
assert.equal(s.variant, 'amber')
|
||
assert.equal(s.alarm, false)
|
||
assert.match(s.detail, /nothing is leaving unprotected/)
|
||
})
|
||
|
||
test('plane full + blocked default with no rules says the network has no way out', () => {
|
||
const s = protectionState(withTraffic({ verdict: 'blocked', default: 'block', tunnel_rules: 0 }))
|
||
assert.equal(s.variant, 'amber')
|
||
assert.equal(s.alarm, true)
|
||
assert.doesNotMatch(s.detail, /going through the tunnel/)
|
||
})
|
||
|
||
test('plane full with no verdict claims nothing either way', () => {
|
||
for (const t of [undefined, { verdict: '' as const }]) {
|
||
const s = protectionState(withTraffic(t))
|
||
assert.notEqual(s.headline, 'Protected')
|
||
assert.equal(s.variant, 'amber')
|
||
assert.equal(s.alarm, false)
|
||
}
|
||
})
|
||
|
||
// --- the branches that were already correct ---------------------------------
|
||
|
||
test('no status yet', () => {
|
||
const s = protectionState(null)
|
||
assert.equal(s.variant, 'off')
|
||
assert.equal(s.alarm, false)
|
||
})
|
||
|
||
test('service switched off is a deliberate state, not a fault', () => {
|
||
const s = protectionState(status({ enabled: false }))
|
||
assert.equal(s.variant, 'amber')
|
||
assert.equal(s.headline, 'Turned off')
|
||
assert.equal(s.alarm, false)
|
||
})
|
||
|
||
test('hold: the kill-switch caught it — protected, offline', () => {
|
||
const s = protectionState(status({ plane: 'hold', engine_running: false, active: false }))
|
||
assert.equal(s.variant, 'amber')
|
||
assert.equal(s.alarm, true)
|
||
assert.match(s.headline, /blocked/)
|
||
})
|
||
|
||
test('none + fail-closed is the leak, and it is crit', () => {
|
||
const s = protectionState(status({ plane: 'none', table: false, engine_running: false }))
|
||
assert.equal(s.variant, 'crit')
|
||
assert.equal(s.alarm, true)
|
||
})
|
||
|
||
test('none + fail-open is the operator’s documented choice, stated not alarmed at', () => {
|
||
const s = protectionState(
|
||
status({ plane: 'none', table: false, engine_running: false, kill_switch: 'open' }),
|
||
)
|
||
assert.equal(s.variant, 'amber')
|
||
assert.equal(s.alarm, true)
|
||
})
|
||
|
||
test('daemon too old to send `plane` keeps its own fallback', () => {
|
||
// Nothing here may depend on `traffic`: a daemon with no `plane` has no
|
||
// `traffic` either, and this branch reads what it can observe instead.
|
||
const { plane, ...noPlane } = status()
|
||
void plane
|
||
assert.equal(protectionState(noPlane as Status).headline, 'Protected')
|
||
assert.equal(protectionState({ ...noPlane, running: false } as Status).headline, 'Service stopped')
|
||
assert.equal(
|
||
protectionState({ ...noPlane, active: false } as Status).headline,
|
||
'Starting up',
|
||
)
|
||
})
|
||
|
||
// --- engineState: the reading that could not say "down" ----------------------
|
||
//
|
||
// `apply.Status.running` was a hardcoded `true` on the daemon, so every panel LED
|
||
// derived from it was lit before it was read: App's master indicator could not
|
||
// reach its "Offline" branch, and Apply's "engine: running / stopped" row had one
|
||
// reachable value. These pin the three answers, and that "up" needs agreement.
|
||
|
||
test('engine_running:false is down even while the daemon claims it is running', () => {
|
||
assert.equal(engineState(status({ running: true, engine_running: false })), 'down')
|
||
assert.equal(engineReadout(status({ running: true, engine_running: false })).variant, 'crit')
|
||
assert.equal(engineReadout(status({ running: true, engine_running: false })).word, 'stopped')
|
||
})
|
||
|
||
test('a daemon that reports itself stopped is down whatever engine_running says', () => {
|
||
assert.equal(engineState(status({ running: false, engine_running: true })), 'down')
|
||
})
|
||
|
||
test('up needs both, and then active/idle splits the lamp', () => {
|
||
assert.equal(engineState(status({ running: true, engine_running: true })), 'up')
|
||
assert.equal(engineReadout(status({ active: true })).variant, 'on')
|
||
assert.equal(engineReadout(status({ active: true })).word, 'active')
|
||
assert.equal(engineReadout(status({ active: false })).variant, 'amber')
|
||
assert.equal(engineReadout(status({ active: false })).word, 'idle')
|
||
})
|
||
|
||
test('an older daemon with no engine_running is unknown — an unlit lamp, never green', () => {
|
||
const { engine_running, ...old } = status()
|
||
void engine_running
|
||
assert.equal(engineState(old as Status), 'unknown')
|
||
const r = engineReadout(old as Status)
|
||
assert.equal(r.variant, 'off')
|
||
assert.notEqual(r.variant, 'on')
|
||
assert.equal(r.word, 'not reported')
|
||
})
|
||
|
||
test('no status at all is unknown, not down', () => {
|
||
assert.equal(engineState(null), 'unknown')
|
||
assert.equal(engineReadout(null).variant, 'off')
|
||
assert.equal(engineReadout(null).word, 'checking…')
|
||
})
|
||
|
||
// --- killSwitchReadout: "I don't know" is not "it's armed" -------------------
|
||
//
|
||
// The Overview module read `killArmed && status?.plane !== 'none'`, and
|
||
// `undefined !== 'none'` is true — so a daemon that never reported `plane`, and
|
||
// the seconds before the first status arrives, both lit a green lamp over the
|
||
// word ARMED. These pin the fourth answer that expression could not express.
|
||
|
||
test('a daemon that does not report `plane` reads as not reported, never ARMED', () => {
|
||
const { plane, ...noPlane } = status()
|
||
void plane
|
||
const k = killSwitchReadout(noPlane as Status)
|
||
assert.equal(k.state, 'unknown')
|
||
assert.notEqual(k.value, 'ARMED')
|
||
assert.equal(k.variant, 'off')
|
||
assert.notEqual(k.variant, 'on')
|
||
assert.equal(k.blockingNow, 'not known')
|
||
})
|
||
|
||
test('no status at all is unknown too, and says there is no reading', () => {
|
||
const k = killSwitchReadout(null)
|
||
assert.equal(k.state, 'unknown')
|
||
assert.equal(k.variant, 'off')
|
||
assert.equal(k.blockingNow, 'no reading yet')
|
||
})
|
||
|
||
test('fail-closed with a plane installed is armed', () => {
|
||
for (const plane of ['full', 'hold'] as const) {
|
||
const k = killSwitchReadout(status({ plane }))
|
||
assert.equal(k.state, 'armed')
|
||
assert.equal(k.value, 'ARMED')
|
||
assert.equal(k.variant, 'on')
|
||
assert.equal(k.blockingNow, null)
|
||
}
|
||
})
|
||
|
||
test('fail-closed with no plane is configured but blocking nothing', () => {
|
||
const k = killSwitchReadout(status({ plane: 'none', table: false }))
|
||
assert.equal(k.state, 'inert')
|
||
assert.equal(k.value, 'NOT IN EFFECT')
|
||
assert.equal(k.variant, 'crit')
|
||
assert.equal(k.hot, true)
|
||
})
|
||
|
||
test('fail-open is the operator’s choice — amber, and never a plane question', () => {
|
||
for (const plane of ['full', 'none', undefined] as const) {
|
||
const k = killSwitchReadout(status({ kill_switch: 'open', plane }))
|
||
assert.equal(k.state, 'open')
|
||
assert.equal(k.value, 'OPEN')
|
||
assert.equal(k.variant, 'amber')
|
||
}
|
||
})
|
||
|
||
test('the live kill_switch wins over the saved one; the saved one only fills a gap', () => {
|
||
const { kill_switch, ...noKill } = status()
|
||
void kill_switch
|
||
// Live says open, config says closed → live wins.
|
||
assert.equal(killSwitchReadout(status({ kill_switch: 'open' }), 'closed').state, 'open')
|
||
// Nothing live → fall back to the saved policy.
|
||
assert.equal(killSwitchReadout(noKill as Status, 'open').state, 'open')
|
||
assert.equal(killSwitchReadout(noKill as Status, 'closed').state, 'armed')
|
||
})
|