Browse Source
Phase 18: API Route Tests - 5 plans created covering: - 18-01: File I/O routes (workflow, save-generation) - 18-02: LLM route (Google, OpenAI providers) - 18-03: Generate route (Gemini provider) - 18-04: Generate route (Replicate, fal.ai providers) - 18-05: Models route (caching, aggregation) - 10 total tasks defined - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>handoff-20260429-1057
7 changed files with 712 additions and 11 deletions
@ -0,0 +1,122 @@ |
|||
--- |
|||
phase: 18-api-route-tests |
|||
plan: 01 |
|||
type: execute |
|||
--- |
|||
|
|||
<objective> |
|||
Add tests for workflow and save-generation API routes covering file I/O operations. |
|||
|
|||
Purpose: Establish API route testing patterns with file system mocking for subsequent plans. |
|||
Output: Test files for workflow and save-generation routes with fs mocking patterns. |
|||
</objective> |
|||
|
|||
<execution_context> |
|||
~/.claude/get-shit-done/workflows/execute-phase.md |
|||
~/.claude/get-shit-done/templates/summary.md |
|||
</execution_context> |
|||
|
|||
<context> |
|||
@.planning/PROJECT.md |
|||
@.planning/ROADMAP.md |
|||
@.planning/STATE.md |
|||
|
|||
**Routes to test:** |
|||
@src/app/api/workflow/route.ts |
|||
@src/app/api/save-generation/route.ts |
|||
|
|||
**Test patterns reference:** |
|||
@src/store/utils/__tests__/localStorage.test.ts |
|||
@vitest.config.ts |
|||
</context> |
|||
|
|||
<tasks> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 1: Create workflow route tests</name> |
|||
<files>src/app/api/workflow/__tests__/route.test.ts</files> |
|||
<action> |
|||
Create tests for /api/workflow route covering: |
|||
- POST: Save workflow - validates required fields, creates directories, writes JSON |
|||
- POST: Rejects missing directoryPath/filename/workflow |
|||
- POST: Rejects non-directory paths |
|||
- POST: Handles directory creation for inputs/generations |
|||
- GET: Validates directory existence |
|||
- GET: Returns exists: false for non-existent paths |
|||
|
|||
Mock fs/promises (stat, mkdir, writeFile, readdir) with vi.mock. |
|||
Mock NextRequest/NextResponse using vitest patterns. |
|||
Use vi.spyOn for fs methods to control return values per test. |
|||
|
|||
Pattern for mocking Next.js API routes: |
|||
```typescript |
|||
import { describe, it, expect, vi, beforeEach } from "vitest"; |
|||
import { NextRequest } from "next/server"; |
|||
import * as fs from "fs/promises"; |
|||
|
|||
vi.mock("fs/promises"); |
|||
|
|||
// Create mock request helper |
|||
function createMockRequest(body: unknown, method = "POST") { |
|||
return { |
|||
json: vi.fn().mockResolvedValue(body), |
|||
nextUrl: { searchParams: new URLSearchParams() }, |
|||
} as unknown as NextRequest; |
|||
} |
|||
``` |
|||
|
|||
Do NOT mock the entire route module - import and call POST/GET directly. |
|||
</action> |
|||
<verify>npm test -- src/app/api/workflow/__tests__/route.test.ts</verify> |
|||
<done>All workflow route tests pass covering POST save and GET validation</done> |
|||
</task> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 2: Create save-generation route tests</name> |
|||
<files>src/app/api/save-generation/__tests__/route.test.ts</files> |
|||
<action> |
|||
Create tests for /api/save-generation route covering: |
|||
- POST: Saves base64 image with hash-based filename |
|||
- POST: Saves base64 video with hash-based filename |
|||
- POST: Deduplicates existing files by hash suffix |
|||
- POST: Rejects missing directoryPath or content |
|||
- POST: Rejects non-directory paths |
|||
- POST: Handles data URL with various MIME types |
|||
- POST: Handles HTTP URLs (mock fetch for external URL) |
|||
|
|||
Mock fs/promises (stat, mkdir, writeFile, readdir) with vi.mock. |
|||
Mock global fetch for HTTP URL handling. |
|||
Use crypto.createHash for consistent hash generation in tests. |
|||
|
|||
Test the deduplication logic: |
|||
1. Mock readdir to return file with matching hash suffix |
|||
2. Verify no writeFile called, isDuplicate: true returned |
|||
|
|||
Test hash generation: |
|||
1. Provide known content, verify hash in filename |
|||
2. Use crypto.createHash('md5').update(buffer).digest('hex') to compute expected hash |
|||
</action> |
|||
<verify>npm test -- src/app/api/save-generation/__tests__/route.test.ts</verify> |
|||
<done>All save-generation route tests pass covering save, deduplication, and validation</done> |
|||
</task> |
|||
|
|||
</tasks> |
|||
|
|||
<verification> |
|||
Before declaring plan complete: |
|||
- [ ] `npm test -- src/app/api/workflow/__tests__/route.test.ts` passes |
|||
- [ ] `npm test -- src/app/api/save-generation/__tests__/route.test.ts` passes |
|||
- [ ] Both test files use consistent mocking patterns |
|||
- [ ] No TypeScript errors in test files |
|||
</verification> |
|||
|
|||
<success_criteria> |
|||
- All tasks completed |
|||
- All verification checks pass |
|||
- Tests cover validation, success, and error paths |
|||
- Mocking patterns established for fs and fetch |
|||
</success_criteria> |
|||
|
|||
<output> |
|||
After completion, create `.planning/phases/18-api-route-tests/18-01-SUMMARY.md` |
|||
</output> |
|||
@ -0,0 +1,140 @@ |
|||
--- |
|||
phase: 18-api-route-tests |
|||
plan: 02 |
|||
type: execute |
|||
--- |
|||
|
|||
<objective> |
|||
Add tests for LLM API route covering Google and OpenAI provider paths. |
|||
|
|||
Purpose: Test text generation API with provider mocking patterns. |
|||
Output: Comprehensive tests for /api/llm route with mocked external APIs. |
|||
</objective> |
|||
|
|||
<execution_context> |
|||
~/.claude/get-shit-done/workflows/execute-phase.md |
|||
~/.claude/get-shit-done/templates/summary.md |
|||
</execution_context> |
|||
|
|||
<context> |
|||
@.planning/PROJECT.md |
|||
@.planning/ROADMAP.md |
|||
@.planning/STATE.md |
|||
|
|||
**Route to test:** |
|||
@src/app/api/llm/route.ts |
|||
|
|||
**Test patterns from previous plan:** |
|||
@src/app/api/workflow/__tests__/route.test.ts |
|||
</context> |
|||
|
|||
<tasks> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 1: Create LLM route tests for Google provider</name> |
|||
<files>src/app/api/llm/__tests__/route.test.ts</files> |
|||
<action> |
|||
Create tests for /api/llm route Google provider path: |
|||
- POST: Generates text with Google/Gemini successfully |
|||
- POST: Handles multimodal input (images + prompt) |
|||
- POST: Rejects missing prompt |
|||
- POST: Rejects missing API key (no env var, no header) |
|||
- POST: Returns 429 on rate limit errors |
|||
- POST: Returns 500 on API errors |
|||
|
|||
Mock @google/genai GoogleGenAI class: |
|||
```typescript |
|||
vi.mock("@google/genai", () => ({ |
|||
GoogleGenAI: vi.fn().mockImplementation(() => ({ |
|||
models: { |
|||
generateContent: vi.fn().mockResolvedValue({ |
|||
text: "Generated response text", |
|||
}), |
|||
}, |
|||
})), |
|||
})); |
|||
``` |
|||
|
|||
Mock process.env.GEMINI_API_KEY for auth tests. |
|||
Test X-Gemini-API-Key header takes precedence over env var. |
|||
|
|||
Test request structure: |
|||
```typescript |
|||
const mockRequest = { |
|||
json: vi.fn().mockResolvedValue({ |
|||
prompt: "Test prompt", |
|||
provider: "google", |
|||
model: "gemini-2.5-flash", |
|||
temperature: 0.7, |
|||
maxTokens: 1024, |
|||
}), |
|||
headers: new Headers(), |
|||
} as unknown as NextRequest; |
|||
``` |
|||
</action> |
|||
<verify>npm test -- src/app/api/llm/__tests__/route.test.ts --testNamePattern="google"</verify> |
|||
<done>Google provider tests pass covering success, validation, and error paths</done> |
|||
</task> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 2: Add LLM route tests for OpenAI provider</name> |
|||
<files>src/app/api/llm/__tests__/route.test.ts</files> |
|||
<action> |
|||
Add tests for /api/llm route OpenAI provider path to existing test file: |
|||
- POST: Generates text with OpenAI successfully |
|||
- POST: Handles vision input (images + prompt) |
|||
- POST: Rejects unknown provider |
|||
- POST: Rejects missing OpenAI API key |
|||
- POST: Returns 429 on rate limit errors |
|||
- POST: Handles OpenAI API error responses |
|||
|
|||
Mock global fetch for OpenAI API calls: |
|||
```typescript |
|||
const mockFetch = vi.fn(); |
|||
global.fetch = mockFetch; |
|||
|
|||
// Success response |
|||
mockFetch.mockResolvedValueOnce({ |
|||
ok: true, |
|||
json: () => Promise.resolve({ |
|||
choices: [{ message: { content: "OpenAI response" } }], |
|||
}), |
|||
}); |
|||
|
|||
// Error response |
|||
mockFetch.mockResolvedValueOnce({ |
|||
ok: false, |
|||
status: 401, |
|||
json: () => Promise.resolve({ |
|||
error: { message: "Invalid API key" }, |
|||
}), |
|||
}); |
|||
``` |
|||
|
|||
Test X-OpenAI-API-Key header takes precedence over env var. |
|||
Test that correct model ID is sent to OpenAI API. |
|||
</action> |
|||
<verify>npm test -- src/app/api/llm/__tests__/route.test.ts</verify> |
|||
<done>All LLM route tests pass covering both providers</done> |
|||
</task> |
|||
|
|||
</tasks> |
|||
|
|||
<verification> |
|||
Before declaring plan complete: |
|||
- [ ] `npm test -- src/app/api/llm/__tests__/route.test.ts` passes |
|||
- [ ] Tests cover Google and OpenAI providers |
|||
- [ ] Error handling tested (rate limits, API errors, missing keys) |
|||
- [ ] No TypeScript errors |
|||
</verification> |
|||
|
|||
<success_criteria> |
|||
- All tasks completed |
|||
- All verification checks pass |
|||
- Both provider paths tested |
|||
- API mocking patterns work correctly |
|||
</success_criteria> |
|||
|
|||
<output> |
|||
After completion, create `.planning/phases/18-api-route-tests/18-02-SUMMARY.md` |
|||
</output> |
|||
@ -0,0 +1,125 @@ |
|||
--- |
|||
phase: 18-api-route-tests |
|||
plan: 03 |
|||
type: execute |
|||
--- |
|||
|
|||
<objective> |
|||
Add tests for generate API route covering Gemini provider path. |
|||
|
|||
Purpose: Test image generation with Gemini, the default and most common path. |
|||
Output: Tests for /api/generate Gemini path with mocked GoogleGenAI SDK. |
|||
</objective> |
|||
|
|||
<execution_context> |
|||
~/.claude/get-shit-done/workflows/execute-phase.md |
|||
~/.claude/get-shit-done/templates/summary.md |
|||
</execution_context> |
|||
|
|||
<context> |
|||
@.planning/PROJECT.md |
|||
@.planning/ROADMAP.md |
|||
@.planning/STATE.md |
|||
|
|||
**Route to test:** |
|||
@src/app/api/generate/route.ts |
|||
|
|||
**Test patterns from previous plans:** |
|||
@src/app/api/llm/__tests__/route.test.ts |
|||
</context> |
|||
|
|||
<tasks> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 1: Create generate route tests for Gemini provider</name> |
|||
<files>src/app/api/generate/__tests__/route.test.ts</files> |
|||
<action> |
|||
Create tests for /api/generate route Gemini (default) path: |
|||
- POST: Generates image successfully with prompt only |
|||
- POST: Generates image with prompt and input images |
|||
- POST: Applies aspect ratio config |
|||
- POST: Applies resolution config for nano-banana-pro model |
|||
- POST: Applies Google Search tool for nano-banana-pro |
|||
- POST: Rejects missing prompt (when no images/dynamicInputs) |
|||
- POST: Returns 500 when API key missing |
|||
- POST: Returns 429 on rate limit errors |
|||
- POST: Handles no candidates in response |
|||
- POST: Handles text-only response (no image) |
|||
|
|||
Mock @google/genai GoogleGenAI class: |
|||
```typescript |
|||
vi.mock("@google/genai", () => ({ |
|||
GoogleGenAI: vi.fn().mockImplementation(() => ({ |
|||
models: { |
|||
generateContent: vi.fn().mockResolvedValue({ |
|||
candidates: [{ |
|||
content: { |
|||
parts: [{ |
|||
inlineData: { |
|||
mimeType: "image/png", |
|||
data: "base64ImageData", |
|||
}, |
|||
}], |
|||
}, |
|||
}], |
|||
}), |
|||
}, |
|||
})), |
|||
})); |
|||
``` |
|||
|
|||
Test default model selection (nano-banana-pro when not specified). |
|||
Test X-Gemini-API-Key header takes precedence over env var. |
|||
Test response structure matches GenerateResponse type. |
|||
</action> |
|||
<verify>npm test -- src/app/api/generate/__tests__/route.test.ts --testNamePattern="Gemini"</verify> |
|||
<done>Gemini provider tests pass covering success, config options, and error paths</done> |
|||
</task> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 2: Add generate route validation and edge case tests</name> |
|||
<files>src/app/api/generate/__tests__/route.test.ts</files> |
|||
<action> |
|||
Add validation and edge case tests to existing test file: |
|||
- POST: Accepts request with only images (image-to-image) |
|||
- POST: Accepts request with dynamicInputs containing prompt |
|||
- POST: Accepts request with dynamicInputs containing image frames |
|||
- POST: Handles large response payloads (warns but succeeds) |
|||
- POST: Extracts MIME type from data URL correctly |
|||
- POST: Falls back to image/png for raw base64 |
|||
|
|||
Test provider routing: |
|||
- POST: Routes to Gemini when no selectedModel provided |
|||
- POST: Routes to Gemini when selectedModel.provider is "gemini" |
|||
|
|||
Test the getInputMappingFromSchema helper (if exported or through behavior): |
|||
- Verify schema parsing maps generic names to model-specific params |
|||
- Test array detection for multi-image inputs |
|||
|
|||
Do NOT test Replicate/fal paths in this plan - those are in 18-04. |
|||
</action> |
|||
<verify>npm test -- src/app/api/generate/__tests__/route.test.ts</verify> |
|||
<done>All Gemini path tests pass including validation and edge cases</done> |
|||
</task> |
|||
|
|||
</tasks> |
|||
|
|||
<verification> |
|||
Before declaring plan complete: |
|||
- [ ] `npm test -- src/app/api/generate/__tests__/route.test.ts` passes |
|||
- [ ] Tests cover Gemini provider path only (Replicate/fal in next plan) |
|||
- [ ] Config options tested (aspectRatio, resolution, googleSearch) |
|||
- [ ] Error handling tested |
|||
- [ ] No TypeScript errors |
|||
</verification> |
|||
|
|||
<success_criteria> |
|||
- All tasks completed |
|||
- All verification checks pass |
|||
- Gemini path thoroughly tested |
|||
- Input validation tested |
|||
</success_criteria> |
|||
|
|||
<output> |
|||
After completion, create `.planning/phases/18-api-route-tests/18-03-SUMMARY.md` |
|||
</output> |
|||
@ -0,0 +1,157 @@ |
|||
--- |
|||
phase: 18-api-route-tests |
|||
plan: 04 |
|||
type: execute |
|||
--- |
|||
|
|||
<objective> |
|||
Add tests for generate API route covering Replicate and fal.ai provider paths. |
|||
|
|||
Purpose: Complete generate route test coverage with external provider mocking. |
|||
Output: Tests for /api/generate Replicate and fal.ai paths with mocked fetch calls. |
|||
</objective> |
|||
|
|||
<execution_context> |
|||
~/.claude/get-shit-done/workflows/execute-phase.md |
|||
~/.claude/get-shit-done/templates/summary.md |
|||
</execution_context> |
|||
|
|||
<context> |
|||
@.planning/PROJECT.md |
|||
@.planning/ROADMAP.md |
|||
@.planning/STATE.md |
|||
|
|||
**Route to test:** |
|||
@src/app/api/generate/route.ts |
|||
|
|||
**Existing tests to extend:** |
|||
@src/app/api/generate/__tests__/route.test.ts |
|||
</context> |
|||
|
|||
<tasks> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 1: Add generate route tests for Replicate provider</name> |
|||
<files>src/app/api/generate/__tests__/route.test.ts</files> |
|||
<action> |
|||
Add tests for /api/generate route Replicate path to existing test file: |
|||
- POST: Generates image successfully via Replicate |
|||
- POST: Generates video successfully via Replicate |
|||
- POST: Returns 401 when Replicate API key missing |
|||
- POST: Handles rate limit (429) from Replicate |
|||
- POST: Handles prediction failure |
|||
- POST: Handles prediction timeout (5 min max) |
|||
- POST: Polls for prediction completion |
|||
- POST: Returns video URL for large videos (>20MB) |
|||
|
|||
Mock global fetch for Replicate API calls: |
|||
```typescript |
|||
// 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: "starting", |
|||
}), |
|||
}); |
|||
|
|||
// Poll prediction (succeeded) |
|||
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)), |
|||
}); |
|||
``` |
|||
|
|||
Test X-Replicate-API-Key header is passed correctly. |
|||
Test dynamicInputs are passed to prediction input. |
|||
</action> |
|||
<verify>npm test -- src/app/api/generate/__tests__/route.test.ts --testNamePattern="Replicate"</verify> |
|||
<done>Replicate provider tests pass covering success, polling, and error paths</done> |
|||
</task> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 2: Add generate route tests for fal.ai provider</name> |
|||
<files>src/app/api/generate/__tests__/route.test.ts</files> |
|||
<action> |
|||
Add tests for /api/generate route fal.ai path to existing test file: |
|||
- POST: Generates image successfully via fal.ai (images array response) |
|||
- POST: Generates video successfully via fal.ai (video object response) |
|||
- POST: Works without API key (fal.ai allows unauthenticated) |
|||
- POST: Handles rate limit (429) with and without key |
|||
- POST: Handles various response formats (image, images, video, output) |
|||
- POST: Returns video URL for large videos (>20MB) |
|||
- POST: Filters empty dynamicInputs values |
|||
|
|||
Mock global fetch for fal.ai API calls: |
|||
```typescript |
|||
// Sync API call (fal.run) |
|||
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)), |
|||
}); |
|||
|
|||
// Video response format |
|||
mockFetch.mockResolvedValueOnce({ |
|||
ok: true, |
|||
json: () => Promise.resolve({ |
|||
video: { url: "https://fal.media/output.mp4" }, |
|||
}), |
|||
}); |
|||
``` |
|||
|
|||
Test X-Fal-API-Key header is passed when provided. |
|||
Test getFalInputMapping schema parsing (via behavior). |
|||
</action> |
|||
<verify>npm test -- src/app/api/generate/__tests__/route.test.ts</verify> |
|||
<done>All generate route tests pass covering all three providers</done> |
|||
</task> |
|||
|
|||
</tasks> |
|||
|
|||
<verification> |
|||
Before declaring plan complete: |
|||
- [ ] `npm test -- src/app/api/generate/__tests__/route.test.ts` passes |
|||
- [ ] Tests cover Replicate and fal.ai provider paths |
|||
- [ ] Polling logic tested for Replicate |
|||
- [ ] Various response formats tested for fal.ai |
|||
- [ ] No TypeScript errors |
|||
</verification> |
|||
|
|||
<success_criteria> |
|||
- All tasks completed |
|||
- All verification checks pass |
|||
- All three providers thoroughly tested |
|||
- Error handling tested for each provider |
|||
</success_criteria> |
|||
|
|||
<output> |
|||
After completion, create `.planning/phases/18-api-route-tests/18-04-SUMMARY.md` |
|||
</output> |
|||
@ -0,0 +1,134 @@ |
|||
--- |
|||
phase: 18-api-route-tests |
|||
plan: 05 |
|||
type: execute |
|||
--- |
|||
|
|||
<objective> |
|||
Add tests for models API route covering caching and provider aggregation. |
|||
|
|||
Purpose: Test model discovery and caching logic for multi-provider support. |
|||
Output: Tests for /api/models route with mocked provider APIs and cache behavior. |
|||
</objective> |
|||
|
|||
<execution_context> |
|||
~/.claude/get-shit-done/workflows/execute-phase.md |
|||
~/.claude/get-shit-done/templates/summary.md |
|||
</execution_context> |
|||
|
|||
<context> |
|||
@.planning/PROJECT.md |
|||
@.planning/ROADMAP.md |
|||
@.planning/STATE.md |
|||
|
|||
**Route to test:** |
|||
@src/app/api/models/route.ts |
|||
|
|||
**Cache module to mock:** |
|||
@src/lib/providers/cache.ts |
|||
</context> |
|||
|
|||
<tasks> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 1: Create models route tests for basic functionality</name> |
|||
<files>src/app/api/models/__tests__/route.test.ts</files> |
|||
<action> |
|||
Create tests for /api/models route core functionality: |
|||
- GET: Returns models from fal.ai when no Replicate key (fal.ai works without key) |
|||
- GET: Returns models from both providers when both keys present |
|||
- GET: Returns 400 when no providers available (Replicate needs key, fal.ai not tested) |
|||
- GET: Filters by provider query param |
|||
- GET: Filters by capabilities query param |
|||
- GET: Searches by query param |
|||
- GET: Returns cached=true when all from cache |
|||
- GET: Returns cached=false when fresh fetch |
|||
|
|||
Mock @/lib/providers/cache: |
|||
```typescript |
|||
vi.mock("@/lib/providers/cache", () => ({ |
|||
getCachedModels: vi.fn(), |
|||
setCachedModels: vi.fn(), |
|||
getCacheKey: vi.fn((provider, search) => `${provider}:${search || ""}`), |
|||
})); |
|||
``` |
|||
|
|||
Mock global fetch for provider API calls: |
|||
```typescript |
|||
// Replicate models response |
|||
mockFetch.mockResolvedValueOnce({ |
|||
ok: true, |
|||
json: () => Promise.resolve({ |
|||
results: [{ owner: "stability-ai", name: "sdxl", description: "SDXL" }], |
|||
next: null, |
|||
}), |
|||
}); |
|||
|
|||
// fal.ai models response |
|||
mockFetch.mockResolvedValueOnce({ |
|||
ok: true, |
|||
json: () => Promise.resolve({ |
|||
models: [{ |
|||
endpoint_id: "fal-ai/flux", |
|||
metadata: { display_name: "Flux", category: "text-to-image", description: "" }, |
|||
}], |
|||
has_more: false, |
|||
}), |
|||
}); |
|||
``` |
|||
</action> |
|||
<verify>npm test -- src/app/api/models/__tests__/route.test.ts --testNamePattern="basic"</verify> |
|||
<done>Basic models route tests pass covering filtering and caching</done> |
|||
</task> |
|||
|
|||
<task type="auto"> |
|||
<name>Task 2: Add models route tests for caching and error handling</name> |
|||
<files>src/app/api/models/__tests__/route.test.ts</files> |
|||
<action> |
|||
Add caching and error tests to existing test file: |
|||
- GET: Returns cached models when available (no fetch) |
|||
- GET: Bypasses cache when refresh=true |
|||
- GET: Client-side filters Replicate models (caches full list, filters on read) |
|||
- GET: Handles partial provider failures gracefully |
|||
- GET: Returns 500 when all providers fail |
|||
- GET: Paginates through Replicate results (max 15 pages) |
|||
- GET: Paginates through fal.ai results (max 15 pages) |
|||
|
|||
Test capability inference for Replicate: |
|||
- Video keywords (video, animate, motion) → text-to-video |
|||
- Image-to-video keywords (img2vid, i2v) → image-to-video |
|||
- Image keywords → text-to-image |
|||
|
|||
Test fal.ai category mapping: |
|||
- Category maps directly to ModelCapability |
|||
- Non-relevant categories are filtered out |
|||
|
|||
Test sorting: |
|||
- Models sorted by provider, then by name |
|||
</action> |
|||
<verify>npm test -- src/app/api/models/__tests__/route.test.ts</verify> |
|||
<done>All models route tests pass covering caching, pagination, and error handling</done> |
|||
</task> |
|||
|
|||
</tasks> |
|||
|
|||
<verification> |
|||
Before declaring plan complete: |
|||
- [ ] `npm test -- src/app/api/models/__tests__/route.test.ts` passes |
|||
- [ ] Cache behavior tested (hit, miss, refresh) |
|||
- [ ] Both providers tested |
|||
- [ ] Filtering and search tested |
|||
- [ ] Error handling tested |
|||
- [ ] No TypeScript errors |
|||
</verification> |
|||
|
|||
<success_criteria> |
|||
- All tasks completed |
|||
- All verification checks pass |
|||
- Cache logic thoroughly tested |
|||
- Provider aggregation tested |
|||
</success_criteria> |
|||
|
|||
<output> |
|||
After completion, create `.planning/phases/18-api-route-tests/18-05-SUMMARY.md` |
|||
</output> |
|||
Loading…
Reference in new issue