build: use default node test runner for unit tests

This commit is contained in:
Eden Zimbelman
2026-02-04 00:08:29 -08:00
parent 951edf9124
commit 648783928f
18 changed files with 811 additions and 675 deletions
-119
View File
@@ -26,9 +26,7 @@
"@types/js-yaml": "^4.0.9",
"@types/markup-js": "^1.5.0",
"@types/node": "^20.19.30",
"@types/sinon": "^21.0.0",
"@vercel/ncc": "^0.38.4",
"sinon": "^21.0.1",
"typescript": "^5.9.3"
},
"engines": {
@@ -386,47 +384,6 @@
"@octokit/openapi-types": "^27.0.0"
}
},
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
"integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"type-detect": "4.0.8"
}
},
"node_modules/@sinonjs/fake-timers": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz",
"integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.1"
}
},
"node_modules/@sinonjs/samsam": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
"integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.1",
"type-detect": "^4.1.0"
}
},
"node_modules/@sinonjs/samsam/node_modules/type-detect": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
"integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/@slack/logger": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz",
@@ -510,23 +467,6 @@
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/@types/sinon": {
"version": "21.0.0",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-21.0.0.tgz",
"integrity": "sha512-+oHKZ0lTI+WVLxx1IbJDNmReQaIsQJjN2e7UUrJHEeByG7bFeKJYsv1E75JxTQ9QKJDp21bAa/0W2Xo4srsDnw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/sinonjs__fake-timers": "*"
}
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
"integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@vercel/ncc": {
"version": "0.38.4",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz",
@@ -634,16 +574,6 @@
"node": ">=0.4.0"
}
},
"node_modules/diff": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
"integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -830,15 +760,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1043,36 +964,6 @@
"node": ">= 4"
}
},
"node_modules/sinon": {
"version": "21.0.1",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz",
"integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.1",
"@sinonjs/fake-timers": "^15.1.0",
"@sinonjs/samsam": "^8.0.3",
"diff": "^8.0.2",
"supports-color": "^7.2.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/sinon"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
@@ -1082,16 +973,6 @@
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
-2
View File
@@ -51,9 +51,7 @@
"@types/js-yaml": "^4.0.9",
"@types/markup-js": "^1.5.0",
"@types/node": "^20.19.30",
"@types/sinon": "^21.0.0",
"@vercel/ncc": "^0.38.4",
"sinon": "^21.0.1",
"typescript": "^5.9.3"
}
}
+308 -221
View File
@@ -1,8 +1,7 @@
import assert from "node:assert";
import { beforeEach, describe, it } from "node:test";
import { beforeEach, describe, it, mock } from "node:test";
import webapi from "@slack/web-api";
import errors from "@slack/web-api/dist/errors.js";
import sinon from "sinon";
import Client from "../src/client.js";
import Config from "../src/config.js";
import SlackError from "../src/errors.js";
@@ -48,7 +47,10 @@ describe("client", () => {
method: "chat.postMessage",
},
};
mocks.core.getInput.withArgs("token").returns("xoxb-example-001");
mocks.inputs = {
...mocks.inputs,
token: "xoxb-example-001",
};
try {
await new Client().post(config);
assert.fail("Failed to throw for missing input");
@@ -64,10 +66,13 @@ describe("client", () => {
describe("api", async () => {
it("uses arguments to send to a slack api method", async () => {
const apis = sinon.stub().resolves({ ok: true });
const constructors = sinon
.stub(mocks.webapi, "WebClient")
.returns({ apiCall: apis });
const apis = mock.fn(() => Promise.resolve({ ok: true }));
const constructorCalls = [];
// Use a regular function as constructor since mock.fn() doesn't work with `new`
mocks.webapi.WebClient = function (...args) {
constructorCalls.push(args);
this.apiCall = apis;
};
/**
* @type {Config}
*/
@@ -87,31 +92,40 @@ describe("client", () => {
webapi: mocks.webapi,
};
await new Client().post(config);
assert.ok(constructors.calledWithNew());
assert.equal(constructorCalls.length, 1);
const [token, options] = constructorCalls[0];
assert.equal(token, "xoxb-example-002");
assert.deepEqual(options, {
agent: undefined,
allowAbsoluteUrls: false,
logger: config.logger,
retryConfig: webapi.retryPolicies.fiveRetriesInFiveMinutes,
slackApiUrl: undefined,
});
assert.equal(apis.mock.callCount(), 1);
const [method, args] = apis.mock.calls[0].arguments;
assert.equal(method, "pins.add");
assert.deepEqual(args, {
channel: "CHANNELHERE",
timestamp: "1234567890.000000",
});
assert.ok(
constructors.calledWith("xoxb-example-002", {
agent: undefined,
allowAbsoluteUrls: false,
logger: config.logger,
retryConfig: webapi.retryPolicies.fiveRetriesInFiveMinutes,
slackApiUrl: undefined,
}),
mocks.core.setOutput.mock.calls.some(
(c) => c.arguments[0] === "ok" && c.arguments[1] === true,
),
);
assert.ok(apis.calledOnce);
assert.ok(
apis.calledWith("pins.add", {
channel: "CHANNELHERE",
timestamp: "1234567890.000000",
}),
);
assert.ok(config.core.setOutput.calledWith("ok", true));
});
it("uses arguments to send to a custom api method", async () => {
const apis = sinon.stub().resolves({ done: true, response: "Infinite" });
const constructors = sinon
.stub(mocks.webapi, "WebClient")
.returns({ apiCall: apis });
const apis = mock.fn(() =>
Promise.resolve({ done: true, response: "Infinite" }),
);
const constructorCalls = [];
// Use a regular function as constructor since mock.fn() doesn't work with `new`
mocks.webapi.WebClient = function (...args) {
constructorCalls.push(args);
this.apiCall = apis;
};
/**
* @type {Config}
*/
@@ -134,29 +148,35 @@ describe("client", () => {
webapi: mocks.webapi,
};
await new Client().post(config);
assert.ok(constructors.calledWithNew());
assert.equal(constructorCalls.length, 1);
const [token, options] = constructorCalls[0];
assert.equal(token, "ollamapassword");
assert.deepEqual(options, {
agent: undefined,
allowAbsoluteUrls: false,
logger: config.logger,
retryConfig: webapi.retryPolicies.tenRetriesInAboutThirtyMinutes,
slackApiUrl: "http://localhost:11434/api/",
});
assert.equal(apis.mock.callCount(), 1);
const [method, args] = apis.mock.calls[0].arguments;
assert.equal(method, "generate");
assert.deepEqual(args, {
model: "llama3.2",
prompt: "How many sides does a circle have?",
stream: false,
});
assert.ok(
constructors.calledWith("ollamapassword", {
agent: undefined,
allowAbsoluteUrls: false,
logger: config.logger,
retryConfig: webapi.retryPolicies.tenRetriesInAboutThirtyMinutes,
slackApiUrl: "http://localhost:11434/api/",
}),
mocks.core.setOutput.mock.calls.some(
(c) => c.arguments[0] === "ok" && c.arguments[1] === undefined,
),
);
assert.ok(apis.calledOnce);
assert.ok(
apis.calledWith("generate", {
model: "llama3.2",
prompt: "How many sides does a circle have?",
stream: false,
}),
);
assert.ok(config.core.setOutput.calledWith("ok", undefined));
assert.ok(
config.core.setOutput.calledWith(
"response",
JSON.stringify({ done: true, response: "Infinite" }),
mocks.core.setOutput.mock.calls.some(
(c) =>
c.arguments[0] === "response" &&
c.arguments[1] ===
JSON.stringify({ done: true, response: "Infinite" }),
),
);
});
@@ -164,123 +184,150 @@ describe("client", () => {
describe("success", () => {
it("calls 'chat.postMessage' with the given token and content", async () => {
try {
const args = {
channel: "C0123456789",
text: "hello",
const args = {
channel: "C0123456789",
text: "hello",
thread_ts: "1234567890.000001",
};
const response = {
ok: true,
channel: "C0123456789",
ts: "1234567890.000002",
message: {
thread_ts: "1234567890.000001",
};
const response = {
ok: true,
channel: "C0123456789",
ts: "1234567890.000002",
message: {
thread_ts: "1234567890.000001",
},
};
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args));
mocks.calls.resolves(response);
await send(mocks.core);
assert.deepEqual(mocks.calls.getCall(0).firstArg, "chat.postMessage");
assert.deepEqual(mocks.calls.getCall(0).lastArg, args);
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(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "channel_id");
assert.equal(mocks.core.setOutput.getCall(2).lastArg, "C0123456789");
assert.equal(mocks.core.setOutput.getCall(3).firstArg, "thread_ts");
assert.equal(
mocks.core.setOutput.getCall(3).lastArg,
"1234567890.000001",
);
assert.equal(mocks.core.setOutput.getCall(4).firstArg, "ts");
assert.equal(
mocks.core.setOutput.getCall(4).lastArg,
"1234567890.000002",
);
assert.equal(mocks.core.setOutput.getCall(5).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 6);
} catch (err) {
console.error(err);
assert.fail("Unexpected error when calling the method");
}
},
};
const payload = JSON.stringify(args);
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload,
};
mocks.calls._resolvesWith = response;
await send(mocks.core);
assert.deepEqual(
mocks.calls.mock.calls[0].arguments[0],
"chat.postMessage",
);
assert.deepEqual(mocks.calls.mock.calls[0].arguments[1], args);
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(response),
);
assert.equal(
mocks.core.setOutput.mock.calls[2].arguments[0],
"channel_id",
);
assert.equal(
mocks.core.setOutput.mock.calls[2].arguments[1],
"C0123456789",
);
assert.equal(
mocks.core.setOutput.mock.calls[3].arguments[0],
"thread_ts",
);
assert.equal(
mocks.core.setOutput.mock.calls[3].arguments[1],
"1234567890.000001",
);
assert.equal(mocks.core.setOutput.mock.calls[4].arguments[0], "ts");
assert.equal(
mocks.core.setOutput.mock.calls[4].arguments[1],
"1234567890.000002",
);
assert.equal(mocks.core.setOutput.mock.calls[5].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 6);
});
it("calls 'conversations.create' with the given token and content", async () => {
try {
const args = {
const args = {
name: "pull-request-review-010101",
};
const response = {
ok: true,
channel: {
id: "C0101010101",
name: "pull-request-review-010101",
};
const response = {
ok: true,
channel: {
id: "C0101010101",
name: "pull-request-review-010101",
is_channel: true,
created: 1730425428,
},
};
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args));
mocks.calls.resolves(response);
await send(mocks.core);
assert.deepEqual(mocks.calls.getCall(0).firstArg, "chat.postMessage");
assert.deepEqual(mocks.calls.getCall(0).lastArg, args);
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(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "channel_id");
assert.equal(mocks.core.setOutput.getCall(2).lastArg, "C0101010101");
assert.equal(mocks.core.setOutput.getCall(3).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 4);
} catch (err) {
console.error(err);
assert.fail("Unexpected error when calling the method");
}
is_channel: true,
created: 1730425428,
},
};
const payload = JSON.stringify(args);
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload,
};
mocks.calls._resolvesWith = response;
await send(mocks.core);
assert.deepEqual(
mocks.calls.mock.calls[0].arguments[0],
"chat.postMessage",
);
assert.deepEqual(mocks.calls.mock.calls[0].arguments[1], args);
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(response),
);
assert.equal(
mocks.core.setOutput.mock.calls[2].arguments[0],
"channel_id",
);
assert.equal(
mocks.core.setOutput.mock.calls[2].arguments[1],
"C0101010101",
);
assert.equal(mocks.core.setOutput.mock.calls[3].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 4);
});
it("calls 'files.uploadV2' with the provided token and content", async () => {
try {
const args = {
channel: "C0000000001",
initial_comment: "the results are in!",
file: "results.out",
filename: "results-888888.out",
};
const response = {
ok: true,
files: [{ id: "F0000000001", created: 1234567890 }],
};
mocks.core.getInput.withArgs("method").returns("files.uploadV2");
mocks.core.getInput.withArgs("token").returns("xoxp-example");
mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args));
mocks.calls.resolves(response);
await send(mocks.core);
assert.deepEqual(mocks.calls.getCall(0).lastArg, args);
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(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 3);
} catch (err) {
console.error(err);
assert.fail("Unexpected error when calling the method");
}
const args = {
channel: "C0000000001",
initial_comment: "the results are in!",
file: "results.out",
filename: "results-888888.out",
};
const response = {
ok: true,
files: [{ id: "F0000000001", created: 1234567890 }],
};
const payload = JSON.stringify(args);
mocks.inputs = {
...mocks.inputs,
method: "files.uploadV2",
token: "xoxp-example",
payload,
};
mocks.calls._resolvesWith = response;
await send(mocks.core);
assert.deepEqual(mocks.calls.mock.calls[0].arguments[1], args);
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(response),
);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 3);
});
});
@@ -296,26 +343,34 @@ describe("client", () => {
message: "Something bad happened!",
},
};
mocks.booleanInputs = {
...mocks.booleanInputs,
errors: true,
};
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload: `"text": "hello"`,
};
mocks.calls._rejectsWith = errors.requestErrorWithOriginal(response, true);
try {
mocks.core.getInput.reset();
mocks.core.getBooleanInput.withArgs("errors").returns(true);
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.calls.rejects(errors.requestErrorWithOriginal(response, true));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (_err) {
assert.ok(mocks.core.setFailed.called);
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");
assert.ok(mocks.core.setFailed.mock.callCount() > 0);
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",
);
assert.deepEqual(
mocks.core.setOutput.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 3);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 3);
}
});
@@ -333,26 +388,32 @@ describe("client", () => {
error: "unknown_http_method",
},
};
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload: `"text": "hello"`,
};
mocks.calls._rejectsWith = errors.httpErrorFromResponse(response);
try {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.calls.rejects(errors.httpErrorFromResponse(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (_err) {
assert.strictEqual(mocks.core.setFailed.called, false);
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");
assert.strictEqual(mocks.core.setFailed.mock.callCount(), 0);
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",
);
response.body = response.data;
response.data = undefined;
assert.deepEqual(
mocks.core.setOutput.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 3);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 3);
}
});
@@ -367,26 +428,34 @@ describe("client", () => {
error: "missing_channel",
},
};
mocks.booleanInputs = {
...mocks.booleanInputs,
errors: true,
};
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload: `"text": "hello"`,
};
mocks.calls._rejectsWith = errors.platformErrorFromResult(response);
try {
mocks.core.getInput.reset();
mocks.core.getBooleanInput.withArgs("errors").returns(true);
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.calls.rejects(errors.platformErrorFromResult(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (_err) {
assert.ok(mocks.core.setFailed.called);
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");
assert.ok(mocks.core.setFailed.mock.callCount() > 0);
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",
);
assert.deepEqual(
mocks.core.setOutput.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 3);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 3);
}
});
@@ -398,24 +467,30 @@ describe("client", () => {
error: "missing_channel",
},
};
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload: `"text": "hello"`,
};
mocks.calls._rejectsWith = errors.platformErrorFromResult(response);
try {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.calls.rejects(errors.platformErrorFromResult(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (_err) {
assert.strictEqual(mocks.core.setFailed.called, false);
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");
assert.strictEqual(mocks.core.setFailed.mock.callCount(), 0);
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",
);
assert.deepEqual(
mocks.core.setOutput.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 3);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 3);
}
});
@@ -424,24 +499,30 @@ describe("client", () => {
code: "slack_webapi_rate_limited_error",
retryAfter: 12,
};
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload: `"text": "hello"`,
};
mocks.calls._rejectsWith = errors.rateLimitedErrorWithDelay(12);
try {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.calls.rejects(errors.rateLimitedErrorWithDelay(12));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (_err) {
assert.strictEqual(mocks.core.setFailed.called, false);
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");
assert.strictEqual(mocks.core.setFailed.mock.callCount(), 0);
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",
);
assert.deepEqual(
mocks.core.setOutput.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify(response),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.equal(mocks.core.setOutput.getCalls().length, 3);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.equal(mocks.core.setOutput.mock.calls.length, 3);
}
});
});
@@ -449,9 +530,12 @@ describe("client", () => {
describe("proxies", () => {
it("sets up the proxy agent for the provided https proxy", async () => {
const proxy = "https://example.com";
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("proxy").returns(proxy);
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
proxy,
token: "xoxb-example",
};
const config = new Config(mocks.core);
const client = new Client();
const { httpsAgent, proxy: proxying } = client.proxies(config);
@@ -461,9 +545,12 @@ describe("client", () => {
it("fails to configure proxies with an invalid proxied url", async () => {
const proxy = "https://";
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("proxy").returns(proxy);
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
proxy,
token: "xoxb-example",
};
try {
const config = new Config(mocks.core);
const client = new Client();
+107 -41
View File
@@ -20,13 +20,19 @@ describe("config", () => {
describe("inputs", () => {
it("valid values are collected from the action inputs", async () => {
mocks.core.getInput.withArgs("api").returns("http://localhost:8080");
mocks.core.getBooleanInput.withArgs("errors").returns(true);
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("payload").returns('"hello": "world"');
mocks.core.getInput.withArgs("proxy").returns("https://example.com");
mocks.core.getInput.withArgs("retries").returns("0");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.inputs = {
...mocks.inputs,
api: "http://localhost:8080",
method: "chat.postMessage",
payload: '"hello": "world"',
proxy: "https://example.com",
retries: "0",
token: "xoxb-example",
};
mocks.booleanInputs = {
...mocks.booleanInputs,
errors: true,
};
const config = new Config(mocks.core);
assert.equal(config.inputs.api, "http://localhost:8080");
assert.equal(config.inputs.errors, true);
@@ -35,36 +41,65 @@ describe("config", () => {
assert.equal(config.inputs.proxy, "https://example.com");
assert.equal(config.inputs.retries, config.Retries.ZERO);
assert.equal(config.inputs.token, "xoxb-example");
assert.ok(mocks.core.setSecret.withArgs("xoxb-example").called);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "xoxb-example",
),
);
});
it("allows token environment variables with a webhook", async () => {
process.env.SLACK_TOKEN = "xoxb-example";
mocks.core.getInput.withArgs("webhook").returns("https://example.com");
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
mocks.inputs = {
...mocks.inputs,
webhook: "https://example.com",
"webhook-type": "incoming-webhook",
};
const config = new Config(mocks.core);
assert.equal(config.inputs.token, "xoxb-example");
assert.equal(config.inputs.webhook, "https://example.com");
assert.equal(config.inputs.webhookType, "incoming-webhook");
assert.ok(mocks.core.setSecret.withArgs("xoxb-example").called);
assert.ok(mocks.core.setSecret.withArgs("https://example.com").called);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "xoxb-example",
),
);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "https://example.com",
),
);
});
it("allows webhook environment variables with a token", async () => {
process.env.SLACK_WEBHOOK_URL = "https://example.com";
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
};
const config = new Config(mocks.core);
assert.equal(config.inputs.method, "chat.postMessage");
assert.equal(config.inputs.token, "xoxb-example");
assert.equal(config.inputs.webhook, "https://example.com");
assert.ok(mocks.core.setSecret.withArgs("xoxb-example").called);
assert.ok(mocks.core.setSecret.withArgs("https://example.com").called);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "xoxb-example",
),
);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "https://example.com",
),
);
});
it("errors when both the token and webhook is provided", async () => {
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("webhook").returns("https://example.com");
mocks.inputs = {
...mocks.inputs,
token: "xoxb-example",
webhook: "https://example.com",
};
try {
new Config(mocks.core);
assert.fail("Failed to error when invalid inputs are provided");
@@ -75,9 +110,15 @@ describe("config", () => {
"Invalid input! Either the token or webhook is required - not both.",
),
);
assert.ok(mocks.core.setSecret.withArgs("xoxb-example").called);
assert.ok(
mocks.core.setSecret.withArgs("https://example.com").called,
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "xoxb-example",
),
);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "https://example.com",
),
);
} else {
assert.fail(err);
@@ -86,7 +127,10 @@ describe("config", () => {
});
it("errors if the method is provided without a token", async () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
};
try {
new Config(mocks.core);
assert.fail("Failed to error when invalid inputs are provided");
@@ -121,7 +165,10 @@ describe("config", () => {
});
it("errors if a webhook is provided without the type", async () => {
mocks.core.getInput.withArgs("webhook").returns("https://example.com");
mocks.inputs = {
...mocks.inputs,
webhook: "https://example.com",
};
try {
new Config(mocks.core);
assert.fail("Failed to error when invalid inputs are provided");
@@ -139,8 +186,11 @@ describe("config", () => {
});
it("errors if the webhook type does not match techniques", async () => {
mocks.core.getInput.withArgs("webhook").returns("https://example.com");
mocks.core.getInput.withArgs("webhook-type").returns("post");
mocks.inputs = {
...mocks.inputs,
webhook: "https://example.com",
"webhook-type": "post",
};
try {
new Config(mocks.core);
assert.fail("Failed to error when invalid inputs are provided");
@@ -160,35 +210,50 @@ describe("config", () => {
describe("mask", async () => {
it("treats the provided token as a secret", async () => {
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.inputs = {
...mocks.inputs,
token: "xoxb-example",
};
try {
await send(mocks.core);
assert.fail("Failed to error for incomplete inputs while testing");
} catch {
assert.ok(mocks.core.setSecret.withArgs("xoxb-example").called);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "xoxb-example",
),
);
}
});
it("treats the provided webhook as a secret", async () => {
mocks.core.getInput.withArgs("webhook").returns("https://slack.com");
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
mocks.inputs = {
...mocks.inputs,
webhook: "https://slack.com",
"webhook-type": "incoming-webhook",
};
try {
await send(mocks.core);
assert.fail("Failed to error for incomplete inputs while testing");
} catch {
assert.ok(mocks.core.setSecret.withArgs("https://slack.com").called);
assert.ok(
mocks.core.setSecret.mock.calls.some(
(c) => c.arguments[0] === "https://slack.com",
),
);
}
});
});
describe("validate", () => {
it('allow the "retries" option with lowercased space', async () => {
mocks.axios.post.returns(Promise.resolve("LGTM"));
mocks.core.getInput.withArgs("retries").returns(" rapid ");
mocks.core.getInput
.withArgs("webhook")
.returns("https://hooks.slack.com");
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
mocks.axios.post._promise = Promise.resolve("LGTM");
mocks.inputs = {
...mocks.inputs,
retries: " rapid ",
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
};
try {
await send(mocks.core);
} catch (err) {
@@ -205,12 +270,13 @@ describe("config", () => {
});
it("errors if an invalid retries option is provided", async () => {
mocks.axios.post.returns(Promise.resolve("LGTM"));
mocks.core.getInput.withArgs("retries").returns("FOREVER");
mocks.core.getInput
.withArgs("webhook")
.returns("https://hooks.slack.com");
mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook");
mocks.axios.post._promise = Promise.resolve("LGTM");
mocks.inputs = {
...mocks.inputs,
retries: "FOREVER",
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
};
try {
await send(mocks.core);
} catch (err) {
+118 -134
View File
@@ -1,6 +1,7 @@
import assert from "node:assert";
import path from "node:path";
import { dirname, join } from "node:path";
import { beforeEach, describe, it } from "node:test";
import { fileURLToPath } from "node:url";
import { YAMLException } from "js-yaml";
import Config from "../src/config.js";
import Content from "../src/content.js";
@@ -8,25 +9,36 @@ import SlackError from "../src/errors.js";
import send from "../src/send.js";
import { mocks } from "./index.spec.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURES = join(__dirname, "fixtures");
/**
* Confirm values from the action input or environment variables are gathered
*/
describe("content", () => {
beforeEach(() => {
mocks.reset();
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
};
// Set up default apiCall mock for tests that use send()
mocks.calls._resolvesWith = { ok: true };
});
describe("flatten", () => {
it("flattens nested payloads provided with delimiter", async () => {
mocks.core.getInput.withArgs("payload").returns(`
mocks.inputs = {
...mocks.inputs,
payload: `
"apples": "tree",
"bananas": {
"truthiness": true
}
`);
mocks.core.getInput.withArgs("payload-delimiter").returns("_");
`,
"payload-delimiter": "_",
};
const config = new Config(mocks.core);
const expected = {
apples: "tree",
@@ -38,8 +50,11 @@ describe("content", () => {
describe("get", () => {
it("errors if both a payload and file path are provided", async () => {
mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`);
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.inputs = {
...mocks.inputs,
payload: `"message"="hello"`,
"payload-file-path": "example.json",
};
try {
await send(mocks.core);
assert.fail("Failed to throw for invalid input");
@@ -59,10 +74,13 @@ describe("content", () => {
describe("payload", async () => {
it("parses complete YAML from the input payload", async () => {
mocks.core.getInput.withArgs("payload").returns(`
mocks.inputs = {
...mocks.inputs,
payload: `
message: "this is wrapped"
channel: "C0123456789"
`);
`,
};
const config = new Config(mocks.core);
const expected = {
message: "this is wrapped",
@@ -72,11 +90,14 @@ describe("content", () => {
});
it("parses complete JSON from the input payload", async () => {
mocks.core.getInput.withArgs("payload").returns(`{
mocks.inputs = {
...mocks.inputs,
payload: `{
"message": "this is wrapped",
"channel": "C0123456789"
}
`);
`,
};
const config = new Config(mocks.core);
const expected = {
message: "this is wrapped",
@@ -86,11 +107,14 @@ describe("content", () => {
});
it("templatizes variables requires configuration", async () => {
mocks.core.getInput.withArgs("payload").returns(`{
mocks.inputs = {
...mocks.inputs,
payload: `{
"message": "this matches an existing variable: \${{ github.apiUrl }}",
"channel": "C0123456789"
}
`);
`,
};
const config = new Config(mocks.core);
// biome-ignore-start lint/suspicious/noTemplateCurlyInString: GitHub Action YAML variable syntax
const expected = {
@@ -102,7 +126,9 @@ describe("content", () => {
});
it("templatizes variables with matching variables", async () => {
mocks.core.getInput.withArgs("payload").returns(`
mocks.inputs = {
...mocks.inputs,
payload: `
channel: C0123456789
reply_broadcast: false
message: Served \${{ env.NUMBER }} items
@@ -136,8 +162,12 @@ describe("content", () => {
type: plain_text
text: "\${{ github.graphqlUrl }}"
value: graphql
`);
mocks.core.getBooleanInput.withArgs("payload-templated").returns(true);
`,
};
mocks.booleanInputs = {
...mocks.booleanInputs,
"payload-templated": true,
};
process.env.DETAILS = `
-fri
-sat
@@ -210,11 +240,15 @@ describe("content", () => {
*/
it("templatizes variables with missing variables", async () => {
// biome-ignore-start lint/suspicious/noTemplateCurlyInString: GitHub Action YAML variable syntax
mocks.core.getInput
.withArgs("payload")
.returns("message: What makes ${{ env.TREASURE }} a secret");
mocks.inputs = {
...mocks.inputs,
payload: "message: What makes ${{ env.TREASURE }} a secret",
};
// biome-ignore-end lint/suspicious/noTemplateCurlyInString: https://docs.github.com/en/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#using-contexts-to-access-variable-values
mocks.core.getBooleanInput.withArgs("payload-templated").returns(true);
mocks.booleanInputs = {
...mocks.booleanInputs,
"payload-templated": true,
};
const config = new Config(mocks.core);
const expected = {
message: "What makes ??? a secret",
@@ -223,10 +257,13 @@ describe("content", () => {
});
it("trims last comma JSON with the input payload", async () => {
mocks.core.getInput.withArgs("payload").returns(`
mocks.inputs = {
...mocks.inputs,
payload: `
"message": "LGTM!",
"channel": "C0123456789",
`);
`,
};
const config = new Config(mocks.core);
const expected = {
message: "LGTM!",
@@ -236,7 +273,9 @@ describe("content", () => {
});
it("wraps incomplete JSON from the input payload", async () => {
mocks.core.getInput.withArgs("payload").returns(`
mocks.inputs = {
...mocks.inputs,
payload: `
"message": "LGTM!",
"channel": "C0123456789",
"blocks": [
@@ -247,7 +286,8 @@ describe("content", () => {
}
}
]
`);
`,
};
const config = new Config(mocks.core);
const expected = {
message: "LGTM!",
@@ -291,7 +331,10 @@ describe("content", () => {
});
it("fails if invalid JSON exists in the input payload", async () => {
mocks.core.getInput.withArgs("payload").returns("{");
mocks.inputs = {
...mocks.inputs,
payload: "{",
};
try {
await send(mocks.core);
assert.fail("Failed to throw for invalid JSON");
@@ -316,13 +359,10 @@ describe("content", () => {
describe("payload file", async () => {
it("parses complete YAML from the input payload file", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml");
mocks.fs.readFileSync
.withArgs(path.resolve("example.yaml"), "utf-8")
.returns(`
message: "drink water"
channel: "C6H12O6H2O2"
`);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload.yaml"),
};
const config = new Config(mocks.core);
const expected = {
message: "drink water",
@@ -332,13 +372,10 @@ describe("content", () => {
});
it("parses complete YML from the input payload file", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.yml");
mocks.fs.readFileSync
.withArgs(path.resolve("example.yml"), "utf-8")
.returns(`
message: "drink coffee"
channel: "C0FFEEEEEEEE"
`);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload.yml"),
};
const config = new Config(mocks.core);
const expected = {
message: "drink coffee",
@@ -348,13 +385,10 @@ describe("content", () => {
});
it("parses complete JSON from the input payload file", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.fs.readFileSync
.withArgs(path.resolve("example.json"), "utf-8")
.returns(`{
"message": "drink water",
"channel": "C6H12O6H2O2"
}`);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload.json"),
};
const config = new Config(mocks.core);
const expected = {
message: "drink water",
@@ -364,14 +398,10 @@ describe("content", () => {
});
it("templatizes variables requires configuration", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.fs.readFileSync
.withArgs(path.resolve("example.json"), "utf-8")
.returns(`{
"message": "this matches an existing variable: \${{ github.apiUrl }}",
"channel": "C0123456789"
}
`);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload-with-github-vars.json"),
};
const config = new Config(mocks.core);
// biome-ignore-start lint/suspicious/noTemplateCurlyInString: GitHub Action YAML variable syntax
const expected = {
@@ -383,66 +413,14 @@ describe("content", () => {
});
it("templatizes variables with matching variables", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.fs.readFileSync
.withArgs(path.resolve("example.json"), "utf-8")
.returns(`{
"channel": "C0123456789",
"reply_broadcast": false,
"message": "Served \${{ env.NUMBER }} items",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Served \${{ env.NUMBER }} items on: \${{ env.DETAILS }}"
}
},
{
"type": "divider"
},
{
"type": "section",
"block_id": "selector",
"text": {
"type": "mrkdwn",
"text": "Send feedback"
},
"accessory": {
"action_id": "response",
"type": "multi_static_select",
"placeholder": {
"type": "plain_text",
"text": "Select URL"
},
"options": [
{
"text": {
"type": "plain_text",
"text": "\${{ github.apiUrl }}"
},
"value": "api"
},
{
"text": {
"type": "plain_text",
"text": "\${{ github.serverUrl }}"
},
"value": "server"
},
{
"text": {
"type": "plain_text",
"text": "\${{ github.graphqlUrl }}"
},
"value": "graphql"
}
]
}
}
]
}`);
mocks.core.getBooleanInput.withArgs("payload-templated").returns(true);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload-with-env-vars.json"),
};
mocks.booleanInputs = {
...mocks.booleanInputs,
"payload-templated": true,
};
process.env.DETAILS = `
-fri
-sat
@@ -514,13 +492,14 @@ describe("content", () => {
* @see {@link https://github.com/slackapi/slack-github-action/issues/203}
*/
it("templatizes variables with missing variables", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.fs.readFileSync
.withArgs(path.resolve("example.json"), "utf-8")
.returns(`{
"message": "What makes $\{{ env.TREASURE }} a secret"
}`);
mocks.core.getBooleanInput.withArgs("payload-templated").returns(true);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload-with-missing-var.json"),
};
mocks.booleanInputs = {
...mocks.booleanInputs,
"payload-templated": true,
};
const config = new Config(mocks.core);
const expected = {
message: "What makes ??? a secret",
@@ -553,7 +532,10 @@ describe("content", () => {
});
it("fails to parse a file path that does not exist", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json");
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "nonexistent.json"),
};
try {
await send(mocks.core);
assert.fail("Failed to throw for nonexistent files");
@@ -571,7 +553,10 @@ describe("content", () => {
});
it("fails to parse a file with an unknown extension", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("unknown.md");
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload.yaml.md"),
};
try {
await send(mocks.core);
assert.fail("Failed to throw for an unknown extension");
@@ -586,7 +571,7 @@ describe("content", () => {
assert.equal(err.cause.values.length, 1);
assert.ok(
err.cause.values[0].message.includes(
"Invalid input! Failed to parse file extension unknown.md",
"Invalid input! Failed to parse file extension",
),
);
} else {
@@ -596,11 +581,10 @@ describe("content", () => {
});
it("fails if invalid JSON exists in the input payload", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.json");
mocks.fs.readFileSync
.withArgs(path.resolve("example.json"), "utf-8")
.returns(`{
"message": "a truncated file without an end`);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload-invalid.json"),
};
try {
await send(mocks.core);
assert.fail("Failed to throw for invalid JSON");
@@ -621,10 +605,10 @@ describe("content", () => {
});
it("fails if invalid YAML exists in the input payload", async () => {
mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml");
mocks.fs.readFileSync
.withArgs(path.resolve("example.yaml"), "utf-8")
.returns(`- "message": "assigned": "values"`);
mocks.inputs = {
...mocks.inputs,
"payload-file-path": join(FIXTURES, "payload-invalid.yaml"),
};
try {
await send(mocks.core);
assert.fail("Failed to throw for invalid YAML");
+2
View File
@@ -0,0 +1,2 @@
{
"message": "a truncated file without an end
+1
View File
@@ -0,0 +1 @@
- "message": "assigned": "values"
+56
View File
@@ -0,0 +1,56 @@
{
"channel": "C0123456789",
"reply_broadcast": false,
"message": "Served ${{ env.NUMBER }} items",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Served ${{ env.NUMBER }} items on: ${{ env.DETAILS }}"
}
},
{
"type": "divider"
},
{
"type": "section",
"block_id": "selector",
"text": {
"type": "mrkdwn",
"text": "Send feedback"
},
"accessory": {
"action_id": "response",
"type": "multi_static_select",
"placeholder": {
"type": "plain_text",
"text": "Select URL"
},
"options": [
{
"text": {
"type": "plain_text",
"text": "${{ github.apiUrl }}"
},
"value": "api"
},
{
"text": {
"type": "plain_text",
"text": "${{ github.serverUrl }}"
},
"value": "server"
},
{
"text": {
"type": "plain_text",
"text": "${{ github.graphqlUrl }}"
},
"value": "graphql"
}
]
}
}
]
}
+4
View File
@@ -0,0 +1,4 @@
{
"message": "this matches an existing variable: ${{ github.apiUrl }}",
"channel": "C0123456789"
}
+3
View File
@@ -0,0 +1,3 @@
{
"message": "What makes ${{ env.TREASURE }} a secret"
}
+4
View File
@@ -0,0 +1,4 @@
{
"message": "drink water",
"channel": "C6H12O6H2O2"
}
+2
View File
@@ -0,0 +1,2 @@
message: "drink water"
channel: "C6H12O6H2O2"
+2
View File
@@ -0,0 +1,2 @@
# This file has an unknown extension
message: "test"
+2
View File
@@ -0,0 +1,2 @@
message: "drink coffee"
channel: "C0FFEEEEEEEE"
+67 -49
View File
@@ -1,7 +1,6 @@
import fs from "node:fs";
import { mock } from "node:test";
import webapi from "@slack/web-api";
import axios, { AxiosError } from "axios";
import sinon from "sinon";
/**
* Hello experimenter! These tests are here to confirm that the happy paths keep
@@ -35,65 +34,84 @@ export class Mock {
};
/**
* Setup stubbed dependencies and configure default input arguments for all
* Lookup tables for input values.
*/
inputs = {};
booleanInputs = {};
/**
* Setup mocked dependencies and configure default input arguments for all
* tests.
*
* @see {@link ../action.yml}
*/
constructor() {
this.sandbox = sinon.createSandbox();
this.axios = this.sandbox.stub(axios);
this.calls = this.sandbox.stub(webapi.WebClient.prototype, "apiCall");
this.axios = {
post: mock.fn((..._args) => {
if (this.axios.post._promise !== undefined) {
return this.axios.post._promise;
}
throw new Error(
"Test error: axios.post was called but no promise was configured. " +
"Set mocks.axios.post._promise = Promise.resolve(...) or Promise.reject(...)",
);
}),
};
axios.post = this.axios.post;
this.calls = mock.fn((..._args) => {
if (this.calls._resolvesWith !== undefined) {
return Promise.resolve(this.calls._resolvesWith);
}
if (this.calls._rejectsWith !== undefined) {
return Promise.reject(this.calls._rejectsWith);
}
throw new Error(
"Test error: apiCall was called but no promise was configured. " +
"Set mocks.calls._resolvesWith = {...} or mocks.calls._rejectsWith = {...}",
);
});
webapi.WebClient.prototype.apiCall = this.calls;
this.core = {
debug: this.sandbox.stub(),
error: this.sandbox.stub(),
getInput: this.sandbox.stub(),
getBooleanInput: this.sandbox.stub(),
info: this.sandbox.stub(),
isDebug: this.sandbox.stub(),
setFailed: this.sandbox.stub(),
setOutput: this.sandbox.stub(),
setSecret: this.sandbox.stub(),
warning: this.sandbox.stub(),
debug: mock.fn(),
error: mock.fn(),
getInput: mock.fn((key) => this.inputs[key] ?? ""),
getBooleanInput: mock.fn((key) => this.booleanInputs[key] ?? false),
info: mock.fn(),
isDebug: mock.fn(() => this._isDebug ?? false),
setFailed: mock.fn(),
setOutput: mock.fn(),
setSecret: mock.fn(),
warning: mock.fn(),
};
this.fs = this.sandbox.stub(fs);
this.webapi = {
WebClient: function () {
this.apiCall = () => ({
ok: true,
});
},
};
this.core.getInput.withArgs("errors").returns("false");
this.core.getInput.withArgs("retries").returns("5");
}
/**
* Testing interface that removes internal state from existing stubs.
* Testing interface that removes internal state from existing mocks.
*/
reset() {
this.sandbox.reset();
this.axios.post.resetHistory();
this.calls.resetHistory();
this.core.debug.reset();
this.core.error.reset();
this.core.getInput.reset();
this.core.getBooleanInput.reset();
this.core.info.reset();
this.core.isDebug.reset();
this.core.setFailed.reset();
this.core.setOutput.reset();
this.core.setSecret.reset();
this.core.warning.reset();
this.webapi = {
WebClient: function () {
this.apiCall = () => ({
ok: true,
});
},
};
this.core.getInput.withArgs("errors").returns("false");
this.core.getInput.withArgs("retries").returns("5");
// Clear lookup tables
this.inputs = {};
this.booleanInputs = {};
// Reset axios mock
this.axios.post.mock.resetCalls();
this.axios.post._promise = undefined;
// Reset apiCall mock
this.calls.mock.resetCalls();
this.calls._resolvesWith = undefined;
this.calls._rejectsWith = undefined;
// Reset core mocks
for (const fn of Object.values(this.core)) {
fn.mock?.resetCalls();
}
this._isDebug = false;
// Reset webapi
this.webapi = {};
// Clear environment variables
process.env.SLACK_TOKEN = "";
process.env.SLACK_WEBHOOK_URL = "";
}
+2 -2
View File
@@ -11,7 +11,7 @@ describe("logger", () => {
describe("level", () => {
it("debug", () => {
mocks.core.isDebug.returns(true);
mocks._isDebug = true;
const { logger } = new Logger(mocks.core);
const actual = logger.getLevel();
const expected = LogLevel.DEBUG;
@@ -19,7 +19,7 @@ describe("logger", () => {
});
it("info", () => {
mocks.core.isDebug.returns(false);
mocks._isDebug = false;
const { logger } = new Logger(mocks.core);
const actual = logger.getLevel();
const expected = LogLevel.INFO;
+42 -36
View File
@@ -19,62 +19,68 @@ describe("send", () => {
describe("techniques", async () => {
it("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('"greetings": "hello"');
mocks.axios.post.returns(
Promise.resolve({ status: 200, data: { ok: true } }),
);
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "webhook-trigger",
payload: '"greetings": "hello"',
};
mocks.axios.post._promise = Promise.resolve({
status: 200,
data: { ok: true },
});
await send(mocks.core);
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.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.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify({ ok: true }),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.ok(mocks.core.setOutput.getCall(2).lastArg >= 0);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.ok(mocks.core.setOutput.mock.calls[2].arguments[1] >= 0);
});
it("token", async () => {
process.env.SLACK_WEBHOOK_URL = "https://example.com"; // https://github.com/slackapi/slack-github-action/issues/373
mocks.calls.resolves({ ok: true });
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns('"text": "hello"');
mocks.calls._resolvesWith = { ok: true };
mocks.inputs = {
...mocks.inputs,
method: "chat.postMessage",
token: "xoxb-example",
payload: '"text": "hello"',
};
await send(mocks.core);
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.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.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify({ ok: true }),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.ok(mocks.core.setOutput.getCall(2).lastArg >= 0);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.ok(mocks.core.setOutput.mock.calls[2].arguments[1] >= 0);
});
it("incoming webhook", async () => {
process.env.SLACK_TOKEN = "xoxb-example"; // https://github.com/slackapi/slack-github-action/issues/373
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": "hello"');
mocks.axios.post.returns(Promise.resolve({ status: 200, data: "ok" }));
mocks.inputs = {
...mocks.inputs,
webhook: "https://hooks.slack.com",
"webhook-type": "incoming-webhook",
payload: '"text": "hello"',
};
mocks.axios.post._promise = Promise.resolve({ status: 200, data: "ok" });
await send(mocks.core);
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.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.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify("ok"),
);
assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time");
assert.ok(mocks.core.setOutput.getCall(2).lastArg >= 0);
assert.equal(mocks.core.setOutput.mock.calls[2].arguments[0], "time");
assert.ok(mocks.core.setOutput.mock.calls[2].arguments[1] >= 0);
});
});
});
+91 -71
View File
@@ -14,26 +14,32 @@ describe("webhook", () => {
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 } }),
);
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.getCalls().length, 1);
const [url, payload, options] = mocks.axios.post.getCall(0).args;
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.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.mock.calls[0].arguments[0], "ok");
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[1], true);
assert.equal(
mocks.core.setOutput.getCall(1).lastArg,
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) {
@@ -43,24 +49,29 @@ describe("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" }));
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.getCalls().length, 1);
const [url, payload, options] = mocks.axios.post.getCall(0).args;
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.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.mock.calls[0].arguments[0], "ok");
assert.equal(mocks.core.setOutput.mock.calls[0].arguments[1], true);
assert.equal(
mocks.core.setOutput.getCall(1).lastArg,
mocks.core.setOutput.mock.calls[1].arguments[0],
"response",
);
assert.equal(
mocks.core.setOutput.mock.calls[1].arguments[1],
JSON.stringify("ok"),
);
} catch (err) {
@@ -92,11 +103,12 @@ describe("webhook", () => {
});
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");
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",
@@ -104,7 +116,7 @@ describe("webhook", () => {
{},
{ status: 400 },
);
mocks.axios.post.resolves(Promise.reject(response));
mocks.axios.post._promise = Promise.reject(response);
try {
await send(mocks.core);
} catch (err) {
@@ -116,22 +128,23 @@ describe("webhook", () => {
assert.fail(err);
}
}
assert.equal(mocks.axios.post.getCalls().length, 1);
const [url, payload, options] = mocks.axios.post.getCall(0).args;
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.getCall(0).firstArg, "ok");
assert.equal(mocks.core.setOutput.getCall(0).lastArg, false);
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
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.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");
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",
@@ -139,7 +152,7 @@ describe("webhook", () => {
{},
{ status: 400 },
);
mocks.axios.post.resolves(Promise.reject(response));
mocks.axios.post._promise = Promise.reject(response);
try {
await send(mocks.core);
} catch (err) {
@@ -151,14 +164,14 @@ describe("webhook", () => {
assert.fail(err);
}
}
assert.equal(mocks.axios.post.getCalls().length, 1);
const [url, payload, options] = mocks.axios.post.getCall(0).args;
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.getCall(0).firstArg, "ok");
assert.equal(mocks.core.setOutput.getCall(0).lastArg, false);
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
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");
});
});
@@ -186,9 +199,12 @@ describe("webhook", () => {
});
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");
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);
@@ -197,11 +213,12 @@ describe("webhook", () => {
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);
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);
@@ -211,11 +228,12 @@ describe("webhook", () => {
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);
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);
@@ -225,11 +243,12 @@ describe("webhook", () => {
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);
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();
@@ -248,11 +267,12 @@ describe("webhook", () => {
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);
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();