This commit is contained in:
commit
5ec99ce227
668 files changed
+164448
No files matched your search
@@ -0,0 +1,101 @@
|
||||
// 插件可见性分层收口 · 回归测试(文件名沿用,语义已随 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'])
|
||||
})
|
||||
Reference in new issue
Block a user