feat(housekeeping): define typed poll query contract

This commit is contained in:
Simo committed 2026-09-02 17:52:41 +02:00
1 parent e69633b753
commit cbc3075e5a
4 files changed
+432 -8

No files matched your search

@@ -57,6 +57,35 @@ const ready: ContentQueryData = {
partialDependencies: [],
};
const pollDetail = {
kind: "poll-detail" as const,
description: "Complete poll",
showResults: true,
multipleChoice: false,
startsAt: "2026-09-02T18:00:00.000Z",
endsAt: null,
questionCount: 1,
voterCount: 2,
answerCount: 2,
questions: [
{
id: "21",
question: "Favourite colour?",
type: "single" as const,
sortOrder: 0,
options: ["Red", "Blue"],
answerCount: 2,
choiceResults: [{ option: "Red", count: 2 }],
},
],
textResponses: {
questionId: null,
items: [],
total: 0,
pageSize: 25,
offset: 0,
},
};
describe("Content query", () => {
beforeEach(() => queryMocks.execute.mockReset());
@@ -355,7 +384,14 @@ describe("Content query", () => {
expect(load).toHaveBeenCalledWith({
routeId: "content.editorial.articles",
params: {},
list: { search: "launch", pageSize: 100, offset: 100_000 },
list: {
search: "launch",
pageSize: 100,
offset: 100_000,
responseQuestionId: "",
responsePageSize: 25,
responseOffset: 0,
},
});
expect(result).toMatchObject({
ok: true,
@@ -367,6 +403,146 @@ describe("Content query", () => {
});
});
it("normalizes bounded poll response-page input before invoking the adapter", async () => {
const load = vi.fn(async () => ({
kind: "engagement" as const,
items: [],
total: 0,
partialDependencies: [],
}));
const query = createContentQuery({ load });
await query.run(context([PERMS.POLLS_VIEW, PERMS.POLLS_EDIT]), {
routeId: "content.engagement.poll-detail",
params: { id: "7" },
list: {
responseQuestionId: " 22 ",
responsePageSize: 50,
responseOffset: 100_000,
},
});
expect(load).toHaveBeenCalledWith({
routeId: "content.engagement.poll-detail",
params: { id: "7" },
list: {
search: "",
pageSize: 25,
offset: 0,
responseQuestionId: "22",
responsePageSize: 50,
responseOffset: 100_000,
},
});
});
it.each([
["mismatched id", "8", pollDetail],
[
"too many questions",
"7",
{ ...pollDetail, questions: Array(101).fill(pollDetail.questions[0]) },
],
[
"invalid response user",
"7",
{
...pollDetail,
textResponses: {
questionId: "22",
items: [
{
id: "1",
questionId: "22",
userId: "0",
username: null,
answer: "Text",
createdAt: "2026-09-02T18:00:00.000Z",
},
],
total: 1,
pageSize: 25,
offset: 0,
},
},
],
] as const)(
"fails closed for poll detail with %s",
async (_label, itemId, payload) => {
const query = createContentQuery({
load: async () => ({
kind: "engagement" as const,
items: [{ id: itemId, title: "Poll", privatePayload: payload }],
total: 1,
partialDependencies: [],
}),
});
const result = await query.run(
context([PERMS.POLLS_VIEW, PERMS.POLLS_EDIT]),
{
routeId: "content.engagement.poll-detail",
params: { id: "7" },
},
);
expect(result).toMatchObject({
ok: false,
error: { code: "DEPENDENCY_UNAVAILABLE" },
});
},
);
it("fails closed when poll creation does not return the exact create payload", async () => {
const query = createContentQuery({
load: async () => ({
kind: "engagement" as const,
items: [
{ id: "7", title: "Poll", privatePayload: { kind: "poll-create" } },
],
total: 1,
partialDependencies: [],
}),
});
expect(
await query.run(context([PERMS.POLLS_EDIT]), {
routeId: "content.engagement.poll-create",
}),
).toMatchObject({
ok: false,
error: { code: "DEPENDENCY_UNAVAILABLE" },
});
});
it("accepts an empty poll response page past its total", async () => {
const query = createContentQuery({
load: async () => ({
kind: "engagement" as const,
items: [
{
id: "7",
title: "Poll",
privatePayload: {
...pollDetail,
textResponses: {
questionId: null,
items: [],
total: 1,
pageSize: 25,
offset: 100_000,
},
},
},
],
total: 1,
partialDependencies: [],
}),
});
expect(
await query.run(context([PERMS.POLLS_VIEW, PERMS.POLLS_EDIT]), {
routeId: "content.engagement.poll-detail",
params: { id: "7" },
}),
).toMatchObject({ ok: true });
});
it("fails closed when an article detail payload is incomplete", async () => {
const query = createContentQuery({
load: async () => ({
@@ -417,6 +593,9 @@ describe("Content query", () => {
status: `published${"x".repeat(23)}`,
pageSize: 25,
offset: 0,
responseQuestionId: "",
responsePageSize: 25,
responseOffset: 0,
},
});
});
@@ -488,19 +667,31 @@ describe("Content query", () => {
});
it("has an authorized production query path for every Content route", async () => {
const load = vi.fn(async (input: ContentQueryInput) => ({
kind: input.routeId.split(".")[1] as ContentQueryData["kind"],
items: [],
total: 0,
partialDependencies: [],
}));
const load = vi.fn(async (input: ContentQueryInput) => {
const isPollCreate = input.routeId === "content.engagement.poll-create";
return {
kind: input.routeId.split(".")[1] as ContentQueryData["kind"],
items: isPollCreate
? [
{
id: "create",
title: "Create poll",
privatePayload: { kind: "poll-create" },
},
]
: [],
total: isPollCreate ? 1 : 0,
partialDependencies: [],
};
});
const query = createContentQuery({ load });
const all = context(Object.values(PERMS));
for (const routeId of CONTENT_ROUTE_IDS) {
const result = await query.run(all, {
routeId,
...(routeId === "content.engagement.event-detail"
...(routeId === "content.engagement.event-detail" ||
routeId === "content.engagement.poll-detail"
? { params: { id: "1" } }
: {}),
});