hermes-agent/apps/desktop/electron/bundled-runtime.test.ts

188 lines
6.7 KiB
TypeScript

import assert from 'node:assert/strict'
import { test } from 'vitest'
import {
EMBEDDED_RUNTIME_ITEMS,
findEmbeddedPython,
latestReleaseFromLsRemote,
PAYLOAD_SCHEMA_VERSION,
resolvePayload,
updateChannelFromConfig
} from '../electron/bundled-runtime'
// ─── resolvePayload ────────────────────────────────────────────────
const readerFor = (manifest: unknown) => (p: string) => {
if (!p.endsWith('manifest.json')) {
throw new Error('ENOENT')
}
return JSON.stringify(manifest)
}
const allDirsExist = () => true
const noDirsExist = () => false
const completeManifest = { schemaVersion: PAYLOAD_SCHEMA_VERSION, tag: 'v1.2.3', commit: 'a'.repeat(40) }
test('resolvePayload returns null for dev runs, external stubs, and garbage', () => {
assert.equal(resolvePayload(null), null)
assert.equal(resolvePayload(undefined), null)
assert.equal(
resolvePayload('/res', readerFor({ schemaVersion: PAYLOAD_SCHEMA_VERSION, external: true }), allDirsExist),
null
)
assert.equal(
resolvePayload(
'/res',
() => {
throw new Error('ENOENT')
},
allDirsExist
),
null
)
assert.equal(resolvePayload('/res', readerFor('not-an-object'), allDirsExist), null)
})
test('resolvePayload rejects old-schema manifests', () => {
// A schema-2 manifest comes from a pre-embedded artifact. The app and
// its payload travel together, so a mismatch means a foreign artifact.
assert.equal(
resolvePayload('/res', readerFor({ schemaVersion: 2, tag: 'v1.0.0', items: { repo: { status: 'staged' } } }), allDirsExist),
null
)
})
test('resolvePayload rejects a payload with a missing item directory', () => {
// Completeness is a build invariant; a missing directory here means a
// damaged or truncated artifact.
assert.equal(resolvePayload('/res', readerFor(completeManifest), noDirsExist), null)
// One missing item out of five is still a rejection.
const allButUv = (p: string) => !p.endsWith('/uv')
assert.equal(resolvePayload('/res', readerFor(completeManifest), allButUv), null)
})
test('resolvePayload returns dir + tag for a complete payload', () => {
const p = resolvePayload('/res', readerFor(completeManifest), allDirsExist)
assert.ok(p)
assert.match(p.dir, /agent-payload$/)
assert.equal(p.tag, 'v1.2.3')
})
test('the required items include uv — plugin lazy installs are mandatory', () => {
assert.deepEqual([...EMBEDDED_RUNTIME_ITEMS].sort(), ['node', 'python', 'repo', 'site-packages', 'uv'])
})
// ─── findEmbeddedPython ────────────────────────────────────────────
test('findEmbeddedPython picks the patch-versioned dir and needs a real binary', () => {
const fsStub = (dirs: string[], files: string[]) => ({
readdirSync: (p: string) => {
if (!p.endsWith('python')) {
throw new Error('ENOENT')
}
return dirs
},
existsSync: (p: string) => files.some(f => p === f)
})
// Patch-versioned real dir wins over the minor alias (reverse sort).
const python = findEmbeddedPython(
'/res/agent-payload',
'darwin',
fsStub(
['cpython-3.11-macos-aarch64-none', 'cpython-3.11.15-macos-aarch64-none'],
['/res/agent-payload/python/cpython-3.11.15-macos-aarch64-none/bin/python3']
) as never
)
assert.match(String(python), /3\.11\.15.*bin\/python3$/)
// No python dir at all → null, not a throw.
assert.equal(
findEmbeddedPython('/res/agent-payload', 'darwin', {
readdirSync: () => {
throw new Error('ENOENT')
},
existsSync: () => false
} as never),
null
)
// Windows binary lives at the install root, not bin/. The
// implementation joins with the HOST path module, so the test builds
// its expected path the same way to stay host-agnostic.
const winRoot = 'win-res/agent-payload'
const winExpected = ['win-res/agent-payload', 'python', 'cpython-3.11.15-windows-x86_64-none', 'python.exe'].join('/')
const winPython = findEmbeddedPython(
winRoot,
'win32',
fsStub(['cpython-3.11.15-windows-x86_64-none'], [winExpected]) as never
)
assert.match(String(winPython), /python\.exe$/)
})
// ─── updateChannelFromConfig ───────────────────────────────────────
test('channel comes from update.channel in config.yaml; absent means main', () => {
assert.equal(updateChannelFromConfig('update:\n channel: stable\n'), 'stable')
assert.equal(updateChannelFromConfig('update:\n channel: "stable"\n'), 'stable')
assert.equal(updateChannelFromConfig('update:\n channel: main\n'), 'main')
assert.equal(updateChannelFromConfig('model:\n provider: nous\n'), 'main')
assert.equal(updateChannelFromConfig(null), 'main')
assert.equal(updateChannelFromConfig(''), 'main')
})
test('channel parsing stays inside the update block', () => {
// A channel key in ANOTHER block must not leak into the answer.
const text = 'gateway:\n channel: stable\nupdate:\n interval: 1\nmodel:\n channel: stable\n'
assert.equal(updateChannelFromConfig(text), 'main')
// The update block ends at the next top-level key.
const ended = 'update:\n interval: 1\nother:\n channel: stable\n'
assert.equal(updateChannelFromConfig(ended), 'main')
})
// ── latestReleaseFromLsRemote ───────────────────────────────────────
test('release picking is numeric, skips prereleases, prefers peeled shas', () => {
const output = [
`${'a'.repeat(40)}\trefs/tags/v0.9.0`,
`${'b'.repeat(40)}\trefs/tags/v0.10.0`,
`${'c'.repeat(40)}\trefs/tags/v0.10.0^{}`,
`${'d'.repeat(40)}\trefs/tags/v0.11.0-rc1`,
`${'e'.repeat(40)}\trefs/tags/v2026.7.20`
].join('\n')
const latest = latestReleaseFromLsRemote(output)
// v0.10.0 beats v0.9.0 numerically (a lexicographic sort would invert
// it), the rc prerelease is skipped, and the CalVer tag is excluded by
// the three-digit major cap — otherwise 2026 would beat every SemVer
// release forever.
assert.equal(latest?.tag, 'v0.10.0')
assert.equal(latest?.sha, 'c'.repeat(40))
const semverOnly = latestReleaseFromLsRemote(
[`${'a'.repeat(40)}\trefs/tags/v0.9.0`, `${'b'.repeat(40)}\trefs/tags/v0.10.0`, `${'c'.repeat(40)}\trefs/tags/v0.10.0^{}`].join('\n')
)
assert.equal(semverOnly?.tag, 'v0.10.0')
assert.equal(semverOnly?.sha, 'c'.repeat(40))
})
test('release picking returns null when no final release tag exists', () => {
assert.equal(latestReleaseFromLsRemote(''), null)
assert.equal(latestReleaseFromLsRemote(`${'d'.repeat(40)}\trefs/tags/v1.0.0-beta.2`), null)
})