mirror of
https://github.com/postmanlabs/openapi-to-postman.git
synced 2022-11-29 22:05:00 +03:00
Added support for internal $ref resolution in validation flows
This commit is contained in:
@@ -3504,13 +3504,20 @@ module.exports = {
|
|||||||
components, options, schemaCache, callback) {
|
components, options, schemaCache, callback) {
|
||||||
// check for body modes
|
// check for body modes
|
||||||
// TODO: The application/json can be anything that's application/*+json
|
// TODO: The application/json can be anything that's application/*+json
|
||||||
let jsonSchemaBody = _.get(schemaPath, ['requestBody', 'content', 'application/json', 'schema']),
|
let jsonSchemaBody,
|
||||||
mismatchProperty = 'BODY';
|
mismatchProperty = 'BODY';
|
||||||
|
|
||||||
if (options.validationPropertiesToIgnore.includes(mismatchProperty)) {
|
if (options.validationPropertiesToIgnore.includes(mismatchProperty)) {
|
||||||
return callback(null, []);
|
return callback(null, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolve $ref in requestBody object if present
|
||||||
|
if (!_.isEmpty(_.get(schemaPath, 'requestBody.$ref'))) {
|
||||||
|
schemaPath.requestBody = this.getRefObject(schemaPath.requestBody.$ref, components, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSchemaBody = _.get(schemaPath, ['requestBody', 'content', 'application/json', 'schema']);
|
||||||
|
|
||||||
// only raw for now
|
// only raw for now
|
||||||
if (requestBody && requestBody.mode === 'raw' && jsonSchemaBody) {
|
if (requestBody && requestBody.mode === 'raw' && jsonSchemaBody) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -3589,6 +3596,19 @@ module.exports = {
|
|||||||
responsePathPrefix = 'default';
|
responsePathPrefix = 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolve $ref in response object if present
|
||||||
|
if (!_.isEmpty(_.get(thisSchemaResponse, '$ref'))) {
|
||||||
|
thisSchemaResponse = this.getRefObject(thisSchemaResponse.$ref, components, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve $ref in all header objects if present
|
||||||
|
_.forEach(_.get(thisSchemaResponse, 'headers'), (header) => {
|
||||||
|
if (header.hasOwnProperty('$ref')) {
|
||||||
|
_.assign(header, this.getRefObject(header.$ref, components, options));
|
||||||
|
_.unset(header, '$ref');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!thisSchemaResponse) {
|
if (!thisSchemaResponse) {
|
||||||
// still didn't find a response
|
// still didn't find a response
|
||||||
responseCallback(null);
|
responseCallback(null);
|
||||||
|
|||||||
@@ -447,6 +447,14 @@ class SchemaPack {
|
|||||||
pathVar.value = _.get(mappedPathVar, 'value', pathVar.value);
|
pathVar.value = _.get(mappedPathVar, 'value', pathVar.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// resolve $ref in all parameter objects if present
|
||||||
|
_.forEach(_.get(matchedPath, 'path.parameters'), (param) => {
|
||||||
|
if (param.hasOwnProperty('$ref')) {
|
||||||
|
_.assign(param, schemaUtils.getRefObject(param.$ref, componentsAndPaths, options));
|
||||||
|
_.unset(param, '$ref');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
matchedEndpoints.push(matchedPath.jsonPath);
|
matchedEndpoints.push(matchedPath.jsonPath);
|
||||||
// 3. validation involves checking these individual properties
|
// 3. validation involves checking these individual properties
|
||||||
async.parallel({
|
async.parallel({
|
||||||
|
|||||||
175
test/data/validationData/internalRefsCollection.json
Normal file
175
test/data/validationData/internalRefsCollection.json
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
{
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"id": "9b8ff406-3176-49ca-b91a-e99277130d40",
|
||||||
|
"name": "searches inventory",
|
||||||
|
"request": {
|
||||||
|
"name": "searches inventory",
|
||||||
|
"description": {
|
||||||
|
"content": "By passing in the appropriate options, you can search for\navailable inventory in the system\n",
|
||||||
|
"type": "text/plain"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"path": [
|
||||||
|
"inventory",
|
||||||
|
":searchString"
|
||||||
|
],
|
||||||
|
"host": [
|
||||||
|
"{{baseUrl}}"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"description": "number of records to skip for pagination",
|
||||||
|
"key": "skip",
|
||||||
|
"value": "71616628"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"description": "pass an optional search string for looking up inventory",
|
||||||
|
"type": "any",
|
||||||
|
"value": "magna",
|
||||||
|
"key": "searchString"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"description": "maximum number of records to return",
|
||||||
|
"key": "limit",
|
||||||
|
"value": "25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"method": "POST",
|
||||||
|
"auth": null,
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"id\": \"d290f1ee-6c54-4b01-90e6-d701748f0851\",\n \"name\": \"Widget Adapter\",\n \"manufacturer\": {\n \"name\": \"ACME Corporation\",\n \"homePage\": \"https://www.acme-corp.com\",\n \"phone\": \"408-867-5309\"\n },\n \"releaseDate\": \"2016-08-29T09:12:33.001Z\"\n}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"id": "103e959b-3d59-4a3b-9a0f-eef9206733e9",
|
||||||
|
"name": "An array of profiles",
|
||||||
|
"originalRequest": {
|
||||||
|
"url": {
|
||||||
|
"path": [
|
||||||
|
"inventory",
|
||||||
|
":searchString"
|
||||||
|
],
|
||||||
|
"host": [
|
||||||
|
"{{baseUrl}}"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "skip",
|
||||||
|
"value": "<integer>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"type": "any",
|
||||||
|
"key": "searchString"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"description": "maximum number of records to return",
|
||||||
|
"key": "limit",
|
||||||
|
"value": "<integer>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"method": "POST",
|
||||||
|
"body": {}
|
||||||
|
},
|
||||||
|
"status": "OK",
|
||||||
|
"code": 200,
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"description": "The date.",
|
||||||
|
"key": "x-date",
|
||||||
|
"value": "2012-06-15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": "{\n \"id\": \"urn:uuid:986f1de0-b002-2c4e-63a7-724ac665365b\",\n \"name\": \"ioneyed\",\n \"given_name\": \"Robert\",\n \"family_name\": \"Buchanan\"\n}",
|
||||||
|
"cookie": [],
|
||||||
|
"_postman_previewlanguage": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7e2062e0-d928-4ae5-924b-858d3bcce494",
|
||||||
|
"name": "The user is unauthorized for this action",
|
||||||
|
"originalRequest": {
|
||||||
|
"url": {
|
||||||
|
"path": [
|
||||||
|
"inventory",
|
||||||
|
":searchString"
|
||||||
|
],
|
||||||
|
"host": [
|
||||||
|
"{{baseUrl}}"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "skip",
|
||||||
|
"value": "<integer>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"type": "any",
|
||||||
|
"key": "searchString"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"description": "maximum number of records to return",
|
||||||
|
"key": "limit",
|
||||||
|
"value": "<integer>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"method": "POST",
|
||||||
|
"body": {}
|
||||||
|
},
|
||||||
|
"status": "Unauthorized",
|
||||||
|
"code": 401,
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": "{\n \"code\": \"PROFILE-108-401\",\n \"message\": \"you do not have appropriate credentials\"\n}",
|
||||||
|
"cookie": [],
|
||||||
|
"_postman_previewlanguage": "json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event": [],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"id": "baseUrl",
|
||||||
|
"type": "string",
|
||||||
|
"value": "https://example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "758b4b2c-df93-4250-a287-6e526f194a75",
|
||||||
|
"name": "Simple Inventory API",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
|
"description": {
|
||||||
|
"content": "This is a simple API\n\nContact Support:\n Email: you@your-company.com",
|
||||||
|
"type": "text/plain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
188
test/data/validationData/internalRefsSpec.yaml
Normal file
188
test/data/validationData/internalRefsSpec.yaml
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
servers:
|
||||||
|
- description: Internal $refs
|
||||||
|
url: https://example.com
|
||||||
|
info:
|
||||||
|
description: This is a simple API
|
||||||
|
version: "1.0.0"
|
||||||
|
title: Simple Inventory API
|
||||||
|
contact:
|
||||||
|
email: you@your-company.com
|
||||||
|
license:
|
||||||
|
name: Apache 2.0
|
||||||
|
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
||||||
|
paths:
|
||||||
|
/inventory/{searchString}:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/searchString'
|
||||||
|
post:
|
||||||
|
summary: searches inventory
|
||||||
|
operationId: searchInventory
|
||||||
|
description: |
|
||||||
|
By passing in the appropriate options, you can search for
|
||||||
|
available inventory in the system
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/skip'
|
||||||
|
- $ref: '#/components/parameters/limit'
|
||||||
|
requestBody:
|
||||||
|
$ref: '#/components/requestBodies/inventoryBody'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'An array of profiles'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Profile'
|
||||||
|
headers:
|
||||||
|
x-date:
|
||||||
|
$ref: '#/components/headers/x-date'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
components:
|
||||||
|
headers:
|
||||||
|
x-date:
|
||||||
|
description: The date.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
parameters:
|
||||||
|
limit:
|
||||||
|
in: header
|
||||||
|
name: limit
|
||||||
|
description: maximum number of records to return
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
minimum: 0
|
||||||
|
maximum: 50
|
||||||
|
searchString:
|
||||||
|
in: path
|
||||||
|
name: searchString
|
||||||
|
description: pass an optional search string for looking up inventory
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SearchString'
|
||||||
|
skip:
|
||||||
|
in: query
|
||||||
|
name: skip
|
||||||
|
description: number of records to skip for pagination
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
minimum: 0
|
||||||
|
requestBodies:
|
||||||
|
inventoryBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InventoryItem'
|
||||||
|
description: Inventory item to add
|
||||||
|
responses:
|
||||||
|
UnexpectedError:
|
||||||
|
description: An unexpected error has occured
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
example:
|
||||||
|
code: 'PROFILE-107-500'
|
||||||
|
message: 'fixing problems'
|
||||||
|
Unauthorized:
|
||||||
|
description: The user is unauthorized for this action
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
example:
|
||||||
|
code: 'PROFILE-108-401'
|
||||||
|
message: 'you do not have appropriate credentials'
|
||||||
|
Forbidden:
|
||||||
|
description: The user in forbidden from completing that action
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
example:
|
||||||
|
code: 'PROFILE-109-403'
|
||||||
|
message: 'thou shall not pass for you are forbidden'
|
||||||
|
NotFound:
|
||||||
|
description: The resource was not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
example:
|
||||||
|
code: 'PROFILE-110-404'
|
||||||
|
message: 'resource was not found'
|
||||||
|
schemas:
|
||||||
|
SearchString:
|
||||||
|
type: string
|
||||||
|
InventoryItem:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- manufacturer
|
||||||
|
- releaseDate
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
example: d290f1ee-6c54-4b01-90e6-d701748f0851
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Widget Adapter
|
||||||
|
releaseDate:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
example: '2016-08-29T09:12:33.001Z'
|
||||||
|
manufacturer:
|
||||||
|
$ref: '#/components/schemas/Manufacturer'
|
||||||
|
Manufacturer:
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: ACME Corporation
|
||||||
|
homePage:
|
||||||
|
type: string
|
||||||
|
format: url
|
||||||
|
example: 'https://www.acme-corp.com'
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
example: 408-867-5309
|
||||||
|
type: object
|
||||||
|
Profile:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- given_name
|
||||||
|
- family_name
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: "ioneyed"
|
||||||
|
given_name:
|
||||||
|
type: string
|
||||||
|
example: "Robert"
|
||||||
|
family_name:
|
||||||
|
type: string
|
||||||
|
example: "Buchanan"
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
pattern: 'PROFILE-\d{3}-\d{3}'
|
||||||
|
example: "PROFILE-100-507"
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "we have run out of storage; this is embarrassing, and someone have been paged"
|
||||||
@@ -480,5 +480,40 @@ describe('VALIDATE FUNCTION TESTS ', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should correctly handle internal $ref when present', function (done) {
|
||||||
|
let internalRefsSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
||||||
|
'/internalRefsSpec.yaml'), 'utf-8'),
|
||||||
|
internalRefsCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
|
||||||
|
'/internalRefsCollection.json'), 'utf-8'),
|
||||||
|
resultObj,
|
||||||
|
options = {
|
||||||
|
showMissingInSchemaErrors: true,
|
||||||
|
strictRequestMatching: true,
|
||||||
|
ignoreUnresolvedVariables: true,
|
||||||
|
validateMetadata: true,
|
||||||
|
suggestAvailableFixes: true,
|
||||||
|
detailedBlobValidation: false
|
||||||
|
},
|
||||||
|
historyRequest = [],
|
||||||
|
schemaPack = new Converter.SchemaPack({ type: 'string', data: internalRefsSpec }, options);
|
||||||
|
|
||||||
|
getAllTransactions(JSON.parse(internalRefsCollection), historyRequest);
|
||||||
|
|
||||||
|
schemaPack.validateTransaction(historyRequest, (err, result) => {
|
||||||
|
expect(err).to.be.null;
|
||||||
|
expect(result).to.be.an('object');
|
||||||
|
resultObj = result.requests[historyRequest[0].id].endpoints[0];
|
||||||
|
|
||||||
|
// no mismatches should be found when resolved correctly
|
||||||
|
expect(resultObj.matched).to.be.true;
|
||||||
|
expect(resultObj.mismatches).to.have.lengthOf(0);
|
||||||
|
_.forEach(resultObj.responses, (response) => {
|
||||||
|
expect(response.matched).to.be.true;
|
||||||
|
expect(response.mismatches).to.have.lengthOf(0);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user