From d132f811f2887c5115a8e5b9da5cc3c048970f95 Mon Sep 17 00:00:00 2001 From: Tim Dawborn Date: Thu, 5 Oct 2023 14:01:48 +1100 Subject: [PATCH] [PSC-1988] Enable `spot mock` to have a 404 fallback proxy (#2066) In order to make the mock server more useful in the case where you have multiple different upstream servers on the same hostname, this PR adds a new optional fallback proxy to `spot mock`, which is used instead of instantly 404'ing the received request if the request does not match any of the given contracts. If provided, instead of 404'ing, it will send the request through to the fallback proxy. --- README.md | 13 +++++++---- cli/src/commands/mock.ts | 10 ++++++-- lib/src/mock-server/server.spec.ts | 37 ++++++++++++++++++++++++++++++ lib/src/mock-server/server.ts | 16 ++++++++++--- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 13a571aea..652eef7c3 100644 --- a/README.md +++ b/README.md @@ -283,12 +283,15 @@ ARGUMENTS SPOT_CONTRACT path to Spot contract OPTIONS - -h, --help show CLI help - -p, --port=port (required) [default: 3010] Port on which to run the mock server - --pathPrefix=pathPrefix Prefix to prepend to each endpoint path + -h, --help show CLI help + -p, --port=port (required) [default: 3010] Port on which to run the mock server + --pathPrefix=pathPrefix Prefix to prepend to each endpoint path - --proxyBaseUrl=proxyBaseUrl If set, the server will act as a proxy and fetch data from the given remote server - instead of mocking it + --proxyBaseUrl=proxyBaseUrl If set, the server will act as a proxy and fetch data from the given + remote server instead of mocking it + + --proxyFallbackBaseUrl=proxyFallbackBaseUrl Like proxyBaseUrl, except used when the requested API does not match + defined SPOT contract. If unset, 404 will always be returned. EXAMPLE $ spot mock api.ts diff --git a/cli/src/commands/mock.ts b/cli/src/commands/mock.ts index 682fc02b3..76dccaaaf 100644 --- a/cli/src/commands/mock.ts +++ b/cli/src/commands/mock.ts @@ -28,6 +28,10 @@ export default class Mock extends Command { description: "If set, the server will act as a proxy and fetch data from the given remote server instead of mocking it" }), + proxyFallbackBaseUrl: flags.string({ + description: + "Like proxyBaseUrl, except used when the requested API does not match defined SPOT contract. If unset, 404 will always be returned." + }), port: flags.integer({ char: "p", description: "Port on which to run the mock server", @@ -42,15 +46,17 @@ export default class Mock extends Command { async run(): Promise { const { args, - flags: { port, pathPrefix, proxyBaseUrl = "" } + flags: { port, pathPrefix, proxyBaseUrl, proxyFallbackBaseUrl = "" } } = this.parse(Mock); try { - const proxyConfig = inferProxyConfig(proxyBaseUrl); + const proxyConfig = inferProxyConfig(proxyBaseUrl || ""); + const proxyFallbackConfig = inferProxyConfig(proxyFallbackBaseUrl || ""); const contract = parse(args[ARG_API]); await runMockServer(contract, { port, pathPrefix: pathPrefix ?? "", proxyConfig, + proxyFallbackConfig, logger: this }).defer(); this.log(`Mock server is running on port ${port}.`); diff --git a/lib/src/mock-server/server.spec.ts b/lib/src/mock-server/server.spec.ts index 9eb3677a4..1b43f6009 100644 --- a/lib/src/mock-server/server.spec.ts +++ b/lib/src/mock-server/server.spec.ts @@ -24,6 +24,14 @@ describe("Server", () => { }; const proxyBaseUrl = buildProxyBaseUrl(proxyConfig); + const fallbackProxyConfig: ProxyConfig = { + isHttps: false, + host: "example.com", + port: 80, + path: "" + }; + const proxyFallbackBaseUrl = buildProxyBaseUrl(fallbackProxyConfig); + afterEach(() => { nock.cleanAll(); }); @@ -167,5 +175,34 @@ describe("Server", () => { expect(typeof response.body.name).toBe(TypeKind.STRING); }); }); + + it("Requests that do not match a contract return 404 without a fallback proxy", async () => { + const { app } = runMockServer(contract, { + logger: mockLogger, + pathPrefix: "/api", + port: 8085 + }); + + await request(app) + .get("/") + .then(response => { + expect(response.statusCode).toBe(404); + }); + }); + + it("Requests that do not match a contract to proxy the request with a fallback proxy", async () => { + const { app } = runMockServer(contract, { + logger: mockLogger, + pathPrefix: "/api", + port: 8085, + proxyFallbackConfig: fallbackProxyConfig + }); + + await request(app) + .get("/") + .then(response => { + expect(response.statusCode).toBe(200); + }); + }); }); }); diff --git a/lib/src/mock-server/server.ts b/lib/src/mock-server/server.ts index b9f8164d9..0ecb6721c 100644 --- a/lib/src/mock-server/server.ts +++ b/lib/src/mock-server/server.ts @@ -24,11 +24,13 @@ export function runMockServer( port, pathPrefix, proxyConfig, + proxyFallbackConfig, logger }: { port: number; pathPrefix: string; proxyConfig?: ProxyConfig | null; + proxyFallbackConfig?: ProxyConfig | null; logger: Logger; } ) { @@ -74,9 +76,17 @@ export function runMockServer( } } - logger.error(`No match for request ${req.method} at ${req.path}.`); - resp.status(404); - resp.send(); + logger.log(`No match for request ${req.method} at ${req.path}.`); + if (proxyFallbackConfig) { + return proxyRequest({ + incomingRequest: req, + response: resp, + proxyConfig: proxyFallbackConfig + }); + } else { + resp.status(404); + resp.send(); + } }); return { app,