From 0870a409e847e3648508fd2b75155ad310c3492c Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Sun, 23 Dec 2018 11:26:54 -0800 Subject: [PATCH 1/2] test: improve samples and add tests --- package.json | 4 +- samples/.eslintrc.yml | 1 + samples/adc.js | 5 +- samples/compute.js | 8 +-- samples/{creds.js => credentials.js} | 19 ++++++- samples/fromJSON.js | 57 --------------------- samples/{authRequest.js => headers.js} | 25 +++++++-- samples/jwt.js | 18 ++++--- samples/keyfile.js | 11 ++-- samples/package.json | 11 ++-- samples/system-test/samples.js | 17 ------ samples/test/.eslintrc.yml | 3 ++ samples/test/jwt.test.js | 71 ++++++++++++++++++++++++++ system-test/test.kitchen.ts | 44 ++++++++-------- 14 files changed, 174 insertions(+), 120 deletions(-) rename samples/{creds.js => credentials.js} (61%) delete mode 100644 samples/fromJSON.js rename samples/{authRequest.js => headers.js} (54%) delete mode 100644 samples/system-test/samples.js create mode 100644 samples/test/.eslintrc.yml create mode 100644 samples/test/jwt.test.js diff --git a/package.json b/package.json index 7489fdaa..6a9c39c9 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "main": "./build/src/index.js", "types": "./build/src/index.d.ts", - "repository": "google/google-auth-library-nodejs.git", + "repository": "googleapis/google-auth-library-nodejs.git", "keywords": [ "google", "api", @@ -77,7 +77,7 @@ "license-check": "jsgl --local .", "docs": "compodoc src/ && touch docs/.nojekyll", "publish-docs": "gh-pages -d docs --remote upstream && git push upstream gh-pages", - "samples-test": "mocha samples/system-test", + "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "system-test": "mocha build/system-test", "presystem-test": "npm run compile" }, diff --git a/samples/.eslintrc.yml b/samples/.eslintrc.yml index 0aa37ac6..da24d6c7 100644 --- a/samples/.eslintrc.yml +++ b/samples/.eslintrc.yml @@ -2,3 +2,4 @@ rules: no-console: off node/no-missing-require: off + node/no-unpublished-require: off diff --git a/samples/adc.js b/samples/adc.js index a79c3847..5168e6a6 100644 --- a/samples/adc.js +++ b/samples/adc.js @@ -22,10 +22,13 @@ const {auth} = require('google-auth-library'); * Acquire a client, and make a request to an API that's enabled by default. */ async function main() { - const client = await auth.getClient(); + const client = await auth.getClient({ + scopes: 'https://www.googleapis.com/auth/cloud-platform', + }); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); + console.log('DNS Info:'); console.log(res.data); } diff --git a/samples/compute.js b/samples/compute.js index 104ae149..1c900f18 100644 --- a/samples/compute.js +++ b/samples/compute.js @@ -13,10 +13,12 @@ 'use strict'; -const {Compute} = require('google-auth-library'); +const {auth, Compute} = require('google-auth-library'); /** - * Acquire a client, and make a request to an API that's enabled by default. + * This example directly instantiates a Compute client to acquire credentials. + * Generally, you wouldn't directly create this class, rather call the + * `auth.getClient()` method to automatically obtain credentials. */ async function main() { const client = new Compute({ @@ -24,7 +26,7 @@ async function main() { // service account if one is not defined. serviceAccountEmail: 'some-service-account@example.com', }); - const projectId = 'el-gato'; + const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); console.log(res.data); diff --git a/samples/creds.js b/samples/credentials.js similarity index 61% rename from samples/creds.js rename to samples/credentials.js index 6f1299f8..3080b107 100644 --- a/samples/creds.js +++ b/samples/credentials.js @@ -19,16 +19,31 @@ const {auth} = require('google-auth-library'); /** - * Acquire a client, and make a request to an API that's enabled by default. + * This sample demonstrates passing a `credentials` object directly into the + * `getClient` method. This is useful if you're storing the fields requiretd + * in environment variables. The original `client_email` and `private_key` + * values are obtained from a service account credential file. */ async function main() { + const clientEmail = process.env.CLIENT_EMAIL; + const privateKey = process.env.PRIVATE_KEY; + if (!clientEmail || !privateKey) { + throw new Error(` + The CLIENT_EMAIL and PRIVATE_KEY environment variables are required for + this sample. + `); + } const client = await auth.getClient({ - credentials: require('./jwt.keys.json'), + credentials: { + client_email: clientEmail, + private_key: privateKey, + }, scopes: 'https://www.googleapis.com/auth/cloud-platform', }); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); + console.log('DNS Info:'); console.log(res.data); } diff --git a/samples/fromJSON.js b/samples/fromJSON.js deleted file mode 100644 index 4ef95477..00000000 --- a/samples/fromJSON.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017, Google, Inc. -// 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. - -'use strict'; - -const {auth} = require('google-auth-library'); - -/** - * Instead of loading credentials from a key file, you can also provide - * them using an environment variable and the `fromJSON` method. This - * is particularly convenient for systems that deploy directly from source - * control (Heroku, App Engine, etc). - * - * To run this program, you can create an environment variables that contains - * the keys: - * - * $ export CREDS='{ - * "type": "service_account", - * "project_id": "your-project-id", - * "private_key_id": "your-private-key-id", - * "private_key": "your-private-key", - * "client_email": "your-client-email", - * "client_id": "your-client-id", - * "auth_uri": "https://accounts.google.com/o/oauth2/auth", - * "token_uri": "https://accounts.google.com/o/oauth2/token", - * "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - * "client_x509_cert_url": "your-cert-url" - * }' - * $ node fromJSON.js - * - **/ - -const keysEnvVar = process.env['CREDS']; -if (!keysEnvVar) { - throw new Error('The $CREDS environment variable was not found!'); -} -const keys = JSON.parse(keysEnvVar); - -async function main() { - const client = auth.fromJSON(keys); - client.scopes = ['https://www.googleapis.com/auth/cloud-platform']; - const url = `https://www.googleapis.com/dns/v1/projects/${keys.project_id}`; - const res = await client.request({url}); - console.log(res.data); -} - -main().catch(console.error); diff --git a/samples/authRequest.js b/samples/headers.js similarity index 54% rename from samples/authRequest.js rename to samples/headers.js index 7353ef7d..02fe30b1 100644 --- a/samples/authRequest.js +++ b/samples/headers.js @@ -17,17 +17,32 @@ * Import the GoogleAuth library, and create a new GoogleAuth client. */ const {auth} = require('google-auth-library'); -const axios = require('axios'); +const fetch = require('node-fetch'); /** - * Acquire a client, and make a request to an API that's enabled by default. + * This example shows obtaining authenticated HTTP request headers, and using + * those headers to construct your own authenticated request. This example uses + * node-fetch, but you could use any HTTP client you like. */ async function main() { const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; - const opts = await auth.authorizeRequest(); - const res = await axios.get(url, opts); - console.log(res.data); + + // obtain an authenticated client + const client = await auth.getClient({ + scopes: 'https://www.googleapis.com/auth/cloud-platform', + }); + + // Use the client to get authenticated request headers + const headers = await client.getRequestHeaders(); + console.log('Headers:'); + console.log(headers); + + // Attach those headers to another request, and use it to call a Google API + const res = await fetch(url, {headers}); + const data = await res.json(); + console.log('DNS Info:'); + console.log(data); } main().catch(console.error); diff --git a/samples/jwt.js b/samples/jwt.js index f72b95e2..72bb83f2 100644 --- a/samples/jwt.js +++ b/samples/jwt.js @@ -13,19 +13,23 @@ 'use strict'; -const {JWT} = require('google-auth-library'); - /** * The JWT authorization is ideal for performing server-to-server - * communication without asking for user consent. + * communication without asking for user consent. Usually, you aren't + * going to directly instantiate a JWT instance. Typically, this is acquired + * by using the `auth.getClient()` method. * * Suggested reading for Admin SDK users using service accounts: * https://developers.google.com/admin-sdk/directory/v1/guides/delegation **/ -const keys = require('./jwt.keys.json'); +const {JWT} = require('google-auth-library'); -async function main() { +async function main( + // Full path to the sevice account credential + keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS +) { + const keys = require(keyFile); const client = new JWT({ email: keys.client_email, key: keys.private_key, @@ -33,6 +37,7 @@ async function main() { }); const url = `https://www.googleapis.com/dns/v1/projects/${keys.project_id}`; const res = await client.request({url}); + console.log('DNS Info:'); console.log(res.data); // After acquiring an access_token, you may want to check on the audience, expiration, @@ -41,4 +46,5 @@ async function main() { console.log(tokenInfo); } -main().catch(console.error); +const args = process.argv.slice(2); +main(...args).catch(console.error); diff --git a/samples/keyfile.js b/samples/keyfile.js index 8dbcc47f..846416ac 100644 --- a/samples/keyfile.js +++ b/samples/keyfile.js @@ -21,15 +21,20 @@ const {auth} = require('google-auth-library'); /** * Acquire a client, and make a request to an API that's enabled by default. */ -async function main() { +async function main( + // Full path to the sevice account credential + keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS +) { const client = await auth.getClient({ - keyFilename: 'jwt.keys.json', + keyFile: keyFile, scopes: 'https://www.googleapis.com/auth/cloud-platform', }); const projectId = await auth.getProjectId(); const url = `https://www.googleapis.com/dns/v1/projects/${projectId}`; const res = await client.request({url}); + console.log('DNS Info:'); console.log(res.data); } -main().catch(console.error); +const args = process.argv.slice(2); +main(...args).catch(console.error); diff --git a/samples/package.json b/samples/package.json index 072e7d82..9ac8106e 100644 --- a/samples/package.json +++ b/samples/package.json @@ -1,20 +1,25 @@ { "name": "google-auth-library-samples", "description": "A set of samples for the google-auth-library npm module.", + "files": [ + "*.js" + ], "scripts": { - "test": "mocha system-test" + "test": "mocha --timeout 60000" }, "engines": { - "node": ">=8" + "node": ">=10" }, "license": "Apache-2.0", "dependencies": { - "axios": "^0.18.0", "google-auth-library": "^2.0.2", + "node-fetch": "^2.3.0", "opn": "^5.3.0", "server-destroy": "^1.0.1" }, "devDependencies": { + "chai": "^4.2.0", + "execa": "^1.0.0", "mocha": "^5.2.0" } } diff --git a/samples/system-test/samples.js b/samples/system-test/samples.js deleted file mode 100644 index fdf8592a..00000000 --- a/samples/system-test/samples.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright 2018 Google LLC. All Rights Reserved. - * - * 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. - */ - -console.warn('no samples tests available 👻'); diff --git a/samples/test/.eslintrc.yml b/samples/test/.eslintrc.yml new file mode 100644 index 00000000..6db2a46c --- /dev/null +++ b/samples/test/.eslintrc.yml @@ -0,0 +1,3 @@ +--- +env: + mocha: true diff --git a/samples/test/jwt.test.js b/samples/test/jwt.test.js new file mode 100644 index 00000000..0b970ded --- /dev/null +++ b/samples/test/jwt.test.js @@ -0,0 +1,71 @@ +/** + * Copyright 2018 Google LLC. All Rights Reserved. + * + * 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. + */ + +const execa = require('execa'); +const {assert} = require('chai'); +const fs = require('fs'); +const {promisify} = require('util'); + +const readFile = promisify(fs.readFile); + +const exec = async cmd => { + const res = await execa.shell(cmd); + assert.isEmpty(res.stderr); + return res.stdout; +}; + +const keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS; + +describe('samples', () => { + it('should acquire application default credentials', async () => { + const output = await exec('node adc'); + assert.match(output, /DNS Info:/); + }); + + it.skip('should acquire compute credentials', async () => { + // TODO: need to figure out deploying to GCF for this to work + const output = await exec('node compute'); + assert.match(output, /DNS Info:/); + }); + + it('should create a JWT', async () => { + const output = await exec('node jwt'); + assert.match(output, /DNS Info:/); + }); + + it('should read from a keyfile', async () => { + const output = await exec('node keyfile'); + assert.match(output, /DNS Info:/); + }); + + it('should allow directly passing creds', async () => { + const keys = JSON.parse(await readFile(keyFile, 'utf8')); + const res = await execa('node', ['credentials'], { + env: { + CLIENT_EMAIL: keys.client_email, + PRIVATE_KEY: keys.private_key, + }, + }); + assert.isEmpty(res.stderr); + assert.match(res.stdout, /DNS Info:/); + }); + + it('should obtain headers for a request', async () => { + const output = await exec('node headers'); + assert.match(output, /Headers:/); + assert.match(output, /DNS Info:/); + }); +}); diff --git a/system-test/test.kitchen.ts b/system-test/test.kitchen.ts index 8b368e91..31eadc4a 100644 --- a/system-test/test.kitchen.ts +++ b/system-test/test.kitchen.ts @@ -27,26 +27,28 @@ const stagingDir = tmp.dirSync({keep, unsafeCleanup: true}); const stagingPath = stagingDir.name; const pkg = require('../../package.json'); -/** - * Create a staging directory with temp fixtures used to test on a fresh - * application. - */ -it('should be able to use the d.ts', async () => { - console.log(`${__filename} staging area: ${stagingPath}`); - await execa('npm', ['pack'], {stdio: 'inherit'}); - const tarball = `${pkg.name}-${pkg.version}.tgz`; - // stagingPath can be on another filesystem so fs.rename() will fail - // with EXDEV, hence we use `mv` module here. - await mvp(tarball, `${stagingPath}/google-auth-library.tgz`); - await ncpp('system-test/fixtures/kitchen', `${stagingPath}/`); - await execa('npm', ['install'], {cwd: `${stagingPath}/`, stdio: 'inherit'}); -}).timeout(40000); +describe('pack and install', () => { + /** + * Create a staging directory with temp fixtures used to test on a fresh + * application. + */ + it('should be able to use the d.ts', async () => { + console.log(`${__filename} staging area: ${stagingPath}`); + await execa('npm', ['pack'], {stdio: 'inherit'}); + const tarball = `${pkg.name}-${pkg.version}.tgz`; + // stagingPath can be on another filesystem so fs.rename() will fail + // with EXDEV, hence we use `mv` module here. + await mvp(tarball, `${stagingPath}/google-auth-library.tgz`); + await ncpp('system-test/fixtures/kitchen', `${stagingPath}/`); + await execa('npm', ['install'], {cwd: `${stagingPath}/`, stdio: 'inherit'}); + }).timeout(40000); -/** - * CLEAN UP - remove the staging directory when done. - */ -after('cleanup staging', () => { - if (!keep) { - stagingDir.removeCallback(); - } + /** + * CLEAN UP - remove the staging directory when done. + */ + after('cleanup staging', () => { + if (!keep) { + stagingDir.removeCallback(); + } + }); }); From f4fca3d0bd1c25bfc387b13ca1933e0823b19873 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Sun, 23 Dec 2018 11:29:47 -0800 Subject: [PATCH 2/2] move timeout to package.json --- package.json | 2 +- system-test/test.kitchen.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6a9c39c9..23ec37d2 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "docs": "compodoc src/ && touch docs/.nojekyll", "publish-docs": "gh-pages -d docs --remote upstream && git push upstream gh-pages", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", - "system-test": "mocha build/system-test", + "system-test": "mocha build/system-test --timeout 60000", "presystem-test": "npm run compile" }, "license": "Apache-2.0" diff --git a/system-test/test.kitchen.ts b/system-test/test.kitchen.ts index 31eadc4a..ba2889df 100644 --- a/system-test/test.kitchen.ts +++ b/system-test/test.kitchen.ts @@ -41,7 +41,7 @@ describe('pack and install', () => { await mvp(tarball, `${stagingPath}/google-auth-library.tgz`); await ncpp('system-test/fixtures/kitchen', `${stagingPath}/`); await execa('npm', ['install'], {cwd: `${stagingPath}/`, stdio: 'inherit'}); - }).timeout(40000); + }); /** * CLEAN UP - remove the staging directory when done.