Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const asEnv = (
organization: { id: env.organizationId, featureFlags: organizationFeatureFlags },
}) as unknown as AuthenticatedEnvironment;

function declarativeTasks(schedule: { cron: string; timezone: string; window?: string }): TasksArg {
function declarativeTasks(schedule: { cron: string; timezone?: string; window?: string }): TasksArg {
return [{ id: "my-task", schedule }] as TasksArg;
}

Expand Down Expand Up @@ -488,6 +488,42 @@ describe("syncDeclarativeSchedules registration", () => {
}
);

containerTest(
"preserves an existing Redis job when task schedule omits explicit timezone (defaults to UTC)",
async ({ prisma, redisOptions }) => {
const { project, prodEnv } = await seedProjectWithEnvs(prisma);
const schedule = await makeDeclarativeSchedule(prisma, project.id, [prodEnv.id]);
const updatedAt = schedule.updatedAt;
await seedScheduledTask(prisma, project.id, prodEnv.id);
const engine = createTestScheduleEngine(prisma, redisOptions);

try {
const jobId = scheduleJobId(schedule.instances[0].id);
await engine.registerNextTaskScheduleInstance({ instanceId: schedule.instances[0].id });
const before = await engine.getJob(jobId);

await syncDeclarativeSchedules(
declarativeTasks({ cron: "0 * * * *" }),
noWorker,
asEnv(prodEnv),
prisma,
engine
);
Comment on lines +505 to +511

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Test bypasses worker metadata validation

The new test passes an omitted timezone directly to syncDeclarativeSchedules. ScheduleMetadata requires one, while schedules.task emits UTC by default. Investigate which production client can reach this case.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.


expect(before).toBeDefined();
expect(await engine.getJob(jobId)).toEqual(before);
expect(
await prisma.taskSchedule.findUniqueOrThrow({
where: { id: schedule.id },
select: { updatedAt: true },
})
).toEqual({ updatedAt });
} finally {
await engine.quit();
}
}
);

containerTest(
"replaces the Redis job when declarative timing changes",
async ({ prisma, redisOptions }) => {
Expand Down
15 changes: 8 additions & 7 deletions apps/webapp/app/v3/services/createBackgroundWorker.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,7 @@ async function prepareDeclarativeSchedules(
}
assertCronMeetsFreeMinimum({
cron: task.schedule.cron,
timezone: task.schedule.timezone,
timezone: task.schedule.timezone ?? "UTC",
minimumWindowDurationSeconds,
scheduleType: "DECLARATIVE",
environmentType: environment.type,
Expand All @@ -1540,7 +1540,7 @@ async function prepareDeclarativeSchedules(
taskId: task.id,
explicitWindow: task.schedule.window,
cron: task.schedule.cron,
timezone: task.schedule.timezone,
timezone: task.schedule.timezone ?? "UTC",
defaultWindowDurationSeconds,
minimumWindowDurationSeconds,
});
Expand Down Expand Up @@ -1588,7 +1588,7 @@ export async function syncDeclarativeSchedules(
environment.projectId,
{
cron: task.schedule.cron,
timezone: task.schedule.timezone,
timezone: task.schedule.timezone ?? "UTC",
taskIdentifier: task.id,
friendlyId: existingSchedule?.friendlyId,
window: task.schedule.window,
Expand All @@ -1610,6 +1610,7 @@ export async function syncDeclarativeSchedules(
const apply = async (db: PrismaClientOrTransaction) => {
for (const { task, existingSchedule, minimumWindowDurationSeconds } of preparedTasks) {
const normalizedWindow = normalizeScheduleWindow(task.schedule.window);
const nextTimezone = task.schedule.timezone ?? "UTC";

if (existingSchedule) {
// Compare effective windows so equivalent explicit/default values preserve the pending job.
Expand All @@ -1625,13 +1626,13 @@ export async function syncDeclarativeSchedules(
}).window;
const timingChanged =
existingSchedule.generatorExpression !== task.schedule.cron ||
existingSchedule.timezone !== task.schedule.timezone ||
existingSchedule.timezone !== nextTimezone ||
!scheduleWindowsEqual(previousWindow, nextWindow) ||
// Clearing the minimum changes the effective range.
existingSchedule.minimumWindowDurationSeconds !== minimumWindowDurationSeconds;
const persistedValuesChanged =
existingSchedule.generatorExpression !== task.schedule.cron ||
existingSchedule.timezone !== task.schedule.timezone ||
existingSchedule.timezone !== nextTimezone ||
existingSchedule.windowDurationSeconds !== normalizedWindow.windowDurationSeconds ||
existingSchedule.windowPercentage !== normalizedWindow.windowPercentage ||
existingSchedule.minimumWindowDurationSeconds !== minimumWindowDurationSeconds;
Expand All @@ -1643,7 +1644,7 @@ export async function syncDeclarativeSchedules(
data: {
generatorExpression: task.schedule.cron,
generatorDescription: cronstrue.toString(task.schedule.cron),
timezone: task.schedule.timezone,
timezone: nextTimezone,
minimumWindowDurationSeconds,
...normalizedWindow,
},
Expand Down Expand Up @@ -1675,7 +1676,7 @@ export async function syncDeclarativeSchedules(
taskIdentifier: task.id,
generatorExpression: task.schedule.cron,
generatorDescription: cronstrue.toString(task.schedule.cron),
timezone: task.schedule.timezone,
timezone: nextTimezone,
type: "DECLARATIVE",
minimumWindowDurationSeconds,
...normalizedWindow,
Expand Down