Skip to content
Open
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
6 changes: 4 additions & 2 deletions hooks/useTaskRunStatus.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { usePrivy } from "@privy-io/react-auth";
import { useAccountOverride } from "@/providers/AccountOverrideProvider";
import {
getTaskRunStatus,
type TaskRunStatus,
Expand All @@ -22,12 +23,13 @@ const isTerminal = (data: TaskRunStatus | undefined): boolean =>
*/
export function useTaskRunStatus(runId: string) {
const { getAccessToken, authenticated } = usePrivy();
const { accountIdOverride } = useAccountOverride();

return useQuery({
queryKey: ["taskRunStatus", runId],
queryKey: ["taskRunStatus", runId, accountIdOverride],
queryFn: async () => {
const accessToken = await getAccessToken();
return getTaskRunStatus(runId, accessToken!);
return getTaskRunStatus(runId, accessToken, { accountIdOverride: accountIdOverride || undefined });
},
enabled: !!runId && authenticated,
refetchInterval: (query) => (isTerminal(query.state.data) ? false : 3000),
Expand Down
51 changes: 51 additions & 0 deletions lib/tasks/__tests__/getTaskRunStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect, vi } from "vitest";
import { getTaskRunStatus } from "@/lib/tasks/getTaskRunStatus";

describe("getTaskRunStatus", () => {
it("requests run status with runId and account_id override", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
status: "success",
runs: [
{
status: "COMPLETED",
output: { ok: true },
metadata: { step: "done" },
taskIdentifier: "task-1",
createdAt: "2026-01-01T00:00:00.000Z",
startedAt: "2026-01-01T00:00:01.000Z",
finishedAt: "2026-01-01T00:00:02.000Z",
durationMs: 1000,
},
],
}),
}) as unknown as typeof fetch;

const result = await getTaskRunStatus("run_123", "token-123", { accountIdOverride: "acc_456" });

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/tasks/runs?runId=run_123&account_id=acc_456"),
expect.objectContaining({
method: "GET",
headers: { Authorization: "Bearer token-123" },
}),
);
expect(result.status).toBe("COMPLETED");
});

it("sends empty bearer when token is null and surfaces API error", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
json: vi.fn().mockResolvedValue({ status: "error", error: "Unauthorized" }),
}) as unknown as typeof fetch;

await expect(getTaskRunStatus("run_123", null)).rejects.toThrow("Unauthorized");
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/tasks/runs?runId=run_123"),
expect.objectContaining({
headers: { Authorization: "Bearer " },
}),
);
});
});
62 changes: 62 additions & 0 deletions lib/tasks/__tests__/getTaskRuns.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getTaskRuns } from "@/lib/tasks/getTaskRuns";
import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl";

vi.mock("@/lib/api/getClientApiBaseUrl", () => ({
getClientApiBaseUrl: vi.fn(),
}));

describe("getTaskRuns", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getClientApiBaseUrl).mockReturnValue("https://api.recoupable.com");
});

it("calls /api/tasks/runs with bearer token and default limit", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({ status: "success", runs: [] }),
}) as unknown as typeof fetch;

await getTaskRuns("token-123");

expect(fetch).toHaveBeenCalledWith(
"https://api.recoupable.com/api/tasks/runs?limit=20",
expect.objectContaining({
method: "GET",
headers: { Authorization: "Bearer token-123" },
}),
);
});

it("passes account_id override when provided", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({ status: "success", runs: [] }),
}) as unknown as typeof fetch;

await getTaskRuns("token-123", { accountIdOverride: "acc_456" });

expect(fetch).toHaveBeenCalledWith(
"https://api.recoupable.com/api/tasks/runs?limit=20&account_id=acc_456",
expect.any(Object),
);
});

it("sends empty bearer when token is null", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 401,
json: vi.fn().mockResolvedValue({ status: "error", error: "Unauthorized" }),
}) as unknown as typeof fetch;

await expect(getTaskRuns(null)).rejects.toThrow("Unauthorized");

expect(fetch).toHaveBeenCalledWith(
"https://api.recoupable.com/api/tasks/runs?limit=20",
expect.objectContaining({
headers: { Authorization: "Bearer " },
}),
);
});
});
13 changes: 11 additions & 2 deletions lib/tasks/getTaskRunStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,29 @@ export interface TaskRunStatus {
durationMs: number | null;
}

interface GetTaskRunStatusOptions {
accountIdOverride?: string;
}

/**
* Fetches the current status of a Trigger.dev task run from the Recoup API.
*/
export async function getTaskRunStatus(
runId: string,
accessToken: string,
accessToken: string | null | undefined,
options: GetTaskRunStatusOptions = {},
): Promise<TaskRunStatus> {
const token = accessToken ?? "";
const url = new URL(`${TASKS_API_URL}/runs`);
url.searchParams.set("runId", runId);
if (options.accountIdOverride) {
url.searchParams.set("account_id", options.accountIdOverride);
}

const response = await fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
Authorization: `Bearer ${token}`,
},
});

Expand Down
5 changes: 3 additions & 2 deletions lib/tasks/getTaskRuns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ interface GetTaskRunsOptions {
* @returns Array of task run items
*/
export async function getTaskRuns(
accessToken: string,
accessToken: string | null | undefined,
options: GetTaskRunsOptions = {},
): Promise<TaskRunItem[]> {
const token = accessToken ?? "";
const url = new URL(`${getClientApiBaseUrl()}/api/tasks/runs`);
url.searchParams.set("limit", "20");

Expand All @@ -44,7 +45,7 @@ export async function getTaskRuns(
const response = await fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
Authorization: `Bearer ${token}`,
},
});

Expand Down
Loading