339 lines
12 KiB
JavaScript
339 lines
12 KiB
JavaScript
import core from "@actions/core";
|
|
import { AxiosError } from "axios";
|
|
import { assert } from "chai";
|
|
import Config from "../src/config.js";
|
|
import SlackError from "../src/errors.js";
|
|
import send from "../src/send.js";
|
|
import Webhook from "../src/webhook.js";
|
|
import { mocks } from "./index.spec.js";
|
|
|
|
describe("webhook", () => {
|
|
beforeEach(() => {
|
|
mocks.reset();
|
|
});
|
|
|
|
describe("success", () => {
|
|
it("sends the parsed payload to the provided webhook trigger", async () => {
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger");
|
|
mocks.core.getInput.withArgs("payload").returns("drinks: coffee");
|
|
mocks.axios.post.returns(
|
|
Promise.resolve({ status: 200, data: { ok: true } }),
|
|
);
|
|
try {
|
|
await send(mocks.core);
|
|
assert.equal(mocks.axios.post.getCalls().length, 1);
|
|
const [url, payload, options] = mocks.axios.post.getCall(0).args;
|
|
assert.equal(url, "https://hooks.slack.com");
|
|
assert.deepEqual(payload, { drinks: "coffee" });
|
|
assert.deepEqual(options, {});
|
|
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
|
|
assert.equal(mocks.core.setOutput.getCall(0).lastArg, true);
|
|
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
|
|
assert.equal(
|
|
mocks.core.setOutput.getCall(1).lastArg,
|
|
JSON.stringify({ ok: true }),
|
|
);
|
|
} catch (err) {
|
|
console.error(err);
|
|
assert.fail("Failed to send the webhook");
|
|
}
|
|
});
|
|
|
|
it("sends the parsed payload to the provided incoming webhook", async () => {
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("payload").returns("text: greetings");
|
|
mocks.axios.post.returns(Promise.resolve({ status: 200, data: "ok" }));
|
|
try {
|
|
await send(mocks.core);
|
|
assert.equal(mocks.axios.post.getCalls().length, 1);
|
|
const [url, payload, options] = mocks.axios.post.getCall(0).args;
|
|
assert.equal(url, "https://hooks.slack.com");
|
|
assert.deepEqual(payload, { text: "greetings" });
|
|
assert.deepEqual(options, {});
|
|
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
|
|
assert.equal(mocks.core.setOutput.getCall(0).lastArg, true);
|
|
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
|
|
assert.equal(
|
|
mocks.core.setOutput.getCall(1).lastArg,
|
|
JSON.stringify("ok"),
|
|
);
|
|
} catch (err) {
|
|
console.error(err);
|
|
assert.fail("Failed to send the webhook");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("failure", () => {
|
|
it("requires that a webhook is provided in inputs", async () => {
|
|
/**
|
|
* @type {Config}
|
|
*/
|
|
const config = {
|
|
core: core,
|
|
inputs: {},
|
|
};
|
|
try {
|
|
await new Webhook().post(config);
|
|
assert.fail("Failed to throw for missing input");
|
|
} catch (err) {
|
|
if (err instanceof SlackError) {
|
|
assert.include(err.message, "No webhook was provided to post to");
|
|
} else {
|
|
assert.fail("Failed to throw a SlackError", err);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("returns the failures from a webhook trigger", async () => {
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger");
|
|
mocks.core.getInput.withArgs("payload").returns("drinks: coffee");
|
|
const response = new AxiosError(
|
|
"Request failed with status code 400",
|
|
"ERR_BAD_REQUEST",
|
|
{},
|
|
{},
|
|
{ status: 400 },
|
|
);
|
|
mocks.axios.post.resolves(Promise.reject(response));
|
|
try {
|
|
await send(mocks.core);
|
|
} catch (err) {
|
|
if (err instanceof SlackError) {
|
|
assert.include(err.message, "Request failed with status code 400");
|
|
} else {
|
|
assert.fail("Failed to throw a SlackError", err);
|
|
}
|
|
}
|
|
assert.equal(mocks.axios.post.getCalls().length, 1);
|
|
const [url, payload, options] = mocks.axios.post.getCall(0).args;
|
|
assert.equal(url, "https://hooks.slack.com");
|
|
assert.deepEqual(payload, { drinks: "coffee" });
|
|
assert.deepEqual(options, {});
|
|
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
|
|
assert.equal(mocks.core.setOutput.getCall(0).lastArg, false);
|
|
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
|
|
});
|
|
|
|
it("returns the failures from an incoming webhook", async () => {
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("payload").returns("textt: oops");
|
|
const response = new AxiosError(
|
|
"Request failed with status code 400",
|
|
"ERR_BAD_REQUEST",
|
|
{},
|
|
{},
|
|
{ status: 400 },
|
|
);
|
|
mocks.axios.post.resolves(Promise.reject(response));
|
|
try {
|
|
await send(mocks.core);
|
|
} catch (err) {
|
|
if (err instanceof SlackError) {
|
|
assert.include(err.message, "Request failed with status code 400");
|
|
} else {
|
|
assert.fail("Failed to throw a SlackError", err);
|
|
}
|
|
}
|
|
assert.equal(mocks.axios.post.getCalls().length, 1);
|
|
const [url, payload, options] = mocks.axios.post.getCall(0).args;
|
|
assert.equal(url, "https://hooks.slack.com");
|
|
assert.deepEqual(payload, { textt: "oops" });
|
|
assert.deepEqual(options, {});
|
|
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
|
|
assert.equal(mocks.core.setOutput.getCall(0).lastArg, false);
|
|
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
|
|
});
|
|
});
|
|
|
|
describe("proxies", () => {
|
|
it("requires a webhook is included in the inputs", async () => {
|
|
/**
|
|
* @type {Config}
|
|
*/
|
|
const config = {
|
|
core: core,
|
|
inputs: {},
|
|
};
|
|
try {
|
|
new Webhook().proxies(config);
|
|
assert.fail("Failed to throw for missing input");
|
|
} catch (err) {
|
|
if (err instanceof SlackError) {
|
|
assert.include(err.message, "No webhook was provided to proxy to");
|
|
} else {
|
|
assert.fail("Failed to throw a SlackError", err);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("skips proxying an http webhook url altogether", async () => {
|
|
mocks.core.getInput.withArgs("webhook").returns("http://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("proxy").returns("https://example.com");
|
|
const config = new Config(mocks.core);
|
|
const webhook = new Webhook();
|
|
const request = webhook.proxies(config);
|
|
assert.isUndefined(request);
|
|
});
|
|
|
|
it("sets up the proxy agent for the provided https proxy", async () => {
|
|
const proxy = "https://example.com";
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("proxy").returns(proxy);
|
|
const config = new Config(mocks.core);
|
|
const webhook = new Webhook();
|
|
const { httpsAgent, proxy: proxying } = webhook.proxies(config);
|
|
assert.deepEqual(httpsAgent.proxy, new URL(proxy));
|
|
assert.isNotFalse(proxying);
|
|
});
|
|
|
|
it("sets up the agent without proxy for http proxies", async () => {
|
|
const proxy = "http://example.com";
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("proxy").returns(proxy);
|
|
const config = new Config(mocks.core);
|
|
const webhook = new Webhook();
|
|
const { httpsAgent, proxy: proxying } = webhook.proxies(config);
|
|
assert.deepEqual(httpsAgent.proxy, new URL(proxy));
|
|
assert.isFalse(proxying);
|
|
});
|
|
|
|
it("fails to configure proxies with an invalid proxied url", async () => {
|
|
const proxy = "https://";
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("proxy").returns(proxy);
|
|
try {
|
|
const config = new Config(mocks.core);
|
|
const webhook = new Webhook();
|
|
webhook.proxies(config);
|
|
assert.fail("An invalid proxy URL was not thrown as error!");
|
|
} catch (err) {
|
|
if (err instanceof SlackError) {
|
|
assert.include(err.message, "Failed to configure the HTTPS proxy");
|
|
} else {
|
|
assert.fail("Failed to throw a SlackError", err);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("fails to configure proxies with an unknown url protocol", async () => {
|
|
const proxy = "ssh://";
|
|
mocks.core.getInput
|
|
.withArgs("webhook")
|
|
.returns("https://hooks.slack.com");
|
|
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
|
|
mocks.core.getInput.withArgs("proxy").returns(proxy);
|
|
try {
|
|
const config = new Config(mocks.core);
|
|
const webhook = new Webhook();
|
|
webhook.proxies(config);
|
|
assert.fail("An unknown URL protocol was not thrown as error!");
|
|
} catch (err) {
|
|
if (err instanceof SlackError) {
|
|
assert.include(err.message, "Failed to configure the HTTPS proxy");
|
|
assert.include(err.cause.message, "Unsupported URL protocol");
|
|
} else {
|
|
assert.fail("Failed to throw a SlackError", err);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("retries", () => {
|
|
it("uses a default of five retries in requests", async () => {
|
|
const webhook = new Webhook();
|
|
const result = webhook.retries();
|
|
assert.equal(result.retries, 5);
|
|
});
|
|
|
|
it('does not attempt retries when "0" is set', async () => {
|
|
const webhook = new Webhook();
|
|
const result = webhook.retries("0");
|
|
assert.equal(result.retries, 0);
|
|
});
|
|
|
|
it('attempts a default amount of "5" retries', async () => {
|
|
const webhook = new Webhook();
|
|
const result = webhook.retries("5");
|
|
assert.equal(result.retries, 5);
|
|
if (!result.retryDelay) {
|
|
assert.fail("No retry delay found!");
|
|
}
|
|
assert.equal(
|
|
result.retryDelay(5, mocks.errors.axios.network_failed),
|
|
300000,
|
|
"5th retry after 5 seconds",
|
|
);
|
|
});
|
|
|
|
it('attempts "10" retries in around "30" minutes', async () => {
|
|
const webhook = new Webhook();
|
|
const result = webhook.retries("10");
|
|
assert.equal(result.retries, 10);
|
|
if (!result.retryDelay) {
|
|
assert.fail("No retry delay found!");
|
|
}
|
|
assert.isAtLeast(
|
|
result.retryDelay(10, mocks.errors.axios.network_failed),
|
|
1800000,
|
|
"last attempt is around 30 minutes after starting",
|
|
);
|
|
assert.isAtMost(
|
|
result.retryDelay(10, mocks.errors.axios.network_failed),
|
|
3600000,
|
|
"last attempt is no more than an hour later",
|
|
);
|
|
});
|
|
|
|
it('attempts a " rapid" burst of "12" retries in seconds', async () => {
|
|
const webhook = new Webhook();
|
|
const result = webhook.retries(" rapid");
|
|
assert.equal(result.retries, 12);
|
|
if (!result.retryDelay) {
|
|
assert.fail("No retry delay found!");
|
|
}
|
|
assert.equal(
|
|
result.retryDelay(12, mocks.errors.axios.network_failed),
|
|
12000,
|
|
"12th retry after 12 seconds",
|
|
);
|
|
});
|
|
|
|
it('attempts a "RAPID" burst of "12" retries in seconds', async () => {
|
|
const webhook = new Webhook();
|
|
const result = webhook.retries("RAPID");
|
|
assert.equal(result.retries, 12);
|
|
if (!result.retryDelay) {
|
|
assert.fail("No retry delay found!");
|
|
}
|
|
assert.equal(
|
|
result.retryDelay(12, mocks.errors.axios.network_failed),
|
|
12000,
|
|
"12th retry after 12 seconds",
|
|
);
|
|
});
|
|
});
|
|
});
|