Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Postman Collection support #985

Merged
merged 54 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1d78f3e
feat: Postman Collection support
karol-maciaszek Feb 14, 2020
fe4247c
Merge commit 'fced5a71f40f70977dfea16c682e44e36851d326' into feat/pos…
karol-maciaszek Feb 19, 2020
ae72d72
Merge branch 'master' into feat/postman-collection
XVincentX Feb 20, 2020
6568745
chore: yarn.lock
XVincentX Feb 20, 2020
53c7c83
chore: upgrade http-spec
XVincentX Feb 20, 2020
0aa90b3
test: adjusted tests to recent http-spec
karol-maciaszek Feb 24, 2020
3913ab2
test: yet another test adjustment
karol-maciaszek Feb 24, 2020
d51ee63
test: fixed await usage
karol-maciaszek Feb 24, 2020
442f94b
test: harness adjustment for recent http-spec
karol-maciaszek Feb 24, 2020
988b209
test: added tests for getHttpOperation
karol-maciaszek Feb 24, 2020
b21a849
test: content negotiation harness tests
karol-maciaszek Feb 25, 2020
80d9a4f
test: more harness tests for Postman Collection
karol-maciaszek Feb 26, 2020
2559498
test: host not needed
karol-maciaszek Feb 26, 2020
e188474
test: harness for empty responses and head requests
karol-maciaszek Feb 26, 2020
d9fa3bd
test: harness for routing
karol-maciaszek Feb 26, 2020
056cbd8
test: harness for security part 1
karol-maciaszek Feb 26, 2020
d882de9
test: harness for security part 2
karol-maciaszek Feb 27, 2020
716cbf1
test: harness for security part 3
karol-maciaszek Feb 27, 2020
53009a5
test: harness for security part 4
karol-maciaszek Feb 27, 2020
ba32d5e
test: harness for security part 5
karol-maciaszek Feb 27, 2020
52baf62
test: harness for security part 5.1
karol-maciaszek Feb 27, 2020
99b6c7b
test: rest of harness tests
karol-maciaszek Mar 2, 2020
bb49c18
fix: fixed detection method for postman collection
karol-maciaszek Mar 2, 2020
68f7d9b
Merge branch 'master' into feat/postman-collection
karol-maciaszek Mar 5, 2020
fcc1f23
chore: updated http-spec ver
karol-maciaszek Mar 5, 2020
677a8ae
docs: changlelog line
karol-maciaszek Mar 5, 2020
6f25322
chore: updated types
karol-maciaszek Mar 5, 2020
5a7ca72
fix: addressed review comments
karol-maciaszek Mar 6, 2020
a2b60a3
refactor: no await for assertResolvesLeft (#1022)
XVincentX Mar 6, 2020
2e5bb84
fix: async bye bye
karol-maciaszek Mar 6, 2020
5c59896
fix: removed unnecessary asserts from harness
karol-maciaszek Mar 6, 2020
b9e1810
test: another batch of fixes
karol-maciaszek Mar 6, 2020
6f964cd
test: added/fixed a harness case
karol-maciaszek Mar 6, 2020
03025d1
test: another batch of harness fixes
karol-maciaszek Mar 6, 2020
aa8f930
test: last fix-pack
karol-maciaszek Mar 6, 2020
1792b24
refactor: remove keep alive check
XVincentX Mar 7, 2020
c1c8b43
docs: add postman-collection limitations
XVincentX Mar 8, 2020
8229be7
test: fixed harness test description
karol-maciaszek Mar 9, 2020
dabd393
test: removed duplicated harness test
karol-maciaszek Mar 9, 2020
7c7d5e6
test: added harness case with prefer header
karol-maciaszek Mar 9, 2020
0f30b72
chore: upgrade http-spec
XVincentX Mar 9, 2020
e2d8300
docs: remove items and folders section
XVincentX Mar 9, 2020
e03d32d
Update CHANGELOG.md
XVincentX Mar 9, 2020
f6782eb
docs: remove disabled section
XVincentX Mar 9, 2020
b1db4ed
chore: remove reqeust body part
XVincentX Mar 9, 2020
b25432c
chore: reword events
XVincentX Mar 9, 2020
86bf0dc
docs: simplify auth limitation
XVincentX Mar 9, 2020
4cc8986
chore: remove postman links
XVincentX Mar 9, 2020
cf6d47b
Merge branch 'master' into feat/postman-collection
XVincentX Mar 9, 2020
ad18667
Merge branch 'master' into feat/postman-collection
XVincentX Mar 9, 2020
a8324ac
docs: clarify we do not support global variables
XVincentX Mar 9, 2020
b3a5f96
docs: add basic flow
XVincentX Mar 9, 2020
73f4602
chore: 3.3.0
XVincentX Mar 10, 2020
398c9e6
v3.3.0
XVincentX Mar 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

# Unreleased

# 3.3.0 (2020-03-10)

## Added

- Prism now supports sending its configuration parameters through the `Prefer` header [#984](https://github.com/stoplightio/prism/pull/984)
- Experimental Postman Collection support [#985](https://github.com/stoplightio/prism/pull/985)

# 3.2.9 (2020-02-19)

Expand Down
100 changes: 100 additions & 0 deletions docs/guides/postman-collection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Postman Collections support

Prism offers a _limited_ support for Postman Collection. The basic workflow of using Prism (both from the CLI and deployed in a Docker container) are fundamentally the same:

```bash
prism mock https://raw.githubusercontent.com/postmanlabs/postman-collection/develop/examples/collection-v2.json

info GET http://127.0.0.1:4010/status/200
info POST http://127.0.0.1:4010/post
info PUT http://127.0.0.1:4010/status/201
info GET http://127.0.0.1:4010/post
info GET http://127.0.0.1:4010/status/400/
info GET http://127.0.0.1:4010/path/to/document
start Prism is listening on http://127.0.0.1:4010
```

…and then use the trusty curl to try out the server:

```bash
curl -s -D "/dev/stderr" http://127.0.0.1:4010/status/200

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
authorization: Hawk id="dh37fgj492je", ts="1448549987", nonce="eOJZCd", mac="O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k="
set-cookie: null
Content-Length: 15
Date: Mon, 09 Mar 2020 19:19:31 GMT
Connection: keep-alive

"response body"%
```

For a complete CLI overview, please see the [CLI page](../getting-started/03-cli.md)

## Known limitations

There are some known limitations that it's important to keep in mind:

### Authentication

Postman supports a few authentication schemes which OpenAPI does not, such as Hawk and AWS. In this case Prism will do a simplified version of security validation, and just check you've got the right headers populated.

### Events

Events are linked to the Postman hosted platform, and so Prism is going to skip this section entirely.

For this reason, if some of these event handlers have scripts that modify a local variable (globals one are ignored by Prism since they are not tracked in the Postman Application and not the singular Postman Collection) used in some of the request/response pair, Prism won't be aware of these changes.

### JSON Schema Generation

OpenAPI 2 and 3 are schema driven, while Postman Collections are examples driven.

Essentially, while in OpenAPI 2/3 you can either define an example request/response body OR define a JSON Schema to describe how the payload will look like, in a Postman Collections you can only define examples.

For such reason, Prism will _try_ to infer a JSON Schema from an example, and use such one to perform request/response validation as well as examples generation in case the it is running in `dynamic` mode.

Infer a JSON Schema from a payload is an operation usually complicated — and there's no way a perfect result will be achieved. We can cover the base cases, but keep in mind you might get validation errors that are legit.

To give an example, this JSON object:

```json
{
"name": "Bugatti Veyron",
"type": "Sport Car",
"speed": 3000
}
```

will likely generate this schema:

```json
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"speed": {
"type": "number"
}
}
}
```

Although the result seems good:

- There is no way to understand whether all or some of the properties are required or not, so they'll all be marked as optionals.
- There is no way to understand whether a type is indeed an `enum` instead of a regular `string`. In this specific example, maybe the author's intention is that type is either `Sport Car` or `SUV`
- There is no way to understand whether a type can be something stricter; for example, maybe the author's intention was to mark the `speed` property as `integer` instead of `number`.

### Operations Merging

Postman Collections allows to define the same response multiple times, as long they differ for the returned response type, payload, examples. Prism will try to merge all these definitions in a single operation and then selecting the appropriate example based on its internal negotiator.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "3.2.9",
"version": "3.3.0",
"independent": false
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@types/node": "^13.1.1",
"@types/node-fetch": "2.5.4",
"@types/pino": "^5.8.11",
"@types/postman-collection": "^3.5.0",
"@types/raw-body": "^2.3.0",
"@types/signale": "^1.2.1",
"@types/split2": "^2.1.6",
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "@stoplight/prism-cli",
"version": "3.2.9",
"version": "3.3.0",
"author": "Stoplight <[email protected]>",
"bin": {
"prism": "./dist/index.js"
},
"bugs": "https://github.com/stoplightio/prism/issues",
"dependencies": {
"@stoplight/prism-core": "^3.2.9",
"@stoplight/prism-http-server": "^3.2.9",
"@stoplight/prism-core": "^3.3.0",
"@stoplight/prism-http-server": "^3.3.0",
"chalk": "^3.0.0",
"chokidar": "^3.2.1",
"fp-ts": "^2.1.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stoplight/prism-core",
"version": "3.2.9",
"version": "3.3.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": "Stoplight <[email protected]>",
Expand Down
6 changes: 3 additions & 3 deletions packages/http-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stoplight/prism-http-server",
"version": "3.2.9",
"version": "3.3.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": "Stoplight <[email protected]>",
Expand All @@ -19,8 +19,8 @@
"access": "public"
},
"dependencies": {
"@stoplight/prism-core": "^3.2.9",
"@stoplight/prism-http": "^3.2.9",
"@stoplight/prism-core": "^3.3.0",
"@stoplight/prism-http": "^3.3.0",
"fast-xml-parser": "^3.12.20",
"fp-ts": "^2.1.1",
"io-ts": "^2.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ paths:
"/pet":
get:
responses:
'200': {}
'200':
description: test
XVincentX marked this conversation as resolved.
Show resolved Hide resolved
servers:
- url: "{schema}://{host}/{basePath}"
variables:
Expand Down
16 changes: 7 additions & 9 deletions packages/http-server/src/__tests__/server.oas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,10 @@ describe('GET /pet?__server', () => {
afterEach(() => server.close());

describe.each([['http://stoplight.io/api'], ['https://stoplight.io/api']])('valid server %s', serverUrl => {
it('returns 200', () => {
return expect(requestPetGivenServer(serverUrl)).resolves.toMatchObject({
it('returns 200', () =>
expect(requestPetGivenServer(serverUrl)).resolves.toMatchObject({
status: 200,
});
});
}));
});

describe.each([['https://stoplight.com/api'], ['https://google.com/api'], ['https://stopligt.io/v1']])(
Expand Down Expand Up @@ -115,16 +114,15 @@ describe.each([[oas2File], [oas3File]])('server %s', file => {
const response = await makeRequest('/pets/123?__code=404');

expect(response.status).toBe(404);
expect(response.text()).resolves.toBe('');
return expect(response.text()).resolves.toBe('');
});

it('will return requested error response with payload', async () => {
const response = await makeRequest('/pets/123?__code=418');

expect(response.status).toBe(418);

const payload = await response.json();
expect(payload).toHaveProperty('name');
return expect(response.json()).resolves.toHaveProperty('name');
});

it('returns 404 with error when a non-existent example is requested', async () => {
Expand Down Expand Up @@ -242,15 +240,15 @@ describe.each([[oas2File], [oas3File]])('server %s', file => {
it(`when the server is not valid for this exact operation then return error`, async () => {
const response = await makeRequest('/store/inventory?__server=https://petstore.swagger.io/v2');
expect(response.status).toBe(404);
expect(response.text()).resolves.toEqual(
return expect(response.text()).resolves.toEqual(
'{"type":"https://stoplight.io/prism/errors#NO_SERVER_MATCHED_ERROR","title":"Route not resolved, no server matched","status":404,"detail":"The server url https://petstore.swagger.io/v2 hasn\'t been matched with any of the provided servers"}'
);
});

it(`when the server is invalid return error`, async () => {
const response = await makeRequest('/store/inventory?__server=https://notvalid.com');
expect(response.status).toBe(404);
expect(response.text()).resolves.toEqual(
return expect(response.text()).resolves.toEqual(
'{"type":"https://stoplight.io/prism/errors#NO_SERVER_MATCHED_ERROR","title":"Route not resolved, no server matched","status":404,"detail":"The server url https://notvalid.com hasn\'t been matched with any of the provided servers"}'
);
});
Expand Down
6 changes: 3 additions & 3 deletions packages/http/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stoplight/prism-http",
"version": "3.2.9",
"version": "3.3.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": "Stoplight <[email protected]>",
Expand All @@ -16,11 +16,11 @@
"node": ">=8"
},
"dependencies": {
"@stoplight/http-spec": "^2.2.3",
"@stoplight/http-spec": "^2.8.2",
"@stoplight/json": "^3.1.2",
"@stoplight/json-ref-readers": "^1.1.1",
"@stoplight/json-ref-resolver": "^3.0.1",
"@stoplight/prism-core": "^3.2.9",
"@stoplight/prism-core": "^3.3.0",
"@stoplight/yaml": "^3.0.2",
"abstract-logging": "^2.0.0",
"accepts": "^1.3.7",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"/pet": {
"get": {
"responses": {
"200": {}
"200": {
"description": "test"
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"/pet": {
"get": {
"responses": {
"200": {}
"200": {
"description": "test"
}
}
}
},
Expand All @@ -21,7 +23,9 @@
],
"get": {
"responses": {
"200": {}
"200": {
"description": "test"
karol-maciaszek marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Expand Down
84 changes: 84 additions & 0 deletions packages/http/src/__tests__/getHttpOperations.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import getHttpOperations from '../getHttpOperations';

describe('getHttpOperations()', () => {
describe('ref resolving fails', () => {
it('fails with exception', () => {
return expect(
getHttpOperations(
JSON.stringify({
openapi: '3.0.0',
paths: { $ref: 'abc://' },
})
)
).rejects.toThrow(
/^There's been an error while trying to resolve external references in your document: Error: EISDIR: illegal operation on a directory, read$/
);
});

it('deduplicates similar errors', () => {
return expect(
getHttpOperations(
JSON.stringify({
openapi: '3.0.0',
paths: { $ref: 'abc://' },
definitions: { $ref: 'abc://' },
})
)
).rejects.toThrow(
/^There's been an error while trying to resolve external references in your document: Error: EISDIR: illegal operation on a directory, read$/
);
});
});

describe('ref resolving succeeds', () => {
describe('OpenAPI 2 document is provided', () => {
it('detects it properly', () => {
return expect(getHttpOperations(JSON.stringify({ swagger: '2.0' }))).resolves.toBeTruthy();
});
});

describe('OpenAPI 3 document is provided', () => {
it('detects it properly', () => {
return expect(getHttpOperations(JSON.stringify({ openapi: '3.0.0' }))).resolves.toBeTruthy();
});

it('returns correct HttpOperation', () => {
return expect(
getHttpOperations(
JSON.stringify({
openapi: '3.0.0',
paths: {
'/pet': { get: { responses: { 200: { description: 'test' } } } },
},
})
)
).resolves.toEqual([
expect.objectContaining({
method: 'get',
path: '/pet',
responses: [
{
code: '200',
contents: [],
description: 'test',
headers: [],
},
],
}),
]);
});
});

describe('Postman Collection document is provided', () => {
it('detects it properly', () => {
return expect(getHttpOperations(JSON.stringify({ info: { name: 'Test' }, item: [] }))).resolves.toBeTruthy();
});
});

describe('unknown document is provided', () => {
it('throws error', () => {
return expect(getHttpOperations(JSON.stringify({}))).rejects.toThrow(/^Unsupported document format$/);
});
});
});
});
Loading