netxops/tests/compatibility.test.mjs
oliver 9b2166e5b6
Some checks failed
Release package / package (macos-latest) (push) Has been cancelled
Release package / package (ubuntu-latest) (push) Has been cancelled
Release package / package (windows-latest) (push) Has been cancelled
Harden public package installation and release validation
2026-10-11 13:32:36 +08:00

112 lines
4.7 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile, stat } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const read = (path) => readFile(resolve(root, path), 'utf8')
test('package declares Desktop 0.2 client contract', async () => {
const pkg = JSON.parse(await read('package.json'))
assert.match(pkg.version, /^\d+\.\d+\.\d+$/)
assert.equal(pkg.engines.node, '>=20')
assert.equal(pkg.dsh.client.immediately, false)
assert.deepEqual(pkg.dsh.client.inject, [
'@deepseek-ai/dsh-client-ui-slots',
'@deepseek-ai/dsh-client-locale',
])
assert.ok(!pkg.dsh.client.inject.includes('@deepseek-ai/dsh-client-runtime'))
assert.equal(pkg.exports['./cordis.patch.yml'], './cordis.bundle.patch.yml')
assert.ok(!pkg.files.includes('lib/agent-tools-shared.js'))
for (const name of [
'@deepseek-ai/cordis',
'@deepseek-ai/dsh-credentials',
'@deepseek-ai/dsh-settings',
'@deepseek-ai/dsh-tools',
'@deepseek-ai/schemastery',
]) {
assert.equal(pkg.peerDependencies[name], '*', `${name} should be open peer *`)
}
})
test('agent-tools entries are self-contained (no shared chunk)', async () => {
for (const name of [
'agent-tools.js',
'agent-tools-ops.js',
'agent-tools-nms.js',
'agent-tools-common.js',
'agent-tools-topology.js',
'agent-tools-biz-monitor.js',
]) {
const entry = await read(`lib/${name}`)
assert.doesNotMatch(entry, /agent-tools-shared/)
assert.match(entry, /applyGroupToolsPlugin|function apply/)
const info = await stat(resolve(root, `lib/${name}`))
assert.ok(info.size > 20_000, `${name} should be a full bundle, got ${info.size}`)
}
})
test('browser half uses deferred configForms and distinct section id', async () => {
const client = await read('lib/client.js')
assert.match(client, /name:\s*["']settings\.section["']/)
assert.match(client, /NETXOPS_SECTION_ID\s*=\s*["']dsh-netxops["']/)
assert.match(client, /NETXOPS_NS\s*=\s*["']netxops["']/)
assert.match(client, /id:\s*NETXOPS_SECTION_ID/)
assert.match(client, /configForms poll/)
assert.match(client, /createDeferredScope/)
assert.match(client, /tools\.status|TOOLS_STATUS_ENDPOINT|toolsStatus/)
assert.doesNotMatch(client, /inject\(\[["']configForms["']\]/)
assert.doesNotMatch(client, /inject\(\[["']settingsScope["']\]/)
assert.match(client, /name:\s*["']settings\.plugin\.item["']/)
assert.match(client, /key:\s*id/)
assert.doesNotMatch(client, /name:\s*["']plugins\.item["']/)
assert.doesNotMatch(client, /dsh-client-runtime/)
assert.doesNotMatch(client, /mcpServers|python -m netx_mcp/)
})
test('host Config marks scalar fields volatile for 0.2 projection', async () => {
const host = await read('lib/index.js')
assert.match(host, /function vol\(/)
assert.match(host, /\.volatile\s*\(/)
assert.match(host, /NETXOPS_SETTINGS_NAMESPACE\s*=\s*["']netxops["']/)
assert.match(host, /document-updated/)
assert.match(host, /normalizeApiUrl/)
assert.match(host, /mergeHostConfigForHeal/)
assert.match(host, /writePatchIfUnchanged/)
assert.match(host, /groupOpsPublic:\s*vol\(z\.boolean\(\)\.default\(false\)\)/)
assert.doesNotMatch(host, /inject\(\[["']settingsScope["']\]/)
})
test('client validates apiUrl and defaults ops public off', async () => {
const client = await read('lib/client.js')
assert.match(client, /apiUrlField\s*\(/)
assert.match(client, /apiUrlInvalid/)
assert.match(client, /groupOpsPublic:\s*false/)
assert.match(client, /booleanFieldPersistFalse\s*\(\s*["']groupOpsPublic["']\s*\)/)
})
test('tools plugin subscribes before first remount (connection race)', async () => {
const tools = await read('lib/agent-tools.js')
const watchIdx = tools.indexOf('watchNetxConnection')
const remountIdx = tools.indexOf('remountTools()')
assert.ok(watchIdx >= 0 && remountIdx >= 0)
assert.ok(
watchIdx < remountIdx,
'watchNetxConnection must be registered before the first remountTools()',
)
assert.match(tools, /setInterval/)
assert.match(tools, /requireNetxConnection|provisionalNetxConnection/)
})
test('host hard-injects tools; soft credentials + provisional publish', async () => {
const host = await read('lib/index.js')
assert.match(host, /inject\s*=\s*\[\s*["']tools["']\s*\]/)
assert.match(host, /provisional connection published/)
assert.match(host, /plane:\s*["']host["']/)
assert.match(host, /tools\.status/)
assert.match(host, /netxops-mount-status\.json/)
assert.match(host, /additionalProperties:\s*!0|additionalProperties:\s*true/)
assert.match(host, /inject\(\[["']credentials["']\]/)
assert.doesNotMatch(host, /export const inject = \[["']credentials["']\]/)
})