Skip to content
Merged
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
88 changes: 88 additions & 0 deletions src/recoup/__tests__/fetchTask.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);

// Shared logger object the mocked module returns; assertions run against it.
const logger = { warn: vi.fn(), error: vi.fn(), log: vi.fn() };
vi.mock("@trigger.dev/sdk/v3", () => ({ logger }));

const ORIGINAL_KEY = process.env.RECOUP_API_KEY;

const validTask = {
id: "t1",
title: "Daily check-in",
prompt: "Draft a check-in",
schedule: "0 9 * * *",
account_id: "acc_owner",
artist_account_id: "artist_1",
enabled: true,
};

describe("fetchTask", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
process.env.RECOUP_API_KEY = "test-key";
});

afterEach(() => {
if (ORIGINAL_KEY === undefined) delete process.env.RECOUP_API_KEY;
else process.env.RECOUP_API_KEY = ORIGINAL_KEY;
});

it("sends the x-api-key header from RECOUP_API_KEY on the GET request", async () => {
mockFetch.mockResolvedValue({
ok: true,
json: async () => ({ status: "success", tasks: [validTask] }),
});

const { fetchTask } = await import("../fetchTask");
const result = await fetchTask("351fc5b2");

expect(mockFetch).toHaveBeenCalledWith(
"https://recoup-api.vercel.app/api/tasks?id=351fc5b2",
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({
"Content-Type": "application/json",
"x-api-key": "test-key",
}),
}),
);
expect(result).toEqual({
prompt: "Draft a check-in",
accountId: "acc_owner",
artistId: "artist_1",
model: undefined,
});
});

it("returns undefined and logs an error when RECOUP_API_KEY is missing", async () => {
delete process.env.RECOUP_API_KEY;

const { fetchTask } = await import("../fetchTask");
const result = await fetchTask("351fc5b2");

expect(result).toBeUndefined();
expect(mockFetch).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalled();
});

it("logs an auth failure (not a generic error) when the API responds 401", async () => {
mockFetch.mockResolvedValue({
ok: false,
status: 401,
statusText: "Unauthorized",
});

const { fetchTask } = await import("../fetchTask");
const result = await fetchTask("351fc5b2");

expect(result).toBeUndefined();
expect(logger.error).toHaveBeenCalledWith(
expect.stringContaining("auth"),
expect.objectContaining({ status: 401 }),
);
});
});
21 changes: 20 additions & 1 deletion src/recoup/fetchTask.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { logger } from "@trigger.dev/sdk/v3";
import { z } from "zod";
import { NEW_API_BASE_URL } from "../consts";
import { NEW_API_BASE_URL, RECOUP_API_KEY } from "../consts";
import { type ChatConfig } from "../schemas/chatSchema";

// Zod schema for validating task response from Recoup Tasks API
Expand Down Expand Up @@ -36,17 +36,36 @@ export async function fetchTask(
return undefined;
}

// GET /api/tasks is auth-gated; without a key it returns 401 and the task
// would silently abort downstream with a misleading "Missing accountId".
if (!RECOUP_API_KEY) {
logger.error("Missing RECOUP_API_KEY environment variable", { externalId });
return undefined;
}

const tasksApiUrl = `${NEW_API_BASE_URL}/api/tasks`;

try {
const response = await fetch(`${tasksApiUrl}?id=${externalId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": RECOUP_API_KEY,
},
});

if (!response.ok) {
// Surface auth failures distinctly so they aren't mistaken for "task not
// found" or collapsed into the downstream "Missing accountId" error.
if (response.status === 401 || response.status === 403) {
logger.error("Recoup Tasks API auth failure", {
externalId,
status: response.status,
statusText: response.statusText,
});
return undefined;
}

logger.error("Recoup Tasks API error", {
externalId,
status: response.status,
Expand Down
Loading