diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..69eb518a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "singleQuote": true, + "jsxBracketSameLine": true, + "trailingComma": "es5", + "arrowParens": "avoid" +} diff --git a/README.md b/README.md index 7228ee2f..c088d3b7 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Here is a summary of all the variables that you can use and their purpose. variable | default | description --- | --- | --- `expo-username` | - | The username of your Expo account _(e.g. `bycedric`)_ +`expo-token` | - | The token of your Expo account _(e.g. [`${{ secrets.EXPO_TOKEN }}`][link-actions-secrets])_ `expo-password` | - | The password of your Expo account _(e.g. [`${{ secrets.EXPO_CLI_PASSWORD }}`][link-actions-secrets])_ `expo-version` | `latest` | The Expo CLI version to use, can be any [SemVer][link-semver-playground]. _(e.g. `3.x`)_ `expo-packager` | `yarn` | The package manager to install the CLI with. _(e.g. `npm`)_ @@ -47,9 +48,13 @@ variable | default | description `expo-cache-key` | - | An optional custom (remote) cache key. _(**use with caution**)_ `expo-patch-watchers` | `true` | If it should [patch the `fs.inotify.` limits](#enospc-errors-on-linux). -> Never hardcode your `expo-password` in your workflow, use [secrets][link-actions-secrets] to store them. +> Never hardcode `expo-token` or `expo-password` in your workflow, use [secrets][link-actions-secrets] to store them. + > It's also recommended to set the `expo-version` to avoid breaking changes when a new major version is released. +> `expo-token` is available from Expo CLI `3.25.0`. + + ## Example workflows Before you dive into the workflow examples, you should know the basics of GitHub Actions. @@ -60,6 +65,7 @@ You can read more about this in the [GitHub Actions documentation][link-actions] 3. [Test PRs and publish a review version](#test-prs-and-publish-a-review-version) 4. [Test PRs on multiple nodes and systems](#test-prs-on-multiple-nodes-and-systems) 5. [Test and build web every day at 08:00](#test-and-build-web-every-day-at-0800) +6. [Authenticate using an Expo token](#authenticate-using-an-expo-token) ### Publish on any push to master @@ -214,15 +220,44 @@ jobs: - run: expo build:web ``` +### Authenticate using an Expo token + +Instead of username and password, you can also authenticate using a token. +This might help increasing security and avoids adding username and password to your repository secrets. + +```yml +name: Expo Publish +on: + push: + branches: + - master +jobs: + publish: + name: Install and publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - uses: expo/expo-github-action@v5 + with: + expo-version: 3.x + expo-token: ${{ secrets.EXPO_TOKEN }} + - run: yarn install + - run: expo publish +``` + ## Things to know ### Automatic Expo login You need to authenticate for some Expo commands like `expo publish` and `expo build`. This action gives you configuration options to keep your workflow simple. -Under the hood, it uses the [`EXPO_CLI_PASSWORD`][link-expo-cli-password] environment variable to make this as secure as possible. +You can choose if you want to authenticate using an `EXPO_TOKEN` or account credentials. +Under the hood, it uses the [`EXPO_CLI_PASSWORD`][link-expo-cli-password] environment variable to make credentials authentication as secure as possible. -> Note, this action only uses your credentials to authenticate with Expo. It doesn't store these anywhere. +> Note, this action only uses your token or credentials to authenticate with Expo. It doesn't store these anywhere. ### Using the built-in cache diff --git a/action.yml b/action.yml index dff27191..58229e35 100644 --- a/action.yml +++ b/action.yml @@ -14,6 +14,8 @@ inputs: default: latest expo-username: description: Your Expo username, for authentication. + expo-token: + description: Your Expo token, for authentication. (use with secrets) expo-password: description: Your Expo password, for authentication. (use with secrets) expo-packager: diff --git a/build/index.js b/build/index.js index 08bd9277..415476b4 100644 --- a/build/index.js +++ b/build/index.js @@ -12072,7 +12072,11 @@ function run() { cacheKey: core_1.getInput('expo-cache-key') || undefined, }); core_1.addPath(path); - yield expo_1.authenticate(core_1.getInput('expo-username'), core_1.getInput('expo-password')); + yield expo_1.authenticate({ + token: core_1.getInput('expo-token') || undefined, + username: core_1.getInput('expo-username') || undefined, + password: core_1.getInput('expo-password') || undefined, + }); const shouldPatchWatchers = core_1.getInput('expo-patch-watchers') || 'true'; if (shouldPatchWatchers !== 'false') { yield system_1.patchWatchers(); @@ -20722,7 +20726,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.authenticate = void 0; +exports.authenticate = exports.authWithToken = exports.authWithCredentials = void 0; const core = __importStar(__webpack_require__(470)); const cli = __importStar(__webpack_require__(986)); /** @@ -20730,20 +20734,53 @@ const cli = __importStar(__webpack_require__(986)); * This step is required for publishing and building new apps. * It uses the `EXPO_CLI_PASSWORD` environment variable for improved security. */ -function authenticate(username, password) { +function authWithCredentials(username, password) { return __awaiter(this, void 0, void 0, function* () { if (!username || !password) { - return core.info('Skipping authentication, `expo-username` and/or `expo-password` not set...'); + return core.info('Skipping authentication: `expo-username` and/or `expo-password` not set...'); } // github actions toolkit will handle commands with `.cmd` on windows, we need that - const bin = process.platform === 'win32' - ? 'expo.cmd' - : 'expo'; + const bin = process.platform === 'win32' ? 'expo.cmd' : 'expo'; yield cli.exec(bin, ['login', `--username=${username}`], { env: Object.assign(Object.assign({}, process.env), { EXPO_CLI_PASSWORD: password }), }); }); } +exports.authWithCredentials = authWithCredentials; +/** + * Authenticate with Expo using `EXPO_TOKEN`. + * This exports the EXPO_TOKEN environment variable for all future steps within the workflow. + * It also double-checks if this token is valid and for what user, by running `expo whoami`. + * + * @see https://github.com/actions/toolkit/blob/905b2c7b0681b11056141a60055f1ba77358b7e9/packages/core/src/core.ts#L39 + */ +function authWithToken(token) { + return __awaiter(this, void 0, void 0, function* () { + if (!token) { + return core.info('Skipping authentication: `expo-token` not set...'); + } + // github actions toolkit will handle commands with `.cmd` on windows, we need that + const bin = process.platform === 'win32' ? 'expo.cmd' : 'expo'; + yield cli.exec(bin, ['whoami'], { + env: Object.assign(Object.assign({}, process.env), { EXPO_TOKEN: token }), + }); + core.exportVariable('EXPO_TOKEN', token); + }); +} +exports.authWithToken = authWithToken; +/** + * Authenticate with Expo using either the token or username/password method. + * If both of them are set, token has priority. + */ +function authenticate(options) { + if (options.token) { + return authWithToken(options.token); + } + if (options.username || options.password) { + return authWithCredentials(options.username, options.password); + } + core.info('Skipping authentication: `expo-token`, `expo-username`, and/or `expo-password` not set...'); +} exports.authenticate = authenticate; @@ -67064,7 +67101,7 @@ function patchWatchers() { yield cli.exec('sudo sysctl -p'); } catch (_a) { - core.warning('Looks like we can\'t patch watchers/inotify limits, you might encouter the `ENOSPC` error.'); + core.warning("Looks like we can't patch watchers/inotify limits, you might encouter the `ENOSPC` error."); core.warning('For more info, https://github.com/expo/expo-github-action/issues/20'); } }); diff --git a/src/expo.ts b/src/expo.ts index 6e875724..237326b6 100644 --- a/src/expo.ts +++ b/src/expo.ts @@ -1,20 +1,24 @@ import * as core from '@actions/core'; import * as cli from '@actions/exec'; +type AuthOptions = { + token?: string; + username?: string; + password?: string; +}; + /** * Authenticate at Expo using `expo login`. * This step is required for publishing and building new apps. * It uses the `EXPO_CLI_PASSWORD` environment variable for improved security. */ -export async function authenticate(username: string, password: string) { +export async function authWithCredentials(username?: string, password?: string) { if (!username || !password) { - return core.info('Skipping authentication, `expo-username` and/or `expo-password` not set...'); + return core.info('Skipping authentication: `expo-username` and/or `expo-password` not set...'); } // github actions toolkit will handle commands with `.cmd` on windows, we need that - const bin = process.platform === 'win32' - ? 'expo.cmd' - : 'expo'; + const bin = process.platform === 'win32' ? 'expo.cmd' : 'expo'; await cli.exec(bin, ['login', `--username=${username}`], { env: { @@ -23,3 +27,44 @@ export async function authenticate(username: string, password: string) { }, }); } + +/** + * Authenticate with Expo using `EXPO_TOKEN`. + * This exports the EXPO_TOKEN environment variable for all future steps within the workflow. + * It also double-checks if this token is valid and for what user, by running `expo whoami`. + * + * @see https://github.com/actions/toolkit/blob/905b2c7b0681b11056141a60055f1ba77358b7e9/packages/core/src/core.ts#L39 + */ +export async function authWithToken(token?: string) { + if (!token) { + return core.info('Skipping authentication: `expo-token` not set...'); + } + + // github actions toolkit will handle commands with `.cmd` on windows, we need that + const bin = process.platform === 'win32' ? 'expo.cmd' : 'expo'; + + await cli.exec(bin, ['whoami'], { + env: { + ...process.env, + EXPO_TOKEN: token, + }, + }); + + core.exportVariable('EXPO_TOKEN', token); +} + +/** + * Authenticate with Expo using either the token or username/password method. + * If both of them are set, token has priority. + */ +export function authenticate(options: AuthOptions) { + if (options.token) { + return authWithToken(options.token); + } + + if (options.username || options.password) { + return authWithCredentials(options.username, options.password); + } + + core.info('Skipping authentication: `expo-token`, `expo-username`, and/or `expo-password` not set...'); +} diff --git a/src/install.ts b/src/install.ts index 86ec8218..3c2e6831 100644 --- a/src/install.ts +++ b/src/install.ts @@ -2,22 +2,17 @@ import * as core from '@actions/core'; import * as cli from '@actions/exec'; import * as io from '@actions/io'; import * as path from 'path'; -import { - fromLocalCache, - fromRemoteCache, - toLocalCache, - toRemoteCache, -} from './cache'; +import { fromLocalCache, fromRemoteCache, toLocalCache, toRemoteCache } from './cache'; // eslint-disable-next-line @typescript-eslint/no-var-requires const registry = require('libnpm'); -interface InstallConfig { +type InstallConfig = { version: string; packager: string; cache?: boolean; cacheKey?: string; -} +}; /** * Resolve the provided semver to exact version of `expo-cli`. @@ -44,7 +39,7 @@ export async function install(config: InstallConfig) { } if (!root) { - root = await fromPackager(exact, config.packager) + root = await fromPackager(exact, config.packager); root = await toLocalCache(root, exact); if (config.cache) { diff --git a/src/run.ts b/src/run.ts index 2deceebd..ede5425c 100644 --- a/src/run.ts +++ b/src/run.ts @@ -13,10 +13,11 @@ export async function run() { addPath(path); - await authenticate( - getInput('expo-username'), - getInput('expo-password'), - ); + await authenticate({ + token: getInput('expo-token') || undefined, + username: getInput('expo-username') || undefined, + password: getInput('expo-password') || undefined, + }); const shouldPatchWatchers = getInput('expo-patch-watchers') || 'true'; diff --git a/src/system.ts b/src/system.ts index 390fbb8e..59248f9b 100644 --- a/src/system.ts +++ b/src/system.ts @@ -22,7 +22,7 @@ export async function patchWatchers() { await cli.exec('sudo sysctl fs.inotify.max_queued_events=524288'); await cli.exec('sudo sysctl -p'); } catch { - core.warning('Looks like we can\'t patch watchers/inotify limits, you might encouter the `ENOSPC` error.'); + core.warning("Looks like we can't patch watchers/inotify limits, you might encouter the `ENOSPC` error."); core.warning('For more info, https://github.com/expo/expo-github-action/issues/20'); } } diff --git a/tests/cache.test.ts b/tests/cache.test.ts index e7ff87b0..647db1b1 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -28,9 +28,13 @@ describe('toLocalCache', () => { }); describe('fromRemoteCache', () => { - const spy = { - restore: jest.spyOn(remoteCache, 'restoreCache').mockImplementation(), - }; + let spy: { [key: string]: jest.SpyInstance } = {}; + + beforeEach(() => { + spy = { + restore: jest.spyOn(remoteCache, 'restoreCache').mockImplementation(), + }; + }); beforeAll(() => { utils.setEnv('RUNNER_TOOL_CACHE', join('cache', 'path')); @@ -38,6 +42,7 @@ describe('fromRemoteCache', () => { afterAll(() => { utils.restoreEnv(); + spy.restore.mockRestore(); }); it('restores remote cache with default key', async () => { @@ -45,7 +50,7 @@ describe('fromRemoteCache', () => { expect(remoteCache.restoreCache).toBeCalledWith( join('cache', 'path', 'expo-cli', '3.20.1', os.arch()), `expo-cli-${process.platform}-${os.arch()}-yarn-3.20.1`, - `expo-cli-${process.platform}-${os.arch()}-yarn-3.20.1`, + `expo-cli-${process.platform}-${os.arch()}-yarn-3.20.1` ); }); @@ -54,14 +59,14 @@ describe('fromRemoteCache', () => { expect(remoteCache.restoreCache).toBeCalledWith( join('cache', 'path', 'expo-cli', '3.20.0', os.arch()), 'custom-cache-key', - 'custom-cache-key', + 'custom-cache-key' ); }); it('returns path when remote cache exists', async () => { spy.restore.mockResolvedValueOnce(true); await expect(cache.fromRemoteCache('3.20.1', 'npm')).resolves.toBe( - join('cache', 'path', 'expo-cli', '3.20.1', os.arch()), + join('cache', 'path', 'expo-cli', '3.20.1', os.arch()) ); }); @@ -73,15 +78,23 @@ describe('fromRemoteCache', () => { }); describe('toRemoteCache', () => { - const spy = { - save: jest.spyOn(remoteCache, 'saveCache').mockImplementation(), - }; + let spy: { [key: string]: jest.SpyInstance } = {}; + + beforeEach(() => { + spy = { + save: jest.spyOn(remoteCache, 'saveCache').mockImplementation(), + }; + }); + + afterAll(() => { + spy.save.mockRestore(); + }); it('saves remote cache with default key', async () => { expect(await cache.toRemoteCache(join('local', 'path'), '3.20.1', 'npm')).toBeUndefined(); expect(remoteCache.saveCache).toBeCalledWith( join('local', 'path'), - `expo-cli-${process.platform}-${os.arch()}-npm-3.20.1`, + `expo-cli-${process.platform}-${os.arch()}-npm-3.20.1` ); }); diff --git a/tests/expo.test.ts b/tests/expo.test.ts index eee781b9..d079d02b 100644 --- a/tests/expo.test.ts +++ b/tests/expo.test.ts @@ -3,41 +3,103 @@ import * as cli from '@actions/exec'; import * as expo from '../src/expo'; import * as utils from './utils'; -describe('authenticate', () => { - const spy = { - exec: jest.spyOn(cli, 'exec').mockImplementation(), - info: jest.spyOn(core, 'info').mockImplementation(), - }; +// describe('authenticate', () => { +// let spy: { [key: string]: jest.SpyInstance } = {}; + +// beforeAll(() => { +// spy = { +// info: jest.spyOn(core, 'info').mockImplementation(), +// exec: jest.spyOn(cli, 'exec').mockImplementation(), +// authWithToken: jest.spyOn(expo, 'authWithToken').mockImplementation(), +// authWithCredentials: jest.spyOn(expo, 'authWithCredentials').mockImplementation(), +// }; +// }); + +// afterAll(() => { +// spy.info.mockRestore(); +// spy.exec.mockRestore(); +// spy.authWithToken.mockRestore(); +// spy.authWithCredentials.mockRestore(); +// }); + +// it('skips authentication without token or credentials', async () => { +// await expo.authenticate({}); +// expect(spy.info).toBeCalledWith(expect.stringContaining('Skipping authentication')); +// }); + +// it('authenticates with token when provided', async () => { +// await expo.authenticate({ token: TOKEN }); +// expect(spy.authWithCredentials).not.toBeCalled(); +// expect(spy.authWithToken).toBeCalledWith(TOKEN); +// }); + +// it('authenticates with credentials when provided', async () => { +// await expo.authenticate({ username: USER, password: PASS }); +// expect(spy.authWithToken).not.toBeCalled(); +// expect(spy.authWithCredentials).toBeCalledWith(USER, PASS); +// }); + +// it('authenticates with token when token and credentials are provided', async () => { +// await expo.authenticate({ token: TOKEN, username: USER, password: PASS }); +// expect(spy.authWithCredentials).not.toBeCalled(); +// expect(spy.authWithToken).toBeCalledWith(TOKEN); +// }); +// }); + +const TOKEN = 'ABC123'; +const USER = 'bycedric'; +const PASS = 'mypassword'; + +describe('authWithCredentials', () => { + let spy: { [key: string]: jest.SpyInstance } = {}; + + beforeEach(() => { + spy = { + exec: jest.spyOn(cli, 'exec').mockImplementation(), + info: jest.spyOn(core, 'info').mockImplementation(), + }; + }); + + afterAll(() => { + spy.exec.mockRestore(); + spy.info.mockRestore(); + }); it('skips authentication without credentials', async () => { - await expo.authenticate('', ''); + await expo.authWithCredentials(); expect(spy.exec).not.toBeCalled(); - expect(spy.info).toBeCalled(); + expect(spy.info).toBeCalledWith(expect.stringContaining('Skipping authentication')); }); it('skips authentication without password', async () => { - await expo.authenticate('bycedric', ''); + await expo.authWithCredentials(USER); expect(spy.exec).not.toBeCalled(); - expect(spy.info).toBeCalled(); + expect(spy.info).toBeCalledWith(expect.stringContaining('Skipping authentication')); }); it('executes login command with password through environment', async () => { utils.setEnv('TEST_INCLUDED', 'hellyeah'); - await expo.authenticate('bycedric', 'mypassword'); + await expo.authWithCredentials(USER, PASS); expect(spy.exec).toBeCalled(); - expect(spy.exec.mock.calls[0][1]).toStrictEqual(['login', '--username=bycedric']) + expect(spy.exec.mock.calls[0][1]).toStrictEqual(['login', `--username=${USER}`]); expect(spy.exec.mock.calls[0][2]).toMatchObject({ env: { TEST_INCLUDED: 'hellyeah', - EXPO_CLI_PASSWORD: 'mypassword', + EXPO_CLI_PASSWORD: PASS, }, }); utils.restoreEnv(); }); + it('fails when credentials are incorrect', async () => { + const error = new Error('Invalid username/password. Please try again.'); + spy.exec.mockRejectedValue(error); + await expect(expo.authWithCredentials(USER, PASS)).rejects.toBe(error); + }); + it('executes login command with `expo` on macos', async () => { utils.setPlatform('darwin'); - await expo.authenticate('bycedric', 'mypassword'); + await expo.authWithCredentials(USER, PASS); expect(spy.exec).toBeCalled(); expect(spy.exec.mock.calls[0][0]).toBe('expo'); utils.restorePlatform(); @@ -45,7 +107,7 @@ describe('authenticate', () => { it('executes login command with `expo` on ubuntu', async () => { utils.setPlatform('linux'); - await expo.authenticate('bycedric', 'mypassword'); + await expo.authWithCredentials(USER, PASS); expect(spy.exec).toBeCalled(); expect(spy.exec.mock.calls[0][0]).toBe('expo'); utils.restorePlatform(); @@ -53,15 +115,77 @@ describe('authenticate', () => { it('executes login command with `expo.cmd` on windows', async () => { utils.setPlatform('win32'); - await expo.authenticate('bycedric', 'mypassword'); + await expo.authWithCredentials(USER, PASS); expect(spy.exec).toBeCalled(); expect(spy.exec.mock.calls[0][0]).toBe('expo.cmd'); utils.restorePlatform(); }); +}); - it('fails when credentials are incorrect', async () => { - const error = new Error('Invalid username/password. Please try again.'); +describe('authWithToken', () => { + let spy: { [key: string]: jest.SpyInstance } = {}; + + beforeEach(() => { + spy = { + exportVariable: jest.spyOn(core, 'exportVariable').mockImplementation(), + exec: jest.spyOn(cli, 'exec').mockImplementation(), + info: jest.spyOn(core, 'info').mockImplementation(), + }; + }); + + afterAll(() => { + spy.exportVariable.mockRestore(); + spy.exec.mockRestore(); + spy.info.mockRestore(); + }); + + it('skips authentication without token', async () => { + await expo.authWithToken(); + expect(spy.exec).not.toBeCalled(); + expect(spy.info).toBeCalledWith(expect.stringContaining('Skipping authentication')); + }); + + it('executes whoami command with token through environment', async () => { + utils.setEnv('TEST_INCLUDED', 'hellyeah'); + await expo.authWithToken(TOKEN); + expect(spy.exec).toBeCalled(); + expect(spy.exec.mock.calls[0][1]).toStrictEqual(['whoami']); + expect(spy.exec.mock.calls[0][2]).toMatchObject({ + env: { + TEST_INCLUDED: 'hellyeah', + EXPO_TOKEN: TOKEN, + }, + }); + utils.restoreEnv(); + }); + + it('fails when token is incorrect', async () => { + const error = new Error('Not logged in'); spy.exec.mockRejectedValue(error); - await expect(expo.authenticate('bycedric', 'incorrect')).rejects.toBe(error); + await expect(expo.authWithToken(TOKEN)).rejects.toBe(error); + }); + + it('executes whoami command with `expo` on macos', async () => { + utils.setPlatform('darwin'); + await expo.authWithToken(TOKEN); + expect(spy.exec).toBeCalled(); + expect(spy.exec.mock.calls[0][0]).toBe('expo'); + utils.restorePlatform(); + }); + + it('executes whoami command with `expo` on ubuntu', async () => { + utils.setPlatform('linux'); + await expo.authWithToken(TOKEN); + expect(spy.exec).toBeCalled(); + expect(spy.exec.mock.calls[0][0]).toBe('expo'); + utils.restorePlatform(); + }); + + it('executes whoami command with `expo.cmd` on windows', async () => { + utils.setPlatform('win32'); + await expo.authWithToken(TOKEN); + expect(spy.exec).toBeCalled(); + expect(spy.exec.mock.calls[0][0]).toBe('expo.cmd'); + utils.restorePlatform(); }); }); diff --git a/tests/run.test.ts b/tests/run.test.ts index cd5df3ac..f3879a52 100644 --- a/tests/run.test.ts +++ b/tests/run.test.ts @@ -15,6 +15,7 @@ import { run } from '../src/run'; interface MockInputProps { version?: string; packager?: string; + token?: string; username?: string; password?: string; patchWatchers?: string; @@ -26,14 +27,24 @@ const mockInput = (props: MockInputProps = {}) => { // fix: kind of dirty workaround for missing "mock 'value' based on arguments" const input = (name: string) => { switch (name) { - case 'expo-version': return props.version || ''; - case 'expo-packager': return props.packager || ''; - case 'expo-username': return props.username || ''; - case 'expo-password': return props.password || ''; - case 'expo-patch-watchers': return props.patchWatchers || ''; - case 'expo-cache': return props.cache || ''; - case 'expo-cache-key': return props.cacheKey || ''; - default: return ''; + case 'expo-version': + return props.version || ''; + case 'expo-packager': + return props.packager || ''; + case 'expo-token': + return props.token || ''; + case 'expo-username': + return props.username || ''; + case 'expo-password': + return props.password || ''; + case 'expo-patch-watchers': + return props.patchWatchers || ''; + case 'expo-cache': + return props.cache || ''; + case 'expo-cache-key': + return props.cacheKey || ''; + default: + return ''; } }; @@ -80,6 +91,12 @@ describe('run', () => { it('authenticates with provided credentials', async () => { mockInput({ username: 'bycedric', password: 'mypassword', patchWatchers: 'false' }); await run(); - expect(expo.authenticate).toBeCalledWith('bycedric', 'mypassword'); + expect(expo.authenticate).toBeCalledWith({ username: 'bycedric', password: 'mypassword' }); + }); + + it('authenticates with provided token', async () => { + mockInput({ token: 'ABC123', patchWatchers: 'false' }); + await run(); + expect(expo.authenticate).toBeCalledWith({ token: 'ABC123' }); }); }); diff --git a/tests/system.test.ts b/tests/system.test.ts index 873fe00c..43a6579a 100644 --- a/tests/system.test.ts +++ b/tests/system.test.ts @@ -4,16 +4,26 @@ import * as system from '../src/system'; import * as utils from './utils'; describe('patchWatchers', () => { - const spy = { - info: jest.spyOn(core, 'info').mockImplementation(), - warning: jest.spyOn(core, 'warning').mockImplementation(), - exec: jest.spyOn(cli, 'exec').mockImplementation(), - }; + let spy: { [key: string]: jest.SpyInstance } = {}; + + beforeEach(() => { + spy = { + info: jest.spyOn(core, 'info').mockImplementation(), + warning: jest.spyOn(core, 'warning').mockImplementation(), + exec: jest.spyOn(cli, 'exec').mockImplementation(), + }; + }); afterEach(() => { utils.restorePlatform(); }); + afterAll(() => { + spy.info.mockRestore(); + spy.warning.mockRestore(); + spy.exec.mockRestore(); + }); + it('increses fs inotify settings with sysctl', async () => { utils.setPlatform('linux'); await system.patchWatchers(); @@ -28,7 +38,7 @@ describe('patchWatchers', () => { spy.exec.mockRejectedValueOnce(error); utils.setPlatform('linux'); await system.patchWatchers(); - expect(core.warning).toBeCalledWith(expect.stringContaining('can\'t patch watchers')); + expect(core.warning).toBeCalledWith(expect.stringContaining("can't patch watchers")); expect(core.warning).toBeCalledWith( expect.stringContaining('https://github.com/expo/expo-github-action/issues/20') );