Skip to content

Commit 2d0808f

Browse files
authoredSep 24, 2019
Http Client (stoplightio#583)
* refactor: cors is enabled by default * tmp refactor: do not use the config merger to get request specific config * refactor: do not use the configMergerFactory at all * refactor: export mocker in the same way of the router * refactor: remove merger logic * test: simplify the snapshots * fix: do not overwrite * refactor: remove the triple config override mechanism * refactor: stuff * refactor: remove indirection function * refactor: remove assign and optionality * refactor: remove partial config * refactor: remove unused import * refactor: process -> request * wip: http client * feat: http client primer * feat: client part 2 * refactor: compute resources once * refactor: use the proper tools for mapping and providing alternative * refactor: simpler name * refactor: kill the horrible cast * chore: remove logging needs * refactor: split getHttpOperations * refactor: move operations utils in http * chore: use nock to not hit the network * refactor: input and output are always defined * refactor: more strict type checks for components * refactor: remove PickRequired stuff * refactor: output is always defined now * test: try to write something better * refactor: better names and body outside * test: add * refactor: put types in the best possible way * refactor: rename * refactor: better message * refactor: rename to output * feat: forward the status code, if provided * fix: correctly pass params * refactor: validator should valitate always. When is decided by the caller * refactor: update usages * test: update and fix * test: add validate enablement tests * refactor: put 3 functions out for flexibility * chore(deps): bump @stoplight/http-spec from 2.1.4 to 2.2.1 (stoplightio#596) * chore(deps): bump @stoplight/http-spec from 2.1.4 to 2.2.1 Bumps [@stoplight/http-spec](https://github.com/stoplightio/http-spec) from 2.1.4 to 2.2.1. - [Release notes](https://github.com/stoplightio/http-spec/releases) - [Commits](stoplightio/http-spec@v2.1.4...v2.2.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> * deps: upgrade again * refactor: write test text and done type * refactor: pipe stuff * refactor: things * refactor: rename to getHttpOperationsFromResource * refactor: rename to createNewClientInstanceFromResource * refactor: reverse parameter order * refactor: name hygiene * refactor: prism output is unknown * Apply suggestions from code review Co-Authored-By: lag-of-death <mateuszwit21@gmail.com> * refactor: use partial application * refactor: use partial instead of bind * refactor: bastardize body output * refactor: if provided headers must not be null * refactor: be serious about out types * refactor: undefined is not an option with me * test:refactor * refactor: validateSecurity is better * chore: security can be disabled * test: fix * refactor: extract type * feat: handle baseUrl option * refactor: validator is not async * chore: remove forwarder leftover and statusText * test: refactors * refactor: correclty import the fake logger * test: fix again * refactor: override are partials * test: write * refactor: client shall use this and functions * refactor: validations->violations * refactor: no need to redefine all the fields * test: make sure we can override the url * test: make sure we can override the url * refactor: check security is better * test: fix * Apply suggestions from code review Co-Authored-By: Karol Maciaszek <karol@maciaszek.pl> * refactor: rename
1 parent 7476966 commit 2d0808f

24 files changed

+439
-110
lines changed
 

‎packages/cli/src/commands/__tests__/mock.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as utils from '@stoplight/prism-http';
12
import * as yargs from 'yargs';
23
import { createMultiProcessPrism, createSingleProcessPrism } from '../../util/createServer';
34
import mockCommand from '../mock';
@@ -8,7 +9,8 @@ jest.mock('../../util/createServer', () => ({
89
createMultiProcessPrism: jest.fn(),
910
createSingleProcessPrism: jest.fn(),
1011
}));
11-
jest.mock('../../util/getHttpOperations', () => ({ default: jest.fn().mockResolvedValue([]) }));
12+
13+
jest.spyOn(utils, 'getHttpOperationsFromResource').mockResolvedValue([]);
1214

1315
describe('mock command', () => {
1416
beforeEach(() => {

‎packages/cli/src/commands/mock.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { getHttpOperationsFromResource } from '@stoplight/prism-http';
12
import * as signale from 'signale';
23
import { CommandModule } from 'yargs';
34
import { createMultiProcessPrism, CreatePrismOptions, createSingleProcessPrism } from '../util/createServer';
4-
import getHttpOperations from '../util/getHttpOperations';
55

66
const mockCommand: CommandModule = {
77
describe: 'Start a mock server with the given spec file',
@@ -12,7 +12,7 @@ const mockCommand: CommandModule = {
1212
description: 'Path to a spec file. Can be both a file or a fetchable resource on the web.',
1313
type: 'string',
1414
})
15-
.middleware(async argv => (argv.operations = await getHttpOperations(argv.spec!)))
15+
.middleware(async argv => (argv.operations = await getHttpOperationsFromResource(argv.spec!)))
1616
.fail((msg, err) => {
1717
if (msg) yargs.showHelp();
1818
else signale.fatal(err.message);

‎packages/cli/src/util/createServer.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ async function createPrismServerWithLogger(options: CreatePrismOptions, logInsta
5252

5353
const server = createHttpServer(options.operations, {
5454
cors: options.cors,
55-
config: { mock: { dynamic: options.dynamic }, validateRequest: true, validateResponse: true },
55+
config: {
56+
mock: { dynamic: options.dynamic },
57+
validateRequest: true,
58+
validateResponse: true,
59+
checkSecurity: true,
60+
},
5661
components: { logger: logInstance.child({ name: 'HTTP SERVER' }) },
5762
});
5863

‎packages/core/src/__tests__/factory.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('validation', () => {
1515
};
1616

1717
const prismInstance = factory<string, string, string, IPrismConfig>(
18-
{ mock: { dynamic: true }, validateRequest: false, validateResponse: false },
18+
{ mock: { dynamic: false }, validateRequest: false, validateResponse: false, checkSecurity: true },
1919
components,
2020
);
2121

@@ -27,7 +27,7 @@ describe('validation', () => {
2727
beforeAll(async () => {
2828
const obj: any = {};
2929
obj[fieldType] = true;
30-
await prismInstance.process('', [], obj);
30+
await prismInstance.request('', [], obj);
3131
});
3232

3333
afterEach(() => jest.clearAllMocks());
@@ -39,7 +39,7 @@ describe('validation', () => {
3939
});
4040

4141
describe('when disabled', () => {
42-
beforeAll(() => prismInstance.process('', []));
42+
beforeAll(() => prismInstance.request('', []));
4343
afterEach(() => jest.clearAllMocks());
4444
afterAll(() => jest.restoreAllMocks());
4545

‎packages/core/src/factory.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function factory<Resource, Input, Output, Config extends IPrismConfig>(
1212
components: IPrismComponents<Resource, Input, Output, Config>,
1313
): IPrism<Resource, Input, Output, Config> {
1414
return {
15-
process: async (input: Input, resources: Resource[], c?: Config) => {
15+
request: async (input: Input, resources: Resource[], c?: Config) => {
1616
// build the config for this request
1717
const config = defaults(c, defaultConfig) as Config; // Cast required because lodash types are wrong — https://github.com/DefinitelyTyped/DefinitelyTyped/pull/38156
1818
const inputValidations: IPrismDiagnostic[] = [];
@@ -51,13 +51,15 @@ export function factory<Resource, Input, Output, Config extends IPrismConfig>(
5151
);
5252
}
5353

54-
const inputValidationResult = inputValidations.concat(
55-
pipe(
56-
validateSecurity(input, resource),
57-
map(sec => [sec]),
58-
getOrElse<IPrismDiagnostic[]>(() => []),
59-
),
60-
);
54+
const inputValidationResult = config.checkSecurity
55+
? inputValidations.concat(
56+
pipe(
57+
validateSecurity(input, resource),
58+
map(sec => [sec]),
59+
getOrElse<IPrismDiagnostic[]>(() => []),
60+
),
61+
)
62+
: inputValidations;
6163

6264
if (resource && config.mock) {
6365
// generate the response

‎packages/core/src/types.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { Logger } from 'pino';
55
export type IPrismDiagnostic = Omit<IDiagnostic, 'range'>;
66

77
export interface IPrism<Resource, Input, Output, Config extends IPrismConfig> {
8-
process: (input: Input, resources: Resource[], config?: Config) => Promise<IPrismOutput<Input, Output>>;
8+
request: (input: Input, resources: Resource[], config?: Config) => Promise<IPrismOutput<Input, Output>>;
99
}
1010

1111
export interface IPrismConfig {
12-
mock?: object;
13-
security?: boolean | object;
12+
mock: unknown;
13+
checkSecurity: boolean;
1414
validateRequest: boolean;
1515
validateResponse: boolean;
1616
}

‎packages/core/src/utils/security/handlers/bearer.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { fromNullable, getOrElse, map } from 'fp-ts/lib/Option';
22
import { pipe } from 'fp-ts/lib/pipeable';
3-
import { get } from 'lodash';
3+
import { get, partial } from 'lodash';
44
import { SecurityScheme } from './types';
55
import { when } from './utils';
66

@@ -18,15 +18,15 @@ function isBearerToken(inputHeaders: Headers) {
1818

1919
export const bearer = {
2020
test: ({ type, scheme }: SecurityScheme) => scheme === 'bearer' && type === 'http',
21-
handle: bearerHandler.bind(undefined, 'Bearer'),
21+
handle: partial(bearerHandler, 'Bearer'),
2222
};
2323

2424
export const oauth2 = {
2525
test: ({ type }: SecurityScheme) => type === 'oauth2',
26-
handle: bearerHandler.bind(undefined, 'OAuth2'),
26+
handle: partial(bearerHandler, 'OAuth2'),
2727
};
2828

2929
export const openIdConnect = {
3030
test: ({ type }: SecurityScheme) => type === 'openIdConnect',
31-
handle: bearerHandler.bind(undefined, 'OpenID'),
31+
handle: partial(bearerHandler, 'OpenID'),
3232
};

‎packages/http-server/src/__tests__/body-params-validation.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function instantiatePrism2(operations: IHttpOperation[]) {
1010
return createServer(operations, {
1111
components: { logger },
1212
cors: true,
13-
config: { validateRequest: true, validateResponse: true, mock: { dynamic: false } },
13+
config: { checkSecurity: true, validateRequest: true, validateResponse: true, mock: { dynamic: false } },
1414
});
1515
}
1616

‎packages/http-server/src/__tests__/server.oas.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import getHttpOperations from '@stoplight/prism-cli/src/util/getHttpOperations';
21
import { createLogger } from '@stoplight/prism-core';
2+
import { getHttpOperationsFromResource } from '@stoplight/prism-http';
33
import { resolve } from 'path';
44
import { createServer } from '../';
55
import { IPrismHttpServer } from '../types';
@@ -16,10 +16,10 @@ function checkErrorPayloadShape(payload: string) {
1616
}
1717

1818
async function instantiatePrism(specPath: string) {
19-
const operations = await getHttpOperations(specPath);
19+
const operations = await getHttpOperationsFromResource(specPath);
2020
const server = createServer(operations, {
2121
components: { logger },
22-
config: { validateRequest: true, validateResponse: true, mock: { dynamic: false } },
22+
config: { checkSecurity: true, validateRequest: true, validateResponse: true, mock: { dynamic: false } },
2323
cors: true,
2424
});
2525
return server;

‎packages/http-server/src/server.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const createServer = (operations: IHttpOperation[], opts: IPrismHttpServe
4040
mock: { dynamic: false },
4141
validateRequest: true,
4242
validateResponse: true,
43+
checkSecurity: true,
4344
});
4445

4546
const prism = createInstance(mergedConfig, components);
@@ -89,7 +90,7 @@ export const createServer = (operations: IHttpOperation[], opts: IPrismHttpServe
8990
const operationSpecificConfig = getHttpConfigFromRequest(input);
9091
const mockConfig = Object.assign({}, opts.config.mock, operationSpecificConfig);
9192

92-
const response = await prismInstance.process(input, operations, {
93+
const response = await prismInstance.request(input, operations, {
9394
...opts.config,
9495
mock: mockConfig,
9596
});

‎packages/http/README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Output
9797
9898
In the following example we will first instantiate Prism to make requests to an actual server.
9999
100-
Later we alter than behaviour by passing a config object to the `process` function.
100+
Later we alter than behaviour by passing a config object to the `request` function.
101101
102102
```javascript
103103
const Prism = require('@stoplight/prism-http');
@@ -138,7 +138,7 @@ const config = { mock: { dynamic: false } };
138138
const prism = Prism.createInstance(config);
139139
140140
return prism
141-
.process({
141+
.request({
142142
method: 'get',
143143
url: {
144144
path: '/facts',
@@ -249,7 +249,7 @@ const request = {
249249
path: '/path', // must be prefixed with slash
250250
},
251251
};
252-
const promise = prism.process(request, operations);
252+
const promise = prism.request(request, operations);
253253
```
254254
255255
The request object has the following interface
@@ -288,13 +288,13 @@ This will instruct Prism to do one of the two:
288288
289289
- if mocking is **disabled** (`{ mock: {dynamic: false} }`)
290290
- it will use that `baseUrl` to make a request to the server (`GET https://cat-fact.herokuapp.com/facts`)
291-
- it will **verify** whether the provided `baseUrl` matches any of the servers defined in your OpenAPI and **add an input warning to the .process return value if it is not valid**
291+
- it will **verify** whether the provided `baseUrl` matches any of the servers defined in your OpenAPI and **add an input warning to the .request return value if it is not valid**
292292
- if mocking is **enabled**
293293
- it will **verify** whether the provided `baseUrl` matches any of the servers defined in your OpenAPI and **return an error if it is not valid**
294294
295295
## Understanding response
296296
297-
The `prism.process` resolved (it's a Promise!) value consists of:
297+
The `prism.request` resolved (it's a Promise!) value consists of:
298298
299299
- input - copy of the request object you provided
300300
- output - the HTTP response

‎packages/http/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"node": ">=8"
1717
},
1818
"dependencies": {
19+
"abstract-logging": "^1.0.0",
1920
"@stoplight/prism-core": "^3.1.1",
2021
"accepts": "^1.3.7",
2122
"ajv": "^6.10.2",

‎packages/http/src/__tests__/__snapshots__/http-prism-instance.spec.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`Http Client .process given no-refs-petstore-minimal.oas2.json when processing GET /pet/findByStatus with valid query params returns generated body 1`] = `
3+
exports[`Http Client .request given no-refs-petstore-minimal.oas2.json when requesting GET /pet/findByStatus with valid query params returns generated body 1`] = `
44
Object {
55
"input": Object {
66
"method": "get",
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { createClientFromOperations } from '../client';
2+
import * as mock from '../mocker';
3+
import { IHttpConfig } from '../types';
4+
5+
describe('User Http Client', () => {
6+
describe('with mocking set to true', () => {
7+
describe('get a resource', () => {
8+
let client: ReturnType<typeof createClientFromOperations>;
9+
10+
const config: IHttpConfig = {
11+
mock: { dynamic: false },
12+
validateRequest: true,
13+
validateResponse: true,
14+
checkSecurity: true,
15+
};
16+
17+
beforeAll(async () => {
18+
jest.spyOn(mock, 'default');
19+
20+
client = await createClientFromOperations(
21+
[
22+
{
23+
id: 'operation',
24+
method: 'get',
25+
path: '/pet',
26+
servers: [
27+
{
28+
url: 'https://www.google.it',
29+
},
30+
],
31+
responses: [
32+
{
33+
code: '200',
34+
},
35+
],
36+
},
37+
],
38+
config,
39+
);
40+
41+
jest.spyOn(client, 'request');
42+
});
43+
44+
afterAll(() => jest.clearAllMocks());
45+
46+
describe('when calling with no options', () => {
47+
beforeAll(() => client.get('/pet'));
48+
49+
test('shall call the mocker with the default options', () =>
50+
expect(mock.default).toHaveBeenCalledWith({ input: expect.anything(), resource: expect.anything(), config }));
51+
52+
test('shall ultimately call the main request method with the current HTTP Method', () =>
53+
expect(client.request).toHaveBeenCalledWith('/pet', { method: 'get' }, undefined));
54+
});
55+
56+
describe('when overriding a config parameter on the request level', () => {
57+
beforeAll(() => client.get('/pet', { checkSecurity: false }));
58+
59+
test('shall call the mocker with the modified options', () =>
60+
expect(mock.default).toHaveBeenCalledWith({
61+
input: expect.anything(),
62+
resource: expect.anything(),
63+
config: { ...config, checkSecurity: false },
64+
}));
65+
});
66+
67+
describe('when calling a method with overridden url', () => {
68+
beforeAll(() => client.get('/pet', { baseUrl: 'https://www.google.it' }));
69+
70+
test('should call the mocker with the replaced full url', () => {
71+
expect(mock.default).toBeCalledWith({
72+
resource: expect.anything(),
73+
input: expect.objectContaining({
74+
data: expect.objectContaining({
75+
url: expect.objectContaining({
76+
baseUrl: 'https://www.google.it',
77+
path: '/pet',
78+
}),
79+
}),
80+
}),
81+
config: expect.anything(),
82+
});
83+
});
84+
});
85+
86+
describe('when calling a method with a full url', () => {
87+
beforeAll(() => client.get('https://www.google.it/pet'));
88+
89+
test('should call the mocker with the replaced full url', () => {
90+
expect(mock.default).toBeCalledWith({
91+
resource: expect.anything(),
92+
input: expect.objectContaining({
93+
data: expect.objectContaining({
94+
url: expect.objectContaining({
95+
baseUrl: 'https://www.google.it',
96+
path: '/pet',
97+
}),
98+
}),
99+
}),
100+
config: expect.anything(),
101+
});
102+
});
103+
});
104+
});
105+
});
106+
});

0 commit comments

Comments
 (0)