Files
paseo/packages/app/src/schedules/schedule-derivation.test.ts
T
Mohamed BoudraandGitHub 6513b56571 Polish schedules list and editing (#1860)
* feat(schedules): polish schedules list and editing

Adds host filtering, status grouping, and target resolution to the
schedules screen. Extracts a shared HostFilter from sessions. Improves
schedule editing with project targets and model hydration. Handles
missing targets server-side instead of retrying forever.

* test(mcp): update schedule mocks for createOrReplace

* fix(schedules): only show host name when multiple hosts exist
2026-07-02 12:05:35 +02:00

134 lines
4.9 KiB
TypeScript

import type { ScheduleSummary } from "@getpaseo/protocol/schedule/types";
import { describe, expect, it } from "vitest";
import { resolveSchedule, scheduleBucket, type ScheduleTargetAgent } from "./schedule-derivation";
const NOW = Date.parse("2026-07-02T00:00:00.000Z");
const AGENT_ID = "00000000-0000-4000-8000-000000000000";
function makeSchedule(overrides: Partial<ScheduleSummary>): ScheduleSummary {
return {
id: "schedule-1",
name: "Nightly",
prompt: "Run the task",
cadence: { type: "every", everyMs: 60_000 },
target: { type: "new-agent", config: { provider: "codex", cwd: "/tmp/project" } },
status: "active",
createdAt: "2026-07-01T00:00:00.000Z",
updatedAt: "2026-07-01T00:00:00.000Z",
nextRunAt: "2026-07-02T01:00:00.000Z",
lastRunAt: null,
pausedAt: null,
expiresAt: null,
maxRuns: null,
...overrides,
};
}
function resolve(
schedule: ScheduleSummary,
options?: {
agents?: Array<[string, ScheduleTargetAgent]>;
projects?: Array<[string, string]>;
agentDataLoaded?: boolean;
},
) {
return resolveSchedule({
schedule,
serverId: "host-1",
now: NOW,
agentsByKey: new Map(options?.agents ?? []),
projectNameByCwd: new Map(options?.projects ?? []),
agentDataLoaded: options?.agentDataLoaded ?? true,
});
}
describe("resolveSchedule state", () => {
it("keeps active and paused schedules runnable", () => {
expect(resolve(makeSchedule({ status: "active" })).state).toBe("active");
expect(resolve(makeSchedule({ status: "paused" })).state).toBe("paused");
expect(scheduleBucket("active")).toBe("runnable");
expect(scheduleBucket("paused")).toBe("runnable");
});
it("treats a past expiresAt as expired regardless of status", () => {
const result = resolve(
makeSchedule({ status: "active", expiresAt: "2026-07-01T00:00:00.000Z" }),
);
expect(result.state).toBe("expired");
expect(result.bucket).toBe("ended");
});
it("ignores an unparseable expiresAt", () => {
expect(resolve(makeSchedule({ expiresAt: "not-a-date" })).state).toBe("active");
});
it("derives finished only from completed-and-not-expired", () => {
expect(resolve(makeSchedule({ status: "completed" })).state).toBe("finished");
expect(
resolve(makeSchedule({ status: "completed", expiresAt: "2026-07-01T00:00:00.000Z" })).state,
).toBe("expired");
});
it("marks an agent target gone when the client has no such agent", () => {
const schedule = makeSchedule({ target: { type: "agent", agentId: AGENT_ID } });
expect(resolve(schedule).state).toBe("targetGone");
expect(resolve(schedule).bucket).toBe("ended");
});
it("does not claim gone before the agent directory has loaded", () => {
const schedule = makeSchedule({ target: { type: "agent", agentId: AGENT_ID } });
expect(resolve(schedule, { agentDataLoaded: false }).state).toBe("active");
});
it("prefers target-gone over the raw paused/completed status for a live agent target", () => {
const paused = makeSchedule({
status: "paused",
target: { type: "agent", agentId: AGENT_ID },
});
expect(resolve(paused).state).toBe("targetGone");
});
it("never claims a new-agent cwd is gone", () => {
expect(resolve(makeSchedule({ status: "active" })).state).toBe("active");
});
});
describe("resolveSchedule target line", () => {
it("names an agent target by its client title and provider", () => {
const schedule = makeSchedule({ target: { type: "agent", agentId: AGENT_ID } });
const result = resolve(schedule, {
agents: [[`host-1:${AGENT_ID}`, { title: "Fix build", provider: "claude" }]],
});
expect(result.target).toEqual({ label: "Fix build", provider: "claude" });
expect(result.state).toBe("active");
});
it("falls back to Untitled agent when the agent has no title", () => {
const schedule = makeSchedule({ target: { type: "agent", agentId: AGENT_ID } });
const result = resolve(schedule, {
agents: [[`host-1:${AGENT_ID}`, { title: " ", provider: "codex" }]],
});
expect(result.target.label).toBe("Untitled agent");
});
it("labels a gone agent target as unavailable with no glyph", () => {
const schedule = makeSchedule({ target: { type: "agent", agentId: AGENT_ID } });
expect(resolve(schedule).target).toEqual({ label: "Agent unavailable", provider: null });
});
it("names a new-agent cwd by matched project, else the shortened path", () => {
const matched = makeSchedule({
target: { type: "new-agent", config: { provider: "codex", cwd: "/tmp/project" } },
});
expect(resolve(matched, { projects: [["host-1:/tmp/project", "My Project"]] }).target).toEqual({
label: "My Project",
provider: "codex",
});
const unmatched = makeSchedule({
target: { type: "new-agent", config: { provider: "codex", cwd: "/Users/alex/work/api" } },
});
expect(resolve(unmatched).target).toEqual({ label: "~/work/api", provider: "codex" });
});
});