lib/commands/run-api-scenario.ts (229 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as fs from "fs";
import * as path from "path";
import { pathDirName, pathJoin, pathResolve } from "@azure-tools/openapi-tools-common";
import { findReadMe } from "@azure/openapi-markdown";
import * as yargs from "yargs";
import winston from "winston";
import {
PostmanCollectionGenerator,
PostmanCollectionGeneratorOption,
} from "../apiScenario/postmanCollectionGenerator";
import { cliSuppressExceptions } from "../cliSuppressExceptions";
import { inversifyGetInstance } from "../inversifyUtils";
import { logger } from "../apiScenario/logger";
import {
findGitRootDirectory,
getApiScenarioFiles,
getDefaultTag,
getInputFiles,
resetPseudoRandomSeed,
} from "../util/utils";
import { EnvironmentVariables } from "../apiScenario/variableEnv";
import { DEFAULT_ARM_ENDPOINT } from "../apiScenario/constants";
import { log } from "../util/logging";
import { LiveValidatorLoggingLevels } from "../liveValidation/liveValidator";
export const command = "run-api-scenario [<api-scenario>]";
export const aliases = ["run"];
export const describe = "newman runner run API scenario file.";
export const apiScenarioEnvKey = "API_SCENARIO_JSON_ENV";
export const builder: yargs.CommandBuilder = {
envFile: {
alias: "e",
describe: "The env file path.",
string: true,
},
tag: {
describe: "The readme tag name.",
string: true,
},
readme: {
describe: "Path to readme.md file",
string: true,
},
flag: {
describe: "readme.test.md flag",
string: true,
},
specs: {
describe: "One or more spec file paths.",
type: "array",
},
output: {
alias: "outputDir",
describe: "Result output folder.",
string: true,
default: ".apitest",
},
report: {
describe: "Generate report type. Supported types: html, markdown, junit",
type: "array",
},
skipValidation: {
describe: "Skip all validations include schema validation, ARM rules validation.",
boolean: true,
default: false,
},
armEndpoint: {
describe: "ARM endpoint",
string: true,
default: DEFAULT_ARM_ENDPOINT,
},
location: {
describe: "Resource provision location parameter",
string: true,
},
subscriptionId: {
alias: "subscription",
describe: "SubscriptionId to run API test",
string: true,
},
resourceGroupName: {
alias: "resourceGroup",
describe: "Resource group name",
string: true,
},
skipCleanUp: {
describe: "Whether delete resource group when all steps finished",
boolean: true,
},
dryRun: {
describe: "Dry run mode. If set, only create postman collection file not run live API test.",
boolean: true,
default: false,
},
savePayload: {
describe: "Save live traffic payload to file",
boolean: true,
default: false,
},
generateExample: {
describe: "Generate examples after API Test",
boolean: true,
default: false,
},
testProxy: {
describe:
"Test-proxy endpoint, e.g., http://localhost:5000. If not set, no proxy will be used.",
string: true,
},
testProxyAssets: {
describe:
"Test-proxy assets file to push and restore recordings. Only used when test-proxy is set.",
string: true,
},
devMode: {
describe: "Development mode. If set, will skip AAD auth and ARM API call.",
boolean: true,
default: false,
},
randomSeed: {
describe: "Random seed for random number generator",
number: true,
},
};
export async function handler(argv: yargs.Arguments): Promise<void> {
await cliSuppressExceptions(async () => {
// suppress warning log in live validator
log.consoleLogLevel = LiveValidatorLoggingLevels.error;
if (argv.randomSeed !== undefined) {
resetPseudoRandomSeed(argv.randomSeed);
}
if (argv.logLevel) {
const transport = logger.transports.find((t) => t instanceof winston.transports.Console);
if (transport !== undefined) {
transport.level = argv.logLevel;
}
}
const scenarioFiles = [];
let readmePath = argv.readme ? pathResolve(argv.readme) : undefined;
if (argv.apiScenario) {
const scenarioFilePath = pathResolve(argv.apiScenario);
scenarioFiles.push(scenarioFilePath);
if (!readmePath) {
readmePath = await findReadMe(pathDirName(scenarioFilePath));
}
}
const swaggerFilePaths: string[] = [];
for (const spec of argv.specs ?? []) {
const specFile = pathResolve(spec);
if (specFile && swaggerFilePaths.indexOf(specFile) < 0) {
swaggerFilePaths.push(specFile);
}
}
if (readmePath) {
const inputFile = await getInputFiles(readmePath, argv.tag);
for (const it of inputFile ?? []) {
if (swaggerFilePaths.indexOf(it) < 0) {
swaggerFilePaths.push(pathJoin(pathDirName(readmePath), it));
}
}
if (!argv.apiScenario) {
const tag = argv.tag ?? (await getDefaultTag(readmePath));
const testResources = await getApiScenarioFiles(
pathJoin(pathDirName(readmePath), "readme.test.md"),
tag,
argv.flag
);
for (const it of testResources ?? []) {
scenarioFiles.push(pathJoin(pathDirName(readmePath), it));
}
}
}
logger.info("swagger-file:");
logger.info(swaggerFilePaths);
logger.info("scenario-file:");
logger.info(scenarioFiles);
let env: EnvironmentVariables = {};
if (argv.envFile !== undefined) {
env = JSON.parse(fs.readFileSync(argv.envFile).toString());
}
if (process.env[apiScenarioEnvKey]) {
const envFromVariable = JSON.parse(process.env[apiScenarioEnvKey] as string);
for (const key of Object.keys(envFromVariable)) {
if (env[key] !== undefined && envFromVariable[key] !== env[key]) {
logger.warn(
`Notice: the variable '${key}' in '${argv.e}' is overwritten by the variable in the environment '${apiScenarioEnvKey}'.`
);
}
}
env = { ...env, ...envFromVariable };
}
["armEndpoint", "location", "subscriptionId", "resourceGroupName"]
.filter((k) => argv[k] !== undefined)
.forEach((k) => (env[k] = argv[k]));
const fileRoot = readmePath
? findGitRootDirectory(readmePath) ?? pathDirName(readmePath)
: process.cwd();
logger.verbose(`fileRoot: ${fileRoot}`);
const opt: PostmanCollectionGeneratorOption = {
fileRoot,
checkUnderFileRoot: false,
swaggerFilePaths: swaggerFilePaths,
generateCollection: true,
useJsonParser: false,
runCollection: !argv.dryRun,
env,
outputFolder: argv.output,
markdown: (argv.report ?? []).includes("markdown"),
junit: (argv.report ?? []).includes("junit"),
html: (argv.report ?? []).includes("html"),
eraseXmsExamples: false,
eraseDescription: false,
testProxy: argv.testProxy,
testProxyAssets:
argv.testProxy && argv.testProxyAssets ? path.resolve(argv.testProxyAssets) : undefined,
skipValidation: argv.skipValidation,
savePayload: argv.savePayload,
generateExample: argv.generateExample,
verbose: ["verbose", "debug", "silly"].indexOf(argv.logLevel) >= 0,
devMode: argv.devMode,
};
logger.debug("options:");
logger.debug(opt);
const generator = inversifyGetInstance(PostmanCollectionGenerator, opt);
for (const scenarioFile of scenarioFiles) {
await generator.run(scenarioFile, argv.skipCleanUp);
}
await generator.cleanUpAll();
return 0;
});
}