329 lines
16 KiB
JavaScript
329 lines
16 KiB
JavaScript
const { assert } = require('chai');
|
|
const sinon = require('sinon');
|
|
const core = require('@actions/core');
|
|
const github = require('@actions/github');
|
|
const rewiremock = require('rewiremock/node');
|
|
|
|
const ChatStub = {
|
|
postMessage: sinon.fake.resolves({ ok: true, ts: '1503435957.111111', thread_ts: '1503435956.000247' }),
|
|
update: sinon.fake.resolves({ ok: true, thread_ts: '1503435956.000247' }),
|
|
};
|
|
/* eslint-disable-next-line global-require */
|
|
rewiremock(() => require('@slack/web-api')).with({
|
|
WebClient: class {
|
|
constructor(token) {
|
|
this.token = token;
|
|
this.chat = ChatStub;
|
|
}
|
|
},
|
|
});
|
|
const AxiosMock = {
|
|
post: sinon.stub().resolves(),
|
|
};
|
|
/* eslint-disable-next-line global-require */
|
|
rewiremock(() => require('axios')).with(AxiosMock);
|
|
rewiremock.enable();
|
|
const slackSend = require('../src/slack-send');
|
|
|
|
rewiremock.disable();
|
|
|
|
const ORIG_TOKEN_VAR = process.env.SLACK_BOT_TOKEN;
|
|
const ORIG_WEBHOOK_VAR = process.env.SLACK_WEBHOOK_URL;
|
|
|
|
describe('slack-send', () => {
|
|
const fakeCore = sinon.stub(core);
|
|
const fakeGithub = sinon.stub(github);
|
|
beforeEach(() => {
|
|
sinon.reset();
|
|
});
|
|
after(() => {
|
|
process.env.SLACK_BOT_TOKEN = ORIG_TOKEN_VAR;
|
|
process.env.SLACK_WEBHOOK_URL = ORIG_WEBHOOK_VAR;
|
|
});
|
|
|
|
it('should set an error if no webhook URL or token is provided', async () => {
|
|
delete process.env.SLACK_BOT_TOKEN;
|
|
delete process.env.SLACK_WEBHOOK_URL;
|
|
await slackSend(fakeCore);
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide at least one botToken or webhook', 'Error set specifying what env vars need to be set.');
|
|
});
|
|
|
|
describe('using a bot token', () => {
|
|
beforeEach(() => {
|
|
process.env.SLACK_BOT_TOKEN = 'xoxb-xxxxx';
|
|
delete process.env.SLACK_WEBHOOK_URL;
|
|
});
|
|
describe('happy path', () => {
|
|
it('should send a message using the postMessage API', async () => {
|
|
fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?');
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
await slackSend(fakeCore);
|
|
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
|
|
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
|
|
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
|
|
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
|
|
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
|
|
assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage');
|
|
});
|
|
|
|
it('should send a message using the update API', async () => {
|
|
fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?');
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
fakeCore.getInput.withArgs('update-ts').returns('123456');
|
|
await slackSend(fakeCore);
|
|
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
|
|
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
|
|
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
|
|
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
const chatArgs = ChatStub.update.lastCall.firstArg;
|
|
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
|
|
assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage');
|
|
});
|
|
|
|
it('should send payload-file-path values with replaced context variables', async () => {
|
|
// Prepare
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json');
|
|
fakeCore.getBooleanInput.withArgs('payload-file-path-parsed').returns(true);
|
|
fakeGithub.context.actor = 'user123';
|
|
|
|
// Run
|
|
await slackSend(fakeCore);
|
|
|
|
// Assert
|
|
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
|
|
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
|
|
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
|
|
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
|
|
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
|
|
assert.equal(chatArgs.text, '', 'Correct message provided to postMessage');
|
|
assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage');
|
|
assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage');
|
|
assert.equal(chatArgs.actor, 'user123', 'Correct message provided to postMessage');
|
|
});
|
|
|
|
it('should send payload-file-path values without replacing context variables', async () => {
|
|
// Prepare
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json');
|
|
fakeCore.getBooleanInput.withArgs('payload-file-path-parsed').returns(false);
|
|
fakeGithub.context.actor = 'user123';
|
|
|
|
// Run
|
|
await slackSend(fakeCore);
|
|
|
|
// Assert
|
|
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
|
|
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
|
|
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
|
|
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
|
|
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
|
|
assert.equal(chatArgs.text, '', 'Correct message provided to postMessage');
|
|
assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage');
|
|
assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage');
|
|
/* eslint-disable-next-line no-template-curly-in-string */
|
|
assert.equal(chatArgs.actor, '${{github.actor}}', 'Correct message provided to postMessage');
|
|
});
|
|
|
|
it('should send the same message to multiple channels', async () => {
|
|
fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?');
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456,C987654');
|
|
await slackSend(fakeCore);
|
|
const firstChatArgs = ChatStub.postMessage.firstCall.firstArg;
|
|
const secondChatArgs = ChatStub.postMessage.lastCall.firstArg;
|
|
assert.oneOf('C123456', [firstChatArgs.channel, secondChatArgs.channel], 'First comma-separated channel provided to postMessage');
|
|
assert.oneOf('C987654', [firstChatArgs.channel, secondChatArgs.channel], 'Second comma-separated channel provided to postMessage');
|
|
assert.equal(firstChatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage with first comma-separated channel');
|
|
assert.equal(secondChatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage with second comma-separated channel');
|
|
});
|
|
|
|
it("should send a reply-message using the postMessage API if thread_ts payload field is used'", async () => {
|
|
fakeCore.getInput
|
|
.withArgs('payload')
|
|
.returns('{"thread_ts":"123456","text":"who let the dogs out?"}');
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
|
|
await slackSend(fakeCore);
|
|
|
|
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
|
|
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
|
|
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
|
|
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
|
|
|
|
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
|
|
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
|
|
assert.equal(chatArgs.thread_ts, '123456', 'Correct thread_ts provided to postMessage');
|
|
assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage');
|
|
});
|
|
});
|
|
describe('sad path', () => {
|
|
it('should set an error if payload cannot be JSON parsed', async () => {
|
|
fakeCore.getInput.withArgs('payload').returns('{not-valid-json');
|
|
await slackSend(fakeCore);
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON', 'Error set specifying JSON was invalid.');
|
|
});
|
|
|
|
it('should fail if an invalid payload-file-path is provided', async () => {
|
|
// Prepare
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
fakeCore.getInput.withArgs('payload-file-path').returns('non-existing-path.json');
|
|
|
|
// Run
|
|
await slackSend(fakeCore);
|
|
|
|
// Assert
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'The payload-file-path may be incorrect. Failed to load the file: non-existing-path.json', 'Error set specifying JSON was invalid.');
|
|
});
|
|
|
|
it('should fail if a valid payload-file-path with an invalid JSON is provided', async () => {
|
|
// Prepare
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/invalid-payload.json');
|
|
|
|
// Run
|
|
await slackSend(fakeCore);
|
|
|
|
// Assert
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON payload', 'Error set specifying JSON was invalid.');
|
|
});
|
|
|
|
it('should fail if Channel ID is missing', async () => {
|
|
// Run
|
|
await slackSend(fakeCore);
|
|
|
|
// Assert
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Channel ID is required to run this action. An empty one has been provided', 'Error set specifying JSON was invalid.');
|
|
});
|
|
|
|
it('should fail if payload is missing or empty', async () => {
|
|
// Prepare
|
|
fakeCore.getInput.withArgs('channel-id').returns('C123456');
|
|
|
|
// Run
|
|
await slackSend(fakeCore);
|
|
|
|
// Assert
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Missing message content, please input a valid payload or message to send. No Message has been send.', 'Error set specifying JSON was invalid.');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('using a webhook URL', () => {
|
|
beforeEach(() => {
|
|
process.env.SLACK_WEBHOOK_URL = 'https://someurl';
|
|
delete process.env.SLACK_BOT_TOKEN;
|
|
});
|
|
describe('happy path', () => {
|
|
const payload = {
|
|
batman: 'robin',
|
|
thor: 'loki',
|
|
};
|
|
beforeEach(() => {
|
|
fakeCore.getInput.withArgs('payload').returns(JSON.stringify(payload));
|
|
});
|
|
it('should post the payload to the webhook URL', async () => {
|
|
await slackSend(fakeCore);
|
|
assert(AxiosMock.post.calledWith('https://someurl', payload));
|
|
});
|
|
describe('proxy config', () => {
|
|
beforeEach(() => {
|
|
delete process.env.https_proxy;
|
|
delete process.env.HTTPS_PROXY;
|
|
});
|
|
it('should use https proxy agent when proxy uses HTTP', async () => {
|
|
process.env.HTTPS_PROXY = 'http://test.proxy:8080/';
|
|
await slackSend(fakeCore);
|
|
assert(AxiosMock.post.calledWith('https://someurl', payload, sinon.match.has('httpsAgent').and(sinon.match.has('proxy'))));
|
|
});
|
|
it('should use default axios config when no proxy set', async () => {
|
|
await slackSend(fakeCore);
|
|
assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {}));
|
|
});
|
|
it('should use default axios config when proxy uses HTTPS', async () => {
|
|
process.env.HTTPS_PROXY = 'https://test.proxy:8080/';
|
|
await slackSend(fakeCore);
|
|
assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {}));
|
|
});
|
|
it('should use default axios config when proxy URL is invalid', async () => {
|
|
process.env.HTTPS_PROXY = 'invalid string';
|
|
const consoleSpy = sinon.spy(console, 'log');
|
|
await slackSend(fakeCore);
|
|
|
|
assert(consoleSpy.calledWith('failed to configure https proxy agent for http proxy. Using default axios configuration'));
|
|
assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {}));
|
|
});
|
|
});
|
|
describe('flatten', () => {
|
|
const mockPayload = {
|
|
apples: 'tree',
|
|
bananas: { truthiness: true },
|
|
};
|
|
beforeEach(() => {
|
|
fakeCore.getInput.withArgs('payload').returns(JSON.stringify(mockPayload));
|
|
});
|
|
afterEach(() => {
|
|
delete process.env.SLACK_WEBHOOK_TYPE;
|
|
});
|
|
it('defaults to using a period to flatten nested paylods', async () => {
|
|
process.env.SLACK_WEBHOOK_TYPE = 'WORKFLOW_TRIGGER';
|
|
await slackSend(fakeCore);
|
|
const expected = {
|
|
apples: 'tree',
|
|
'bananas.truthiness': 'true',
|
|
};
|
|
const count = AxiosMock.post.callCount;
|
|
assert.equal(count, 1);
|
|
const post = AxiosMock.post.getCall(0);
|
|
const [url, actual] = post.args;
|
|
assert.equal(url, 'https://someurl');
|
|
assert.deepEqual(actual, expected);
|
|
});
|
|
it('replaces delimiter with provided payload settings', async () => {
|
|
fakeCore.getInput.withArgs('payload-delimiter').returns('_');
|
|
process.env.SLACK_WEBHOOK_TYPE = 'WORKFLOW_TRIGGER';
|
|
await slackSend(fakeCore);
|
|
const expected = {
|
|
apples: 'tree',
|
|
bananas_truthiness: 'true',
|
|
};
|
|
const count = AxiosMock.post.callCount;
|
|
assert.equal(count, 1);
|
|
const post = AxiosMock.post.getCall(0);
|
|
const [url, actual] = post.args;
|
|
assert.equal(url, 'https://someurl');
|
|
assert.deepEqual(actual, expected);
|
|
});
|
|
it('does not flatten nested values of incoming webhook', async () => {
|
|
process.env.SLACK_WEBHOOK_TYPE = 'INCOMING_WEBHOOK';
|
|
await slackSend(fakeCore);
|
|
const expected = {
|
|
apples: 'tree',
|
|
bananas: { truthiness: true },
|
|
};
|
|
const count = AxiosMock.post.callCount;
|
|
assert.equal(count, 1);
|
|
const post = AxiosMock.post.getCall(0);
|
|
const [url, actual] = post.args;
|
|
assert.equal(url, 'https://someurl');
|
|
assert.deepEqual(actual, expected);
|
|
});
|
|
});
|
|
});
|
|
describe('sad path', () => {
|
|
it('should set an error if the POST to the webhook fails without a response', async () => {
|
|
AxiosMock.post.rejects(new Error('boom'));
|
|
await slackSend(fakeCore);
|
|
assert.include(fakeCore.setFailed.lastCall.firstArg, 'boom', 'Error set to whatever axios reports as error.');
|
|
});
|
|
});
|
|
});
|
|
});
|