From 5ecf107449e3135e0f908ea3610e6645f78d4379 Mon Sep 17 00:00:00 2001 From: pradipthaadhi Date: Fri, 8 May 2026 09:45:52 +0700 Subject: [PATCH] feat: enhance task run status handling with account ID override - Updated `useTaskRunStatus` to include `accountIdOverride` in the query key and API call, allowing for account-specific task run status retrieval. - Modified `getTaskRunStatus` to accept an options parameter for `accountIdOverride`, improving flexibility in API requests. - Adjusted `getTaskRuns` to handle `accessToken` as nullable, ensuring robustness in token management. These changes improve the integration of account-specific data in task management workflows. --- hooks/useTaskRunStatus.ts | 6 +- lib/tasks/__tests__/getTaskRunStatus.test.ts | 51 ++++++++++++++++ lib/tasks/__tests__/getTaskRuns.test.ts | 62 ++++++++++++++++++++ lib/tasks/getTaskRunStatus.ts | 13 +++- lib/tasks/getTaskRuns.ts | 5 +- 5 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 lib/tasks/__tests__/getTaskRunStatus.test.ts create mode 100644 lib/tasks/__tests__/getTaskRuns.test.ts diff --git a/hooks/useTaskRunStatus.ts b/hooks/useTaskRunStatus.ts index 6b9d08e30..6bf22467e 100644 --- a/hooks/useTaskRunStatus.ts +++ b/hooks/useTaskRunStatus.ts @@ -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, @@ -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), diff --git a/lib/tasks/__tests__/getTaskRunStatus.test.ts b/lib/tasks/__tests__/getTaskRunStatus.test.ts new file mode 100644 index 000000000..2743cea54 --- /dev/null +++ b/lib/tasks/__tests__/getTaskRunStatus.test.ts @@ -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 " }, + }), + ); + }); +}); diff --git a/lib/tasks/__tests__/getTaskRuns.test.ts b/lib/tasks/__tests__/getTaskRuns.test.ts new file mode 100644 index 000000000..9d70d22d0 --- /dev/null +++ b/lib/tasks/__tests__/getTaskRuns.test.ts @@ -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 " }, + }), + ); + }); +}); diff --git a/lib/tasks/getTaskRunStatus.ts b/lib/tasks/getTaskRunStatus.ts index 7416ecedb..bf32f8037 100644 --- a/lib/tasks/getTaskRunStatus.ts +++ b/lib/tasks/getTaskRunStatus.ts @@ -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 { + 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}`, }, }); diff --git a/lib/tasks/getTaskRuns.ts b/lib/tasks/getTaskRuns.ts index 11d3bf7d2..6a5f1f12a 100644 --- a/lib/tasks/getTaskRuns.ts +++ b/lib/tasks/getTaskRuns.ts @@ -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 { + const token = accessToken ?? ""; const url = new URL(`${getClientApiBaseUrl()}/api/tasks/runs`); url.searchParams.set("limit", "20"); @@ -44,7 +45,7 @@ export async function getTaskRuns( const response = await fetch(url.toString(), { method: "GET", headers: { - Authorization: `Bearer ${accessToken}`, + Authorization: `Bearer ${token}`, }, });