Files
hermes-relay/plugin/desktop/test/plugin.test.mjs
T

146 lines
5.8 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'node:test'
import plugin, { pairingMintBody, profileQueryKey, secureLinkPreflightPath, secureLinkPairingBody } from '../plugin.js'
const here = dirname(fileURLToPath(import.meta.url))
test('Secure Link checks encode inputs and pairing requires verified listener state', () => {
assert.equal(secureLinkPreflightPath('relay.example', 9443), '/remote-access/secure-link/preflight?host=relay.example&port=9443')
assert.equal(secureLinkPreflightPath('relay.example&port=22', 9443), '/remote-access/secure-link/preflight?host=relay.example%26port%3D22&port=9443')
assert.throws(() => secureLinkPairingBody({ pairing_ready: false }), /Check Secure Link/)
assert.deepEqual(secureLinkPairingBody({ pairing_ready: true, current_url: 'https://relay.example:9443' }), {
mode: 'auto', prefer: 'plugin_proxy', dashboard_url: 'https://relay.example:9443/dashboard'
})
})
function harness() {
const contributions = []
const disposed = []
const revealed = []
const cleanups = []
const ctx = {
i18n: {
register() {
const dispose = () => disposed.push('i18n')
cleanups.push(dispose)
return dispose
},
t: key => key
},
onDispose(fn) {
cleanups.push(fn)
},
os: { writeClipboard: async () => true },
panes: { reveal: id => revealed.push(id) },
register(contribution) {
contributions.push(contribution)
const dispose = () => disposed.push(contribution.id)
cleanups.push(dispose)
return dispose
},
registerMany(batch) {
contributions.push(...batch)
const dispose = () => disposed.push(...batch.map(item => item.id))
cleanups.push(dispose)
return dispose
},
rest: async () => ({})
}
return { cleanups, contributions, ctx, disposed, revealed }
}
test('ships opt-in and registers no pane or network work at load', () => {
const state = harness()
let restCalls = 0
state.ctx.rest = async () => {
restCalls += 1
return {}
}
plugin.register(state.ctx)
assert.equal(plugin.defaultEnabled, false)
assert.deepEqual(state.contributions.map(item => item.id), ['nav', 'status', 'open'])
assert.equal(state.contributions.some(item => item.area === 'panes'), false)
assert.equal(state.revealed.length, 0)
assert.equal(restCalls, 0)
})
test('startup, reconnect, profile-change, and reload registration never reveal the pane', () => {
for (const lifecycle of ['startup', 'reconnect', 'profile-change', 'reload']) {
const state = harness()
plugin.register(state.ctx)
assert.equal(state.contributions.some(item => item.area === 'panes'), false, lifecycle)
assert.deepEqual(state.revealed, [], lifecycle)
}
})
test('every labeled entry point opens lazily and repeated opens reuse the pane', () => {
const state = harness()
plugin.register(state.ctx)
const nav = state.contributions.find(item => item.id === 'nav')
const status = state.contributions.find(item => item.id === 'status')
const command = state.contributions.find(item => item.id === 'open')
nav.data.onSelect()
const statusElement = status.render()
const renderedStatus = statusElement.type(statusElement.props)
renderedStatus.props.children.props.onClick()
command.data.run()
const panes = state.contributions.filter(item => item.area === 'panes')
assert.equal(panes.length, 1)
assert.equal(panes[0].data.closeBehavior, 'dismiss')
assert.equal(panes[0].data.placement, 'right')
assert.deepEqual(state.revealed, ['management', 'management', 'management'])
})
test('unload disposes entry points, lazy pane, locale bundle, and module state', () => {
const state = harness()
plugin.register(state.ctx)
state.contributions.find(item => item.id === 'nav').data.onSelect()
for (const cleanup of [...state.cleanups].reverse()) cleanup()
assert.ok(state.disposed.includes('management'))
assert.ok(state.disposed.includes('nav'))
assert.ok(state.disposed.includes('i18n'))
})
test('query keys isolate cached backend state by active profile', () => {
assert.deepEqual(profileQueryKey('default', 'overview'), ['hermes-relay', 'default', 'overview'])
assert.notDeepEqual(profileQueryKey('work', 'sessions'), profileQueryKey('personal', 'sessions'))
})
test('pairing keeps direct Relay compatibility explicit', () => {
assert.deepEqual(pairingMintBody(), { mode: 'auto' })
assert.deepEqual(pairingMintBody({ desktopCompatibility: false }), { mode: 'auto' })
assert.deepEqual(pairingMintBody({ desktopCompatibility: true }), {
mode: 'auto',
legacy_direct_relay: true
})
})
test('unified package uses only runtime-plugin imports and contains no auto-open primitive', async () => {
const source = await readFile(resolve(here, '..', 'plugin.js'), 'utf8')
const imports = [...source.matchAll(/from\s+['\"]([^'\"]+)['\"]/g)].map(match => match[1])
assert.deepEqual([...new Set(imports)].sort(), ['@hermes/plugin-sdk', 'react', 'react/jsx-runtime'])
assert.equal(/setInterval|setTimeout|host\.navigate|window\.location|focus\s*\(/.test(source), false)
assert.match(source, /const open = \(\) => \{/)
assert.match(source, /paneRegistered = true/)
})
test('desktop half is bundled beside the existing dashboard half', async () => {
const pluginRoot = resolve(here, '..', '..')
const manifest = await readFile(resolve(pluginRoot, 'plugin.yaml'), 'utf8')
const dashboard = await readFile(resolve(pluginRoot, 'dashboard', 'manifest.json'), 'utf8')
const desktop = await readFile(resolve(pluginRoot, 'desktop', 'plugin.js'), 'utf8')
assert.match(manifest, /^name:\s+hermes-relay/m)
assert.equal(JSON.parse(dashboard).name, 'hermes-relay')
assert.match(desktop, /id:\s*PLUGIN_ID/)
})