/** * Contract Glue Generator Tests */ import { describe, test, expect } from "bun:test"; import { generateContractTypeGlue, generateContractTemplate, generateContractTypesIndex, } from "./contract-glue"; import type { RouteSpec } from "../spec/schema"; describe("generateContractTypeGlue", () => { test("should generate type glue for route with contract", () => { const route: RouteSpec = { id: "users", pattern: "/api/users", kind: "api", module: "generated/routes/api/users.ts", contractModule: "spec/contracts/users.contract.ts", slotModule: "spec/slots/users.slot.ts", }; const result = generateContractTypeGlue(route); expect(result).toContain("// Generated by Mandu"); expect(result).toContain("import type { InferContract"); expect(result).toContain("export type UsersContract"); expect(result).toContain("export type UsersGetQuery"); expect(result).toContain("export type UsersPostBody"); expect(result).toContain("export type UsersResponse200"); expect(result).toContain("export { contract }"); }); test("should return empty string for route without contract", () => { const route: RouteSpec = { id: "users", pattern: "/api/users", kind: "api", module: "generated/routes/api/users.ts", slotModule: "spec/slots/users.slot.ts", }; const result = generateContractTypeGlue(route); expect(result).toBe(""); }); test("should convert kebab-case to PascalCase", () => { const route: RouteSpec = { id: "user-profiles", pattern: "/api/user-profiles", kind: "api", module: "generated/routes/api/user-profiles.ts", contractModule: "spec/contracts/user-profiles.contract.ts", }; const result = generateContractTypeGlue(route); expect(result).toContain("export type UserProfilesContract"); expect(result).toContain("export type UserProfilesGetQuery"); }); test("should convert snake_case to PascalCase", () => { const route: RouteSpec = { id: "user_settings", pattern: "/api/user_settings", kind: "api", module: "generated/routes/api/user_settings.ts", contractModule: "spec/contracts/user_settings.contract.ts", }; const result = generateContractTypeGlue(route); expect(result).toContain("export type UserSettingsContract"); }); }); describe("generateContractTemplate", () => { test("should generate template with GET method", () => { const route: RouteSpec = { id: "users", pattern: "/api/users", kind: "api", module: "generated/routes/api/users.ts", methods: ["GET"], }; const result = generateContractTemplate(route); expect(result).toContain("// 📜 Mandu Contract - users"); expect(result).toContain("// Pattern: /api/users"); expect(result).toContain('import { z } from "zod"'); expect(result).toContain("Mandu.contract({"); expect(result).toContain("GET: {"); expect(result).toContain("query: z.object({"); expect(result).not.toContain("POST:"); }); test("should generate template with GET and POST methods", () => { const route: RouteSpec = { id: "users", pattern: "/api/users", kind: "api", module: "generated/routes/api/users.ts", methods: ["GET", "POST"], }; const result = generateContractTemplate(route); expect(result).toContain("GET: {"); expect(result).toContain("POST: {"); expect(result).toContain("body: z.object({"); expect(result).toContain("201: z.object({"); // 201 for POST }); test("should generate template with all CRUD methods", () => { const route: RouteSpec = { id: "users", pattern: "/api/users/:id", kind: "api", module: "generated/routes/api/users.ts", methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], }; const result = generateContractTemplate(route); expect(result).toContain("GET: {"); expect(result).toContain("POST: {"); expect(result).toContain("PUT: {"); expect(result).toContain("PATCH: {"); expect(result).toContain("DELETE: {"); }); test("should include helpful comments", () => { const route: RouteSpec = { id: "users", pattern: "/api/users", kind: "api", module: "generated/routes/api/users.ts", methods: ["GET"], }; const result = generateContractTemplate(route); expect(result).toContain("// TODO:"); expect(result).toContain("💡 Contract 사용법:"); expect(result).toContain("mandu generate"); }); test("should set correct description and tags", () => { const route: RouteSpec = { id: "user-profiles", pattern: "/api/user-profiles", kind: "api", module: "generated/routes/api/user-profiles.ts", methods: ["GET"], }; const result = generateContractTemplate(route); expect(result).toContain('description: "UserProfiles API"'); expect(result).toContain('tags: ["user-profiles"]'); }); test("should include error response schemas", () => { const route: RouteSpec = { id: "users", pattern: "/api/users", kind: "api", module: "generated/routes/api/users.ts", methods: ["GET"], }; const result = generateContractTemplate(route); expect(result).toContain("400: z.object({"); expect(result).toContain("error: z.string()"); expect(result).toContain("404: z.object({"); }); }); describe("generateContractTypesIndex", () => { test("should generate index file with all route exports", () => { const routeIds = ["users", "posts", "comments"]; const result = generateContractTypesIndex(routeIds); expect(result).toContain("// Generated by Mandu"); expect(result).toContain('export * from "./users.types";'); expect(result).toContain('export * from "./posts.types";'); expect(result).toContain('export * from "./comments.types";'); }); test("should handle empty route list", () => { const routeIds: string[] = []; const result = generateContractTypesIndex(routeIds); expect(result).toContain("// Generated by Mandu"); expect(result).not.toContain("export *"); }); test("should handle single route", () => { const routeIds = ["users"]; const result = generateContractTypesIndex(routeIds); expect(result).toContain('export * from "./users.types";'); }); });