* add validation for final state via

* add validation for azure async operation

* fix comment

* add live validation for LRO final get

* fix comments

* fix comment

* add more ARM validation

* update post man rule generation

* improve postman assertion

* fix test

* Azure-AsyncOperation is prior

* fix print detail response log

* fix lint error

* fix baseUri

* update snapshot

* generate examples for LRO final get

* fix lint error

* update change log

* update package lock

---------

Co-authored-by: jianyexi <jianyxi@microsoft.com>
Co-authored-by: Lei Ni <7233663+leni-msft@users.noreply.github.com>
This commit is contained in:
Jianye Xi 2023-02-23 15:06:17 +08:00 коммит произвёл GitHub
Родитель a993efe883
Коммит 34676f38e4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 1289 добавлений и 721 удалений

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

@ -1,5 +1,12 @@
# Change Log - oav
## 02/23/2023 3.2.6
- Improved the API scenario long running operation pooling mechanism.
- support for checking final-via-state
- update the mechanism of gettig the lro polling url.
- Support generating the ARM rules for api scenario.
## 02/19/2023 3.2.6
- LiveValidator - logging INVALID_TYPE error for additionalProperties for RpaaS call

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

@ -154,6 +154,8 @@ const stepIsFatal = (sr: StepResult) => sr.runtimeError && sr.runtimeError.lengt
const stepIsFailed = (sr: StepResult) =>
(sr.liveValidationResult && sr.liveValidationResult.requestValidationResult.errors.length > 0) ||
(sr.liveValidationResult && sr.liveValidationResult.responseValidationResult.errors.length > 0) ||
(sr.liveValidationForLroFinalGetResult &&
sr.liveValidationForLroFinalGetResult.responseValidationResult.errors.length > 0) ||
(sr.roundtripValidationResult && sr.roundtripValidationResult.errors.length > 0);
const asMarkdownStepResult = (sr: StepResult): ApiScenarioMarkdownStepResult => {
@ -167,6 +169,9 @@ const asMarkdownStepResult = (sr: StepResult): ApiScenarioMarkdownStepResult =>
const failedErrorsCount =
(sr.liveValidationResult ? sr.liveValidationResult.requestValidationResult.errors.length : 0) +
(sr.liveValidationResult ? sr.liveValidationResult.responseValidationResult.errors.length : 0) +
(sr.liveValidationForLroFinalGetResult
? sr.liveValidationForLroFinalGetResult.responseValidationResult.errors.length
: 0) +
(sr.roundtripValidationResult ? sr.roundtripValidationResult.errors.length : 0);
const r: ApiScenarioMarkdownStepResult = {

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

@ -60,6 +60,7 @@ export interface StepResult {
runtimeError?: RuntimeError[];
liveValidationResult?: RequestResponseLiveValidationResult;
roundtripValidationResult?: LiveValidationResult;
liveValidationForLroFinalGetResult?: RequestResponseLiveValidationResult;
}
export interface RuntimeError {
@ -173,14 +174,34 @@ export class NewmanReportValidator {
}
const payload = this.convertToLiveValidationPayload(it);
let lroFinalPayLoad;
// associate final get response to the
if (it.annotation.type === "LRO" && [200, 201, 202].includes(it.response.statusCode)) {
const lroFinalGetExecution = this.getLROFinalResponse(
newmanReport.executions,
it.annotation.step
);
if (lroFinalGetExecution?.response.statusCode === 200) {
lroFinalPayLoad = this.convertToLROLiveValidationPayload(it, lroFinalGetExecution);
}
}
let payloadFilePath;
let lroPayloadFilePath;
if (this.opts.savePayload) {
payloadFilePath = `./payloads/${it.annotation.itemName}.json`;
await this.fileLoader.writeFile(
path.resolve(path.dirname(this.opts.reportOutputFilePath), payloadFilePath),
JSON.stringify(payload, null, 2)
);
if (lroFinalPayLoad) {
lroPayloadFilePath = `./payloads/${it.annotation.itemName}-finalResult.json`;
await this.fileLoader.writeFile(
path.resolve(path.dirname(this.opts.reportOutputFilePath), lroPayloadFilePath),
JSON.stringify(lroFinalPayLoad, null, 2)
);
}
}
const matchedStep = this.getMatchedStep(it.annotation.step);
@ -207,24 +228,38 @@ export class NewmanReportValidator {
let liveValidationResult = undefined;
let roundtripValidationResult = undefined;
let liveValidationForLroFinalGetResult = undefined;
let specFilePath = undefined;
if (matchedStep.type === "restCall" && !matchedStep.externalReference) {
if (this.opts.generateExample) {
const statusCode = `${it.response.statusCode}`;
let statusCodes = {
[statusCode]: {
headers: payload.liveResponse.headers,
body: payload.liveResponse.body,
},
};
if (lroFinalPayLoad) {
const statusCode = lroFinalPayLoad.liveResponse.statusCode;
statusCodes[statusCode] = {
headers: lroFinalPayLoad.liveResponse.headers,
body: lroFinalPayLoad.liveResponse.body,
};
}
const generatedExample: SwaggerExample = {
operationId: matchedStep.operationId,
title: matchedStep.step,
description: matchedStep.description,
parameters: matchedStep._resolvedParameters!,
responses: {
[statusCode]: {
headers: payload.liveResponse.headers,
body: payload.liveResponse.body,
},
...statusCodes,
},
};
const exampleFilePath = `./examples/${matchedStep.operationId}_${statusCode}.json`;
const exampleFilePath = `./examples/${matchedStep.operationId}_${Object.keys(
statusCodes
).join("_")}.json`;
await this.fileLoader.writeFile(
path.resolve(path.dirname(this.opts.reportOutputFilePath), exampleFilePath),
JSON.stringify(generatedExample, null, 2)
@ -236,6 +271,11 @@ export class NewmanReportValidator {
? await this.liveValidator.validateLiveRequestResponse(payload)
: undefined;
liveValidationForLroFinalGetResult =
!this.opts.skipValidation && lroFinalPayLoad && matchedStep.isManagementPlane
? await this.liveValidator.validateLiveRequestResponse(lroFinalPayLoad)
: undefined;
// // Roundtrip validation
// if (
// !this.opts.skipValidation &&
@ -271,6 +311,7 @@ export class NewmanReportValidator {
stepName: it.annotation.step,
liveValidationResult,
roundtripValidationResult,
liveValidationForLroFinalGetResult,
});
}
}
@ -295,28 +336,28 @@ export class NewmanReportValidator {
};
}
// private convertToLROLiveValidationPayload(
// putReq: NewmanExecution,
// getReq: NewmanExecution
// ): RequestResponsePair {
// const request = putReq.request;
// const response = getReq.response;
// const liveRequest: LiveRequest = {
// url: request.url,
// method: request.method.toLowerCase(),
// headers: request.headers,
// body: this.parseBody(request.body),
// };
// const liveResponse: LiveResponse = {
// statusCode: `${response.statusCode}`,
// headers: response.headers,
// body: this.parseBody(response.body),
// };
// return {
// liveRequest,
// liveResponse,
// };
// }
private convertToLROLiveValidationPayload(
putReq: NewmanExecution,
getReq: NewmanExecution
): RequestResponsePair {
const request = putReq.request;
const response = getReq.response;
const liveRequest: LiveRequest = {
url: request.url,
method: request.method.toLowerCase(),
headers: request.headers,
body: this.parseBody(request.body),
};
const liveResponse: LiveResponse = {
statusCode: `${response.statusCode}`,
headers: response.headers,
body: this.parseBody(response.body),
};
return {
liveRequest,
liveResponse,
};
}
// body may not be json string
private parseBody(body: string): any {
@ -348,11 +389,11 @@ export class NewmanReportValidator {
return result;
}
// private getLROFinalResponse(executions: NewmanExecution[], initialStep: string) {
// return executions.find(
// (it) => it.annotation?.type === "finalGet" && it.annotation.step === initialStep
// );
// }
private getLROFinalResponse(executions: NewmanExecution[], initialStep: string) {
return executions.find(
(it) => it.annotation?.type === "finalGet" && it.annotation.step === initialStep
);
}
private getMatchedStep(stepName: string): Step | undefined {
return this.scenario.steps?.find((s) => s.step === stepName);

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

@ -0,0 +1,146 @@
import { Item } from "postman-collection";
import { StepArmTemplate, StepResponseAssertion, StepRestCall } from "./apiScenarioTypes";
type HttpMethod = "put" | "get" | "post" | "patch" | "delete" | "head" | "option";
type OpenapiType = "Dataplane" | "Management";
type AssertMatchParameter = { item?: Item; step?: StepArmTemplate | StepRestCall; opts?: any };
export type CallType = "lroPolling" | "lroFinalGet" | "stepCall" | "armTemplateCall";
export type AssertionRule = {
name: string;
assertion?: { stepAssertion?: StepResponseAssertion; postmanTestScript?: string[] };
conditions: {
openapiTypes: OpenapiType | OpenapiType[];
httpMethods: HttpMethod | HttpMethod[];
callTypes: CallType | CallType[];
isAsync?: boolean;
match?: (params: AssertMatchParameter) => boolean;
};
};
// RPC-Async-V1-08 Async PATCH should return 202.
const AsyncPatchShouldReturn202: AssertionRule = {
name: "AsyncPatchShouldReturn202",
assertion: { stepAssertion: { 202: {} } },
conditions: {
openapiTypes: "Management",
isAsync: true,
httpMethods: "patch",
callTypes: "stepCall",
},
};
// RPC-Async-V1-09 Async DELETE should return 202.
const AsyncDeleteShouldReturn202: AssertionRule = {
name: "AsyncDeleteShouldReturn202",
assertion: { stepAssertion: { 202: {} } },
conditions: {
openapiTypes: "Management",
isAsync: true,
httpMethods: "delete",
callTypes: "stepCall",
},
};
// RPC-Async-V1-11 Async POST should return 202.
const AsyncPostShouldReturn202: AssertionRule = {
name: "AsyncPostShouldReturn202",
assertion: { stepAssertion: { 202: {} } },
conditions: {
openapiTypes: "Management",
isAsync: true,
httpMethods: "post",
callTypes: "stepCall",
},
};
//RPC-Async-V1-01 Async PUT should return 201/200.
const AsyncPutShouldReturn201: AssertionRule = {
name: "AsyncPutShouldReturn201",
assertion: { stepAssertion: { 201: {}, 200: {} } },
conditions: {
openapiTypes: "Management",
isAsync: true,
httpMethods: "put",
callTypes: "stepCall",
},
};
// RPC-Async-V1-07 Location header must be supported for all async operations that return 202. Azure-AsyncOperation header is optional.
const Async202ResponseShouldIncludeLocationHeader: AssertionRule = {
name: "Async202ResponseShouldIncludeLocationHeader",
assertion: {
postmanTestScript: ['if(pm.response.code === 202) { pm.response.to.have.header("Location");}'],
},
conditions: {
openapiTypes: "Management",
isAsync: true,
httpMethods: ["put", "patch", "post", "delete"],
callTypes: "stepCall",
},
};
const ArmTemplateStatusCodeCheck: AssertionRule = {
name: "ArmTemplateStatusCodeCheck",
assertion: {
postmanTestScript: [
"pm.response.to.be.success;",
'pm.expect(pm.response.json().status).to.be.oneOf(["Succeeded", "Accepted", "Running", "Ready", "Creating", "Created", "Deleting", "Deleted", "Canceled", "Updating"]);',
],
},
conditions: {
openapiTypes: "Management",
httpMethods: "put",
callTypes: "armTemplateCall",
},
};
const DetailResponseLog: AssertionRule = {
name: "DetailResponseLog",
assertion: {
postmanTestScript: ["console.log(pm.response.text());"],
},
conditions: {
openapiTypes: ["Management", "Dataplane"],
httpMethods: ["put", "get", "delete", "patch", "option", "post", "head"],
callTypes: ["armTemplateCall", "lroFinalGet", "lroPolling", "stepCall"],
match: (params: AssertMatchParameter) => {
return params.opts?.verbose === true;
},
},
};
// RPC-Async-V1-06 x-ms-long-running-operation-options should indicate the type of response header to track the async operation.
// Here is for checking the case of final-state-via:azureAsyncOperation.
const AzureAsyncOperationFinalStateCheck: AssertionRule = {
name: "AzureAsyncOperationFinalStateCheck",
assertion: {
stepAssertion: {
"200": [{ test: "/body/properties", expression: "to.be.not.undefined" }],
},
},
conditions: {
openapiTypes: "Management",
httpMethods: ["put"],
callTypes: "lroFinalGet",
match: (params: AssertMatchParameter) => {
const step = params.step;
return (
step?.type === "restCall" &&
step.operation?.["x-ms-long-running-operation-options"]?.["final-state-via"] ===
"azure-async-operation"
);
},
},
};
export const postmanArmRules = [
AsyncPatchShouldReturn202,
AsyncDeleteShouldReturn202,
AsyncPostShouldReturn202,
AsyncPutShouldReturn201,
Async202ResponseShouldIncludeLocationHeader,
ArmTemplateStatusCodeCheck,
AzureAsyncOperationFinalStateCheck,
DetailResponseLog,
];

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

@ -13,7 +13,13 @@ import {
VariableScope,
} from "postman-collection";
import { urlParse } from "@azure-tools/openapi-tools-common";
import { xmsLongRunningOperation, xmsSkipUrlEncoding } from "../util/constants";
import { isArray } from "lodash";
import {
xmsLongRunningOperation,
xmsLongRunningOperationOptions,
xmsLongRunningOperationOptionsField,
xmsSkipUrlEncoding,
} from "../util/constants";
import { JsonLoader } from "../swagger/jsonLoader";
import { usePseudoRandom } from "../util/utils";
import {
@ -39,6 +45,7 @@ import {
import { DEFAULT_ARM_API_VERSION } from "./constants";
import * as PostmanHelper from "./postmanHelper";
import { VariableEnv } from "./variableEnv";
import { CallType, postmanArmRules } from "./postmanAssertionRules";
export interface PostmanCollectionRunnerClientOption {
scenarioFile: string;
@ -65,6 +72,54 @@ interface PostmanAzureKeyAuthOption {
type PostmanAuthOption = PostmanAADTokenAuthOption | PostmanAzureKeyAuthOption;
function generatePostmanAssertion(parameter: {
step: StepRestCall | StepArmTemplate;
item: Item;
type: CallType;
opts?: any;
}) {
const scripts: string[] = [];
if (parameter.step.type === "armTemplateDeployment") {
return;
}
const rules = postmanArmRules;
for (const rule of rules) {
const conditions = rule.conditions;
const httpMethods = isArray(conditions.httpMethods)
? conditions.httpMethods
: [conditions.httpMethods];
const openapiTypes = isArray(conditions.openapiTypes)
? conditions.openapiTypes
: [conditions.openapiTypes];
const callTypes = isArray(conditions.callTypes) ? conditions.callTypes : [conditions.callTypes];
if (
!!(parameter.step as StepRestCall).isManagementPlane ===
openapiTypes.includes("Management") &&
httpMethods.includes(parameter.step.operation?._method as any) &&
callTypes.includes(parameter.type) &&
(conditions.isAsync === undefined ||
!!parameter.step.operation?.[xmsLongRunningOperation] === conditions.isAsync) &&
(!conditions.match || conditions.match(parameter))
) {
if (rule.assertion?.stepAssertion) {
PostmanHelper.appendScripts(scripts, {
name: rule.name!,
types: ["ResponseDataAssertion", "StatusCodeAssertion"],
responseAssertion: rule.assertion.stepAssertion,
});
}
if (rule.assertion?.postmanTestScript) {
scripts.push(`pm.test("${rule.name}", function() {`);
rule.assertion.postmanTestScript.forEach((s) => scripts.push(s));
scripts.push("});");
}
}
}
if (scripts.length) {
PostmanHelper.addEvent(parameter.item.events, "test", scripts);
}
}
export class PostmanCollectionRunnerClient implements ApiScenarioRunnerClient {
private opts: PostmanCollectionRunnerClientOption;
private collection: Collection;
@ -656,9 +711,7 @@ pm.test("Stopped TestProxy recording", function() {
value: `{{${outputName}}}`,
});
}
const scriptTypes: PostmanHelper.TestScriptType[] = this.opts.verbose
? ["DetailResponseLog", "StatusCodeAssertion"]
: ["StatusCodeAssertion"];
const scriptTypes: PostmanHelper.TestScriptType[] = ["StatusCodeAssertion"];
if (step.responseAssertion) {
step.responseAssertion = env.resolveObjectValues(step.responseAssertion);
@ -681,14 +734,8 @@ pm.test("Stopped TestProxy recording", function() {
step: item.name,
};
item.description = JSON.stringify(metadata);
this.lroPoll(
itemGroup!,
item,
clientRequest.host,
postScripts,
false,
step.responseAssertion
);
this.lroPoll(itemGroup!, item, step, clientRequest.host, postScripts);
// generate final get
if (step.operation?._method !== "post") {
@ -696,9 +743,10 @@ pm.test("Stopped TestProxy recording", function() {
this.generateFinalGetItem(
item.name,
baseUri,
item.request.url,
item.name,
step.operation._method
step,
step.operation._method,
undefined,
undefined
)
);
}
@ -716,38 +764,83 @@ pm.test("Stopped TestProxy recording", function() {
if (postScripts.length > 0) {
PostmanHelper.addEvent(item.events, "test", postScripts);
}
generatePostmanAssertion({ step, type: "stepCall", item, opts: this.opts });
}
private lroPoll(
itemGroup: ItemGroup<Item>,
item: Item,
step: StepRestCall | StepArmTemplate,
baseUri: string,
postScripts: string[],
checkStatus: boolean = false,
responseAssertion?: StepResponseAssertion
postScripts: string[]
) {
const finalStateVia =
step.type === "restCall"
? step?.operation?.[xmsLongRunningOperationOptions]?.[xmsLongRunningOperationOptionsField]
: undefined;
const isManagementPlane = step.type === "armTemplateDeployment" || step.isManagementPlane;
if (this.opts.skipLroPoll) return;
const url = item.request.url;
const urlStr = `${baseUri}${url.getPathWithQuery()}`;
// For ARM put or patch operations , by default the final get is original url.
const isArmResourceCreate =
isManagementPlane && ["put", "patch"].includes(item.request.method.toLowerCase());
postScripts.push(
`
const pollingUrl = pm.response.headers.get("Location") || pm.response.headers.get("Azure-AsyncOperation") || pm.response.headers.get("Operation-Location");
// RPC-Async-V1-06 x-ms-long-running-operation-options should indicate the type of response header to track the async operation.
function getLroFinalGetUrl(finalStateVia) {
if (!finalStateVia) {
// by default, the final url header is Location for ARM, Operation-Location for dataplane.
return ${
isArmResourceCreate
? `'${urlStr}'`
: `pm.response.headers.get("Location") || pm.response.headers.get("Operation-Location")`
}
}
switch (finalStateVia) {
case "location": {
return pm.response.headers.get("Location");
}
case "azure-async-operation": {
return pm.response.headers.get("Azure-AsyncOperation");
}
case "original-uri": {
return "${urlStr}";
}
case "operation-location": {
return pm.response.headers.get("Operation-Location");
}
default:
return "";
}
}
function getProxyUrl(url) {
return "${this.opts.testProxy ?? ""}" ? url.replace("${baseUri}","${
this.opts.testProxy ?? ""
}") : url
}
const pollingUrl = pm.response.headers.get("Azure-AsyncOperation") || pm.response.headers.get("Location") || pm.response.headers.get("Operation-Location") || ${
isArmResourceCreate ? `'${urlStr}'` : "''"
}
if (pollingUrl) {
pm.variables.set("x_polling_url", ${
this.opts.testProxy
? `pollingUrl.replace("${baseUri}","${this.opts.testProxy}")`
: "pollingUrl"
});
pm.variables.set("x_polling_url", getProxyUrl(pollingUrl));
pm.variables.set("x_final_get_url", getProxyUrl(getLroFinalGetUrl("${finalStateVia ?? ""}")))
pm.variables.set("x_retry_after", "3");
}`
);
const { item: delayItem } = this.addNewItem("Blank", {
name: `_${item.name}_delay`,
request: {
url: "https://postman-echo.com/delay/{{x_retry_after}}",
method: "GET",
const { item: delayItem } = this.addNewItem(
"Blank",
{
name: `_${item.name}_delay`,
request: {
url: "https://postman-echo.com/delay/{{x_retry_after}}",
method: "GET",
},
},
});
baseUri
);
const delayItemMetadata: DelayItemMetadata = {
type: "delay",
lro_item_name: item.name,
@ -799,25 +892,10 @@ try {
`
);
if (checkStatus) {
PostmanHelper.appendScripts(pollerPostScripts, {
name: "armTemplate deployment status check",
types: ["StatusCodeAssertion", "ARMDeploymentStatusAssertion"],
});
}
if (responseAssertion) {
PostmanHelper.appendScripts(pollerPostScripts, {
name: "LRO response assertion",
types: ["ResponseDataAssertion"],
responseAssertion: responseAssertion,
});
}
if (postScripts.length > 0) {
PostmanHelper.addEvent(pollerItem.events, "test", pollerPostScripts);
}
generatePostmanAssertion({ step, type: "lroPolling", item: pollerItem, opts: this.opts });
itemGroup.items.add(pollerItem);
}
@ -825,12 +903,10 @@ try {
types: PostmanHelper.TestScriptType[] = ["StatusCodeAssertion"],
overwriteVariables?: Map<string, string>,
armTemplate?: ArmTemplate,
responseAssertion?: StepResponseAssertion
responseAssertion?: StepResponseAssertion,
name?: string
): string[] {
const scripts: string[] = [];
if (this.opts.verbose) {
types.push("DetailResponseLog");
}
if (overwriteVariables !== undefined) {
types.push("OverwriteVariables");
}
@ -841,7 +917,7 @@ try {
if (types.length > 0) {
// generate assertion from example
PostmanHelper.appendScripts(scripts, {
name: "response status code assertion.",
name: name || "response status code assertion.",
types: types,
variables: overwriteVariables,
armTemplate,
@ -901,9 +977,7 @@ try {
raw: JSON.stringify(body, null, 2),
});
item.request.addHeader({ key: "Content-Type", value: "application/json" });
const scriptTypes: PostmanHelper.TestScriptType[] = this.opts.verbose
? ["StatusCodeAssertion", "DetailResponseLog"]
: ["StatusCodeAssertion"];
const scriptTypes: PostmanHelper.TestScriptType[] = ["StatusCodeAssertion"];
const postScripts: string[] = [];
PostmanHelper.appendScripts(postScripts, {
@ -912,20 +986,20 @@ try {
variables: undefined,
});
this.lroPoll(itemGroup!, item, armEndpoint, postScripts, true);
this.lroPoll(itemGroup!, item, step, armEndpoint, postScripts);
if (postScripts.length > 0) {
// to be improved
PostmanHelper.addEvent(item.events, "test", postScripts);
}
const generatedGetScriptTypes: PostmanHelper.TestScriptType[] = this.opts.verbose
? ["DetailResponseLog", "ExtractARMTemplateOutput"]
: ["ExtractARMTemplateOutput"];
generatePostmanAssertion({ step, type: "armTemplateCall", item: item, opts: this.opts });
const generatedGetScriptTypes: PostmanHelper.TestScriptType[] = ["ExtractARMTemplateOutput"];
const generatedGetOperationItem = this.generateFinalGetItem(
item.name,
armEndpoint,
item.request.url,
step.step,
step,
"put",
generatedGetScriptTypes,
armTemplate
@ -936,11 +1010,11 @@ try {
private generateFinalGetItem(
name: string,
baseUri: string,
url: Url,
step: string,
step: StepRestCall | StepArmTemplate,
prevMethod: string = "put",
scriptTypes: PostmanHelper.TestScriptType[] = [],
armTemplate?: ArmTemplate
armTemplate?: ArmTemplate,
finalStateVia?: string
): Item {
const { item } = this.addNewItem(
"Blank",
@ -948,26 +1022,36 @@ try {
name: `_${name}_final_get`,
request: {
method: "GET",
url: "",
url: `{{x_final_get_url}}`,
},
},
baseUri
);
item.request.url = url;
const metadata: FinalGetItemMetadata = {
type: "finalGet",
lro_item_name: name,
step,
step: step.step,
};
item.description = JSON.stringify(metadata);
item.request.addHeader({ key: "Content-Type", value: "application/json" });
if (prevMethod !== "delete") {
scriptTypes.push("StatusCodeAssertion");
}
const postScripts = this.generatePostScripts(scriptTypes, undefined, armTemplate);
const postScripts = this.generatePostScripts(scriptTypes, undefined, armTemplate, undefined);
if (postScripts.length > 0) {
// to be improved
PostmanHelper.addEvent(item.events, "test", postScripts);
}
if (finalStateVia && finalStateVia !== "original-uri") {
PostmanHelper.addEvent(item.events, "prerequest", [
`pm.test("LRO final-state-via is valid", () =>
{
pm.expect(pm.variables.get("x_final_get_url")).to.be.not.undefined;
})`,
]);
}
generatePostmanAssertion({ step, type: "lroFinalGet", item: item, opts: this.opts });
return item;
}
}

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

@ -179,6 +179,7 @@ export function appendScripts(scripts: string[], parameter: TestScriptParameter)
if (parameter.types.includes("ResponseDataAssertion") && parameter.responseAssertion) {
assertions.push(generateResponseDataAssertionScript(parameter.responseAssertion));
}
if (assertions.length > 0) {
scripts.push(`pm.test("${parameter.name}", function() {`);
assertions.forEach((s) => scripts.push(s));

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

@ -104,14 +104,14 @@
}
},
"@azure/core-http": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.2.7.tgz",
"integrity": "sha512-TyGMeDm90mkRS8XzSQbSMD+TqnWL1XKGCh0x0QVGMD8COH2yU0q5SaHm/IBEBkzcq0u73NhS/p57T3KVSgUFqQ==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.3.1.tgz",
"integrity": "sha512-cur03BUwV0Tbv81bQBOLafFB02B6G++K6F2O3IMl8pSE2QlXm3cu11bfyBNlDUKi5U+xnB3GC63ae3athhkx6Q==",
"requires": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-tracing": "1.0.0-preview.13",
"@azure/core-util": "^1.1.0",
"@azure/core-util": "^1.1.1",
"@azure/logger": "^1.0.0",
"@types/node-fetch": "^2.5.0",
"@types/tunnel": "^0.0.3",
@ -125,6 +125,15 @@
"xml2js": "^0.4.19"
},
"dependencies": {
"@azure/core-util": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz",
"integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==",
"requires": {
"@azure/abort-controller": "^1.0.0",
"tslib": "^2.2.0"
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@ -151,14 +160,6 @@
"tslib": "^2.2.0"
}
},
"@azure/core-util": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.0.tgz",
"integrity": "sha512-+i93lNJNA3Pl3KSuC6xKP2jTL4YFeDfO6VNOaYdk0cppZcLCxt811gS878VsqsCisaltdhl9lhMzK5kbxCiF4w==",
"requires": {
"tslib": "^2.2.0"
}
},
"@azure/logger": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz",
@ -1103,9 +1104,9 @@
}
},
"@opentelemetry/api": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.2.0.tgz",
"integrity": "sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g=="
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.0.tgz",
"integrity": "sha512-IgMK9i3sFGNUqPMbjABm0G26g0QCKCUBfglhQ7rQq6WcxbKfEHRcmwsoER4hZcuYqJgkYn2OeuoJIv7Jsftp7g=="
},
"@postman/form-data": {
"version": "3.1.1",
@ -3039,12 +3040,13 @@
}
},
"eslint-module-utils": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
"integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz",
"integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==",
"dev": true,
"requires": {
"debug": "^3.2.7"
"debug": "^3.2.7",
"find-up": "^2.1.0"
},
"dependencies": {
"debug": {
@ -3178,9 +3180,9 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz",
"integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==",
"dev": true,
"requires": {
"estraverse": "^5.1.0"
@ -3576,6 +3578,15 @@
"to-regex-range": "^5.0.1"
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
"dev": true,
"requires": {
"locate-path": "^2.0.0"
}
},
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@ -3587,9 +3598,9 @@
}
},
"flatted": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz",
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
"dev": true
},
"fn.name": {
@ -5194,9 +5205,9 @@
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"dev": true
},
"jsonfile": {
@ -5309,6 +5320,16 @@
}
}
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
"integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
"dev": true,
"requires": {
"p-locate": "^2.0.0",
"path-exists": "^3.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -6126,6 +6147,30 @@
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
"dev": true
},
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"dev": true,
"requires": {
"p-try": "^1.0.0"
}
},
"p-locate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
"dev": true,
"requires": {
"p-limit": "^1.1.0"
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
"dev": true
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -6162,6 +6207,12 @@
"integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==",
"dev": true
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -6517,9 +6568,9 @@
"dev": true
},
"prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true
},
"prettier-linter-helpers": {
@ -6880,12 +6931,6 @@
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@ -7888,6 +7933,12 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
}
}
},
@ -8101,9 +8152,9 @@
},
"dependencies": {
"json5": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
@ -8636,7 +8687,7 @@
"xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
"dev": true
},
"xml-name-validator": {

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

@ -12,7 +12,7 @@ import { inversifyGetInstance } from "../lib/inversifyUtils";
import { JsonLoader } from "../lib/swagger/jsonLoader";
import { setDefaultOpts } from "../lib/swagger/loader";
import { SwaggerLoader, SwaggerLoaderOption } from "../lib/swagger/swaggerLoader";
import { getInputFiles } from "../lib/util/utils";
import { getInputFiles, resetPseudoRandomSeed } from "../lib/util/utils";
jest.setTimeout(9999999);
@ -97,6 +97,10 @@ function isCommonSpec(swagger:string) {
}
describe("Api Test rule based generator test", () => {
beforeEach(() => {
resetPseudoRandomSeed(0);
});
const specFolder = resolve(`${__dirname}/../regression/azure-rest-api-specs`);
const specPaths: string[] = glob.sync(
join(specFolder, "specification/**/resource-manager/readme.md"),

Разница между файлами не показана из-за своего большого размера Загрузить разницу