Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- swagger.ts: changed apis glob from dist/routes/*.js to src/routes/*.ts so OpenAPI spec includes demo endpoints during tests (fixes 6 test failures) - Dockerfile: copy src/ to final stage for runtime swagger-jsdoc - Updated stale /v1/signup/verify test refs to /v1/signup/free (endpoint was removed when free tier was discontinued)
108 lines
4.4 KiB
TypeScript
108 lines
4.4 KiB
TypeScript
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"
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
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");
|
|
});
|
|
});
|
|
});
|