in lib/apiScenario/apiScenarioLoader.ts [422:680]
private async loadStepRestCall(
rawStep: RawStepOperation | RawStepExample,
ctx: ApiScenarioContext
): Promise<StepRestCall> {
if (rawStep.step !== undefined && ctx.stepTracking.has(rawStep.step)) {
throw new Error(`Duplicated step name: ${rawStep.step}`);
}
const step: StepRestCall = {
type: "restCall",
step: rawStep.step ?? `_${ctx.stepIndex}`,
description: rawStep.description,
operationId: "",
operation: {} as Operation,
parameters: {} as SwaggerExample["parameters"],
responses: {} as SwaggerExample["responses"],
outputVariables: rawStep.outputVariables ?? {},
...convertVariables(rawStep.variables),
authentication:
this.loadAuthentication(rawStep.authentication) ??
ctx.scenario?.authentication ??
ctx.scenarioDef.authentication,
};
const getVariable = (
name: string,
...scopes: Array<VariableScope | undefined>
): Variable | undefined => {
if (!scopes || scopes.length === 0) {
scopes = [step, ctx.scenario, ctx.scenarioDef];
}
for (const scope of scopes) {
if (scope && scope.variables[name]) {
return scope.variables[name];
}
}
for (const scope of scopes) {
if (scope && scope.requiredVariables.includes(name)) {
return {
type: "string",
value: `$(${name})`,
};
}
}
if (
(ctx.scenarioDef.scope === "ResourceGroup" &&
["subscriptionId", "resourceGroupName", "location"].includes(name)) ||
(ctx.scenarioDef.scope === "Subscription" && ["subscriptionId"].includes(name))
) {
return {
type: "string",
value: `$(${name})`,
};
}
return undefined;
};
const requireVariable = (name: string) => {
if (ctx.scenarioDef.scope === "ResourceGroup" && ["resourceGroupName"].includes(name)) {
return;
}
const requiredVariables =
ctx.scenario?.requiredVariables ?? ctx.scenarioDef.requiredVariables;
if (!requiredVariables.includes(name)) {
requiredVariables.push(`${name}`);
}
};
let operation: Operation | undefined;
if (!("exampleFile" in rawStep)) {
// load operation step
step.operationId = rawStep.operationId;
operation = rawStep.readmeTag
? this.additionalMap.get(rawStep.readmeTag)?.operationsMap.get(step.operationId)
: this.operationsMap.get(step.operationId);
if (operation === undefined) {
throw new Error(`Operation not found for ${step.operationId} in step ${step.step}`);
}
if (rawStep.readmeTag) {
step.externalReference = true;
}
step.isManagementPlane = this.isManagementPlane(operation);
if (this.opts.includeOperation) {
step.operation = operation;
}
if (rawStep.variables) {
for (const [name, value] of Object.entries(rawStep.variables)) {
if (typeof value === "string") {
step.variables[name] = { type: "string", value };
continue;
}
if (value.type === "object" || value.type === "secureObject" || value.type === "array") {
if (value.patches) {
const variable = ctx.scenario
? getVariable(name, ctx.scenario, ctx.scenarioDef)
: getVariable(name, ctx.scenarioDef);
if (!variable) {
throw new Error(`Variable ${name} not found in step ${step.step}`);
}
const obj = cloneDeep(variable);
if (typeof obj !== "object") {
// TODO dynamic json patch
throw new Error(`Can not Json Patch on ${name}, type of ${typeof obj}`);
}
jsonPatchApply(obj.value, value.patches);
step.variables[name] = obj;
continue;
}
}
step.variables[name] = value;
}
}
operation.parameters?.forEach((param) => {
param = this.jsonLoader.resolveRefObj(param);
if (param.name === "api-version") {
step.parameters["api-version"] = rawStep.readmeTag
? this.additionalMap.get(rawStep.readmeTag)?.apiVersionsMap.get(step.operationId)!
: this.apiVersionsMap.get(step.operationId)!;
} else if (rawStep.parameters?.[param.name]) {
step.parameters[param.name] = rawStep.parameters[param.name];
} else {
const v = getVariable(param.name);
if (v) {
if (param.in === "body") {
step.parameters[param.name] = v.value;
} else {
step.parameters[param.name] = `$(${param.name})`;
}
} else if (param.in === "path" || param.required) {
step.parameters[param.name] = `$(${param.name})`;
requireVariable(param.name);
}
}
});
const xHost = operation._path._spec["x-ms-parameterized-host"];
if (xHost) {
xHost.parameters.forEach((param) => {
param = this.jsonLoader.resolveRefObj(param);
if (rawStep.parameters?.[param.name]) {
step.variables[param.name] = {
type: "string",
value: rawStep.parameters[param.name] as string,
};
}
});
}
step.responseAssertion = rawStep.responses;
} else {
// load example step
step.exampleFile = rawStep.exampleFile;
const exampleFilePath = pathJoin(pathDirName(ctx.scenarioDef._filePath), step.exampleFile!);
// Load example file
const fileContent = await this.fileLoader.load(exampleFilePath);
const exampleFileContent = JSON.parse(fileContent) as SwaggerExample;
// Load Operation
if (rawStep.operationId || exampleFileContent.operationId) {
step.operationId = (rawStep.operationId ?? exampleFileContent.operationId)!;
operation = this.operationsMap.get(step.operationId);
if (operation === undefined) {
throw new Error(`Operation not found for ${step.operationId} in step ${step.step}`);
}
step.isManagementPlane = this.isManagementPlane(operation);
if (this.opts.includeOperation) {
step.operation = operation;
}
} else {
const opMap = this.exampleToOperation.get(exampleFilePath);
if (opMap === undefined) {
throw new Error(`Example file is not referenced by any operation: ${step.exampleFile}`);
}
const ops = Object.keys(opMap);
if (ops.length > 1) {
throw new Error(
`Example file is referenced by multiple operation: ${Object.keys(opMap)} ${
step.exampleFile
}`
);
}
step.operationId = ops[0];
const exampleName = opMap[step.operationId];
operation = this.operationsMap.get(step.operationId);
if (operation === undefined) {
throw new Error(`Operation not found for ${step.operationId} in step ${step.step}`);
}
step.isManagementPlane = this.isManagementPlane(operation);
if (this.opts.includeOperation) {
step.operation = operation;
}
step.description = step.description ?? exampleName;
}
step.parameters = exampleFileContent.parameters;
// force update api-version
if (step.parameters["api-version"]) {
step.parameters["api-version"] = this.apiVersionsMap.get(step.operationId)!;
}
step.responses = exampleFileContent.responses;
await this.applyPatches(step, rawStep, operation);
this.templateGenerator.exampleParameterConvention(step, getVariable, operation);
}
const security = operation?.security || operation?._path._spec.security;
if (!step.authentication && security) {
if (security.length > 1) {
logger.warn("Multiple security definitions found, only the first one will be used");
}
const security0 = security[0];
const key = Object.keys(security0)[0];
const securityDefinition = operation?._path._spec.securityDefinitions?.[key];
if (!securityDefinition) {
throw new Error(`Security definition not found for ${key}`);
}
if (securityDefinition.type === "oauth2") {
const value = security0[key];
if (value.length > 1) {
throw new Error(`Multiple scopes are not supported yet: ${JSON.stringify(value)}`);
}
const scope = value[0];
step.authentication = {
type: "AADToken",
scope: scope,
};
} else if (securityDefinition.type === "apiKey") {
const def = securityDefinition as ApiKeySecurity;
step.authentication = {
type: "AzureKey",
key: `$(${key})`,
in: def.in === "query" ? "query" : "header",
name: def.name ?? "Authorization",
};
}
}
if (!rawStep.step) {
step.step = step.operationId;
let i = 1;
while (ctx.stepTracking.has(step.step)) {
step.step += `_${i++}`;
}
}
ctx.stepTracking.set(step.step, step);
return step;
}