diff --git a/.circleci/config.yml b/.circleci/config.yml index 775676f2..e8c0ec4a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,6 @@ jobs: - run: name: Authenticate with npm command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - - run: npx lerna publish from-package --yes - add_ssh_keys: fingerprints: - "34:32:ff:02:fe:1e:00:10:49:75:8e:d4:6c:2b:12:ca" @@ -45,6 +44,7 @@ jobs: TAG=v`cat packages/unmock-core/package.json | grep version | awk 'BEGIN { FS = "\"" } { print $4 }'` git tag -a $TAG git push origin $TAG + - run: npm run publish --yes workflows: version: 2 diff --git a/package.json b/package.json index 53ee97ca..0bffb454 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "compile": "tsc --build tsconfig.build.json", "compile:clean": "tsc --build tsconfig.build.json --clean", "watch": "tsc --build tsconfig.build.json --watch", - "release": "npm run clean && npm install && lerna publish --exact --force-publish=unmock-core --include-merged-tags", + "release": "npm run clean && npm install && lerna publish from-package --exact --force-publish=unmock-core --include-merged-tags", "postinstall": "lerna run prepare && npm run lint-ts && npm run compile", "format-check": "prettier-check '**/*.{js,ts}'", "format": "prettier '**/*.{js,ts}' --write", diff --git a/packages/unmock-core/src/__tests__/dsl.test.ts b/packages/unmock-core/src/__tests__/dsl.test.ts index 76bb95d8..e221f53a 100644 --- a/packages/unmock-core/src/__tests__/dsl.test.ts +++ b/packages/unmock-core/src/__tests__/dsl.test.ts @@ -65,6 +65,7 @@ describe("Translates top level DSL to OAS", () => { it("Throws on invalid $times (wrong type) with STRICT_MODE", () => { DSL.STRICT_MODE = true; const translated = () => + // @ts-ignore // uses incorrect type on purpose DSL.translateTopLevelToOAS({ $times: "a" }, responsesWithoutProperties); expect(translated).toThrow( "Can't set response $times with non-numeric value!", diff --git a/packages/unmock-core/src/__tests__/generator.test.ts b/packages/unmock-core/src/__tests__/generator.test.ts index 94566c93..1dfd1ef7 100644 --- a/packages/unmock-core/src/__tests__/generator.test.ts +++ b/packages/unmock-core/src/__tests__/generator.test.ts @@ -1,9 +1,6 @@ import fs from "fs"; import path from "path"; -import { IServiceDefLoader } from ".."; -import { responseCreatorFactory } from "../generator"; - -// const slackYamlString: string = fs.readFileSync(slackAbsPath, "utf-8"); +import { IServiceDefLoader, responseCreatorFactory } from ".."; const serviceDefLoader: IServiceDefLoader = { load: () => Promise.all(serviceDefLoader.loadSync()), diff --git a/packages/unmock-core/src/__tests__/matcher.test.ts b/packages/unmock-core/src/__tests__/matcher.test.ts index cbadab1d..e99ebd0c 100644 --- a/packages/unmock-core/src/__tests__/matcher.test.ts +++ b/packages/unmock-core/src/__tests__/matcher.test.ts @@ -1,7 +1,7 @@ import fs from "fs"; import jsYaml from "js-yaml"; import path from "path"; -import { ISerializedRequest } from "../interfaces"; +import { ISerializedRequest } from ".."; import { OASMatcher } from "../service/matcher"; const petStoreYamlString: string = fs.readFileSync( diff --git a/packages/unmock-core/src/__tests__/options.test.ts b/packages/unmock-core/src/__tests__/options.test.ts index 80e74909..687e3055 100644 --- a/packages/unmock-core/src/__tests__/options.test.ts +++ b/packages/unmock-core/src/__tests__/options.test.ts @@ -1,4 +1,4 @@ -import { UnmockOptions } from "../options"; +import { UnmockOptions } from ".."; describe("tests whitelist", () => { test("test default whitelist", () => { diff --git a/packages/unmock-core/src/__tests__/parser.test.ts b/packages/unmock-core/src/__tests__/parser.test.ts index f299287a..c9ec88b2 100644 --- a/packages/unmock-core/src/__tests__/parser.test.ts +++ b/packages/unmock-core/src/__tests__/parser.test.ts @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import { IServiceDef } from "../interfaces"; +import { IServiceDef } from ".."; import { ServiceParser } from "../service/parser"; const absPath = path.join(__dirname, "__unmock__", "petstore", "spec.yaml"); diff --git a/packages/unmock-core/src/__tests__/state.test.ts b/packages/unmock-core/src/__tests__/state.test.ts index b3fbb42a..8acd1fe7 100644 --- a/packages/unmock-core/src/__tests__/state.test.ts +++ b/packages/unmock-core/src/__tests__/state.test.ts @@ -1,10 +1,10 @@ +import { dsl } from ".."; import { ExtendedHTTPMethod, HTTPMethod, IStateInputGenerator, } from "../service/interfaces"; import { State } from "../service/state/state"; -import defMiddleware, { textResponse } from "../service/state/transformers"; const fullSchema = { openapi: "", @@ -98,27 +98,33 @@ describe("Test state management", () => { it("returns undefined when setting empty state", () => { const state = new State(); expect( - updateState(state, "any", "**", defMiddleware(), "**"), + updateState(state, "any", "**", dsl.objResponse(), "**"), ).toBeUndefined(); }); it("throws when setting state for non-existing method with generic endpoint", () => { const state = new State(); expect(() => - updateState(state, "post", "**", defMiddleware(), "**"), + updateState(state, "post", "**", dsl.objResponse(), "**"), ).toThrow("Can't find any endpoints with method 'post'"); }); it("throws when setting state for non-existing method with specific, existing endpoint", () => { const state = new State(); expect(() => - updateState(state, "post", "/test/5", defMiddleware(), "/test/{test_id}"), + updateState( + state, + "post", + "/test/5", + dsl.objResponse(), + "/test/{test_id}", + ), ).toThrow("Can't find response for 'post /test/5'"); }); it("saves state from any endpoint and get method as expected", () => { const state = new State(); - updateState(state, "get", "**", defMiddleware({ foo: { id: 5 } }), "**"); + updateState(state, "get", "**", dsl.objResponse({ foo: { id: 5 } }), "**"); const stateResult = getState(state, "get", "/"); expect(stateResult).toEqual({ 200: { @@ -142,7 +148,13 @@ describe("Test state management", () => { it("parses $times on specific endpoint as expected", () => { const state = new State(); - updateState(state, "get", "**", defMiddleware({ id: 5, $times: 2 }), "**"); + updateState( + state, + "get", + "**", + dsl.objResponse({ id: 5, $times: 2 }), + "**", + ); const getRes = () => getState(state, "get", "/"); expect(getRes()).toEqual({ 200: { @@ -194,7 +206,7 @@ describe("Test state management", () => { it("saves state from any endpoint and any method as expected", () => { const state = new State(); - updateState(state, "any", "**", defMiddleware({ id: 5 }), "**"); + updateState(state, "any", "**", dsl.objResponse({ id: 5 }), "**"); const stateResult = getState(state, "get", "/"); expect(stateResult).toEqual({ 200: { @@ -214,19 +226,19 @@ describe("Test state management", () => { it("spreads states from multiple paths correctly", () => { const state = new State(); - updateState(state, "any", "**", defMiddleware({ id: 5 }), "**"); + updateState(state, "any", "**", dsl.objResponse({ id: 5 }), "**"); updateState( state, "any", "/test/5", - defMiddleware({ id: 3 }), + dsl.objResponse({ id: 3 }), "/test/{test_id}", ); updateState( state, "any", "/test/*", - defMiddleware({ id: -1 }), + dsl.objResponse({ id: -1 }), "/test/{test_id}", ); const stateResult = getState(state, "get", "/test/5"); @@ -248,7 +260,7 @@ describe("Test state management", () => { it("saves nested state correctly", () => { const state = new State(); - updateState(state, "any", "**", defMiddleware({ foo: { id: 5 } }), "**"); + updateState(state, "any", "**", dsl.objResponse({ foo: { id: 5 } }), "**"); const stateResult = getState(state, "get", "/"); expect(stateResult).toEqual({ 200: { @@ -272,7 +284,7 @@ describe("Test state management", () => { it("Handles textual state correctly", () => { const state = new State(); - updateState(state, "any", "**", textResponse("foobar"), "**"); + updateState(state, "any", "**", dsl.textResponse("foobar"), "**"); const stateResult = getState(state, "get", "/"); expect(stateResult).toEqual({ 200: { "text/plain": { const: "foobar", type: "string" } }, diff --git a/packages/unmock-core/src/__tests__/middleware.test.ts b/packages/unmock-core/src/__tests__/transformers.test.ts similarity index 82% rename from packages/unmock-core/src/__tests__/middleware.test.ts rename to packages/unmock-core/src/__tests__/transformers.test.ts index 20abfab7..ad312fc0 100644 --- a/packages/unmock-core/src/__tests__/middleware.test.ts +++ b/packages/unmock-core/src/__tests__/transformers.test.ts @@ -1,4 +1,4 @@ -import { transformers } from "../index"; +import { dsl } from ".."; import { Schema } from "../service/interfaces"; const schema: Schema = { @@ -34,7 +34,7 @@ const schema: Schema = { describe("Test text provider", () => { it("returns empty object for undefined state", () => { - const p = transformers.textResponse(); + const p = dsl.textResponse(); expect(p.isEmpty).toBeTruthy(); expect(p.top).toEqual({}); // @ts-ignore // deliberately checking with empty input @@ -42,7 +42,7 @@ describe("Test text provider", () => { }); it("returns empty object for empty state", () => { - const p = transformers.textResponse(""); + const p = dsl.textResponse(""); expect(p.isEmpty).toBeTruthy(); expect(p.top).toEqual({}); // @ts-ignore // deliberately checking with empty input @@ -50,7 +50,7 @@ describe("Test text provider", () => { }); it("returns empty object for empty schema", () => { - const p = transformers.textResponse("foo"); + const p = dsl.textResponse("foo"); expect(p.isEmpty).toBeFalsy(); expect(p.top).toEqual({}); // @ts-ignore // deliberately checking with empty input @@ -58,14 +58,14 @@ describe("Test text provider", () => { }); it("returns empty object for non-text schema", () => { - const p = transformers.textResponse("foo"); + const p = dsl.textResponse("foo"); expect(p.isEmpty).toBeFalsy(); expect(p.top).toEqual({}); expect(p.gen({ type: "array", items: {} })).toEqual({}); }); it("returns correct state object for valid input", () => { - const p = transformers.textResponse("foo"); + const p = dsl.textResponse("foo"); expect(p.isEmpty).toBeFalsy(); expect(p.top).toEqual({}); expect(p.gen({ type: "string" })).toEqual({ type: "string", const: "foo" }); @@ -73,7 +73,7 @@ describe("Test text provider", () => { it("top level DSL doesn't change response", () => { // @ts-ignore // invalid value on purpose - const p = transformers.textResponse("foo", { $code: 200, notDSL: "a" }); + const p = dsl.textResponse("foo", { $code: 200, notDSL: "a" }); expect(p.isEmpty).toBeFalsy(); expect(p.top).toEqual({ $code: 200 }); // non DSL is filtered out expect(p.gen({ type: "string" })).toEqual({ type: "string", const: "foo" }); @@ -82,21 +82,21 @@ describe("Test text provider", () => { describe("Test default provider", () => { it("returns empty objects for undefined state", () => { - const p = transformers.default(); + const p = dsl.objResponse(); expect(p.isEmpty).toBeTruthy(); expect(p.top).toEqual({}); expect(p.gen({})).toEqual({}); }); it("returns empty objects for empty state", () => { - const p = transformers.default({}); + const p = dsl.objResponse({}); expect(p.isEmpty).toBeTruthy(); expect(p.top).toEqual({}); expect(p.gen({})).toEqual({}); }); it("filters out top level DSL from state", () => { - const p = transformers.default({ $code: 200, foo: "bar" }); + const p = dsl.objResponse({ $code: 200, foo: "bar" }); expect(p.isEmpty).toBeFalsy(); expect(p.top).toEqual({ $code: 200 }); expect(p.gen({})).toEqual({}); // no schema to expand @@ -111,13 +111,13 @@ describe("Test default provider", () => { }); it("with empty path", () => { - const spreadState = transformers.default({}).gen(schema); + const spreadState = dsl.objResponse({}).gen(schema); expect(spreadState).toEqual({}); // Empty state => empty spread state }); it("with specific path", () => { - const spreadState = transformers - .default({ + const spreadState = dsl + .objResponse({ test: { id: "a" }, }) .gen(schema); @@ -134,13 +134,13 @@ describe("Test default provider", () => { }); it("with vague path", () => { - const spreadState = transformers.default({ id: 5 }).gen(schema); + const spreadState = dsl.objResponse({ id: 5 }).gen(schema); // no "id" in top-most level or immediately under properties\items expect(spreadState).toEqual({ id: null }); }); it("with missing parameters", () => { - const spreadState = transformers.default({ ida: "a" }).gen(schema); + const spreadState = dsl.objResponse({ ida: "a" }).gen(schema); expect(spreadState).toEqual({ ida: null }); // Nothing to spread }); }); diff --git a/packages/unmock-core/src/__tests__/validator.test.ts b/packages/unmock-core/src/__tests__/validator.test.ts index 9d2b538f..640a6f76 100644 --- a/packages/unmock-core/src/__tests__/validator.test.ts +++ b/packages/unmock-core/src/__tests__/validator.test.ts @@ -1,5 +1,5 @@ +import { dsl } from ".."; import { Response, Responses, Schema } from "../service/interfaces"; -import defMiddleware from "../service/state/transformers"; import { getValidStatesForOperationWithState } from "../service/state/validator"; const arraySchema: Schema = { @@ -66,7 +66,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { it("with empty state", () => { const spreadState = getValidStatesForOperationWithState( op, - defMiddleware(), + dsl.objResponse(), deref, ); expect(spreadState.error).toBeUndefined(); @@ -76,7 +76,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { it("invalid parameter returns error", () => { const spreadState = getValidStatesForOperationWithState( op, - defMiddleware({ + dsl.objResponse({ boom: 5, }), deref, @@ -91,7 +91,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { 200: { content: { "application/json": {} }, description: "foo" }, }, }, - defMiddleware(), + dsl.objResponse(), deref, ); expect(spreadState.error).toContain("No schema defined"); @@ -100,7 +100,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { it("with $code specified", () => { const spreadState = getValidStatesForOperationWithState( op, - defMiddleware({ + dsl.objResponse({ $code: 200, tag: "foo", }), @@ -119,7 +119,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { it("with $size in top-level specified", () => { const spreadState = getValidStatesForOperationWithState( arrResponses, - defMiddleware({ $size: 5 }), + dsl.objResponse({ $size: 5 }), deref, ); expect(spreadState.error).toBeUndefined(); @@ -136,7 +136,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { it("with missing $code specified", () => { const spreadState = getValidStatesForOperationWithState( op, - defMiddleware({ + dsl.objResponse({ $code: 404, }), deref, @@ -150,7 +150,7 @@ describe("Tests getValidResponsesForOperationWithState", () => { it("with no $code specified", () => { const spreadState = getValidStatesForOperationWithState( op, - defMiddleware({ + dsl.objResponse({ test: { id: 5 }, }), deref, diff --git a/packages/unmock-core/src/index.ts b/packages/unmock-core/src/index.ts index 88afacce..ce6e9bae 100644 --- a/packages/unmock-core/src/index.ts +++ b/packages/unmock-core/src/index.ts @@ -1,20 +1,34 @@ -import { IBackend, IUnmockOptions } from "./interfaces"; +import { IBackend, IUnmockOptions, IUnmockPackage } from "./interfaces"; import { UnmockOptions } from "./options"; -import * as trns from "./service/state/transformers"; +import * as transformers from "./service/state/transformers"; // top-level exports export { UnmockOptions } from "./options"; export * from "./interfaces"; export * from "./generator"; +export const dsl = transformers; -export const unmock = (baseOptions: UnmockOptions, backend: IBackend) => ( - maybeOptions?: IUnmockOptions, -): any => { - const options = baseOptions.reset(maybeOptions); - if (process.env.NODE_ENV !== "production" || options.useInProduction) { - return backend.initialize(options); +export abstract class CorePackage implements IUnmockPackage { + protected readonly backend: IBackend; + private readonly options: UnmockOptions; + + constructor(baseOptions: UnmockOptions, backend: IBackend) { + this.options = baseOptions; + this.backend = backend; + } + + public on(maybeOptions?: IUnmockOptions) { + return this.backend.initialize(this.options.reset(maybeOptions)); + } + public init(maybeOptions?: IUnmockOptions) { + this.on(maybeOptions); } - return () => { - throw new Error("Are you trying to run unmock in production?"); - }; -}; -export const transformers = trns; + public initialize(maybeOptions?: IUnmockOptions) { + this.on(maybeOptions); + } + + public off() { + this.backend.reset(); + } + + public abstract states(): any; +} diff --git a/packages/unmock-core/src/interfaces.ts b/packages/unmock-core/src/interfaces.ts index de619ff3..0b381b54 100644 --- a/packages/unmock-core/src/interfaces.ts +++ b/packages/unmock-core/src/interfaces.ts @@ -34,6 +34,14 @@ export interface IUnmockOptions { useInProduction?: boolean; } +export interface IUnmockPackage { + on: (maybeOptions?: IUnmockOptions) => any; + init: (maybeOptions?: IUnmockOptions) => any; + initialize: (maybeOptions?: IUnmockOptions) => any; + off: () => void; + states: () => any; +} + /** * Analogous to `IncomingHttpHeaders` in @types/node. * Header names are expected to be _lowercased_. diff --git a/packages/unmock-core/src/service/serviceStore.ts b/packages/unmock-core/src/service/serviceStore.ts index e51767e5..08cab38a 100644 --- a/packages/unmock-core/src/service/serviceStore.ts +++ b/packages/unmock-core/src/service/serviceStore.ts @@ -9,7 +9,7 @@ import { MatcherResponse, UnmockServiceState, } from "./interfaces"; -import defMiddleware from "./state/transformers"; +import { objResponse } from "./state/transformers"; export class ServiceStore { private readonly serviceMapping: IServiceMapping = {}; @@ -75,7 +75,7 @@ export class ServiceStore { let stateGen: IStateInputGenerator; if (!isStateInputGenerator(state)) { // Given an object, set default generator for state - stateGen = defMiddleware(state as UnmockServiceState); + stateGen = objResponse(state as UnmockServiceState); } else { stateGen = state as IStateInputGenerator; } diff --git a/packages/unmock-core/src/service/state/transformers.ts b/packages/unmock-core/src/service/state/transformers.ts index 76f9f27a..e0db0743 100644 --- a/packages/unmock-core/src/service/state/transformers.ts +++ b/packages/unmock-core/src/service/state/transformers.ts @@ -10,7 +10,9 @@ import { // These are specific to OAS and not part of json schema standard const ajv = new Ajv({ unknownFormats: ["int32", "int64"] }); -export default (state?: UnmockServiceState): IStateInputGenerator => ({ +export const objResponse = ( + state?: UnmockServiceState, +): IStateInputGenerator => ({ isEmpty: state === undefined || Object.keys(state).length === 0, top: getTopLevelDSL(state || {}), gen: (schema: Schema) => diff --git a/packages/unmock-jsdom/src/index.ts b/packages/unmock-jsdom/src/index.ts index 3e3c8525..66710e4a 100644 --- a/packages/unmock-jsdom/src/index.ts +++ b/packages/unmock-jsdom/src/index.ts @@ -1,4 +1,4 @@ -import { unmock, UnmockOptions } from "unmock-core"; +import { CorePackage, UnmockOptions } from "unmock-core"; import JSDomBackend from "./backend"; import BrowserLogger from "./logger/browser-logger"; @@ -7,7 +7,11 @@ export const options = new UnmockOptions({ }); const backend = new JSDomBackend(); -export const on = unmock(options, backend); -export const init = on; -export const initialize = on; -export const off = backend.reset(); +class UnmockJSDOM extends CorePackage { + public states() { + throw new Error("Unmock JSDOM does not implement state management yet!"); + } +} + +const unmock = new UnmockJSDOM(options, backend); +export default unmock; diff --git a/packages/unmock-node/src/__tests__/backend/states.test.ts b/packages/unmock-node/src/__tests__/backend/states.test.ts index 88a30b11..a407569f 100644 --- a/packages/unmock-node/src/__tests__/backend/states.test.ts +++ b/packages/unmock-node/src/__tests__/backend/states.test.ts @@ -1,23 +1,31 @@ import axios from "axios"; import path from "path"; -import { transformers, UnmockOptions } from "unmock-core"; +import { CorePackage, dsl, UnmockOptions } from "unmock-core"; import NodeBackend from "../../backend"; +class StateTestPackage extends CorePackage { + public states() { + return (this.backend as NodeBackend).states; + } +} + const servicesDirectory = path.join(__dirname, "..", "loaders", "resources"); describe("Node.js interceptor", () => { describe("with state requests in place", () => { let nodeInterceptor: NodeBackend; + let unmock: CorePackage; let states: any; beforeAll(() => { nodeInterceptor = new NodeBackend({ servicesDirectory }); const unmockOptions = new UnmockOptions(); - states = nodeInterceptor.initialize(unmockOptions); + unmock = new StateTestPackage(unmockOptions, nodeInterceptor); + states = unmock.on(); }); afterAll(() => { - nodeInterceptor.reset(); + unmock.off(); states = undefined; }); @@ -70,7 +78,7 @@ describe("Node.js interceptor", () => { }); test("gets correct state when setting textual middleware", async () => { - states.petstore(transformers.textResponse("foo")); + states.petstore(dsl.textResponse("foo")); const response = await axios("http://petstore.swagger.io/v1/pets"); expect(response.status).toBe(200); expect(response.data).toBe("foo"); @@ -78,7 +86,7 @@ describe("Node.js interceptor", () => { test("throws when setting textual middleware with DSL with non-existing status code", async () => { expect(() => - states.petstore(transformers.textResponse("foo", { $code: 400 })), + states.petstore(dsl.textResponse("foo", { $code: 400 })), ).toThrow("status code '400'"); }); }); diff --git a/packages/unmock-node/src/backend/index.ts b/packages/unmock-node/src/backend/index.ts index 5714622d..7ebceec7 100644 --- a/packages/unmock-node/src/backend/index.ts +++ b/packages/unmock-node/src/backend/index.ts @@ -84,6 +84,9 @@ export default class NodeBackend implements IBackend { * @returns `states` object, with which one can modify states of various services. */ public initialize(options: UnmockOptions): any { + if (process.env.NODE_ENV === "production" && !options.useInProduction) { + throw new Error("Are you trying to run unmock in production?"); + } if (this.mitm !== undefined) { this.reset(); } diff --git a/packages/unmock-node/src/index.ts b/packages/unmock-node/src/index.ts index 6c2e74da..f3c23846 100644 --- a/packages/unmock-node/src/index.ts +++ b/packages/unmock-node/src/index.ts @@ -1,14 +1,17 @@ -import { unmock, UnmockOptions } from "unmock-core"; +import { CorePackage, UnmockOptions } from "unmock-core"; import NodeBackend from "./backend"; import _WinstonLogger from "./logger/winston-logger"; -export { transformers } from "unmock-core"; +export { dsl } from "unmock-core"; const backend = new NodeBackend(); -export const options = new UnmockOptions({ logger: new _WinstonLogger() }); +const options = new UnmockOptions({ logger: new _WinstonLogger() }); -export const on = unmock(options, backend); -export const init = on; -export const initialize = on; -export const off = () => backend.reset(); -export const states = () => backend.states; +class UnmockNode extends CorePackage { + public states() { + return (this.backend as NodeBackend).states; + } +} + +const unmock = new UnmockNode(options, backend); +export default unmock;