Files
admin e6207aa691
build / build-and-scan (push) Waiting to run
chore(仓库对齐): 文档库结构治理 + IM/插件线落地
文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。

IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。

插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。

仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
2026-09-24 07:25:16 +08:00

415 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// DB adapter regression tests (node:test). Runs against the built `lib/`
// output — `npm test` builds first. Covers the constraint-mapping and
// transaction semantics shared by both backends:
// - unique / foreign-key errors converge on UniqueViolationError /
// ForeignKeyViolationError
// - the "disable-all-then-enable-one" credential upsert keeps exactly one
// enabled key under concurrent writes
// - concurrent createUser / audit writes land cleanly
// The Postgres suite self-skips unless DSHS_TEST_DB_URL is set
// (mirrors the CI e2e self-skip convention).
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { SqliteAdapter } from '../lib/db/sqlite.js'
import { openPgAdapter } from '../lib/db/pg.js'
import { ForeignKeyViolationError, UniqueViolationError } from '../lib/db/errors.js'
const user = (id, username) => ({ id, username, passHash: 'x', role: 'active', homeDir: `/home/${username}` })
function register(backend, makeAdapter) {
test(`${backend}: duplicate username → UniqueViolationError`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
await assert.rejects(() => db.createUser(user('b', 'alice')), UniqueViolationError)
} finally {
await db.close()
}
})
test(`${backend}: session for unknown user → ForeignKeyViolationError`, async () => {
const db = await makeAdapter()
try {
await assert.rejects(
() => db.createSession({ tokenHash: 't', userId: 'missing', expiresAt: Date.now() + 1000 }),
ForeignKeyViolationError,
)
} finally {
await db.close()
}
})
// 档案 87:条目口径从「互斥单选」改为「**各自开关、可同时启用**」⇒ 老断言整体改写。
test(`${backend}: concurrent setCredentialKey keeps every entry enabled (不再互斥)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
await Promise.all(
Array.from({ length: 5 }, (_, i) => db.setCredentialKey('a', `k${i}`, `ref${i}`)),
)
const keys = await db.listCredentialKeys('a')
assert.equal(keys.length, 5, 'five rows')
// 写新条目**不再**把其它条目全关(老实现是 `SET enabled = 0 WHERE user_id = ?`)。
assert.equal(keys.filter((k) => k.enabled).length, 5, 'every entry stays enabled')
assert.equal((await db.listEnabledCredentialKeys('a')).length, 5, 'listEnabled agrees with list')
const ref = await db.getEnabledCredentialKeyRef('a')
assert.ok(ref !== null && ref.startsWith('ref'), 'enabled key has a ref')
} finally {
await db.close()
}
})
test(`${backend}: toggleCredentialKey 只动一行(不影响其它条目)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
const k1 = await db.setCredentialKey('a', 'k1', 'r1')
await db.setCredentialKey('a', 'k2', 'r2')
assert.equal(await db.toggleCredentialKey('a', k1.id, false), true)
const keys = await db.listCredentialKeys('a')
assert.equal(keys.find((k) => k.id === k1.id).enabled, false, 'target row off')
assert.equal(keys.find((k) => k.name === 'k2').enabled, true, 'the other row untouched')
assert.equal((await db.listEnabledCredentialKeys('a')).length, 1, 'only one enabled now')
// 不存在的 id ⇒ false(路由据此回 404)
assert.equal(await db.toggleCredentialKey('a', 'nope', true), false)
} finally {
await db.close()
}
})
test(`${backend}: getEnabledCredentialKeyRef 只认内置条目(档案 87 语义重定义)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
// 自定义厂家(给了 baseUrl)**不是**"用户自己的 DeepSeek key",否则 keySourceOf /
// resolveApiKey 会把一个网关的 key 当成平台内置 key 用。
await db.setCredentialKey('a', 'gw', 'gwref', {
route: 'my-gw',
baseUrl: 'https://api.example.com/v1',
api: 'openai-completions',
models: '["gpt-4o"]',
})
assert.equal(await db.getEnabledCredentialKeyRef('a'), null, 'custom provider is not the builtin ref')
await db.setCredentialKey('a', 'ds', 'dsref')
assert.equal(await db.getEnabledCredentialKeyRef('a'), 'dsref', 'builtin entry wins')
// 元数据必须原样存回来(route / baseUrl / api / models)
const gw = (await db.listCredentialKeys('a')).find((k) => k.name === 'gw')
assert.equal(gw.route, 'my-gw')
assert.equal(gw.baseUrl, 'https://api.example.com/v1')
assert.equal(gw.api, 'openai-completions')
assert.equal(gw.models, '["gpt-4o"]')
} finally {
await db.close()
}
})
test(`${backend}: sharedModelEnabled 默认开、可关(档案 87 口径②)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
assert.equal(await db.getSharedModelEnabled('a'), true, 'V6 默认 true')
assert.equal(await db.setSharedModelEnabled('a', false), true)
assert.equal(await db.getSharedModelEnabled('a'), false)
assert.equal(await db.setSharedModelEnabled('a', true), true)
assert.equal(await db.getSharedModelEnabled('a'), true)
} finally {
await db.close()
}
})
// 档案 138(v11):管理员逐用户授权 —— **默认关闭**、可开可关、且与用户偏好**互相独立**。
// 这三条判据是门禁的全部内容;它们任一被写反(尤其"默认"那条)都等于门禁不存在。
test(`${backend}: sharedModelGranted 默认关、可开可关,且与用户偏好互不影响(档案 138)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
assert.equal(await db.getSharedModelGranted('a'), false, 'v11 默认 false(授权默认关闭)')
assert.equal(await db.setSharedModelGranted('a', true), true)
assert.equal(await db.getSharedModelGranted('a'), true)
// 用户偏好仍是它自己的默认值 —— 授权不改变偏好(两列互不覆盖)。
assert.equal(await db.getSharedModelEnabled('a'), true, '授权不改动用户侧偏好')
assert.equal(await db.setSharedModelGranted('a', false), true)
assert.equal(await db.getSharedModelGranted('a'), false)
// 未知用户:授权按 false(**失败关闭**),偏好按 true(宁可多给)—— 两条故意相反,钉死它。
assert.equal(await db.getSharedModelGranted('nobody'), false)
assert.equal(await db.getSharedModelEnabled('nobody'), true)
} finally {
await db.close()
}
})
test(`${backend}: listPublicUsers 带出 sharedModelGranted(admin 列表要用)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
await db.setSharedModelGranted('a', true)
const users = await db.listPublicUsers()
assert.equal(users.length, 1)
assert.equal(users[0].sharedModelGranted, true)
} finally {
await db.close()
}
})
test(`${backend}: selectCredentialKey 不再关掉别的条目(档案 87)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
const k1 = await db.setCredentialKey('a', 'k1', 'r1')
const k2 = await db.setCredentialKey('a', 'k2', 'r2')
// 两条内置条目都启用 ⇒ 取"最新一条"(两条写在同一毫秒时由 id 决定,故这里只断非空)
assert.ok((await db.getEnabledCredentialKeyRef('a')) !== null)
assert.equal(await db.selectCredentialKey('a', k1.id), true)
// 老语义会先把所有条目关掉再开这一个;新语义只保证"这一个开"。
const keys = await db.listCredentialKeys('a')
assert.equal(keys.find((k) => k.id === k2.id).enabled, true, 'another entry is not switched off')
assert.equal(keys.find((k) => k.id === k1.id).enabled, true, 'target stays enabled')
} finally {
await db.close()
}
})
test(`${backend}: concurrent createUser`, async () => {
const db = await makeAdapter()
try {
await Promise.all(Array.from({ length: 20 }, (_, i) => db.createUser(user(`u${i}`, `user${i}`))))
assert.equal((await db.listPublicUsers()).length, 20)
} finally {
await db.close()
}
})
test(`${backend}: getOrCreateWorkspace is idempotent`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
const w1 = await db.getOrCreateWorkspace('a', 'proj/one')
const w2 = await db.getOrCreateWorkspace('a', 'proj/one')
assert.equal(w1.id, w2.id)
} finally {
await db.close()
}
})
test(`${backend}: createUser assigns a unique uid`, async () => {
const db = await makeAdapter()
try {
const a = await db.createUser(user('a', 'alice'))
const b = await db.createUser(user('b', 'bob'))
assert.ok(a.uid !== null && a.uid !== undefined, 'first user has a uid')
assert.notEqual(a.uid, b.uid, 'uids are unique across users')
assert.equal((await db.listUsersWithoutUid()).length, 0, 'no unassigned uids remain')
} finally {
await db.close()
}
})
test(`${backend}: concurrent audit writes`, async () => {
const db = await makeAdapter()
try {
await Promise.all(Array.from({ length: 10 }, (_, i) => db.audit('system', 'test', `detail-${i}`)))
} finally {
await db.close()
}
})
test(`${backend}: upsertInstance round-trips folder + patch and is idempotent`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
await db.upsertInstance({ id: 'dsh-a', userId: 'a', role: 'main', status: 'starting', folder: '/ws/proj', patch: '- insert:\n' })
const first = await db.findInstance('dsh-a')
assert.equal(first.folder, '/ws/proj')
assert.equal(first.patch, '- insert:\n')
assert.equal(first.status, 'starting')
assert.ok(first.startedAt > 0, 'started_at stamped')
// Same deterministic id → overwrite, not a duplicate row.
await db.upsertInstance({ id: 'dsh-a', userId: 'a', role: 'main', status: 'running', folder: '/ws/other' })
const second = await db.findInstance('dsh-a')
assert.equal(second.folder, '/ws/other')
assert.equal(second.patch, null, 'omitted patch clears the column')
assert.equal((await db.listInstancesByRole('main')).filter((i) => i.userId === 'a').length, 1)
} finally {
await db.close()
}
})
test(`${backend}: setInstanceStatus records the exit outcome`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
await db.upsertInstance({ id: 'dsh-a', userId: 'a', role: 'main', status: 'running', folder: '/ws' })
assert.equal(await db.setInstanceStatus('dsh-a', 'crashed', { exitCode: 137, lastError: 'OOMKilled' }), true)
const row = await db.findInstance('dsh-a')
assert.equal(row.status, 'crashed')
assert.equal(row.exitCode, 137)
assert.equal(row.lastError, 'OOMKilled')
assert.ok(row.lastExit > 0, 'last_exit stamped')
assert.equal(await db.setInstanceStatus('dsh-missing', 'stopped'), false, 'unknown id reports no change')
} finally {
await db.close()
}
})
test(`${backend}: instances are scoped by user and role, and cascade on user delete`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
await db.createUser(user('b', 'bob'))
await db.upsertInstance({ id: 'dsh-a', userId: 'a', role: 'main', status: 'running', folder: '/ws' })
await db.upsertInstance({ id: 'dsh-a-watchdog', userId: 'a', role: 'watchdog', status: 'starting' })
await db.upsertInstance({ id: 'dsh-b', userId: 'b', role: 'main', status: 'running', folder: '/ws' })
assert.equal((await db.findUserInstance('a', 'main')).id, 'dsh-a')
assert.equal((await db.findUserInstance('a', 'watchdog')).id, 'dsh-a-watchdog')
assert.equal((await db.listInstancesByRole('main')).length, 2)
assert.equal((await db.listInstancesByRole('watchdog')).length, 1)
await db.deleteUserInstances('a')
assert.equal(await db.findUserInstance('a', 'main'), undefined)
assert.equal((await db.listInstancesByRole('main')).length, 1, "b's instance survives")
} finally {
await db.close()
}
})
test(`${backend}: hasActiveSession reflects unexpired vs expired sessions`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('a', 'alice'))
assert.equal(await db.hasActiveSession('a'), false, 'no session → inactive')
await db.createSession({ tokenHash: 't1', userId: 'a', expiresAt: Date.now() + 60_000 })
assert.equal(await db.hasActiveSession('a'), true, 'unexpired session → active')
await db.createSession({ tokenHash: 't2', userId: 'a', expiresAt: Date.now() - 1000 })
assert.equal(await db.hasActiveSession('a'), true, 'at least one unexpired session → active')
await db.deleteSession('t1')
assert.equal(await db.hasActiveSession('a'), false, 'only expired session left → inactive')
} finally {
await db.close()
}
})
test(`${backend}: instance for unknown user → ForeignKeyViolationError`, async () => {
const db = await makeAdapter()
try {
await assert.rejects(
() => db.upsertInstance({ id: 'dsh-ghost', userId: 'missing', role: 'main', status: 'starting' }),
ForeignKeyViolationError,
)
} finally {
await db.close()
}
})
// ── v12 · 设备台账(序㊻ 步骤 6 / S3)────────────────────────────────────────
test(`${backend}: upsertOverlayDevice —— 首签 = 1、续签 = +1(同一行)、租约被刷新`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('u1', 'u1'))
const t0 = 1_700_000_000_000
const base = { network: 'u:u1', hostId: 'd-u1-abcdef12', userId: 'u1', nodeKey: 'ab'.repeat(32) }
const first = await db.upsertOverlayDevice({ ...base, leaseExpiresAt: t0 + 1000, at: t0 })
assert.equal(first.grantRenewals, 1, '首次插入 = 1')
assert.equal(first.status, 'active', '首次插入 = active')
assert.equal(first.createdAt, t0)
assert.equal(first.grantIssuedAt, t0)
const second = await db.upsertOverlayDevice({ ...base, leaseExpiresAt: t0 + 2000, at: t0 + 500 })
assert.equal(second.grantRenewals, 2, '续签 = 旧值 + 1(⛔ 不是恒为 1,也不是 2 卡住)')
assert.equal(second.leaseExpiresAt, t0 + 2000, '租约必须被刷新')
assert.equal(second.createdAt, t0, 'created_at 不被续签改动')
const third = await db.upsertOverlayDevice({ ...base, leaseExpiresAt: t0 + 3000, at: t0 + 900 })
assert.equal(third.grantRenewals, 3, '第三次 = 3(计数真的在累加)')
assert.equal((await db.listOverlayDevices('u:u1')).length, 1, '续签 = 同一行,⛔ 不是插新行')
// 按网过滤 / 按 userId 归属
await db.createUser(user('u2', 'u2'))
await db.upsertOverlayDevice({
network: 'u:u2',
hostId: 'd-u2-11111111',
userId: 'u2',
nodeKey: 'cd'.repeat(32),
leaseExpiresAt: t0 + 1000,
at: t0,
})
assert.equal((await db.listOverlayDevices()).length, 2, '不带过滤 = 全部')
assert.equal((await db.listOverlayDevices('u:u2')).length, 1, '按网过滤')
assert.equal((await db.findOverlayDevice('u:u2', 'd-u2-11111111')).userId, 'u2')
assert.equal(await db.findOverlayDevice('u:u2', 'd-u2-22222222'), undefined, '不存在 ⇒ undefined(⛔ 不抛)')
} finally {
await db.close()
}
})
test(`${backend}: setOverlayDeviceStatus —— 停发是**黏性**的(续签不复活)+ 不存在的行 = false`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('u3', 'u3'))
const row = {
network: 'u:u3',
hostId: 'd-u3-abcdef12',
userId: 'u3',
nodeKey: 'ef'.repeat(32),
leaseExpiresAt: Date.now() + 1000,
}
await db.upsertOverlayDevice(row)
assert.equal(await db.setOverlayDeviceStatus('u:u3', 'd-u3-abcdef12', 'revoked'), true)
assert.equal((await db.findOverlayDevice('u:u3', 'd-u3-abcdef12')).status, 'revoked')
// 🔴 关键:续签**不许**把它复活(SQL 层的 DO UPDATE SET 里没有 status 这一列)
const renewed = await db.upsertOverlayDevice({ ...row, leaseExpiresAt: Date.now() + 9999 })
assert.equal(renewed.status, 'revoked', '⚠️ 续签不得让被停发的设备自动复活')
assert.equal(renewed.grantRenewals, 2, '但它确实是一次续签(计数照常累加)')
// 恢复只能显式来
assert.equal(await db.setOverlayDeviceStatus('u:u3', 'd-u3-abcdef12', 'active'), true)
assert.equal((await db.findOverlayDevice('u:u3', 'd-u3-abcdef12')).status, 'active')
// 不存在的行 ⇒ false(路由据此回 404)
assert.equal(await db.setOverlayDeviceStatus('u:u3', 'd-u3-00000000', 'revoked'), false)
} finally {
await db.close()
}
})
test(`${backend}: overlay_devices 归属 user(删除用户即级联清理台账)`, async () => {
const db = await makeAdapter()
try {
await db.createUser(user('u4', 'u4'))
await db.upsertOverlayDevice({
network: 'u:u4',
hostId: 'd-u4-abcdef12',
userId: 'u4',
nodeKey: 'aa'.repeat(32),
leaseExpiresAt: Date.now() + 1000,
})
assert.equal((await db.listOverlayDevices()).length, 1)
await db.deleteUser('u4')
assert.equal((await db.listOverlayDevices()).length, 0, '删用户应当把台账一起带走(ON DELETE CASCADE)')
// 未知用户 ⇒ 外键违例(与其它表同一口径)
await assert.rejects(
() =>
db.upsertOverlayDevice({
network: 'u:ghost',
hostId: 'd-ghost-abcdef12',
userId: 'missing',
nodeKey: 'bb'.repeat(32),
leaseExpiresAt: Date.now(),
}),
ForeignKeyViolationError,
)
} finally {
await db.close()
}
})
}
register('sqlite', () => new SqliteAdapter(':memory:', 100000))
const pgUrl = process.env.DSHS_TEST_DB_URL
if (pgUrl) {
register('pg', () => openPgAdapter(pgUrl, 100000))
} else {
test('pg: skipped — set DSHS_TEST_DB_URL to run', { skip: 'no DSHS_TEST_DB_URL' }, () => {})
}