/** * Tests for API definition utility types and entry point mechanism. */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect, expectTypeOf } from "vitest"; import { z } from "zod"; import { api, __setEntryPoint, getEntryPoint, type ExtractApiInput, type ExtractApiOutput, } from "./definition.js"; describe("ExtractApiInput", () => { it("extracts input type from simple API", () => { const _SimpleApi = api({ name: "SimpleApi", input: z.object({ name: z.string(), age: z.number(), }), output: z.object({ message: z.string(), }), async run() { return { message: "hello" }; }, }); type Input = ExtractApiInput; expectTypeOf().toEqualTypeOf<{ name: string; age: number; }>(); }); it("extracts input type with optional fields", () => { const _ApiWithOptional = api({ name: "ApiWithOptional", input: z.object({ email: z.string().optional(), name: z.string().optional(), }), output: z.object({ success: z.boolean(), }), async run() { return { success: true }; }, }); type Input = ExtractApiInput; expectTypeOf().toEqualTypeOf<{ email?: string; name?: string; }>(); }); it("extracts input type with nested objects", () => { const _ApiWithNested = api({ name: "ApiWithNested", input: z.object({ user: z.object({ id: z.string(), profile: z.object({ bio: z.string(), }), }), }), output: z.object({ result: z.string(), }), async run() { return { result: "ok" }; }, }); type Input = ExtractApiInput; expectTypeOf().toEqualTypeOf<{ user: { id: string; profile: { bio: string; }; }; }>(); }); it("extracts input type with arrays", () => { const _ApiWithArray = api({ name: "ApiWithArray", input: z.object({ tags: z.array(z.string()), items: z.array( z.object({ id: z.number(), name: z.string(), }), ), }), output: z.object({ count: z.number(), }), async run() { return { count: 0 }; }, }); type Input = ExtractApiInput; expectTypeOf().toEqualTypeOf<{ tags: string[]; items: Array<{ id: number; name: string; }>; }>(); }); }); describe("ExtractApiOutput", () => { it("extracts output type from simple API", () => { const _SimpleApi = api({ name: "SimpleApi", input: z.object({ name: z.string(), }), output: z.object({ greeting: z.string(), timestamp: z.number(), }), async run() { return { greeting: "hello", timestamp: Date.now() }; }, }); type Output = ExtractApiOutput; expectTypeOf().toEqualTypeOf<{ greeting: string; timestamp: number; }>(); }); it("extracts output type with complex nested structure", () => { const _ComplexApi = api({ name: "ComplexApi", input: z.object({ userId: z.string(), }), output: z.object({ user: z.object({ id: z.string(), email: z.string(), profile: z.object({ firstName: z.string(), lastName: z.string(), age: z.number().optional(), }), }), metadata: z.object({ createdAt: z.string(), updatedAt: z.string(), }), }), async run() { return { user: { id: "1", email: "test@example.com", profile: { firstName: "John", lastName: "Doe", }, }, metadata: { createdAt: "2024-01-01", updatedAt: "2024-01-01", }, }; }, }); type Output = ExtractApiOutput; expectTypeOf().toEqualTypeOf<{ user: { id: string; email: string; profile: { firstName: string; lastName: string; age?: number; }; }; metadata: { createdAt: string; updatedAt: string; }; }>(); }); it("extracts output type with arrays", () => { const _ListApi = api({ name: "ListApi", input: z.object({ limit: z.number(), }), output: z.object({ items: z.array( z.object({ id: z.string(), name: z.string(), }), ), total: z.number(), }), async run() { return { items: [], total: 0 }; }, }); type Output = ExtractApiOutput; expectTypeOf().toEqualTypeOf<{ items: Array<{ id: string; name: string; }>; total: number; }>(); }); it("extracts output type with union types", () => { const _UnionApi = api({ name: "UnionApi", input: z.object({ query: z.string(), }), output: z.object({ status: z.union([z.literal("success"), z.literal("error")]), data: z.string().optional(), error: z.string().optional(), }), async run() { return { status: "success" as const }; }, }); type Output = ExtractApiOutput; expectTypeOf().toEqualTypeOf<{ status: "success" | "error"; data?: string; error?: string; }>(); }); }); describe("ExtractApiInput and ExtractApiOutput together", () => { it("correctly extracts both input and output types", () => { const _FullApi = api({ name: "FullApi", input: z.object({ userId: z.string().uuid(), includeDetails: z.boolean().optional(), }), output: z.object({ user: z.object({ id: z.string(), name: z.string(), email: z.string().email(), }), details: z .object({ lastLogin: z.string(), accountType: z.enum(["free", "premium"]), }) .optional(), }), async run() { return { user: { id: "1", name: "John", email: "john@example.com", }, }; }, }); type Input = ExtractApiInput; type Output = ExtractApiOutput; expectTypeOf().toEqualTypeOf<{ userId: string; includeDetails?: boolean; }>(); expectTypeOf().toEqualTypeOf<{ user: { id: string; name: string; email: string; }; details?: { lastLogin: string; accountType: "free" | "premium"; }; }>(); }); }); describe("entryPoint mechanism", () => { it("api() has no entryPoint when __setEntryPoint was not called", () => { const compiled = api({ name: "NoEntryPoint", input: z.object({}), output: z.object({ ok: z.boolean() }), async run() { return { ok: true }; }, }); expect(compiled.entryPoint).toBeUndefined(); }); it("api() picks up entryPoint set via __setEntryPoint", () => { __setEntryPoint("server/apis/GetUsers/api.ts"); const compiled = api({ name: "GetUsers", input: z.object({}), output: z.object({ users: z.array(z.string()) }), async run() { return { users: [] }; }, }); expect(compiled.entryPoint).toBe("server/apis/GetUsers/api.ts"); }); it("entryPoint persists across multiple api() calls from the same file", () => { __setEntryPoint("server/apis/slack/helper.ts"); const dashboard = api({ name: "SlackDashboard", input: z.object({}), output: z.object({}), async run() { return {}; }, }); const search = api({ name: "SlackSearch", input: z.object({}), output: z.object({}), async run() { return {}; }, }); const postMessage = api({ name: "SlackPostMessage", input: z.object({}), output: z.object({}), async run() { return {}; }, }); expect(dashboard.entryPoint).toBe("server/apis/slack/helper.ts"); expect(search.entryPoint).toBe("server/apis/slack/helper.ts"); expect(postMessage.entryPoint).toBe("server/apis/slack/helper.ts"); }); it("new __setEntryPoint overwrites previous value for a different file", () => { __setEntryPoint("server/apis/slack/helper.ts"); const slackApi = api({ name: "SlackDashboard", input: z.object({}), output: z.object({}), async run() { return {}; }, }); __setEntryPoint("server/apis/users/api.ts"); const usersApi = api({ name: "GetUsers", input: z.object({}), output: z.object({}), async run() { return {}; }, }); expect(slackApi.entryPoint).toBe("server/apis/slack/helper.ts"); expect(usersApi.entryPoint).toBe("server/apis/users/api.ts"); }); });