Skip to content

Commit

Permalink
fix: set the webapi response if the request fails without exiting action
Browse files Browse the repository at this point in the history
  • Loading branch information
zimeg committed Oct 29, 2024
1 parent 1712817 commit adf1dc2
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 29 deletions.
57 changes: 37 additions & 20 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,43 @@ export default class Client {
setName: (_name) => {},
},
});
/**
* @type {webapi.WebAPICallResult & MessageResult}
*/
const response = await client.apiCall(
config.inputs.method,
config.content.values,
);
config.core.setOutput("ok", response.ok);
config.core.setOutput("response", JSON.stringify(response));
if (!response.ok) {
throw new Error(response.error);
}
if (response.channel) {
config.core.setOutput("channel_id", response.channel);
}
if (response.message?.thread_ts) {
config.core.setOutput("thread_ts", response.message.thread_ts);
}
if (response.ts) {
config.core.setOutput("ts", response.ts);
try {
/**
* @type {webapi.WebAPICallResult & MessageResult=}
*/
const response = await client.apiCall(
config.inputs.method,
config.content.values,
);
config.core.setOutput("ok", response.ok);
config.core.setOutput("response", JSON.stringify(response));
if (response.channel) {
config.core.setOutput("channel_id", response.channel);
}
if (response.message?.thread_ts) {
config.core.setOutput("thread_ts", response.message.thread_ts);
}
if (response.ts) {
config.core.setOutput("ts", response.ts);
}
} catch (/** @type {any} */ err) {
const slackErr = /** @type {webapi.WebAPICallError} */ (err);
config.core.setOutput("ok", false);
switch (slackErr.code) {
case webapi.ErrorCode.RequestError:
config.core.setOutput("response", JSON.stringify(slackErr.original));
break;
case webapi.ErrorCode.HTTPError:
config.core.setOutput("response", JSON.stringify(slackErr));
break;
case webapi.ErrorCode.PlatformError:
config.core.setOutput("response", JSON.stringify(slackErr.data));
break;
case webapi.ErrorCode.RateLimitedError:
config.core.setOutput("response", JSON.stringify(slackErr));
break;
}
throw new Error(err);
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export default async function send(core) {
config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000));
} catch (error) {
config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000));
throw new SlackError(core, error, config.inputs.errors);
if (config.inputs.errors) {
throw new SlackError(core, error);
}
}
}

Expand Down
121 changes: 113 additions & 8 deletions test/client.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import core from "@actions/core";
import webapi from "@slack/web-api";
import errors from "@slack/web-api/dist/errors.js";
import { assert } from "chai";
import Client from "../src/client.js";
import Config from "../src/config.js";
Expand Down Expand Up @@ -141,18 +142,95 @@ describe("client", () => {
});

describe("failure", () => {
it("errors when the request to the api cannot be sent correct", async () => {
/**
* @type {webapi.WebAPICallError}
*/
const response = {
code: "slack_webapi_request_error",
data: {
error: "unexpected_request_failure",
message: "Something bad happened!",
},
};
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.api.rejects(errors.requestErrorWithOriginal(response, true));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
assert.isTrue(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.deepEqual(
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);
}
});

it("errors when the http portion of the request fails to send", async () => {
/**
* @type {import("axios").AxiosResponse}
*/
const response = {
code: "slack_webapi_http_error",
headers: {
authorization: "none",
},
data: {
ok: false,
error: "unknown_http_method",
},
};
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.api.rejects(errors.httpErrorFromResponse(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
assert.isFalse(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");
response.body = response.data;
response.data = undefined;
assert.deepEqual(
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);
}
});

it("errors when the payload arguments are invalid for the api", async () => {
/**
* @type {webapi.WebAPICallError}
*/
const response = {
ok: false,
error: "missing_channel",
code: "slack_webapi_platform_error",
data: {
ok: false,
error: "missing_channel",
},
};
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.api.resolves(response);
mocks.api.rejects(errors.platformErrorFromResult(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
Expand All @@ -171,16 +249,43 @@ describe("client", () => {

it("returns the api error and details without a exit failing", async () => {
const response = {
ok: false,
error: "missing_channel",
code: "slack_webapi_platform_error",
data: {
ok: false,
error: "missing_channel",
},
};
try {
mocks.core.getInput.reset();
mocks.core.getBooleanInput.withArgs("errors").returns(false);
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.resolves(response);
mocks.api.rejects(errors.platformErrorFromResult(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
assert.isFalse(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.deepEqual(
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);
}
});

it("errors if rate limit responses are returned after retries", async () => {
const response = {
code: "slack_webapi_rate_limited_error",
retryAfter: 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.api.rejects(errors.rateLimitedErrorWithDelay(12));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
Expand Down

0 comments on commit adf1dc2

Please sign in to comment.