diff --git a/core/packages/gax/src/apitypes.ts b/core/packages/gax/src/apitypes.ts index 6004cc63a640..a7141cf1d1cc 100644 --- a/core/packages/gax/src/apitypes.ts +++ b/core/packages/gax/src/apitypes.ts @@ -35,10 +35,7 @@ export interface GRPCCallResult { // when it might be useful for users. export interface RequestType { [index: string]: - | string - | number - | RequestType - | Array; + string | number | RequestType | Array; } export type ResponseType = {} | null; export type NextPageRequestType = { @@ -85,10 +82,7 @@ export type BiDiStreamingCall = ( options: {}, ) => Duplex & GRPCCallResult; export type GRPCCall = - | UnaryCall - | ServerStreamingCall - | ClientStreamingCall - | BiDiStreamingCall; + UnaryCall | ServerStreamingCall | ClientStreamingCall | BiDiStreamingCall; // GAX wraps gRPC calls so that the wrapper functions return either a // cancellable promise, or a stream (also cancellable!) diff --git a/core/packages/gax/src/fallbackServiceStub.ts b/core/packages/gax/src/fallbackServiceStub.ts index 119e3b3aab4e..da99f213b176 100644 --- a/core/packages/gax/src/fallbackServiceStub.ts +++ b/core/packages/gax/src/fallbackServiceStub.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import type {Response as NodeFetchResponse} from 'node-fetch' with {'resolution-mode': 'import'}; +import type {Response as NodeFetchResponse} from 'node-fetch' with { + 'resolution-mode': 'import', +}; import {AuthClient, GoogleAuth, gaxios} from 'google-auth-library'; import * as serializer from 'proto3-json-serializer'; @@ -33,8 +35,7 @@ import type {Agent as HttpsAgent} from 'https'; // - https://github.com/node-fetch/node-fetch#custom-agent // - https://github.com/googleapis/gax-nodejs/pull/1534 let agentOption: - | ((parsedUrl: {protocol: string}) => HttpAgent | HttpsAgent) - | null = null; + ((parsedUrl: {protocol: string}) => HttpAgent | HttpsAgent) | null = null; if (isNodeJS()) { const http = require('http'); const https = require('https'); diff --git a/core/packages/gax/src/paginationCalls/pagedApiCaller.ts b/core/packages/gax/src/paginationCalls/pagedApiCaller.ts index c75f14bc1971..0cb2bf5efa19 100644 --- a/core/packages/gax/src/paginationCalls/pagedApiCaller.ts +++ b/core/packages/gax/src/paginationCalls/pagedApiCaller.ts @@ -21,8 +21,8 @@ import { SimpleCallbackFunction, UnaryCall, RequestType, + APICallback, } from '../apitypes'; -import {APICallback} from '../apitypes'; import {OngoingCall, OngoingCallPromise} from '../call'; import {CallOptions} from '../gax'; import {GoogleError} from '../googleError'; diff --git a/core/packages/gax/src/streamingCalls/streaming.ts b/core/packages/gax/src/streamingCalls/streaming.ts index 81f6566049a4..d223d0e80b14 100644 --- a/core/packages/gax/src/streamingCalls/streaming.ts +++ b/core/packages/gax/src/streamingCalls/streaming.ts @@ -16,7 +16,14 @@ /* This file describes the gRPC-streaming. */ -import {Duplex, DuplexOptions, Readable, Stream, Writable} from 'stream'; +import { + Duplex, + DuplexOptions, + Readable, + Stream, + Writable, + PassThrough, +} from 'stream'; import { APICallback, @@ -24,6 +31,7 @@ import { GRPCCallResult, RequestType, SimpleCallbackFunction, + ResponseType, } from '../apitypes'; import { RetryOptions, @@ -32,8 +40,6 @@ import { } from '../gax'; import {GoogleError} from '../googleError'; import {Status} from '../status'; -import {PassThrough} from 'stream'; -import {ResponseType} from '../apitypes'; // eslint-disable-next-line @typescript-eslint/no-var-requires const duplexify: DuplexifyConstructor = require('duplexify'); // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/core/packages/gax/src/transcoding.ts b/core/packages/gax/src/transcoding.ts index 070612ec117c..f855c8875ac8 100644 --- a/core/packages/gax/src/transcoding.ts +++ b/core/packages/gax/src/transcoding.ts @@ -137,7 +137,9 @@ function validateUriPath(propertyName: string, value: string): void { // valid domain-scoped resource segments (e.g. projects/example.com:project-id). const segments = value.split('/'); if (segments.some(segment => segment === '.' || segment === '..')) { - throw new Error(`Value for ${propertyName} must not contain segments that are exactly . or ..`); + throw new Error( + `Value for ${propertyName} must not contain segments that are exactly . or ..`, + ); } } } @@ -164,7 +166,9 @@ export function buildQueryStringComponents( } else { resultList.push( `${prefix}${encodeWithoutSlashes(key)}=${encodeWithoutSlashes( - requestValue === null || requestValue === undefined ? 'null' : requestValue.toString(), + requestValue === null || requestValue === undefined + ? 'null' + : requestValue.toString(), )}`, ); } @@ -187,7 +191,7 @@ export function buildQueryStringComponents( export function encodeWithSlashes(str: string): string { return encodeURIComponent(str).replace( /[!'()*]/g, // Characters preserved by encodeURIComponent - character => '%' + character.charCodeAt(0).toString(16).toUpperCase() + character => '%' + character.charCodeAt(0).toString(16).toUpperCase(), ); } diff --git a/core/packages/gax/test/unit/apiCallable.ts b/core/packages/gax/test/unit/apiCallable.ts index f41143b3f7ce..e2ec502d0f64 100644 --- a/core/packages/gax/test/unit/apiCallable.ts +++ b/core/packages/gax/test/unit/apiCallable.ts @@ -1106,8 +1106,8 @@ describe('createApiCall', () => { }); describe('Promise', () => { - it('calls api call', done => { - let deadlineArg: string; + it('calls api call', async () => { + let deadlineArg: string | undefined = undefined; function func( argument: {}, metadata: {}, @@ -1119,14 +1119,10 @@ describe('Promise', () => { } const apiCall = createApiCall(func); // eslint-disable-next-line @typescript-eslint/no-explicit-any - (apiCall as any)(null) - .then((response: number[]) => { - assert.ok(Array.isArray(response)); - assert.strictEqual(response[0], 42); - assert.ok(deadlineArg); - return done(); - }) - .catch(done); + const response = (await (apiCall as any)(null)) as number[]; + assert.ok(Array.isArray(response)); + assert.strictEqual(response[0], 42); + assert.ok(deadlineArg); }); it('emits error on rejected promise', async () => { @@ -1143,28 +1139,30 @@ describe('Promise', () => { await assert.rejects(apiCall({}, undefined)); }); - it('has cancel method', done => { + it('has cancel method', async () => { function func(argument: {}, metadata: {}, options: {}, callback: Function) { setTimeout(() => { callback(null, 42); }, 0); } - const apiCall = createApiCall(func, {cancel: done}); + const apiCall = createApiCall(func); // eslint-disable-next-line @typescript-eslint/no-explicit-any const promise = (apiCall as any)(null); - promise - .then(() => { - return done(new Error('should not reach')); - }) - .catch((err: {code: number}) => { + assert.strictEqual(typeof promise.cancel, 'function'); + promise.cancel(); + await assert.rejects( + async () => { + await promise; + }, + (err: GoogleError) => { assert(err instanceof GoogleError); assert.strictEqual(err.code, status.CANCELLED); - done(); - }); - promise.cancel(); + return true; + }, + ); }); - it('cancels retrying call', done => { + it('cancels retrying call', async () => { const retryOptions = utils.createRetryOptions(0, 0, 0, 0, 0, 0, 100); let callCount = 0; @@ -1192,18 +1190,13 @@ describe('Promise', () => { }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const promise = (apiCall as any)(null); - promise - .then(() => { - return done(new Error('should not reach')); - }) - .catch(() => { - assert(callCount < 4); - done(); - }) - .catch(done); setTimeout(() => { promise.cancel(); }, 15); + await assert.rejects(async () => { + await promise; + }); + assert(callCount < 4); }); it('does not return promise when callback is supplied', done => { @@ -1259,9 +1252,9 @@ describe('retryable', () => { }); }); - it('retries the API call with promise', done => { + it('retries the API call with promise', async () => { let toAttempt = 3; - let deadlineArg: string; + let deadlineArg: string | undefined = undefined; function func( argument: {}, metadata: {}, @@ -1277,18 +1270,14 @@ describe('retryable', () => { callback(null, 1729); } const apiCall = createApiCall(func, settings); - apiCall({}, undefined) - .then(resp => { - assert.ok(Array.isArray(resp)); - assert.strictEqual(resp[0], 1729); - assert.strictEqual(toAttempt, 0); - assert.ok(deadlineArg); - return done(); - }) - .catch(done); + const resp = (await apiCall({}, undefined)) as [number, unknown, unknown]; + assert.ok(Array.isArray(resp)); + assert.strictEqual(resp[0], 1729); + assert.strictEqual(toAttempt, 0); + assert.ok(deadlineArg); }); - it('cancels in the middle of retries', done => { + it('cancels in the middle of retries', async () => { let callCount = 0; // eslint-disable-next-line @typescript-eslint/no-explicit-any function func(argument: {}, metadata: {}, options: {}, callback: Function) { @@ -1306,14 +1295,15 @@ describe('retryable', () => { } const apiCall = createApiCall(func, settings); const promise = apiCall({}, undefined); - promise - .then(() => { - return done(new Error('should not reach')); - }) - .catch((err: Error) => { + await assert.rejects( + async () => { + await promise; + }, + (err: Error) => { assert(err instanceof Error); - done(); - }); + return true; + }, + ); }); it("doesn't retry if no codes", done => { @@ -1499,7 +1489,7 @@ describe('retryable', () => { }); }); - it.skip('retries with exponential backoff', done => { + it.skip('retries with exponential backoff', async () => { const startTime = new Date(); const spy = sinon.spy(fail); @@ -1509,23 +1499,28 @@ describe('retryable', () => { settings: {timeout: 0, retry: retryOptions}, }); - void apiCall({}, undefined, err => { - assert(err instanceof Error); - assert.strictEqual(err!.code, FAKE_STATUS_CODE_1); - assert(err!.note); - const now = new Date(); - assert( - now.getTime() - startTime.getTime() >= backoff.totalTimeoutMillis!, - ); - const callsLowerBound = - backoff.totalTimeoutMillis! / - (backoff.maxRetryDelayMillis + backoff.maxRpcTimeoutMillis!); - const callsUpperBound = - backoff.totalTimeoutMillis! / backoff.initialRetryDelayMillis; - assert(spy.callCount > callsLowerBound); - assert(spy.callCount < callsUpperBound); - done(); - }).catch(done); + await assert.rejects( + async () => { + await apiCall({}, undefined); + }, + (err: GoogleError) => { + assert(err instanceof Error); + assert.strictEqual(err!.code, FAKE_STATUS_CODE_1); + assert(err!.note); + const now = new Date(); + assert( + now.getTime() - startTime.getTime() >= backoff.totalTimeoutMillis!, + ); + const callsLowerBound = + backoff.totalTimeoutMillis! / + (backoff.maxRetryDelayMillis + backoff.maxRpcTimeoutMillis!); + const callsUpperBound = + backoff.totalTimeoutMillis! / backoff.initialRetryDelayMillis; + assert(spy.callCount > callsLowerBound); + assert(spy.callCount < callsUpperBound); + return true; + }, + ); }); it.skip('reports A/B testing', () => { @@ -1573,12 +1568,12 @@ describe('retryable', () => { }); }); - it('forwards metadata to builder', done => { + it('forwards metadata to builder', async () => { function func(argument: {}, metadata: {}, options: {}, callback: Function) { callback(null, {}); } - let gotHeaders: {h1?: string; h2?: string}; + let gotHeaders: {h1?: string; h2?: string} = {}; const mockBuilder = (abTest: {}, headers: {}) => { gotHeaders = headers; }; @@ -1592,14 +1587,8 @@ describe('retryable', () => { h1: 'val1', h2: 'val2', }; - void apiCall({}, {otherArgs: {headers}}).then(() => { - try { - assert.strictEqual(gotHeaders.h1, 'val1'); - assert.strictEqual(gotHeaders.h2, 'val2'); - return done(); - } catch (err) { - return done(err); - } - }); + await apiCall({}, {otherArgs: {headers}}); + assert.strictEqual(gotHeaders.h1, 'val1'); + assert.strictEqual(gotHeaders.h2, 'val2'); }); }); diff --git a/core/packages/gax/test/unit/pagedIteration.ts b/core/packages/gax/test/unit/pagedIteration.ts index 6d40bc66bc8e..75ff5c6ba76b 100644 --- a/core/packages/gax/test/unit/pagedIteration.ts +++ b/core/packages/gax/test/unit/pagedIteration.ts @@ -20,14 +20,13 @@ import assert from 'assert'; import * as pumpify from 'pumpify'; import * as sinon from 'sinon'; -import {PassThrough} from 'stream'; +import {PassThrough, Stream} from 'stream'; import streamEvents from 'stream-events'; import {PageDescriptor} from '../../src/paginationCalls/pageDescriptor'; import {APICallback, GaxCall, RequestType} from '../../src/apitypes'; import {describe, it, beforeEach} from 'mocha'; import * as util from './utils'; -import {Stream} from 'stream'; import * as gax from '../../src/gax'; import * as warnings from '../../src/warnings'; diff --git a/core/packages/gax/test/unit/streamArrayParser.ts b/core/packages/gax/test/unit/streamArrayParser.ts index ca9da211b277..6eaab47b111d 100644 --- a/core/packages/gax/test/unit/streamArrayParser.ts +++ b/core/packages/gax/test/unit/streamArrayParser.ts @@ -17,10 +17,9 @@ import assert from 'assert'; import {StreamArrayParser} from '../../src/streamArrayParser'; import {before, describe, it} from 'mocha'; -import {pipeline} from 'stream'; +import {pipeline, PassThrough} from 'stream'; import path = require('path'); import protobuf = require('protobufjs'); -import {PassThrough} from 'stream'; import {toProtobufJSON} from './utils'; interface User { diff --git a/core/packages/gax/test/unit/streaming.ts b/core/packages/gax/test/unit/streaming.ts index e9e54dba21b5..0480b63b6e36 100644 --- a/core/packages/gax/test/unit/streaming.ts +++ b/core/packages/gax/test/unit/streaming.ts @@ -27,13 +27,13 @@ import { RequestType, CancellableStream, SimpleCallbackFunction, + APICallback, } from '../../src/apitypes'; import {createApiCall} from '../../src/createApiCall'; import {StreamingApiCaller} from '../../src/streamingCalls/streamingApiCaller'; import * as gax from '../../src/gax'; import {StreamDescriptor} from '../../src/streamingCalls/streamDescriptor'; import * as streaming from '../../src/streamingCalls/streaming'; -import {APICallback} from '../../src/apitypes'; import * as warnings from '../../src/warnings'; import internal = require('stream'); import {StreamArrayParser} from '../../src/streamArrayParser'; diff --git a/core/packages/gax/test/unit/transcoding.ts b/core/packages/gax/test/unit/transcoding.ts index 3d1d613adf1e..5eb901223f2c 100644 --- a/core/packages/gax/test/unit/transcoding.ts +++ b/core/packages/gax/test/unit/transcoding.ts @@ -382,7 +382,7 @@ describe('gRPC to HTTP transcoding', () => { assert.strictEqual(encodeWithSlashes(unreserved), unreserved); // Reserved and special characters: should be percent encoded, including !\'()* - const specialChars = "!\'()*"; + const specialChars = "!'()*"; const encoded = encodeWithSlashes(specialChars); // ! -> %21, ' -> %27, ( -> %28, ) -> %29, * -> %2A assert.strictEqual(encoded, '%21%27%28%29%2A'); @@ -440,7 +440,7 @@ describe('gRPC to HTTP transcoding', () => { applyPattern( 'projects/*/locations/*/agents/*/sessions/**', 'projects/p/locations/l/agents/a/sessions/agents/../subagent', - 'session' + 'session', ); }, /Value for session must not contain segments that are exactly \. or \.\./); }); @@ -450,7 +450,7 @@ describe('gRPC to HTTP transcoding', () => { applyPattern( 'projects/*/locations/*/agents/*/sessions/**', 'projects/p/locations/l/agents/a/sessions/agents/./subagent', - 'session' + 'session', ); }, /Value for session must not contain segments that are exactly \. or \.\./); }); @@ -459,9 +459,12 @@ describe('gRPC to HTTP transcoding', () => { const res = applyPattern( 'projects/*/locations/*/agents/*/sessions/**', 'projects/p/locations/l/agents/a/sessions/..?$foo=BAR#', - 'session' + 'session', + ); + assert.strictEqual( + res, + 'projects/p/locations/l/agents/a/sessions/..%3F%24foo%3DBAR%23', ); - assert.strictEqual(res, 'projects/p/locations/l/agents/a/sessions/..%3F%24foo%3DBAR%23'); }); it('applyPattern should handle optional unmatched groups gracefully without throwing TypeErrors', () => { diff --git a/core/packages/gax/test/unit/transcoding_validation.ts b/core/packages/gax/test/unit/transcoding_validation.ts index f3815e74fa22..220d01ccc50c 100644 --- a/core/packages/gax/test/unit/transcoding_validation.ts +++ b/core/packages/gax/test/unit/transcoding_validation.ts @@ -13,7 +13,7 @@ // limitations under the License. import * as assert from 'assert'; -import { describe, it } from 'mocha'; +import {describe, it} from 'mocha'; import {v3} from '@google-cloud/dialogflow-cx'; const sinon = require('sinon'); @@ -25,7 +25,7 @@ describe('Dialogflow CX Fallback Transcoding and Path Traversal Prevention', () beforeEach(() => { client = new v3.SessionsClient({ fallback: true, - credentials: { client_email: 'bogus@example.com', private_key: 'bogus' }, + credentials: {client_email: 'bogus@example.com', private_key: 'bogus'}, projectId: 'bogus', }); fetchStub = sinon.stub().resolves({ @@ -42,9 +42,9 @@ describe('Dialogflow CX Fallback Transcoding and Path Traversal Prevention', () await assert.rejects( client.detectIntent({ session: 'projects/p/locations/l/agents/a/sessions/.', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }), - /Invalid value \. for session/ + /Invalid value \. for session/, ); }); @@ -54,24 +54,26 @@ describe('Dialogflow CX Fallback Transcoding and Path Traversal Prevention', () await assert.rejects( client.detectIntent({ session: 'projects/p/locations/l/agents/a/sessions/..', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }), - /Invalid value \.\. for session/ + /Invalid value \.\. for session/, ); }); - - // Test 5: Standard Valid Path fallback REST call it('5. should pass transcoding validation with a valid session path and construct the correct REST URL', async () => { await client.initialize(); await client.detectIntent({ session: 'projects/p/locations/l/agents/a/sessions/valid-session-id', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }); assert.strictEqual(fetchStub.callCount, 1); const requestUrl = fetchStub.firstCall.args[0]; - assert.ok(requestUrl.includes('/v3/projects/p/locations/l/agents/a/sessions/valid-session-id:detectIntent')); + assert.ok( + requestUrl.includes( + '/v3/projects/p/locations/l/agents/a/sessions/valid-session-id:detectIntent', + ), + ); }); // Test 6: Query Parameter Injection Prevention via percent-encoding @@ -79,7 +81,7 @@ describe('Dialogflow CX Fallback Transcoding and Path Traversal Prevention', () await client.initialize(); await client.detectIntent({ session: 'projects/p/locations/l/agents/a/sessions/my-session?$foo=BAR#', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }); assert.strictEqual(fetchStub.callCount, 1); const requestUrl = fetchStub.firstCall.args[0]; @@ -96,11 +98,15 @@ describe('Dialogflow CX Fallback Transcoding and Path Traversal Prevention', () await client.initialize(); await client.detectIntent({ session: 'projects/p/locations/l/agents/a/sessions/..?$foo=BAR#', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }); assert.strictEqual(fetchStub.callCount, 1); const requestUrl = fetchStub.firstCall.args[0]; - assert.ok(requestUrl.includes('/v3/projects/p/locations/l/agents/a/sessions/..%3F%24foo%3DBAR%23:detectIntent')); + assert.ok( + requestUrl.includes( + '/v3/projects/p/locations/l/agents/a/sessions/..%3F%24foo%3DBAR%23:detectIntent', + ), + ); }); // Test 8: Combined Path Traversal (.) and Query Parameter Injection @@ -108,22 +114,30 @@ describe('Dialogflow CX Fallback Transcoding and Path Traversal Prevention', () await client.initialize(); await client.detectIntent({ session: 'projects/p/locations/l/agents/a/sessions/.?$foo=BAR#', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }); assert.strictEqual(fetchStub.callCount, 1); const requestUrl = fetchStub.firstCall.args[0]; - assert.ok(requestUrl.includes('/v3/projects/p/locations/l/agents/a/sessions/.%3F%24foo%3DBAR%23:detectIntent')); + assert.ok( + requestUrl.includes( + '/v3/projects/p/locations/l/agents/a/sessions/.%3F%24foo%3DBAR%23:detectIntent', + ), + ); }); // Test 9: Percent-encoding all other characters it('9. should percent-encode all other characters except unreserved ones', async () => { await client.initialize(); await client.detectIntent({ - session: 'projects/p/locations/l/agents/a/sessions/ !@$&\'()*+,;=:%', - queryInput: { text: { text: 'hello' }, languageCode: 'en' }, + session: "projects/p/locations/l/agents/a/sessions/ !@$&'()*+,;=:%", + queryInput: {text: {text: 'hello'}, languageCode: 'en'}, }); assert.strictEqual(fetchStub.callCount, 1); const requestUrl = fetchStub.firstCall.args[0]; - assert.ok(requestUrl.includes('/v3/projects/p/locations/l/agents/a/sessions/%20%21%40%24%26%27%28%29%2A%2B%2C%3B%3D%3A%25:detectIntent')); + assert.ok( + requestUrl.includes( + '/v3/projects/p/locations/l/agents/a/sessions/%20%21%40%24%26%27%28%29%2A%2B%2C%3B%3D%3A%25:detectIntent', + ), + ); }); });