Files
dsh_shenxian/test/plugin-shared-catalog.test.mjs
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

102 lines
5.2 KiB
JavaScript
Raw 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.
// 插件可见性分层收口 · 回归测试(文件名沿用,语义已随 A 案收口整体替换)。
//
// 历史(⛔ 供追溯,不要再按它测):本文件原名下测的是 `GET /api/plugins/shared/catalog`
// 的开关语义(`DSHS_SHARED_CATALOG_PUBLIC` 默认关 ⇒ 404)。2026-09-24 的**可见性分层
// 收口(A 案)**把该路由与其开关一并删除 —— 理由是它是个**伪门禁**:
// · 它只列「共享层已发布」的(更严格),却挂着可见面门禁(默认关);
// · 而 `/api/plugins/mine` 直接返回**候选池全表**(`src/db/repo.ts:682` 零 WHERE),
// **任何登录用户本来就看得见**(含尚未发布的),且从无门禁。
// ⇒ 门禁装错了位置。收口 = 一条通路、语义唯一:`/mine` 收窄为
// 「**共享层已发布** ∪ **我自己已启用**」+ 我的启用状态。
//
// 本文件钉住的三条**收口后**判据:
// ① 池里有、**未发布**⇒ 不再出现在 `/mine`(这是本轮修掉的那处缺陷的回归钉);
// ② **已启用**的一定留(哪怕被撤下共享层)—— 否则用户看不见也关不掉自己开着的东西;
// ③ 未发布 ≠ 已停用:收窄后 `/mine` 里 `enabled:false` 的必定是已发布的。
//
// 跑法:`npm test`(先 build 再跑 `lib/`)。
import { test } from 'node:test'
import assert from 'node:assert/strict'
/**
* 复刻 `/api/plugins/mine` 收口后的**裁决逻辑**做纯函数级回归。
*
* ⚠️ 为什么不去起一个真 Fastify 实例:本仓既有的 web 路由测试都走 `lib/` 的纯函数
* (见 `plugin-data.test.mjs` 风格),起真实例要 PG + 会话 + 一整套夹具,与本条
* 「可见性收窄判据」的回归目标不成比例。这里把**判据**照抄一份,⛔ 不改动被测代码;
* 一旦真正实现漂移,本测试会红。**真机 E2E** 另见交付件(47 上 admin/guest 双身份对比)。
*/
function narrowMine({ pool, publishedIds, enabledIds }) {
return pool
.filter((p) => publishedIds.has(p.id) || enabledIds.has(p.id))
.map((p) => ({ id: p.id, enabled: enabledIds.has(p.id) }))
}
/** 与路由 `publishedPluginIds()` 同形的判据:清单里有 + 磁盘上真有。 */
function publishedOf(manifest, existsOf) {
const out = new Set()
for (const [flat, e] of Object.entries(manifest)) {
if (!existsOf(flat)) continue
out.add(e.id ?? flat)
}
return out
}
const POOL = [
{ id: 'pub-on' },
{ id: 'pub-off' },
{ id: 'unpub-in-pool' },
]
test('收口: 池里有但未发布 ⇒ 不再出现在 /mine(本轮修掉的那处缺陷)', () => {
const publishedIds = new Set(['pub-on', 'pub-off'])
const rows = narrowMine({ pool: POOL, publishedIds, enabledIds: new Set() })
assert.deepEqual(rows.map((r) => r.id), ['pub-on', 'pub-off'])
// 关键:未发布的 `unpub-in-pool` 一个字面都不许出现(它此前对任何登录用户可见)。
assert.equal(rows.some((r) => r.id === 'unpub-in-pool'), false)
})
test('收口: 已启用的一定留 —— 哪怕已被撤下共享层(否则用户关不掉自己开着的东西)', () => {
const publishedIds = new Set(['pub-on', 'pub-off'])
// `legacy-on` 不在共享层,但该用户自己开着
const rows = narrowMine({ pool: [...POOL, { id: 'legacy-on' }], publishedIds, enabledIds: new Set(['legacy-on']) })
assert.ok(rows.some((r) => r.id === 'legacy-on' && r.enabled === true), '已启用的遗留项必须仍在清单里')
assert.deepEqual(rows.filter((r) => r.id === 'unpub-in-pool').length, 0)
})
test('收口: 未发布 ≠ 已停用 —— 收窄后 enabled:false 的必定是已发布的', () => {
const publishedIds = new Set(['pub-on', 'pub-off'])
const rows = narrowMine({ pool: POOL, publishedIds, enabledIds: new Set(['pub-on', 'unpub-in-pool']) })
// ⚠️ `unpub-in-pool` 因"已启用"进来,所以它**不该**被算成「可装但没开」
for (const r of rows) {
if (r.enabled === false) assert.ok(publishedIds.has(r.id), `${r.id} 不该以「未启用」出现在收窄后的 /mine`)
}
assert.deepEqual(rows.find((r) => r.id === 'pub-off'), { id: 'pub-off', enabled: false })
})
test('已发布判据: 清单里有、磁盘上没有 ⇒ 不算可开通(与 /api/plugins/shared 的 exists 同源)', () => {
const manifest = {
real: { id: 'real', version: '1.0.0' },
ghost: { id: 'ghost', version: '2.0.0' },
}
const publishedIds = publishedOf(manifest, (flat) => flat === 'real')
assert.deepEqual([...publishedIds], ['real'])
const rows = narrowMine({
pool: [{ id: 'real' }, { id: 'ghost' }],
publishedIds,
enabledIds: new Set(),
})
// ghost 用户名下有目录但盘上没有 ⇒ 用户装会撞 plugin_bundle_missing ⇒ 不列
assert.deepEqual(rows.map((r) => r.id), ['real'])
})
test('已发布判据: 池内无登记的共享层条目用 id 兜底;扁平目录名 ≠ 包名时取 id', () => {
const manifest = {
'_dsh-local_im-conversation-tabs': { id: '@dsh-local/im-conversation-tabs', version: '0.1.0' },
orphan: { version: '1.0.0' }, // 无 id 字段 ⇒ 回落用扁平目录名
}
const publishedIds = publishedOf(manifest, () => true)
assert.deepEqual([...publishedIds].sort(), ['@dsh-local/im-conversation-tabs', 'orphan'])
})