diff --git a/.github/workflows/setup-gcloud.yml b/.github/workflows/setup-gcloud.yml index e7566527d..c4f490dc8 100644 --- a/.github/workflows/setup-gcloud.yml +++ b/.github/workflows/setup-gcloud.yml @@ -33,3 +33,7 @@ jobs: - name: Lint working-directory: ./setup-gcloud run: npm run lint + + - name: Test + working-directory: ./setup-gcloud + run: npm test diff --git a/setup-gcloud/README.md b/setup-gcloud/README.md index 7df9cd417..9b9cee29a 100644 --- a/setup-gcloud/README.md +++ b/setup-gcloud/README.md @@ -72,10 +72,12 @@ steps: * `service_account_email`: (Optional) Service account email address to use for authentication. This is required for legacy .p12 keys but can be omitted for .json keys. This is usually of the format `@.iam.gserviceaccount.com`. -* `service_account_key`: (Optional) The service account key which will be used for authentication. This key should be [created](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) and stored as a [secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets). It can be encoded as a [Base64](https://en.wikipedia.org/wiki/Base64) string (eg. `cat my-key.json | base64` on macOS) or as JSON. +* `service_account_key`: (Optional) The service account key which will be used for authentication. This key should be [created](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) and stored as a [secret](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets). It can be encoded as a [Base64](https://en.wikipedia.org/wiki/Base64) string (eg. `cat my-key.json | base64` on macOS) or as JSON. * `export_default_credentials`: (Optional) Export the provided credentials as [Google Default Application Credentials][dac]. This will make the credentials available to later steps. Future steps that consume Default Application Credentials will automatically detect and use these credentials. +* `credentials_file_path`: (Optional) Only valid when `export_default_credentials` is `true`. Sets the path at which the credentials should be written. If not provided, `GITHUB_WORKSPACE` is used. + * `project_id`: (Optional) ID of the Google Cloud project. If provided, this will configure gcloud to use this project ID by default for commands. Individual commands can still override the project using the --project flag which takes precedence. [dac]: https://cloud.google.com/docs/authentication/production diff --git a/setup-gcloud/action.yml b/setup-gcloud/action.yml index 44ee73ca8..b073dac9c 100644 --- a/setup-gcloud/action.yml +++ b/setup-gcloud/action.yml @@ -58,6 +58,14 @@ inputs: these credentials. default: false required: false + credentials_file_path: + description: |- + The path and name of the file to which to write the shared default + credentials. This option is only valid when + export_default_credentials=true. By default, the credentials will be + written to a new file in the root of GITHUB_WORKSPACE. + default: null + required: false runs: using: node12 diff --git a/setup-gcloud/dist/index.js b/setup-gcloud/dist/index.js index 014fe90a7..578eac816 100644 --- a/setup-gcloud/dist/index.js +++ b/setup-gcloud/dist/index.js @@ -16327,7 +16327,7 @@ function run() { version = yield setupGcloud.getLatestGcloudSDKVersion(); } // Install the gcloud if not already present - if (!setupGcloud.isInstalled()) { + if (!setupGcloud.isInstalled(version)) { yield setupGcloud.installGcloudSDK(version); } else { diff --git a/setup-gcloud/package-lock.json b/setup-gcloud/package-lock.json index 3caf43aab..ffeab3d78 100644 --- a/setup-gcloud/package-lock.json +++ b/setup-gcloud/package-lock.json @@ -84,6 +84,51 @@ "resolved": "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.0.0.tgz", "integrity": "sha512-Ibk4Vfl46dSrhtH5fHsrTA4waAuyP7/qcr3uo0mO70azRc6LWgJILlMy3B1oOvyiN9jQcdqwsThaQkPKLiYKTg==" }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", + "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/chai": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.9.tgz", @@ -135,6 +180,12 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz", + "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", + "dev": true + }, "@types/tmp": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz", @@ -1235,6 +1286,12 @@ "has-symbols": "^1.0.1" } }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1275,6 +1332,12 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "just-extend": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -1306,6 +1369,12 @@ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -1501,6 +1570,19 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -1664,6 +1746,15 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -1863,6 +1954,44 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "sinon": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", + "integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.2", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.0.3", + "diff": "^4.0.2", + "nise": "^4.0.1", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/setup-gcloud/package.json b/setup-gcloud/package.json index ed7e05ee5..29e6802dd 100644 --- a/setup-gcloud/package.json +++ b/setup-gcloud/package.json @@ -4,8 +4,9 @@ "description": "Setup gcloud GitHub action", "main": "dist/index.js", "scripts": { - "build": "ncc build src/setup-gcloud.ts", + "build": "ncc build src/index.ts", "lint": "eslint . --ext .ts,.tsx", + "test": "mocha -r ts-node/register -t 180s 'tests/*.test.ts'", "format": "prettier --write **/*.ts" }, "repository": { @@ -38,18 +39,20 @@ "@types/mocha": "^7.0.1", "@types/node": "^13.7.4", "@types/semver": "^7.1.0", + "@types/sinon": "^7.5.2", "@types/tmp": "^0.1.0", "@types/uuid": "^3.4.7", "@typescript-eslint/eslint-plugin": "^2.20.0", "@typescript-eslint/parser": "^2.20.0", "@zeit/ncc": "^0.21.0", "chai": "^4.2.0", + "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-prettier": "^3.1.2", - "eslint": "^6.8.0", "husky": "^4.2.3", "mocha": "^7.0.1", "prettier": "^1.19.1", + "sinon": "^9.0.2", "ts-node": "^8.6.2", "typescript": "^3.8.2" } diff --git a/setup-gcloud/src/index.ts b/setup-gcloud/src/index.ts new file mode 100644 index 000000000..e7aa757a4 --- /dev/null +++ b/setup-gcloud/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { run } from './setup-gcloud'; + +run(); diff --git a/setup-gcloud/src/setup-gcloud.ts b/setup-gcloud/src/setup-gcloud.ts index 52399ac90..15892c93e 100644 --- a/setup-gcloud/src/setup-gcloud.ts +++ b/setup-gcloud/src/setup-gcloud.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import * as core from '@actions/core'; import * as toolCache from '@actions/tool-cache'; import * as setupGcloud from '../../setupGcloudSDK/dist/index'; @@ -20,7 +21,7 @@ import { promises as fs } from 'fs'; import path from 'path'; import { v4 as uuidv4 } from 'uuid'; -async function run(): Promise { +export async function run(): Promise { try { let version = core.getInput('version'); if (!version || version == 'latest') { @@ -56,15 +57,23 @@ async function run(): Promise { // all steps. const exportCreds = core.getInput('export_default_credentials'); if (String(exportCreds).toLowerCase() === 'true') { - const workspace = process.env.GITHUB_WORKSPACE; - if (!workspace) { - throw new Error('Missing GITHUB_WORKSPACE!'); + let credsPath = core.getInput('credentials_file_path'); + + if (!credsPath) { + const credsDir = process.env.GITHUB_WORKSPACE; + if (!credsDir) { + throw new Error( + 'No path for credentials. Set credentials_file_path or process.env.GITHUB_WORKSPACE', + ); + } + + credsPath = path.join(credsDir, uuidv4()); } - const credsPath = path.join(workspace, uuidv4()); const serviceAccountKeyObj = setupGcloud.parseServiceAccountKey( serviceAccountKey, ); + await fs.writeFile( credsPath, JSON.stringify(serviceAccountKeyObj, null, 2), // Print to file as string w/ indents @@ -80,5 +89,3 @@ async function run(): Promise { core.setFailed(error.message); } } - -run(); diff --git a/setup-gcloud/tests/setup-gcloud.test.ts b/setup-gcloud/tests/setup-gcloud.test.ts new file mode 100644 index 000000000..52d134103 --- /dev/null +++ b/setup-gcloud/tests/setup-gcloud.test.ts @@ -0,0 +1,211 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Tests setup-gcloud. + */ +import 'mocha'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; + +import os from 'os'; +import { promises as fs } from 'fs'; +import * as setupGcloud from '../../setupGcloudSDK/dist/index'; +import * as core from '@actions/core'; +import * as toolCache from '@actions/tool-cache'; + +import { run } from '../src/setup-gcloud'; + +/* eslint-disable @typescript-eslint/camelcase */ +// These are mock data for github actions inputs, where camel case is expected. +const fakeInputs: { [key: string]: string } = { + version: '999', + project_id: 'test', + service_account_key: 'abc', + export_default_credentials: 'false', + credentials_file_path: '/creds', +}; +/* eslint-enable @typescript-eslint/camelcase */ + +function getInputMock(name: string): string { + return fakeInputs[name]; +} + +describe('#run', function() { + beforeEach(async function() { + this.stubs = { + getInput: sinon.stub(core, 'getInput').callsFake(getInputMock), + exportVariable: sinon.stub(core, 'exportVariable'), + setFailed: sinon.stub(core, 'setFailed'), + installGcloudSDK: sinon.stub(setupGcloud, 'installGcloudSDK'), + authenticateGcloudSDK: sinon.stub(setupGcloud, 'authenticateGcloudSDK'), + isInstalled: sinon.stub(setupGcloud, 'isInstalled').returns(false), + setProject: sinon.stub(setupGcloud, 'setProject'), + parseServiceAccountKey: sinon.stub(setupGcloud, 'parseServiceAccountKey'), + toolCacheFind: sinon.stub(toolCache, 'find').returns('/'), + writeFile: sinon.stub(fs, 'writeFile'), + env: sinon.stub(process, 'env').value({}), + }; + }); + + afterEach(function() { + Object.keys(this.stubs).forEach((k) => this.stubs[k].restore()); + }); + + it('downloads the latest gcloud SDK if version is not provided', async function() { + this.stubs.getInput.withArgs('version').returns(''); + await run(); + // getLatestGcloudSDKVersion is implemented as a getter on on exports and so stubbing doesn't work. + // Instead, make sure that installGcloudSDK was called with an expected version string. + expect( + this.stubs.installGcloudSDK.withArgs(sinon.match(/\d+\.\d+\.\d+/)) + .callCount, + ).to.eq(1); + }); + + it('downloads the latest gcloud SDK if version is "latest"', async function() { + this.stubs.getInput.withArgs('version').returns('latest'); + await run(); + expect( + this.stubs.installGcloudSDK.withArgs(sinon.match(/\d+\.\d+\.\d+/)) + .callCount, + ).to.eq(1); + }); + + it('doesnt download the SDK if version is provided', async function() { + this.stubs.getInput.withArgs('version').returns('999'); + await run(); + expect(this.stubs.installGcloudSDK.withArgs('999').callCount).to.eq(1); + }); + + it('installs the gcloud SDK if it is not already installed', async function() { + this.stubs.isInstalled.returns(false); + this.stubs.getInput.withArgs('version').returns('888'); + await run(); + expect(this.stubs.installGcloudSDK.withArgs('888').callCount).to.eq(1); + }); + + it('uses the cached gcloud SDK if it was already installed', async function() { + this.stubs.isInstalled.returns(true); + this.stubs.getInput.withArgs('version').returns('777'); + await run(); + expect(this.stubs.toolCacheFind.withArgs('gcloud', '777').callCount).to.eq( + 1, + ); + }); + + it('sets the project ID if provided', async function() { + this.stubs.getInput.withArgs('project_id').returns('test'); + await run(); + expect(this.stubs.setProject.withArgs('test').callCount).to.eq(1); + }); + + it('does not set the project ID if not provided', async function() { + this.stubs.getInput.withArgs('project_id').returns(''); + await run(); + expect(this.stubs.setProject.callCount).to.eq(0); + }); + + it('does not run any authentication functions if key not provided', async function() { + this.stubs.getInput.withArgs('service_account_key').returns(''); + await run(); + expect(this.stubs.authenticateGcloudSDK.callCount).to.eq(0); + // test early return + expect( + this.stubs.getInput.withArgs('export_default_credentials').callCount, + ).to.eq(0); + }); + + it('authenticates if key is provided', async function() { + this.stubs.getInput.withArgs('service_account_key').returns('key'); + await run(); + expect(this.stubs.authenticateGcloudSDK.withArgs('key').callCount).to.eq(1); + }); + + it('writes default credentials to disk and exports the path if export_default_credentials=true', async function() { + this.stubs.env.value({ GITHUB_WORKSPACE: '/usr/workspace' }); + this.stubs.getInput.withArgs('export_default_credentials').returns('true'); + this.stubs.getInput.withArgs('credentials_file_path').returns(''); + this.stubs.getInput.withArgs('service_account_key').returns('key'); + this.stubs.parseServiceAccountKey.withArgs('key').returns({ json: true }); + + await run(); + + expect(this.stubs.parseServiceAccountKey.withArgs('key').callCount).to.eq( + 1, + ); + + let expectedPath; + if (os.platform() === 'win32') { + expectedPath = sinon.match('\\usr\\workspace'); + } else { + expectedPath = sinon.match('/usr/workspace'); + } + + expect( + this.stubs.writeFile.withArgs(expectedPath, sinon.match.string).callCount, + ).to.eq(1); + + expect( + this.stubs.exportVariable.withArgs( + 'GOOGLE_APPLICATION_CREDENTIALS', + expectedPath, + ).callCount, + ).to.eq(1); + }); + + it('works if export_default_credentials is a boolean', async function() { + this.stubs.getInput.withArgs('export_default_credentials').returns(true); + this.stubs.getInput.withArgs('credentials_file_path').returns('/'); + this.stubs.getInput.withArgs('service_account_key').returns('key'); + + await run(); + + expect(this.stubs.writeFile.callCount).to.eq(1); + }); + + it('works if export_default_credentials is all caps', async function() { + this.stubs.getInput.withArgs('export_default_credentials').returns('TRUE'); + this.stubs.getInput.withArgs('credentials_file_path').returns('/'); + this.stubs.getInput.withArgs('service_account_key').returns('key'); + + await run(); + + expect(this.stubs.writeFile.callCount).to.eq(1); + }); + + it('writes credentials to the given path if provided', async function() { + this.stubs.getInput.withArgs('export_default_credentials').returns('true'); + this.stubs.getInput.withArgs('credentials_file_path').returns('/usr/creds'); + + await run(); + + expect(this.stubs.writeFile.withArgs('/usr/creds').callCount).to.eq(1); + expect( + this.stubs.exportVariable.withArgs( + 'GOOGLE_APPLICATION_CREDENTIALS', + '/usr/creds', + ).callCount, + ).to.eq(1); + }); + + it('throws an error if credentials_file_path is not provided and GITHUB_WORKSPACE is not set', async function() { + this.stubs.getInput.withArgs('export_default_credentials').returns('true'); + this.stubs.getInput.withArgs('credentials_file_path').returns(''); + await run(); + expect(this.stubs.setFailed.callCount).to.eq(1); + }); +});