fix: 严格校验隔离重放计数
This commit is contained in:
@@ -12,18 +12,42 @@ test("local replay classification reports transport_failed below server delivere
|
||||
assert.deepEqual(result.counts, { delivered: 3, clientReceived: 2, decoded: 2, applied: 2 });
|
||||
});
|
||||
|
||||
test("local replay classification reports transport_failed above server delivered", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 4, decoded: 4, applied: 4 });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, "transport_failed");
|
||||
});
|
||||
|
||||
test("local replay classification reports contract_invalid when server delivered is missing", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(), { clientReceived: 0, decoded: 0, applied: 0 });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, "contract_invalid");
|
||||
});
|
||||
|
||||
test("local replay classification reports decoder_rejected below client received", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 2, applied: 2 });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, "decoder_rejected");
|
||||
});
|
||||
|
||||
test("local replay classification reports decoder_rejected above client received", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 4, applied: 4 });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, "decoder_rejected");
|
||||
});
|
||||
|
||||
test("local replay classification reports reducer_rejected below decoded", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 3, applied: 2 });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, "reducer_rejected");
|
||||
});
|
||||
|
||||
test("local replay classification reports reducer_rejected above decoded", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 3, applied: 4 });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, "reducer_rejected");
|
||||
});
|
||||
|
||||
test("local replay classification preserves terminal_complete when every client stage agrees", () => {
|
||||
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 3, applied: 3 });
|
||||
assert.equal(result.ok, true);
|
||||
@@ -32,13 +56,13 @@ test("local replay classification preserves terminal_complete when every client
|
||||
assert.equal(result.localOverlay, true);
|
||||
});
|
||||
|
||||
function terminalResult(delivered: number): WorkbenchKafkaDebugReplayResult {
|
||||
function terminalResult(delivered?: number): WorkbenchKafkaDebugReplayResult {
|
||||
return {
|
||||
contractVersion: "workbench-kafka-debug-replay-v2",
|
||||
ok: true,
|
||||
phase: "terminal",
|
||||
code: "terminal_complete",
|
||||
terminalObserved: true,
|
||||
counts: { delivered, clientReceived: null, decoded: null, applied: null }
|
||||
counts: { ...(delivered === undefined ? {} : { delivered }), clientReceived: null, decoded: null, applied: null }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface WorkbenchKafkaDebugClientCounts {
|
||||
applied: number;
|
||||
}
|
||||
|
||||
export type WorkbenchKafkaDebugClientFailureCode = "transport_failed" | "decoder_rejected" | "reducer_rejected";
|
||||
export type WorkbenchKafkaDebugClientFailureCode = "contract_invalid" | "transport_failed" | "decoder_rejected" | "reducer_rejected";
|
||||
|
||||
export function classifyWorkbenchKafkaDebugLocalResult(
|
||||
serverResult: WorkbenchKafkaDebugReplayResult,
|
||||
@@ -19,14 +19,17 @@ export function classifyWorkbenchKafkaDebugLocalResult(
|
||||
if (serverResult.code !== "terminal_complete") return overlaid;
|
||||
|
||||
const delivered = nonNegativeInteger(serverResult.counts?.delivered);
|
||||
if (delivered !== null && clientCounts.clientReceived < delivered) {
|
||||
return clientFailure(overlaid, "transport_failed", `浏览器收到 ${clientCounts.clientReceived} 条,少于服务端已投递的 ${delivered} 条。`);
|
||||
if (delivered === null) {
|
||||
return clientFailure(overlaid, "contract_invalid", "服务端 terminal_complete 缺少有效的 delivered 计数。");
|
||||
}
|
||||
if (clientCounts.decoded < clientCounts.clientReceived) {
|
||||
return clientFailure(overlaid, "decoder_rejected", `浏览器收到 ${clientCounts.clientReceived} 条,但只解码 ${clientCounts.decoded} 条。`);
|
||||
if (clientCounts.clientReceived !== delivered) {
|
||||
return clientFailure(overlaid, "transport_failed", `浏览器收到 ${clientCounts.clientReceived} 条,与服务端已投递的 ${delivered} 条不一致。`);
|
||||
}
|
||||
if (clientCounts.applied < clientCounts.decoded) {
|
||||
return clientFailure(overlaid, "reducer_rejected", `浏览器解码 ${clientCounts.decoded} 条,但生产 reducer 只应用 ${clientCounts.applied} 条。`);
|
||||
if (clientCounts.decoded !== clientCounts.clientReceived) {
|
||||
return clientFailure(overlaid, "decoder_rejected", `浏览器收到 ${clientCounts.clientReceived} 条,但解码计数为 ${clientCounts.decoded} 条。`);
|
||||
}
|
||||
if (clientCounts.applied !== clientCounts.decoded) {
|
||||
return clientFailure(overlaid, "reducer_rejected", `浏览器解码 ${clientCounts.decoded} 条,但生产 reducer 应用计数为 ${clientCounts.applied} 条。`);
|
||||
}
|
||||
return { ...overlaid, ok: true, phase: "terminal", code: "terminal_complete" };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user