import { describe, it, expect } from "vitest"; import { swaggerSpec } from "../swagger.js"; describe("OpenAPI spec accuracy", () => { const spec = swaggerSpec as any; it("should NOT include /v1/billing/webhook (internal Stripe endpoint)", () => { expect(spec.paths).not.toHaveProperty("/v1/billing/webhook"); }); it("should NOT include /v1/billing/success (browser redirect page)", () => { expect(spec.paths).not.toHaveProperty("/v1/billing/success"); }); it("should mark /v1/signup/free as deprecated", () => { expect(spec.paths["/v1/signup/free"]?.post?.deprecated).toBe(true); }); describe("Rate limit headers", () => { it("should define rate limit header components", () => { expect(spec.components.headers).toBeDefined(); expect(spec.components.headers["X-RateLimit-Limit"]).toBeDefined(); expect(spec.components.headers["X-RateLimit-Remaining"]).toBeDefined(); expect(spec.components.headers["X-RateLimit-Reset"]).toBeDefined(); expect(spec.components.headers["Retry-After"]).toBeDefined(); }); it("X-RateLimit-Limit should be integer type with description", () => { const header = spec.components.headers["X-RateLimit-Limit"]; expect(header.schema.type).toBe("integer"); expect(header.description).toContain("maximum"); }); it("X-RateLimit-Remaining should be integer type with description", () => { const header = spec.components.headers["X-RateLimit-Remaining"]; expect(header.schema.type).toBe("integer"); expect(header.description).toContain("remaining"); }); it("X-RateLimit-Reset should be integer type with Unix timestamp description", () => { const header = spec.components.headers["X-RateLimit-Reset"]; expect(header.schema.type).toBe("integer"); expect(header.description.toLowerCase()).toContain("unix"); expect(header.description.toLowerCase()).toContain("timestamp"); }); it("Retry-After should be integer type with description about seconds", () => { const header = spec.components.headers["Retry-After"]; expect(header.schema.type).toBe("integer"); expect(header.description.toLowerCase()).toContain("second"); }); const conversionEndpoints = [ "/v1/convert/html", "/v1/convert/markdown", "/v1/convert/url", ]; const demoEndpoints = ["/v1/demo/html", "/v1/demo/markdown"]; const allRateLimitedEndpoints = [...conversionEndpoints, ...demoEndpoints]; allRateLimitedEndpoints.forEach((endpoint) => { describe(`${endpoint}`, () => { it("should include rate limit headers in 200 response", () => { const response200 = spec.paths[endpoint]?.post?.responses["200"]; expect(response200).toBeDefined(); expect(response200.headers).toBeDefined(); expect(response200.headers["X-RateLimit-Limit"]).toBeDefined(); expect(response200.headers["X-RateLimit-Remaining"]).toBeDefined(); expect(response200.headers["X-RateLimit-Reset"]).toBeDefined(); }); it("should reference header components in 200 response", () => { const headers = spec.paths[endpoint]?.post?.responses["200"]?.headers; expect(headers["X-RateLimit-Limit"].$ref).toBe( "#/components/headers/X-RateLimit-Limit" ); expect(headers["X-RateLimit-Remaining"].$ref).toBe( "#/components/headers/X-RateLimit-Remaining" ); expect(headers["X-RateLimit-Reset"].$ref).toBe( "#/components/headers/X-RateLimit-Reset" ); }); it("should include Retry-After header in 429 response", () => { const response429 = spec.paths[endpoint]?.post?.responses["429"]; expect(response429).toBeDefined(); expect(response429.headers).toBeDefined(); expect(response429.headers["Retry-After"]).toBeDefined(); expect(response429.headers["Retry-After"].$ref).toBe( "#/components/headers/Retry-After" ); }); }); }); const allConversionEndpoints = [ "/v1/convert/html", "/v1/convert/markdown", "/v1/convert/url", "/v1/demo/html", "/v1/demo/markdown", ]; describe("X-Render-Time header", () => { it("should define X-Render-Time header component", () => { expect(spec.components.headers["X-Render-Time"]).toBeDefined(); expect(spec.components.headers["X-Render-Time"].schema.type).toBe("integer"); expect(spec.components.headers["X-Render-Time"].description).toContain("render time"); }); allConversionEndpoints.forEach((endpoint) => { it(`${endpoint} 200 response should include X-Render-Time header`, () => { const response200 = spec.paths[endpoint]?.post?.responses["200"]; expect(response200.headers["X-Render-Time"]).toBeDefined(); expect(response200.headers["X-Render-Time"].$ref).toBe( "#/components/headers/X-Render-Time" ); }); }); }); it("should mention rate limit headers in API description", () => { const description = spec.info.description; expect(description).toContain("X-RateLimit-Limit"); expect(description).toContain("X-RateLimit-Remaining"); expect(description).toContain("X-RateLimit-Reset"); expect(description).toContain("Retry-After"); expect(description).toContain("429"); }); }); });