367 lines
12 KiB
JavaScript
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",
|
|
);
|
|
});
|
|
});
|
|
});
|