Skip to content

Commit

Permalink
feat!: Remove support check_run (#2521)
Browse files Browse the repository at this point in the history
Migration:
In the past we used for scaling the check_run event, since GitHub had now support for a workflow_job event. Now the event is upport for quite some time. And also support exists on GHES. We will phase out check_run support.

Ensure you configure the GitHub app to subscribe to workflow_job. In case you on an old GHES server version, the only. migration path is to update GHES or remain on old version of the module
  • Loading branch information
npalm committed Dec 13, 2022
1 parent 18746fb commit e70dc12
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 125 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ A logical question would be, why not Kubernetes? In the current approach, we sta

## Overview

The moment a GitHub action workflow requiring a `self-hosted` runner is triggered, GitHub will try to find a runner which can execute the workload. This module reacts to GitHub's [`check_run` event](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) or [`workflow_job` event](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#workflow_job) for the triggered workflow and creates a new runner if necessary.
The moment a GitHub action workflow requiring a `self-hosted` runner is triggered, GitHub will try to find a runner which can execute the workload. This module reacts to GitHub's [`workflow_job` event](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#workflow_job) for the triggered workflow and creates a new runner if necessary.

For receiving the `check_run` or `workflow_job` event by the webhook (lambda), a webhook needs to be created in GitHub. The `workflow_job` is the preferred option, and the `check_run` option will be maintained for backward compatibility. The advantage of the `workflow_job` event is that the runner checks if the received event can run on the configured runners by matching the labels, which avoid instances being scaled up and never used. The following options are available:
For receiving the `workflow_job` event by the webhook (lambda), a webhook needs to be created in GitHub. The `check_run` option is dropped from version 2.x. The following options to sent the event are supported.

- `workflow_job`: **(preferred option)** create a webhook on enterprise, org or app level. Select this option for ephemeral runners.
- `check_run`: create a webhook on enterprise, org, repo or app level. When using the app option, the app needs to be installed to repo's are using the self-hosted runners.
- a Webhook needs to be created. The webhook hook can be defined on enterprise, org, repo, or app level.
- Create a GitHup app, define a webhook and subscribe the app to the `workflow_job` event.
- Create a webhook on enterprise, org or repo level, define a webhook and subscribe the app to the `workflow_job` event.

In AWS a [API gateway](https://docs.aws.amazon.com/apigateway/index.html) endpoint is created that is able to receive the GitHub webhook events via HTTP post. The gateway triggers the webhook lambda which will verify the signature of the event. This check guarantees the event is sent by the GitHub App. The lambda only handles `workflow_job` or `check_run` events with status `queued` and matching the runner labels (only for `workflow_job`). The accepted events are posted on a SQS queue. Messages on this queue will be delayed for a configurable amount of seconds (default 30 seconds) to give the available runners time to pick up this build.
In AWS a [API gateway](https://docs.aws.amazon.com/apigateway/index.html) endpoint is created that is able to receive the GitHub webhook events via HTTP post. The gateway triggers the webhook lambda which will verify the signature of the event. This check guarantees the event is sent by the GitHub App. The lambda only handles `workflow_job` events with status `queued` and matching the runner labels. The accepted events are posted on a SQS queue. Messages on this queue will be delayed for a configurable amount of seconds (default 30 seconds) to give the available runners time to pick up this build.

The "scale up runner" lambda listens to the SQS queue and picks up events. The lambda runs various checks to decide whether a new EC2 spot instance needs to be created. For example, the instance is not created if the build is already started by an existing runner, or the maximum number of runners is reached.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747"
integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==

"@babel/core@7.17.8":
"@babel/core@7.17.8", "@babel/core@^7.12.3":
version "7.17.8"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a"
integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==
Expand All @@ -62,7 +62,7 @@
json5 "^2.1.2"
semver "^6.3.0"

"@babel/core@^7.11.6", "@babel/core@^7.12.3":
"@babel/core@^7.11.6":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c"
integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==
Expand Down Expand Up @@ -101,6 +101,15 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"

"@babel/generator@^7.19.3", "@babel/generator@^7.19.4":
version "7.19.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.5.tgz#da3f4b301c8086717eee9cab14da91b1fa5dcca7"
integrity sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==
dependencies:
"@babel/types" "^7.19.4"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"

"@babel/helper-compilation-targets@^7.17.7":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf"
Expand Down Expand Up @@ -150,6 +159,14 @@
"@babel/template" "^7.18.10"
"@babel/types" "^7.19.0"

"@babel/helper-function-name@^7.19.0":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==
dependencies:
"@babel/template" "^7.18.10"
"@babel/types" "^7.19.0"

"@babel/helper-get-function-arity@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419"
Expand Down Expand Up @@ -488,6 +505,22 @@
debug "^4.1.0"
globals "^11.1.0"

"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.3", "@babel/traverse@^7.19.4":
version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.4.tgz#f117820e18b1e59448a6c1fa9d0ff08f7ac459a8"
integrity sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.19.4"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.19.4"
"@babel/types" "^7.19.4"
debug "^4.1.0"
globals "^11.1.0"

"@babel/types@7.17.0":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
Expand All @@ -505,6 +538,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"

"@babel/types@^7.18.10", "@babel/types@^7.19.0", "@babel/types@^7.19.3", "@babel/types@^7.19.4":
version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7"
integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==
dependencies:
"@babel/helper-string-parser" "^7.19.4"
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"

"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
Expand Down Expand Up @@ -1536,9 +1578,9 @@ caniuse-lite@^1.0.30001366:
integrity sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==

caniuse-lite@^1.0.30001400:
version "1.0.30001420"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz#f62f35f051e0b6d25532cf376776d41e45b47ef6"
integrity sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==
version "1.0.30001419"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz#3542722d57d567c8210d5e4d0f9f17336b776457"
integrity sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==

chalk@^2.0.0:
version "2.4.2"
Expand Down Expand Up @@ -1725,6 +1767,11 @@ diff-sequences@^29.3.1:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e"
integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==

diff-sequences@^29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6"
integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==

diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
Expand Down Expand Up @@ -1757,9 +1804,9 @@ electron-to-chromium@^1.4.188:
integrity sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw==

electron-to-chromium@^1.4.251:
version "1.4.283"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.283.tgz#d4f263f5df402fd799c0a06255d580dcf8aa9a8e"
integrity sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA==
version "1.4.282"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz#02af3fd6051e97ac3388a4b11d455bc1ca49838f"
integrity sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==

emittery@^0.13.1:
version "0.13.1"
Expand Down Expand Up @@ -2662,6 +2709,16 @@ jest-diff@^29.3.1:
jest-get-type "^29.2.0"
pretty-format "^29.3.1"

jest-diff@^29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.2.0.tgz#b1e11ac1a1401fc4792ef8ba406b48f1ae7d2bc5"
integrity sha512-GsH07qQL+/D/GxlnU+sSg9GL3fBOcuTlmtr3qr2pnkiODCwubNN2/7slW4m3CvxDsEus/VEOfQKRFLyXsUlnZw==
dependencies:
chalk "^4.0.0"
diff-sequences "^29.2.0"
jest-get-type "^29.2.0"
pretty-format "^29.2.0"

jest-docblock@^29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82"
Expand Down Expand Up @@ -2692,6 +2749,11 @@ jest-environment-node@^29.3.1:
jest-mock "^29.3.1"
jest-util "^29.3.1"

jest-get-type@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1"
integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==

jest-get-type@^29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408"
Expand Down Expand Up @@ -3377,6 +3439,11 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==

react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==

readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
Expand Down
18 changes: 0 additions & 18 deletions modules/runners/lambdas/runners/src/scale-runners/scale-up.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,6 @@ describe('scaleUp with GHES', () => {
expect(createRunner).toBeCalledWith(expectedRunnerParams);
});

it('creates a runner with legacy event check_run', async () => {
await scaleUpModule.scaleUp('aws:sqs', { ...TEST_DATA, eventType: 'check_run' });
expect(createRunner).toBeCalledWith(expectedRunnerParams);
});

it('creates a runner with labels in a specific group', async () => {
process.env.RUNNER_EXTRA_LABELS = 'label1,label2';
process.env.RUNNER_GROUP_NAME = 'TEST_GROUP';
Expand Down Expand Up @@ -402,14 +397,6 @@ describe('scaleUp with public GH', () => {
expect(listEC2Runners).not.toBeCalled();
});

it('does not list runners when no workflows are queued (check_run)', async () => {
mockOctokit.checks.get.mockImplementation(() => ({
data: { status: 'completed' },
}));
await scaleUpModule.scaleUp('aws:sqs', { ...TEST_DATA, eventType: 'check_run' });
expect(listEC2Runners).not.toBeCalled();
});

describe('on org level', () => {
beforeEach(() => {
process.env.ENABLE_ORGANIZATION_RUNNERS = 'true';
Expand Down Expand Up @@ -449,11 +436,6 @@ describe('scaleUp with public GH', () => {
expect(createRunner).toBeCalledWith(expectedRunnerParams);
});

it('creates a runner with legacy event check_run', async () => {
await scaleUpModule.scaleUp('aws:sqs', { ...TEST_DATA, eventType: 'check_run' });
expect(createRunner).toBeCalledWith(expectedRunnerParams);
});

it('creates a runner with labels in s specific group', async () => {
process.env.RUNNER_EXTRA_LABELS = 'label1,label2';
process.env.RUNNER_GROUP_NAME = 'TEST_GROUP';
Expand Down
7 changes: 0 additions & 7 deletions modules/runners/lambdas/runners/src/scale-runners/scale-up.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,6 @@ async function isJobQueued(githubInstallationClient: Octokit, payload: ActionReq
repo: payload.repositoryName,
});
isQueued = jobForWorkflowRun.data.status === 'queued';
} else if (payload.eventType === 'check_run') {
const checkRun = await githubInstallationClient.checks.get({
check_run_id: payload.id,
owner: payload.repositoryOwner,
repo: payload.repositoryName,
});
isQueued = checkRun.data.status === 'queued';
} else {
throw Error(`Event ${payload.eventType} is not supported`);
}
Expand Down
12 changes: 6 additions & 6 deletions modules/runners/lambdas/runners/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2392,9 +2392,9 @@ caniuse-lite@^1.0.30001366:
integrity sha512-wgfRYa9DenEomLG/SdWgQxpIyvdtH3NW8Vq+tB6AwR9e56iOIcu1im5F/wNdDf04XlKHXqIx4N8Jo0PemeBenQ==

caniuse-lite@^1.0.30001400:
version "1.0.30001420"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz#f62f35f051e0b6d25532cf376776d41e45b47ef6"
integrity sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==
version "1.0.30001419"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz#3542722d57d567c8210d5e4d0f9f17336b776457"
integrity sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==

chalk@^2.0.0:
version "2.4.2"
Expand Down Expand Up @@ -2615,9 +2615,9 @@ electron-to-chromium@^1.4.188:
integrity sha512-uxMa/Dt7PQsLBVXwH+t6JvpHJnrsYBaxWKi/J6HE+/nBtoHENhwBoNkgkm226/Kfxeg0z1eMQLBRPPKcDH8xWA==

electron-to-chromium@^1.4.251:
version "1.4.283"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.283.tgz#d4f263f5df402fd799c0a06255d580dcf8aa9a8e"
integrity sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA==
version "1.4.282"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz#02af3fd6051e97ac3388a4b11d455bc1ca49838f"
integrity sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==

emittery@^0.13.1:
version "0.13.1"
Expand Down
58 changes: 3 additions & 55 deletions modules/webhook/lambdas/webhook/src/webhook/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,67 +246,15 @@ describe('handler', () => {
});
});

describe('Test for check_run event (legacy): ', () => {
describe('Test for check_run is ignored.', () => {
it('handles check_run events', async () => {
const event = JSON.stringify(checkrun_event);
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'check_run' },
event,
);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toBeCalled();
});

it('does not handle check_run events with actions other than queued (action = started)', async () => {
const event = JSON.stringify({ ...checkrun_event, action: 'started' });
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'check_run' },
event,
);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).not.toBeCalled();
});

it('does not handle check_run events with actions other than queued (action = completed)', async () => {
const event = JSON.stringify({ ...checkrun_event, action: 'completed' });
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'check_run' },
event,
);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).not.toBeCalled();
});

it('does not handle check_run events from unlisted repositories', async () => {
const event = JSON.stringify(checkrun_event);
process.env.REPOSITORY_WHITE_LIST = '["NotCodertocat/Hello-World"]';
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'check_run' },
event,
);
expect(resp.statusCode).toBe(403);
expect(sendActionRequest).not.toBeCalled();
});

it('handles check_run events from whitelisted repositories', async () => {
const event = JSON.stringify(checkrun_event);
process.env.REPOSITORY_WHITE_LIST = '["Codertocat/Hello-World"]';
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'check_run' },
event,
);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toBeCalled();
});

it('handles check_run events with no installation id.', async () => {
const event = JSON.stringify({ ...checkrun_event, installation: { id: null } });
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'check_run' },
event,
);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toBeCalled();
expect(resp.statusCode).toBe(202);
expect(sendActionRequest).toBeCalledTimes(0);
});
});

Expand Down
Loading

0 comments on commit e70dc12

Please sign in to comment.