From 0f3f9e50e176b96c4c5e6b0e21218ef769cef104 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Tue, 13 Jan 2026 20:23:05 +1300 Subject: [PATCH] test(22-01): add API route tests for parameters field Add tests verifying the parameters field (UI settings) is passed to provider APIs: - Replicate: parameters passed to prediction input - Replicate: dynamicInputs take precedence when merging - fal.ai: parameters passed to request body - fal.ai: dynamicInputs take precedence when merging Co-Authored-By: Claude Opus 4.5 --- src/app/api/generate/__tests__/route.test.ts | 233 +++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/src/app/api/generate/__tests__/route.test.ts b/src/app/api/generate/__tests__/route.test.ts index 9c1472a6..01537ec7 100644 --- a/src/app/api/generate/__tests__/route.test.ts +++ b/src/app/api/generate/__tests__/route.test.ts @@ -1418,6 +1418,131 @@ describe("/api/generate route", () => { }) ); }); + + it("should pass parameters to prediction input", async () => { + // Model info fetch + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + latest_version: { id: "version123", openapi_schema: {} }, + }), + }); + + // Create prediction + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + id: "prediction123", + status: "succeeded", + output: ["https://replicate.delivery/output.png"], + }), + }); + + // Fetch output media + mockFetch.mockResolvedValueOnce({ + ok: true, + headers: new Headers({ "content-type": "image/png" }), + arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)), + }); + + const request = createMockPostRequest( + { + prompt: "Test prompt", + selectedModel: { + provider: "replicate", + modelId: "stability-ai/sdxl", + displayName: "SDXL", + }, + parameters: { + seed: 42, + num_inference_steps: 30, + guidance_scale: 7.5, + }, + }, + { "X-Replicate-API-Key": "test-replicate-key" } + ); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + + // Verify parameters were passed to prediction + const createPredictionCall = mockFetch.mock.calls[1]; + const requestBody = JSON.parse(createPredictionCall[1].body); + expect(requestBody.input).toEqual( + expect.objectContaining({ + seed: 42, + num_inference_steps: 30, + guidance_scale: 7.5, + }) + ); + }); + + it("should merge parameters with dynamicInputs (dynamicInputs take precedence)", async () => { + // Model info fetch + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + latest_version: { id: "version123", openapi_schema: {} }, + }), + }); + + // Create prediction + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + id: "prediction123", + status: "succeeded", + output: ["https://replicate.delivery/output.png"], + }), + }); + + // Fetch output media + mockFetch.mockResolvedValueOnce({ + ok: true, + headers: new Headers({ "content-type": "image/png" }), + arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)), + }); + + const request = createMockPostRequest( + { + prompt: "", + selectedModel: { + provider: "replicate", + modelId: "stability-ai/sdxl", + displayName: "SDXL", + }, + parameters: { + seed: 42, + guidance_scale: 7.5, + }, + dynamicInputs: { + prompt: "Dynamic prompt", + guidance_scale: "10.0", // Should override parameters + }, + }, + { "X-Replicate-API-Key": "test-replicate-key" } + ); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + + // Verify dynamicInputs override parameters + const createPredictionCall = mockFetch.mock.calls[1]; + const requestBody = JSON.parse(createPredictionCall[1].body); + expect(requestBody.input).toEqual( + expect.objectContaining({ + seed: 42, + prompt: "Dynamic prompt", + guidance_scale: "10.0", // dynamicInputs value + }) + ); + }); }); describe("fal.ai provider", () => { @@ -1923,6 +2048,114 @@ describe("/api/generate route", () => { ); }); + it("should pass parameters to fal.ai request body", async () => { + // fal.run API call + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + images: [{ url: "https://fal.media/output.png" }], + }), + }); + + // Fetch output media + mockFetch.mockResolvedValueOnce({ + ok: true, + headers: new Headers({ "content-type": "image/png" }), + arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)), + }); + + const request = createMockPostRequest( + { + prompt: "", + selectedModel: { + provider: "fal", + modelId: "fal-ai/flux/schnell", + displayName: "Flux Schnell", + }, + dynamicInputs: { + prompt: "Test prompt", + }, + parameters: { + seed: 12345, + num_inference_steps: 28, + guidance_scale: 3.5, + }, + }, + { "X-Fal-API-Key": "test-fal-key" } + ); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + + // Verify parameters were passed to fal.ai + const falCall = mockFetch.mock.calls[0]; + const requestBody = JSON.parse(falCall[1].body); + expect(requestBody).toEqual( + expect.objectContaining({ + seed: 12345, + num_inference_steps: 28, + guidance_scale: 3.5, + }) + ); + }); + + it("should merge parameters with dynamicInputs (dynamicInputs take precedence)", async () => { + // fal.run API call + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + images: [{ url: "https://fal.media/output.png" }], + }), + }); + + // Fetch output media + mockFetch.mockResolvedValueOnce({ + ok: true, + headers: new Headers({ "content-type": "image/png" }), + arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)), + }); + + const request = createMockPostRequest( + { + prompt: "", + selectedModel: { + provider: "fal", + modelId: "fal-ai/flux/schnell", + displayName: "Flux Schnell", + }, + parameters: { + seed: 42, + num_inference_steps: 25, + }, + dynamicInputs: { + prompt: "Dynamic prompt", + num_inference_steps: "30", // Should override parameters + }, + }, + { "X-Fal-API-Key": "test-fal-key" } + ); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + + // Verify dynamicInputs override parameters + const falCall = mockFetch.mock.calls[0]; + const requestBody = JSON.parse(falCall[1].body); + expect(requestBody).toEqual( + expect.objectContaining({ + seed: 42, + prompt: "Dynamic prompt", + num_inference_steps: "30", // dynamicInputs value + }) + ); + }); + it("should handle error response with error.message format", async () => { // Schema fetch (for input mapping when no dynamicInputs) mockFetch.mockResolvedValueOnce({