Files
deepseek-harness/examples/desktop/test/plugin-runtime-fold.test.js
T

254 lines
11 KiB
JavaScript
Raw Normal View History

// Unit tests for plugin-runtime-fold.js — the pure fs-vs-runtime pairing that
// backs the Plugins tab's Runtime column. Kept in node:test because the module
// is dependency-free and doesn't need JSDOM.
'use strict'
const test = require('node:test')
const assert = require('node:assert')
const { foldRuntime, normalize, healthSnapshot, healthPhrase, unknownReasonPhrase } = require('../src/renderer/plugin-runtime-fold.js')
test('normalize strips scope, relative-path, and separators', () => {
assert.strictEqual(normalize('@deepseek-ai/dsh-bash-local'), 'bashlocal')
assert.strictEqual(normalize('../echo-agent/src/mock-llm.ts'), 'mockllm')
assert.strictEqual(normalize('SessionPersistenceJsonl'), 'sessionpersistencejsonl')
assert.strictEqual(normalize(''), '')
assert.strictEqual(normalize('@cordisjs/plugin-timer'), 'plugintimer')
})
test('foldRuntime returns rows with runtime=null when runtimePlugins is undefined', () => {
const entries = [
{ id: 'a', name: '@deepseek-ai/dsh-bash-local', disabled: false, source: 'base' },
]
const { rows, extras } = foldRuntime(entries, undefined)
assert.strictEqual(rows.length, 1)
assert.strictEqual(rows[0].runtime, null)
assert.deepStrictEqual(extras, [])
})
test('foldRuntime pairs on normalized specifier and reports active state', () => {
const entries = [
{ id: 'bash', name: '@deepseek-ai/dsh-bash-local', disabled: false, source: 'base' },
{ id: 'llm', name: '@deepseek-ai/dsh-llm-deepseek', disabled: false, source: 'base' },
]
const runtime = [
{ name: 'BashLocal', state: 'active' },
{ name: 'llm-deepseek', state: 'active' },
]
const { rows, extras } = foldRuntime(entries, runtime)
assert.strictEqual(rows[0].runtime.state, 'active')
assert.strictEqual(rows[0].runtime.mismatch, false)
assert.strictEqual(rows[1].runtime.state, 'active')
assert.strictEqual(rows[1].runtime.mismatch, false)
assert.deepStrictEqual(extras, [])
})
test('foldRuntime flags "configured enabled but not loaded" when no runtime match', () => {
const entries = [
{ id: 'ghost', name: '@example/dsh-not-a-thing', disabled: false, source: 'user' },
]
const { rows } = foldRuntime(entries, [{ name: 'SomethingElse', state: 'active' }])
assert.strictEqual(rows[0].runtime.state, 'absent')
assert.strictEqual(rows[0].runtime.mismatch, true)
assert.match(rows[0].runtime.reason, /configured enabled but not loaded/)
})
test('foldRuntime flags "disabled in overlay but still loaded" when runtime keeps a disabled plugin', () => {
const entries = [
{ id: 'stale', name: '@deepseek-ai/dsh-bash-local', disabled: true, source: 'base' },
]
const { rows } = foldRuntime(entries, [{ name: 'BashLocal', state: 'active' }])
assert.strictEqual(rows[0].runtime.state, 'active')
assert.strictEqual(rows[0].runtime.mismatch, true)
assert.match(rows[0].runtime.reason, /disabled in overlay but still loaded/)
})
test('foldRuntime collects runtime plugins that no fs row claimed into extras', () => {
const entries = [
{ id: 'bash', name: '@deepseek-ai/dsh-bash-local', disabled: false, source: 'base' },
]
const runtime = [
{ name: 'BashLocal', state: 'active' },
// The agent-spine bundle plugs the persistence/subagent stack under names
// that don't appear in the user's cordis.yml — those become "extras",
// which the UI paints as informational tail rows.
{ name: 'SessionPersistenceJsonl', state: 'active' },
{ name: 'SubagentService', state: 'active' },
]
const { rows, extras } = foldRuntime(entries, runtime)
assert.strictEqual(rows.length, 1)
assert.strictEqual(rows[0].runtime.state, 'active')
assert.strictEqual(extras.length, 2)
const extraNames = extras.map((e) => e.name).sort()
assert.deepStrictEqual(extraNames, ['SessionPersistenceJsonl', 'SubagentService'])
})
test('foldRuntime treats pending state as not-yet-a-mismatch', () => {
const entries = [
{ id: 'waiting', name: '@example/dsh-waiting-on-injection', disabled: false, source: 'user' },
]
const { rows } = foldRuntime(entries, [{ name: 'waiting-on-injection', state: 'pending' }])
assert.strictEqual(rows[0].runtime.state, 'pending')
// Pending is legal — the plugin is waiting on an inject, not a mismatch.
assert.strictEqual(rows[0].runtime.mismatch, false)
})
test('foldRuntime flags a failed runtime state for an enabled row', () => {
const entries = [
{ id: 'boom', name: '@example/dsh-broken', disabled: false, source: 'user' },
]
const { rows } = foldRuntime(entries, [{ name: 'broken', state: 'failed' }])
assert.strictEqual(rows[0].runtime.state, 'failed')
assert.strictEqual(rows[0].runtime.mismatch, true)
assert.match(rows[0].runtime.reason, /runtime is failed/)
})
// B-P0-1 (2026-07-16): the diagnostics strip used to shout "Configuration
// OK" while every runtime row underneath showed absent. healthSnapshot +
// healthPhrase pin the layered "5 enabled · 3 running · 2 not loaded"
// phrasing the team-lead ruling nailed down.
test('healthSnapshot: no runtime yet → unknown with expected count preserved', () => {
const fold = {
rows: [
{ id: 'a', name: 'a', disabled: false, source: 'base', runtime: null },
{ id: 'b', name: 'b', disabled: false, source: 'base', runtime: null },
],
}
const snap = healthSnapshot(fold)
assert.strictEqual(snap.status, 'unknown')
assert.strictEqual(snap.expected, 2)
assert.strictEqual(snap.active, 0)
})
test('healthSnapshot: all enabled rows active → status=active, running=expected', () => {
const fold = {
rows: [
{ id: 'a', disabled: false, runtime: { state: 'active' } },
{ id: 'b', disabled: false, runtime: { state: 'loading' } },
{ id: 'c', disabled: true, runtime: null }, // disabled row is not counted
],
}
const snap = healthSnapshot(fold)
assert.deepStrictEqual(snap, {
status: 'active', expected: 2, active: 2, pending: 0, notLoaded: 0,
})
})
test('healthSnapshot: some absent → partial, counters split into buckets', () => {
const fold = {
rows: [
{ id: 'a', disabled: false, runtime: { state: 'active' } },
{ id: 'b', disabled: false, runtime: { state: 'active' } },
{ id: 'c', disabled: false, runtime: { state: 'active' } },
{ id: 'd', disabled: false, runtime: { state: 'pending' } },
{ id: 'e', disabled: false, runtime: { state: 'absent' } },
],
}
const snap = healthSnapshot(fold)
assert.strictEqual(snap.status, 'partial')
assert.strictEqual(snap.expected, 5)
assert.strictEqual(snap.active, 3)
assert.strictEqual(snap.pending, 1)
assert.strictEqual(snap.notLoaded, 1)
})
test('healthPhrase: layered wording drops zero buckets', () => {
assert.strictEqual(
healthPhrase({ status: 'active', expected: 5, active: 5, pending: 0, notLoaded: 0 }),
'5 enabled · 5 running',
)
assert.strictEqual(
healthPhrase({ status: 'partial', expected: 5, active: 3, pending: 0, notLoaded: 2 }),
'5 enabled · 3 running · 2 not loaded',
)
assert.strictEqual(
healthPhrase({ status: 'partial', expected: 5, active: 2, pending: 1, notLoaded: 2 }),
'5 enabled · 2 running · 1 waiting · 2 not loaded',
)
})
test('healthPhrase: unknown status keeps expected in the message when non-zero', () => {
assert.strictEqual(
healthPhrase({ status: 'unknown', expected: 3, active: 0, pending: 0, notLoaded: 0 }),
'3 enabled · runtime status unknown',
)
assert.strictEqual(
healthPhrase({ status: 'unknown', expected: 0, active: 0, pending: 0, notLoaded: 0 }),
'runtime status unknown',
)
})
test('healthPhrase: mismatch (running < enabled) never renders as OK', () => {
// Regression pin for B-P0-1: the strip shouted "Configuration OK" while
// every row said absent. Phrase must always name the mismatch.
const snap = healthSnapshot({
rows: [
{ id: 'a', disabled: false, runtime: { state: 'absent' } },
{ id: 'b', disabled: false, runtime: { state: 'absent' } },
],
})
assert.strictEqual(snap.status, 'partial')
const phrase = healthPhrase(snap)
assert.doesNotMatch(phrase, /ok/i)
assert.match(phrase, /not loaded/)
})
// QA round-3 shot 07 regression pins (2026-07-16):
// stdio profiles hit `plugins:listRuntime` with no supervisor to ask, so
// main-side returns `{supported:false, reason:'no-daemon'}`. The strip
// used to compute a fold-based phrase and misread the empty runtime as
// "0/5 mounted"; now it must show the specific unavailability message.
test('unknownReasonPhrase: no-daemon reason wins over generic snapshot', () => {
const snap = healthSnapshot({
rows: [
{ id: 'a', disabled: false, runtime: { state: 'absent' } },
{ id: 'b', disabled: false, runtime: { state: 'absent' } },
],
})
// Even if the caller managed to build a partial snapshot, if the runtime
// reason is `no-daemon` we surface that — the fold numbers are noise on
// a profile that never has a runtime to reconcile against.
const phrase = unknownReasonPhrase(snap, { supported: false, reason: 'no-daemon' })
assert.strictEqual(phrase, 'runtime state unavailable (no daemon on this profile)')
})
test('unknownReasonPhrase: no-daemon renders regardless of snapshot shape', () => {
const phrase = unknownReasonPhrase(
{ status: 'unknown', expected: 0, active: 0, pending: 0, notLoaded: 0 },
{ supported: false, reason: 'no-daemon' },
)
assert.strictEqual(phrase, 'runtime state unavailable (no daemon on this profile)')
// Regression guard against the earlier phrasing.
assert.doesNotMatch(phrase, /mounted/i)
assert.doesNotMatch(phrase, /0\/\d/) // "0/5 mounted"-style should be gone
})
test('unknownReasonPhrase: MethodNotFound gets its own line', () => {
const phrase = unknownReasonPhrase(
{ status: 'unknown', expected: 3, active: 0, pending: 0, notLoaded: 0 },
{ supported: false, reason: 'MethodNotFound' },
)
assert.strictEqual(phrase, 'runtime state unavailable (daemon does not implement plugins/list)')
})
test('unknownReasonPhrase: fresh boot (no reason) falls back to snapshot + hint', () => {
const phrase = unknownReasonPhrase(
{ status: 'unknown', expected: 4, active: 0, pending: 0, notLoaded: 0 },
null,
)
assert.match(phrase, /4 enabled · runtime status unknown \(Test boot to check\)/)
})
test('unknownReasonPhrase: unknown reason string treated as generic (not misclassified)', () => {
const phrase = unknownReasonPhrase(
{ status: 'unknown', expected: 2, active: 0, pending: 0, notLoaded: 0 },
{ supported: false, reason: 'unexpected wire error' },
)
// Preserve the fallback hint; do NOT expose the raw reason (it's a wire
// string not curated for end users).
assert.match(phrase, /Test boot to check/)
assert.doesNotMatch(phrase, /unexpected wire error/)
})