From fa4b777dbccf4e166fc5587943c62727b1222b5a Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Wed, 25 Apr 2018 11:27:24 -0700 Subject: [PATCH] chore: switch from google-auto-auth to google-auth-library (#59) --- package.json | 5 +- src/index.ts | 147 ++++++++++++------------- test/fixtures/keys.json | 12 +++ test/test.ts | 232 ++++++++++++++++++---------------------- 4 files changed, 186 insertions(+), 210 deletions(-) create mode 100644 test/fixtures/keys.json diff --git a/package.json b/package.json index 2e3867b..1c2f9cb 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "gcs-upload": "build/src/cli.js" }, "scripts": { - "test": "mocha build/test -r source-map-support/register", + "test": "mocha build/test -r source-map-support/register --timeout 4000", "check": "gts check", "clean": "gts clean", "compile": "tsc -p .", @@ -33,8 +33,9 @@ "author": "Stephen Sawchuk ", "license": "MIT", "dependencies": { + "axios": "^0.18.0", "configstore": "^3.1.2", - "google-auto-auth": "^0.10.0", + "google-auth-library": "^1.4.0", "pumpify": "^1.4.0", "request": "^2.85.0", "stream-events": "^1.0.3" diff --git a/src/index.ts b/src/index.ts index ef30acf..88584ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,19 @@ +import {AxiosError, AxiosRequestConfig, AxiosResponse} from 'axios'; import * as ConfigStore from 'configstore'; import * as crypto from 'crypto'; +import {GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; +import * as Pumpify from 'pumpify'; import * as r from 'request'; import {Duplex, PassThrough, Readable, Stream} from 'stream'; import * as streamEvents from 'stream-events'; -const googleAuth = require('google-auto-auth'); -import * as Pumpify from 'pumpify'; - // tslint:disable-next-line no-any export type RequestBody = any; -export type RequestResponse = r.Response; +export type RequestResponse = AxiosResponse; export type Request = r.Request; -export type RequestOptions = r.OptionsWithUri; +export type RequestOptions = AxiosRequestConfig; export type RequestCallback = - (err: Error|null, response?: r.Response, body?: RequestBody) => void; + (err: Error|null, response?: AxiosResponse, body?: RequestBody) => void; export type AuthorizeRequestCallback = (err: Error|null, authorizedReqOpts: RequestOptions) => void; @@ -30,13 +30,6 @@ const wrapError = (message: string, err: Error) => { export type CreateUriCallback = (err: Error|null, uri?: string) => void; -export interface AuthClient { - authorizeRequest: - (reqOpts: r.OptionsWithUri, - callback: (err: Error|null, authorizedReqOpts: RequestOptions) => - void) => void; -} - export interface Encryption { key: {}; hash: {}; @@ -52,12 +45,12 @@ export interface UploadConfig { * The name of the destination file. */ file: string; - authConfig?: {scopes?: string[];}; + authConfig?: GoogleAuthOptions; /** * If you want to re-use an auth client from google-auto-auth, pass an * instance here. */ - authClient?: AuthClient; + authClient?: GoogleAuth; /** * This will cause the upload to fail if the current generation of the remote @@ -143,7 +136,7 @@ export class Upload extends Pumpify { bucket: string; file: string; authConfig?: {scopes?: string[];}; - authClient: AuthClient; + authClient: GoogleAuth; generation?: number; key?: string|Buffer; kmsKeyName?: string; @@ -178,7 +171,7 @@ export class Upload extends Pumpify { cfg.authConfig = cfg.authConfig || {}; cfg.authConfig.scopes = ['https://www.googleapis.com/auth/devstorage.full_control']; - this.authClient = cfg.authClient || googleAuth(cfg.authConfig); + this.authClient = cfg.authClient || new GoogleAuth(cfg.authConfig); this.bucket = cfg.bucket; this.file = cfg.file; @@ -235,9 +228,9 @@ export class Upload extends Pumpify { const reqOpts: RequestOptions = { method: 'POST', - uri: [BASE_URI, this.bucket, 'o'].join('/'), - qs: {name: this.file, uploadType: 'resumable'}, - json: metadata, + url: [BASE_URI, this.bucket, 'o'].join('/'), + params: {name: this.file, uploadType: 'resumable'}, + data: metadata, headers: {} }; @@ -250,15 +243,15 @@ export class Upload extends Pumpify { } if (typeof this.generation !== 'undefined') { - reqOpts.qs.ifGenerationMatch = this.generation; + reqOpts.params.ifGenerationMatch = this.generation; } if (this.kmsKeyName) { - reqOpts.qs.kmsKeyName = this.kmsKeyName; + reqOpts.params.kmsKeyName = this.kmsKeyName; } if (this.predefinedAcl) { - reqOpts.qs.predefinedAcl = this.predefinedAcl; + reqOpts.params.predefinedAcl = this.predefinedAcl; } if (this.origin) { @@ -287,11 +280,11 @@ export class Upload extends Pumpify { private startUploading() { const reqOpts = { method: 'PUT', - uri: this.uri, + url: this.uri, headers: { 'Content-Range': 'bytes ' + this.offset + '-*/' + this.contentLength } - } as r.OptionsWithUri; + }; const bufferStream = this.bufferStream = new PassThrough(); const offsetStream = this.offsetStream = @@ -363,16 +356,16 @@ export class Upload extends Pumpify { private getAndSetOffset(callback: () => void) { const opts = { method: 'PUT', - uri: this.uri, + url: this.uri, headers: {'Content-Length': 0, 'Content-Range': 'bytes */*'} - } as r.OptionsWithUri; + }; this.makeRequest(opts, (err, resp) => { if (err) { // we don't return a 404 to the user if they provided the resumable // URI. if we're just using the configstore file to tell us that this // file exists, and it turns out that it doesn't (the 404), that's // probably stale config data. - if (resp && resp.statusCode === 404 && !this.uriProvidedManually) { + if (resp && resp.status === 404 && !this.uriProvidedManually) { return this.restart(); } @@ -381,14 +374,14 @@ export class Upload extends Pumpify { // https://github.com/stephenplusplus/gcs-resumable-upload/issues/15 // - // https://github.com/stephenplusplus/gcs-resumable-upload/pull/16#discussion_r80363774 - if (resp && resp.statusCode === TERMINATED_UPLOAD_STATUS_CODE) { + if (resp && resp.status === TERMINATED_UPLOAD_STATUS_CODE) { return this.restart(); } return this.destroy(err); } - if (resp && resp.statusCode === RESUMABLE_INCOMPLETE_STATUS_CODE) { + if (resp && resp.status === RESUMABLE_INCOMPLETE_STATUS_CODE) { if (resp.headers.range) { const range = resp.headers.range as string; this.offset = Number(range.split('-')[1]) + 1; @@ -411,62 +404,52 @@ export class Upload extends Pumpify { } if (this.userProject) { - reqOpts.qs = reqOpts.qs || {}; - reqOpts.qs.userProject = this.userProject; + reqOpts.params = reqOpts.params || {}; + reqOpts.params.userProject = this.userProject; } - this.authClient.authorizeRequest(reqOpts, (err, authorizedReqOpts) => { - if (err) { - err = wrapError('Could not authenticate request', err); - return callback(err, null!, null); - } - - request(authorizedReqOpts, (err, resp, body) => { - if (err) { - return callback(err, resp, body); - } - - if (body && body.error) { - return callback(body.error, resp, body); - } - - const nonSuccess = - Math.floor(resp.statusCode / 100) !== 2; // 200-299 status code - if (nonSuccess && - resp.statusCode !== RESUMABLE_INCOMPLETE_STATUS_CODE) { - return callback(new Error(body), resp, body); - } + reqOpts.validateStatus = (status: number) => { + return (status >= 200 && status < 300) || + status === RESUMABLE_INCOMPLETE_STATUS_CODE; + }; - callback(null, resp, body); - }); - }); + this.authClient.request(reqOpts).then( + r => { + return callback(null, r, r.data); + }, + (err: AxiosError) => { + const body = err.response ? err.response.data : undefined; + const e = (body && body.error) ? body.error : err; + return callback(e, err.response, body); + }); } private getRequestStream( reqOpts: RequestOptions, callback: (requestStream: Request) => void) { if (this.userProject) { - reqOpts.qs = reqOpts.qs || {}; - reqOpts.qs.userProject = this.userProject; + reqOpts.params = reqOpts.params || {}; + reqOpts.params.userProject = this.userProject; } - this.authClient.authorizeRequest(reqOpts, (err, authorizedReqOpts) => { - if (err) { - return this.destroy(wrapError('Could not authenticate request', err)); - } - - const requestStream = request(authorizedReqOpts); - requestStream.on('error', this.destroy.bind(this)); - requestStream.on('response', this.onResponse.bind(this)); - requestStream.on('complete', (resp) => { - const body = resp.body; - if (body && body.error) this.destroy(body.error); - }); - - // this makes the response body come back in the response (weird?) - requestStream.callback = () => {}; - - callback(requestStream); - }); + this.authClient.authorizeRequest(reqOpts) + .then(opts => { + const authorizedReqOpts = axiosToRequest(reqOpts); + const requestStream = request(authorizedReqOpts); + requestStream.on('error', this.destroy.bind(this)); + requestStream.on('response', this.onResponse.bind(this)); + requestStream.on('complete', (resp) => { + const body = resp.body; + if (body && body.error) this.destroy(body.error); + }); + + // this makes the response body come back in the response (weird?) + requestStream.callback = () => {}; + + callback(requestStream); + }) + .catch(err => { + return this.destroy(wrapError('Could not authenticate request', err)); + }); } private restart() { @@ -498,7 +481,7 @@ export class Upload extends Pumpify { * @return {bool} is the request good? */ private onResponse(resp: RequestResponse) { - if (resp.statusCode === 404) { + if (resp.status === 404) { if (this.numRetries < RETRY_LIMIT) { this.numRetries++; this.startUploading(); @@ -508,7 +491,7 @@ export class Upload extends Pumpify { return false; } - if (resp.statusCode > 499 && resp.statusCode < 600) { + if (resp.status > 499 && resp.status < 600) { if (this.numRetries < RETRY_LIMIT) { const randomMs = Math.round(Math.random() * 1000); const waitTime = Math.pow(2, this.numRetries) * 1000 + randomMs; @@ -527,6 +510,14 @@ export class Upload extends Pumpify { } } +function axiosToRequest(opts: AxiosRequestConfig): r.OptionsWithUri { + const reqOpts = opts as r.OptionsWithUri; + reqOpts.qs = opts.params; + reqOpts.json = opts.data; + reqOpts.uri = opts.url as string; + return reqOpts; +} + export function upload(cfg: UploadConfig) { return new Upload(cfg); } diff --git a/test/fixtures/keys.json b/test/fixtures/keys.json new file mode 100644 index 0000000..073edb7 --- /dev/null +++ b/test/fixtures/keys.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "project-id", + "private_key_id": "12345", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5z21IhrvlHBj7\nifRhobA9ibn25Od7DpE5OauGmqy7B+A9LQOsk1ZujAFdHItnBPcjihSVHpiYxf1a\nLpFbM8z/hRvDvYS3Hs1pyRejmpGiznoOjCyUf6Wv3T1xKelbgn0twHHjqD1o0xzW\njyUILl7yuCbsAf8QlsV6ewS3IqO3i5A9RNHfKjeap8e6A7U3s9QBtR58RrxaMQpM\nz72gw7yOdJRfElkerQfyZTbtu/EBfE6CcskOyoMoRN3YgkjQqLMr1yVdL5/phEcQ\n5hpbDN5lrafGHN7FUtsrMge2iIuIYFfWQUTqu7HtnNXVmwj1LJNq5WeI1iInWaGz\nb7c1rUT9AgMBAAECggEAEB0FicqVX3L7qk9LsBkeItgKFnfB/eaaKsTuM7K/fqCv\njjPpzlIgprQ20g+i+dYbuytC9Fjo5tFp/SNuBji3Ha7kuil56Yoe9NOJh0M6P0zP\nQj+0W1Rj1p0bB5tDhLoLh6eEDjgNde+zioUeCFhCck4MogmHnbVVfspNnba/99oD\nl36heAioqj/KODdkbQ83+ByiH+3BzqblqJ4TR/3y0wOUXtlQvCHko1qximJFIM0z\n3TNoPiit74hTiFFOYfJyHpmRsiEJ5FUUImkmCJz2gk4fbpafKrgxxOMo1m7GqlsE\nE+ybHxyAq61HYbZOoUOO8B4md1/52QXP7DgPvV7JyQKBgQD+JS5nsR4TXRl61c9G\nNxoPW9yCMCoarIjkpyPmhh0uJ7y68cj9wHFgX6ATi1QuTnG9BzJ4z27PMgvv70N+\nAK6k74sdIT2ts8wYsD8H0UyuxDxeKiAnb2JW2f5GTcXNmELQi6rKkMNMoS8jv00d\ngzLCV7UbCbdf+ng9uRPs+Fvk9wKBgQC7KpNaeYFf5dmIYRWQhlZWBRoftdm1ROH/\n5GJsURkzlEjUH1g1y9eAigBn5I+Z9hylX2q1vHLpUHqONWwDz8oQ1L1o2iLz+tkp\nkNoaLSAb9uCl6t8tpqCG2dqUrxOmy1+xj3G8KI8XuYb+IwVSy6KK2df8fWN4d+i0\ng+TBb75MqwKBgEezwcXriKq554hqblJHFYkjx7DLWfWwm+a26UAOsojlGTA9KxG8\ni8A++nDJLHTsGNbWAv1muMKoQgntnUMdeih6lOshB7/MLFcC0qWn/VSJdOa0R+IY\nYMxUMJMxOg9pV+BypzsDYLZr+1rAjEc5TsbZ6/S25w+jIO15HBANeg+9AoGAZulz\nGkVDCLq2UJGpLM1gvW2Svqrb6RrV9UDbiVlSNRUssk4Fz5akiM3YiUeYWfyEJb4A\nS6sxt+4DZRwkpzfikDyZZQTEQUjFjWBTPB9hz16AiVpKmqxLCbrRv/1AHe8nT9di\nnyXiABaIDkatT6geWKCNbQx43C16a382EdJiXX8CgYEAqyAS2xuDi2+uoljRm1Bp\naz7Q2UBtBbcBr/CQmagEacWPXsSyCL6EySOH0e985k7ABZiW+AzWlOwKS5WMWAIb\nkncmxP0SU6WQDWl8xGbXAQ8Dw+HTu5G1n0vrl1rRO5FPwRs3pbV94ML+d5eoai6D\njHs1asOGIpdQ3OGpBpNRub0=\n-----END PRIVATE KEY-----\n", + "client_email": "some-email@example.com", + "client_id": "12345", + "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": "https://www.googleapis.com/robot/v1/metadata/x509/el-gato%40el-gato.iam.gserviceaccount.com" +} diff --git a/test/test.ts b/test/test.ts index a3fb148..50d8c3e 100644 --- a/test/test.ts +++ b/test/test.ts @@ -27,14 +27,14 @@ function ConfigStore() { }; } -// tslint:disable-next-line no-any -function mockAuthorizeRequest(up: any) { - up.authClient = { - authorizeRequest( - reqOpts: RequestOptions, callback: AuthorizeRequestCallback) { - callback(null, reqOpts); - } - }; +const queryPath = '/?userProject=user-project-id'; + +function mockAuthorizeRequest(code = 200, data: {}|string = { + access_token: 'abc123' +}) { + return nock('https://www.googleapis.com') + .post('/oauth2/v4/token') + .reply(code, data); } describe('gcs-resumable-upload', () => { @@ -50,7 +50,8 @@ describe('gcs-resumable-upload', () => { const ORIGIN = '*'; const PREDEFINED_ACL = 'authenticatedRead'; const USER_PROJECT = 'user-project-id'; - const REQ_OPTS = {uri: 'http://uri'}; + const REQ_OPTS = {url: 'http://fake.local'}; + const keyFile = path.join(__dirname, '../../test/fixtures/keys.json'); before(() => { mockery.registerMock('configstore', ConfigStore); @@ -67,7 +68,8 @@ describe('gcs-resumable-upload', () => { metadata: METADATA, origin: ORIGIN, predefinedAcl: PREDEFINED_ACL, - userProject: USER_PROJECT + userProject: USER_PROJECT, + authConfig: {keyFile} }); }); @@ -217,15 +219,15 @@ describe('gcs-resumable-upload', () => { up.makeRequest = (reqOpts: RequestOptions) => { assert.strictEqual(reqOpts.method, 'POST'); assert.strictEqual( - reqOpts.uri, + reqOpts.url, `https://www.googleapis.com/upload/storage/v1/b/${BUCKET}/o`); - assert.deepEqual(reqOpts.qs, { + assert.deepEqual(reqOpts.params, { predefinedAcl: up.predefinedAcl, name: FILE, uploadType: 'resumable', ifGenerationMatch: GENERATION }); - assert.strictEqual(reqOpts.json, up.metadata); + assert.strictEqual(reqOpts.data, up.metadata); done(); }; @@ -237,7 +239,7 @@ describe('gcs-resumable-upload', () => { const up = upload({bucket: BUCKET, file: FILE, kmsKeyName}); up.makeRequest = (reqOpts: RequestOptions) => { - assert.strictEqual(reqOpts.qs.kmsKeyName, kmsKeyName); + assert.strictEqual(reqOpts.params.kmsKeyName, kmsKeyName); done(); }; @@ -246,7 +248,7 @@ describe('gcs-resumable-upload', () => { it('should respect 0 as a generation', (done) => { up.makeRequest = (reqOpts: RequestOptions) => { - assert.strictEqual(reqOpts.qs.ifGenerationMatch, 0); + assert.strictEqual(reqOpts.params.ifGenerationMatch, 0); done(); }; up.generation = 0; @@ -363,7 +365,7 @@ describe('gcs-resumable-upload', () => { up.getRequestStream = (reqOpts: RequestOptions) => { assert.strictEqual(reqOpts.method, 'PUT'); - assert.strictEqual(reqOpts.uri, up.uri); + assert.strictEqual(reqOpts.url, up.uri); assert.deepEqual( reqOpts.headers, {'Content-Range': 'bytes ' + OFFSET + '-*/' + up.contentLength}); @@ -609,14 +611,15 @@ describe('gcs-resumable-upload', () => { const RANGE = 123456; // tslint:disable-next-line no-any - const RESP: any = {statusCode: 308, headers: {range: `range-${RANGE}`}}; + const RESP = {status: 308, headers: {range: `range-${RANGE}`}} as + RequestResponse; it('should make the correct request', (done) => { const URI = 'uri'; up.uri = URI; up.makeRequest = (reqOpts: RequestOptions) => { assert.strictEqual(reqOpts.method, 'PUT'); - assert.strictEqual(reqOpts.uri, URI); + assert.strictEqual(reqOpts.url, URI); assert.deepEqual( reqOpts.headers, {'Content-Length': 0, 'Content-Range': 'bytes */*'}); @@ -629,7 +632,7 @@ describe('gcs-resumable-upload', () => { describe('restart on 404', () => { const ERROR = new Error(':('); - const RESP = {statusCode: 404} as RequestResponse; + const RESP = {status: 404} as RequestResponse; beforeEach(() => { up.makeRequest = @@ -656,7 +659,7 @@ describe('gcs-resumable-upload', () => { describe('restart on 410', () => { const ERROR = new Error(':('); - const RESP = {statusCode: 410} as RequestResponse; + const RESP = {status: 410} as RequestResponse; beforeEach(() => { up.makeRequest = @@ -699,92 +702,67 @@ describe('gcs-resumable-upload', () => { describe('#makeRequest', () => { it('should set encryption headers', (done) => { const key = crypto.randomBytes(32); - const up = upload({bucket: 'BUCKET', file: FILE, key}); - - up.authClient = { - authorizeRequest(reqOpts: RequestOptions) { - assert.deepEqual(reqOpts.headers, { - 'x-goog-encryption-algorithm': 'AES256', - 'x-goog-encryption-key': up.encryption.key, - 'x-goog-encryption-key-sha256': up.encryption.hash - }); - done(); - } - }; - - up.makeRequest(REQ_OPTS); + const up = + upload({bucket: 'BUCKET', file: FILE, key, authConfig: {keyFile}}); + const scopes = + [mockAuthorizeRequest(), nock(REQ_OPTS.url).get('/').reply(200, {})]; + up.makeRequest(REQ_OPTS, (err: Error|null, res: RequestResponse) => { + assert.ifError(err); + scopes.forEach(x => x.done()); + const headers = res.config.headers; + assert.equal(headers['x-goog-encryption-algorithm'], 'AES256'); + assert.equal(headers['x-goog-encryption-key'], up.encryption.key); + assert.equal( + headers['x-goog-encryption-key-sha256'], up.encryption.hash); + done(); + }); }); it('should set userProject', (done) => { - up.authClient = { - authorizeRequest(reqOpts: RequestOptions) { - assert.deepEqual(reqOpts.qs, {userProject: USER_PROJECT}); - done(); - } - }; - - up.makeRequest(REQ_OPTS); - }); - - it('should authorize the request', (done) => { - up.authClient = { - authorizeRequest(reqOpts: RequestOptions) { - assert.strictEqual(reqOpts, REQ_OPTS); - done(); - } - }; - - up.makeRequest(REQ_OPTS); + const scopes = [ + mockAuthorizeRequest(), nock(REQ_OPTS.url).get(queryPath).reply(200, {}) + ]; + up.makeRequest(REQ_OPTS, (err: Error|null, res: RequestResponse) => { + assert.ifError(err); + assert.deepEqual(res.config.params, {userProject: USER_PROJECT}); + scopes.forEach(x => x.done()); + done(); + }); }); it('should execute the callback with error & response if one occurred', (done) => { - const error = new Error(':('); const response = {} as RequestResponse; - - up.authClient = { - authorizeRequest( - reqOpts: RequestOptions, callback: RequestCallback) { - callback(error, response, null); - } - }; - + const scope = mockAuthorizeRequest(500, ':('); up.makeRequest({}, (err: Error, resp: RequestResponse) => { - assert(err.message.indexOf(error.message) > -1); + assert.equal(err.message, 'Request failed with status code 500'); done(); }); }); it('should make the correct request', (done) => { - const uri = 'http://uri/'; const headers = {}; - const authorizedReqOpts = {uri, headers} as RequestOptions; - - up.authClient = { - authorizeRequest(reqOpts: RequestOptions, callback: Function) { - callback(null, authorizedReqOpts); - } - }; - - const scope = nock(uri).get('/').reply(200, undefined, headers); + const scopes = [ + mockAuthorizeRequest(), + nock(REQ_OPTS.url).get(queryPath).reply(200, undefined, headers) + ]; up.makeRequest(REQ_OPTS, (err: Error|null, res: RequestResponse) => { - const opts = res.request; - scope.done(); - assert.strictEqual(opts.uri.href, authorizedReqOpts.uri); - assert.deepStrictEqual(authorizedReqOpts.headers, {}); - assert.deepStrictEqual(opts.headers, {accept: 'application/json'}); + assert.ifError(err); + scopes.forEach(x => x.done()); + assert.strictEqual(res.config.url, REQ_OPTS.url); + assert.deepStrictEqual(res.headers, {}); done(); }); }); it('should execute the callback with error & response', (done) => { - const response = {body: 'wooo'} as RequestResponse; - mockAuthorizeRequest(up); - const scope = nock(REQ_OPTS.uri) + const response = {data: 'wooo'} as RequestResponse; + mockAuthorizeRequest(); + const scope = nock(REQ_OPTS.url) .get('/?userProject=user-project-id') - .reply(500, response.body); + .reply(500, response.data); up.makeRequest(REQ_OPTS, (err: Error, resp: RequestResponse) => { - assert.strictEqual(resp.body, response.body); + assert.strictEqual(resp.data, response.data); scope.done(); done(); }); @@ -792,29 +770,29 @@ describe('gcs-resumable-upload', () => { it('should execute the callback with a body error & response', (done) => { const response = {error: ':('}; - mockAuthorizeRequest(up); - const scope = nock(REQ_OPTS.uri) + mockAuthorizeRequest(); + const scope = nock(REQ_OPTS.url) .get('/?userProject=user-project-id') .reply(500, response); up.makeRequest(REQ_OPTS, (err: Error, res: RequestResponse) => { - assert.equal(res.statusCode, 500); - assert.deepStrictEqual(response, res.body); + assert.equal(res.status, 500); + assert.deepStrictEqual(response, res.data); done(); }); }); it('should execute the callback with a body error & response for non-2xx status codes', (done) => { - const response = {statusCode: 500, body: {error: '!$#@'}}; - mockAuthorizeRequest(up); - const scope = nock(REQ_OPTS.uri) + const response = {status: 500, body: {error: '!$#@'}}; + mockAuthorizeRequest(); + const scope = nock(REQ_OPTS.url) .get('/?userProject=user-project-id') .reply(500, response.body); up.makeRequest( REQ_OPTS, (err: Error, resp: RequestResponse, body: RequestBody) => { assert.strictEqual(err, response.body.error); - assert.deepStrictEqual(resp.statusCode, 500); + assert.deepStrictEqual(resp.status, 500); assert.deepStrictEqual(body, response.body); done(); }); @@ -822,18 +800,18 @@ describe('gcs-resumable-upload', () => { it('should execute the callback', (done) => { const data = {red: 'tape'}; - mockAuthorizeRequest(up); + mockAuthorizeRequest(); up.onResponse = () => { return true; }; - const scope = nock(REQ_OPTS.uri) + const scope = nock(REQ_OPTS.url) .get('/?userProject=user-project-id') .reply(200, data); up.makeRequest( REQ_OPTS, (err: Error, res: RequestResponse, body: RequestBody) => { assert.ifError(err); scope.done(); - assert.strictEqual(res.statusCode, 200); + assert.strictEqual(res.status, 200); assert.deepStrictEqual(body, data); done(); }); @@ -854,7 +832,7 @@ describe('gcs-resumable-upload', () => { it('should set userProject', (done) => { up.authClient = { authorizeRequest(reqOpts: RequestOptions) { - assert.deepEqual(reqOpts.qs, {userProject: USER_PROJECT}); + assert.deepEqual(reqOpts.params, {userProject: USER_PROJECT}); done(); } }; @@ -862,23 +840,19 @@ describe('gcs-resumable-upload', () => { }); it('should destroy the stream if an error occurred', (done) => { - const error = new Error(':('); up.destroy = (err: Error) => { - assert(err.message.indexOf(error.message) > -1); + assert.equal( + err.message, + 'Could not authenticate request\nRequest failed with status code 500'); done(); }; - up.authClient = { - authorizeRequest( - reqOpts: RequestOptions, callback: AuthorizeRequestCallback) { - callback(error, null!); - } - }; + const scope = mockAuthorizeRequest(500); up.getRequestStream(REQ_OPTS); }); it('should make the correct request', (done) => { - mockAuthorizeRequest(up); - const scope = nock(REQ_OPTS.uri) + mockAuthorizeRequest(); + const scope = nock(REQ_OPTS.url) .get('/?userProject=user-project-id') .replyWithFile(200, dawPath); up.getRequestStream(REQ_OPTS, (requestStream: stream.Readable) => { @@ -889,9 +863,9 @@ describe('gcs-resumable-upload', () => { }); it('should set the callback to a noop', (done) => { - mockAuthorizeRequest(up); + mockAuthorizeRequest(); const scope = - nock(REQ_OPTS.uri).get('/?userProject=user-project-id').reply(200); + nock(REQ_OPTS.url).get('/?userProject=user-project-id').reply(200); up.getRequestStream( REQ_OPTS, (requestStream: stream.Readable&{callback: Function}) => { assert.strictEqual( @@ -901,9 +875,9 @@ describe('gcs-resumable-upload', () => { }); it('should destroy the stream if there was an error', (done) => { - mockAuthorizeRequest(up); + mockAuthorizeRequest(); const scope = - nock(REQ_OPTS.uri).get('/?userProject=user-project-id').reply(200); + nock(REQ_OPTS.url).get('/?userProject=user-project-id').reply(200); up.getRequestStream(REQ_OPTS, (requestStream: stream.Readable) => { const error = new Error(':('); up.on('error', (err: Error) => { @@ -915,9 +889,9 @@ describe('gcs-resumable-upload', () => { }); it('should destroy the stream if there was a body error', (done) => { - mockAuthorizeRequest(up); + mockAuthorizeRequest(); const scope = - nock(REQ_OPTS.uri).get('/?userProject=user-project-id').reply(200); + nock(REQ_OPTS.url).get('/?userProject=user-project-id').reply(200); up.getRequestStream(REQ_OPTS, (requestStream: stream.Readable) => { const response = {body: {error: new Error(':(')}}; up.on('error', (err: Error) => { @@ -930,17 +904,10 @@ describe('gcs-resumable-upload', () => { it('should check if it should retry on response', (done) => { let fired = false; - up.authClient = { - authorizeRequest( - reqOpts: RequestOptions, callback: AuthorizeRequestCallback) { - callback(null, reqOpts); - } - }; - - const res = {statusCode: 200}; - const scope = - nock(REQ_OPTS.uri).get('/?userProject=user-project-id').reply(200); - + const res = {status: 200}; + const scopes = [ + mockAuthorizeRequest(), nock(REQ_OPTS.url).get(queryPath).reply(200) + ]; up.onResponse = function(resp: RequestResponse) { if (!fired) { assert.strictEqual(this, up); @@ -956,13 +923,18 @@ describe('gcs-resumable-upload', () => { }); it('should execute the callback with the stream', (done) => { - const scope = nock(REQ_OPTS.uri) - .get('/?userProject=user-project-id') - .replyWithFile(200, dawPath); - mockAuthorizeRequest(up); + const scopes = [ + mockAuthorizeRequest(), + nock(REQ_OPTS.url) + .get('/?userProject=user-project-id') + .replyWithFile(200, dawPath) + ]; up.getRequestStream(REQ_OPTS, (reqStream: stream.Readable) => { assert(reqStream); - done(); + reqStream.on('complete', (res: RequestResponse) => { + scopes.forEach(x => x.done()); + done(); + }); }); }); }); @@ -1079,7 +1051,7 @@ describe('gcs-resumable-upload', () => { }); describe('404', () => { - const RESP = {statusCode: 404}; + const RESP = {status: 404}; it('should increase the retry count if less than limit', () => { assert.strictEqual(up.numRetries, 0); @@ -1108,7 +1080,7 @@ describe('gcs-resumable-upload', () => { }); describe('500s', () => { - const RESP = {statusCode: 500}; + const RESP = {status: 500}; it('should increase the retry count if less than limit', () => { assert.strictEqual(up.numRetries, 0); @@ -1160,7 +1132,7 @@ describe('gcs-resumable-upload', () => { }); describe('all others', () => { - const RESP = {statusCode: 200}; + const RESP = {status: 200}; it('should emit the response on the stream', (done) => { up.on('response', (resp: RequestResponse) => {