Skip to content

Commit

Permalink
Merge pull request #13 from pactumjs/feat/openapi3-support
Browse files Browse the repository at this point in the history
Feat: openapi3 support
  • Loading branch information
leelaprasadv authored Sep 17, 2023
2 parents 851ca14 + 62964ce commit 89cbb85
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ reports/
*.tgz
/.idea/
/package-lock.json
coverage
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -37,10 +37,16 @@ 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';

/**
* 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";
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 5 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 6 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -27,13 +28,14 @@ const psc = {
async end() {
config.swaggerJsonUrl = this.swaggerJsonUrl;
config.swaggerYamlPath = this.swaggerYamlPath;
const coverage = await core.getSwaggerCoverage(testsCoveredApis)
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));

},

Expand Down
38 changes: 27 additions & 11 deletions src/lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,28 @@ 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);
}
}

/**
* 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;
}

Expand All @@ -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,
Expand All @@ -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 config.basePath || apiDefinition.basePath;
}

/**
* Function to RegEx match api paths
* @param {String} apiPath
Expand Down
6 changes: 3 additions & 3 deletions tests/spec.test.js → tests/openapi2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const handler = pactum.handler;
const psc = require('../src/index');

test.before(() => {
psc.swaggerYamlPath = './tests/testObjects/swagger.yaml';
psc.file = 'report.json'
psc.swaggerYamlPath = './tests/testObjects/openapi2.yaml';
psc.reportFile = 'report.json'
reporter.add(psc);
request.setBaseUrl('http://localhost:9393');
handler.addInteractionHandler('get all ninjas', () => {
Expand Down Expand Up @@ -118,7 +118,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);
Expand Down
130 changes: 130 additions & 0 deletions tests/openapi3.test.js
Original file line number Diff line number Diff line change
@@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -82,7 +82,7 @@ paths:
get:
tags: ["Ninjas"]
description: "Get Ninja details by Name"
operationId: "getNinjaByRank"
operationId: "getNinjaByName"
parameters:
- name: name
in: path
Expand All @@ -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
Expand Down
Loading

0 comments on commit 89cbb85

Please sign in to comment.