chore(format): 撤销误入提交的整体重排,并关闭本仓库的格式化器
# 发生了什么 pi-lens 内置「安全格式化」:它会自动安装 biome 并对**编辑过的文件**跑 `biome format --write`。本机原先没有任何 biome 配置,于是 biome 用它自己的 默认值 —— tab 缩进 + 双引号 —— 把文件整体重写。 我在19a3161那次提交里用了 `git add -A`,把这批与功能无关的重排一起扫了进去: 约 7000 行改动散落在 20 个文件上,使那次提交无法审查,还掩盖了 server/internal/handler/permission.go 的一处删行(实为文件末尾空行,无代码丢失)。 # 为什么是「关掉」而不是「配置成我们的风格」 试过把缩进/引号/lineWidth 全部对齐本仓库习惯(biome.json + space/2/single/ lineWidth 120):`biome format --write` 仍然改动 17 个文件。原因是本仓库从未按 biome 的规则排版过 —— 注释按语义换行、数组与调用按可读性手工折行, 这些无法由格式化器还原。也就是说只要格式化器开着,每次编辑都会产生与内容无关的 大面积 diff,把真正的改动埋掉。 因此 biome.jsonc 里 formatter 与 linter 都关闭:本仓库的静态检查由 tsc / go vet / tree-sitter / ast-grep 与各自测试套件承担,不引入会改动无关行的 自动修复。 (pi-lens 这一版把 format 服务的 enabled 硬编码为 true,没有配置开关, 所以只能在仓库侧用 biome 配置让它不动文件;已验证 `biome format --write` 对这些文件零改动。) # 本提交内容 把19a3161里除「有意改动」外的 20 个文件还原到重排前的样子。19a3161中真正有意的改动是 deploy/install.sh 的扩展注册与 plugins/pi-mail-bridge/extension/index.ts 新文件,两者原样保留。 验证:Go 全量、三桥插件(320/362/409)、前端 196 全绿; `biome format --write` 对还原后的文件零改动。
This commit is contained in:
10
plugins/dsh-mail-bridge/lib/mail-session-id.d.ts
vendored
10
plugins/dsh-mail-bridge/lib/mail-session-id.d.ts
vendored
@ -1,9 +1,3 @@
|
||||
export declare function dshSessionIdForMail(mailSessionID: any): string;
|
||||
export declare function matchesMailSession(
|
||||
dshSessionId: any,
|
||||
mailSessionID: any,
|
||||
): boolean;
|
||||
export declare function pickMailSession(
|
||||
dshSessionIds: any[] | undefined,
|
||||
mailSessionID: any,
|
||||
): string | undefined;
|
||||
export declare function matchesMailSession(dshSessionId: any, mailSessionID: any): boolean;
|
||||
export declare function pickMailSession(dshSessionIds: any[] | undefined, mailSessionID: any): string | undefined;
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
/** 首次尝试使用的 DSH 会话 id。 */
|
||||
export function dshSessionIdForMail(mailSessionID) {
|
||||
return `mail-${String(mailSessionID ?? "")}`;
|
||||
return `mail-${String(mailSessionID ?? '')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,12 +35,12 @@ export function dshSessionIdForMail(mailSessionID) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function matchesMailSession(dshSessionId, mailSessionID) {
|
||||
const id = String(dshSessionId ?? "");
|
||||
const base = dshSessionIdForMail(mailSessionID);
|
||||
if (id === base) return true;
|
||||
// 模型降级重试:mail-<id>-r1 / -r2 / …
|
||||
const suffix = id.startsWith(`${base}-r`) ? id.slice(base.length + 2) : "";
|
||||
return suffix.length > 0 && /^\d+$/.test(suffix);
|
||||
const id = String(dshSessionId ?? '');
|
||||
const base = dshSessionIdForMail(mailSessionID);
|
||||
if (id === base) return true;
|
||||
// 模型降级重试:mail-<id>-r1 / -r2 / …
|
||||
const suffix = id.startsWith(`${base}-r`) ? id.slice(base.length + 2) : '';
|
||||
return suffix.length > 0 && /^\d+$/.test(suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,19 +54,19 @@ export function matchesMailSession(dshSessionId, mailSessionID) {
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function pickMailSession(dshSessionIds, mailSessionID) {
|
||||
const base = dshSessionIdForMail(mailSessionID);
|
||||
const list = Array.isArray(dshSessionIds) ? dshSessionIds.map(String) : [];
|
||||
if (list.includes(base)) return base;
|
||||
const base = dshSessionIdForMail(mailSessionID);
|
||||
const list = Array.isArray(dshSessionIds) ? dshSessionIds.map(String) : [];
|
||||
if (list.includes(base)) return base;
|
||||
|
||||
let best;
|
||||
let bestIndex = Infinity;
|
||||
for (const id of list) {
|
||||
if (!matchesMailSession(id, mailSessionID)) continue;
|
||||
const idx = Number(id.slice(base.length + 2));
|
||||
if (idx < bestIndex) {
|
||||
bestIndex = idx;
|
||||
best = id;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
let best;
|
||||
let bestIndex = Infinity;
|
||||
for (const id of list) {
|
||||
if (!matchesMailSession(id, mailSessionID)) continue;
|
||||
const idx = Number(id.slice(base.length + 2));
|
||||
if (idx < bestIndex) {
|
||||
bestIndex = idx;
|
||||
best = id;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
22
plugins/dsh-mail-bridge/lib/sse-client.d.ts
vendored
22
plugins/dsh-mail-bridge/lib/sse-client.d.ts
vendored
@ -1,17 +1,17 @@
|
||||
export interface SseClientOptions {
|
||||
authHeaders: () => Record<string, string>;
|
||||
baseURL: string;
|
||||
path: string;
|
||||
onEvent: (evt: string, data: any) => void;
|
||||
log?: (msg: string) => void;
|
||||
authHeaders: () => Record<string, string>;
|
||||
baseURL: string;
|
||||
path: string;
|
||||
onEvent: (evt: string, data: any) => void;
|
||||
log?: (msg: string) => void;
|
||||
}
|
||||
export declare function createSSEClient(options: SseClientOptions): {
|
||||
stop: () => void;
|
||||
reset: () => void;
|
||||
stop: () => void;
|
||||
reset: () => void;
|
||||
};
|
||||
export declare function createFrameParser(): {
|
||||
push: (chunk: string) => Array<{ event: string; data: string; id: string }>;
|
||||
reset: () => void;
|
||||
lastEventId: () => string;
|
||||
setLastEventId: (id: string) => void;
|
||||
push: (chunk: string) => Array<{ event: string; data: string; id: string }>;
|
||||
reset: () => void;
|
||||
lastEventId: () => string;
|
||||
setLastEventId: (id: string) => void;
|
||||
};
|
||||
|
||||
22
plugins/dsh-mail-bridge/lib/user-question.d.ts
vendored
22
plugins/dsh-mail-bridge/lib/user-question.d.ts
vendored
@ -2,20 +2,12 @@ export declare function hasOptions(question: any): boolean;
|
||||
export declare function optionLabels(question: any): string[];
|
||||
export declare function questionTitle(question: any): string;
|
||||
export declare function flattenQuestions(questions: any[]): {
|
||||
question: string;
|
||||
options: string[];
|
||||
context: string;
|
||||
multiSelect: boolean;
|
||||
question: string;
|
||||
options: string[];
|
||||
context: string;
|
||||
multiSelect: boolean;
|
||||
};
|
||||
export declare function answersFromDecision(
|
||||
questions: any[],
|
||||
decision: string,
|
||||
note?: string,
|
||||
): {
|
||||
answers: Array<{ id: string; selected: string[]; custom?: string }>;
|
||||
export declare function answersFromDecision(questions: any[], decision: string, note?: string): {
|
||||
answers: Array<{ id: string; selected: string[]; custom?: string }>;
|
||||
};
|
||||
export declare function isBlankAnswer(
|
||||
questions: any[],
|
||||
decision: string,
|
||||
note?: string,
|
||||
): boolean;
|
||||
export declare function isBlankAnswer(questions: any[], decision: string, note?: string): boolean;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,69 +6,59 @@
|
||||
* 运行时仍按旧档位执行 —— 人以为自己收紧了权限。
|
||||
*/
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import {
|
||||
dshSessionIdForMail,
|
||||
matchesMailSession,
|
||||
pickMailSession,
|
||||
} from "../lib/mail-session-id.js";
|
||||
dshSessionIdForMail,
|
||||
matchesMailSession,
|
||||
pickMailSession,
|
||||
} from '../lib/mail-session-id.js';
|
||||
|
||||
test("首次尝试的 id 是 mail-<邮件会话 id>", () => {
|
||||
assert.equal(dshSessionIdForMail("abc-123"), "mail-abc-123");
|
||||
test('首次尝试的 id 是 mail-<邮件会话 id>', () => {
|
||||
assert.equal(dshSessionIdForMail('abc-123'), 'mail-abc-123');
|
||||
});
|
||||
|
||||
test("匹配首次尝试的 id", () => {
|
||||
assert.equal(matchesMailSession("mail-abc", "abc"), true);
|
||||
test('匹配首次尝试的 id', () => {
|
||||
assert.equal(matchesMailSession('mail-abc', 'abc'), true);
|
||||
});
|
||||
|
||||
test("匹配模型降级重试的 -r<i>", () => {
|
||||
assert.equal(matchesMailSession("mail-abc-r1", "abc"), true);
|
||||
assert.equal(matchesMailSession("mail-abc-r12", "abc"), true);
|
||||
test('匹配模型降级重试的 -r<i>', () => {
|
||||
assert.equal(matchesMailSession('mail-abc-r1', 'abc'), true);
|
||||
assert.equal(matchesMailSession('mail-abc-r12', 'abc'), true);
|
||||
});
|
||||
|
||||
test("不匹配别的会话(前缀相同也不行)", () => {
|
||||
assert.equal(
|
||||
matchesMailSession("mail-abcdef", "abc"),
|
||||
false,
|
||||
"mail-abc 的前缀不能吃掉 mail-abcdef",
|
||||
);
|
||||
assert.equal(matchesMailSession("mail-abd", "abc"), false);
|
||||
assert.equal(matchesMailSession("other-abc", "abc"), false);
|
||||
assert.equal(matchesMailSession("", "abc"), false);
|
||||
test('不匹配别的会话(前缀相同也不行)', () => {
|
||||
assert.equal(matchesMailSession('mail-abcdef', 'abc'), false, 'mail-abc 的前缀不能吃掉 mail-abcdef');
|
||||
assert.equal(matchesMailSession('mail-abd', 'abc'), false);
|
||||
assert.equal(matchesMailSession('other-abc', 'abc'), false);
|
||||
assert.equal(matchesMailSession('', 'abc'), false);
|
||||
});
|
||||
|
||||
test("不匹配非数字后缀(避免误吞其它会话)", () => {
|
||||
assert.equal(matchesMailSession("mail-abc-rx", "abc"), false);
|
||||
assert.equal(matchesMailSession("mail-abc-r", "abc"), false);
|
||||
assert.equal(matchesMailSession("mail-abc-retry", "abc"), false);
|
||||
test('不匹配非数字后缀(避免误吞其它会话)', () => {
|
||||
assert.equal(matchesMailSession('mail-abc-rx', 'abc'), false);
|
||||
assert.equal(matchesMailSession('mail-abc-r', 'abc'), false);
|
||||
assert.equal(matchesMailSession('mail-abc-retry', 'abc'), false);
|
||||
});
|
||||
|
||||
test("pickMailSession 优先首次尝试,而不是数组顺序", () => {
|
||||
// 数组顺序可能来自 ctx.agents.list(),与尝试顺序无关
|
||||
assert.equal(
|
||||
pickMailSession(["mail-abc-r2", "mail-abc-r1", "mail-abc"], "abc"),
|
||||
"mail-abc",
|
||||
);
|
||||
test('pickMailSession 优先首次尝试,而不是数组顺序', () => {
|
||||
// 数组顺序可能来自 ctx.agents.list(),与尝试顺序无关
|
||||
assert.equal(pickMailSession(['mail-abc-r2', 'mail-abc-r1', 'mail-abc'], 'abc'), 'mail-abc');
|
||||
});
|
||||
|
||||
test("pickMailSession 没有首次尝试时取序号最小的重试", () => {
|
||||
assert.equal(
|
||||
pickMailSession(["mail-abc-r3", "mail-abc-r1", "mail-abc-r2"], "abc"),
|
||||
"mail-abc-r1",
|
||||
);
|
||||
test('pickMailSession 没有首次尝试时取序号最小的重试', () => {
|
||||
assert.equal(pickMailSession(['mail-abc-r3', 'mail-abc-r1', 'mail-abc-r2'], 'abc'), 'mail-abc-r1');
|
||||
});
|
||||
|
||||
test("pickMailSession 找不到时返回 undefined(调用方据此跳过)", () => {
|
||||
assert.equal(pickMailSession(["other-1", "mail-def"], "abc"), undefined);
|
||||
assert.equal(pickMailSession([], "abc"), undefined);
|
||||
assert.equal(pickMailSession(undefined, "abc"), undefined);
|
||||
test('pickMailSession 找不到时返回 undefined(调用方据此跳过)', () => {
|
||||
assert.equal(pickMailSession(['other-1', 'mail-def'], 'abc'), undefined);
|
||||
assert.equal(pickMailSession([], 'abc'), undefined);
|
||||
assert.equal(pickMailSession(undefined, 'abc'), undefined);
|
||||
});
|
||||
|
||||
test("接管的平台会话推不出 id:不能被误判成邮件会话", () => {
|
||||
// 平台自己生成的 id(如 DSH 界面里开的会话)与邮件会话无关,
|
||||
// 匹配函数必须说「不是」,否则会给错误的会话改档位。
|
||||
assert.equal(matchesMailSession("session-7f3a91", "abc"), false);
|
||||
assert.equal(pickMailSession(["session-7f3a91"], "abc"), undefined);
|
||||
test('接管的平台会话推不出 id:不能被误判成邮件会话', () => {
|
||||
// 平台自己生成的 id(如 DSH 界面里开的会话)与邮件会话无关,
|
||||
// 匹配函数必须说「不是」,否则会给错误的会话改档位。
|
||||
assert.equal(matchesMailSession('session-7f3a91', 'abc'), false);
|
||||
assert.equal(pickMailSession(['session-7f3a91'], 'abc'), undefined);
|
||||
});
|
||||
|
||||
@ -9,132 +9,121 @@
|
||||
* 生产上表现为「新邮件偶尔收不到」「权限决策点了没反应」,且日志里一个字都没有。
|
||||
*/
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { createFrameParser } from "../lib/sse-client.js";
|
||||
import { createFrameParser } from '../lib/sse-client.js';
|
||||
|
||||
/** JSON.parse 的测试包装:解析失败让断言带原文失败,而不是抛未捕获异常。 */
|
||||
function parse(s) {
|
||||
try {
|
||||
return JSON.parse(s);
|
||||
} catch (e) {
|
||||
assert.fail(`不是合法 JSON: ${s}(${e.message})`);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(s);
|
||||
} catch (e) {
|
||||
assert.fail(`不是合法 JSON: ${s}(${e.message})`);
|
||||
}
|
||||
}
|
||||
|
||||
test("完整帧一次喂入:正常解析", () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('id: 7\nevent: new_mail\ndata: {"mail_id":"m1"}\n\n');
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].event, "new_mail");
|
||||
assert.deepEqual(parse(events[0].data), { mail_id: "m1" });
|
||||
assert.equal(events[0].id, "7");
|
||||
assert.equal(p.lastEventId(), "7");
|
||||
test('完整帧一次喂入:正常解析', () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('id: 7\nevent: new_mail\ndata: {"mail_id":"m1"}\n\n');
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].event, 'new_mail');
|
||||
assert.deepEqual(parse(events[0].data), { mail_id: 'm1' });
|
||||
assert.equal(events[0].id, '7');
|
||||
assert.equal(p.lastEventId(), '7');
|
||||
});
|
||||
|
||||
test("帧被切在换行处:跨 chunk 保住 event 名(原 bug 的核心)", () => {
|
||||
const p = createFrameParser();
|
||||
// chunk1 恰好停在 event 行之后、data 行之前
|
||||
const first = p.push("id: 12\nevent: content_delta\n");
|
||||
assert.deepEqual(first, [], "半帧不该派发");
|
||||
test('帧被切在换行处:跨 chunk 保住 event 名(原 bug 的核心)', () => {
|
||||
const p = createFrameParser();
|
||||
// chunk1 恰好停在 event 行之后、data 行之前
|
||||
const first = p.push('id: 12\nevent: content_delta\n');
|
||||
assert.deepEqual(first, [], '半帧不该派发');
|
||||
|
||||
const second = p.push('data: {"x":1}\n\n');
|
||||
assert.equal(
|
||||
second.length,
|
||||
1,
|
||||
"跨 chunk 的半帧必须被拼回完整事件,而不是丢弃",
|
||||
);
|
||||
assert.equal(second[0].event, "content_delta");
|
||||
assert.equal(p.lastEventId(), "12");
|
||||
const second = p.push('data: {"x":1}\n\n');
|
||||
assert.equal(second.length, 1, '跨 chunk 的半帧必须被拼回完整事件,而不是丢弃');
|
||||
assert.equal(second[0].event, 'content_delta');
|
||||
assert.equal(p.lastEventId(), '12');
|
||||
});
|
||||
|
||||
test("帧被切在行中间:buffer 保留半行", () => {
|
||||
const p = createFrameParser();
|
||||
const a = p.push("event: new_ma");
|
||||
assert.deepEqual(a, []);
|
||||
const b = p.push('il\ndata: {"mail_id":"m9"}\n\n');
|
||||
assert.equal(b.length, 1);
|
||||
assert.equal(b[0].event, "new_mail");
|
||||
test('帧被切在行中间:buffer 保留半行', () => {
|
||||
const p = createFrameParser();
|
||||
const a = p.push('event: new_ma');
|
||||
assert.deepEqual(a, []);
|
||||
const b = p.push('il\ndata: {"mail_id":"m9"}\n\n');
|
||||
assert.equal(b.length, 1);
|
||||
assert.equal(b[0].event, 'new_mail');
|
||||
});
|
||||
|
||||
test("一个 chunk 里多帧连续:全部派发", () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push(
|
||||
'event: new_mail\ndata: {"n":1}\n\n' +
|
||||
'event: new_mail\ndata: {"n":2}\n\n' +
|
||||
'event: session_update\ndata: {"n":3}\n\n',
|
||||
);
|
||||
assert.equal(events.length, 3);
|
||||
assert.deepEqual(
|
||||
events.map((e) => e.event),
|
||||
["new_mail", "new_mail", "session_update"],
|
||||
);
|
||||
test('一个 chunk 里多帧连续:全部派发', () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push(
|
||||
'event: new_mail\ndata: {"n":1}\n\n' +
|
||||
'event: new_mail\ndata: {"n":2}\n\n' +
|
||||
'event: session_update\ndata: {"n":3}\n\n'
|
||||
);
|
||||
assert.equal(events.length, 3);
|
||||
assert.deepEqual(events.map((e) => e.event), ['new_mail', 'new_mail', 'session_update']);
|
||||
});
|
||||
|
||||
test("注释/心跳行被忽略,不影响后续帧", () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push(': heartbeat\n\nevent: new_mail\ndata: {"n":1}\n\n');
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].event, "new_mail");
|
||||
test('注释/心跳行被忽略,不影响后续帧', () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push(': heartbeat\n\nevent: new_mail\ndata: {"n":1}\n\n');
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].event, 'new_mail');
|
||||
});
|
||||
|
||||
test("多行 data 用换行拼接", () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push("event: x\ndata: line1\ndata: line2\n\n");
|
||||
assert.equal(events[0].data, "line1\nline2");
|
||||
test('多行 data 用换行拼接', () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('event: x\ndata: line1\ndata: line2\n\n');
|
||||
assert.equal(events[0].data, 'line1\nline2');
|
||||
});
|
||||
|
||||
test("CRLF 不被当成事件名或 JSON 的一部分", () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('id: 3\r\nevent: new_mail\r\ndata: {"n":1}\r\n\r\n');
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].event, "new_mail");
|
||||
assert.equal(events[0].id, "3");
|
||||
assert.deepEqual(parse(events[0].data), { n: 1 });
|
||||
test('CRLF 不被当成事件名或 JSON 的一部分', () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('id: 3\r\nevent: new_mail\r\ndata: {"n":1}\r\n\r\n');
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].event, 'new_mail');
|
||||
assert.equal(events[0].id, '3');
|
||||
assert.deepEqual(parse(events[0].data), { n: 1 });
|
||||
});
|
||||
|
||||
test("事件 id 只向前推进:重放旧 id 不回退断点", () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 10\nevent: new_mail\ndata: {"n":1}\n\n');
|
||||
assert.equal(p.lastEventId(), "10");
|
||||
// 服务端重放一条更早的事件:断点不该退回 5,否则下次重连会重复回放 6..10
|
||||
p.push('id: 5\nevent: new_mail\ndata: {"n":0}\n\n');
|
||||
assert.equal(
|
||||
p.lastEventId(),
|
||||
"5",
|
||||
"解析器如实记录当前 id(是否回退由使用方决定)",
|
||||
);
|
||||
test('事件 id 只向前推进:重放旧 id 不回退断点', () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 10\nevent: new_mail\ndata: {"n":1}\n\n');
|
||||
assert.equal(p.lastEventId(), '10');
|
||||
// 服务端重放一条更早的事件:断点不该退回 5,否则下次重连会重复回放 6..10
|
||||
p.push('id: 5\nevent: new_mail\ndata: {"n":0}\n\n');
|
||||
assert.equal(p.lastEventId(), '5', '解析器如实记录当前 id(是否回退由使用方决定)');
|
||||
});
|
||||
|
||||
test("id 在派发前记录:回调抛异常也不丢断点", () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 42\nevent: new_mail\ndata: {"n":1}\n\n');
|
||||
assert.equal(p.lastEventId(), "42");
|
||||
test('id 在派发前记录:回调抛异常也不丢断点', () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 42\nevent: new_mail\ndata: {"n":1}\n\n');
|
||||
assert.equal(p.lastEventId(), '42');
|
||||
});
|
||||
|
||||
test("只有 data 没有 event 不派发(避免把心跳数据当事件)", () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('data: {"orphan":true}\n\n');
|
||||
assert.deepEqual(events, []);
|
||||
test('只有 data 没有 event 不派发(避免把心跳数据当事件)', () => {
|
||||
const p = createFrameParser();
|
||||
const events = p.push('data: {"orphan":true}\n\n');
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
|
||||
test("reset 清缓冲但保留断点(重连后仍能续传)", () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 99\nevent: a\ndata: {"n":1}\n\n');
|
||||
p.push("event: partial"); // 半帧
|
||||
p.reset();
|
||||
assert.equal(p.lastEventId(), "99", "断点必须保留,否则重连从头回放");
|
||||
// reset 后半帧不该复活
|
||||
const after = p.push('data: {"n":2}\n\n');
|
||||
assert.deepEqual(after, []);
|
||||
test('reset 清缓冲但保留断点(重连后仍能续传)', () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 99\nevent: a\ndata: {"n":1}\n\n');
|
||||
p.push('event: partial'); // 半帧
|
||||
p.reset();
|
||||
assert.equal(p.lastEventId(), '99', '断点必须保留,否则重连从头回放');
|
||||
// reset 后半帧不该复活
|
||||
const after = p.push('data: {"n":2}\n\n');
|
||||
assert.deepEqual(after, []);
|
||||
});
|
||||
|
||||
test("setLastEventId 清空 = 换 Gateway 后不再拿旧序号问新服务端", () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 123\nevent: a\ndata: {"n":1}\n\n');
|
||||
assert.equal(p.lastEventId(), "123");
|
||||
// connect_to_server 换了坐标:旧序号属于旧 Gateway 的环形缓冲,必须丢掉
|
||||
p.setLastEventId("");
|
||||
assert.equal(p.lastEventId(), "", "首次连接不得携带 Last-Event-ID");
|
||||
test('setLastEventId 清空 = 换 Gateway 后不再拿旧序号问新服务端', () => {
|
||||
const p = createFrameParser();
|
||||
p.push('id: 123\nevent: a\ndata: {"n":1}\n\n');
|
||||
assert.equal(p.lastEventId(), '123');
|
||||
// connect_to_server 换了坐标:旧序号属于旧 Gateway 的环形缓冲,必须丢掉
|
||||
p.setLastEventId('');
|
||||
assert.equal(p.lastEventId(), '', '首次连接不得携带 Last-Event-ID');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user