Add Passed opeartions to the report (#1044)
* add pass operations to reports * update package.json, package-lock.json and ChangeLog.md
This commit is contained in:
Родитель
d24383eb55
Коммит
9a2fcc1bf5
|
@ -1,5 +1,9 @@
|
|||
# Change Log - oav
|
||||
|
||||
## 09/12/2024 3.5.0
|
||||
|
||||
- Add names of `Passed Operations` in the HTML and JSON reports.
|
||||
|
||||
## 07/12/2024 3.4.0
|
||||
|
||||
- During example generation, fields `password`, `adminPassword`, and `pwd` are all generated with a single value of "<a-password-goes-here>" instead of random characters.
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface OperationCoverageResult {
|
|||
coveredOperationNumber: number;
|
||||
totalOperationNumber: number;
|
||||
coverage: number;
|
||||
coveredOperationIds: string[];
|
||||
uncoveredOperationIds: string[];
|
||||
}
|
||||
|
||||
|
@ -22,6 +23,7 @@ export class CoverageCalculator {
|
|||
coveredOperationNumber: 0,
|
||||
totalOperationNumber: 0,
|
||||
coverage: 0,
|
||||
coveredOperationIds: [],
|
||||
uncoveredOperationIds: [],
|
||||
};
|
||||
|
||||
|
@ -55,6 +57,7 @@ export class CoverageCalculator {
|
|||
allOperationIds.size === 0 ? 0 : coverageOperationIds.size / allOperationIds.size;
|
||||
ret.coveredOperationNumber = coverageOperationIds.size;
|
||||
ret.totalOperationNumber = allOperationIds.size;
|
||||
ret.coveredOperationIds = [...coverageOperationIds];
|
||||
const difference = [...allOperationIds].filter((x) => !coverageOperationIds.has(x));
|
||||
ret.uncoveredOperationIds = difference;
|
||||
return ret;
|
||||
|
@ -70,6 +73,7 @@ export class CoverageCalculator {
|
|||
coveredOperationNumber: 0,
|
||||
totalOperationNumber: 0,
|
||||
coverage: 0,
|
||||
coveredOperationIds: [],
|
||||
uncoveredOperationIds: [],
|
||||
};
|
||||
|
||||
|
@ -102,6 +106,7 @@ export class CoverageCalculator {
|
|||
allOperationIds.size === 0 ? 0 : coverageOperationIds.size / allOperationIds.size;
|
||||
result.coveredOperationNumber = coverageOperationIds.size;
|
||||
result.totalOperationNumber = allOperationIds.size;
|
||||
result.coveredOperationIds = [...coverageOperationIds];
|
||||
const difference = [...allOperationIds].filter((x) => !coverageOperationIds.has(x));
|
||||
result.uncoveredOperationIds = difference;
|
||||
|
||||
|
|
|
@ -376,6 +376,9 @@ class PostmanCollectionRunner {
|
|||
apiVersion: getApiVersionFromFilePath(specPath),
|
||||
unCoveredOperations: result.uncoveredOperationIds.length,
|
||||
coveredOperations: result.totalOperationNumber - result.uncoveredOperationIds.length,
|
||||
coveredOperationsList: result.coveredOperationIds.map((id) => {
|
||||
return { operationId: id };
|
||||
}),
|
||||
validationFailOperations: new Set(
|
||||
trafficValidationResult
|
||||
.filter((it) => key.indexOf(it.specFilePath!) !== -1 && it.errors!.length > 0)
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as path from "path";
|
|||
import * as Mustache from "mustache";
|
||||
import {
|
||||
OperationCoverageInfo,
|
||||
OperationMeta,
|
||||
TrafficValidationIssue,
|
||||
TrafficValidationOptions,
|
||||
} from "../swaggerValidator/trafficValidator";
|
||||
|
@ -52,9 +53,18 @@ export interface ErrorDefinition {
|
|||
link?: string;
|
||||
}
|
||||
|
||||
export interface ValidationPassOperationsFormatInner extends OperationMeta {
|
||||
readonly key: string;
|
||||
}
|
||||
|
||||
export interface ValidationPassOperationsFormat {
|
||||
readonly operationIdList: ValidationPassOperationsFormatInner[];
|
||||
}
|
||||
|
||||
export interface OperationCoverageInfoForRendering extends OperationCoverageInfo {
|
||||
specLinkLabel?: string;
|
||||
validationPassOperations?: number;
|
||||
validationPassOperationList: ValidationPassOperationsFormat[];
|
||||
generalErrorsInnerList: TrafficValidationIssueForRenderingInner[];
|
||||
}
|
||||
|
||||
|
@ -201,18 +211,77 @@ export class CoverageView {
|
|||
runtimeExceptions: element.runtimeExceptions,
|
||||
});
|
||||
});
|
||||
|
||||
const generalErrorsInnerOrigin = this.validationResultsForRendering.filter((x) => {
|
||||
return x.errors && x.errors.length > 0;
|
||||
});
|
||||
|
||||
this.coverageResults.forEach((element) => {
|
||||
const specLink = this.overrideLinkInReport
|
||||
? `${this.specLinkPrefix}/${element.spec?.substring(
|
||||
element.spec?.indexOf("specification")
|
||||
)}`
|
||||
: `${element.spec}`;
|
||||
|
||||
let errorOperationIds = generalErrorsInnerOrigin.map(
|
||||
(item) => item.operationInfo?.operationId
|
||||
);
|
||||
let passOperations: ValidationPassOperationsFormatInner[] = element.coveredOperationsList
|
||||
.filter((item) => errorOperationIds.indexOf(item.operationId) === -1)
|
||||
.map((item) => {
|
||||
return {
|
||||
key: item.operationId.split("_")[0],
|
||||
operationId: item.operationId,
|
||||
};
|
||||
});
|
||||
|
||||
const passOperationsInnerList: ValidationPassOperationsFormatInner[][] = Object.values(
|
||||
passOperations.reduce(
|
||||
(res: { [key: string]: ValidationPassOperationsFormatInner[] }, item) => {
|
||||
/* eslint-disable no-unused-expressions */
|
||||
res[item.key] ? res[item.key].push(item) : (res[item.key] = [item]);
|
||||
/* eslint-enable no-unused-expressions */
|
||||
return res;
|
||||
},
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
const passOperationsListFormat: ValidationPassOperationsFormat[] = [];
|
||||
passOperationsInnerList.forEach((element) => {
|
||||
passOperationsListFormat.push({
|
||||
operationIdList: element,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Sort untested operationId by bubble sort
|
||||
* Controlling the results of localeCompare can set the sorting method
|
||||
* X.localeCompare(Y) > 0 descending sort
|
||||
* X.localeCompare(Y) < 0 ascending sort
|
||||
*/
|
||||
for (let i = 0; i < passOperationsListFormat.length - 1; i++) {
|
||||
for (let j = 0; j < passOperationsListFormat.length - 1 - i; j++) {
|
||||
if (
|
||||
passOperationsListFormat[j].operationIdList[0].key.localeCompare(
|
||||
passOperationsListFormat[j + 1].operationIdList[0].key
|
||||
) > 0
|
||||
) {
|
||||
var temp = passOperationsListFormat[j];
|
||||
passOperationsListFormat[j] = passOperationsListFormat[j + 1];
|
||||
passOperationsListFormat[j + 1] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.coverageResultsForRendering.push({
|
||||
spec: specLink,
|
||||
specLinkLabel: element.spec?.substring(element.spec?.lastIndexOf("/") + 1),
|
||||
apiVersion: element.apiVersion,
|
||||
coveredOperations: element.coveredOperations,
|
||||
coveredOperationsList: element.coveredOperationsList,
|
||||
validationPassOperations: element.coveredOperations - element.validationFailOperations,
|
||||
validationPassOperationList: passOperationsListFormat,
|
||||
validationFailOperations: element.validationFailOperations,
|
||||
unCoveredOperations: element.unCoveredOperations,
|
||||
unCoveredOperationsList: element.unCoveredOperationsList,
|
||||
|
@ -235,9 +304,6 @@ export class CoverageView {
|
|||
} as any;
|
||||
});
|
||||
|
||||
const generalErrorsInnerOrigin = this.validationResultsForRendering.filter((x) => {
|
||||
return x.errors && x.errors.length > 0;
|
||||
});
|
||||
const generalErrorsInnerFormat: TrafficValidationIssueForRendering[][] = Object.values(
|
||||
generalErrorsInnerOrigin.reduce(
|
||||
(res: { [key: string]: TrafficValidationIssueForRendering[] }, item) => {
|
||||
|
|
|
@ -53,6 +53,7 @@ export interface OperationCoverageInfo {
|
|||
readonly spec: string;
|
||||
readonly apiVersion: string;
|
||||
readonly coveredOperations: number;
|
||||
readonly coveredOperationsList: OperationMeta[];
|
||||
readonly validationFailOperations: number;
|
||||
readonly unCoveredOperations: number;
|
||||
readonly unCoveredOperationsList: OperationMeta[];
|
||||
|
@ -290,11 +291,13 @@ export class TrafficValidator {
|
|||
let coveredOperations: number;
|
||||
let coverageRate: number;
|
||||
let validationFailOperations: number;
|
||||
let coveredOperationsList: OperationMeta[];
|
||||
let unCoveredOperationsList: unCoveredOperationsFormatInner[];
|
||||
this.operationSpecMapper.forEach((value: string[], key: string) => {
|
||||
// identify the spec has been match traffic file
|
||||
let isMatch: boolean = true;
|
||||
const unCoveredOperationsListFormat: unCoveredOperationsFormat[] = [];
|
||||
coveredOperationsList = [];
|
||||
unCoveredOperationsList = [];
|
||||
if (this.trafficOperation.get(key) === undefined) {
|
||||
coveredOperations = 0;
|
||||
|
@ -308,6 +311,7 @@ export class TrafficValidator {
|
|||
this.coverageData.set(key, coverageRate);
|
||||
const unValidatedOperations = [...value];
|
||||
validatedOperations!.forEach((element) => {
|
||||
coveredOperationsList.push({ operationId: element });
|
||||
unValidatedOperations.splice(unValidatedOperations.indexOf(element), 1);
|
||||
});
|
||||
unValidatedOperations.forEach((element) => {
|
||||
|
@ -356,6 +360,18 @@ export class TrafficValidator {
|
|||
return 0;
|
||||
});
|
||||
|
||||
const sortedCoveredOperationsList = coveredOperationsList.sort(function (op1, op2) {
|
||||
const opId1 = op1.operationId;
|
||||
const opId2 = op2.operationId;
|
||||
if (opId1 < opId2) {
|
||||
return -1;
|
||||
}
|
||||
if (opId1 > opId2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* Sort untested operationId by bubble sort
|
||||
* Controlling the results of localeCompare can set the sorting method
|
||||
|
@ -385,6 +401,7 @@ export class TrafficValidator {
|
|||
unCoveredOperations: value.length - coveredOperations,
|
||||
totalOperations: value.length,
|
||||
validationFailOperations: validationFailOperations,
|
||||
coveredOperationsList: sortedCoveredOperationsList,
|
||||
unCoveredOperationsList: sortedUnCoveredOperationsList,
|
||||
unCoveredOperationsListGen: unCoveredOperationsListFormat,
|
||||
});
|
||||
|
|
|
@ -280,7 +280,52 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.testPassed {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.testPassed table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.testPassed .table-body {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.testPassed .table-body>table>tbody>tr>td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.testPassed .table-body table tbody tr td {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.testPassed table tbody tr td:nth-child(-n + 2) {
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.testPassed .operationTag {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.testPassed .operationTag .tag {
|
||||
color: var(--report-base-colo);
|
||||
background-color: var(--report-bg-blue);
|
||||
border: 1px solid var(--report-bg-blue);
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
margin: 4px 2px;
|
||||
line-height: 30px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notTested table,
|
||||
.testPassed table,
|
||||
.testFailed table,
|
||||
.runtimeExceptions table,
|
||||
.innerTable table {
|
||||
|
@ -556,7 +601,11 @@
|
|||
</span>
|
||||
( {{validationFailOperations}} )
|
||||
</th>
|
||||
<th style="background-color: var(--report-bg-green);">Pass ( {{validationPassOperations}} )</th>
|
||||
<th style="background-color: var(--report-bg-green);">
|
||||
Pass<span class="hashLinkClass" data-hash-link="testPassed_{{index}}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" class="svg_dd790ee3 x-hidden-focus" focusable="false" width="25" height="15" style="vertical-align: middle;"><path d="M1707 715q76 27 139 75t108 111 69 138 25 156q0 106-40 199t-110 162-163 110-199 41h-512q-106 0-199-40t-162-110-110-163-41-199q0-106 40-199t110-162 163-110 199-41h171q0 35-13 66t-37 54-55 36-66 14q-71 0-133 27t-108 73-73 109-27 133q0 71 27 133t73 108 108 73 133 27h512q70 0 132-27t109-73 73-108 27-133q0-92-46-168t-124-123V715zM171 683q0 91 46 167t124 124v189q-76-27-139-75T94 977 25 839 0 683q0-106 40-199t110-162 163-110 199-41h512q106 0 199 40t162 110 110 163 41 199q0 106-40 199t-110 162-163 110-199 41H853q0-35 13-66t37-54 54-37 67-14q70 0 132-27t109-73 73-108 27-133q0-70-26-132t-73-109-109-74-133-27H512q-71 0-133 27t-108 73-73 109-27 133z" class="x-hidden-focus"></path></svg>
|
||||
( {{validationPassOperations}} )
|
||||
</span>
|
||||
</th>
|
||||
<th style="background-color: var(--report-bg-blue);">
|
||||
<div class="file">
|
||||
{{specLinkLabel}}<a target="_blank" href="{{spec}}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" class="svg_dd790ee3 x-hidden-focus" focusable="false" width="25" height="15" style="vertical-align: middle;"><path d="M1707 715q76 27 139 75t108 111 69 138 25 156q0 106-40 199t-110 162-163 110-199 41h-512q-106 0-199-40t-162-110-110-163-41-199q0-106 40-199t110-162 163-110 199-41h171q0 35-13 66t-37 54-55 36-66 14q-71 0-133 27t-108 73-73 109-27 133q0 71 27 133t73 108 108 73 133 27h512q70 0 132-27t109-73 73-108 27-133q0-92-46-168t-124-123V715zM171 683q0 91 46 167t124 124v189q-76-27-139-75T94 977 25 839 0 683q0-106 40-199t110-162 163-110 199-41h512q106 0 199 40t162 110 110 163 41 199q0 106-40 199t-110 162-163 110-199 41H853q0-35 13-66t37-54 54-37 67-14q70 0 132-27t109-73 73-108 27-133q0-70-26-132t-73-109-109-74-133-27H512q-71 0-133 27t-108 73-73 109-27 133z" class="x-hidden-focus"></path></svg></a>
|
||||
|
@ -667,6 +716,33 @@
|
|||
</article>
|
||||
</section>
|
||||
|
||||
<section class="testPassed">
|
||||
<h2 class="title" id="testPassed_{{index}}">Passed Operations ( {{validationPassOperations}} )</h2>
|
||||
<article>
|
||||
<div class="table-body">
|
||||
<table summary="Operations Test Passed" cellspacing="0" cellpadding="0">
|
||||
<colgroup>
|
||||
<col style="width: 15%" />
|
||||
<col style="width: 85%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{{#validationPassOperationList}}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="operationTag">
|
||||
{{#operationIdList}}
|
||||
<span class="tag">{{operationId}}<a target="_blank" href="{{spec}}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" class="svg_dd790ee3 x-hidden-focus" focusable="false" width="25" height="15" style="vertical-align: middle;"><path d="M1707 715q76 27 139 75t108 111 69 138 25 156q0 106-40 199t-110 162-163 110-199 41h-512q-106 0-199-40t-162-110-110-163-41-199q0-106 40-199t110-162 163-110 199-41h171q0 35-13 66t-37 54-55 36-66 14q-71 0-133 27t-108 73-73 109-27 133q0 71 27 133t73 108 108 73 133 27h512q70 0 132-27t109-73 73-108 27-133q0-92-46-168t-124-123V715zM171 683q0 91 46 167t124 124v189q-76-27-139-75T94 977 25 839 0 683q0-106 40-199t110-162 163-110 199-41h512q106 0 199 40t162 110 110 163 41 199q0 106-40 199t-110 162-163 110-199 41H853q0-35 13-66t37-54 54-37 67-14q70 0 132-27t109-73 73-108 27-133q0-70-26-132t-73-109-109-74-133-27H512q-71 0-133 27t-108 73-73 109-27 133z" class="x-hidden-focus"></path></svg></a></span>
|
||||
{{/operationIdList}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/validationPassOperationList}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{/resultsForRendering}}
|
||||
</div>
|
||||
|
|
|
@ -235,6 +235,18 @@ export async function validateTraffic(
|
|||
operationIds: item.unCoveredOperationsList.map((opeartion) => opeartion.operationId),
|
||||
};
|
||||
}),
|
||||
passedOperationsList: validator.operationCoverageResult.map((item) => {
|
||||
return {
|
||||
spec: item.spec,
|
||||
operationIds: item.coveredOperationsList
|
||||
.map((opeartion) => opeartion.operationId)
|
||||
.filter((id) => {
|
||||
return !trafficValidationResult.some(
|
||||
(error) => error.operationInfo?.operationId === id
|
||||
);
|
||||
}),
|
||||
};
|
||||
}),
|
||||
failedOperations: validator.operationCoverageResult
|
||||
.map((item) => item.validationFailOperations)
|
||||
.reduce((a, b) => a + b, 0),
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "oav",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "oav",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "oav",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation",
|
||||
"email": "azsdkteam@microsoft.com",
|
||||
|
|
|
@ -7,6 +7,14 @@ Object {
|
|||
"apiVersion": "2019-02-02",
|
||||
"coverageRate": 0.14285714285714285,
|
||||
"coveredOperations": 2,
|
||||
"coveredOperationsList": Array [
|
||||
Object {
|
||||
"operationId": "Table_Delete",
|
||||
},
|
||||
Object {
|
||||
"operationId": "Table_Query",
|
||||
},
|
||||
],
|
||||
"totalOperations": 14,
|
||||
"unCoveredOperations": 12,
|
||||
"unCoveredOperationsList": Array [
|
||||
|
|
Загрузка…
Ссылка в новой задаче