fix: align poll duration parsing with strict guard

This commit is contained in:
Gustavo Madeira Santana
2026-03-05 18:13:28 -05:00
parent 5a24d7158b
commit f77824419e
6 changed files with 61 additions and 3 deletions

View File

@@ -48,6 +48,16 @@ describe("readNumberParam", () => {
expect(readNumberParam(params, "messageId")).toBe(42);
});
it("keeps partial parse behavior by default", () => {
const params = { messageId: "42abc" };
expect(readNumberParam(params, "messageId")).toBe(42);
});
it("rejects partial numeric strings when strict is enabled", () => {
const params = { messageId: "42abc" };
expect(readNumberParam(params, "messageId", { strict: true })).toBeUndefined();
});
it("truncates when integer is true", () => {
const params = { messageId: "42.9" };
expect(readNumberParam(params, "messageId", { integer: true })).toBe(42);

View File

@@ -129,9 +129,9 @@ export function readStringOrNumberParam(
export function readNumberParam(
params: Record<string, unknown>,
key: string,
options: { required?: boolean; label?: string; integer?: boolean } = {},
options: { required?: boolean; label?: string; integer?: boolean; strict?: boolean } = {},
): number | undefined {
const { required = false, label = key, integer = false } = options;
const { required = false, label = key, integer = false, strict = false } = options;
const raw = readParamRaw(params, key);
let value: number | undefined;
if (typeof raw === "number" && Number.isFinite(raw)) {
@@ -139,7 +139,7 @@ export function readNumberParam(
} else if (typeof raw === "string") {
const trimmed = raw.trim();
if (trimmed) {
const parsed = Number.parseFloat(trimmed);
const parsed = strict ? Number(trimmed) : Number.parseFloat(trimmed);
if (Number.isFinite(parsed)) {
value = parsed;
}

View File

@@ -348,6 +348,25 @@ describe("handleDiscordMessageAction", () => {
allowMultiselect: true,
},
},
{
name: "rejects partially numeric poll duration for discord poll adapter params",
input: {
action: "poll" as const,
params: {
to: "channel:123",
pollQuestion: "Ready?",
pollOption: ["Yes", "No"],
pollDurationHours: "24h",
},
},
expected: {
action: "poll",
to: "channel:123",
question: "Ready?",
answers: ["Yes", "No"],
durationHours: undefined,
},
},
{
name: "forwards accountId for thread replies",
input: {
@@ -734,6 +753,30 @@ describe("telegramMessageActions", () => {
accountId: undefined,
},
},
{
name: "poll rejects partially numeric duration strings before telegram action handoff",
action: "poll" as const,
params: {
to: "123",
pollQuestion: "Ready?",
pollOption: ["Yes", "No"],
pollDurationSeconds: "60s",
},
expectedPayload: {
action: "poll",
to: "123",
question: "Ready?",
answers: ["Yes", "No"],
allowMultiselect: undefined,
durationHours: undefined,
durationSeconds: undefined,
replyToMessageId: undefined,
messageThreadId: undefined,
isAnonymous: undefined,
silent: undefined,
accountId: undefined,
},
},
{
name: "topic-create maps to createForumTopic",
action: "topic-create" as const,

View File

@@ -91,6 +91,7 @@ export async function handleDiscordMessageAction(
const allowMultiselect = readBooleanParam(params, "pollMulti");
const durationHours = readNumberParam(params, "pollDurationHours", {
integer: true,
strict: true,
});
return await handleDiscordAction(
{

View File

@@ -159,9 +159,11 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
const answers = readStringArrayParam(params, "pollOption", { required: true });
const durationHours = readNumberParam(params, "pollDurationHours", {
integer: true,
strict: true,
});
const durationSeconds = readNumberParam(params, "pollDurationSeconds", {
integer: true,
strict: true,
});
const replyToMessageId = readNumberParam(params, "replyTo", { integer: true });
const messageThreadId = readNumberParam(params, "threadId", { integer: true });

View File

@@ -584,9 +584,11 @@ async function handlePollAction(ctx: ResolvedActionContext): Promise<MessageActi
const isAnonymous = resolveTelegramPollVisibility({ pollAnonymous, pollPublic });
const durationHours = readNumberParam(params, "pollDurationHours", {
integer: true,
strict: true,
});
const durationSeconds = readNumberParam(params, "pollDurationSeconds", {
integer: true,
strict: true,
});
const maxSelections = resolvePollMaxSelections(options.length, allowMultiselect);