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:
Zhenhua Hu 2024-09-13 08:54:58 +08:00 коммит произвёл GitHub
Родитель d24383eb55
Коммит 9a2fcc1bf5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 198 добавлений и 7 удалений

Просмотреть файл

@ -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),

4
package-lock.json сгенерированный
Просмотреть файл

@ -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 [