109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { classifySessionTimelineSeq } from "./session-timeline-seq-gate";
|
|
import {
|
|
deriveBootstrapTailTimelinePolicy,
|
|
shouldResolveTimelineInit,
|
|
} from "./session-timeline-bootstrap-policy";
|
|
|
|
describe("deriveBootstrapTailTimelinePolicy", () => {
|
|
it("always replaces on explicit reset without catch-up cursor", () => {
|
|
const policy = deriveBootstrapTailTimelinePolicy({
|
|
direction: "after",
|
|
reset: true,
|
|
epoch: "epoch-1",
|
|
endCursor: { seq: 200 },
|
|
isInitializing: false,
|
|
hasActiveInitDeferred: false,
|
|
});
|
|
|
|
expect(policy.replace).toBe(true);
|
|
expect(policy.catchUpCursor).toBeNull();
|
|
});
|
|
|
|
it("forces baseline replace and canonical catch-up for init tail race", () => {
|
|
const advancedCursor = { epoch: "epoch-1", endSeq: 205 };
|
|
const tailSeqStart = 101;
|
|
const tailSeqEnd = 200;
|
|
|
|
let acceptedWithoutBootstrap = 0;
|
|
for (let seq = tailSeqStart; seq <= tailSeqEnd; seq += 1) {
|
|
const decision = classifySessionTimelineSeq({
|
|
cursor: advancedCursor,
|
|
epoch: "epoch-1",
|
|
seq,
|
|
});
|
|
if (decision === "accept" || decision === "init") {
|
|
acceptedWithoutBootstrap += 1;
|
|
}
|
|
}
|
|
expect(acceptedWithoutBootstrap).toBe(0);
|
|
|
|
const policy = deriveBootstrapTailTimelinePolicy({
|
|
direction: "tail",
|
|
reset: false,
|
|
epoch: "epoch-1",
|
|
endCursor: { seq: 200 },
|
|
isInitializing: true,
|
|
hasActiveInitDeferred: true,
|
|
});
|
|
|
|
expect(policy.replace).toBe(true);
|
|
expect(policy.catchUpCursor).toEqual({
|
|
epoch: "epoch-1",
|
|
endSeq: 200,
|
|
});
|
|
});
|
|
|
|
it("does not replace non-bootstrap, non-reset responses", () => {
|
|
const policy = deriveBootstrapTailTimelinePolicy({
|
|
direction: "tail",
|
|
reset: false,
|
|
epoch: "epoch-1",
|
|
endCursor: { seq: 200 },
|
|
isInitializing: false,
|
|
hasActiveInitDeferred: false,
|
|
});
|
|
|
|
expect(policy.replace).toBe(false);
|
|
expect(policy.catchUpCursor).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("shouldResolveTimelineInit", () => {
|
|
it("resolves tail init when the tail response arrives", () => {
|
|
expect(
|
|
shouldResolveTimelineInit({
|
|
hasActiveInitDeferred: true,
|
|
isInitializing: true,
|
|
initRequestDirection: "tail",
|
|
responseDirection: "tail",
|
|
reset: false,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("does not resolve tail init when an after response arrives first", () => {
|
|
expect(
|
|
shouldResolveTimelineInit({
|
|
hasActiveInitDeferred: true,
|
|
isInitializing: true,
|
|
initRequestDirection: "tail",
|
|
responseDirection: "after",
|
|
reset: false,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("resolves after init when an after response arrives", () => {
|
|
expect(
|
|
shouldResolveTimelineInit({
|
|
hasActiveInitDeferred: true,
|
|
isInitializing: true,
|
|
initRequestDirection: "after",
|
|
responseDirection: "after",
|
|
reset: false,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
});
|