test: integration tests for main.js (#56)
Part of https://github.com/actions/create-github-app-token/issues/43 This PR adds tests for [`main.js`](https://github.com/actions/create-github-app-token/blob/main/lib/main.js), similar to [the tests that already exist for `post.js`](https://github.com/actions/create-github-app-token/tree/main/tests). Specifically, it tests that: - `main` exits with an error when `GITHUB_REPOSITORY` is missing. - `main` exits with an error when `GITHUB_REPOSITORY_OWNER` is missing. - `main` successfully obtains a token when… - …the `owner` and `repositories` inputs are set (and the latter is a single repo). - …the `owner` and `repositories` inputs are set (and the latter is a list of repos). - …the `owner` input is set (to an org), but the `repositories` input isn’t set. - …the `owner` input is set (to a user), but the `repositories` input isn’t set. - …the `owner` input is not set, but the `repositories` input is set. - …neither the `owner` nor `repositories` input is set. ❧ Architecturally, in order to keep individual tests concise, this PR adds `tests/main.js`, which: - sets commonly-used inputs, environment variables, and mocks, then - calls a callback function that can edit the variables and add additional mocks, then - runs `main.js` itself. The `tests/main-token-get-*.test.js` test files run `tests/main.js` with various scenario-specific callback functions.
This commit is contained in:
+6
-1
@@ -7,7 +7,12 @@ const tests = readdirSync("tests").filter((file) => file.endsWith(".test.js"));
|
||||
|
||||
for (const file of tests) {
|
||||
test(file, async (t) => {
|
||||
const { stderr, stdout } = await execa("node", [`tests/${file}`]);
|
||||
// Override Actions environment variables that change `core`’s behavior
|
||||
const env = {
|
||||
GITHUB_OUTPUT: undefined,
|
||||
GITHUB_STATE: undefined,
|
||||
};
|
||||
const { stderr, stdout } = await execa("node", [`tests/${file}`], { env });
|
||||
t.snapshot(stderr, "stderr");
|
||||
t.snapshot(stdout, "stdout");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
|
||||
delete process.env.GITHUB_REPOSITORY_OWNER;
|
||||
|
||||
// Verify `main` exits with an error when `GITHUB_REPOSITORY_OWNER` is missing.
|
||||
try {
|
||||
await import("../main.js");
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
delete process.env.GITHUB_REPOSITORY;
|
||||
|
||||
// Verify `main` exits with an error when `GITHUB_REPOSITORY` is missing.
|
||||
try {
|
||||
await import("../main.js");
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a list of repos).
|
||||
await test(() => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = `${process.env.GITHUB_REPOSITORY},actions/toolkit`;
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a single repo).
|
||||
await test(() => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY;
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` input is set (to an org), but the `repositories` input isn’t set.
|
||||
await test((mockPool) => {
|
||||
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
|
||||
delete process.env.INPUT_REPOSITORIES;
|
||||
|
||||
// Mock installation id request
|
||||
const mockInstallationId = "123456";
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` input is set (to a user), but the `repositories` input isn’t set.
|
||||
await test((mockPool) => {
|
||||
process.env.INPUT_OWNER = "smockle";
|
||||
delete process.env.INPUT_REPOSITORIES;
|
||||
|
||||
// Mock installation id request
|
||||
const mockInstallationId = "123456";
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/orgs/${process.env.INPUT_OWNER}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(404);
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/users/${process.env.INPUT_OWNER}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when the `owner` input is not set, but the `repositories` input is set.
|
||||
await test(() => {
|
||||
delete process.env.INPUT_OWNER;
|
||||
process.env.INPUT_REPOSITORIES = process.env.GITHUB_REPOSITORY;
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test } from "./main.js";
|
||||
|
||||
// Verify `main` successfully obtains a token when neither the `owner` nor `repositories` input is set.
|
||||
await test((mockPool) => {
|
||||
delete process.env.INPUT_OWNER;
|
||||
delete process.env.INPUT_REPOSITORIES;
|
||||
|
||||
// Mock installation id request
|
||||
const mockInstallationId = "123456";
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/repos/${process.env.GITHUB_REPOSITORY}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
// Base for all `main` tests.
|
||||
// @ts-check
|
||||
import { MockAgent, setGlobalDispatcher } from "undici";
|
||||
|
||||
export async function test(cb = (_mockPool) => {}) {
|
||||
// Set required environment variables and inputs
|
||||
process.env.GITHUB_REPOSITORY_OWNER = "actions";
|
||||
process.env.GITHUB_REPOSITORY = "actions/create-github-app-token";
|
||||
// inputs are set as environment variables with the prefix INPUT_
|
||||
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
|
||||
process.env.INPUT_APP_ID = "123456";
|
||||
process.env.INPUT_PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA280nfuUM9w00Ib9E2rvZJ6Qu3Ua3IqR34ZlK53vn/Iobn2EL
|
||||
Z9puc5Q/nFBU15NKwHyQNb+OG2hTCkjd1Xi9XPzEOH1r42YQmTGq8YCkUSkk6KZA
|
||||
5dnhLwN9pFquT9fQgrf4r1D5GJj3rqvj8JDr1sBmunArqY5u4gziSrIohcjLIZV0
|
||||
cIMz/RUIMe/EAsNeiwzEteHAtf/WpMs+OfF94SIUrDlkPr0H0H3DER8l1HZAvE0e
|
||||
eD3ZJ6njrF6UHQWDVrekSTB0clpVTTU9TMpe+gs2nnFww9G8As+WsW8xHVjVipJy
|
||||
AwqBhiR+s7wlcbh2i0NQqt8GL9/jIFTmleiwsQIDAQABAoIBAHNyP8pgl/yyzKzk
|
||||
/0871wUBMTQ7zji91dGCaFtJM0HrcDK4D/uOOPEv7nE1qDpKPLr5Me1pHUS7+NGw
|
||||
EAPtlNhgUtew2JfppdIwyi5qeOPADoi7ud6AH8xHsxg+IMwC+JuP8WhzyUHoJj9y
|
||||
PRi/pX94Mvy9qdE25HqKddjx1mLdaHhxqPkr16/em23uYZqm1lORsCPD3vTlthj7
|
||||
WiEbBSqmpYvjj8iFP4yFk2N+LvuWgSilRzq1Af3qE7PUp4xhjmcOPs78Sag1T7nl
|
||||
ww/pgqCegISABHik7j++/5T+UlI5cLsyp/XENU9zAd4kCIczwNKpun2bn+djJdft
|
||||
ravyX4ECgYEA+k2mHfi1zwKF3wT+cJbf30+uXrJczK2yZ33//4RKnhBaq7nSbQAI
|
||||
nhWz2JESBK0TEo0+L7yYYq3HnT9vcES5R1NxzruH9wXgxssSx3JUj6w1raXYPh3B
|
||||
+1XpYQsa6/bo2LmBELEx47F8FQbNgD5dmTJ4jBrf6MV4rRh9h6Bs7UkCgYEA4M3K
|
||||
eAw52c2MNMIxH/LxdOQtEBq5GMu3AQC8I64DSSRrAoiypfEgyTV6S4gWJ5TKgYfD
|
||||
zclnOVJF+tITe3neO9wHoZp8iCjCnoijcT6p2cJ4IaW68LEHPOokWBk0EpLjF4p2
|
||||
7sFi9+lUpXYXfCN7aMJ77QpGzB7dNsBf0KUxMCkCgYEAjw/mjGbk82bLwUaHby6s
|
||||
0mQmk7V6WPpGZ+Sadx7TzzglutVAslA8nK5m1rdEBywtJINaMcqnhm8xEm15cj+1
|
||||
blEBUVnaQpQ3fyf+mcR9FIknPRL3X7l+b/sQowjH4GqFd6m/XR0KGMwO0a3Lsyry
|
||||
MGeqgtmxdMe5S6YdyXEmERECgYAgQsgklDSVIh9Vzux31kh6auhgoEUh3tJDbZSS
|
||||
Vj2YeIZ21aE1mTYISglj34K2aW7qSc56sMWEf18VkKJFHQccdgYOVfo7HAZZ8+fo
|
||||
r4J2gqb0xTDfq7gLMNrIXc2QQM4gKbnJp60JQM3p9NmH8huavBZGvSvNzTwXyGG3
|
||||
so0tiQKBgGQXZaxaXhYUcxYHuCkQ3V4Vsj3ezlM92xXlP32SGFm3KgFhYy9kATxw
|
||||
Cax1ytZzvlrKLQyQFVK1COs2rHt7W4cJ7op7C8zXfsigXCiejnS664oAuX8sQZID
|
||||
x3WQZRiXlWejSMUAHuMwXrhGlltF3lw83+xAjnqsVp75kGS6OH61
|
||||
-----END RSA PRIVATE KEY-----`; // This key is invalidated. It’s from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327.
|
||||
|
||||
// Set up mocking
|
||||
const mockAgent = new MockAgent();
|
||||
mockAgent.disableNetConnect();
|
||||
setGlobalDispatcher(mockAgent);
|
||||
const mockPool = mockAgent.get("https://api.github.com");
|
||||
|
||||
// Calling `auth({ type: "app" })` to obtain a JWT doesn’t make network requests, so no need to intercept.
|
||||
|
||||
// Mock installation id request
|
||||
const mockInstallationId = "123456";
|
||||
const owner = process.env.INPUT_OWNER ?? process.env.GITHUB_REPOSITORY_OWNER;
|
||||
const repo = encodeURIComponent(
|
||||
(process.env.INPUT_REPOSITORIES ?? process.env.GITHUB_REPOSITORY).split(
|
||||
","
|
||||
)[0]
|
||||
);
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/repos/${owner}/${repo}/installation`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{ id: mockInstallationId },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
|
||||
// Mock installation access token request
|
||||
const mockInstallationAccessToken =
|
||||
"ghs_16C7e42F292c6912E7710c838347Ae178B4a"; // This token is invalidated. It’s from https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app.
|
||||
mockPool
|
||||
.intercept({
|
||||
path: `/app/installations/${mockInstallationId}/access_tokens`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
"user-agent": "actions/create-github-app-token",
|
||||
// Note: Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
201,
|
||||
{ token: mockInstallationAccessToken },
|
||||
{ headers: { "content-type": "application/json" } }
|
||||
);
|
||||
|
||||
// Run the callback
|
||||
cb(mockPool);
|
||||
|
||||
// Run the main script
|
||||
await import("../main.js");
|
||||
}
|
||||
@@ -4,6 +4,110 @@ The actual snapshot is saved in `index.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## main-missing-owner.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
'GITHUB_REPOSITORY_OWNER missing, must be set to \'<owner>\''
|
||||
|
||||
> stdout
|
||||
|
||||
''
|
||||
|
||||
## main-missing-repository.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
'GITHUB_REPOSITORY missing, must be set to \'<owner>/<repo>\''
|
||||
|
||||
> stdout
|
||||
|
||||
''
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-many.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories set, creating token for repositories "actions/create-github-app-token,actions/toolkit" owned by "actions"␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
|
||||
|
||||
## main-token-get-owner-set-repo-set-to-one.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories set, creating token for repositories "actions/create-github-app-token" owned by "actions"␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
|
||||
|
||||
## main-token-get-owner-set-to-org-repo-unset.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`repositories not set, creating token for all repositories for given owner "actions"␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
|
||||
|
||||
## main-token-get-owner-set-to-user-repo-unset.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`repositories not set, creating token for all repositories for given owner "smockle"␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
|
||||
|
||||
## main-token-get-owner-unset-repo-set.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`owner not set, creating owner for given repositories "actions/create-github-app-token" in current owner ("actions")␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
|
||||
|
||||
## main-token-get-owner-unset-repo-unset.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
''
|
||||
|
||||
> stdout
|
||||
|
||||
`owner and repositories not set, creating token for the current repository ("create-github-app-token")␊
|
||||
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
␊
|
||||
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
|
||||
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a`
|
||||
|
||||
## post-token-set.test.js
|
||||
|
||||
> stderr
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user