/** * ★ 投递即标已读 —— 防「插件重启 → 重投 → 回声」。 * * 与 pi / opencode 的同名判据同源:三个桥共享同一套 Gateway 契约,缺陷也一样。 * 用户报的是 pi 桥,但三处必须一起修,否则换个平台复发。 */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; const src = readFileSync(new URL('../src/index.ts', import.meta.url), 'utf8'); test('★ deliveredMails.add 只允许出现在 markDelivered 内部', () => { const lines = src.split('\n'); const fnStart = lines.findIndex(l => /function markDelivered\(/.test(l)); assert.ok(fnStart >= 0, 'markDelivered 必须存在'); let fnEnd = fnStart; for (let i = fnStart + 1; i < lines.length; i++) { if (/^ \}/.test(lines[i])) { fnEnd = i; break; } } const strays = []; lines.forEach((line, i) => { if (!/deliveredMails\.add\(/.test(line)) return; if (i > fnStart && i < fnEnd) return; if (/^\s*(\/\/|\*|\/\*)/.test(line)) return; strays.push(`${i + 1}: ${line.trim()}`); }); assert.deepEqual(strays, [], `这些地方绕过 markDelivered ⇒ 库里 status 不更新 ⇒ 重启重投:\n${strays.join('\n')}`); }); test('markDelivered 同时写内存与库', () => { const i = src.indexOf('function markDelivered('); const body = src.slice(i, src.indexOf('\n }', i)); assert.match(body, /deliveredMails\.add\(/, '要写内存集合'); assert.match(body, /client\.post\('\/mail\/read'/, '要写库里的 status'); assert.match(body, /mail_ids: \[id\]/, '按 id 标(不传会被要求 workspace)'); }); test('★ 三个投递点都走 markDelivered', () => { assert.match(src, /if \(deliveredMails\.has\(ev\.mail_id\)\) continue;[^\n]*\n\s*markDelivered\(ev\.mail_id\);/, '补投路径'); assert.match(src, /if \(data\?\.decision_mail_id\) markDelivered\(data\.decision_mail_id\);/, '决策回执'); assert.match(src, /if \(data\?\.mail_id\) markDelivered\(data\.mail_id\);/, 'SSE 主投递路径'); }); test('判据自检:反例必须判红', () => { const before = 'if (data?.mail_id) deliveredMails.add(data.mail_id);'; assert.ok(/deliveredMails\.add\(/.test(before), '反例确实会 add'); assert.ok(!/mail\/read/.test(before), '反例不标已读 ⇒ 判红'); });