From c7e3e056e7ed54de621d62ccd180743029a16ca6 Mon Sep 17 00:00:00 2001 From: Leela Prasad <47483946+leelaprasadv@users.noreply.github.com> Date: Sun, 17 Sep 2023 16:06:02 +0530 Subject: [PATCH 1/3] openapi3-support --- .gitignore | 1 + README.md | 4 +- package.json | 2 +- src/config.js | 8 +- src/index.js | 8 +- src/lib/core.js | 38 +++-- tests/{spec.test.js => openapi2.test.js} | 5 +- tests/openapi3.test.js | 130 ++++++++++++++++ tests/testObjects/openapi3.yaml | 179 +++++++++++++++++++++++ tests/testObjects/swagger.yaml | 6 +- 10 files changed, 356 insertions(+), 25 deletions(-) rename tests/{spec.test.js => openapi2.test.js} (96%) create mode 100644 tests/openapi3.test.js create mode 100644 tests/testObjects/openapi3.yaml diff --git a/.gitignore b/.gitignore index d9e683a..2a4b2d2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ reports/ *.tgz /.idea/ /package-lock.json +coverage diff --git a/README.md b/README.md index a01c48d..b3e7ca4 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ after(() => { const psc = require('pactum-swagger-coverage'); // name of the report file - defaults to "swagger-cov-report.json" -psc.file = 'report-name.json'; +psc.reportFile = 'report-name.json'; // folder path for the report file - defaults to "./reports" -psc.path = './reports-path'; +psc.reportPath = './reports-path'; // Swagger json url of the server - defaults to "" psc.swaggerJsonUrl = "http://localhost:3010/api/server/v1/json"; diff --git a/package.json b/package.json index 98d97c1..5270d9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pactum-swagger-coverage", - "version": "1.1.0", + "version": "2.0.0", "description": "Swagger api coverage report for pactum tests", "main": "./src/index.js", "types": "./src/index.d.ts", diff --git a/src/config.js b/src/config.js index a0aafdb..f1be6bb 100644 --- a/src/config.js +++ b/src/config.js @@ -1,9 +1,11 @@ const config = { name: 'SwaggerCovReporter', - path: './reports', - file: 'swagger-cov-report.json', + reportPath: './reports', + reportFile: 'swagger-cov-report.json', swaggerJsonUrl: "", - swaggerYamlPath: "" + swaggerYamlPath: "", + basePath: "", + oasTag: "swagger" } module.exports = config; diff --git a/src/index.js b/src/index.js index 7949a11..4054718 100644 --- a/src/index.js +++ b/src/index.js @@ -8,10 +8,11 @@ const testsCoveredApis = []; const psc = { name: config.name, - reportPath: config.path, - file: config.file, + reportPath: config.reportPath, + reportFile: config.reportFile, swaggerJsonUrl: config.swaggerJsonUrl, swaggerYamlPath: config.swaggerYamlPath, + basePath: config.basePath, afterSpec(spec) { const _specApiPath = {} @@ -27,13 +28,14 @@ const psc = { async end() { config.swaggerJsonUrl = this.swaggerJsonUrl; config.swaggerYamlPath = this.swaggerYamlPath; + config.basePath = this.basePath; const coverage = await core.getSwaggerCoverage(testsCoveredApis) if (!fs.existsSync(this.reportPath)) { fs.mkdirSync(this.reportPath, { recursive: true }); } - fs.writeFileSync(path.resolve(this.reportPath, this.file), JSON.stringify(coverage, null, 2)); + fs.writeFileSync(path.resolve(this.reportPath, this.reportFile), JSON.stringify(coverage, null, 2)); }, diff --git a/src/lib/core.js b/src/lib/core.js index c66f36a..8d93255 100644 --- a/src/lib/core.js +++ b/src/lib/core.js @@ -28,15 +28,15 @@ async function loadSwaggerYaml() { * @returns {Object} Swagger file object */ async function loadSwaggerJson() { - let swaggerInfo = {}; + let apiDefinition = {}; let swaggerJsonUrl = config.swaggerJsonUrl.trim(); if (!swaggerJsonUrl || swaggerJsonUrl === null) { throw new PSCConfigurationError("Swagger definition cannot be empty! Provide 'swaggerYamlPath' or 'swaggerJsonUrl'."); } try { - swaggerInfo = await http.get(swaggerJsonUrl); - return swaggerInfo; + apiDefinition = await http.get(swaggerJsonUrl); + return apiDefinition; } catch (error) { throw new PSCClientError(error); } @@ -44,12 +44,12 @@ async function loadSwaggerJson() { /** * Fuction to all get api path's from swagger file - * @param {Object} swaggerInfo + * @param {Object} apiDefinition * @returns {Array} Array of API paths */ -function getApiPaths(swaggerInfo) { - const apiPaths = Object.keys(swaggerInfo.paths); - apiPaths.forEach((apiPath, index) => apiPaths[index] = `${swaggerInfo.basePath}${apiPath}`); +function getApiPaths(apiDefinition) { + const apiPaths = Object.keys(apiDefinition.paths); + apiPaths.forEach((apiPath, index) => apiPaths[index] = `${config.basePath}${apiPath}`); return apiPaths; } @@ -59,16 +59,20 @@ function getApiPaths(swaggerInfo) { * @returns {object} Swagger coverage stats */ async function getSwaggerCoverage(testsCoveredApis) { - const swaggerInfo = config.swaggerYamlPath ? await loadSwaggerYaml() : await loadSwaggerJson(); - const apiPaths = getApiPaths(swaggerInfo); + const apiDefinition = config.swaggerYamlPath ? await loadSwaggerYaml() : await loadSwaggerJson(); + if (apiDefinition.hasOwnProperty("openapi")) { + config.oasTag = "openapi"; + } + config.basePath = getBasePath(apiDefinition); + const apiPaths = getApiPaths(apiDefinition); const apiCovList = apiPaths.map(apiPath => !!testsCoveredApis.find(({ path }) => { return !!regExMatchOfPath(apiPath, path); })); return { - basePath: swaggerInfo.basePath, - coverage: apiCovList.reduce((total, result, index, results) => result ? total + 1 / results.length : total, 0), + basePath: config.basePath, + coverage: Math.round(apiCovList.reduce((total, result, index, results) => result ? total + 1 / results.length : total, 0)*100)/100, coveredApiCount: apiPaths.filter((_, idx) => apiCovList[idx]).length, missedApiCount: apiPaths.filter((_, idx) => !apiCovList[idx]).length, totalApiCount: apiCovList.length, @@ -77,6 +81,18 @@ async function getSwaggerCoverage(testsCoveredApis) { } } +/** + * Function to return basePath + * @param {object} apiDefinition + * @returns + */ +function getBasePath(apiDefinition){ + if (apiDefinition.hasOwnProperty("openapi") && apiDefinition.servers && apiDefinition.servers[0].url) { + apiDefinition.basePath = apiDefinition.servers[0].url; + } + return apiDefinition.basePath || config.basePath; +} + /** * Function to RegEx match api paths * @param {String} apiPath diff --git a/tests/spec.test.js b/tests/openapi2.test.js similarity index 96% rename from tests/spec.test.js rename to tests/openapi2.test.js index 0b2225e..6206749 100644 --- a/tests/spec.test.js +++ b/tests/openapi2.test.js @@ -10,7 +10,8 @@ const psc = require('../src/index'); test.before(() => { psc.swaggerYamlPath = './tests/testObjects/swagger.yaml'; - psc.file = 'report.json' + psc.basePath = '/api/server/v1' + psc.reportFile = 'report.json' reporter.add(psc); request.setBaseUrl('http://localhost:9393'); handler.addInteractionHandler('get all ninjas', () => { @@ -118,7 +119,7 @@ test('validate json reporter', async () => { assert.equal(report.hasOwnProperty("totalApiCount"), true) assert.equal(report.hasOwnProperty("coveredApiList"), true) assert.equal(report.hasOwnProperty("missedApiList"), true) - assert.equal(report.coverage, 0.6666666666666666); + assert.equal(report.coverage, 0.67); assert.equal(report.coveredApiCount, 4); assert.equal(report.missedApiCount, 2); assert.equal(report.totalApiCount, 6); diff --git a/tests/openapi3.test.js b/tests/openapi3.test.js new file mode 100644 index 0000000..77447f6 --- /dev/null +++ b/tests/openapi3.test.js @@ -0,0 +1,130 @@ +const test = require('uvu').test; +const assert = require('uvu/assert'); +const pactum = require('pactum'); +const reporter = pactum.reporter; +const mock = pactum.mock; +const request = pactum.request; +const handler = pactum.handler; + +const psc = require('../src/index'); + +test.before(() => { + psc.swaggerYamlPath = './tests/testObjects/openapi3.yaml'; + psc.basePath = '/api/server/v2' + psc.reportFile = 'report-openapi3.json' + reporter.add(psc); + request.setBaseUrl('http://localhost:9393'); + handler.addInteractionHandler('get all ninjas', () => { + return { + request: { + method: 'GET', + path: '/api/server/v2/getallninjas' + }, + response: { + status: 200 + } + } + }); + handler.addInteractionHandler('get ninjas by rank', (ctx) => { + return { + request: { + method: 'GET', + path: `/api/server/v2/getninjas/${ctx.data}` + }, + response: { + status: 200 + } + } + }); + handler.addInteractionHandler('get ninja by rank and name', (ctx) => { + return { + request: { + method: 'GET', + path: `/api/server/v2/getninja/${ctx.data.rank}/${ctx.data.name}` + }, + response: { + status: 200 + } + } + }); + + handler.addInteractionHandler('get health', () => { + return { + request: { + method: 'GET', + path: `/api/server/v2/health` + }, + response: { + status: 200 + } + } + }); + return mock.start(); +}); + +test.after(() => { + return mock.stop(); +}); + +test('spec passed', async () => { + await pactum.spec() + .useInteraction('get all ninjas') + .get('/api/server/v2/getallninjas') + .expectStatus(200); +}); + +test('spec passed - additional path params', async () => { + await pactum.spec() + .useInteraction('get ninjas by rank', "jounin") + .get('/api/server/v2/getninjas/jounin') + .expectStatus(200); +}); + +test('spec passed - no path params', async () => { + await pactum.spec() + .useInteraction('get health') + .get('/api/server/v2/health') + .expectStatus(200); +}); + +test('spec passed - different api path with path params', async () => { + await pactum.spec() + .useInteraction('get ninja by rank and name', {rank: "jounin", name: "kakashi"}) + .get('/api/server/v2/getninja/jounin/kakashi') + .expectStatus(200); +}); + +test('spec failed', async () => { + try { + await pactum.spec() + .get('/api/server/v2/getallninjas') + .expectStatus(200); + } catch (error) { + console.log(error); + } +}); + +test('run reporter', async () => { + await reporter.end(); +}); + +test('validate json reporter', async () => { + const report = require('../reports/report-openapi3.json'); + console.log(JSON.stringify(report, null, 2)); + assert.equal(Object.keys(report).length, 7); + assert.equal(report.hasOwnProperty("basePath"), true) + assert.equal(report.hasOwnProperty("coverage"), true) + assert.equal(report.hasOwnProperty("coveredApiCount"), true) + assert.equal(report.hasOwnProperty("missedApiCount"), true) + assert.equal(report.hasOwnProperty("totalApiCount"), true) + assert.equal(report.hasOwnProperty("coveredApiList"), true) + assert.equal(report.hasOwnProperty("missedApiList"), true) + assert.equal(report.coverage, 0.57); + assert.equal(report.coveredApiCount, 4); + assert.equal(report.missedApiCount, 3); + assert.equal(report.totalApiCount, 7); + assert.equal(report.coveredApiList.length, 4); + assert.equal(report.missedApiList.length, 3); +}); + +test.run(); diff --git a/tests/testObjects/openapi3.yaml b/tests/testObjects/openapi3.yaml new file mode 100644 index 0000000..49e68c0 --- /dev/null +++ b/tests/testObjects/openapi3.yaml @@ -0,0 +1,179 @@ +openapi: 3.0.1 +info: + title: Test API server + version: 1.0.0 +servers: +- url: /api/server/v2 +paths: + /health: + get: + tags: + - HealthCheck + description: Health check + operationId: getHealth + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + text/html: + schema: + $ref: '#/components/schemas/Health' + x-swagger-router-controller: health.controller + /getallninjas: + get: + tags: + - Ninjas + description: Get All Ninjas + operationId: getAllNinjas + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + text/html: + schema: + $ref: '#/components/schemas/Health' + x-swagger-router-controller: test.controller + /getninjas/{rank}: + get: + tags: + - Ninjas + description: Get Ninja details by Rank + operationId: getNinjaByRank + parameters: + - name: rank + in: path + description: Rank of Ninja + required: true + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + text/html: + schema: + $ref: '#/components/schemas/Health' + x-swagger-router-controller: test.controller + /getninjas/{clan}/{rank}: + get: + tags: + - Ninjas + description: Get Ninja details by Clan and Rank + operationId: getNinjaByClanRank + parameters: + - name: clan + in: path + description: Clan of Ninja + required: true + schema: + type: string + - name: rank + in: path + description: Rank of Ninja + required: true + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + text/html: + schema: + $ref: '#/components/schemas/Health' + x-swagger-router-controller: test.controller + /getninja/{name}: + get: + tags: + - Ninjas + description: Get Ninja details by Name + operationId: getNinjaByName + parameters: + - name: name + in: path + description: Name of Ninja + required: true + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + text/html: + schema: + $ref: '#/components/schemas/Health' + x-swagger-router-controller: test.controller + /ninja: + post: + tags: + - Ninjas + description: Add a Ninja + operationId: addNinja + requestBody: + description: Add New Ninja + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + /getninja/{rank}/{name}: + get: + tags: + - Ninjas + description: Get Ninja details by Rank and name + operationId: getNinjaByRankName + parameters: + - name: rank + in: path + description: Rank of Ninja + required: true + schema: + type: string + - name: name + in: path + description: Name of Ninja + required: true + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Health' + text/html: + schema: + $ref: '#/components/schemas/Health' + x-swagger-router-controller: test.controller +components: + schemas: + Health: + type: object + properties: + message: + type: string + description: Health Response + example: + message: OK diff --git a/tests/testObjects/swagger.yaml b/tests/testObjects/swagger.yaml index c78bee0..34d0b81 100644 --- a/tests/testObjects/swagger.yaml +++ b/tests/testObjects/swagger.yaml @@ -59,7 +59,7 @@ paths: get: tags: ["Ninjas"] description: "Get Ninja details by Clan and Rank" - operationId: "getNinjaByRank" + operationId: "getNinjaByClanRank" parameters: - name: clan in: path @@ -82,7 +82,7 @@ paths: get: tags: ["Ninjas"] description: "Get Ninja details by Name" - operationId: "getNinjaByRank" + operationId: "getNinjaByName" parameters: - name: name in: path @@ -100,7 +100,7 @@ paths: get: tags: ["Ninjas"] description: "Get Ninja details by Rank and name" - operationId: "getNinjaByRank" + operationId: "getNinjaByRankName" parameters: - name: rank in: path From 770b5a8d507764c0d53d6db8ff89c75a59e84ec3 Mon Sep 17 00:00:00 2001 From: Leela Prasad <47483946+leelaprasadv@users.noreply.github.com> Date: Sun, 17 Sep 2023 16:34:06 +0530 Subject: [PATCH 2/3] Allow basePath override --- README.md | 8 +++++++- src/index.js | 2 +- src/lib/core.js | 2 +- tests/openapi2.test.js | 4 ++-- tests/testObjects/{swagger.yaml => openapi2.yaml} | 0 5 files changed, 11 insertions(+), 5 deletions(-) rename tests/testObjects/{swagger.yaml => openapi2.yaml} (100%) diff --git a/README.md b/README.md index b3e7ca4..2c24dff 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![Size](https://img.shields.io/bundlephobia/minzip/pactum-swagger-coverage) ![Platform](https://img.shields.io/node/v/pactum) -JSON swagger coverage reporter for [Pactum](https://www.npmjs.com/package/pactum) tests. It's capable of reading the swagger definitions from either `swagger.yaml` or `swagger.json` (served on a http server endpoint). +JSON swagger/openapi3 coverage reporter for [Pactum](https://www.npmjs.com/package/pactum) tests. It's capable of reading the swagger/oas3 definitions from either `swagger.yaml` or `swagger.json` or `openapi3.yaml` (served on a http server endpoint). ## Installation @@ -42,6 +42,12 @@ psc.reportFile = 'report-name.json'; // folder path for the report file - defaults to "./reports" psc.reportPath = './reports-path'; +/** +* base path - defaults to `basePath` for swagger 2.0/openapi2 and first server url +* or empty if either of them doesn't exist or not set explicitly +*/ +psc.basePath = '/api/server/v1'; + // Swagger json url of the server - defaults to "" psc.swaggerJsonUrl = "http://localhost:3010/api/server/v1/json"; diff --git a/src/index.js b/src/index.js index 4054718..edb7d80 100644 --- a/src/index.js +++ b/src/index.js @@ -29,7 +29,7 @@ const psc = { config.swaggerJsonUrl = this.swaggerJsonUrl; config.swaggerYamlPath = this.swaggerYamlPath; config.basePath = this.basePath; - const coverage = await core.getSwaggerCoverage(testsCoveredApis) + const coverage = await core.getSwaggerCoverage(testsCoveredApis); if (!fs.existsSync(this.reportPath)) { fs.mkdirSync(this.reportPath, { recursive: true }); diff --git a/src/lib/core.js b/src/lib/core.js index 8d93255..aec49b3 100644 --- a/src/lib/core.js +++ b/src/lib/core.js @@ -90,7 +90,7 @@ function getBasePath(apiDefinition){ if (apiDefinition.hasOwnProperty("openapi") && apiDefinition.servers && apiDefinition.servers[0].url) { apiDefinition.basePath = apiDefinition.servers[0].url; } - return apiDefinition.basePath || config.basePath; + return config.basePath || apiDefinition.basePath; } /** diff --git a/tests/openapi2.test.js b/tests/openapi2.test.js index 6206749..bb24042 100644 --- a/tests/openapi2.test.js +++ b/tests/openapi2.test.js @@ -9,8 +9,8 @@ const handler = pactum.handler; const psc = require('../src/index'); test.before(() => { - psc.swaggerYamlPath = './tests/testObjects/swagger.yaml'; - psc.basePath = '/api/server/v1' + psc.swaggerYamlPath = './tests/testObjects/openapi2.yaml'; + psc.basePath = '/api/server/v3' psc.reportFile = 'report.json' reporter.add(psc); request.setBaseUrl('http://localhost:9393'); diff --git a/tests/testObjects/swagger.yaml b/tests/testObjects/openapi2.yaml similarity index 100% rename from tests/testObjects/swagger.yaml rename to tests/testObjects/openapi2.yaml From 62964ceb6631ac6ae66cefe2616ade1bae883c28 Mon Sep 17 00:00:00 2001 From: Leela Prasad <47483946+leelaprasadv@users.noreply.github.com> Date: Sun, 17 Sep 2023 16:44:05 +0530 Subject: [PATCH 3/3] Update tests --- tests/openapi2.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/openapi2.test.js b/tests/openapi2.test.js index bb24042..779cf51 100644 --- a/tests/openapi2.test.js +++ b/tests/openapi2.test.js @@ -10,7 +10,6 @@ const psc = require('../src/index'); test.before(() => { psc.swaggerYamlPath = './tests/testObjects/openapi2.yaml'; - psc.basePath = '/api/server/v3' psc.reportFile = 'report.json' reporter.add(psc); request.setBaseUrl('http://localhost:9393');