Files
dsh/upstream/container/platform/test/container-integration.test.mjs
T

234 lines
14 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import test from 'node:test'
test('container smoke targets ephemeral views and the separated persistent layout', async () => {
const script = await readFile(new URL('../../test/container-smoke.sh', import.meta.url), 'utf8')
assert.match(script, /\/run\/dsh-platform\/views\/runtime/)
assert.match(script, /\/run\/dsh-platform\/bootstrap\.sock/)
assert.match(script, /\/data\/platform\/state\/updater\/transaction\.json/)
assert.match(script, /\/data\/platform\/store\/snapshots/)
assert.match(script, /recovery\.sock/)
assert.match(script, /dsh-platform restart/)
assert.match(script, /dsh-platform stop/)
assert.match(script, /dsh web --no-open/)
assert.match(script, /requested managed DSH start/)
assert.match(script, /bundled-plugins\/action/)
assert.match(script, /"action":"uninstall"/)
assert.match(script, /managed by the platform/)
assert.match(script, /\.dshLifecycle\.taskId == \$task and \.dshLifecycle\.state == "running"/)
assert.match(script, /stage0_pid/)
assert.match(script, /\.source == "stage0" and \.message == "stage0\.ready"/)
assert.match(script, /\.source == "bootstrap" and \.message == "platform\.ready"/)
assert.match(script, /\.source == "gateway" and \.message == "gateway\.ready"/)
assert.match(script, /dsh_pid/)
assert.match(script, /kill -9 "\$dsh_pid"/)
assert.match(script, /managed-lifecycle-restart-smoke\.mjs/)
assert.match(script, /\.helperExitCode == 0 and \.sawRestarting == true and \.dshCount == 1/)
assert.match(script, /\.dshLifecycle\.state == "running" and \.recoveryMode == null/)
assert.match(script, /DSH recovered after an explicit stop/)
assert.match(script, /\.RestartCount/)
assert.match(script, /gateway\.upstream\.failed/)
assert.match(script, /gateway\.upstream\.recovered/)
assert.match(script, /standalone-recovery-smoke\.mjs/)
assert.match(script, /standalone-file-management-smoke\.mjs/)
assert.match(script, /profile-package-compatibility-smoke\.sh/)
assert.match(script, /--group-add dsh-sudo-false/)
assert.match(script, /sudo -n true/)
assert.match(script, /--unix-socket \/run\/dsh-platform\/maintenance\.sock/)
assert.match(script, /file-task\.copy\.completed/)
assert.match(script, /files\.content\.saved/)
assert.match(script, /runtime\.reset\.completed/)
assert.match(script, /runtime-reset-sentinel/)
assert.match(script, /terminal\.session\.created/)
assert.match(script, /user-plugin\.apply\.completed/)
assert.match(script, /readiness_timeout_ms=30000/)
assert.match(script, /platform readiness exceeded 30 seconds/)
assert.match(script, /DSH orphaned-bundle restart did not complete within the readiness timeout/)
assert.match(script, /DSH started before interrupted update recovery completed/)
assert.doesNotMatch(script, /echo "DSH restart did not complete"/)
assert.match(script, /clear only \/data\/platform/)
assert.match(script, /Do not delete \/data\/dsh/)
assert.doesNotMatch(script, /\/data\/platform\/(?:runtime|environments|system-plugins|bootstrap|run)\//)
})
test('Bootstrap resolves update and User Plugin recovery before concurrent startup', async () => {
const bootstrap = await readFile(new URL('../bootstrap/index.mjs', import.meta.url), 'utf8')
const updateRecovery = bootstrap.indexOf('await recoverPlatformUpdateBeforeDshStart(')
const userPluginRecovery = bootstrap.indexOf('await recoverUserPluginBeforeDshStart(')
const runtimeStart = bootstrap.indexOf('await runtime.start({')
assert.ok(updateRecovery >= 0)
assert.ok(userPluginRecovery > updateRecovery)
assert.ok(runtimeStart > userPluginRecovery)
})
test('Profile compatibility smoke migrates old stores and preserves node ownership', async () => {
const script = await readFile(new URL('../../test/profile-package-compatibility-smoke.sh', import.meta.url), 'utf8')
assert.match(script, /dsh-platform stop --wait/)
assert.match(script, /pnpm install --offline --frozen-lockfile/)
assert.match(script, /dsh-platform start --wait/)
assert.match(script, /npm_config_userconfig=\/root\/\.config\/npm\/npmrc/)
assert.match(script, /dsh plugin --profile web add/)
assert.match(script, /stat -c %U/)
assert.match(script, /dsh plugin --profile web remove/)
})
test('the public DSH shim keeps Profile operations under the node identity', async () => {
const script = await readFile(new URL('../tools/dsh-shim.sh', import.meta.url), 'utf8')
assert.match(script, /if \[ "\$\(id -u\)" -eq 0 \]/)
assert.match(script, /--reuid=1000/)
assert.match(script, /--regid=1000/)
assert.match(script, /--keep-groups/)
assert.match(script, /-u npm_config_userconfig/)
assert.match(script, /-u NPM_CONFIG_USERCONFIG/)
assert.match(script, /-u PNPM_HOME/)
assert.match(script, /HOME=\/home\/node/)
assert.match(script, /XDG_CONFIG_HOME=\/home\/node\/\.config/)
assert.match(script, /XDG_STATE_HOME=\/home\/node\/\.local\/state/)
assert.match(script, /DSH_HOME="\$\{DSH_HOME:-\/data\/dsh\}"/)
assert.doesNotMatch(script, /HOME=\/root|XDG_CONFIG_HOME=\/root/)
})
test('managed lifecycle smoke rejects replacement instances and converts SIGTERM into restart', async () => {
const script = await readFile(new URL('../../test/managed-lifecycle-restart-smoke.mjs', import.meta.url), 'utf8')
assert.match(script, /spawn\(DSH_EXECUTABLE, \['web', '--no-open'\]/)
assert.match(script, /detached: true/)
assert.match(script, /DSH_PLATFORM_LAUNCH_TOKEN: ''/)
assert.match(script, /process\.kill\(oldPid, 'SIGTERM'\)/)
assert.match(script, /unregistered SIGTERM did not create a managed restart task/)
assert.match(script, /newPids\.length === 1/)
assert.doesNotMatch(script, /dsh-market|better-sidebar|net-proxy/)
})
test('standalone file smoke covers recovery-mode transfer, edit, search, symlink, and mutations', async () => {
const script = await readFile(new URL('../../test/standalone-file-management-smoke.mjs', import.meta.url), 'utf8')
assert.match(script, /files\/upload/)
assert.match(script, /range: 'bytes=3-6'/)
assert.match(script, /edited in recovery mode/)
assert.match(script, /operation: 'copy'/)
assert.match(script, /operation: 'move'/)
assert.match(script, /operation: 'delete'/)
assert.match(script, /operation: 'search'/)
assert.match(script, /operation: 'size'/)
assert.match(script, /operation: 'attributes'/)
assert.match(script, /recursive: true/)
assert.match(script, /type, 'symlink'/)
assert.match(script, /\/data\/dsh\/file-management-smoke/)
assert.match(script, /\/root\/dsh-maintenance-smoke/)
assert.match(script, /config\.privileged, true/)
assert.match(script, /\['queued', 'running'\]\.includes\(state\.status\)/)
})
test('standalone recovery smoke covers live terminal and consecutive faulty plugins', async () => {
const script = await readFile(new URL('../../test/standalone-recovery-smoke.mjs', import.meta.url), 'utf8')
assert.match(script, /terminal-before-restart/)
assert.match(script, /terminal-after-restart-中文/)
assert.match(script, /operation\?\.status \?\? operation\?\.state/)
assert.match(script, /uid=0/)
assert.match(script, /cwd=\\\/root/)
assert.match(script, /smoke-fault-one/)
assert.match(script, /smoke-fault-two/)
assert.match(script, /firstOutcome/)
assert.match(script, /userPluginOperation/)
assert.match(script, /recoveryMode === null/)
assert.match(script, /assert\.doesNotMatch\(JSON\.stringify\(logEntries\.filter/)
})
test('Stage-0 owns privileged User Skill mutations behind a constrained local API', async () => {
const [stage0, management, paths] = await Promise.all([
readFile(new URL('../stage0/index.mjs', import.meta.url), 'utf8'),
readFile(new URL('../../control-plane/services/management/index.mjs', import.meta.url), 'utf8'),
readFile(new URL('../lib/paths.mjs', import.meta.url), 'utf8'),
])
assert.match(stage0, /prepareUserSkillRoots, UserSkillManager/)
assert.match(stage0, /new UserSkillManager\(\{ dshHome, agentsHome \}\)/)
assert.match(stage0, /listenUserSkills\(userSkillServer, paths\.userSkillSocket\)/)
assert.match(management, /new LocalApiClient\(paths\.userSkillSocket\)/)
assert.match(management, /configureUserSkill: value => userSkillApi\.request\('POST', '\/v1\/action', value\)/)
assert.match(paths, /this\.userSkillSocket = join\(this\.runRoot, 'user-skills\.sock'\)/)
})
test('isolated service identities can traverse the runtime root without inheriting platform socket access', async () => {
const paths = await readFile(new URL('../lib/paths.mjs', import.meta.url), 'utf8')
const accessLaunch = await readFile(new URL('../stage0/lib/access-launch-server.mjs', import.meta.url), 'utf8')
assert.match(paths, /chmod\(paths\.runRoot, 0o1771\)/)
assert.match(accessLaunch, /'--clear-groups'/)
assert.doesNotMatch(accessLaunch, /--groups=/)
})
test('Docker health reports Gateway control-plane liveness independently of DSH', async () => {
const dockerfile = await readFile(new URL('../../../Dockerfile', import.meta.url), 'utf8')
const healthcheck = dockerfile.split('HEALTHCHECK')[1]?.split('\n\n')[0] ?? ''
assert.match(healthcheck, /--start-period=60s/)
assert.match(healthcheck, /127\.0\.0\.1:3080\/_dsh_gateway\/health/)
assert.doesNotMatch(healthcheck, /127\.0\.0\.1:3079\/|stage0-trust\.sock/)
})
test('Management readiness does not depend on an authenticated management API', async () => {
const component = JSON.parse(await readFile(new URL('../../control-plane/services/management/component.json', import.meta.url)))
assert.equal(component.health.type, 'exec')
assert.equal(component.health.command.executable, '/usr/local/bin/node')
assert.deepEqual(component.health.command.args, [
'/run/dsh-platform/views/bootstrap/control-plane/services/management/healthcheck.mjs',
])
const probe = await readFile(new URL('../../control-plane/services/management/healthcheck.mjs', import.meta.url), 'utf8')
assert.match(probe, /\/_dsh_platform\/internal\/health/)
assert.doesNotMatch(probe, /\/_dsh_platform\/api\/v1/)
})
test('public platform CLI and Gateway status probes use a separate restricted Management socket', async () => {
const [paths, cli, management, gateway] = await Promise.all([
readFile(new URL('../lib/paths.mjs', import.meta.url), 'utf8'),
readFile(new URL('../../control-plane/services/management/cli.mjs', import.meta.url), 'utf8'),
readFile(new URL('../../control-plane/services/management/index.mjs', import.meta.url), 'utf8'),
readFile(new URL('../../control-plane/services/gateway/lib/lifecycle.mjs', import.meta.url), 'utf8'),
])
assert.match(paths, /this\.managementCliSocket = join\(this\.runRoot, 'management-cli\.sock'\)/)
assert.match(cli, /DSH_PLATFORM_MANAGEMENT_CLI_SOCKET.*management-cli\.sock/)
assert.match(management, /restrictedCliRoute/)
assert.match(gateway, /DSH_PLATFORM_MANAGEMENT_CLI_SOCKET.*management-cli\.sock/)
})
test('Docker installs locked Management dependencies inside the Seed stage only', async () => {
const dockerfile = await readFile(new URL('../../../Dockerfile', import.meta.url), 'utf8')
const ignore = await readFile(new URL('../../../.dockerignore', import.meta.url), 'utf8')
const seed = await readFile(new URL('../stage0/lib/seed.mjs', import.meta.url), 'utf8')
const buildSeed = await readFile(new URL('../tools/build-seed.mjs', import.meta.url), 'utf8')
assert.match(dockerfile, /npm ci --omit=dev --ignore-scripts --no-audit --no-fund[\s\S]*--prefix \/opt\/dsh-platform-source\/control-plane\/services\/management/)
assert.match(dockerfile, /npm ci --omit=dev --ignore-scripts --no-audit --no-fund --legacy-peer-deps[\s\S]*--prefix \/opt\/dsh-platform-source\/environment\/resources\/plugins\/platform-management\/package/)
assert.match(dockerfile, /util-linux/)
assert.match(dockerfile, /\n\s+g\+\+ \\\n/)
assert.match(dockerfile, /\n\s+make \\\n/)
assert.equal(ignore.split('\n').includes('**/node_modules'), true)
assert.match(seed, /paths\.managementStateRoot/)
assert.match(seed, /paths\.managementStateRoot, paths\.fileTasksRoot/)
assert.match(seed, /paths\.userPluginSnapshotsRoot/)
assert.match(buildSeed, /file-manager/)
const managementManifest = dockerfile.indexOf('COPY container/control-plane/services/management/package.json')
const pluginManifest = dockerfile.indexOf('COPY container/environment/resources/plugins/platform-management/package/package.json')
const dependencyInstall = dockerfile.indexOf('RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund')
const sourceCopy = dockerfile.indexOf('COPY container /opt/dsh-platform-source')
assert.ok(managementManifest >= 0 && pluginManifest > managementManifest)
assert.ok(dependencyInstall > pluginManifest && sourceCopy > dependencyInstall)
})
test('local Docker builds resolve the default DSH from the supported target without a sticky latest cache', async () => {
const dockerfile = await readFile(new URL('../../../Dockerfile', import.meta.url), 'utf8')
assert.match(dockerfile, /COPY release\/supported-target\.json \/opt\/dsh-supported-target\.json/)
assert.match(dockerfile, /ARG SIGNED_IMAGE_INPUT=false/)
assert.match(dockerfile, /case "\$SIGNED_IMAGE_INPUT" in[\s\S]*true\)[\s\S]*npm install --global \/opt\/dsh-platform-image-input\/dsh\.tgz[\s\S]*false\)[\s\S]*latestSupportedDsh/)
assert.match(dockerfile, /if \[ "\$requested_version" = latest \]; then[\s\S]*latestSupportedDsh/)
assert.match(dockerfile, /npm install --global "@deepseek-ai\/dsh@\$\{requested_version\}"/)
assert.match(dockerfile, /false\) image_input=-/)
assert.doesNotMatch(dockerfile, /if \[ -f \/opt\/dsh-platform-(?:image-input|source\/platform\/image-input)/)
})
test('formal images identify DSH, Environment, target sequence, and the signed update channel', async () => {
const dockerfile = await readFile(new URL('../../../Dockerfile', import.meta.url), 'utf8')
assert.match(dockerfile, /org\.opencontainers\.image\.version="\$DSH_VERSION"/)
assert.match(dockerfile, /io\.dsh-docker\.environment\.version="\$ENVIRONMENT_VERSION"/)
assert.match(dockerfile, /io\.dsh-docker\.target-sequence="\$TARGET_SEQUENCE"/)
assert.match(dockerfile, /DSH_UPDATE_METADATA_URL=https:\/\/raw\.githubusercontent\.com\/yjrszcq\/dsh-docker\/release-channel\//)
assert.doesNotMatch(dockerfile, /releases\/latest\/download/)
})