From d936ab364c622d7418c345f591b8056f1ee71030 Mon Sep 17 00:00:00 2001 From: itskrishna21 Date: Wed, 8 Apr 2026 00:45:28 +0530 Subject: [PATCH] fix(webapp): avoid scheduled test page crash on empty payloads Harden scheduled run payload parsing to handle empty/malformed packets without throwing, and add a regression test. Made-with: Cursor --- ...-scheduled-task-test-page-empty-payload.md | 7 +++ .../presenters/v3/TestTaskPresenter.server.ts | 11 +---- .../v3/getScheduleTaskRunPayload.server.ts | 22 ++++++++++ .../scheduledTaskTestPageEmptyPayload.test.ts | 44 +++++++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 .server-changes/fix-scheduled-task-test-page-empty-payload.md create mode 100644 apps/webapp/app/presenters/v3/getScheduleTaskRunPayload.server.ts create mode 100644 apps/webapp/test/scheduledTaskTestPageEmptyPayload.test.ts diff --git a/.server-changes/fix-scheduled-task-test-page-empty-payload.md b/.server-changes/fix-scheduled-task-test-page-empty-payload.md new file mode 100644 index 00000000000..924357c8d16 --- /dev/null +++ b/.server-changes/fix-scheduled-task-test-page-empty-payload.md @@ -0,0 +1,7 @@ +--- +area: webapp +type: fix +--- + +Prevent scheduled task test page crashes when prior runs have empty payloads + diff --git a/apps/webapp/app/presenters/v3/TestTaskPresenter.server.ts b/apps/webapp/app/presenters/v3/TestTaskPresenter.server.ts index 09abb22639e..9703c56f686 100644 --- a/apps/webapp/app/presenters/v3/TestTaskPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TestTaskPresenter.server.ts @@ -1,5 +1,5 @@ import { ClickHouse } from "@internal/clickhouse"; -import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3"; +import { parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3"; import { type RuntimeEnvironmentType, type TaskRunStatus, @@ -13,6 +13,7 @@ import { RunsRepository } from "~/services/runsRepository/runsRepository.server" import { getTimezones } from "~/utils/timezones.server"; import { findCurrentWorkerDeployment } from "~/v3/models/workerDeployment.server"; import { queueTypeFromType } from "./QueueRetrievePresenter.server"; +import { getScheduleTaskRunPayload } from "./getScheduleTaskRunPayload.server"; export type RunTemplate = TaskRunTemplate & { scheduledTaskPayload?: ScheduledRun["payload"]; @@ -380,11 +381,3 @@ export class TestTaskPresenter { } } -async function getScheduleTaskRunPayload(payload: string, payloadType: string) { - const packet = await parsePacket({ data: payload, dataType: payloadType }); - if (!packet.timezone) { - packet.timezone = "UTC"; - } - const parsed = ScheduledTaskPayload.safeParse(packet); - return parsed; -} diff --git a/apps/webapp/app/presenters/v3/getScheduleTaskRunPayload.server.ts b/apps/webapp/app/presenters/v3/getScheduleTaskRunPayload.server.ts new file mode 100644 index 00000000000..55c1f2458bf --- /dev/null +++ b/apps/webapp/app/presenters/v3/getScheduleTaskRunPayload.server.ts @@ -0,0 +1,22 @@ +import { ScheduledTaskPayload, parsePacket } from "@trigger.dev/core/v3"; + +export async function getScheduleTaskRunPayload(payload: string, payloadType: string) { + let packet: unknown; + + try { + packet = await parsePacket({ data: payload, dataType: payloadType }); + } catch { + packet = undefined; + } + + if (packet && typeof packet === "object" && !Array.isArray(packet)) { + const maybeTimezone = (packet as { timezone?: unknown }).timezone; + + if (typeof maybeTimezone !== "string" || maybeTimezone.length === 0) { + (packet as { timezone: string }).timezone = "UTC"; + } + } + + return ScheduledTaskPayload.safeParse(packet); +} + diff --git a/apps/webapp/test/scheduledTaskTestPageEmptyPayload.test.ts b/apps/webapp/test/scheduledTaskTestPageEmptyPayload.test.ts new file mode 100644 index 00000000000..867c6a6446a --- /dev/null +++ b/apps/webapp/test/scheduledTaskTestPageEmptyPayload.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from "vitest"; +import superjson from "superjson"; +import { getScheduleTaskRunPayload } from "../app/presenters/v3/getScheduleTaskRunPayload.server"; + +describe("getScheduleTaskRunPayload", () => { + it("should return failure when payload is empty", async () => { + const result = await getScheduleTaskRunPayload("", "application/json"); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBeDefined(); + } + }); + + it("should parse a valid scheduled payload", async () => { + const now = new Date(); + const result = await getScheduleTaskRunPayload( + superjson.stringify({ + scheduleId: "sch_123", + type: "DECLARATIVE", + timestamp: now, + timezone: "UTC", + upcoming: [now], + }), + "application/super+json" + ); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.scheduleId).toBe("sch_123"); + expect(result.data.type).toBe("DECLARATIVE"); + expect(result.data.timezone).toBe("UTC"); + expect(result.data.upcoming.length).toBe(1); + expect(result.data.timestamp).toBeInstanceOf(Date); + } + }); + + it("should return failure for invalid JSON", async () => { + const result = await getScheduleTaskRunPayload("{invalid", "application/json"); + + expect(result.success).toBe(false); + }); +}); +