Files
slack-github-action/test/webhook.spec.js

367 lines
12 KiB
JavaScript

import assert from "node:assert";
import { beforeEach, describe, it } from "node:test";
import { AxiosError } from "axios";
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.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "webhook-trigger",
payload: "drinks: coffee",
};
mocks.axios.post._promise = Promise.resolve({
status: 200,
data: { ok: true },
});
try {
await send(mocks.core);
assert.equal(mocks.axios.post.mock.calls.length, 1);
const [url, payload, options] =
mocks.axios.post.mock.calls[0].arguments;
assert.equal(url, "https://hooks.slack.com");
assert.deepEqual(payload, { drinks: "coffee" });
assert.deepEqual(options, {});
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[0], "ok");
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[1], true);
assert.equal(
mocks.core.setOutput.mock.calls[1].arguments[0],
"response",
);
assert.equal(
mocks.core.setOutput.mock.calls[1].arguments[1],
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.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
payload: "text: greetings",
};
mocks.axios.post._promise = Promise.resolve({ status: 200, data: "ok" });
try {
await send(mocks.core);
assert.equal(mocks.axios.post.mock.calls.length, 1);
const [url, payload, options] =
mocks.axios.post.mock.calls[0].arguments;
assert.equal(url, "https://hooks.slack.com");
assert.deepEqual(payload, { text: "greetings" });
assert.deepEqual(options, {});
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[0], "ok");
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[1], true);
assert.equal(
mocks.core.setOutput.mock.calls[1].arguments[0],
"response",
);
assert.equal(
mocks.core.setOutput.mock.calls[1].arguments[1],
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: mocks.core,
inputs: {},
};
try {
await new Webhook().post(config);
assert.fail("Failed to throw for missing input");
} catch (err) {
if (err instanceof SlackError) {
assert.ok(err.message.includes("No webhook was provided to post to"));
} else {
assert.fail(err);
}
}
});
it("returns the failures from a webhook trigger", async () => {
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "webhook-trigger",
payload: "drinks: coffee",
};
const response = new AxiosError(
"Request failed with status code 400",
"ERR_BAD_REQUEST",
{},
{},
{ status: 400 },
);
mocks.axios.post._promise = Promise.reject(response);
try {
await send(mocks.core);
} catch (err) {
if (err instanceof SlackError) {
assert.ok(
err.message.includes("Request failed with status code 400"),
);
} else {
assert.fail(err);
}
}
assert.equal(mocks.axios.post.mock.calls.length, 1);
const [url, payload, options] = mocks.axios.post.mock.calls[0].arguments;
assert.equal(url, "https://hooks.slack.com");
assert.deepEqual(payload, { drinks: "coffee" });
assert.deepEqual(options, {});
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[0], "ok");
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[1], false);
assert.equal(mocks.core.setOutput.mock.calls[1].arguments[0], "response");
});
it("returns the failures from an incoming webhook", async () => {
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
payload: "textt: oops",
};
const response = new AxiosError(
"Request failed with status code 400",
"ERR_BAD_REQUEST",
{},
{},
{ status: 400 },
);
mocks.axios.post._promise = Promise.reject(response);
try {
await send(mocks.core);
} catch (err) {
if (err instanceof SlackError) {
assert.ok(
err.message.includes("Request failed with status code 400"),
);
} else {
assert.fail(err);
}
}
assert.equal(mocks.axios.post.mock.calls.length, 1);
const [url, payload, options] = mocks.axios.post.mock.calls[0].arguments;
assert.equal(url, "https://hooks.slack.com");
assert.deepEqual(payload, { textt: "oops" });
assert.deepEqual(options, {});
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[0], "ok");
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[1], false);
assert.equal(mocks.core.setOutput.mock.calls[1].arguments[0], "response");
});
});
describe("proxies", () => {
it("requires a webhook is included in the inputs", async () => {
/**
* @type {Config}
*/
const config = {
core: mocks.core,
inputs: {},
};
try {
new Webhook().proxies(config);
assert.fail("Failed to throw for missing input");
} catch (err) {
if (err instanceof SlackError) {
assert.ok(
err.message.includes("No webhook was provided to proxy to"),
);
} else {
assert.fail(err);
}
}
});
it("skips proxying an http webhook url altogether", async () => {
mocks.inputs = {
...mocks.inputs,
webhook: "http://hooks.slack.com",
"webhook-type": "incoming-webhook",
proxy: "https://example.com",
};
const config = new Config(mocks.core);
const webhook = new Webhook();
const request = webhook.proxies(config);
assert.strictEqual(request, undefined);
});
it("sets up the proxy agent for the provided https proxy", async () => {
const proxy = "https://example.com";
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
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.notStrictEqual(proxying, false);
});
it("sets up the agent without proxy for http proxies", async () => {
const proxy = "http://example.com";
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
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.strictEqual(proxying, false);
});
it("fails to configure proxies with an invalid proxied url", async () => {
const proxy = "https://";
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
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.ok(
err.message.includes("Failed to configure the HTTPS proxy"),
);
} else {
assert.fail(err);
}
}
});
it("fails to configure proxies with an unknown url protocol", async () => {
const proxy = "ssh://";
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
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.ok(
err.message.includes("Failed to configure the HTTPS proxy"),
);
assert.ok(err.cause.message.includes("Unsupported URL protocol"));
} else {
assert.fail(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.ok(
result.retryDelay(10, mocks.errors.axios.network_failed) > 1800000,
"last attempt is around 30 minutes after starting",
);
assert.ok(
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",
);
});
});
});