test(sync): preserve question bootstrap and reducer invariants (#3439)

* test(sync): preserve question bootstrap and reducer invariants

Port the main-independent test assets from the superseded hybrid V2
question PRs (#3266/#3267/#3288) onto current main's V1-only
architecture:

- listPendingQuestions: unscoped + per-directory fan-out, merge with
  id-dedupe (first wins), failure throws instead of empty success,
  malformed-item skip, true-empty success (client.questions.test.ts)
- question reducer invariants: idempotent upsert-by-id, replace-not-
  first-wins, removal by session/request pair, duplicate-terminal no-op,
  late-asked re-registration (no tombstone)
- bootstrap deferred-phase question merge: signature-based replace and
  disappearance-deletion, in-flight-change preservation (stale guard),
  retry-then-merge on transient failure

Tests only: zero production changes, zero V2 SDK calls, zero fallback
logic, zero raw V2 event aliases.

* test(questions): clarify V1 merge id-filter test title

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
Leonid
2026-09-09 17:44:11 +03:00
committed by GitHub
co-authored by bashrusakh
parent 5c127a1e3c
commit c1f19b1158
3 changed files with 366 additions and 3 deletions
@@ -355,3 +355,98 @@ describe("applyDirectoryEvent", () => {
expect(draft.question.ses_1).toEqual([])
})
})
describe("question reducer invariants (main contract)", () => {
const questionRequest = (id: string, sessionID = "ses_1"): QuestionRequest => ({
id,
sessionID,
questions: [],
})
const askedEvent = (id: string, sessionID = "ses_1"): Event => ({
id: `evt_${id}`,
type: "question.asked",
properties: questionRequest(id, sessionID),
})
const repliedEvent = (requestID: string, sessionID = "ses_1"): Event => ({
id: `evt_${requestID}`,
type: "question.replied",
properties: { sessionID, requestID, answers: [] },
})
const rejectedEvent = (requestID: string, sessionID = "ses_1"): Event => ({
id: `evt_${requestID}`,
type: "question.rejected",
properties: { sessionID, requestID },
})
test("question.asked is an idempotent upsert-by-id — replaying does not duplicate", () => {
const draft = state({ question: { ses_1: [questionRequest("ques_1")] } })
expect(applyDirectoryEvent(draft, askedEvent("ques_1"))).toBe(true)
expect(applyDirectoryEvent(draft, askedEvent("ques_1"))).toBe(true)
expect(draft.question.ses_1).toHaveLength(1)
expect(draft.question.ses_1[0]?.id).toBe("ques_1")
})
test("question.asked replaces the stored record in place (not first-wins)", () => {
const draft = state({ question: { ses_1: [questionRequest("ques_1")] } })
const replacement: QuestionRequest = {
id: "ques_1",
sessionID: "ses_1",
questions: [
{ question: "updated?", header: "Build", options: [{ label: "Yes", description: "Go" }] },
],
}
expect(applyDirectoryEvent(draft, {
id: "evt_ques_1",
type: "question.asked",
properties: replacement,
})).toBe(true)
expect(draft.question.ses_1).toHaveLength(1)
expect(draft.question.ses_1[0]).toEqual(replacement)
})
test("question.replied and question.rejected remove exactly the matching request; unknown removal is a no-op returning false", () => {
const draft = state({
question: { ses_1: [questionRequest("ques_1"), questionRequest("ques_2")] },
})
expect(applyDirectoryEvent(draft, repliedEvent("ques_1"))).toBe(true)
expect(draft.question.ses_1.map((q) => q.id)).toEqual(["ques_2"])
expect(applyDirectoryEvent(draft, rejectedEvent("ques_2"))).toBe(true)
expect(draft.question.ses_1).toEqual([])
// Removal for an unknown request is a safe no-op.
expect(applyDirectoryEvent(draft, repliedEvent("ques_missing"))).toBe(false)
expect(applyDirectoryEvent(draft, rejectedEvent("ques_missing"))).toBe(false)
expect(draft.question.ses_1).toEqual([])
})
test("a duplicate terminal event after removal is a safe no-op", () => {
const draft = state({ question: { ses_1: [questionRequest("ques_1")] } })
expect(applyDirectoryEvent(draft, repliedEvent("ques_1"))).toBe(true)
expect(draft.question.ses_1).toEqual([])
// Replayed terminal event: no error, no duplicate, no state change.
expect(applyDirectoryEvent(draft, repliedEvent("ques_1"))).toBe(false)
expect(draft.question.ses_1).toEqual([])
})
test("a late question.asked after a terminal event re-registers the request (no tombstone)", () => {
const draft = state({ question: { ses_1: [questionRequest("ques_1")] } })
expect(applyDirectoryEvent(draft, repliedEvent("ques_1"))).toBe(true)
expect(draft.question.ses_1).toEqual([])
// Ordered-stream replay: a late asked re-inserts; there is no tombstone.
expect(applyDirectoryEvent(draft, askedEvent("ques_1"))).toBe(true)
expect(draft.question.ses_1.map((q) => q.id)).toEqual(["ques_1"])
})
})