@mzzsfy/dsh-context-manager 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ // global 浮层轮询指纹缓存 BDD:stat 指纹命中不重复读盘,产物变更即失效,
2
+ // 删除/损坏文件与目录缺失不残留缓存条目。
3
+
4
+ import { test } from 'node:test'
5
+ import assert from 'node:assert/strict'
6
+ import { mkdtemp, writeFile, utimes, rm, readdir, mkdir } from 'node:fs/promises'
7
+ import { tmpdir } from 'node:os'
8
+ import { join } from 'node:path'
9
+
10
+ const { listWorkspaceCachesCached } = await import('../src/history-cache.mjs')
11
+
12
+ async function makeDir(t) {
13
+ const dir = await mkdtemp(join(tmpdir(), 'cx-fp-cache-'))
14
+ t.after(() => rm(dir, { recursive: true, force: true }))
15
+ return dir
16
+ }
17
+
18
+ async function writeCache(dir, name, cwd, entries, mtime) {
19
+ const file = join(dir, name)
20
+ await writeFile(file, JSON.stringify({ cwd, entries }), 'utf8')
21
+ if (mtime !== undefined) await utimes(file, mtime, mtime)
22
+ return file
23
+ }
24
+
25
+ test('指纹命中:产物未变时复用解析结果,内容对象同引用', async (t) => {
26
+ const dir = await makeDir(t)
27
+ await writeCache(dir, 'a-1.json', 'C:\\a', [{ sid: 's1', text: 'x' }])
28
+ const first = await listWorkspaceCachesCached(dir)
29
+ assert.equal(first.length, 1)
30
+ assert.equal(first[0].cwd, 'C:\\a')
31
+ // When 再次读取(文件未动)
32
+ const second = await listWorkspaceCachesCached(dir)
33
+ // Then 指纹命中,返回缓存中的同一解析对象
34
+ assert.equal(second[0], first[0])
35
+ })
36
+
37
+ test('指纹失效:文件内容变更后重读,新内容可见', async (t) => {
38
+ const dir = await makeDir(t)
39
+ const stamp = new Date()
40
+ const file = await writeCache(dir, 'b-1.json', 'C:\\b', [{ sid: 's1', text: 'old' }], stamp)
41
+ const first = await listWorkspaceCachesCached(dir)
42
+ assert.equal(first[0].entries[0].text, 'old')
43
+ // When 追加条目并推进 mtime(写入侧经临时文件改名,mtime 必变)
44
+ await writeFile(file, JSON.stringify({ cwd: 'C:\\b', entries: [{ sid: 's1', text: 'old' }, { sid: 's2', text: 'new' }] }), 'utf8')
45
+ const later = new Date(stamp.getTime() + 2000)
46
+ await utimes(file, later, later)
47
+ const second = await listWorkspaceCachesCached(dir)
48
+ // Then 新内容可见
49
+ assert.equal(second[0].entries.length, 2)
50
+ assert.equal(second[0].entries[1].text, 'new')
51
+ })
52
+
53
+ test('卫生:文件删除与目录缺失清空对应缓存', async (t) => {
54
+ const dir = await makeDir(t)
55
+ const file = await writeCache(dir, 'c-1.json', 'C:\\c', [])
56
+ await listWorkspaceCachesCached(dir)
57
+ // When 删除文件后再读
58
+ await rm(file)
59
+ const afterRemove = await listWorkspaceCachesCached(dir)
60
+ // Then 结果为空(目录仍在,缓存条目清除)
61
+ assert.deepEqual(afterRemove, [])
62
+ // When 目录整个消失(缓存目录被手工清理)
63
+ await rm(dir, { recursive: true, force: true })
64
+ const afterDirGone = await listWorkspaceCachesCached(dir)
65
+ // Then 返回空数组不抛
66
+ assert.deepEqual(afterDirGone, [])
67
+ // 目录恢复后缓存可重建
68
+ await mkdir(dir, { recursive: true })
69
+ assert.deepEqual(await readdir(dir), [])
70
+ await writeCache(dir, 'd-1.json', 'C:\\d', [{ sid: 's9', text: 'fresh' }])
71
+ const rebuilt = await listWorkspaceCachesCached(dir)
72
+ assert.equal(rebuilt.length, 1)
73
+ assert.equal(rebuilt[0].entries[0].text, 'fresh')
74
+ })
75
+
76
+ test('过滤:损坏文件与 prompts.json 不进结果', async (t) => {
77
+ const dir = await makeDir(t)
78
+ await writeFile(join(dir, 'broken.json'), '{not json', 'utf8')
79
+ await writeFile(join(dir, 'prompts.json'), JSON.stringify({ items: [] }), 'utf8')
80
+ await writeCache(dir, 'ok-1.json', 'C:\\ok', [])
81
+ const caches = await listWorkspaceCachesCached(dir)
82
+ assert.equal(caches.length, 1)
83
+ assert.equal(caches[0].cwd, 'C:\\ok')
84
+ })
85
+
86
+ // ── 文件名卫生与常用提示词读写 ──
87
+
88
+ const { readWorkspaceCache, writeWorkspaceCache, readPrompts, writePrompts } = await import('../src/history-cache.mjs')
89
+
90
+ test('盘根工作区:C:\\ 读写不抛且文件名不含冒号(win32 非法字符)', async (t) => {
91
+ const dir = await makeDir(t)
92
+ await writeWorkspaceCache(dir, 'C:\\', { entries: [{ sid: 's1', text: '盘根输入' }], extracts: {} })
93
+ const names = await readdir(dir)
94
+ assert.equal(names.length, 1)
95
+ assert.equal(names[0].includes(':'), false, '盘根 leaf 不得携带冒号')
96
+ const back = await readWorkspaceCache(dir, 'C:\\')
97
+ assert.equal(back.entries[0].text, '盘根输入')
98
+ })
99
+
100
+ test('root 回退:纯符号工作区路径的缓存文件落 root-<hash>.json', async (t) => {
101
+ const dir = await makeDir(t)
102
+ await writeWorkspaceCache(dir, '?#?', { entries: [], extracts: {} })
103
+ const names = await readdir(dir)
104
+ assert.equal(names.length, 1)
105
+ assert.match(names[0], /^root-[0-9a-f]{8}\.json$/)
106
+ })
107
+
108
+ test('readPrompts 归一化:非法形状回退空数组,非字符串 text 与缺 at 条目被清洗', async (t) => {
109
+ const dir = await makeDir(t)
110
+ // 文件缺失 → 空数组
111
+ assert.deepEqual(await readPrompts(dir), [])
112
+ // 损坏 JSON → 空数组
113
+ await writeFile(join(dir, 'prompts.json'), '{nope', 'utf8')
114
+ assert.deepEqual(await readPrompts(dir), [])
115
+ // 非数组 items → 空数组
116
+ await writeFile(join(dir, 'prompts.json'), JSON.stringify({ items: 'bogus' }), 'utf8')
117
+ assert.deepEqual(await readPrompts(dir), [])
118
+ // 条目清洗:非字符串/空 text 剔除,at 非数归 0
119
+ await writeFile(join(dir, 'prompts.json'), JSON.stringify({
120
+ items: [
121
+ { text: '保留', at: 7 },
122
+ { text: 42, at: 8 },
123
+ { text: '', at: 9 },
124
+ null,
125
+ { text: '缺时间' },
126
+ ],
127
+ }), 'utf8')
128
+ assert.deepEqual(await readPrompts(dir), [
129
+ { text: '保留', at: 7 },
130
+ { text: '缺时间', at: 0 },
131
+ ])
132
+ // writePrompts 落盘后回读同值
133
+ await writePrompts(dir, [{ text: '落盘', at: 3 }])
134
+ assert.deepEqual(await readPrompts(dir), [{ text: '落盘', at: 3 }])
135
+ })