semantic-release/test/cli.test.js
2022-11-23 16:02:51 -08:00

292 lines
7.8 KiB
JavaScript

import test from "ava";
import { escapeRegExp } from "lodash-es";
import * as td from "testdouble";
import { stub } from "sinon";
import { SECRET_REPLACEMENT } from "../lib/definitions/constants.js";
let previousArgv;
let previousEnv;
test.beforeEach((t) => {
t.context.logs = "";
t.context.errors = "";
t.context.stdout = stub(process.stdout, "write").callsFake((value) => {
t.context.logs += value.toString();
});
t.context.stderr = stub(process.stderr, "write").callsFake((value) => {
t.context.errors += value.toString();
});
previousArgv = process.argv;
previousEnv = process.env;
});
test.afterEach.always((t) => {
t.context.stdout.restore();
t.context.stderr.restore();
process.argv = previousArgv;
process.env = previousEnv;
td.reset();
});
test.serial("Pass options to semantic-release API", async (t) => {
const argv = [
"",
"",
"-b",
"master",
"next",
"-r",
"https://github/com/owner/repo.git",
"-t",
`v\${version}`,
"-p",
"plugin1",
"plugin2",
"-e",
"config1",
"config2",
"--verify-conditions",
"condition1",
"condition2",
"--analyze-commits",
"analyze",
"--verify-release",
"verify1",
"verify2",
"--generate-notes",
"notes",
"--prepare",
"prepare1",
"prepare2",
"--publish",
"publish1",
"publish2",
"--success",
"success1",
"success2",
"--fail",
"fail1",
"fail2",
"--debug",
"-d",
];
const index = await td.replaceEsm("../index.js");
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
td.verify(
index.default({
branches: ["master", "next"],
b: ["master", "next"],
"repository-url": "https://github/com/owner/repo.git",
repositoryUrl: "https://github/com/owner/repo.git",
r: "https://github/com/owner/repo.git",
"tag-format": `v\${version}`,
tagFormat: `v\${version}`,
t: `v\${version}`,
plugins: ["plugin1", "plugin2"],
p: ["plugin1", "plugin2"],
extends: ["config1", "config2"],
e: ["config1", "config2"],
"dry-run": true,
dryRun: true,
d: true,
verifyConditions: ["condition1", "condition2"],
"verify-conditions": ["condition1", "condition2"],
analyzeCommits: "analyze",
"analyze-commits": "analyze",
verifyRelease: ["verify1", "verify2"],
"verify-release": ["verify1", "verify2"],
generateNotes: ["notes"],
"generate-notes": ["notes"],
prepare: ["prepare1", "prepare2"],
publish: ["publish1", "publish2"],
success: ["success1", "success2"],
fail: ["fail1", "fail2"],
debug: true,
_: [],
$0: "",
})
);
t.is(exitCode, 0);
});
test.serial("Pass options to semantic-release API with alias arguments", async (t) => {
const argv = [
"",
"",
"--branches",
"master",
"--repository-url",
"https://github/com/owner/repo.git",
"--tag-format",
`v\${version}`,
"--plugins",
"plugin1",
"plugin2",
"--extends",
"config1",
"config2",
"--dry-run",
];
const index = await td.replaceEsm("../index.js");
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
td.verify(
index.default({
branches: ["master"],
b: ["master"],
"repository-url": "https://github/com/owner/repo.git",
repositoryUrl: "https://github/com/owner/repo.git",
r: "https://github/com/owner/repo.git",
"tag-format": `v\${version}`,
tagFormat: `v\${version}`,
t: `v\${version}`,
plugins: ["plugin1", "plugin2"],
p: ["plugin1", "plugin2"],
extends: ["config1", "config2"],
e: ["config1", "config2"],
"dry-run": true,
dryRun: true,
d: true,
_: [],
$0: "",
})
);
t.is(exitCode, 0);
});
test.serial("Pass unknown options to semantic-release API", async (t) => {
const argv = ["", "", "--bool", "--first-option", "value1", "--second-option", "value2", "--second-option", "value3"];
const index = await td.replaceEsm("../index.js");
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
td.verify(
index.default({
bool: true,
firstOption: "value1",
"first-option": "value1",
secondOption: ["value2", "value3"],
"second-option": ["value2", "value3"],
_: [],
$0: "",
})
);
t.is(exitCode, 0);
});
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async (t) => {
const argv = ["", "", "--publish", "false"];
const index = await td.replaceEsm("../index.js");
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
td.verify(index.default({ publish: [], _: [], $0: "" }));
t.is(exitCode, 0);
});
test.serial("Do not set properties in option for which arg is not in command line", async (t) => {
const run = stub().resolves(true);
const argv = ["", "", "-b", "master"];
await td.replaceEsm("../index.js", null, run);
process.argv = argv;
const cli = (await import("../cli.js")).default;
await cli();
t.false("ci" in run.args[0][0]);
t.false("d" in run.args[0][0]);
t.false("dry-run" in run.args[0][0]);
t.false("debug" in run.args[0][0]);
t.false("r" in run.args[0][0]);
t.false("t" in run.args[0][0]);
t.false("p" in run.args[0][0]);
t.false("e" in run.args[0][0]);
});
test.serial("Display help", async (t) => {
const run = stub().resolves(true);
const argv = ["", "", "--help"];
await td.replaceEsm("../index.js", null, run);
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
t.regex(t.context.logs, /Run automated package publishing/);
t.is(exitCode, 0);
});
test.serial("Return error exitCode and prints help if called with a command", async (t) => {
const run = stub().resolves(true);
const argv = ["", "", "pre"];
await td.replaceEsm("../index.js", null, run);
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
t.regex(t.context.errors, /Run automated package publishing/);
t.regex(t.context.errors, /Too many non-option arguments/);
t.is(exitCode, 1);
});
test.serial("Return error exitCode if multiple plugin are set for single plugin", async (t) => {
const run = stub().resolves(true);
const argv = ["", "", "--analyze-commits", "analyze1", "analyze2"];
await td.replaceEsm("../index.js", null, run);
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
t.regex(t.context.errors, /Run automated package publishing/);
t.regex(t.context.errors, /Too many non-option arguments/);
t.is(exitCode, 1);
});
test.serial("Return error exitCode if semantic-release throw error", async (t) => {
const argv = ["", ""];
const index = await td.replaceEsm("../index.js");
td.when(index.default({ _: [], $0: "" })).thenReject(new Error("semantic-release error"));
process.argv = argv;
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
t.regex(t.context.errors, /semantic-release error/);
t.is(exitCode, 1);
});
test.serial("Hide sensitive environment variable values from the logs", async (t) => {
const env = { MY_TOKEN: "secret token" };
const argv = ["", ""];
const index = await td.replaceEsm("../index.js");
td.when(index.default({ _: [], $0: "" })).thenReject(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`));
process.argv = argv;
process.env = { ...process.env, ...env };
const cli = (await import("../cli.js")).default;
const exitCode = await cli();
t.regex(t.context.errors, new RegExp(`Throw error: Exposing token ${escapeRegExp(SECRET_REPLACEMENT)}`));
t.is(exitCode, 1);
});