Browse Source

fix: restore backward compatibility for test mocks and FAL API key

Update stale test mocks from currentNodeId to currentNodeIds in
AudioInputNode, EaseCurveNode, and VideoStitchNode tests. Replace
hard 401 return for missing FAL API key with console.warn, allowing
requests to proceed without auth (rate-limited) since
generateWithFal() already handles null keys gracefully.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
8444f2a9e6
  1. 50
      src/app/api/generate/__tests__/route.test.ts
  2. 8
      src/app/api/generate/route.ts
  3. 2
      src/components/__tests__/AudioInputNode.test.tsx
  4. 2
      src/components/__tests__/EaseCurveNode.test.tsx
  5. 2
      src/components/__tests__/VideoStitchNode.test.tsx

50
src/app/api/generate/__tests__/route.test.ts

@ -1720,9 +1720,30 @@ describe("/api/generate route", () => {
expect(data.contentType).toBe("video"); expect(data.contentType).toBe("video");
}); });
it("should return 401 without API key", async () => { it("should proceed without API key (rate-limited)", async () => {
delete process.env.FAL_API_KEY; delete process.env.FAL_API_KEY;
// Schema fetch (for input mapping when no dynamicInputs)
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ models: [] }),
});
// fal.run API call succeeds without auth
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({
images: [{ url: "https://example.com/image.png", content_type: "image/png" }],
}),
});
// Image download
mockFetch.mockResolvedValueOnce({
ok: true,
headers: new Headers({ "content-type": "image/png" }),
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
});
const request = createMockPostRequest({ const request = createMockPostRequest({
prompt: "A beautiful landscape", prompt: "A beautiful landscape",
selectedModel: { selectedModel: {
@ -1735,9 +1756,9 @@ describe("/api/generate route", () => {
const response = await POST(request); const response = await POST(request);
const data = await response.json(); const data = await response.json();
expect(response.status).toBe(401); // Should proceed without key (no 401 early return)
expect(data.success).toBe(false); expect(response.status).toBe(200);
expect(data.error).toContain("API key not configured"); expect(data.success).toBe(true);
}); });
it("should handle rate limit (429) with API key", async () => { it("should handle rate limit (429) with API key", async () => {
@ -1775,9 +1796,22 @@ describe("/api/generate route", () => {
expect(data.error).toContain("Try again in a moment"); expect(data.error).toContain("Try again in a moment");
}); });
it("should return 401 for rate limit scenario without API key", async () => { it("should handle rate limit (429) without API key", async () => {
delete process.env.FAL_API_KEY; delete process.env.FAL_API_KEY;
// Schema fetch (for input mapping when no dynamicInputs)
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ models: [] }),
});
// fal.run returns 429 since no auth
mockFetch.mockResolvedValueOnce({
ok: false,
status: 429,
text: () => Promise.resolve(JSON.stringify({ detail: "Rate limit exceeded" })),
});
const request = createMockPostRequest({ const request = createMockPostRequest({
prompt: "Test prompt", prompt: "Test prompt",
selectedModel: { selectedModel: {
@ -1790,10 +1824,10 @@ describe("/api/generate route", () => {
const response = await POST(request); const response = await POST(request);
const data = await response.json(); const data = await response.json();
// Without API key, route returns 401 before reaching fal.ai // Without API key, request proceeds but may get rate-limited by fal.ai
expect(response.status).toBe(401); expect(response.status).toBe(500);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.error).toContain("API key not configured"); expect(data.error).toContain("Rate limit exceeded");
}); });
it("should handle image object response format", async () => { it("should handle image object response format", async () => {

8
src/app/api/generate/route.ts

@ -2339,13 +2339,7 @@ export async function POST(request: NextRequest) {
const falApiKey = request.headers.get("X-Fal-API-Key") || process.env.FAL_API_KEY || null; const falApiKey = request.headers.get("X-Fal-API-Key") || process.env.FAL_API_KEY || null;
if (!falApiKey) { if (!falApiKey) {
return NextResponse.json<GenerateResponse>( console.warn(`[API:${requestId}] No FAL API key configured. Proceeding without auth (rate-limited).`);
{
success: false,
error: "fal.ai API key not configured. Add FAL_API_KEY to .env.local or configure in Settings.",
},
{ status: 401 }
);
} }
// For fal.ai, keep Data URIs as-is since localhost URLs won't work // For fal.ai, keep Data URIs as-is since localhost URLs won't work

2
src/components/__tests__/AudioInputNode.test.tsx

@ -89,7 +89,7 @@ function setMockStoreState(overrides: Record<string, unknown> = {}) {
edges: [], edges: [],
nodes: [], nodes: [],
isRunning: false, isRunning: false,
currentNodeId: null, currentNodeIds: [],
groups: {}, groups: {},
getNodesWithComments: vi.fn(() => []), getNodesWithComments: vi.fn(() => []),
markCommentViewed: vi.fn(), markCommentViewed: vi.fn(),

2
src/components/__tests__/EaseCurveNode.test.tsx

@ -95,7 +95,7 @@ function setMockStoreState(overrides: Record<string, unknown> = {}) {
edges: [], edges: [],
nodes: [], nodes: [],
isRunning: false, isRunning: false,
currentNodeId: null, currentNodeIds: [],
groups: {}, groups: {},
getNodesWithComments: vi.fn(() => []), getNodesWithComments: vi.fn(() => []),
markCommentViewed: vi.fn(), markCommentViewed: vi.fn(),

2
src/components/__tests__/VideoStitchNode.test.tsx

@ -82,7 +82,7 @@ function setMockStoreState(overrides: Record<string, unknown> = {}) {
edges: [], edges: [],
nodes: [], nodes: [],
isRunning: false, isRunning: false,
currentNodeId: null, currentNodeIds: [],
groups: {}, groups: {},
getNodesWithComments: vi.fn(() => []), getNodesWithComments: vi.fn(() => []),
markCommentViewed: vi.fn(), markCommentViewed: vi.fn(),

Loading…
Cancel
Save