From e81d32e40d862f43b742e10fb2d32e4b2addecf9 Mon Sep 17 00:00:00 2001 From: Patrick McElhaney Date: Mon, 4 Sep 2023 19:47:11 -0400 Subject: [PATCH] fixed a bunch of TypeScript error and a bug where exmaples were not read properly --- src/server/registry.ts | 7 +++- src/server/response-builder.ts | 14 ++++++-- test/server/koa-middleware.test.ts | 48 +++++++++++++++++++++------- test/server/module-loader.test.ts | 2 ++ test/server/registry.test.ts | 24 +++++++++++--- test/server/response-builder.test.ts | 8 ++++- 6 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/server/registry.ts b/src/server/registry.ts index d3f051a3..f2c0ef94 100644 --- a/src/server/registry.ts +++ b/src/server/registry.ts @@ -232,4 +232,9 @@ export class Registry { } } -export type { CounterfactResponseObject, HttpMethods, Module }; +export type { + CounterfactResponseObject, + HttpMethods, + Module, + RequestDataWithBody, +}; diff --git a/src/server/response-builder.ts b/src/server/response-builder.ts index 7bd5fcb3..c4e5f59a 100644 --- a/src/server/response-builder.ts +++ b/src/server/response-builder.ts @@ -50,6 +50,12 @@ function unknownStatusCodeResponse(statusCode: number | undefined) { }; } +interface Example { + description: string; + summary: string; + value: unknown; +} + export type MediaType = `${string}/${string}`; export interface OpenApiResponse { @@ -64,7 +70,7 @@ export interface OpenApiOperation { [status: string]: { content?: { [type: number | string]: { - examples?: unknown[]; + examples?: { [key: string]: Example }; schema: unknown; }; }; @@ -142,7 +148,11 @@ export function createResponseBuilder( content: Object.keys(content).map((type) => ({ body: content[type]?.examples - ? oneOf(content[type]?.examples ?? []) + ? oneOf( + Object.values(content[type]?.examples ?? []).map( + (example) => example.value, + ), + ) : JSONSchemaFaker.generate( // eslint-disable-next-line total-functions/no-unsafe-readonly-mutable-assignment content[type]?.schema ?? { type: "object" }, diff --git a/test/server/koa-middleware.test.ts b/test/server/koa-middleware.test.ts index ddcdef5c..2a25919c 100644 --- a/test/server/koa-middleware.test.ts +++ b/test/server/koa-middleware.test.ts @@ -1,22 +1,24 @@ // eslint-disable-next-line import/no-extraneous-dependencies, n/no-extraneous-import import { jest } from "@jest/globals"; +import type { Context as KoaContext, ParameterizedContext } from "koa"; +import type KoaProxy from "koa-proxy"; import { ContextRegistry } from "../../src/server/context-registry.js"; import { Dispatcher } from "../../src/server/dispatcher.js"; import { koaMiddleware } from "../../src/server/koa-middleware.js"; import { Registry } from "../../src/server/registry.js"; -function mockKoaProxy(options: { host: string }) { - return function proxy(ctx: { mockProxyHost: string }) { - ctx.mockProxyHost = options.host; +const mockKoaProxy = (options: KoaProxy.Options | undefined) => + function proxy(ctx: KoaContext) { + ctx.mockProxyHost = options?.host; }; -} describe("koa middleware", () => { it("passes the request to the dispatcher and returns the response", async () => { const registry = new Registry(); registry.add("/hello", { + // @ts-expect-error - not obvious how to make TS happy here, and it's just a unit test POST({ body }: { body: { name: string } }) { return { body: `Hello, ${body.name}!`, @@ -26,6 +28,7 @@ describe("koa middleware", () => { const dispatcher = new Dispatcher(registry, new ContextRegistry()); const middleware = koaMiddleware(dispatcher); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const ctx = { req: { path: "/hello", @@ -38,9 +41,11 @@ describe("koa middleware", () => { }, set: jest.fn(), - }; + } as unknown as ParameterizedContext; - await middleware(ctx, () => undefined); + await middleware(ctx, async () => { + await Promise.resolve(undefined); + }); expect(ctx.status).toBe(200); expect(ctx.body).toBe("Hello, Homer!"); @@ -63,9 +68,13 @@ describe("koa middleware", () => { request: { method: "GET", path: "/not-modified" }, set: () => undefined, + status: undefined, }; - await middleware(ctx); + // @ts-expect-error - not obvious how to make TS happy here, and it's just a unit test + await middleware(ctx, async () => { + await Promise.resolve(undefined); + }); expect(ctx.status).toBe(304); }); @@ -86,10 +95,14 @@ describe("koa middleware", () => { mockKoaProxy, ); const ctx = { + mockProxyHost: undefined, request: { method: "GET", path: "/proxy" }, }; - await middleware(ctx); + // @ts-expect-error - not obvious how to make TS happy here, and it's just a unit test + await middleware(ctx, async () => { + await Promise.resolve(undefined); + }); expect(ctx.mockProxyHost).toBe("https://example.com"); }); @@ -100,7 +113,8 @@ describe("koa middleware", () => { registry.add("/hello", { POST({ body }) { return { - body: `Hello, ${body.name}!`, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + body: `Hello, ${(body as { name: string }).name}!`, }; }, }); @@ -108,6 +122,8 @@ describe("koa middleware", () => { const dispatcher = new Dispatcher(registry, new ContextRegistry()); const middleware = koaMiddleware(dispatcher); const ctx = { + body: undefined, + req: { path: "/hello", }, @@ -119,9 +135,14 @@ describe("koa middleware", () => { }, set: jest.fn(), + + status: undefined, }; - await middleware(ctx); + // @ts-expect-error - not obvious how to make TS happy here, and it's just a unit test + await middleware(ctx, async () => { + await Promise.resolve(undefined); + }); expect(ctx.status).toBe(200); expect(ctx.body).toBe("Hello, Homer!"); @@ -141,7 +162,8 @@ describe("koa middleware", () => { registry.add("/hello", { POST({ body }) { return { - body: `Hello, ${body.name}!`, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + body: `Hello, ${(body as { name: string }).name}!`, }; }, }); @@ -149,6 +171,8 @@ describe("koa middleware", () => { const dispatcher = new Dispatcher(registry, new ContextRegistry()); const middleware = koaMiddleware(dispatcher); const ctx = { + body: undefined, + req: { path: "/hello", }, @@ -167,8 +191,10 @@ describe("koa middleware", () => { }, set: jest.fn(), + status: undefined, }; + // @ts-expect-error - not obvious how to make TS happy here, and it's just a unit test await middleware(ctx); expect(ctx.status).toBe(200); diff --git a/test/server/module-loader.test.ts b/test/server/module-loader.test.ts index 5ca97b29..7cc06fd5 100644 --- a/test/server/module-loader.test.ts +++ b/test/server/module-loader.test.ts @@ -152,8 +152,10 @@ describe("a module loader", () => { const response = registry.endpoint( "GET", "/change", + // @ts-expect-error - not going to create a whole context object for a test )({ headers: {}, matchedPath: "", path: {}, query: {} }); + // @ts-expect-error - TypeScript doesn't know that the response will have a body property expect(response.body).toBe("after change"); expect(registry.exists("GET", "/late/addition")).toBe(true); diff --git a/test/server/registry.test.ts b/test/server/registry.test.ts index 5b0e6d30..a191cfe3 100644 --- a/test/server/registry.test.ts +++ b/test/server/registry.test.ts @@ -1,4 +1,8 @@ -import { type Module, Registry } from "../../src/server/registry.js"; +import { + type Module, + Registry, + type RequestDataWithBody, +} from "../../src/server/registry.js"; function makeModule(name: string): Module { return { @@ -11,6 +15,7 @@ function makeModule(name: string): Module { async function identifyModule( node: { module?: Module } | undefined, ): Promise { + // @ts-expect-error - not creating an entire request object // eslint-disable-next-line new-cap return await node?.module?.GET?.({ headers: {}, @@ -78,9 +83,13 @@ describe("a registry", () => { query: {}, }; + // @ts-expect-error - chill out, TypeScript const getA = await registry.endpoint("GET", "/a")(props); + // @ts-expect-error - chill out, TypeScript const getB = await registry.endpoint("GET", "/b")(props); + // @ts-expect-error - chill out, TypeScript const postA = await registry.endpoint("POST", "/a")(props); + // @ts-expect-error - chill out, TypeScript const postB = await registry.endpoint("POST", "/b")(props); expect(getA).toBe("GET a"); @@ -119,9 +128,9 @@ describe("a registry", () => { registry.add("/{organization}/users/{username}/friends/{page}", { GET({ path }) { return { - body: `page ${String(path.page)} of ${String( - path.username, - )}'s friends in ${String(path.organization)}`, + body: `page ${String(path?.page)} of ${String( + path?.username, + )}'s friends in ${String(path?.organization)}`, headers: { "content-type": "text/plain" }, @@ -134,6 +143,7 @@ describe("a registry", () => { await registry.endpoint( "GET", "/acme/users/alice/friends/2", + // @ts-expect-error - not creating an entire request object )({ headers: {}, matchedPath: "", path: {}, query: {} }), ).toStrictEqual({ body: "page 2 of alice's friends in acme", @@ -156,7 +166,11 @@ describe("a registry", () => { }); expect( - await registry.endpoint("GET", "/Acme/users/alice/Friends/2")({}), + await registry.endpoint( + "GET", + "/Acme/users/alice/Friends/2", + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + )({} as RequestDataWithBody), ).toStrictEqual({ body: "page 2 of alice's friends in Acme", }); diff --git a/test/server/response-builder.test.ts b/test/server/response-builder.test.ts index 475570e6..8323aadc 100644 --- a/test/server/response-builder.test.ts +++ b/test/server/response-builder.test.ts @@ -79,7 +79,13 @@ describe("a response builder", () => { }, "text/plain": { - examples: { text: "example text response" }, + examples: { + text: { + description: "a text response", + summary: "summary", + value: "example text response", + }, + }, schema: { examples: ["hello"],