diff --git a/README.md b/README.md index 6dfeae7..d1b22c1 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,26 @@ Behind the scenes, [automatic retries][retries] are handled with the [`@slack/web-api`][slack-web-api] package for Slack API methods, and [`axios-retry`][axios-retry] when sending with a webhook. +### Sending to a custom API URL + +In certain circumstances, such as testing the sent payload, a +[custom API URL][custom-api-url] can be used to change where `method` requests +are sent: + +```yaml +- name: Send to a custom API URL + uses: slackapi/slack-github-action@v2.0.0 + with: + api: http://localhost:8080 + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "What's happening on localhost?" +``` + +The default value of `api` is "https://slack.com/api/" for steps using `method`. + ## Expected outputs Each technique above [outputs values][github-outputs] that can be used as inputs @@ -517,6 +537,7 @@ All contributions are encouraged! Check out the [config-tokens]: https://api.slack.com/reference/manifests#config-tokens [contributing]: .github/contributing.md [conversation]: https://api.slack.com/types/conversation +[custom-api-url]: https://tools.slack.dev/node-slack-sdk/web-api/#custom-api-url [epoch]: https://en.wikipedia.org/wiki/Unix_time [event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6 [event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads diff --git a/action.yml b/action.yml index db07c14..d5ed8ff 100644 --- a/action.yml +++ b/action.yml @@ -2,6 +2,9 @@ name: "Slack: Send to Slack" author: "slackapi" description: "Send data to Slack to start a Slack workflow in Workflow Builder, call a Slack API method, or post a message into a channel" inputs: + api: + description: "A custom API URL to send Slack API method requests to." + required: false errors: default: "false" description: "If the step exits with an error on errors or continues." diff --git a/src/client.js b/src/client.js index 8d371b2..e476a88 100644 --- a/src/client.js +++ b/src/client.js @@ -26,6 +26,7 @@ export default class Client { agent: this.proxies(config)?.httpsAgent, logger: config.logger, retryConfig: this.retries(config.inputs.retries), + slackApiUrl: config.inputs.api || undefined, }); try { /** diff --git a/src/config.js b/src/config.js index 9a3ff11..8ba4502 100644 --- a/src/config.js +++ b/src/config.js @@ -40,6 +40,7 @@ export default class Config { /** * @typedef Inputs - Values provided to this job. + * @property {string?} api - A custom API URL to send method requests to. * @property {boolean} errors - If the job should exit after errors or succeed. * @property {string?} method - The Slack API method to call. * @property {string?} payload - Request contents from the provided input. @@ -101,6 +102,7 @@ export default class Config { this.logger = new Logger(core).logger; this.webapi = webapi; this.inputs = { + api: core.getInput("api"), errors: core.getBooleanInput("errors"), method: core.getInput("method"), payload: core.getInput("payload"), diff --git a/test/client.spec.js b/test/client.spec.js index 5bfff66..58f2dcd 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -73,6 +73,7 @@ describe("client", () => { core: core, logger: new Logger(core).logger, inputs: { + api: "http://localhost:8080/api", method: "pins.add", retries: "10", token: "xoxb-example-002", @@ -86,6 +87,7 @@ describe("client", () => { agent: undefined, logger: config.logger, retryConfig: webapi.retryPolicies.tenRetriesInAboutThirtyMinutes, + slackApiUrl: "http://localhost:8080/api", }), ); }); @@ -110,10 +112,10 @@ describe("client", () => { 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.api.resolves(response); + mocks.calls.resolves(response); await send(mocks.core); - assert.deepEqual(mocks.api.getCall(0).firstArg, "chat.postMessage"); - assert.deepEqual(mocks.api.getCall(0).lastArg, args); + 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"); @@ -158,10 +160,10 @@ describe("client", () => { 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.api.resolves(response); + mocks.calls.resolves(response); await send(mocks.core); - assert.deepEqual(mocks.api.getCall(0).firstArg, "chat.postMessage"); - assert.deepEqual(mocks.api.getCall(0).lastArg, args); + 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"); @@ -194,9 +196,9 @@ describe("client", () => { 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.api.resolves(response); + mocks.calls.resolves(response); await send(mocks.core); - assert.deepEqual(mocks.api.getCall(0).lastArg, args); + 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"); @@ -231,7 +233,7 @@ describe("client", () => { 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.api.rejects(errors.requestErrorWithOriginal(response, true)); + mocks.calls.rejects(errors.requestErrorWithOriginal(response, true)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { @@ -266,7 +268,7 @@ describe("client", () => { 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.api.rejects(errors.httpErrorFromResponse(response)); + mocks.calls.rejects(errors.httpErrorFromResponse(response)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { @@ -302,7 +304,7 @@ describe("client", () => { 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.api.rejects(errors.platformErrorFromResult(response)); + mocks.calls.rejects(errors.platformErrorFromResult(response)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { @@ -331,7 +333,7 @@ describe("client", () => { 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.api.rejects(errors.platformErrorFromResult(response)); + mocks.calls.rejects(errors.platformErrorFromResult(response)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { @@ -357,7 +359,7 @@ describe("client", () => { 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.api.rejects(errors.rateLimitedErrorWithDelay(12)); + mocks.calls.rejects(errors.rateLimitedErrorWithDelay(12)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { diff --git a/test/config.spec.js b/test/config.spec.js index 17e914f..470b35e 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -19,6 +19,7 @@ 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"'); @@ -26,6 +27,7 @@ describe("config", () => { mocks.core.getInput.withArgs("retries").returns("0"); mocks.core.getInput.withArgs("token").returns("xoxb-example"); const config = new Config(mocks.core); + assert.equal(config.inputs.api, "http://localhost:8080"); assert.equal(config.inputs.errors, true); assert.equal(config.inputs.method, "chat.postMessage"); assert.equal(config.inputs.payload, '"hello": "world"'); diff --git a/test/index.spec.js b/test/index.spec.js index 009a2f2..186db60 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -43,8 +43,8 @@ export class Mock { */ constructor() { this.sandbox = sinon.createSandbox(); - this.api = sinon.stub(webapi.WebClient.prototype, "apiCall"); this.axios = this.sandbox.stub(axios); + this.calls = sinon.stub(webapi.WebClient.prototype, "apiCall"); this.core = this.sandbox.stub(core); this.fs = this.sandbox.stub(fs); this.webapi = { @@ -63,8 +63,8 @@ export class Mock { */ reset() { this.sandbox.reset(); - this.api.resetHistory(); this.axios.post.resetHistory(); + this.calls.resetHistory(); this.core.getInput.reset(); this.core.getInput.withArgs("errors").returns("false"); this.core.getInput.withArgs("retries").returns("5"); diff --git a/test/send.spec.js b/test/send.spec.js index b35398b..0d20056 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -40,7 +40,7 @@ describe("send", () => { it("token", async () => { process.env.SLACK_WEBHOOK_URL = "https://example.com"; // https://github.com/slackapi/slack-github-action/issues/373 - mocks.api.resolves({ ok: true }); + 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"');