Skip to content

Commit

Permalink
feat(mocker): integrate mocker with business logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Miaskowski committed Oct 15, 2018
1 parent 16dbe2d commit e4513c5
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 53 deletions.
2 changes: 0 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import * as types from './types';

export { types, filesystemLoader };



export function factory<Resource, Input, Output, Config, LoadOpts>(
defaultComponents: Partial<types.IPrismComponents<Resource, Input, Output, Config, LoadOpts>>
): (
Expand Down
27 changes: 13 additions & 14 deletions packages/core/src/mock/http/HttpProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,46 @@ import { IHttpOperation } from '@stoplight/types';
import { IExampleGenerator } from '../generator/IExampleGenerator';
import { IMockProvider } from '../IMockProvider';
import { IMockResult } from '../IMockResult';
import { IHttpOperationOptions } from './IHttpOperationOptions';
import { IMockHttpResponse } from './IMockHttpResponse';
import { IHttpOperationConfig, IHttpResponse } from '@stoplight/prism-http/types';

export class HttpProvider
implements IMockProvider<IHttpOperation, IHttpOperationOptions, IMockResult<IMockHttpResponse>> {
implements IMockProvider<IHttpOperation, IHttpOperationConfig, IMockResult<IHttpResponse>> {
constructor(private exampleGenerator: IExampleGenerator<any>) {}

public async mock(
operation: IHttpOperation,
options: IHttpOperationOptions
): Promise<IMockResult<IMockHttpResponse>> {
const response = operation.responses.find(r => r.code === options.status);
resource: IHttpOperation,
config: IHttpOperationConfig
): Promise<IMockResult<IHttpResponse>> {
const response = resource.responses.find(r => r.code === config.code);
if (!response) {
throw new Error(`Response for status code '${options.status}' not found`);
throw new Error(`Response for status code '${config.code}' not found`);
}

const content = response.content.find(c => c.mediaType === options.mediaType);
const content = response.content.find(c => c.mediaType === config.mediaType);
if (!content) {
throw new Error(`Content for media type '${options.mediaType}' not found`);
throw new Error(`Content for media type '${config.mediaType}' not found`);
}

let body;
if (options.dynamic) {
if (config.dynamic) {
if (!content.schema) {
throw new Error('Cannot generate response, schema is missing');
}

body = await this.exampleGenerator.generate(content.schema, content.mediaType);
} else {
const example = content.examples && content.examples.find(e => e.key === options.example);
const example = content.examples && content.examples.find(e => e.key === config.exampleKey);

if (!example) {
throw new Error(`Example for key '${options.example}' not found`);
throw new Error(`Example for key '${config.exampleKey}' not found`);
}

body = example.value;
}

return {
data: {
status: parseInt(response.code),
statusCode: parseInt(response.code),
// @todo: HttpHeaderParam[] ?
headers: { 'Content-type': content.mediaType },
body,
Expand Down
5 changes: 0 additions & 5 deletions packages/core/src/mock/http/IMockHttpResponse.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export interface IForwarder<Resource, Input, Config, Output> {

export interface IMocker<Resource, Input, Config, Output> {
mock: (
opts: IMockerOpts<Resource, Input, Config>,
opts: Partial<IMockerOpts<Resource, Input, Config>>,
defaultMocker?: IMocker<Resource, Input, Config, Output>
) => Promise<Output>;
}

export interface IMockerOpts<Resource, Input, Config> {
resource?: Resource;
input?: IPrismInput<Input>;
config?: Config
resource: Resource;
input: IPrismInput<Input>;
config: Config
}

export interface IValidator<Resource, Input, Config, Output> {
Expand Down
6 changes: 3 additions & 3 deletions packages/http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"node": ">=8"
},
"dependencies": {
"@stoplight/prism-core": "0.0.0"
"@stoplight/prism-core": "0.0.0",
"lodash": "^4.17.11"
},
"devDependencies": {
"@types/lodash": "^4.14.117",
"lodash": "^4.17.11"
"@types/lodash": "^4.14.117"
}
}
27 changes: 25 additions & 2 deletions packages/http/src/mocker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { types } from '@stoplight/prism-core';
import { IHttpOperation } from '@stoplight/types';

import * as t from '../types';
import HttpOperationConfigNegotiator from '@stoplight/prism-http/mocker/negotiator/HttpOperationConfigNegotiator';
import { HttpProvider } from '@stoplight/prism-core/mock/http/HttpProvider';
import { JSONSchemaExampleGenerator } from '@stoplight/prism-core/mock/generator/JSONSchemaExampleGenerator';

const negotiator = new HttpOperationConfigNegotiator();
const mockProvider = new HttpProvider(new JSONSchemaExampleGenerator());

export const mocker: types.IMocker<
IHttpOperation,
Expand All @@ -10,7 +16,24 @@ export const mocker: types.IMocker<
t.IHttpResponse
> = {
mock: async ({ resource, input, config }) => {
// build response for resource, input, and config
throw new Error('Method not implemented.');
//TODO: what if resource || input || config not defined?
const mock: boolean | t.IHttpOperationConfig = config.mock;
if(typeof mock === 'boolean') {
// TODO: ??
throw new Error();
} else {
const negotiationResult = await negotiator.negotiate({ resource, input, config: mock });
const { error, httpOperationConfig } = negotiationResult;
if(error) {
// TODO: ??
throw negotiationResult.error;
} else if(!!httpOperationConfig) {
const { data } = await mockProvider.mock(resource, httpOperationConfig);
return data;
} else {
// TODO: ??
throw new Error();
}
}
},
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IHttpRequest, IHttpOperationConfig } from "@stoplight/prism-http/types";
import { IHttpOperation } from "@stoplight/types/http";
import { IPrismInput } from "@stoplight/prism-core/types";
import { IMockerOpts } from "@stoplight/prism-core/types";
import helpers from '@stoplight/prism-http/mocker/negotiator/NegotiatorHelpers';

export interface IHttpOperationConfigNegotiationResult {
Expand All @@ -9,18 +9,19 @@ export interface IHttpOperationConfigNegotiationResult {
}

interface IOperationConfigNegotiator<Resource, Input, OperationConfig, Output> {
negotiate(resource: Resource, input: Input, desiredConfig: OperationConfig): Promise<Output>;
negotiate(opts: IMockerOpts<Resource, Input, OperationConfig>): Promise<Output>;
}

export default class HttpOperationOptionsNegotiator implements IOperationConfigNegotiator<
export default class HttpOperationConfigNegotiator implements IOperationConfigNegotiator<
IHttpOperation,
IPrismInput<IHttpRequest>,
IHttpRequest,
IHttpOperationConfig,
IHttpOperationConfigNegotiationResult> {

public negotiate(resource: IHttpOperation, input: IPrismInput<IHttpRequest>, desiredConfig: IHttpOperationConfig): Promise<IHttpOperationConfigNegotiationResult> {
public negotiate(opts: IMockerOpts<IHttpOperation, IHttpRequest, IHttpOperationConfig>): Promise<IHttpOperationConfigNegotiationResult> {
try {
const httpRequest = input.data;
const { resource, input, config: desiredConfig } = opts;
const httpRequest = opts.input.data;
let httpOperationConfig: IHttpOperationConfig;

if (input.validations.input.length > 0) {
Expand Down
3 changes: 1 addition & 2 deletions packages/http/src/mocker/negotiator/NegotiatorHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as _ from 'lodash';
import { IHttpOperationConfig, IHttpRequest } from "@stoplight/prism-http/types";
import { IHttpContent, IHttpResponse, IHttpOperation } from "@stoplight/types/http";

Expand Down Expand Up @@ -162,7 +161,7 @@ const helpers = {
throw new Error('No 400 response defined');
}
// find first response with any static examples
const responseWithExamples = response.content.find(content => !_.isEmpty(content.examples));
const responseWithExamples = response.content.find(content => !content.examples || content.examples.length === 0);
// find first response with a schema
const responseWithSchema = response.content.find(content => !!content.schema);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import helpers from '../NegotiatorHelpers';
import HttpOperationOptionsNegotiator from '../HttpOperationOptionsNegotiator';
import HttpOperationConfigNegotiator from '../HttpOperationConfigNegotiator';
import { Chance } from 'chance';
import { anHttpOperation } from '@stoplight/prism-http/mocker/negotiator/__tests__/utils';
import { IValidation, ValidationSeverity } from '@stoplight/prism-core/types';
Expand All @@ -8,17 +8,17 @@ import { IHttpRequest } from '@stoplight/prism-http/types';
const chance = new Chance();

describe('HttpOperationOptionsNegotiator', () => {
let negotiator: HttpOperationOptionsNegotiator;
let negotiator: HttpOperationConfigNegotiator;

beforeEach(() => {
negotiator = new HttpOperationOptionsNegotiator();
negotiator = new HttpOperationConfigNegotiator();
});

afterEach(() => {
jest.restoreAllMocks();
});

describe.only('negotiate()', () => {
describe('negotiate()', () => {
const httpOperationConfig = {
code: chance.string(),
mediaType: chance.string(),
Expand All @@ -33,16 +33,20 @@ describe('HttpOperationOptionsNegotiator', () => {
const desiredConfig = {};
const httpOperation = anHttpOperation().instance();

function getOpts(resource: any, input: any, config: any) {
return { resource, input, config };
}

describe('given valid input', () => {
it('and negotiations succeed should return config', async () => {
jest.spyOn(helpers, 'negotiateOptionsForValidRequest').mockReturnValue(httpOperationConfig);

const result = await negotiator.negotiate(httpOperation, {
const result = await negotiator.negotiate(getOpts(httpOperation, {
data: httpRequest,
validations: {
input: []
}
}, desiredConfig);
}, desiredConfig));

expect(helpers.negotiateOptionsForValidRequest).toHaveBeenCalledTimes(1);
expect(helpers.negotiateOptionsForValidRequest).toHaveBeenCalledWith(httpOperation, desiredConfig, httpRequest);
Expand All @@ -55,12 +59,12 @@ describe('HttpOperationOptionsNegotiator', () => {
const error = new Error('fake error');
jest.spyOn(helpers, 'negotiateOptionsForValidRequest').mockImplementation(() => { throw error; });

const result = await negotiator.negotiate(httpOperation, {
const result = await negotiator.negotiate(getOpts(httpOperation, {
data: httpRequest,
validations: {
input: []
}
}, desiredConfig);
}, desiredConfig));

expect(helpers.negotiateOptionsForValidRequest).toHaveBeenCalledTimes(1);
expect(helpers.negotiateOptionsForValidRequest).toHaveBeenCalledWith(httpOperation, desiredConfig, httpRequest);
Expand All @@ -82,12 +86,12 @@ describe('HttpOperationOptionsNegotiator', () => {
it('and negotiations succeed should return config', async () => {
jest.spyOn(helpers, 'negotiateOptionsForInvalidRequest').mockReturnValue(httpOperationConfig);

const result = await negotiator.negotiate(httpOperation, {
const result = await negotiator.negotiate(getOpts(httpOperation, {
data: httpRequest,
validations: {
input: [validation]
}
}, desiredConfig);
}, desiredConfig));

expect(helpers.negotiateOptionsForInvalidRequest).toHaveBeenCalledTimes(1);
expect(helpers.negotiateOptionsForInvalidRequest).toHaveBeenCalledWith(httpOperation.responses, httpRequest);
Expand All @@ -100,12 +104,12 @@ describe('HttpOperationOptionsNegotiator', () => {
const error = new Error();
jest.spyOn(helpers, 'negotiateOptionsForInvalidRequest').mockImplementation(() => { throw error; });

const result = await negotiator.negotiate(httpOperation, {
const result = await negotiator.negotiate(getOpts(httpOperation, {
data: httpRequest,
validations: {
input: [validation]
}
}, desiredConfig);
}, desiredConfig));

expect(helpers.negotiateOptionsForInvalidRequest).toHaveBeenCalledTimes(1);
expect(helpers.negotiateOptionsForInvalidRequest).toHaveBeenCalledWith(httpOperation.responses, httpRequest);
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface IHttpOperationConfig {
}

export interface IHttpConfig extends types.IPrismConfig {
mock?:
mock:
| boolean
| IHttpOperationConfig;

Expand Down
5 changes: 3 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"

"@stoplight/types@stoplightio/types":
"@stoplight/types@1.0.x":
version "1.0.1"
resolved "https://codeload.github.com/stoplightio/types/tar.gz/daf340627dab8e529ad87a68e8e4c50e8706fb59"
resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-1.0.1.tgz#4a1c49c494f66e46efce4da30edcb03fd94617c8"
integrity sha512-5eobPkXsxJDP3q3IUT+iucRwiKwnJlaIRQY7bA42p6O3IyV9URBPuzfuMQNlyrDj2tt6StNEuYo6ReQxWFCHXg==

"@types/chance@^1.0.1":
version "1.0.1"
Expand Down

0 comments on commit e4513c5

Please sign in to comment.