From a5af12ef2b9015f125361173645544c87bffd615 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Sun, 6 Feb 2022 10:47:59 +0000 Subject: [PATCH 01/14] refactor: move createExpressApp to server file and simplify run file --- src/index.ts | 3 +- src/run.ts | 346 +------------------------------------------------- src/server.ts | 345 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 350 insertions(+), 344 deletions(-) create mode 100644 src/server.ts diff --git a/src/index.ts b/src/index.ts index ff72555..e1dda4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,4 +7,5 @@ export { HttpMock, Scenario, } from './types'; -export { run, createExpressApp } from './run'; +export { run } from './run'; +export { createExpressApp } from './server'; diff --git a/src/run.ts b/src/run.ts index 869ecc8..bf1360c 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,41 +1,9 @@ -import cookieParser from 'cookie-parser'; -import cors from 'cors'; -import express, { Request, Response, NextFunction } from 'express'; -import path from 'path'; import { transform } from 'server-with-kill'; -import { - modifyScenarios, - resetScenarios, - getScenarios as apiGetScenarios, -} from './apis'; -import { - getGraphQlMocks, - getGraphQlMock, - createGraphQlRequestHandler, -} from './graph-ql'; -import { - getHttpMocks, - getHttpMockAndParams, - createHttpRequestHandler, -} from './http'; -import { - Mock, - Options, - ScenarioMap, - DefaultScenario, - Context, - Scenario, - PartialContext, -} from './types'; -import { getUi, updateUi } from './ui'; -import { - getScenariosFromCookie, - getContextFromCookie, - setContextAndScenariosCookie, -} from './cookies'; +import { createExpressApp } from './server'; +import { Options, ScenarioMap, DefaultScenario } from './types'; -export { createExpressApp, run }; +export { run }; function run({ default: defaultScenario, @@ -58,311 +26,3 @@ function run({ }), ); } - -function createExpressApp({ - default: defaultScenario, - scenarios: scenarioMap = {}, - options = {}, -}: { - default: DefaultScenario; - scenarios?: ScenarioMap; - options?: Options; -}) { - let selectedScenarioNames: string[] = []; - let currentContext = getContextFromScenarios([defaultScenario]); - const { - uiPath = '/', - modifyScenariosPath = '/modify-scenarios', - resetScenariosPath = '/reset-scenarios', - scenariosPath = '/scenarios', - cookieMode = false, - } = options; - - const app = express(); - const scenarioNames = Object.keys(scenarioMap); - const groupNames = Object.values(scenarioMap).reduce( - (result, mock) => { - if ( - Array.isArray(mock) || - mock.group == null || - result.includes(mock.group) - ) { - return result; - } - - result.push(mock.group); - return result; - }, - [], - ); - - app.use(cors({ credentials: true })); - app.use(cookieParser()); - app.use(uiPath, express.static(path.join(__dirname, 'assets'))); - app.use(express.urlencoded({ extended: false })); - app.use(express.json()); - app.use(express.text({ type: 'application/graphql' })); - - app.get( - uiPath, - getUi({ - uiPath, - scenarioMap, - getScenarioNames, - }), - ); - - app.post( - uiPath, - updateUi({ - uiPath, - groupNames, - scenarioNames, - scenarioMap, - updateScenariosAndContext, - }), - ); - - app.put( - modifyScenariosPath, - modifyScenarios({ - scenarioNames, - scenarioMap, - updateScenariosAndContext, - }), - ); - - app.put( - resetScenariosPath, - resetScenarios({ - updateScenariosAndContext, - }), - ); - - app.get( - scenariosPath, - apiGetScenarios({ - scenarioMap, - getScenarioNames, - }), - ); - - app.use( - createRequestHandler({ - getScenarioNames, - defaultScenario, - scenarioMap, - getContext: ( - req: Request, - res: Response, - selectedScenarios: Scenario[], - ) => { - if (cookieMode) { - return getContextFromCookie({ - req, - res, - defaultValue: { - scenarios: getScenarioNames(req, res), - context: getContextFromScenarios(selectedScenarios), - }, - }); - } - - return currentContext; - }, - setContext: (req: Request, res: Response, context: Context) => { - if (cookieMode) { - setContextAndScenariosCookie(res, { - scenarios: getScenarioNames(req, res), - context, - }); - } else { - currentContext = context; - } - }, - }), - ); - - return app; - - function updateScenariosAndContext( - res: Response, - updatedScenarioNames: string[], - ) { - const updatedScenarios = getScenarios({ - defaultScenario, - scenarioMap, - scenarioNames: updatedScenarioNames, - }); - const context = getContextFromScenarios(updatedScenarios); - - if (cookieMode) { - setContextAndScenariosCookie(res, { - context, - scenarios: updatedScenarioNames, - }); - - return; - } - - currentContext = context; - selectedScenarioNames = updatedScenarioNames; - - return updatedScenarioNames; - } - - function getScenarioNames(req: Request, res: Response) { - if (cookieMode) { - const defaultScenarios: string[] = []; - - return getScenariosFromCookie({ - req, - res, - defaultValue: { - context: getContextFromScenarios( - getScenarios({ - defaultScenario, - scenarioMap, - scenarioNames: defaultScenarios, - }), - ), - scenarios: defaultScenarios, - }, - }); - } - - return selectedScenarioNames; - } -} - -function updateContext(context: Context, partialContext: PartialContext) { - const newContext = { - ...context, - ...(typeof partialContext === 'function' - ? partialContext(context) - : partialContext), - }; - - return newContext; -} - -function mergeMocks(scenarioMap: ({ mocks: Mock[] } | Mock[])[]) { - return scenarioMap.reduce( - (result, scenarioMock) => - result.concat( - Array.isArray(scenarioMock) ? scenarioMock : scenarioMock.mocks, - ), - [], - ); -} - -function getMocksFromScenarios(scenarios: Scenario[]) { - const mocks = mergeMocks(scenarios); - const httpMocks = getHttpMocks(mocks); - const graphQlMocks = getGraphQlMocks(mocks); - - return { httpMocks, graphQlMocks }; -} - -function getScenarios({ - defaultScenario, - scenarioMap, - scenarioNames, -}: { - defaultScenario: DefaultScenario; - scenarioMap: ScenarioMap; - scenarioNames: string[]; -}): Scenario[] { - return [defaultScenario].concat( - scenarioNames.map(scenario => scenarioMap[scenario]), - ); -} - -function getContextFromScenarios(scenarios: Scenario[]) { - let context: Context = {}; - scenarios.forEach(mock => { - if (!Array.isArray(mock) && mock.context) { - context = { ...context, ...mock.context }; - } - }); - - return context; -} - -function createRequestHandler({ - getScenarioNames, - defaultScenario, - scenarioMap, - getContext, - setContext, -}: { - getScenarioNames: (req: Request, res: Response) => string[]; - defaultScenario: DefaultScenario; - scenarioMap: ScenarioMap; - getContext: ( - req: Request, - res: Response, - selectedScenarios: Scenario[], - ) => Context; - setContext: (req: Request, res: Response, context: Context) => void; -}) { - return (req: Request, res: Response, next: NextFunction) => { - const scenarioNames = getScenarioNames(req, res); - const selectedScenarios = getScenarios({ - defaultScenario, - scenarioMap, - scenarioNames, - }); - - const { httpMocks, graphQlMocks } = getMocksFromScenarios( - selectedScenarios, - ); - let context: Context = getContext(req, res, selectedScenarios); - - const graphQlMock = getGraphQlMock(req, graphQlMocks); - - if (graphQlMock) { - const requestHandler = createGraphQlRequestHandler({ - graphQlMock, - updateContext: localUpdateContext, - getContext: localGetContext, - }); - - requestHandler(req, res, next); - - return; - } - - const { httpMock, params } = getHttpMockAndParams(req, httpMocks); - if (httpMock) { - const requestHandler = createHttpRequestHandler({ - httpMock, - params, - getContext: localGetContext, - updateContext: localUpdateContext, - }); - - requestHandler(req, res); - - return; - } - - // Nothing matched - default 404 from express - next(); - - function localUpdateContext(partialContext: PartialContext) { - // Although "setContext" below will ensure the context is set correctly - // for the server/cookie, if response functions call "updateContext" multiple - // times, the local version of "getContext" will return the wrong value - context = updateContext(context, partialContext); - - setContext(req, res, context); - - return context; - } - - function localGetContext() { - return context; - } - }; -} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..0ca2bf3 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,345 @@ +import cookieParser from 'cookie-parser'; +import cors from 'cors'; +import express, { Request, Response, NextFunction } from 'express'; +import path from 'path'; + +import { + modifyScenarios, + resetScenarios, + getScenarios as apiGetScenarios, +} from './apis'; +import { + getGraphQlMocks, + getGraphQlMock, + createGraphQlRequestHandler, +} from './graph-ql'; +import { + getHttpMocks, + getHttpMockAndParams, + createHttpRequestHandler, +} from './http'; +import { + Mock, + Options, + ScenarioMap, + DefaultScenario, + Context, + Scenario, + PartialContext, +} from './types'; +import { getUi, updateUi } from './ui'; +import { + getScenariosFromCookie, + getContextFromCookie, + setContextAndScenariosCookie, +} from './cookies'; + +export { createExpressApp }; + +function createExpressApp({ + default: defaultScenario, + scenarios: scenarioMap = {}, + options = {}, +}: { + default: DefaultScenario; + scenarios?: ScenarioMap; + options?: Options; +}) { + let selectedScenarioNames: string[] = []; + let currentContext = getContextFromScenarios([defaultScenario]); + const { + uiPath = '/', + modifyScenariosPath = '/modify-scenarios', + resetScenariosPath = '/reset-scenarios', + scenariosPath = '/scenarios', + cookieMode = false, + } = options; + + const app = express(); + const scenarioNames = Object.keys(scenarioMap); + const groupNames = Object.values(scenarioMap).reduce( + (result, mock) => { + if ( + Array.isArray(mock) || + mock.group == null || + result.includes(mock.group) + ) { + return result; + } + + result.push(mock.group); + return result; + }, + [], + ); + + app.use(cors({ credentials: true })); + app.use(cookieParser()); + app.use(uiPath, express.static(path.join(__dirname, 'assets'))); + app.use(express.urlencoded({ extended: false })); + app.use(express.json()); + app.use(express.text({ type: 'application/graphql' })); + + app.get( + uiPath, + getUi({ + uiPath, + scenarioMap, + getScenarioNames, + }), + ); + + app.post( + uiPath, + updateUi({ + uiPath, + groupNames, + scenarioNames, + scenarioMap, + updateScenariosAndContext, + }), + ); + + app.put( + modifyScenariosPath, + modifyScenarios({ + scenarioNames, + scenarioMap, + updateScenariosAndContext, + }), + ); + + app.put( + resetScenariosPath, + resetScenarios({ + updateScenariosAndContext, + }), + ); + + app.get( + scenariosPath, + apiGetScenarios({ + scenarioMap, + getScenarioNames, + }), + ); + + app.use( + createRequestHandler({ + getScenarioNames, + defaultScenario, + scenarioMap, + getContext: ( + req: Request, + res: Response, + selectedScenarios: Scenario[], + ) => { + if (cookieMode) { + return getContextFromCookie({ + req, + res, + defaultValue: { + scenarios: getScenarioNames(req, res), + context: getContextFromScenarios(selectedScenarios), + }, + }); + } + + return currentContext; + }, + setContext: (req: Request, res: Response, context: Context) => { + if (cookieMode) { + setContextAndScenariosCookie(res, { + scenarios: getScenarioNames(req, res), + context, + }); + } else { + currentContext = context; + } + }, + }), + ); + + return app; + + function updateScenariosAndContext( + res: Response, + updatedScenarioNames: string[], + ) { + const updatedScenarios = getScenarios({ + defaultScenario, + scenarioMap, + scenarioNames: updatedScenarioNames, + }); + const context = getContextFromScenarios(updatedScenarios); + + if (cookieMode) { + setContextAndScenariosCookie(res, { + context, + scenarios: updatedScenarioNames, + }); + + return; + } + + currentContext = context; + selectedScenarioNames = updatedScenarioNames; + + return updatedScenarioNames; + } + + function getScenarioNames(req: Request, res: Response) { + if (cookieMode) { + const defaultScenarios: string[] = []; + + return getScenariosFromCookie({ + req, + res, + defaultValue: { + context: getContextFromScenarios( + getScenarios({ + defaultScenario, + scenarioMap, + scenarioNames: defaultScenarios, + }), + ), + scenarios: defaultScenarios, + }, + }); + } + + return selectedScenarioNames; + } +} + +function updateContext(context: Context, partialContext: PartialContext) { + const newContext = { + ...context, + ...(typeof partialContext === 'function' + ? partialContext(context) + : partialContext), + }; + + return newContext; +} + +function mergeMocks(scenarioMap: ({ mocks: Mock[] } | Mock[])[]) { + return scenarioMap.reduce( + (result, scenarioMock) => + result.concat( + Array.isArray(scenarioMock) ? scenarioMock : scenarioMock.mocks, + ), + [], + ); +} + +function getMocksFromScenarios(scenarios: Scenario[]) { + const mocks = mergeMocks(scenarios); + const httpMocks = getHttpMocks(mocks); + const graphQlMocks = getGraphQlMocks(mocks); + + return { httpMocks, graphQlMocks }; +} + +function getScenarios({ + defaultScenario, + scenarioMap, + scenarioNames, +}: { + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + scenarioNames: string[]; +}): Scenario[] { + return [defaultScenario].concat( + scenarioNames.map(scenario => scenarioMap[scenario]), + ); +} + +function getContextFromScenarios(scenarios: Scenario[]) { + let context: Context = {}; + scenarios.forEach(mock => { + if (!Array.isArray(mock) && mock.context) { + context = { ...context, ...mock.context }; + } + }); + + return context; +} + +function createRequestHandler({ + getScenarioNames, + defaultScenario, + scenarioMap, + getContext, + setContext, +}: { + getScenarioNames: (req: Request, res: Response) => string[]; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + getContext: ( + req: Request, + res: Response, + selectedScenarios: Scenario[], + ) => Context; + setContext: (req: Request, res: Response, context: Context) => void; +}) { + return (req: Request, res: Response, next: NextFunction) => { + const scenarioNames = getScenarioNames(req, res); + const selectedScenarios = getScenarios({ + defaultScenario, + scenarioMap, + scenarioNames, + }); + + const { httpMocks, graphQlMocks } = getMocksFromScenarios( + selectedScenarios, + ); + let context: Context = getContext(req, res, selectedScenarios); + + const graphQlMock = getGraphQlMock(req, graphQlMocks); + + if (graphQlMock) { + const requestHandler = createGraphQlRequestHandler({ + graphQlMock, + updateContext: localUpdateContext, + getContext: localGetContext, + }); + + requestHandler(req, res, next); + + return; + } + + const { httpMock, params } = getHttpMockAndParams(req, httpMocks); + if (httpMock) { + const requestHandler = createHttpRequestHandler({ + httpMock, + params, + getContext: localGetContext, + updateContext: localUpdateContext, + }); + + requestHandler(req, res); + + return; + } + + // Nothing matched - default 404 from express + next(); + + function localUpdateContext(partialContext: PartialContext) { + // Although "setContext" below will ensure the context is set correctly + // for the server/cookie, if response functions call "updateContext" multiple + // times, the local version of "getContext" will return the wrong value + context = updateContext(context, partialContext); + + setContext(req, res, context); + + return context; + } + + function localGetContext() { + return context; + } + }; +} From 2fa89c8f83c81496bdc4cf95ccf35bb045e4b694 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Sun, 6 Feb 2022 10:58:07 +0000 Subject: [PATCH 02/14] refactor: simplify UI generation --- src/Html.tsx | 78 ++++++++++++++++++++++++++++------------------------ src/types.ts | 9 ------ src/ui.tsx | 34 +++-------------------- 3 files changed, 46 insertions(+), 75 deletions(-) diff --git a/src/Html.tsx b/src/Html.tsx index cc4e6fd..0464e54 100644 --- a/src/Html.tsx +++ b/src/Html.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { UiGroups } from './types'; +import { Groups } from './types'; function Html({ updatedScenarios, @@ -9,8 +9,8 @@ function Html({ other, }: { uiPath: string; - groups: UiGroups; - other: Array<{ name: string; checked: boolean }>; + groups: Groups; + other: Array<{ id: string; selected: boolean }>; updatedScenarios?: string[]; }) { if (uiPath[uiPath.length - 1] !== '/') { @@ -39,39 +39,45 @@ function Html({ Refresh page

- {groups.map(group => ( -
- -

{group.name}

-
-
-
- - -
- {group.scenarios.map(scenario => ( -
+ {groups.map(group => { + const noneSelected = group.scenarios.every( + scenario => !scenario.selected, + ); + + return ( +
+ +

{group.name}

+
+
+
- +
- ))} -
-
- ))} + {group.scenarios.map(scenario => ( +
+ + +
+ ))} +
+
+ ); + })} {!other.length ? null : (
@@ -79,15 +85,15 @@ function Html({
{other.map(scenario => ( -
+
- +
))}
diff --git a/src/types.ts b/src/types.ts index a20b3ec..2f0f470 100644 --- a/src/types.ts +++ b/src/types.ts @@ -98,15 +98,6 @@ export type UpdateContext = (partialContext: PartialContext) => Context; export type GetContext = () => Context; -export type UiGroups = Array<{ - name: string; - noneChecked: boolean; - scenarios: Array<{ - name: string; - checked: boolean; - }>; -}>; - export type Groups = Array<{ name: string; scenarios: Array<{ diff --git a/src/ui.tsx b/src/ui.tsx index 6b8c070..d459407 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { RequestHandler, Request, Response } from 'express'; -import { UiGroups, ScenarioMap } from './types'; +import { ScenarioMap } from './types'; import { Html } from './Html'; import { getScenarios } from './utils/get-scenarios'; @@ -18,10 +18,8 @@ function getUi({ getScenarioNames: (req: Request, res: Response) => string[]; }): RequestHandler { return (req: Request, res: Response) => { - const { groups, other } = getPageVariables( - scenarioMap, - getScenarioNames(req, res), - ); + const selectedScenarios = getScenarioNames(req, res); + const { groups, other } = getScenarios(scenarioMap, selectedScenarios); const html = renderToStaticMarkup( , @@ -65,7 +63,7 @@ function updateUi({ updateScenariosAndContext(res, updatedScenarios); - const { groups, other } = getPageVariables(scenarioMap, updatedScenarios); + const { groups, other } = getScenarios(scenarioMap, updatedScenarios); const html = renderToStaticMarkup( \n' + html); }; } - -function getPageVariables( - scenarioMap: ScenarioMap, - selectedScenarios: string[], -): { groups: UiGroups; other: Array<{ name: string; checked: boolean }> } { - const { groups, other } = getScenarios(scenarioMap, selectedScenarios); - - return { - groups: groups.map(group => { - const scenarios = group.scenarios.map(({ id, selected }) => ({ - name: id, - checked: selected, - })); - const noneChecked = scenarios.every(({ checked }) => !checked); - - return { - name: group.name, - scenarios, - noneChecked, - }; - }), - other: other.map(({ id, selected }) => ({ name: id, checked: selected })), - }; -} From 1982389589d0e3e949d16bb488956267e782837b Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Sun, 6 Feb 2022 11:02:06 +0000 Subject: [PATCH 03/14] refactor: rename utils function getScenarios to getAllScenarios --- src/apis.ts | 4 ++-- src/ui.tsx | 6 +++--- src/utils/{get-scenarios.ts => get-all-scenarios.ts} | 7 +++++-- 3 files changed, 10 insertions(+), 7 deletions(-) rename src/utils/{get-scenarios.ts => get-all-scenarios.ts} (90%) diff --git a/src/apis.ts b/src/apis.ts index 3318034..8064447 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -1,6 +1,6 @@ import { RequestHandler, Response, Request } from 'express'; import { ScenarioMap } from './types'; -import { getScenarios as utilsGetScenarios } from './utils/get-scenarios'; +import { getAllScenarios } from './utils/get-all-scenarios'; export { modifyScenarios, resetScenarios, getScenarios }; @@ -70,7 +70,7 @@ function getScenarios({ getScenarioNames: (req: Request, res: Response) => string[]; }): RequestHandler { return (req: Request, res: Response) => { - const data = utilsGetScenarios(scenarioMap, getScenarioNames(req, res)); + const data = getAllScenarios(scenarioMap, getScenarioNames(req, res)); res.json(data); }; diff --git a/src/ui.tsx b/src/ui.tsx index d459407..c8164e4 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -4,7 +4,7 @@ import { RequestHandler, Request, Response } from 'express'; import { ScenarioMap } from './types'; import { Html } from './Html'; -import { getScenarios } from './utils/get-scenarios'; +import { getAllScenarios } from './utils/get-all-scenarios'; export { getUi, updateUi }; @@ -19,7 +19,7 @@ function getUi({ }): RequestHandler { return (req: Request, res: Response) => { const selectedScenarios = getScenarioNames(req, res); - const { groups, other } = getScenarios(scenarioMap, selectedScenarios); + const { groups, other } = getAllScenarios(scenarioMap, selectedScenarios); const html = renderToStaticMarkup( , @@ -63,7 +63,7 @@ function updateUi({ updateScenariosAndContext(res, updatedScenarios); - const { groups, other } = getScenarios(scenarioMap, updatedScenarios); + const { groups, other } = getAllScenarios(scenarioMap, updatedScenarios); const html = renderToStaticMarkup( Date: Sun, 6 Feb 2022 11:06:46 +0000 Subject: [PATCH 04/14] refactor: refactor logic for adding / to css href --- src/Html.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Html.tsx b/src/Html.tsx index 0464e54..026573b 100644 --- a/src/Html.tsx +++ b/src/Html.tsx @@ -13,10 +13,6 @@ function Html({ other: Array<{ id: string; selected: boolean }>; updatedScenarios?: string[]; }) { - if (uiPath[uiPath.length - 1] !== '/') { - uiPath = uiPath + '/'; - } - return ( @@ -25,7 +21,10 @@ function Html({ {updatedScenarios ? 'Updated - ' : ''}Scenarios - Data Mocks Server - +
From 064aedbda9dcc3b7dcbab4a1a78c0c3638f94643 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Sun, 6 Feb 2022 11:10:18 +0000 Subject: [PATCH 05/14] refactor: change file name "server.ts" to "express.ts" --- src/{server.ts => express.ts} | 0 src/index.ts | 2 +- src/run.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{server.ts => express.ts} (100%) diff --git a/src/server.ts b/src/express.ts similarity index 100% rename from src/server.ts rename to src/express.ts diff --git a/src/index.ts b/src/index.ts index e1dda4d..6add6bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,4 +8,4 @@ export { Scenario, } from './types'; export { run } from './run'; -export { createExpressApp } from './server'; +export { createExpressApp } from './express'; diff --git a/src/run.ts b/src/run.ts index bf1360c..513a184 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,6 +1,6 @@ import { transform } from 'server-with-kill'; -import { createExpressApp } from './server'; +import { createExpressApp } from './express'; import { Options, ScenarioMap, DefaultScenario } from './types'; export { run }; From 8d806ccf1f6edd04ee9e136a3f1c55cedc186ec9 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Sun, 6 Feb 2022 11:47:35 +0000 Subject: [PATCH 06/14] refactor: renamed variable names --- src/apis.ts | 6 ++--- src/cookies.ts | 4 ++-- src/express.ts | 61 +++++++++++++++++++++++++------------------------- src/ui.tsx | 8 +++---- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/apis.ts b/src/apis.ts index 8064447..19940b3 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -64,13 +64,13 @@ function resetScenarios({ function getScenarios({ scenarioMap, - getScenarioNames, + getSelectedScenarioIds, }: { scenarioMap: ScenarioMap; - getScenarioNames: (req: Request, res: Response) => string[]; + getSelectedScenarioIds: (req: Request, res: Response) => string[]; }): RequestHandler { return (req: Request, res: Response) => { - const data = getAllScenarios(scenarioMap, getScenarioNames(req, res)); + const data = getAllScenarios(scenarioMap, getSelectedScenarioIds(req, res)); res.json(data); }; diff --git a/src/cookies.ts b/src/cookies.ts index a80371e..14a62a2 100644 --- a/src/cookies.ts +++ b/src/cookies.ts @@ -2,7 +2,7 @@ import { Response, Request } from 'express'; import { Context, CookieValue } from './types'; export { - getScenariosFromCookie, + getScenarioIdsFromCookie, getContextFromCookie, setContextAndScenariosCookie, }; @@ -48,7 +48,7 @@ function getCookie({ return defaultValue; } -function getScenariosFromCookie({ +function getScenarioIdsFromCookie({ req, res, defaultValue, diff --git a/src/express.ts b/src/express.ts index 0ca2bf3..4f4f5e8 100644 --- a/src/express.ts +++ b/src/express.ts @@ -29,7 +29,7 @@ import { } from './types'; import { getUi, updateUi } from './ui'; import { - getScenariosFromCookie, + getScenarioIdsFromCookie, getContextFromCookie, setContextAndScenariosCookie, } from './cookies'; @@ -45,8 +45,6 @@ function createExpressApp({ scenarios?: ScenarioMap; options?: Options; }) { - let selectedScenarioNames: string[] = []; - let currentContext = getContextFromScenarios([defaultScenario]); const { uiPath = '/', modifyScenariosPath = '/modify-scenarios', @@ -55,8 +53,7 @@ function createExpressApp({ cookieMode = false, } = options; - const app = express(); - const scenarioNames = Object.keys(scenarioMap); + const scenarioIds = Object.keys(scenarioMap); const groupNames = Object.values(scenarioMap).reduce( (result, mock) => { if ( @@ -73,6 +70,10 @@ function createExpressApp({ [], ); + let serverSelectedScenarioIds: string[] = []; + let serverContext = getContextFromScenarios([defaultScenario]); + + const app = express(); app.use(cors({ credentials: true })); app.use(cookieParser()); app.use(uiPath, express.static(path.join(__dirname, 'assets'))); @@ -85,7 +86,7 @@ function createExpressApp({ getUi({ uiPath, scenarioMap, - getScenarioNames, + getSelectedScenarioIds, }), ); @@ -94,7 +95,7 @@ function createExpressApp({ updateUi({ uiPath, groupNames, - scenarioNames, + scenarioNames: scenarioIds, scenarioMap, updateScenariosAndContext, }), @@ -103,7 +104,7 @@ function createExpressApp({ app.put( modifyScenariosPath, modifyScenarios({ - scenarioNames, + scenarioNames: scenarioIds, scenarioMap, updateScenariosAndContext, }), @@ -120,13 +121,13 @@ function createExpressApp({ scenariosPath, apiGetScenarios({ scenarioMap, - getScenarioNames, + getSelectedScenarioIds, }), ); app.use( createRequestHandler({ - getScenarioNames, + getSelectedScenarioIds, defaultScenario, scenarioMap, getContext: ( @@ -139,22 +140,22 @@ function createExpressApp({ req, res, defaultValue: { - scenarios: getScenarioNames(req, res), + scenarios: getSelectedScenarioIds(req, res), context: getContextFromScenarios(selectedScenarios), }, }); } - return currentContext; + return serverContext; }, setContext: (req: Request, res: Response, context: Context) => { if (cookieMode) { setContextAndScenariosCookie(res, { - scenarios: getScenarioNames(req, res), + scenarios: getSelectedScenarioIds(req, res), context, }); } else { - currentContext = context; + serverContext = context; } }, }), @@ -169,7 +170,7 @@ function createExpressApp({ const updatedScenarios = getScenarios({ defaultScenario, scenarioMap, - scenarioNames: updatedScenarioNames, + scenarioIds: updatedScenarioNames, }); const context = getContextFromScenarios(updatedScenarios); @@ -182,17 +183,17 @@ function createExpressApp({ return; } - currentContext = context; - selectedScenarioNames = updatedScenarioNames; + serverContext = context; + serverSelectedScenarioIds = updatedScenarioNames; return updatedScenarioNames; } - function getScenarioNames(req: Request, res: Response) { + function getSelectedScenarioIds(req: Request, res: Response) { if (cookieMode) { - const defaultScenarios: string[] = []; + const defaultScenarioIds: string[] = []; - return getScenariosFromCookie({ + return getScenarioIdsFromCookie({ req, res, defaultValue: { @@ -200,15 +201,15 @@ function createExpressApp({ getScenarios({ defaultScenario, scenarioMap, - scenarioNames: defaultScenarios, + scenarioIds: defaultScenarioIds, }), ), - scenarios: defaultScenarios, + scenarios: defaultScenarioIds, }, }); } - return selectedScenarioNames; + return serverSelectedScenarioIds; } } @@ -244,14 +245,14 @@ function getMocksFromScenarios(scenarios: Scenario[]) { function getScenarios({ defaultScenario, scenarioMap, - scenarioNames, + scenarioIds, }: { defaultScenario: DefaultScenario; scenarioMap: ScenarioMap; - scenarioNames: string[]; + scenarioIds: string[]; }): Scenario[] { return [defaultScenario].concat( - scenarioNames.map(scenario => scenarioMap[scenario]), + scenarioIds.map(scenarioId => scenarioMap[scenarioId]), ); } @@ -267,13 +268,13 @@ function getContextFromScenarios(scenarios: Scenario[]) { } function createRequestHandler({ - getScenarioNames, + getSelectedScenarioIds, defaultScenario, scenarioMap, getContext, setContext, }: { - getScenarioNames: (req: Request, res: Response) => string[]; + getSelectedScenarioIds: (req: Request, res: Response) => string[]; defaultScenario: DefaultScenario; scenarioMap: ScenarioMap; getContext: ( @@ -284,11 +285,11 @@ function createRequestHandler({ setContext: (req: Request, res: Response, context: Context) => void; }) { return (req: Request, res: Response, next: NextFunction) => { - const scenarioNames = getScenarioNames(req, res); + const selectedScenarioIds = getSelectedScenarioIds(req, res); const selectedScenarios = getScenarios({ defaultScenario, scenarioMap, - scenarioNames, + scenarioIds: selectedScenarioIds, }); const { httpMocks, graphQlMocks } = getMocksFromScenarios( diff --git a/src/ui.tsx b/src/ui.tsx index c8164e4..0e3ab21 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -11,15 +11,15 @@ export { getUi, updateUi }; function getUi({ uiPath, scenarioMap, - getScenarioNames, + getSelectedScenarioIds, }: { uiPath: string; scenarioMap: ScenarioMap; - getScenarioNames: (req: Request, res: Response) => string[]; + getSelectedScenarioIds: (req: Request, res: Response) => string[]; }): RequestHandler { return (req: Request, res: Response) => { - const selectedScenarios = getScenarioNames(req, res); - const { groups, other } = getAllScenarios(scenarioMap, selectedScenarios); + const selectedScenarioIds = getSelectedScenarioIds(req, res); + const { groups, other } = getAllScenarios(scenarioMap, selectedScenarioIds); const html = renderToStaticMarkup( , From 81ed0f1b99ab90f7b56b5a2357e9aacf5cd6e4af Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Tue, 8 Feb 2022 23:43:01 +0000 Subject: [PATCH 07/14] refactor: removed express req, res and next request specific terms from the main business logic --- example/index.ts | 1 + src/cookies.ts | 75 ++++--- src/create-handler.ts | 75 ++++--- src/express.ts | 259 +++++++++++++----------- src/graph-ql.ts | 118 ++++++----- src/http.ts | 18 +- src/types.ts | 11 + src/utils/get-context-from-scenarios.ts | 14 ++ tsconfig.json | 2 +- 9 files changed, 338 insertions(+), 235 deletions(-) create mode 100644 src/utils/get-context-from-scenarios.ts diff --git a/example/index.ts b/example/index.ts index 34286ff..33c445f 100644 --- a/example/index.ts +++ b/example/index.ts @@ -159,5 +159,6 @@ run({ uiPath: '/scenarios-ui', modifyScenariosPath: '/modify', resetScenariosPath: '/reset', + cookieMode: true, }, }); diff --git a/src/cookies.ts b/src/cookies.ts index 14a62a2..ee25ef9 100644 --- a/src/cookies.ts +++ b/src/cookies.ts @@ -1,15 +1,16 @@ import { Response, Request } from 'express'; -import { Context, CookieValue } from './types'; +import { CookieValue, DefaultScenario } from './types'; +import { getContextFromScenarios } from './utils/get-context-from-scenarios'; export { getScenarioIdsFromCookie, - getContextFromCookie, - setContextAndScenariosCookie, + getDataMocksServerCookie, + setDataMocksServerCookie, }; const CONTEXT_AND_SCENARIOS_COOKIE_NAME = 'data-mocks-server'; -function setCookie({ +function expressSetCookie({ res, name, value, @@ -23,7 +24,7 @@ function setCookie({ }); } -function getCookie({ +function expressGetCookie({ req, res, name, @@ -41,7 +42,7 @@ function getCookie({ return value; } catch (error) { // Cookie value was malformed, so needs resetting - setCookie({ res, name, value: defaultValue }); + expressSetCookie({ res, name, value: defaultValue }); } } @@ -57,7 +58,7 @@ function getScenarioIdsFromCookie({ res: Response; defaultValue: CookieValue; }) { - return getCookie({ + return expressGetCookie({ req, res, name: CONTEXT_AND_SCENARIOS_COOKIE_NAME, @@ -65,30 +66,44 @@ function getScenarioIdsFromCookie({ }).scenarios; } -function getContextFromCookie({ - req, - res, - defaultValue, +function getDataMocksServerCookie({ + getCookie, + defaultScenario, }: { - req: Request; - res: Response; - defaultValue: CookieValue; -}) { - return getCookie({ - req, - res, - name: CONTEXT_AND_SCENARIOS_COOKIE_NAME, - defaultValue, - }).context; + getCookie: (cookieName: string) => any; + defaultScenario: DefaultScenario; +}): CookieValue { + const cookie = getCookie(CONTEXT_AND_SCENARIOS_COOKIE_NAME); + + if (cookie) { + try { + const parsedCookie = JSON.parse(cookie); + + // Check that the parsed cookie matches the shape expected + if (parsedCookie.context && Array.isArray(parsedCookie.scenarios)) { + return parsedCookie; + } else { + console.error('Cookie value does not match expected shape'); + } + } catch (error) { + console.error('Cookie value could not be parsed'); + } + } + + const defaultValue = { + scenarios: [], + context: getContextFromScenarios([defaultScenario]), + }; + + return defaultValue; } -function setContextAndScenariosCookie( - res: Response, - contextAndScenarios: { context: Context; scenarios: string[] }, -) { - setCookie({ - res, - name: CONTEXT_AND_SCENARIOS_COOKIE_NAME, - value: contextAndScenarios, - }); +function setDataMocksServerCookie({ + setCookie, + value, +}: { + setCookie: (cookieName: string, cookieValue: string) => void; + value: CookieValue; +}) { + setCookie(CONTEXT_AND_SCENARIOS_COOKIE_NAME, JSON.stringify(value)); } diff --git a/src/create-handler.ts b/src/create-handler.ts index 6c6605d..8ba3427 100644 --- a/src/create-handler.ts +++ b/src/create-handler.ts @@ -1,5 +1,3 @@ -import { Response } from 'express'; - import { ResponseProps, MockResponse, @@ -22,35 +20,30 @@ function createHandler({ updateContext: UpdateContext; getContext: GetContext; }) { - return async (req: TInput, res: Response) => { - const actualResponse = - typeof response === 'function' - ? await ((response as unknown) as ResponseFunction)({ - ...req, - updateContext, - context: getContext(), - }) - : response; + return async (req: TInput) => { + const actualResponse = isResponseFunction(response) + ? await response({ + ...req, + updateContext, + context: getContext(), + }) + : response; let responseCollection: { response?: any; responseDelay: number; - responseHeaders?: Record; + responseHeaders: Record; responseCode: number; } = { responseDelay, - responseHeaders, + responseHeaders: lowerCaseKeys(responseHeaders || {}), responseCode, }; - if ( - actualResponse !== null && - typeof actualResponse === 'object' && - (actualResponse as Override).__override && - Object.keys(actualResponse).length === 1 - ) { + + if (isOverride(actualResponse)) { responseCollection = { ...responseCollection, - ...(actualResponse as Override).__override, + ...actualResponse.__override, }; } else { responseCollection.response = actualResponse; @@ -58,31 +51,55 @@ function createHandler({ await addDelay(responseCollection.responseDelay); + // Default repsonses to JSON when there's no content-type header if ( responseCollection.response !== undefined && - (!responseCollection.responseHeaders || - !responseCollection.responseHeaders['Content-Type']) + !responseCollection.responseHeaders['content-type'] ) { responseCollection.responseHeaders = { ...responseCollection.responseHeaders, - 'Content-Type': 'application/json', + 'content-type': 'application/json', }; } if ( - responseCollection.responseHeaders && - responseCollection.responseHeaders['Content-Type'] === 'application/json' + responseCollection.responseHeaders['content-type'] === 'application/json' ) { + // TODO: This is express specific so shouldn't live here responseCollection.response = JSON.stringify(responseCollection.response); } - res - .set(responseCollection.responseHeaders) - .status(responseCollection.responseCode) - .send(responseCollection.response); + return { + status: responseCollection.responseCode, + response: responseCollection.response, + headers: responseCollection.responseHeaders, + }; }; } function addDelay(responseDelay: number) { return new Promise(res => setTimeout(res, responseDelay)); } + +function isOverride( + response: TResponse | Override | undefined, +): response is Override { + return ( + response !== null && + typeof response === 'object' && + (response as Override).__override && + Object.keys(response).length === 1 + ); +} + +function isResponseFunction( + response: MockResponse | undefined, +): response is ResponseFunction { + return typeof response === 'function'; +} + +function lowerCaseKeys(obj: Record) { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]), + ); +} diff --git a/src/express.ts b/src/express.ts index 4f4f5e8..bcdf015 100644 --- a/src/express.ts +++ b/src/express.ts @@ -1,6 +1,6 @@ import cookieParser from 'cookie-parser'; import cors from 'cors'; -import express, { Request, Response, NextFunction } from 'express'; +import express, { Request, Response } from 'express'; import path from 'path'; import { @@ -26,13 +26,16 @@ import { Context, Scenario, PartialContext, + CookieValue, + InternalRequest, } from './types'; import { getUi, updateUi } from './ui'; import { getScenarioIdsFromCookie, - getContextFromCookie, - setContextAndScenariosCookie, + getDataMocksServerCookie, + setDataMocksServerCookie, } from './cookies'; +import { getContextFromScenarios } from './utils/get-context-from-scenarios'; export { createExpressApp }; @@ -125,44 +128,84 @@ function createExpressApp({ }), ); - app.use( - createRequestHandler({ - getSelectedScenarioIds, + app.use(async (req, res, next) => { + const dataMocksServerCookie = getDataMocksServerCookie({ + getCookie: expressGetCookie(req), + defaultScenario, + }); + + const internalRequest: InternalRequest = { + body: req.body, + headers: cleanExpressHeaders(req.headers || {}), + method: req.method, + path: req.path, + query: req.query || {}, + }; + + const result = await handleRequest({ + req: internalRequest, + getSelectedScenarioIds: getSelectedScenarioIds2( + () => dataMocksServerCookie, + ), defaultScenario, scenarioMap, - getContext: ( - req: Request, - res: Response, - selectedScenarios: Scenario[], - ) => { - if (cookieMode) { - return getContextFromCookie({ - req, - res, - defaultValue: { - scenarios: getSelectedScenarioIds(req, res), - context: getContextFromScenarios(selectedScenarios), - }, - }); - } - - return serverContext; - }, - setContext: (req: Request, res: Response, context: Context) => { - if (cookieMode) { - setContextAndScenariosCookie(res, { - scenarios: getSelectedScenarioIds(req, res), - context, - }); - } else { - serverContext = context; - } - }, - }), - ); + getContext: getContext(() => dataMocksServerCookie), + setContext: setContext(context => { + dataMocksServerCookie.context = context; + }), + }); + + if (cookieMode) { + setDataMocksServerCookie({ + setCookie: expressSetCookie(res), + value: dataMocksServerCookie, + }); + } + + if (result.status === 404) { + next(); + + return; + } + + res + .set(result.headers) + .status(result.status) + .send(result.response); + }); return app; + function setContext(setCookieContext: (context: Context) => void) { + return (context: Context) => { + if (cookieMode) { + setCookieContext(context); + } else { + serverContext = context; + } + }; + } + + function getSelectedScenarioIds2(getCookieValue: () => CookieValue) { + return () => { + if (cookieMode) { + return getCookieValue().scenarios; + } + + return serverSelectedScenarioIds; + }; + } + + function getContext(getCookieValue: () => CookieValue) { + return () => { + if (cookieMode) { + return getCookieValue().context; + } + + return serverContext; + }; + } + function updateScenariosAndContext( res: Response, updatedScenarioNames: string[], @@ -175,9 +218,12 @@ function createExpressApp({ const context = getContextFromScenarios(updatedScenarios); if (cookieMode) { - setContextAndScenariosCookie(res, { - context, - scenarios: updatedScenarioNames, + setDataMocksServerCookie({ + setCookie: expressSetCookie(res), + value: { + context, + scenarios: updatedScenarioNames, + }, }); return; @@ -191,20 +237,12 @@ function createExpressApp({ function getSelectedScenarioIds(req: Request, res: Response) { if (cookieMode) { - const defaultScenarioIds: string[] = []; - return getScenarioIdsFromCookie({ req, res, defaultValue: { - context: getContextFromScenarios( - getScenarios({ - defaultScenario, - scenarioMap, - scenarioIds: defaultScenarioIds, - }), - ), - scenarios: defaultScenarioIds, + context: getContextFromScenarios([defaultScenario]), + scenarios: [], }, }); } @@ -214,7 +252,7 @@ function createExpressApp({ } function updateContext(context: Context, partialContext: PartialContext) { - const newContext = { + const newContext: Context = { ...context, ...(typeof partialContext === 'function' ? partialContext(context) @@ -256,91 +294,82 @@ function getScenarios({ ); } -function getContextFromScenarios(scenarios: Scenario[]) { - let context: Context = {}; - scenarios.forEach(mock => { - if (!Array.isArray(mock) && mock.context) { - context = { ...context, ...mock.context }; - } - }); - - return context; -} - -function createRequestHandler({ +async function handleRequest({ + req, getSelectedScenarioIds, defaultScenario, scenarioMap, getContext, setContext, }: { - getSelectedScenarioIds: (req: Request, res: Response) => string[]; + req: InternalRequest; + getSelectedScenarioIds: () => string[]; defaultScenario: DefaultScenario; scenarioMap: ScenarioMap; - getContext: ( - req: Request, - res: Response, - selectedScenarios: Scenario[], - ) => Context; - setContext: (req: Request, res: Response, context: Context) => void; + getContext: () => Context; + setContext: (context: Context) => void; }) { - return (req: Request, res: Response, next: NextFunction) => { - const selectedScenarioIds = getSelectedScenarioIds(req, res); - const selectedScenarios = getScenarios({ - defaultScenario, - scenarioMap, - scenarioIds: selectedScenarioIds, - }); + const selectedScenarioIds = getSelectedScenarioIds(); + const selectedScenarios = getScenarios({ + defaultScenario, + scenarioMap, + scenarioIds: selectedScenarioIds, + }); - const { httpMocks, graphQlMocks } = getMocksFromScenarios( - selectedScenarios, - ); - let context: Context = getContext(req, res, selectedScenarios); + const { httpMocks, graphQlMocks } = getMocksFromScenarios(selectedScenarios); - const graphQlMock = getGraphQlMock(req, graphQlMocks); + const graphQlMock = getGraphQlMock(req.path, graphQlMocks); - if (graphQlMock) { - const requestHandler = createGraphQlRequestHandler({ - graphQlMock, - updateContext: localUpdateContext, - getContext: localGetContext, - }); - - requestHandler(req, res, next); + if (graphQlMock) { + const requestHandler = createGraphQlRequestHandler({ + graphQlMock, + updateContext: localUpdateContext, + getContext, + }); - return; - } + return requestHandler(req); + } - const { httpMock, params } = getHttpMockAndParams(req, httpMocks); - if (httpMock) { - const requestHandler = createHttpRequestHandler({ - httpMock, - params, - getContext: localGetContext, - updateContext: localUpdateContext, - }); + const { httpMock, params } = getHttpMockAndParams(req, httpMocks); + if (httpMock) { + const requestHandler = createHttpRequestHandler({ + httpMock, + params, + getContext, + updateContext: localUpdateContext, + }); - requestHandler(req, res); + return requestHandler(req); + } - return; - } + return { status: 404 }; - // Nothing matched - default 404 from express - next(); + function localUpdateContext(partialContext: PartialContext) { + const newContext = updateContext(getContext(), partialContext); - function localUpdateContext(partialContext: PartialContext) { - // Although "setContext" below will ensure the context is set correctly - // for the server/cookie, if response functions call "updateContext" multiple - // times, the local version of "getContext" will return the wrong value - context = updateContext(context, partialContext); + setContext(newContext); - setContext(req, res, context); + return newContext; + } +} - return context; - } +function expressGetCookie(req: Request) { + return (cookieName: string) => req.cookies[cookieName]; +} - function localGetContext() { - return context; - } +function expressSetCookie(res: Response) { + return (cookieName: string, cookieValue: string) => { + res.cookie(cookieName, cookieValue, { encode: String }); }; } + +function cleanExpressHeaders( + headers: Request['headers'], +): Record { + return Object.fromEntries( + Object.entries(headers).filter( + (keyValuePair): keyValuePair is [string, string] => + typeof keyValuePair[1] === 'string', + ), + ); +} diff --git a/src/graph-ql.ts b/src/graph-ql.ts index 3d39b9b..fa41a89 100644 --- a/src/graph-ql.ts +++ b/src/graph-ql.ts @@ -1,4 +1,3 @@ -import { Request, Response, NextFunction } from 'express'; import gql from 'graphql-tag'; import { createHandler } from './create-handler'; @@ -8,18 +7,22 @@ import { Mock, UpdateContext, GetContext, + InternalRequest, } from './types'; export { getGraphQlMocks, getGraphQlMock, createGraphQlRequestHandler }; -type GraphQlHandler = ( - req: { - operationType: 'query' | 'mutation'; - operationName: string; - variables: Record; - }, - res: Response, -) => boolean; +type Result = { + status: number; + headers?: Record; + response?: any; +}; + +type GraphQlHandler = (req: { + operationType: 'query' | 'mutation'; + operationName: string; + variables: Record; +}) => Promise; function getGraphQlMocks(mocks: Mock[]) { const initialGraphQlMocks = mocks.filter( @@ -63,27 +66,24 @@ function createGraphQlHandler({ }): GraphQlHandler { const handler = createHandler(rest); - return ({ operationType, operationName, variables }, res) => { + return async ({ operationType, operationName, variables }) => { if ( operationType === operationTypeToCheck && operationName === operationNameToCheck ) { - handler( - { - variables, - }, - res, - ); + const result = await handler({ + variables, + }); - return true; + return result; } - return false; + return null; }; } function createInternalGraphQlRequestHandler(handlers: GraphQlHandler[]) { - return (req: Request, res: Response, next: NextFunction) => { + return async (req: InternalRequest) => { const query = req.headers['content-type'] === 'application/graphql' ? req.body @@ -93,10 +93,15 @@ function createInternalGraphQlRequestHandler(handlers: GraphQlHandler[]) { try { graphqlAst = gql(query); } catch (error) { - res.status(400).json({ - message: `query "${query}" is not a valid GraphQL query`, - }); - return; + const result = { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + response: { message: `query "${query}" is not a valid GraphQL query` }, + }; + + return result; } const operationTypesAndNames = (graphqlAst.definitions as Array<{ @@ -115,10 +120,17 @@ function createInternalGraphQlRequestHandler(handlers: GraphQlHandler[]) { !req.body.operationName && !req.query.operationName ) { - res.status(400).json({ - message: `query "${query}" is not a valid GraphQL query`, - }); - return; + const result = { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + response: { + message: `query "${query}" is not a valid GraphQL query`, + }, + }; + + return result; } const operationName: string = @@ -132,10 +144,17 @@ function createInternalGraphQlRequestHandler(handlers: GraphQlHandler[]) { ); if (!operationTypeAndName) { - res.status(400).json({ - message: `operation name "${operationName}" does not exist in GraphQL query`, - }); - return; + const result = { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + response: { + message: `operation name "${operationName}" does not exist in GraphQL query`, + }, + }; + + return result; } const operationType = operationTypeAndName.type; @@ -149,26 +168,23 @@ function createInternalGraphQlRequestHandler(handlers: GraphQlHandler[]) { variables = variables || {}; for (const handler of handlers) { - const responseHandled = handler( - { - operationType, - operationName, - variables, - }, - res, - ); + const result = await handler({ + operationType, + operationName, + variables, + }); - if (responseHandled) { - return; + if (result) { + return result; } } - next(); + return { status: 404 }; }; } -function getGraphQlMock(req: Request, graphqlMocks: GraphQlMock[]) { - return graphqlMocks.find(graphQlMock => graphQlMock.url === req.path) || null; +function getGraphQlMock(path: string, graphqlMocks: GraphQlMock[]) { + return graphqlMocks.find(graphQlMock => graphQlMock.url === path) || null; } function getQueries({ @@ -219,8 +235,8 @@ function createGraphQlRequestHandler({ graphQlMock: GraphQlMock; updateContext: UpdateContext; getContext: GetContext; -}) { - return (req: Request, res: Response, next: NextFunction) => { +}): (req: InternalRequest) => Promise { + return req => { if (req.method === 'GET') { const queries = getQueries({ graphQlMock, @@ -229,9 +245,8 @@ function createGraphQlRequestHandler({ }); const requestHandler = createInternalGraphQlRequestHandler(queries); - requestHandler(req, res, next); - return; + return requestHandler(req); } if (req.method === 'POST') { @@ -245,15 +260,14 @@ function createGraphQlRequestHandler({ updateContext, getContext, }); + const requestHandler = createInternalGraphQlRequestHandler( queries.concat(mutations), ); - requestHandler(req, res, next); - return; + return requestHandler(req); } - // req.method doesn't make sense for GraphQL - default 404 from express - next(); + return Promise.resolve({ status: 404 }); }; } diff --git a/src/http.ts b/src/http.ts index a39ea9f..09f2195 100644 --- a/src/http.ts +++ b/src/http.ts @@ -1,8 +1,13 @@ import { pathToRegexp, Key } from 'path-to-regexp'; -import { Request, Response } from 'express'; import { createHandler } from './create-handler'; -import { Mock, HttpMock, UpdateContext, GetContext } from './types'; +import { + Mock, + HttpMock, + UpdateContext, + GetContext, + InternalRequest, +} from './types'; export { getHttpMocks, getHttpMockAndParams, createHttpRequestHandler }; @@ -35,21 +40,18 @@ function createHttpRequestHandler({ updateContext: UpdateContext; getContext: GetContext; }) { - return (req: Request, res: Response) => { - // Matching all routes so need to create params manually - req.params = params; - + return (req: InternalRequest) => { const handler = createHandler({ ...httpMock, getContext, updateContext, }); - handler(req, res); + return handler({ ...req, params }); }; } -function getHttpMockAndParams(req: Request, httpMocks: HttpMock[]) { +function getHttpMockAndParams(req: InternalRequest, httpMocks: HttpMock[]) { for (const httpMock of httpMocks) { if (httpMock.method !== req.method) { continue; diff --git a/src/types.ts b/src/types.ts index 2f0f470..1e53236 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { IncomingHttpHeaders } from 'http'; + export type DefaultScenario = | Mock[] | { @@ -110,3 +112,12 @@ export type CookieValue = { context: Context; scenarios: string[]; }; + +export type InternalRequest = { + method: string; + headers: Record; + query: Record; + path: string; + // TODO: Should probably only accept string or object + body: any; +}; diff --git a/src/utils/get-context-from-scenarios.ts b/src/utils/get-context-from-scenarios.ts new file mode 100644 index 0000000..80d4100 --- /dev/null +++ b/src/utils/get-context-from-scenarios.ts @@ -0,0 +1,14 @@ +import { Context, Scenario } from '../types'; + +export { getContextFromScenarios }; + +function getContextFromScenarios(scenarios: Scenario[]) { + let context: Context = {}; + scenarios.forEach(mock => { + if (!Array.isArray(mock) && mock.context) { + context = { ...context, ...mock.context }; + } + }); + + return context; +} diff --git a/tsconfig.json b/tsconfig.json index 7e6def1..98df841 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ // Allow default imports from modules with no default export. "allowSyntheticDefaultImports": true, // Target latest version of ECMAScript. - "target": "ES6", + "target": "ES2019", // Search under node_modules for non-relative imports. "moduleResolution": "node", // Enable strictest settings like strictNullChecks & noImplicitAny. From 728f9b26287ae48f67323f521b6ee1cd473298e7 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Wed, 9 Feb 2022 00:31:52 +0000 Subject: [PATCH 08/14] refactor: move cookie handling into main request handler --- src/express.ts | 132 ++++++++++++++++++++++-------------------------- src/graph-ql.ts | 7 +-- src/types.ts | 6 ++- 3 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/express.ts b/src/express.ts index bcdf015..ae81297 100644 --- a/src/express.ts +++ b/src/express.ts @@ -26,8 +26,8 @@ import { Context, Scenario, PartialContext, - CookieValue, InternalRequest, + Result, } from './types'; import { getUi, updateUi } from './ui'; import { @@ -129,11 +129,6 @@ function createExpressApp({ ); app.use(async (req, res, next) => { - const dataMocksServerCookie = getDataMocksServerCookie({ - getCookie: expressGetCookie(req), - defaultScenario, - }); - const internalRequest: InternalRequest = { body: req.body, headers: cleanExpressHeaders(req.headers || {}), @@ -144,24 +139,18 @@ function createExpressApp({ const result = await handleRequest({ req: internalRequest, - getSelectedScenarioIds: getSelectedScenarioIds2( - () => dataMocksServerCookie, - ), + getServerSelectedScenarioIds: () => serverSelectedScenarioIds, defaultScenario, scenarioMap, - getContext: getContext(() => dataMocksServerCookie), - setContext: setContext(context => { - dataMocksServerCookie.context = context; - }), + getServerContext: () => serverContext, + setServerContext: (context: Context) => { + serverContext = context; + }, + cookieMode, + getCookie: (cookieName: string) => req.cookies[cookieName], + setCookie: expressSetCookie(res), }); - if (cookieMode) { - setDataMocksServerCookie({ - setCookie: expressSetCookie(res), - value: dataMocksServerCookie, - }); - } - if (result.status === 404) { next(); @@ -176,36 +165,6 @@ function createExpressApp({ return app; - function setContext(setCookieContext: (context: Context) => void) { - return (context: Context) => { - if (cookieMode) { - setCookieContext(context); - } else { - serverContext = context; - } - }; - } - - function getSelectedScenarioIds2(getCookieValue: () => CookieValue) { - return () => { - if (cookieMode) { - return getCookieValue().scenarios; - } - - return serverSelectedScenarioIds; - }; - } - - function getContext(getCookieValue: () => CookieValue) { - return () => { - if (cookieMode) { - return getCookieValue().context; - } - - return serverContext; - }; - } - function updateScenariosAndContext( res: Response, updatedScenarioNames: string[], @@ -296,20 +255,46 @@ function getScenarios({ async function handleRequest({ req, - getSelectedScenarioIds, + getServerSelectedScenarioIds, defaultScenario, scenarioMap, - getContext, - setContext, + getServerContext, + setServerContext, + getCookie, + cookieMode, + setCookie, }: { req: InternalRequest; - getSelectedScenarioIds: () => string[]; + getServerSelectedScenarioIds: () => string[]; defaultScenario: DefaultScenario; scenarioMap: ScenarioMap; - getContext: () => Context; - setContext: (context: Context) => void; + getServerContext: () => Context; + setServerContext: (context: Context) => void; + getCookie: (cookieName: string) => string; + setCookie: (cookieName: string, cookieValue: string) => void; + cookieMode: boolean; }) { + const dataMocksServerCookie = getDataMocksServerCookie({ + getCookie, + defaultScenario, + }); + + const getSelectedScenarioIds = cookieMode + ? () => dataMocksServerCookie.scenarios + : getServerSelectedScenarioIds; + + const getContext = cookieMode + ? () => dataMocksServerCookie.context + : getServerContext; + + const setContext = cookieMode + ? (context: Context) => { + dataMocksServerCookie.context = context; + } + : setServerContext; + const selectedScenarioIds = getSelectedScenarioIds(); + const selectedScenarios = getScenarios({ defaultScenario, scenarioMap, @@ -320,6 +305,9 @@ async function handleRequest({ const graphQlMock = getGraphQlMock(req.path, graphQlMocks); + // Default when nothing matches + let result: Result = { status: 404 }; + if (graphQlMock) { const requestHandler = createGraphQlRequestHandler({ graphQlMock, @@ -327,22 +315,26 @@ async function handleRequest({ getContext, }); - return requestHandler(req); - } + result = await requestHandler(req); + } else { + const { httpMock, params } = getHttpMockAndParams(req, httpMocks); + if (httpMock) { + const requestHandler = createHttpRequestHandler({ + httpMock, + params, + getContext, + updateContext: localUpdateContext, + }); - const { httpMock, params } = getHttpMockAndParams(req, httpMocks); - if (httpMock) { - const requestHandler = createHttpRequestHandler({ - httpMock, - params, - getContext, - updateContext: localUpdateContext, - }); + result = await requestHandler(req); + } + } - return requestHandler(req); + if (cookieMode) { + setDataMocksServerCookie({ setCookie, value: dataMocksServerCookie }); } - return { status: 404 }; + return result; function localUpdateContext(partialContext: PartialContext) { const newContext = updateContext(getContext(), partialContext); @@ -353,10 +345,6 @@ async function handleRequest({ } } -function expressGetCookie(req: Request) { - return (cookieName: string) => req.cookies[cookieName]; -} - function expressSetCookie(res: Response) { return (cookieName: string, cookieValue: string) => { res.cookie(cookieName, cookieValue, { encode: String }); diff --git a/src/graph-ql.ts b/src/graph-ql.ts index fa41a89..e22df18 100644 --- a/src/graph-ql.ts +++ b/src/graph-ql.ts @@ -8,16 +8,11 @@ import { UpdateContext, GetContext, InternalRequest, + Result, } from './types'; export { getGraphQlMocks, getGraphQlMock, createGraphQlRequestHandler }; -type Result = { - status: number; - headers?: Record; - response?: any; -}; - type GraphQlHandler = (req: { operationType: 'query' | 'mutation'; operationName: string; diff --git a/src/types.ts b/src/types.ts index 1e53236..cb2afab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,8 @@ -import { IncomingHttpHeaders } from 'http'; +export type Result = { + status: number; + headers?: Record; + response?: any; +}; export type DefaultScenario = | Mock[] From 4797bdd11993ddd1eebbd241bab8ab76caa862d1 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Fri, 11 Feb 2022 10:48:52 +0000 Subject: [PATCH 09/14] refactor: function name change --- src/express.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/express.ts b/src/express.ts index ae81297..62e9f98 100644 --- a/src/express.ts +++ b/src/express.ts @@ -131,7 +131,7 @@ function createExpressApp({ app.use(async (req, res, next) => { const internalRequest: InternalRequest = { body: req.body, - headers: cleanExpressHeaders(req.headers || {}), + headers: expressCleanHeaders(req.headers || {}), method: req.method, path: req.path, query: req.query || {}, @@ -351,7 +351,7 @@ function expressSetCookie(res: Response) { }; } -function cleanExpressHeaders( +function expressCleanHeaders( headers: Request['headers'], ): Record { return Object.fromEntries( From 9a79a8421e6eea2e3a5930c9685cdfb541f14b67 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Thu, 2 Feb 2023 18:32:31 +0000 Subject: [PATCH 10/14] refactor: move the generic request handler to separate file --- src/express.ts | 141 +------------------------------------ src/handle-request.ts | 158 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 139 deletions(-) create mode 100644 src/handle-request.ts diff --git a/src/express.ts b/src/express.ts index 62e9f98..43de76b 100644 --- a/src/express.ts +++ b/src/express.ts @@ -9,33 +9,17 @@ import { getScenarios as apiGetScenarios, } from './apis'; import { - getGraphQlMocks, - getGraphQlMock, - createGraphQlRequestHandler, -} from './graph-ql'; -import { - getHttpMocks, - getHttpMockAndParams, - createHttpRequestHandler, -} from './http'; -import { - Mock, Options, ScenarioMap, DefaultScenario, Context, Scenario, - PartialContext, InternalRequest, - Result, } from './types'; import { getUi, updateUi } from './ui'; -import { - getScenarioIdsFromCookie, - getDataMocksServerCookie, - setDataMocksServerCookie, -} from './cookies'; +import { getScenarioIdsFromCookie, setDataMocksServerCookie } from './cookies'; import { getContextFromScenarios } from './utils/get-context-from-scenarios'; +import { handleRequest } from './handle-request'; export { createExpressApp }; @@ -210,35 +194,6 @@ function createExpressApp({ } } -function updateContext(context: Context, partialContext: PartialContext) { - const newContext: Context = { - ...context, - ...(typeof partialContext === 'function' - ? partialContext(context) - : partialContext), - }; - - return newContext; -} - -function mergeMocks(scenarioMap: ({ mocks: Mock[] } | Mock[])[]) { - return scenarioMap.reduce( - (result, scenarioMock) => - result.concat( - Array.isArray(scenarioMock) ? scenarioMock : scenarioMock.mocks, - ), - [], - ); -} - -function getMocksFromScenarios(scenarios: Scenario[]) { - const mocks = mergeMocks(scenarios); - const httpMocks = getHttpMocks(mocks); - const graphQlMocks = getGraphQlMocks(mocks); - - return { httpMocks, graphQlMocks }; -} - function getScenarios({ defaultScenario, scenarioMap, @@ -253,98 +208,6 @@ function getScenarios({ ); } -async function handleRequest({ - req, - getServerSelectedScenarioIds, - defaultScenario, - scenarioMap, - getServerContext, - setServerContext, - getCookie, - cookieMode, - setCookie, -}: { - req: InternalRequest; - getServerSelectedScenarioIds: () => string[]; - defaultScenario: DefaultScenario; - scenarioMap: ScenarioMap; - getServerContext: () => Context; - setServerContext: (context: Context) => void; - getCookie: (cookieName: string) => string; - setCookie: (cookieName: string, cookieValue: string) => void; - cookieMode: boolean; -}) { - const dataMocksServerCookie = getDataMocksServerCookie({ - getCookie, - defaultScenario, - }); - - const getSelectedScenarioIds = cookieMode - ? () => dataMocksServerCookie.scenarios - : getServerSelectedScenarioIds; - - const getContext = cookieMode - ? () => dataMocksServerCookie.context - : getServerContext; - - const setContext = cookieMode - ? (context: Context) => { - dataMocksServerCookie.context = context; - } - : setServerContext; - - const selectedScenarioIds = getSelectedScenarioIds(); - - const selectedScenarios = getScenarios({ - defaultScenario, - scenarioMap, - scenarioIds: selectedScenarioIds, - }); - - const { httpMocks, graphQlMocks } = getMocksFromScenarios(selectedScenarios); - - const graphQlMock = getGraphQlMock(req.path, graphQlMocks); - - // Default when nothing matches - let result: Result = { status: 404 }; - - if (graphQlMock) { - const requestHandler = createGraphQlRequestHandler({ - graphQlMock, - updateContext: localUpdateContext, - getContext, - }); - - result = await requestHandler(req); - } else { - const { httpMock, params } = getHttpMockAndParams(req, httpMocks); - if (httpMock) { - const requestHandler = createHttpRequestHandler({ - httpMock, - params, - getContext, - updateContext: localUpdateContext, - }); - - result = await requestHandler(req); - } - } - - if (cookieMode) { - setDataMocksServerCookie({ setCookie, value: dataMocksServerCookie }); - } - - return result; - - function localUpdateContext(partialContext: PartialContext) { - const newContext = updateContext(getContext(), partialContext); - - setContext(newContext); - - return newContext; - } -} - function expressSetCookie(res: Response) { return (cookieName: string, cookieValue: string) => { res.cookie(cookieName, cookieValue, { encode: String }); diff --git a/src/handle-request.ts b/src/handle-request.ts new file mode 100644 index 0000000..64300e5 --- /dev/null +++ b/src/handle-request.ts @@ -0,0 +1,158 @@ +import { + getGraphQlMocks, + getGraphQlMock, + createGraphQlRequestHandler, +} from './graph-ql'; +import { + getHttpMocks, + getHttpMockAndParams, + createHttpRequestHandler, +} from './http'; +import { + Mock, + ScenarioMap, + DefaultScenario, + Context, + Scenario, + PartialContext, + InternalRequest, + Result, +} from './types'; +import { getDataMocksServerCookie, setDataMocksServerCookie } from './cookies'; + +function updateContext(context: Context, partialContext: PartialContext) { + const newContext: Context = { + ...context, + ...(typeof partialContext === 'function' + ? partialContext(context) + : partialContext), + }; + + return newContext; +} + +function mergeMocks(scenarioMap: ({ mocks: Mock[] } | Mock[])[]) { + return scenarioMap.reduce( + (result, scenarioMock) => + result.concat( + Array.isArray(scenarioMock) ? scenarioMock : scenarioMock.mocks, + ), + [], + ); +} + +function getMocksFromScenarios(scenarios: Scenario[]) { + const mocks = mergeMocks(scenarios); + const httpMocks = getHttpMocks(mocks); + const graphQlMocks = getGraphQlMocks(mocks); + + return { httpMocks, graphQlMocks }; +} + +function getScenarios({ + defaultScenario, + scenarioMap, + scenarioIds, +}: { + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + scenarioIds: string[]; +}): Scenario[] { + return [defaultScenario].concat( + scenarioIds.map(scenarioId => scenarioMap[scenarioId]), + ); +} + +async function handleRequest({ + req, + getServerSelectedScenarioIds, + defaultScenario, + scenarioMap, + getServerContext, + setServerContext, + getCookie, + cookieMode, + setCookie, +}: { + req: InternalRequest; + getServerSelectedScenarioIds: () => string[]; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + getServerContext: () => Context; + setServerContext: (context: Context) => void; + getCookie: (cookieName: string) => string; + setCookie: (cookieName: string, cookieValue: string) => void; + cookieMode: boolean; +}) { + const dataMocksServerCookie = getDataMocksServerCookie({ + getCookie, + defaultScenario, + }); + + const getSelectedScenarioIds = cookieMode + ? () => dataMocksServerCookie.scenarios + : getServerSelectedScenarioIds; + + const getContext = cookieMode + ? () => dataMocksServerCookie.context + : getServerContext; + + const setContext = cookieMode + ? (context: Context) => { + dataMocksServerCookie.context = context; + } + : setServerContext; + + const selectedScenarioIds = getSelectedScenarioIds(); + + const selectedScenarios = getScenarios({ + defaultScenario, + scenarioMap, + scenarioIds: selectedScenarioIds, + }); + + const { httpMocks, graphQlMocks } = getMocksFromScenarios(selectedScenarios); + + const graphQlMock = getGraphQlMock(req.path, graphQlMocks); + + // Default when nothing matches + let result: Result = { status: 404 }; + + if (graphQlMock) { + const requestHandler = createGraphQlRequestHandler({ + graphQlMock, + updateContext: localUpdateContext, + getContext, + }); + + result = await requestHandler(req); + } else { + const { httpMock, params } = getHttpMockAndParams(req, httpMocks); + if (httpMock) { + const requestHandler = createHttpRequestHandler({ + httpMock, + params, + getContext, + updateContext: localUpdateContext, + }); + + result = await requestHandler(req); + } + } + + if (cookieMode) { + setDataMocksServerCookie({ setCookie, value: dataMocksServerCookie }); + } + + return result; + + function localUpdateContext(partialContext: PartialContext) { + const newContext = updateContext(getContext(), partialContext); + + setContext(newContext); + + return newContext; + } +} + +export { handleRequest }; From 52dbb305bb04aae482e4e255cca7371f02b19a67 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Fri, 3 Feb 2023 00:29:12 +0000 Subject: [PATCH 11/14] refactor: refactored out api functions to be agnostic of express --- src/apis.ts | 266 +++++++++++++++++++++++++++++++++--------- src/cookies.ts | 66 +++-------- src/create-handler.ts | 7 -- src/express.ts | 107 +++++++++++------ src/handle-request.ts | 6 +- src/types.ts | 3 + 6 files changed, 308 insertions(+), 147 deletions(-) diff --git a/src/apis.ts b/src/apis.ts index 19940b3..f13d99c 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -1,77 +1,239 @@ -import { RequestHandler, Response, Request } from 'express'; -import { ScenarioMap } from './types'; +import { getScenarioIdsFromCookie, setDataMocksServerCookie } from './cookies'; +import { + Context, + DefaultScenario, + GetCookie, + Result, + Scenario, + ScenarioMap, + SetCookie, +} from './types'; import { getAllScenarios } from './utils/get-all-scenarios'; +import { getContextFromScenarios } from './utils/get-context-from-scenarios'; -export { modifyScenarios, resetScenarios, getScenarios }; +export { resetScenarios, modifyScenarios, getScenarios }; + +function resetScenarios({ + setCookie, + setServerContext, + setServerSelectedScenarioIds, + defaultScenario, + scenarioMap, + cookieMode, +}: { + setCookie: SetCookie; + setServerContext: (context: Context) => void; + setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + cookieMode: boolean; +}): Result { + updateScenariosAndContext({ + updatedScenarioIds: [], + setCookie, + setServerContext, + setServerSelectedScenarioIds, + defaultScenario, + scenarioMap, + cookieMode, + }); + + return { + status: 204, + }; +} + +function updateScenariosAndContext({ + updatedScenarioIds, + setCookie, + setServerContext, + setServerSelectedScenarioIds, + defaultScenario, + scenarioMap, + cookieMode, +}: { + updatedScenarioIds: string[]; + setCookie: SetCookie; + setServerContext: (context: Context) => void; + setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + cookieMode: boolean; +}) { + const updatedScenarios = getScenariosInternal({ + defaultScenario, + scenarioMap, + scenarioIds: updatedScenarioIds, + }); + const context = getContextFromScenarios(updatedScenarios); + + if (cookieMode) { + setDataMocksServerCookie({ + setCookie, + value: { + context, + scenarios: updatedScenarioIds, + }, + }); + + return; + } + + setServerContext(context); + setServerSelectedScenarioIds(updatedScenarioIds); +} + +function getScenarios({ + getCookie, + setCookie, + getServerSelectedScenarioIds, + cookieMode, + defaultScenario, + scenarioMap, +}: { + getCookie: GetCookie; + setCookie: SetCookie; + getServerSelectedScenarioIds: () => string[]; + cookieMode: boolean; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; +}): Result { + const allScenarios = getAllScenarios( + scenarioMap, + getSelectedScenarioIds({ + getCookie, + setCookie, + getServerSelectedScenarioIds, + cookieMode, + defaultScenario, + }), + ); + + return { + status: 200, + headers: { + 'content-type': 'application/json', + }, + response: allScenarios, + }; +} function modifyScenarios({ - scenarioNames, + updatedScenarioIds, + scenarioIds, scenarioMap, - updateScenariosAndContext, + cookieMode, + defaultScenario, + setCookie, + setServerContext, + setServerSelectedScenarioIds, }: { - scenarioNames: string[]; + updatedScenarioIds: unknown; + scenarioIds: string[]; scenarioMap: ScenarioMap; - updateScenariosAndContext: (res: Response, scenarios: string[]) => void; -}): RequestHandler { - return ({ body: { scenarios: scenariosBody } }: Request, res: Response) => { - if (!Array.isArray(scenariosBody)) { - res.status(400).json({ + cookieMode: boolean; + defaultScenario: DefaultScenario; + setCookie: SetCookie; + setServerContext: (context: Context) => void; + setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; +}) { + // TODO: Check what type of individual items are + if (!Array.isArray(updatedScenarioIds)) { + return { + status: 400, + headers: { + 'content-type': 'application/json', + }, + response: { message: '"scenarios" must be an array of scenario names (empty array allowed)', - }); - return; - } + }, + }; + } - const scenariosByGroup: { [key: string]: number } = {}; - for (const scenario of scenariosBody) { - if (!scenarioNames.includes(scenario)) { - res.status(400).json({ + const scenariosByGroup: { [key: string]: number } = {}; + for (const scenario of updatedScenarioIds) { + if (!scenarioIds.includes(scenario)) { + return { + status: 400, + headers: { + 'content-type': 'application/json', + }, + response: { message: `Scenario "${scenario}" does not exist`, - }); - return; - } + }, + }; + } - const scenarioMock = scenarioMap[scenario]; - if (!Array.isArray(scenarioMock) && scenarioMock.group) { - const { group } = scenarioMock; - if (scenariosByGroup[group]) { - res.status(400).json({ + const scenarioMock = scenarioMap[scenario]; + if (!Array.isArray(scenarioMock) && scenarioMock.group) { + const { group } = scenarioMock; + if (scenariosByGroup[group]) { + return { + status: 400, + headers: { + 'content-type': 'application/json', + }, + response: { message: `Scenario "${scenario}" cannot be selected, because scenario "${scenariosByGroup[group]}" from group "${group}" has already been selected`, - }); - return; - } - - scenariosByGroup[group] = scenario; + }, + }; } + + scenariosByGroup[group] = scenario; } + } - updateScenariosAndContext(res, scenariosBody); + updateScenariosAndContext({ + cookieMode, + defaultScenario, + updatedScenarioIds, + scenarioMap, + setCookie, + setServerContext, + setServerSelectedScenarioIds, + }); - res.sendStatus(204); - }; + return { status: 204 }; } -function resetScenarios({ - updateScenariosAndContext, +function getScenariosInternal({ + defaultScenario, + scenarioMap, + scenarioIds, }: { - updateScenariosAndContext: (res: Response, scenarios: string[]) => void; -}): RequestHandler { - return (_, res: Response) => { - updateScenariosAndContext(res, []); - res.sendStatus(204); - }; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + scenarioIds: string[]; +}): Scenario[] { + return [defaultScenario].concat( + scenarioIds.map(scenarioId => scenarioMap[scenarioId]), + ); } -function getScenarios({ - scenarioMap, - getSelectedScenarioIds, +function getSelectedScenarioIds({ + getCookie, + setCookie, + getServerSelectedScenarioIds, + cookieMode, + defaultScenario, }: { - scenarioMap: ScenarioMap; - getSelectedScenarioIds: (req: Request, res: Response) => string[]; -}): RequestHandler { - return (req: Request, res: Response) => { - const data = getAllScenarios(scenarioMap, getSelectedScenarioIds(req, res)); + getCookie: GetCookie; + setCookie: SetCookie; + getServerSelectedScenarioIds: () => string[]; + cookieMode: boolean; + defaultScenario: DefaultScenario; +}) { + if (cookieMode) { + return getScenarioIdsFromCookie({ + getCookie, + setCookie, + defaultValue: { + context: getContextFromScenarios([defaultScenario]), + scenarios: [], + }, + }); + } - res.json(data); - }; + return getServerSelectedScenarioIds(); } diff --git a/src/cookies.ts b/src/cookies.ts index ee25ef9..91b42cd 100644 --- a/src/cookies.ts +++ b/src/cookies.ts @@ -1,76 +1,42 @@ -import { Response, Request } from 'express'; -import { CookieValue, DefaultScenario } from './types'; +import { CookieValue, DefaultScenario, GetCookie, SetCookie } from './types'; import { getContextFromScenarios } from './utils/get-context-from-scenarios'; export { - getScenarioIdsFromCookie, getDataMocksServerCookie, setDataMocksServerCookie, + getScenarioIdsFromCookie, }; const CONTEXT_AND_SCENARIOS_COOKIE_NAME = 'data-mocks-server'; -function expressSetCookie({ - res, - name, - value, -}: { - res: Response; - name: string; - value: CookieValue; -}) { - res.cookie(name, JSON.stringify(value), { - encode: String, - }); -} - -function expressGetCookie({ - req, - res, - name, +function getScenarioIdsFromCookie({ + getCookie, + setCookie, defaultValue, }: { - req: Request; - res: Response; - name: string; + getCookie: GetCookie; + setCookie: SetCookie; defaultValue: CookieValue; -}): CookieValue { - if (req.cookies[name]) { +}) { + let cookieValue = defaultValue; + const cookie = getCookie(CONTEXT_AND_SCENARIOS_COOKIE_NAME); + if (cookie) { try { - const value = JSON.parse(req.cookies[name]); - - return value; + cookieValue = JSON.parse(cookie); } catch (error) { // Cookie value was malformed, so needs resetting - expressSetCookie({ res, name, value: defaultValue }); + setCookie(CONTEXT_AND_SCENARIOS_COOKIE_NAME, JSON.stringify(cookieValue)); } } - return defaultValue; -} - -function getScenarioIdsFromCookie({ - req, - res, - defaultValue, -}: { - req: Request; - res: Response; - defaultValue: CookieValue; -}) { - return expressGetCookie({ - req, - res, - name: CONTEXT_AND_SCENARIOS_COOKIE_NAME, - defaultValue, - }).scenarios; + return cookieValue.scenarios; } function getDataMocksServerCookie({ getCookie, defaultScenario, }: { - getCookie: (cookieName: string) => any; + getCookie: GetCookie; defaultScenario: DefaultScenario; }): CookieValue { const cookie = getCookie(CONTEXT_AND_SCENARIOS_COOKIE_NAME); @@ -102,7 +68,7 @@ function setDataMocksServerCookie({ setCookie, value, }: { - setCookie: (cookieName: string, cookieValue: string) => void; + setCookie: SetCookie; value: CookieValue; }) { setCookie(CONTEXT_AND_SCENARIOS_COOKIE_NAME, JSON.stringify(value)); diff --git a/src/create-handler.ts b/src/create-handler.ts index 8ba3427..8da5e9b 100644 --- a/src/create-handler.ts +++ b/src/create-handler.ts @@ -62,13 +62,6 @@ function createHandler({ }; } - if ( - responseCollection.responseHeaders['content-type'] === 'application/json' - ) { - // TODO: This is express specific so shouldn't live here - responseCollection.response = JSON.stringify(responseCollection.response); - } - return { status: responseCollection.responseCode, response: responseCollection.response, diff --git a/src/express.ts b/src/express.ts index 43de76b..edee3b3 100644 --- a/src/express.ts +++ b/src/express.ts @@ -3,11 +3,6 @@ import cors from 'cors'; import express, { Request, Response } from 'express'; import path from 'path'; -import { - modifyScenarios, - resetScenarios, - getScenarios as apiGetScenarios, -} from './apis'; import { Options, ScenarioMap, @@ -15,11 +10,17 @@ import { Context, Scenario, InternalRequest, + Result, } from './types'; import { getUi, updateUi } from './ui'; import { getScenarioIdsFromCookie, setDataMocksServerCookie } from './cookies'; import { getContextFromScenarios } from './utils/get-context-from-scenarios'; import { handleRequest } from './handle-request'; +import { + getScenarios as apiGetScenarios, + modifyScenarios, + resetScenarios, +} from './apis'; export { createExpressApp }; @@ -90,29 +91,49 @@ function createExpressApp({ app.put( modifyScenariosPath, - modifyScenarios({ - scenarioNames: scenarioIds, - scenarioMap, - updateScenariosAndContext, - }), - ); + ({ body: { scenarios: updatedScenarioIds } }: Request, res: Response) => { + const result = modifyScenarios({ + cookieMode, + defaultScenario, + scenarioIds, + scenarioMap, + setCookie: expressSetCookie(res), + setServerContext, + setServerSelectedScenarioIds, + updatedScenarioIds, + }); - app.put( - resetScenariosPath, - resetScenarios({ - updateScenariosAndContext, - }), + expressResponse(res, result); + }, ); - app.get( - scenariosPath, - apiGetScenarios({ + app.put(resetScenariosPath, (_, res: Response) => { + const result = resetScenarios({ + setCookie: expressSetCookie(res), + setServerContext, + setServerSelectedScenarioIds, + defaultScenario, scenarioMap, - getSelectedScenarioIds, - }), - ); + cookieMode, + }); + + expressResponse(res, result); + }); + + app.get(scenariosPath, (req: Request, res: Response) => { + const result = apiGetScenarios({ + getCookie: expressGetCookie(req), + setCookie: expressSetCookie(res), + getServerSelectedScenarioIds: () => serverSelectedScenarioIds, + cookieMode, + defaultScenario, + scenarioMap, + }); - app.use(async (req, res, next) => { + expressResponse(res, result); + }); + + app.use(async (req, res) => { const internalRequest: InternalRequest = { body: req.body, headers: expressCleanHeaders(req.headers || {}), @@ -131,20 +152,11 @@ function createExpressApp({ serverContext = context; }, cookieMode, - getCookie: (cookieName: string) => req.cookies[cookieName], + getCookie: expressGetCookie(req), setCookie: expressSetCookie(res), }); - if (result.status === 404) { - next(); - - return; - } - - res - .set(result.headers) - .status(result.status) - .send(result.response); + expressResponse(res, result); }); return app; @@ -181,8 +193,8 @@ function createExpressApp({ function getSelectedScenarioIds(req: Request, res: Response) { if (cookieMode) { return getScenarioIdsFromCookie({ - req, - res, + getCookie: expressGetCookie(req), + setCookie: expressSetCookie(res), defaultValue: { context: getContextFromScenarios([defaultScenario]), scenarios: [], @@ -192,6 +204,14 @@ function createExpressApp({ return serverSelectedScenarioIds; } + + function setServerContext(context: Context) { + serverContext = context; + } + + function setServerSelectedScenarioIds(selectedScenarioIds: string[]) { + serverSelectedScenarioIds = selectedScenarioIds; + } } function getScenarios({ @@ -214,6 +234,10 @@ function expressSetCookie(res: Response) { }; } +function expressGetCookie(req: Request) { + return (cookieName: string) => req.cookies[cookieName]; +} + function expressCleanHeaders( headers: Request['headers'], ): Record { @@ -224,3 +248,14 @@ function expressCleanHeaders( ), ); } + +function expressResponse(res: Response, { status, headers, response }: Result) { + res + .set(headers) + .status(status) + .send( + headers && headers['content-type'] === 'application/json' + ? JSON.stringify(response) + : response, + ); +} diff --git a/src/handle-request.ts b/src/handle-request.ts index 64300e5..e434d57 100644 --- a/src/handle-request.ts +++ b/src/handle-request.ts @@ -17,6 +17,8 @@ import { PartialContext, InternalRequest, Result, + GetCookie, + SetCookie, } from './types'; import { getDataMocksServerCookie, setDataMocksServerCookie } from './cookies'; @@ -80,8 +82,8 @@ async function handleRequest({ scenarioMap: ScenarioMap; getServerContext: () => Context; setServerContext: (context: Context) => void; - getCookie: (cookieName: string) => string; - setCookie: (cookieName: string, cookieValue: string) => void; + getCookie: GetCookie; + setCookie: SetCookie; cookieMode: boolean; }) { const dataMocksServerCookie = getDataMocksServerCookie({ diff --git a/src/types.ts b/src/types.ts index cb2afab..ea6ad8a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -125,3 +125,6 @@ export type InternalRequest = { // TODO: Should probably only accept string or object body: any; }; + +export type GetCookie = (cookieName: string) => string | undefined; +export type SetCookie = (cookieName: string, cookieValue: string) => void; From fc4b1fa90d2093f7ef1f268263498d04c7dca5d3 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Fri, 3 Feb 2023 01:30:14 +0000 Subject: [PATCH 12/14] refactor: remove express concepts from ui utils --- src/apis.ts | 58 +-------- src/express.ts | 108 +++++---------- src/ui.tsx | 152 +++++++++++++++------- src/utils/update-scenarios-and-context.ts | 65 +++++++++ 4 files changed, 208 insertions(+), 175 deletions(-) create mode 100644 src/utils/update-scenarios-and-context.ts diff --git a/src/apis.ts b/src/apis.ts index f13d99c..88a00ca 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -1,15 +1,15 @@ -import { getScenarioIdsFromCookie, setDataMocksServerCookie } from './cookies'; +import { getScenarioIdsFromCookie } from './cookies'; import { Context, DefaultScenario, GetCookie, Result, - Scenario, ScenarioMap, SetCookie, } from './types'; import { getAllScenarios } from './utils/get-all-scenarios'; import { getContextFromScenarios } from './utils/get-context-from-scenarios'; +import { updateScenariosAndContext } from './utils/update-scenarios-and-context'; export { resetScenarios, modifyScenarios, getScenarios }; @@ -43,46 +43,6 @@ function resetScenarios({ }; } -function updateScenariosAndContext({ - updatedScenarioIds, - setCookie, - setServerContext, - setServerSelectedScenarioIds, - defaultScenario, - scenarioMap, - cookieMode, -}: { - updatedScenarioIds: string[]; - setCookie: SetCookie; - setServerContext: (context: Context) => void; - setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; - defaultScenario: DefaultScenario; - scenarioMap: ScenarioMap; - cookieMode: boolean; -}) { - const updatedScenarios = getScenariosInternal({ - defaultScenario, - scenarioMap, - scenarioIds: updatedScenarioIds, - }); - const context = getContextFromScenarios(updatedScenarios); - - if (cookieMode) { - setDataMocksServerCookie({ - setCookie, - value: { - context, - scenarios: updatedScenarioIds, - }, - }); - - return; - } - - setServerContext(context); - setServerSelectedScenarioIds(updatedScenarioIds); -} - function getScenarios({ getCookie, setCookie, @@ -197,20 +157,6 @@ function modifyScenarios({ return { status: 204 }; } -function getScenariosInternal({ - defaultScenario, - scenarioMap, - scenarioIds, -}: { - defaultScenario: DefaultScenario; - scenarioMap: ScenarioMap; - scenarioIds: string[]; -}): Scenario[] { - return [defaultScenario].concat( - scenarioIds.map(scenarioId => scenarioMap[scenarioId]), - ); -} - function getSelectedScenarioIds({ getCookie, setCookie, diff --git a/src/express.ts b/src/express.ts index edee3b3..4185861 100644 --- a/src/express.ts +++ b/src/express.ts @@ -8,12 +8,10 @@ import { ScenarioMap, DefaultScenario, Context, - Scenario, InternalRequest, Result, } from './types'; import { getUi, updateUi } from './ui'; -import { getScenarioIdsFromCookie, setDataMocksServerCookie } from './cookies'; import { getContextFromScenarios } from './utils/get-context-from-scenarios'; import { handleRequest } from './handle-request'; import { @@ -69,24 +67,40 @@ function createExpressApp({ app.use(express.json()); app.use(express.text({ type: 'application/graphql' })); - app.get( - uiPath, - getUi({ + app.get(uiPath, (req, res) => { + const html = getUi({ uiPath, scenarioMap, - getSelectedScenarioIds, - }), - ); + cookieMode, + defaultScenario, + getCookie: expressGetCookie(req), + getServerSelectedScenarioIds, + setCookie: expressSetCookie(res), + }); + + res.send(html); + }); app.post( uiPath, - updateUi({ - uiPath, - groupNames, - scenarioNames: scenarioIds, - scenarioMap, - updateScenariosAndContext, - }), + ({ body: { scenarios: scenariosBody, button, ...rest } }, res) => { + const html = updateUi({ + uiPath, + groupNames, + scenarioNames: scenarioIds, + updatedScenarioIds: scenariosBody, + buttonType: button, + scenarioMap, + cookieMode, + defaultScenario, + setCookie: expressSetCookie(res), + setServerContext, + setServerSelectedScenarioIds, + groupScenario: rest, + }); + + res.send(html); + }, ); app.put( @@ -124,7 +138,7 @@ function createExpressApp({ const result = apiGetScenarios({ getCookie: expressGetCookie(req), setCookie: expressSetCookie(res), - getServerSelectedScenarioIds: () => serverSelectedScenarioIds, + getServerSelectedScenarioIds, cookieMode, defaultScenario, scenarioMap, @@ -144,13 +158,11 @@ function createExpressApp({ const result = await handleRequest({ req: internalRequest, - getServerSelectedScenarioIds: () => serverSelectedScenarioIds, + getServerSelectedScenarioIds, defaultScenario, scenarioMap, getServerContext: () => serverContext, - setServerContext: (context: Context) => { - serverContext = context; - }, + setServerContext, cookieMode, getCookie: expressGetCookie(req), setCookie: expressSetCookie(res), @@ -161,47 +173,7 @@ function createExpressApp({ return app; - function updateScenariosAndContext( - res: Response, - updatedScenarioNames: string[], - ) { - const updatedScenarios = getScenarios({ - defaultScenario, - scenarioMap, - scenarioIds: updatedScenarioNames, - }); - const context = getContextFromScenarios(updatedScenarios); - - if (cookieMode) { - setDataMocksServerCookie({ - setCookie: expressSetCookie(res), - value: { - context, - scenarios: updatedScenarioNames, - }, - }); - - return; - } - - serverContext = context; - serverSelectedScenarioIds = updatedScenarioNames; - - return updatedScenarioNames; - } - - function getSelectedScenarioIds(req: Request, res: Response) { - if (cookieMode) { - return getScenarioIdsFromCookie({ - getCookie: expressGetCookie(req), - setCookie: expressSetCookie(res), - defaultValue: { - context: getContextFromScenarios([defaultScenario]), - scenarios: [], - }, - }); - } - + function getServerSelectedScenarioIds() { return serverSelectedScenarioIds; } @@ -214,20 +186,6 @@ function createExpressApp({ } } -function getScenarios({ - defaultScenario, - scenarioMap, - scenarioIds, -}: { - defaultScenario: DefaultScenario; - scenarioMap: ScenarioMap; - scenarioIds: string[]; -}): Scenario[] { - return [defaultScenario].concat( - scenarioIds.map(scenarioId => scenarioMap[scenarioId]), - ); -} - function expressSetCookie(res: Response) { return (cookieName: string, cookieValue: string) => { res.cookie(cookieName, cookieValue, { encode: String }); diff --git a/src/ui.tsx b/src/ui.tsx index 0e3ab21..8e4d3cf 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -1,79 +1,143 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; -import { RequestHandler, Request, Response } from 'express'; -import { ScenarioMap } from './types'; +import { + Context, + DefaultScenario, + GetCookie, + ScenarioMap, + SetCookie, +} from './types'; import { Html } from './Html'; import { getAllScenarios } from './utils/get-all-scenarios'; +import { updateScenariosAndContext } from './utils/update-scenarios-and-context'; +import { getScenarioIdsFromCookie } from './cookies'; +import { getContextFromScenarios } from './utils/get-context-from-scenarios'; export { getUi, updateUi }; function getUi({ uiPath, scenarioMap, - getSelectedScenarioIds, + cookieMode, + getCookie, + setCookie, + defaultScenario, + getServerSelectedScenarioIds, }: { uiPath: string; scenarioMap: ScenarioMap; - getSelectedScenarioIds: (req: Request, res: Response) => string[]; -}): RequestHandler { - return (req: Request, res: Response) => { - const selectedScenarioIds = getSelectedScenarioIds(req, res); - const { groups, other } = getAllScenarios(scenarioMap, selectedScenarioIds); + cookieMode: boolean; + getCookie: GetCookie; + setCookie: SetCookie; + defaultScenario: DefaultScenario; + getServerSelectedScenarioIds: () => string[]; +}) { + const selectedScenarioIds = getSelectedScenarioIdsV2({ + cookieMode, + getCookie, + setCookie, + defaultScenario, + getServerSelectedScenarioIds, + }); + const { groups, other } = getAllScenarios(scenarioMap, selectedScenarioIds); - const html = renderToStaticMarkup( - , - ); + const html = renderToStaticMarkup( + , + ); - res.send('\n' + html); - }; + return '\n' + html; +} + +function getSelectedScenarioIdsV2({ + cookieMode, + getCookie, + setCookie, + defaultScenario, + getServerSelectedScenarioIds, +}: { + cookieMode: boolean; + getCookie: GetCookie; + setCookie: SetCookie; + defaultScenario: DefaultScenario; + getServerSelectedScenarioIds: () => string[]; +}) { + if (cookieMode) { + return getScenarioIdsFromCookie({ + getCookie, + setCookie, + defaultValue: { + context: getContextFromScenarios([defaultScenario]), + scenarios: [], + }, + }); + } + + return getServerSelectedScenarioIds(); } function updateUi({ uiPath, groupNames, scenarioNames, + updatedScenarioIds, + buttonType, scenarioMap, - updateScenariosAndContext, + groupScenario, + cookieMode, + defaultScenario, + setCookie, + setServerContext, + setServerSelectedScenarioIds, }: { uiPath: string; groupNames: string[]; scenarioNames: string[]; + updatedScenarioIds: string[]; + buttonType: 'modify' | 'reset'; scenarioMap: ScenarioMap; - updateScenariosAndContext: (res: Response, scenarios: string[]) => void; -}): RequestHandler { - return (req: Request, res: Response) => { - const { - body: { scenarios: scenariosBody, button, ...rest }, - } = req; - let updatedScenarios: string[] = []; + groupScenario: Record; + cookieMode: boolean; + defaultScenario: DefaultScenario; + setCookie: SetCookie; + setServerContext: (context: Context) => void; + setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; +}) { + let updatedScenarios: string[] = []; - if (button === 'modify') { - updatedScenarios = groupNames - .reduce((result, groupName) => { - if (rest[groupName]) { - result.push(rest[groupName]); - } + if (buttonType === 'modify') { + updatedScenarios = groupNames + .reduce((result, groupName) => { + if (groupScenario[groupName]) { + result.push(groupScenario[groupName]); + } - return result; - }, []) - .concat(scenariosBody == null ? [] : scenariosBody) - .filter(scenarioName => scenarioNames.includes(scenarioName)); - } + return result; + }, []) + .concat(updatedScenarioIds == null ? [] : updatedScenarioIds) + .filter(scenarioName => scenarioNames.includes(scenarioName)); + } - updateScenariosAndContext(res, updatedScenarios); + updateScenariosAndContext({ + updatedScenarioIds: updatedScenarios, + scenarioMap, + cookieMode, + defaultScenario, + setCookie, + setServerContext, + setServerSelectedScenarioIds, + }); - const { groups, other } = getAllScenarios(scenarioMap, updatedScenarios); + const { groups, other } = getAllScenarios(scenarioMap, updatedScenarios); - const html = renderToStaticMarkup( - , - ); + const html = renderToStaticMarkup( + , + ); - res.send('\n' + html); - }; + return '\n' + html; } diff --git a/src/utils/update-scenarios-and-context.ts b/src/utils/update-scenarios-and-context.ts new file mode 100644 index 0000000..2881338 --- /dev/null +++ b/src/utils/update-scenarios-and-context.ts @@ -0,0 +1,65 @@ +import { setDataMocksServerCookie } from '../cookies'; +import { + Context, + DefaultScenario, + Scenario, + ScenarioMap, + SetCookie, +} from '../types'; +import { getContextFromScenarios } from './get-context-from-scenarios'; + +export { updateScenariosAndContext }; + +function updateScenariosAndContext({ + updatedScenarioIds, + setCookie, + setServerContext, + setServerSelectedScenarioIds, + defaultScenario, + scenarioMap, + cookieMode, +}: { + updatedScenarioIds: string[]; + setCookie: SetCookie; + setServerContext: (context: Context) => void; + setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + cookieMode: boolean; +}) { + const updatedScenarios = getScenarios({ + defaultScenario, + scenarioMap, + scenarioIds: updatedScenarioIds, + }); + const context = getContextFromScenarios(updatedScenarios); + + if (cookieMode) { + setDataMocksServerCookie({ + setCookie, + value: { + context, + scenarios: updatedScenarioIds, + }, + }); + + return; + } + + setServerContext(context); + setServerSelectedScenarioIds(updatedScenarioIds); +} + +function getScenarios({ + defaultScenario, + scenarioMap, + scenarioIds, +}: { + defaultScenario: DefaultScenario; + scenarioMap: ScenarioMap; + scenarioIds: string[]; +}): Scenario[] { + return [defaultScenario].concat( + scenarioIds.map(scenarioId => scenarioMap[scenarioId]), + ); +} From 3122463a5e20b1439fdc06607212b524973f4eef Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Fri, 3 Feb 2023 01:35:49 +0000 Subject: [PATCH 13/14] chore: fix type --- src/apis.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apis.ts b/src/apis.ts index 88a00ca..063c2d4 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -97,8 +97,7 @@ function modifyScenarios({ setServerContext: (context: Context) => void; setServerSelectedScenarioIds: (selectedScenarioIds: string[]) => void; }) { - // TODO: Check what type of individual items are - if (!Array.isArray(updatedScenarioIds)) { + if (!isStringArray(updatedScenarioIds)) { return { status: 400, headers: { @@ -111,7 +110,7 @@ function modifyScenarios({ }; } - const scenariosByGroup: { [key: string]: number } = {}; + const scenariosByGroup: Record = {}; for (const scenario of updatedScenarioIds) { if (!scenarioIds.includes(scenario)) { return { @@ -157,6 +156,10 @@ function modifyScenarios({ return { status: 204 }; } +function isStringArray(value: unknown): value is string[] { + return Array.isArray(value) && value.every(item => typeof item === 'string'); +} + function getSelectedScenarioIds({ getCookie, setCookie, From 4d19085afb3dff3f43d762ab50d5b12891ccf198 Mon Sep 17 00:00:00 2001 From: Kenny Gray Date: Fri, 3 Feb 2023 01:52:25 +0000 Subject: [PATCH 14/14] refactor: rename scenarioName to scenarioId internally --- src/express.ts | 2 +- src/ui.tsx | 6 +++--- src/utils/get-all-scenarios.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/express.ts b/src/express.ts index 4185861..0ceb4a2 100644 --- a/src/express.ts +++ b/src/express.ts @@ -87,7 +87,7 @@ function createExpressApp({ const html = updateUi({ uiPath, groupNames, - scenarioNames: scenarioIds, + scenarioIds, updatedScenarioIds: scenariosBody, buttonType: button, scenarioMap, diff --git a/src/ui.tsx b/src/ui.tsx index 8e4d3cf..d95a143 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -79,7 +79,7 @@ function getSelectedScenarioIdsV2({ function updateUi({ uiPath, groupNames, - scenarioNames, + scenarioIds, updatedScenarioIds, buttonType, scenarioMap, @@ -92,7 +92,7 @@ function updateUi({ }: { uiPath: string; groupNames: string[]; - scenarioNames: string[]; + scenarioIds: string[]; updatedScenarioIds: string[]; buttonType: 'modify' | 'reset'; scenarioMap: ScenarioMap; @@ -115,7 +115,7 @@ function updateUi({ return result; }, []) .concat(updatedScenarioIds == null ? [] : updatedScenarioIds) - .filter(scenarioName => scenarioNames.includes(scenarioName)); + .filter(scenarioId => scenarioIds.includes(scenarioId)); } updateScenariosAndContext({ diff --git a/src/utils/get-all-scenarios.ts b/src/utils/get-all-scenarios.ts index ce23cf7..5319b73 100644 --- a/src/utils/get-all-scenarios.ts +++ b/src/utils/get-all-scenarios.ts @@ -10,9 +10,9 @@ function getAllScenarios( other: string[]; [key: string]: string[]; }>( - (result, [scenarioName, scenarioMock]) => { + (result, [scenarioId, scenarioMock]) => { if (Array.isArray(scenarioMock) || scenarioMock.group == null) { - result.other.push(scenarioName); + result.other.push(scenarioId); return result; } @@ -23,7 +23,7 @@ function getAllScenarios( result[group] = []; } - result[group].push(scenarioName); + result[group].push(scenarioId); return result; },