From 78c1c8371346c131acd37378401e1d1dd1030aed Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Tue, 21 Jan 2020 16:57:04 -0800 Subject: [PATCH 01/11] Log decoration - preliminary work: - Building out 'findOperation' class method on OAS. Enables ingestion of an log, and combines it with the existing oas spec to determine the target path --- package.json | 1 + src/lib/get-user-variable.js | 6 +-- src/oas.js | 83 ++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5bb21c33..e23fae5d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "node-status": "^1.0.0", "oas-normalize": "1.0.0", "open": "^7.0.0", + "path-to-regexp": "^6.1.0", "prompt-sync": "^4.1.4", "request": "^2.88.0", "swagger-inline": "3.0.0", diff --git a/src/lib/get-user-variable.js b/src/lib/get-user-variable.js index ecc70bb5..e1260796 100644 --- a/src/lib/get-user-variable.js +++ b/src/lib/get-user-variable.js @@ -2,7 +2,7 @@ function getKey(user, property) { return user[property] || null; } -function getUserVariable(user, property, selectedApp = false) { +module.exports = function getUserVariable(user, property, selectedApp = false) { if (user.keys) { if (selectedApp) { return getKey( @@ -15,6 +15,4 @@ function getUserVariable(user, property, selectedApp = false) { } return getKey(user, property); -} - -module.exports = getUserVariable; +}; diff --git a/src/oas.js b/src/oas.js index c9b2e908..5bd04326 100644 --- a/src/oas.js +++ b/src/oas.js @@ -1,4 +1,5 @@ /* eslint-disable max-classes-per-file */ +const { pathToRegexp, match } = require('path-to-regexp'); const getPathOperation = require('./lib/get-path-operation'); const getUserVariable = require('./lib/get-user-variable'); @@ -101,6 +102,71 @@ function normalizedUrl(oas) { return ensureProtocol(url); } +function normalizePath(path) { + const curlBacketMatch = /{(.*?)}/; + + return path + .split('/') + .map(p => { + const pathMatch = curlBacketMatch.exec(p); + if (pathMatch) return `:${pathMatch[1]}`; + return p; + }) + .join('/'); +} + +function generatePathMatches(paths, pathName) { + return Object.keys(paths) + .map(path => { + const cleanedPath = normalizePath(path); + const matchStatement = match(cleanedPath, { decode: decodeURIComponent }); + const matchResult = matchStatement(pathName); + + return { + path, + ref: paths[path], + match: matchResult, + params: matchResult && Object.keys(matchResult.params).length ? matchResult.params : {}, + }; + }) + .filter(p => p.match); +} + +function filterPathMethods(pathMatches, targetMethod) { + const regExp = pathToRegexp(targetMethod); + return pathMatches + .map(p => { + const captures = Object.keys(p.ref).filter(r => regExp.exec(r)); + + if (captures.length) { + const method = captures[0]; + return { + path: p.path, + ref: p.ref[method], + params: p.params, + }; + } + return undefined; + }) + .filter(p => p); +} + +function findTargetPath(pathMatches) { + let minCount = Object.keys(pathMatches[0].params).length; + let candidate; + + for (let m = 0; m < pathMatches.length; m += 1) { + const selection = pathMatches[m]; + const paramCount = Object.keys(selection.params).length; + if (paramCount <= minCount) { + minCount = paramCount; + candidate = selection; + } + } + + return candidate; +} + class Oas { constructor(oas, user) { Object.assign(this, oas); @@ -128,6 +194,23 @@ class Oas { const operation = getPathOperation(this, { swagger: { path }, api: { method } }); return new Operation(this, path, method, operation); } + + findOperation(logInput) { + const { url, method } = logInput.request.log.entries[0].request; + const { origin, pathname } = new URL(url); + const { servers, paths } = this; + + const targetServer = servers.find(s => s.url === origin); + if (!targetServer) return undefined; + + const annotatedPaths = generatePathMatches(paths, pathname); + if (!annotatedPaths.length) return undefined; + + const includesMethod = filterPathMethods(annotatedPaths, method); + if (!includesMethod.length) return undefined; + + return findTargetPath(includesMethod); + } } module.exports = Oas; From 701ef965e2f80b324d5cf28460e4f75abd2f9d72 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Tue, 21 Jan 2020 20:24:20 -0800 Subject: [PATCH 02/11] modified res payload with built out URL key --- src/oas.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/oas.js b/src/oas.js index 5bd04326..47926764 100644 --- a/src/oas.js +++ b/src/oas.js @@ -115,18 +115,28 @@ function normalizePath(path) { .join('/'); } -function generatePathMatches(paths, pathName) { +function generatePathMatches(paths, pathName, origin) { return Object.keys(paths) .map(path => { const cleanedPath = normalizePath(path); const matchStatement = match(cleanedPath, { decode: decodeURIComponent }); const matchResult = matchStatement(pathName); + const slugs = {}; + + if (matchResult && Object.keys(matchResult.params).length) { + Object.keys(matchResult.params).forEach(param => { + slugs[`:${param}`] = matchResult.params[param]; + }); + } return { - path, + url: { + origin, + path: cleanedPath, + slugs, + }, ref: paths[path], match: matchResult, - params: matchResult && Object.keys(matchResult.params).length ? matchResult.params : {}, }; }) .filter(p => p.match); @@ -141,9 +151,8 @@ function filterPathMethods(pathMatches, targetMethod) { if (captures.length) { const method = captures[0]; return { - path: p.path, + url: p.url, ref: p.ref[method], - params: p.params, }; } return undefined; @@ -152,12 +161,12 @@ function filterPathMethods(pathMatches, targetMethod) { } function findTargetPath(pathMatches) { - let minCount = Object.keys(pathMatches[0].params).length; + let minCount = Object.keys(pathMatches[0].url.slugs).length; let candidate; for (let m = 0; m < pathMatches.length; m += 1) { const selection = pathMatches[m]; - const paramCount = Object.keys(selection.params).length; + const paramCount = Object.keys(selection.url.slugs).length; if (paramCount <= minCount) { minCount = paramCount; candidate = selection; @@ -203,7 +212,7 @@ class Oas { const targetServer = servers.find(s => s.url === origin); if (!targetServer) return undefined; - const annotatedPaths = generatePathMatches(paths, pathname); + const annotatedPaths = generatePathMatches(paths, pathname, origin); if (!annotatedPaths.length) return undefined; const includesMethod = filterPathMethods(annotatedPaths, method); From 5a5469cd17247682943cdbda44f3611bbfca7e7a Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Wed, 22 Jan 2020 10:41:24 -0800 Subject: [PATCH 03/11] Minor improvements suggested by @dok in PR comments: - More robust regex for path matching - findOperation more agnostic --- src/oas.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/oas.js b/src/oas.js index 47926764..ba096d50 100644 --- a/src/oas.js +++ b/src/oas.js @@ -103,16 +103,7 @@ function normalizedUrl(oas) { } function normalizePath(path) { - const curlBacketMatch = /{(.*?)}/; - - return path - .split('/') - .map(p => { - const pathMatch = curlBacketMatch.exec(p); - if (pathMatch) return `:${pathMatch[1]}`; - return p; - }) - .join('/'); + return path.replace(/{(.*?)}/g, ':$1'); } function generatePathMatches(paths, pathName, origin) { @@ -204,8 +195,7 @@ class Oas { return new Operation(this, path, method, operation); } - findOperation(logInput) { - const { url, method } = logInput.request.log.entries[0].request; + findOperation(url, method) { const { origin, pathname } = new URL(url); const { servers, paths } = this; From 0ef815744d1c66c43cab39902ab5d3bba5363423 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Wed, 22 Jan 2020 19:42:56 -0800 Subject: [PATCH 04/11] Prelim functionality operation class getHeaders method --- src/oas.js | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/oas.js b/src/oas.js index ba096d50..7c01379a 100644 --- a/src/oas.js +++ b/src/oas.js @@ -66,6 +66,31 @@ class Operation { return prev; }, {}); } + + getHeaders() { + this.headers = { + request: [], + response: [], + }; + + const security = this.prepareSecurity(); + if (security.Header && security.Header.length) { + this.headers.request = security.Header.map(h => h.name); + } + + if (this.parameters) { + this.headers.request = this.headers.request.concat( + this.parameters.filter(p => p.in === 'header').map(p => p.name) + ); + } + + this.headers.response = Object.keys(this.responses) + .filter(r => this.responses[r].headers) + .map(r => Object.keys(this.responses[r].headers)) + .reduce((a, b) => a.concat(b), []); + + return this.headers; + } } function ensureProtocol(url) { @@ -126,7 +151,7 @@ function generatePathMatches(paths, pathName, origin) { path: cleanedPath, slugs, }, - ref: paths[path], + operation: paths[path], match: matchResult, }; }) @@ -137,13 +162,15 @@ function filterPathMethods(pathMatches, targetMethod) { const regExp = pathToRegexp(targetMethod); return pathMatches .map(p => { - const captures = Object.keys(p.ref).filter(r => regExp.exec(r)); + const captures = Object.keys(p.operation).filter(r => regExp.exec(r)); if (captures.length) { const method = captures[0]; + p.url.method = method.toUpperCase(); + return { url: p.url, - ref: p.ref[method], + operation: p.operation[method], }; } return undefined; @@ -151,20 +178,23 @@ function filterPathMethods(pathMatches, targetMethod) { .filter(p => p); } -function findTargetPath(pathMatches) { +function findTargetPath(pathMatches, index) { let minCount = Object.keys(pathMatches[0].url.slugs).length; - let candidate; + let logOperation; for (let m = 0; m < pathMatches.length; m += 1) { const selection = pathMatches[m]; const paramCount = Object.keys(selection.url.slugs).length; if (paramCount <= minCount) { minCount = paramCount; - candidate = selection; + logOperation = selection; } } - return candidate; + return { + logOperation, + index, + }; } class Oas { @@ -195,7 +225,7 @@ class Oas { return new Operation(this, path, method, operation); } - findOperation(url, method) { + findOperation(url, method, index = 0) { const { origin, pathname } = new URL(url); const { servers, paths } = this; @@ -208,7 +238,7 @@ class Oas { const includesMethod = filterPathMethods(annotatedPaths, method); if (!includesMethod.length) return undefined; - return findTargetPath(includesMethod); + return findTargetPath(includesMethod, index); } } From c8bb4372c6724f25fa42f43423c46e1adeb4b050 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 23 Jan 2020 09:37:21 -0800 Subject: [PATCH 05/11] Syncing package-lock.json --- package-lock.json | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 41478feb..0c453053 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6863,6 +6863,23 @@ "just-extend": "^4.0.2", "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } } }, "node-fetch": { @@ -7575,21 +7592,9 @@ "dev": true }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.1.0.tgz", + "integrity": "sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==" }, "path-type": { "version": "3.0.0", From 61319a0a4a7cfd51fb439dfb8490cca275c65d37 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 23 Jan 2020 11:43:42 -0800 Subject: [PATCH 06/11] Added tests, fleshed out edge cases with origin formats --- __tests__/__snapshots__/oas.test.js.snap | 4 +- __tests__/fixtures/multiple-securities.json | 3 + __tests__/oas.test.js | 508 ++++++++++++-------- src/oas.js | 8 +- 4 files changed, 305 insertions(+), 218 deletions(-) diff --git a/__tests__/__snapshots__/oas.test.js.snap b/__tests__/__snapshots__/oas.test.js.snap index fd370d3b..91a638b4 100644 --- a/__tests__/__snapshots__/oas.test.js.snap +++ b/__tests__/__snapshots__/oas.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`operation() should return a default when no operation 1`] = ` +exports[`class.Oas operation() should return a default when no operation 1`] = ` Operation { "method": "get", "oas": Oas { @@ -11,7 +11,7 @@ Operation { } `; -exports[`operation.prepareSecurity() should work for petstore 1`] = ` +exports[`class.operation prepareSecurity() should work for petstore 1`] = ` Object { "OAuth2": Array [ Object { diff --git a/__tests__/fixtures/multiple-securities.json b/__tests__/fixtures/multiple-securities.json index 39cf176f..634de191 100644 --- a/__tests__/fixtures/multiple-securities.json +++ b/__tests__/fixtures/multiple-securities.json @@ -140,6 +140,9 @@ }, { "oauthDiff": ["write:things", "read:things"] + }, + { + "apiKeyScheme": [] } ], "summary": "second and does not show security", diff --git a/__tests__/oas.test.js b/__tests__/oas.test.js index 47b9a150..0fb7cf41 100644 --- a/__tests__/oas.test.js +++ b/__tests__/oas.test.js @@ -3,280 +3,362 @@ const { Operation } = require('../src/oas'); const petstore = require('./fixtures/petstore.json'); const multipleSecurities = require('./fixtures/multiple-securities.json'); -describe('operation()', () => { - it('should return an operation object', () => { - const oas = { paths: { '/path': { get: { a: 1 } } } }; - const operation = new Oas(oas).operation('/path', 'get'); - expect(operation).toBeInstanceOf(Operation); - expect(operation.a).toBe(1); - expect(operation.path).toBe('/path'); - expect(operation.method).toBe('get'); - }); +describe('class.Oas', () => { + describe('operation()', () => { + it('should return an operation object', () => { + const oas = { paths: { '/path': { get: { a: 1 } } } }; + const operation = new Oas(oas).operation('/path', 'get'); + expect(operation).toBeInstanceOf(Operation); + expect(operation.a).toBe(1); + expect(operation.path).toBe('/path'); + expect(operation.method).toBe('get'); + }); - it('should return a default when no operation', () => { - expect(new Oas({}).operation('/unknown', 'get')).toMatchSnapshot(); + it('should return a default when no operation', () => { + expect(new Oas({}).operation('/unknown', 'get')).toMatchSnapshot(); + }); }); -}); -test('should remove end slash from the server URL', () => { - expect(new Oas({ servers: [{ url: 'http://example.com/' }] }).url()).toBe('http://example.com'); -}); + it('should remove end slash from the server URL', () => { + expect(new Oas({ servers: [{ url: 'http://example.com/' }] }).url()).toBe('http://example.com'); + }); -test('should default missing servers array to example.com', () => { - expect(new Oas({}).url()).toBe('https://example.com'); -}); + it('should default missing servers array to example.com', () => { + expect(new Oas({}).url()).toBe('https://example.com'); + }); -test('should default empty servers array to example.com', () => { - expect(new Oas({ servers: [] }).url()).toBe('https://example.com'); -}); + it('should default empty servers array to example.com', () => { + expect(new Oas({ servers: [] }).url()).toBe('https://example.com'); + }); -test('should default empty server object to example.com', () => { - expect(new Oas({ servers: [{}] }).url()).toBe('https://example.com'); -}); + it('should default empty server object to example.com', () => { + expect(new Oas({ servers: [{}] }).url()).toBe('https://example.com'); + }); -test('should add https:// if url starts with //', () => { - expect(new Oas({ servers: [{ url: '//example.com' }] }).url()).toBe('https://example.com'); -}); + it('should add https:// if url starts with //', () => { + expect(new Oas({ servers: [{ url: '//example.com' }] }).url()).toBe('https://example.com'); + }); -test('should add https:// if url does not start with a protocol', () => { - expect(new Oas({ servers: [{ url: 'example.com' }] }).url()).toBe('https://example.com'); -}); + it('should add https:// if url does not start with a protocol', () => { + expect(new Oas({ servers: [{ url: 'example.com' }] }).url()).toBe('https://example.com'); + }); -describe('server variables', () => { - it('should use defaults', () => { + it('should be able to access properties on oas', () => { expect( new Oas({ - servers: [{ url: 'https://example.com/{path}', variables: { path: { default: 'path' } } }], - }).url() - ).toBe('https://example.com/path'); + info: { version: '1.0' }, + }).info.version + ).toBe('1.0'); }); - it('should use user variables over defaults', () => { - expect( - new Oas( - { - servers: [{ url: 'https://{username}.example.com', variables: { username: { default: 'demo' } } }], - }, - { username: 'domh' } - ).url() - ).toBe('https://domh.example.com'); - }); + describe('server variables', () => { + it('should use defaults', () => { + expect( + new Oas({ + servers: [{ url: 'https://example.com/{path}', variables: { path: { default: 'path' } } }], + }).url() + ).toBe('https://example.com/path'); + }); - it('should fetch user variables from keys array', () => { - expect( - new Oas( - { - servers: [{ url: 'https://{username}.example.com', variables: { username: { default: 'demo' } } }], - }, - { keys: [{ name: 1, username: 'domh' }] } - ).url() - ).toBe('https://domh.example.com'); - }); + it('should use user variables over defaults', () => { + expect( + new Oas( + { + servers: [{ url: 'https://{username}.example.com', variables: { username: { default: 'demo' } } }], + }, + { username: 'domh' } + ).url() + ).toBe('https://domh.example.com'); + }); - it.skip('should fetch user variables from selected app', () => { - expect( - new Oas( - { - servers: [{ url: 'https://{username}.example.com', variables: { username: { default: 'demo' } } }], - }, - { - keys: [ - { name: 1, username: 'domh' }, - { name: 2, username: 'readme' }, - ], - }, - 2 - ).url() - ).toBe('https://readme.example.com'); - }); + it('should fetch user variables from keys array', () => { + expect( + new Oas( + { + servers: [{ url: 'https://{username}.example.com', variables: { username: { default: 'demo' } } }], + }, + { keys: [{ name: 1, username: 'domh' }] } + ).url() + ).toBe('https://domh.example.com'); + }); - // Test encodeURI - it('should pass through if no default set', () => { - expect(new Oas({ servers: [{ url: 'https://example.com/{path}' }] }).url()).toBe('https://example.com/{path}'); + it.skip('should fetch user variables from selected app', () => { + expect( + new Oas( + { + servers: [{ url: 'https://{username}.example.com', variables: { username: { default: 'demo' } } }], + }, + { + keys: [ + { name: 1, username: 'domh' }, + { name: 2, username: 'readme' }, + ], + }, + 2 + ).url() + ).toBe('https://readme.example.com'); + }); + + // Test encodeURI + it('should pass through if no default set', () => { + expect(new Oas({ servers: [{ url: 'https://example.com/{path}' }] }).url()).toBe('https://example.com/{path}'); + }); }); -}); -test('should be able to access properties on oas', () => { - expect( - new Oas({ - info: { version: '1.0' }, - }).info.version - ).toBe('1.0'); -}); + describe('findOperation', () => { + it('should return undefined if no server found', () => { + const oas = new Oas(petstore); + const uri = `http://localhost:3000/pet/1`; + const method = 'DELETE'; -describe('operation.getSecurity()', () => { - const security = [{ auth: [] }]; + const res = oas.findOperation(uri, method); + expect(res).toBeUndefined(); + }); - it('should return the security on this endpoint', () => { - expect( - new Oas({ - info: { version: '1.0' }, - paths: { - '/things': { - post: { - security, - }, + it('should return undefined if no path matches found', () => { + const oas = new Oas(petstore); + const uri = `http://petstore.swagger.io/v2/search`; + const method = 'GET'; + + const res = oas.findOperation(uri, method); + expect(res).toBeUndefined(); + }); + + it('should return undefined if no matching methods in path', () => { + const oas = new Oas(petstore); + const uri = `http://petstore.swagger.io/v2/pet/1`; + const method = 'PATCH'; + + const res = oas.findOperation(uri, method); + expect(res).toBeUndefined(); + }); + + it('should return a a result if found', () => { + const oas = new Oas(petstore); + const uri = `http://petstore.swagger.io/v2/pet/1`; + const method = 'DELETE'; + + const res = oas.findOperation(uri, method); + console.log(JSON.stringify(res)); + expect(res.logOperation).toMatchObject({ + url: { + origin: 'http://petstore.swagger.io/v2', + path: '/pet/:petId', + slugs: { + ':petId': '1', }, + method: 'DELETE', }, - }) - .operation('/things', 'post') - .getSecurity() - ).toBe(security); + }); + }); }); +}); - it('should fallback to global security', () => { - expect( - new Oas({ - info: { version: '1.0' }, - paths: { - '/things': { - post: {}, +describe('class.operation', () => { + describe('getSecurity()', () => { + const security = [{ auth: [] }]; + + it('should return the security on this endpoint', () => { + expect( + new Oas({ + info: { version: '1.0' }, + paths: { + '/things': { + post: { + security, + }, + }, }, - }, - security, - }) - .operation('/things', 'post') - .getSecurity() - ).toBe(security); + }) + .operation('/things', 'post') + .getSecurity() + ).toBe(security); + }); + + it('should fallback to global security', () => { + expect( + new Oas({ + info: { version: '1.0' }, + paths: { + '/things': { + post: {}, + }, + }, + security, + }) + .operation('/things', 'post') + .getSecurity() + ).toBe(security); + }); + + it('should default to empty array', () => { + expect( + new Oas({ + info: { version: '1.0' }, + paths: { + '/things': { + post: {}, + }, + }, + }) + .operation('/things', 'post') + .getSecurity() + ).toStrictEqual([]); + }); }); - it('should default to empty array', () => { - expect( - new Oas({ - info: { version: '1.0' }, + // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject + describe('prepareSecurity()', () => { + const path = '/auth'; + const method = 'get'; + + function createSecurityOas(schemes) { + // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject + const security = Object.keys(schemes).map(scheme => { + return { [scheme]: [] }; + }); + + return new Oas({ + components: { securitySchemes: schemes }, paths: { - '/things': { - post: {}, + [path]: { + [method]: { security }, }, }, - }) - .operation('/things', 'post') - .getSecurity() - ).toStrictEqual([]); - }); -}); - -// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject -describe('operation.prepareSecurity()', () => { - const path = '/auth'; - const method = 'get'; + }); + } + + it('http/basic: should return with a type of Basic', () => { + const oas = createSecurityOas({ + securityScheme: { + type: 'http', + scheme: 'basic', + }, + }); + const operation = oas.operation(path, method); - function createSecurityOas(schemes) { - // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject - const security = Object.keys(schemes).map(scheme => { - return { [scheme]: [] }; + expect(operation.prepareSecurity()).toStrictEqual({ + Basic: [oas.components.securitySchemes.securityScheme], + }); }); - return new Oas({ - components: { securitySchemes: schemes }, - paths: { - [path]: { - [method]: { security }, + it('http/bearer: should return with a type of Bearer', () => { + const oas = createSecurityOas({ + securityScheme: { + type: 'http', + scheme: 'bearer', }, - }, - }); - } - - it('http/basic: should return with a type of Basic', () => { - const oas = createSecurityOas({ - securityScheme: { - type: 'http', - scheme: 'basic', - }, - }); - const operation = oas.operation(path, method); + }); + const operation = oas.operation(path, method); - expect(operation.prepareSecurity()).toStrictEqual({ - Basic: [oas.components.securitySchemes.securityScheme], + expect(operation.prepareSecurity()).toStrictEqual({ + Bearer: [oas.components.securitySchemes.securityScheme], + }); }); - }); - it('http/bearer: should return with a type of Bearer', () => { - const oas = createSecurityOas({ - securityScheme: { - type: 'http', - scheme: 'bearer', - }, - }); - const operation = oas.operation(path, method); + it('apiKey/query: should return with a type of Query', () => { + const oas = createSecurityOas({ + securityScheme: { + type: 'apiKey', + in: 'query', + }, + }); + const operation = oas.operation(path, method); - expect(operation.prepareSecurity()).toStrictEqual({ - Bearer: [oas.components.securitySchemes.securityScheme], + expect(operation.prepareSecurity()).toStrictEqual({ + Query: [oas.components.securitySchemes.securityScheme], + }); }); - }); - it('apiKey/query: should return with a type of Query', () => { - const oas = createSecurityOas({ - securityScheme: { - type: 'apiKey', - in: 'query', - }, + it('apiKey/header: should return with a type of Header', () => { + const oas = createSecurityOas({ + securityScheme: { + type: 'apiKey', + in: 'header', + }, + }); + const operation = oas.operation(path, method); + + expect(operation.prepareSecurity()).toStrictEqual({ + Header: [oas.components.securitySchemes.securityScheme], + }); }); - const operation = oas.operation(path, method); - expect(operation.prepareSecurity()).toStrictEqual({ - Query: [oas.components.securitySchemes.securityScheme], + it('should work for petstore', () => { + const operation = new Oas(petstore).operation('/pet', 'post'); + + expect(operation.prepareSecurity()).toMatchSnapshot(); }); - }); - it('apiKey/header: should return with a type of Header', () => { - const oas = createSecurityOas({ - securityScheme: { - type: 'apiKey', - in: 'header', - }, + it('should work for multiple securities (||)', () => { + const operation = new Oas(multipleSecurities).operation('/or-security', 'post'); + + expect(Object.keys(operation.prepareSecurity())).toHaveLength(2); }); - const operation = oas.operation(path, method); - expect(operation.prepareSecurity()).toStrictEqual({ - Header: [oas.components.securitySchemes.securityScheme], + it('should work for multiple securities (&&)', () => { + const operation = new Oas(multipleSecurities).operation('/and-security', 'post'); + + expect(Object.keys(operation.prepareSecurity())).toHaveLength(2); }); - }); - it('should work for petstore', () => { - const operation = new Oas(petstore).operation('/pet', 'post'); + it('should work for multiple securities (&& and ||)', () => { + const operation = new Oas(multipleSecurities).operation('/and-or-security', 'post'); - expect(operation.prepareSecurity()).toMatchSnapshot(); - }); + expect(operation.prepareSecurity().OAuth2).toHaveLength(2); + expect(operation.prepareSecurity().Header).toHaveLength(1); + }); - it('should work for multiple securities (||)', () => { - const operation = new Oas(multipleSecurities).operation('/or-security', 'post'); + it.todo('should set a `key` property'); - expect(Object.keys(operation.prepareSecurity())).toHaveLength(2); - }); + // TODO We dont currently support cookies? + it.todo('apiKey/cookie: should return with a type of Cookie'); - it('should work for multiple securities (&&)', () => { - const operation = new Oas(multipleSecurities).operation('/and-security', 'post'); + it.todo('should throw if attempting to use a non-existent scheme'); - expect(Object.keys(operation.prepareSecurity())).toHaveLength(2); - }); + it('should return empty object if no security', () => { + const operation = new Oas(multipleSecurities).operation('/no-auth', 'post'); + expect(Object.keys(operation.prepareSecurity())).toHaveLength(0); + }); - it('should work for multiple securities (&& and ||)', () => { - const operation = new Oas(multipleSecurities).operation('/and-or-security', 'post'); + it('should return empty object if security scheme doesnt exist', () => { + const operation = new Oas(multipleSecurities).operation('/unknown-scheme', 'post'); + expect(Object.keys(operation.prepareSecurity())).toHaveLength(0); + }); - expect(operation.prepareSecurity().OAuth2).toHaveLength(2); - expect(operation.prepareSecurity().Header).toHaveLength(1); + it('should return empty if security scheme type doesnt exist', () => { + const operation = new Oas(multipleSecurities).operation('/unknown-auth-type', 'post'); + expect(Object.keys(operation.prepareSecurity())).toHaveLength(0); + }); }); - it.todo('should set a `key` property'); + describe('getHeaders()', () => { + it('should return an object containing request headers if params exist', () => { + const oas = new Oas(petstore); + const uri = `http://petstore.swagger.io/v2/pet/1`; + const method = 'DELETE'; - // TODO We dont currently support cookies? - it.todo('apiKey/cookie: should return with a type of Cookie'); + const { logOperation } = oas.findOperation(uri, method); + const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); - it.todo('should throw if attempting to use a non-existent scheme'); + expect(operation.getHeaders()).toMatchObject({ + request: ['api_key'], + response: [], + }); + }); - it('should return empty object if no security', () => { - const operation = new Oas(multipleSecurities).operation('/no-auth', 'post'); - expect(Object.keys(operation.prepareSecurity())).toHaveLength(0); - }); + it('should return an object containing request headers if security exists', () => { + const oas = new Oas(multipleSecurities); + const uri = 'http://example.com/multiple-combo-auths'; + const method = 'POST'; - it('should return empty object if security scheme doesnt exist', () => { - const operation = new Oas(multipleSecurities).operation('/unknown-scheme', 'post'); - expect(Object.keys(operation.prepareSecurity())).toHaveLength(0); - }); + const { logOperation } = oas.findOperation(uri, method); + const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); - it('should return empty if security scheme type doesnt exist', () => { - const operation = new Oas(multipleSecurities).operation('/unknown-auth-type', 'post'); - expect(Object.keys(operation.prepareSecurity())).toHaveLength(0); + expect(operation.getHeaders()).toMatchObject({ + request: ['apiKey'], + response: [], + }); + }); }); }); diff --git a/src/oas.js b/src/oas.js index 7c01379a..5270726b 100644 --- a/src/oas.js +++ b/src/oas.js @@ -226,13 +226,15 @@ class Oas { } findOperation(url, method, index = 0) { - const { origin, pathname } = new URL(url); + const { origin } = new URL(url); + const originRegExp = new RegExp(origin); const { servers, paths } = this; - const targetServer = servers.find(s => s.url === origin); + const targetServer = servers.find(s => originRegExp.exec(s.url)); if (!targetServer) return undefined; - const annotatedPaths = generatePathMatches(paths, pathname, origin); + const [, pathName] = url.split(targetServer.url); + const annotatedPaths = generatePathMatches(paths, pathName, targetServer.url); if (!annotatedPaths.length) return undefined; const includesMethod = filterPathMethods(annotatedPaths, method); From 2bc6f08c13c398eb49c23d0b3e57ad2fe370870b Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 23 Jan 2020 13:04:17 -0800 Subject: [PATCH 07/11] Cleaning up test bed --- __tests__/oas.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/__tests__/oas.test.js b/__tests__/oas.test.js index 0fb7cf41..0861a30f 100644 --- a/__tests__/oas.test.js +++ b/__tests__/oas.test.js @@ -133,13 +133,12 @@ describe('class.Oas', () => { expect(res).toBeUndefined(); }); - it('should return a a result if found', () => { + it('should return a result if found', () => { const oas = new Oas(petstore); const uri = `http://petstore.swagger.io/v2/pet/1`; const method = 'DELETE'; const res = oas.findOperation(uri, method); - console.log(JSON.stringify(res)); expect(res.logOperation).toMatchObject({ url: { origin: 'http://petstore.swagger.io/v2', From 4a927d535485967d483233b038390780394f37fd Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 23 Jan 2020 13:25:56 -0800 Subject: [PATCH 08/11] Maintaining index in FE, removing from codebase --- __tests__/oas.test.js | 6 +++--- src/oas.js | 15 ++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/__tests__/oas.test.js b/__tests__/oas.test.js index 0861a30f..87fc5546 100644 --- a/__tests__/oas.test.js +++ b/__tests__/oas.test.js @@ -139,7 +139,7 @@ describe('class.Oas', () => { const method = 'DELETE'; const res = oas.findOperation(uri, method); - expect(res.logOperation).toMatchObject({ + expect(res).toMatchObject({ url: { origin: 'http://petstore.swagger.io/v2', path: '/pet/:petId', @@ -337,7 +337,7 @@ describe('class.operation', () => { const uri = `http://petstore.swagger.io/v2/pet/1`; const method = 'DELETE'; - const { logOperation } = oas.findOperation(uri, method); + const logOperation = oas.findOperation(uri, method); const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); expect(operation.getHeaders()).toMatchObject({ @@ -351,7 +351,7 @@ describe('class.operation', () => { const uri = 'http://example.com/multiple-combo-auths'; const method = 'POST'; - const { logOperation } = oas.findOperation(uri, method); + const logOperation = oas.findOperation(uri, method); const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); expect(operation.getHeaders()).toMatchObject({ diff --git a/src/oas.js b/src/oas.js index 5270726b..a304a49a 100644 --- a/src/oas.js +++ b/src/oas.js @@ -178,23 +178,20 @@ function filterPathMethods(pathMatches, targetMethod) { .filter(p => p); } -function findTargetPath(pathMatches, index) { +function findTargetPath(pathMatches) { let minCount = Object.keys(pathMatches[0].url.slugs).length; - let logOperation; + let operation; for (let m = 0; m < pathMatches.length; m += 1) { const selection = pathMatches[m]; const paramCount = Object.keys(selection.url.slugs).length; if (paramCount <= minCount) { minCount = paramCount; - logOperation = selection; + operation = selection; } } - return { - logOperation, - index, - }; + return operation; } class Oas { @@ -225,7 +222,7 @@ class Oas { return new Operation(this, path, method, operation); } - findOperation(url, method, index = 0) { + findOperation(url, method) { const { origin } = new URL(url); const originRegExp = new RegExp(origin); const { servers, paths } = this; @@ -240,7 +237,7 @@ class Oas { const includesMethod = filterPathMethods(annotatedPaths, method); if (!includesMethod.length) return undefined; - return findTargetPath(includesMethod, index); + return findTargetPath(includesMethod); } } From 96cad79244daa194ad5f66012a44e0e90aa202b1 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Thu, 23 Jan 2020 21:12:07 -0800 Subject: [PATCH 09/11] Built out security types, and reference potential for parameters --- src/lib/get-reference.js | 5 +++++ src/oas.js | 29 ++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 src/lib/get-reference.js diff --git a/src/lib/get-reference.js b/src/lib/get-reference.js new file mode 100644 index 00000000..21621e38 --- /dev/null +++ b/src/lib/get-reference.js @@ -0,0 +1,5 @@ +module.exports = function findReference(reference, type, oas) { + const explodedRef = reference.split('/'); + const key = explodedRef[explodedRef.length - 1]; + return oas.components[type][key]; +}; diff --git a/src/oas.js b/src/oas.js index a304a49a..4222d75d 100644 --- a/src/oas.js +++ b/src/oas.js @@ -2,6 +2,7 @@ const { pathToRegexp, match } = require('path-to-regexp'); const getPathOperation = require('./lib/get-path-operation'); const getUserVariable = require('./lib/get-user-variable'); +const getReference = require('./lib/get-reference'); class Operation { constructor(oas, path, method, operation) { @@ -42,10 +43,9 @@ class Operation { if (security.scheme === 'bearer') type = 'Bearer'; } else if (security.type === 'oauth2') { type = 'OAuth2'; - } else if (security.type === 'apiKey' && security.in === 'query') { - type = 'Query'; - } else if (security.type === 'apiKey' && security.in === 'header') { - type = 'Header'; + } else if (security.type === 'apiKey') { + if (security.in === 'query') type = 'Query'; + else if (security.in === 'header' || security.in === 'cookie') type = 'Header'; } else { return false; } @@ -74,13 +74,28 @@ class Operation { }; const security = this.prepareSecurity(); - if (security.Header && security.Header.length) { - this.headers.request = security.Header.map(h => h.name); + if (security.Header) { + this.headers.request = security.Header.map(h => { + if (h.in === 'cookie') return 'Cookie'; + return h.name; + }); + } + if (security.Bearer || security.Basic) { + this.headers.request.push('Authorization'); } if (this.parameters) { this.headers.request = this.headers.request.concat( - this.parameters.filter(p => p.in === 'header').map(p => p.name) + this.parameters + .map(p => { + if (p.in && p.in === 'header') return p.name; + if (p.$ref) { + const { name } = getReference(p.$ref, 'parameters', this.oas); + return name; + } + return undefined; + }) + .filter(p => p) ); } From ed8aca04eef0bec8c7cdb6f9babcf72437014b39 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Fri, 24 Jan 2020 10:10:55 -0800 Subject: [PATCH 10/11] Test updates covering header logic: - Handles Refs - Different security scheme types --- __tests__/fixtures/local-link.json | 369 ++++++++++++++++++++ __tests__/fixtures/multiple-securities.json | 2 +- __tests__/fixtures/petstore.json | 4 +- __tests__/oas.test.js | 50 ++- 4 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 __tests__/fixtures/local-link.json diff --git a/__tests__/fixtures/local-link.json b/__tests__/fixtures/local-link.json new file mode 100644 index 00000000..0c1007e9 --- /dev/null +++ b/__tests__/fixtures/local-link.json @@ -0,0 +1,369 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Link Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://local-link.com" + } + ], + "paths": { + "/2.0/users/{username}": { + "get": { + "operationId": "getUserByName", + "security": [ + { + "cookieAuth": [] + }, + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The User", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + }, + "links": { + "userRepositories": { + "$ref": "#/components/links/UserRepositories" + } + } + } + } + } + }, + "/2.0/repositories/{username}": { + "get": { + "operationId": "getRepositoriesByOwner", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "repositories owned by the supplied user", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/repository" + } + } + } + }, + "links": { + "userRepository": { + "$ref": "#/components/links/UserRepository" + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}": { + "get": { + "operationId": "getRepository", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + }, + "links": { + "repositoryPullRequests": { + "$ref": "#/components/links/RepositoryPullRequests" + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}/pullrequests": { + "get": { + "operationId": "getPullRequestsByRepository", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "open", + "merged", + "declined" + ] + } + }, + { + "$ref": "#/components/parameters/host" + } + ], + "responses": { + "200": { + "description": "an array of pull request objects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}/pullrequests/{pid}": { + "get": { + "operationId": "getPullRequestsById", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "a pull request object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + }, + "links": { + "pullRequestMerge": { + "$ref": "#/components/links/PullRequestMerge" + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge": { + "post": { + "operationId": "mergePullRequest", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "the PR was successfully merged" + } + } + } + } + }, + "components": { + "links": { + "UserRepositories": { + "operationId": "getRepositoriesByOwner", + "parameters": { + "username": "$response.body#/username" + } + }, + "UserRepository": { + "operationId": "getRepository", + "parameters": { + "username": "$response.body#/owner/username", + "slug": "$response.body#/slug" + } + }, + "RepositoryPullRequests": { + "operationId": "getPullRequestsByRepository", + "parameters": { + "username": "$response.body#/owner/username", + "slug": "$response.body#/slug" + } + }, + "PullRequestMerge": { + "operationId": "mergePullRequest", + "parameters": { + "username": "$response.body#/author/username", + "slug": "$response.body#/repository/slug", + "pid": "$response.body#/id" + } + } + }, + "schemas": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + } + } + }, + "repository": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/user" + } + } + }, + "pullrequest": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "author": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "securitySchemes": { + "apiKey": { + "name": "X-API-KEY", + "type": "apiKey", + "in": "header" + }, + "basicAuth": { + "type": "http", + "scheme": "basic" + }, + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }, + "cookieAuth": { + "type": "apiKey", + "in": "cookie", + "name": "cookieSessionId" + } + }, + "parameters": { + "host": { + "in": "header", + "name": "hostname", + "schema": { + "type": "string" + } + } + } + } +} diff --git a/__tests__/fixtures/multiple-securities.json b/__tests__/fixtures/multiple-securities.json index 634de191..1a556413 100644 --- a/__tests__/fixtures/multiple-securities.json +++ b/__tests__/fixtures/multiple-securities.json @@ -237,7 +237,7 @@ }, "apiKeyScheme": { "type": "apiKey", - "name": "apiKey", + "name": "testKey", "in": "header" }, "unknownAuthType": { diff --git a/__tests__/fixtures/petstore.json b/__tests__/fixtures/petstore.json index eef09161..39d0b3be 100644 --- a/__tests__/fixtures/petstore.json +++ b/__tests__/fixtures/petstore.json @@ -303,7 +303,7 @@ "operationId": "deletePet", "parameters": [ { - "name": "api_key", + "name": "testKey", "in": "header", "required": false, "schema": { @@ -984,7 +984,7 @@ }, "api_key": { "type": "apiKey", - "name": "api_key", + "name": "testKey", "in": "header" } }, diff --git a/__tests__/oas.test.js b/__tests__/oas.test.js index 87fc5546..5a712f94 100644 --- a/__tests__/oas.test.js +++ b/__tests__/oas.test.js @@ -2,6 +2,7 @@ const Oas = require('../src/oas'); const { Operation } = require('../src/oas'); const petstore = require('./fixtures/petstore.json'); const multipleSecurities = require('./fixtures/multiple-securities.json'); +const referenceSpec = require('./fixtures/local-link.json'); describe('class.Oas', () => { describe('operation()', () => { @@ -150,6 +151,24 @@ describe('class.Oas', () => { }, }); }); + + it('should return normally if path is formatted poorly', () => { + const oas = new Oas(petstore); + const uri = `http://petstore.swagger.io/v2/pet/1/`; + const method = 'DELETE'; + + const res = oas.findOperation(uri, method); + expect(res).toMatchObject({ + url: { + origin: 'http://petstore.swagger.io/v2', + path: '/pet/:petId', + slugs: { + ':petId': '1', + }, + method: 'DELETE', + }, + }); + }); }); }); @@ -341,7 +360,7 @@ describe('class.operation', () => { const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); expect(operation.getHeaders()).toMatchObject({ - request: ['api_key'], + request: ['testKey'], response: [], }); }); @@ -355,7 +374,34 @@ describe('class.operation', () => { const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); expect(operation.getHeaders()).toMatchObject({ - request: ['apiKey'], + request: ['testKey'], + response: [], + }); + }); + + it('should return a Cookie header if security is located in cookie scheme', () => { + const oas = new Oas(referenceSpec); + const uri = 'http://local-link.com/2.0/users/johnSmith'; + const method = 'GET'; + + const logOperation = oas.findOperation(uri, method); + const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); + console.log(operation.getHeaders()); + expect(operation.getHeaders()).toMatchObject({ + request: ['Cookie', 'Authorization'], + response: [], + }); + }); + + it('should target parameter refs and return names if applicable', () => { + const oas = new Oas(referenceSpec); + const uri = 'http://local-link.com/2.0/repositories/janeDoe/oas/pullrequests'; + const method = 'GET'; + + const logOperation = oas.findOperation(uri, method); + const operation = new Operation(oas, logOperation.url.path, logOperation.url.method, logOperation.operation); + expect(operation.getHeaders()).toMatchObject({ + request: ['hostname'], response: [], }); }); From 93f8e3a3c81aeabe4038fd082ee92896eca7c779 Mon Sep 17 00:00:00 2001 From: Gabriel Ratcliff Date: Fri, 24 Jan 2020 11:15:31 -0800 Subject: [PATCH 11/11] Swapping out ref matching to existing util --- src/lib/get-reference.js | 5 ----- src/oas.js | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 src/lib/get-reference.js diff --git a/src/lib/get-reference.js b/src/lib/get-reference.js deleted file mode 100644 index 21621e38..00000000 --- a/src/lib/get-reference.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = function findReference(reference, type, oas) { - const explodedRef = reference.split('/'); - const key = explodedRef[explodedRef.length - 1]; - return oas.components[type][key]; -}; diff --git a/src/oas.js b/src/oas.js index 4222d75d..5643dec8 100644 --- a/src/oas.js +++ b/src/oas.js @@ -2,7 +2,7 @@ const { pathToRegexp, match } = require('path-to-regexp'); const getPathOperation = require('./lib/get-path-operation'); const getUserVariable = require('./lib/get-user-variable'); -const getReference = require('./lib/get-reference'); +const findSchemaDefinition = require('./lib/find-schema-definition'); class Operation { constructor(oas, path, method, operation) { @@ -90,7 +90,7 @@ class Operation { .map(p => { if (p.in && p.in === 'header') return p.name; if (p.$ref) { - const { name } = getReference(p.$ref, 'parameters', this.oas); + const { name } = findSchemaDefinition(p.$ref, this.oas); return name; } return undefined;