Skip to content

Commit 1bba6c3

Browse files
Merge pull request #422 from recoupable/test
feat: include task owner email in tasks response (#418)
2 parents d673a22 + fe78298 commit 1bba6c3

File tree

6 files changed

+342
-157
lines changed

6 files changed

+342
-157
lines changed

lib/tasks/__tests__/enrichTaskWithTriggerInfo.test.ts

Lines changed: 0 additions & 105 deletions
This file was deleted.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest";
2+
import { enrichTasks } from "../enrichTasks";
3+
4+
vi.mock("@/lib/trigger/fetchTriggerRuns", () => ({
5+
fetchTriggerRuns: vi.fn(),
6+
}));
7+
8+
vi.mock("@/lib/trigger/retrieveTaskRun", () => ({
9+
retrieveTaskRun: vi.fn(),
10+
}));
11+
12+
vi.mock("@/lib/supabase/account_emails/selectAccountEmails", () => ({
13+
default: vi.fn(),
14+
}));
15+
16+
import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns";
17+
import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun";
18+
import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails";
19+
20+
const mockTask = {
21+
id: "task-123",
22+
title: "Test Task",
23+
prompt: "Do something",
24+
schedule: "0 9 * * *",
25+
account_id: "account-456",
26+
artist_account_id: "artist-789",
27+
trigger_schedule_id: "sched_abc",
28+
enabled: true,
29+
created_at: "2026-01-01T00:00:00Z",
30+
next_run: null,
31+
last_run: null,
32+
model: null,
33+
updated_at: null,
34+
} as Parameters<typeof enrichTasks>[0][number];
35+
36+
const mockRun = {
37+
id: "run_xyz",
38+
status: "COMPLETED",
39+
createdAt: "2026-03-20T09:00:00.000Z",
40+
startedAt: "2026-03-20T09:00:01.000Z",
41+
finishedAt: "2026-03-20T09:01:00.000Z",
42+
durationMs: 59000,
43+
};
44+
45+
describe("enrichTasks", () => {
46+
beforeEach(() => {
47+
vi.clearAllMocks();
48+
});
49+
50+
it("returns recent_runs, upcoming, and owner_email", async () => {
51+
vi.mocked(fetchTriggerRuns).mockResolvedValue([mockRun] as never);
52+
vi.mocked(retrieveTaskRun).mockResolvedValue({
53+
...mockRun,
54+
payload: {
55+
upcoming: ["2026-03-27T09:00:00Z", "2026-04-03T09:00:00Z"],
56+
},
57+
} as never);
58+
vi.mocked(selectAccountEmails).mockResolvedValue([
59+
{
60+
id: "email-1",
61+
account_id: "account-456",
62+
email: "owner@example.com",
63+
updated_at: "2026-01-01T00:00:00Z",
64+
},
65+
]);
66+
67+
const result = await enrichTasks([mockTask]);
68+
69+
expect(result).toEqual([
70+
{
71+
...mockTask,
72+
recent_runs: [mockRun],
73+
upcoming: ["2026-03-27T09:00:00Z", "2026-04-03T09:00:00Z"],
74+
owner_email: "owner@example.com",
75+
},
76+
]);
77+
expect(fetchTriggerRuns).toHaveBeenCalledWith({ "filter[schedule]": "sched_abc" }, 5);
78+
expect(selectAccountEmails).toHaveBeenCalledWith({ accountIds: ["account-456"] });
79+
});
80+
81+
it("returns empty trigger fields and null owner_email when no schedule or email exists", async () => {
82+
vi.mocked(selectAccountEmails).mockResolvedValue([]);
83+
84+
const result = await enrichTasks([{ ...mockTask, trigger_schedule_id: null }]);
85+
86+
expect(result).toEqual([
87+
{
88+
...mockTask,
89+
trigger_schedule_id: null,
90+
recent_runs: [],
91+
upcoming: [],
92+
owner_email: null,
93+
},
94+
]);
95+
expect(fetchTriggerRuns).not.toHaveBeenCalled();
96+
});
97+
98+
it("returns empty trigger enrichment when Trigger.dev fails", async () => {
99+
vi.mocked(fetchTriggerRuns).mockRejectedValue(new Error("API error"));
100+
vi.mocked(selectAccountEmails).mockResolvedValue([]);
101+
102+
const result = await enrichTasks([mockTask]);
103+
104+
expect(result).toEqual([
105+
{
106+
...mockTask,
107+
recent_runs: [],
108+
upcoming: [],
109+
owner_email: null,
110+
},
111+
]);
112+
});
113+
114+
it("returns empty upcoming when no runs exist", async () => {
115+
vi.mocked(fetchTriggerRuns).mockResolvedValue([] as never);
116+
vi.mocked(selectAccountEmails).mockResolvedValue([]);
117+
118+
const result = await enrichTasks([mockTask]);
119+
120+
expect(result).toEqual([
121+
{
122+
...mockTask,
123+
recent_runs: [],
124+
upcoming: [],
125+
owner_email: null,
126+
},
127+
]);
128+
expect(retrieveTaskRun).not.toHaveBeenCalled();
129+
});
130+
});
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest";
2+
import { NextRequest, NextResponse } from "next/server";
3+
import { getTasksHandler } from "@/lib/tasks/getTasksHandler";
4+
import { validateGetTasksQuery } from "@/lib/tasks/validateGetTasksQuery";
5+
import { selectScheduledActions } from "@/lib/supabase/scheduled_actions/selectScheduledActions";
6+
import { enrichTasks } from "@/lib/tasks/enrichTasks";
7+
8+
vi.mock("@/lib/networking/getCorsHeaders", () => ({
9+
getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })),
10+
}));
11+
12+
vi.mock("@/lib/tasks/validateGetTasksQuery", () => ({
13+
validateGetTasksQuery: vi.fn(),
14+
}));
15+
16+
vi.mock("@/lib/supabase/scheduled_actions/selectScheduledActions", () => ({
17+
selectScheduledActions: vi.fn(),
18+
}));
19+
20+
vi.mock("@/lib/tasks/enrichTasks", () => ({
21+
enrichTasks: vi.fn(),
22+
}));
23+
24+
describe("getTasksHandler", () => {
25+
beforeEach(() => {
26+
vi.clearAllMocks();
27+
});
28+
29+
it("returns enriched tasks with owner_email", async () => {
30+
const validatedQuery = {
31+
account_id: "owner-1",
32+
artist_account_id: "artist-1",
33+
};
34+
35+
const tasks = [
36+
{
37+
id: "task-1",
38+
account_id: "owner-1",
39+
artist_account_id: "artist-1",
40+
created_at: null,
41+
enabled: true,
42+
last_run: null,
43+
model: null,
44+
next_run: null,
45+
prompt: "prompt 1",
46+
schedule: "0 9 * * *",
47+
title: "Task One",
48+
trigger_schedule_id: null,
49+
updated_at: null,
50+
},
51+
{
52+
id: "task-2",
53+
account_id: "owner-2",
54+
artist_account_id: "artist-1",
55+
created_at: null,
56+
enabled: true,
57+
last_run: null,
58+
model: null,
59+
next_run: null,
60+
prompt: "prompt 2",
61+
schedule: "0 10 * * *",
62+
title: "Task Two",
63+
trigger_schedule_id: null,
64+
updated_at: null,
65+
},
66+
];
67+
68+
vi.mocked(validateGetTasksQuery).mockResolvedValue(validatedQuery);
69+
vi.mocked(selectScheduledActions).mockResolvedValue(tasks);
70+
vi.mocked(enrichTasks).mockResolvedValue([
71+
{
72+
...tasks[0],
73+
recent_runs: [],
74+
upcoming: [],
75+
owner_email: "owner1@example.com",
76+
},
77+
{
78+
...tasks[1],
79+
recent_runs: [],
80+
upcoming: [],
81+
owner_email: null,
82+
},
83+
]);
84+
85+
const request = new NextRequest("http://localhost:3000/api/tasks");
86+
const response = await getTasksHandler(request);
87+
const body = await response.json();
88+
89+
expect(response.status).toBe(200);
90+
expect(enrichTasks).toHaveBeenCalledWith(tasks);
91+
expect(body).toEqual({
92+
status: "success",
93+
tasks: [
94+
{
95+
...tasks[0],
96+
recent_runs: [],
97+
upcoming: [],
98+
owner_email: "owner1@example.com",
99+
},
100+
{
101+
...tasks[1],
102+
recent_runs: [],
103+
upcoming: [],
104+
owner_email: null,
105+
},
106+
],
107+
});
108+
});
109+
110+
it("returns validator errors directly", async () => {
111+
const errorResponse = NextResponse.json(
112+
{ status: "error", error: "Unauthorized" },
113+
{ status: 401 },
114+
);
115+
vi.mocked(validateGetTasksQuery).mockResolvedValue(errorResponse);
116+
117+
const request = new NextRequest("http://localhost:3000/api/tasks");
118+
const response = await getTasksHandler(request);
119+
120+
expect(response).toBe(errorResponse);
121+
expect(selectScheduledActions).not.toHaveBeenCalled();
122+
expect(enrichTasks).not.toHaveBeenCalled();
123+
});
124+
});

0 commit comments

Comments
 (0)