Skip to content

Commit 89cbb85

Browse files
authored
Merge pull request #13 from pactumjs/feat/openapi3-support
Feat: openapi3 support
2 parents 851ca14 + 62964ce commit 89cbb85

File tree

10 files changed

+364
-28
lines changed

10 files changed

+364
-28
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ reports/
55
*.tgz
66
/.idea/
77
/package-lock.json
8+
coverage

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
![Size](https://img.shields.io/bundlephobia/minzip/pactum-swagger-coverage)
66
![Platform](https://img.shields.io/node/v/pactum)
77

8-
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).
8+
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).
99

1010
## Installation
1111

@@ -37,10 +37,16 @@ after(() => {
3737
const psc = require('pactum-swagger-coverage');
3838

3939
// name of the report file - defaults to "swagger-cov-report.json"
40-
psc.file = 'report-name.json';
40+
psc.reportFile = 'report-name.json';
4141

4242
// folder path for the report file - defaults to "./reports"
43-
psc.path = './reports-path';
43+
psc.reportPath = './reports-path';
44+
45+
/**
46+
* base path - defaults to `basePath` for swagger 2.0/openapi2 and first server url
47+
* or empty if either of them doesn't exist or not set explicitly
48+
*/
49+
psc.basePath = '/api/server/v1';
4450

4551
// Swagger json url of the server - defaults to ""
4652
psc.swaggerJsonUrl = "http://localhost:3010/api/server/v1/json";

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pactum-swagger-coverage",
3-
"version": "1.1.0",
3+
"version": "2.0.0",
44
"description": "Swagger api coverage report for pactum tests",
55
"main": "./src/index.js",
66
"types": "./src/index.d.ts",

src/config.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
const config = {
22
name: 'SwaggerCovReporter',
3-
path: './reports',
4-
file: 'swagger-cov-report.json',
3+
reportPath: './reports',
4+
reportFile: 'swagger-cov-report.json',
55
swaggerJsonUrl: "",
6-
swaggerYamlPath: ""
6+
swaggerYamlPath: "",
7+
basePath: "",
8+
oasTag: "swagger"
79
}
810

911
module.exports = config;

src/index.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ const testsCoveredApis = [];
88
const psc = {
99

1010
name: config.name,
11-
reportPath: config.path,
12-
file: config.file,
11+
reportPath: config.reportPath,
12+
reportFile: config.reportFile,
1313
swaggerJsonUrl: config.swaggerJsonUrl,
1414
swaggerYamlPath: config.swaggerYamlPath,
15+
basePath: config.basePath,
1516

1617
afterSpec(spec) {
1718
const _specApiPath = {}
@@ -27,13 +28,14 @@ const psc = {
2728
async end() {
2829
config.swaggerJsonUrl = this.swaggerJsonUrl;
2930
config.swaggerYamlPath = this.swaggerYamlPath;
30-
const coverage = await core.getSwaggerCoverage(testsCoveredApis)
31+
config.basePath = this.basePath;
32+
const coverage = await core.getSwaggerCoverage(testsCoveredApis);
3133

3234
if (!fs.existsSync(this.reportPath)) {
3335
fs.mkdirSync(this.reportPath, { recursive: true });
3436
}
3537

36-
fs.writeFileSync(path.resolve(this.reportPath, this.file), JSON.stringify(coverage, null, 2));
38+
fs.writeFileSync(path.resolve(this.reportPath, this.reportFile), JSON.stringify(coverage, null, 2));
3739

3840
},
3941

src/lib/core.js

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,28 @@ async function loadSwaggerYaml() {
2828
* @returns {Object} Swagger file object
2929
*/
3030
async function loadSwaggerJson() {
31-
let swaggerInfo = {};
31+
let apiDefinition = {};
3232
let swaggerJsonUrl = config.swaggerJsonUrl.trim();
3333
if (!swaggerJsonUrl || swaggerJsonUrl === null) {
3434
throw new PSCConfigurationError("Swagger definition cannot be empty! Provide 'swaggerYamlPath' or 'swaggerJsonUrl'.");
3535
}
3636

3737
try {
38-
swaggerInfo = await http.get(swaggerJsonUrl);
39-
return swaggerInfo;
38+
apiDefinition = await http.get(swaggerJsonUrl);
39+
return apiDefinition;
4040
} catch (error) {
4141
throw new PSCClientError(error);
4242
}
4343
}
4444

4545
/**
4646
* Fuction to all get api path's from swagger file
47-
* @param {Object} swaggerInfo
47+
* @param {Object} apiDefinition
4848
* @returns {Array} Array of API paths
4949
*/
50-
function getApiPaths(swaggerInfo) {
51-
const apiPaths = Object.keys(swaggerInfo.paths);
52-
apiPaths.forEach((apiPath, index) => apiPaths[index] = `${swaggerInfo.basePath}${apiPath}`);
50+
function getApiPaths(apiDefinition) {
51+
const apiPaths = Object.keys(apiDefinition.paths);
52+
apiPaths.forEach((apiPath, index) => apiPaths[index] = `${config.basePath}${apiPath}`);
5353
return apiPaths;
5454
}
5555

@@ -59,16 +59,20 @@ function getApiPaths(swaggerInfo) {
5959
* @returns {object} Swagger coverage stats
6060
*/
6161
async function getSwaggerCoverage(testsCoveredApis) {
62-
const swaggerInfo = config.swaggerYamlPath ? await loadSwaggerYaml() : await loadSwaggerJson();
63-
const apiPaths = getApiPaths(swaggerInfo);
62+
const apiDefinition = config.swaggerYamlPath ? await loadSwaggerYaml() : await loadSwaggerJson();
63+
if (apiDefinition.hasOwnProperty("openapi")) {
64+
config.oasTag = "openapi";
65+
}
66+
config.basePath = getBasePath(apiDefinition);
67+
const apiPaths = getApiPaths(apiDefinition);
6468
const apiCovList = apiPaths.map(apiPath =>
6569
!!testsCoveredApis.find(({ path }) => {
6670
return !!regExMatchOfPath(apiPath, path);
6771
}));
6872

6973
return {
70-
basePath: swaggerInfo.basePath,
71-
coverage: apiCovList.reduce((total, result, index, results) => result ? total + 1 / results.length : total, 0),
74+
basePath: config.basePath,
75+
coverage: Math.round(apiCovList.reduce((total, result, index, results) => result ? total + 1 / results.length : total, 0)*100)/100,
7276
coveredApiCount: apiPaths.filter((_, idx) => apiCovList[idx]).length,
7377
missedApiCount: apiPaths.filter((_, idx) => !apiCovList[idx]).length,
7478
totalApiCount: apiCovList.length,
@@ -77,6 +81,18 @@ async function getSwaggerCoverage(testsCoveredApis) {
7781
}
7882
}
7983

84+
/**
85+
* Function to return basePath
86+
* @param {object} apiDefinition
87+
* @returns
88+
*/
89+
function getBasePath(apiDefinition){
90+
if (apiDefinition.hasOwnProperty("openapi") && apiDefinition.servers && apiDefinition.servers[0].url) {
91+
apiDefinition.basePath = apiDefinition.servers[0].url;
92+
}
93+
return config.basePath || apiDefinition.basePath;
94+
}
95+
8096
/**
8197
* Function to RegEx match api paths
8298
* @param {String} apiPath

tests/spec.test.js renamed to tests/openapi2.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const handler = pactum.handler;
99
const psc = require('../src/index');
1010

1111
test.before(() => {
12-
psc.swaggerYamlPath = './tests/testObjects/swagger.yaml';
13-
psc.file = 'report.json'
12+
psc.swaggerYamlPath = './tests/testObjects/openapi2.yaml';
13+
psc.reportFile = 'report.json'
1414
reporter.add(psc);
1515
request.setBaseUrl('http://localhost:9393');
1616
handler.addInteractionHandler('get all ninjas', () => {
@@ -118,7 +118,7 @@ test('validate json reporter', async () => {
118118
assert.equal(report.hasOwnProperty("totalApiCount"), true)
119119
assert.equal(report.hasOwnProperty("coveredApiList"), true)
120120
assert.equal(report.hasOwnProperty("missedApiList"), true)
121-
assert.equal(report.coverage, 0.6666666666666666);
121+
assert.equal(report.coverage, 0.67);
122122
assert.equal(report.coveredApiCount, 4);
123123
assert.equal(report.missedApiCount, 2);
124124
assert.equal(report.totalApiCount, 6);

tests/openapi3.test.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
const test = require('uvu').test;
2+
const assert = require('uvu/assert');
3+
const pactum = require('pactum');
4+
const reporter = pactum.reporter;
5+
const mock = pactum.mock;
6+
const request = pactum.request;
7+
const handler = pactum.handler;
8+
9+
const psc = require('../src/index');
10+
11+
test.before(() => {
12+
psc.swaggerYamlPath = './tests/testObjects/openapi3.yaml';
13+
psc.basePath = '/api/server/v2'
14+
psc.reportFile = 'report-openapi3.json'
15+
reporter.add(psc);
16+
request.setBaseUrl('http://localhost:9393');
17+
handler.addInteractionHandler('get all ninjas', () => {
18+
return {
19+
request: {
20+
method: 'GET',
21+
path: '/api/server/v2/getallninjas'
22+
},
23+
response: {
24+
status: 200
25+
}
26+
}
27+
});
28+
handler.addInteractionHandler('get ninjas by rank', (ctx) => {
29+
return {
30+
request: {
31+
method: 'GET',
32+
path: `/api/server/v2/getninjas/${ctx.data}`
33+
},
34+
response: {
35+
status: 200
36+
}
37+
}
38+
});
39+
handler.addInteractionHandler('get ninja by rank and name', (ctx) => {
40+
return {
41+
request: {
42+
method: 'GET',
43+
path: `/api/server/v2/getninja/${ctx.data.rank}/${ctx.data.name}`
44+
},
45+
response: {
46+
status: 200
47+
}
48+
}
49+
});
50+
51+
handler.addInteractionHandler('get health', () => {
52+
return {
53+
request: {
54+
method: 'GET',
55+
path: `/api/server/v2/health`
56+
},
57+
response: {
58+
status: 200
59+
}
60+
}
61+
});
62+
return mock.start();
63+
});
64+
65+
test.after(() => {
66+
return mock.stop();
67+
});
68+
69+
test('spec passed', async () => {
70+
await pactum.spec()
71+
.useInteraction('get all ninjas')
72+
.get('/api/server/v2/getallninjas')
73+
.expectStatus(200);
74+
});
75+
76+
test('spec passed - additional path params', async () => {
77+
await pactum.spec()
78+
.useInteraction('get ninjas by rank', "jounin")
79+
.get('/api/server/v2/getninjas/jounin')
80+
.expectStatus(200);
81+
});
82+
83+
test('spec passed - no path params', async () => {
84+
await pactum.spec()
85+
.useInteraction('get health')
86+
.get('/api/server/v2/health')
87+
.expectStatus(200);
88+
});
89+
90+
test('spec passed - different api path with path params', async () => {
91+
await pactum.spec()
92+
.useInteraction('get ninja by rank and name', {rank: "jounin", name: "kakashi"})
93+
.get('/api/server/v2/getninja/jounin/kakashi')
94+
.expectStatus(200);
95+
});
96+
97+
test('spec failed', async () => {
98+
try {
99+
await pactum.spec()
100+
.get('/api/server/v2/getallninjas')
101+
.expectStatus(200);
102+
} catch (error) {
103+
console.log(error);
104+
}
105+
});
106+
107+
test('run reporter', async () => {
108+
await reporter.end();
109+
});
110+
111+
test('validate json reporter', async () => {
112+
const report = require('../reports/report-openapi3.json');
113+
console.log(JSON.stringify(report, null, 2));
114+
assert.equal(Object.keys(report).length, 7);
115+
assert.equal(report.hasOwnProperty("basePath"), true)
116+
assert.equal(report.hasOwnProperty("coverage"), true)
117+
assert.equal(report.hasOwnProperty("coveredApiCount"), true)
118+
assert.equal(report.hasOwnProperty("missedApiCount"), true)
119+
assert.equal(report.hasOwnProperty("totalApiCount"), true)
120+
assert.equal(report.hasOwnProperty("coveredApiList"), true)
121+
assert.equal(report.hasOwnProperty("missedApiList"), true)
122+
assert.equal(report.coverage, 0.57);
123+
assert.equal(report.coveredApiCount, 4);
124+
assert.equal(report.missedApiCount, 3);
125+
assert.equal(report.totalApiCount, 7);
126+
assert.equal(report.coveredApiList.length, 4);
127+
assert.equal(report.missedApiList.length, 3);
128+
});
129+
130+
test.run();

tests/testObjects/swagger.yaml renamed to tests/testObjects/openapi2.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ paths:
5959
get:
6060
tags: ["Ninjas"]
6161
description: "Get Ninja details by Clan and Rank"
62-
operationId: "getNinjaByRank"
62+
operationId: "getNinjaByClanRank"
6363
parameters:
6464
- name: clan
6565
in: path
@@ -82,7 +82,7 @@ paths:
8282
get:
8383
tags: ["Ninjas"]
8484
description: "Get Ninja details by Name"
85-
operationId: "getNinjaByRank"
85+
operationId: "getNinjaByName"
8686
parameters:
8787
- name: name
8888
in: path
@@ -100,7 +100,7 @@ paths:
100100
get:
101101
tags: ["Ninjas"]
102102
description: "Get Ninja details by Rank and name"
103-
operationId: "getNinjaByRank"
103+
operationId: "getNinjaByRankName"
104104
parameters:
105105
- name: rank
106106
in: path

0 commit comments

Comments
 (0)