From 80c660e33af1c063e53645047bde93bc056558b3 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Wed, 15 Nov 2023 01:07:59 +0200 Subject: [PATCH] Refactoring: Convert global config to client config and make it available through client context (#202) Convert global config to client config and make it available through client context Signed-off-by: Levko Kravets --- lib/DBSQLClient.ts | 27 ++++- lib/DBSQLSession.ts | 12 +- lib/connection/connections/HttpConnection.ts | 13 ++- lib/contracts/IClientContext.ts | 16 +++ lib/globalConfig.ts | 27 ----- lib/hive/Commands/BaseCommand.ts | 19 +-- lib/hive/HiveDriver.ts | 42 +++---- lib/result/CloudFetchResultHandler.ts | 4 +- tests/e2e/arrow.test.js | 104 ++++++++++------- tests/e2e/batched_fetch.test.js | 27 ++--- tests/e2e/cloudfetch.test.js | 32 ++--- tests/e2e/data_types.test.js | 26 ++--- tests/e2e/timeouts.test.js | 27 ++--- tests/unit/DBSQLClient.test.js | 4 +- tests/unit/DBSQLSession.test.js | 4 + .../connections/HttpConnection.test.js | 110 +++++++++++++----- tests/unit/hive/commands/BaseCommand.test.js | 105 +++++++++++++---- .../result/CloudFetchResultHandler.test.js | 64 +++++----- 18 files changed, 397 insertions(+), 266 deletions(-) delete mode 100644 lib/globalConfig.ts diff --git a/lib/DBSQLClient.ts b/lib/DBSQLClient.ts index 779492e6..5c25d540 100644 --- a/lib/DBSQLClient.ts +++ b/lib/DBSQLClient.ts @@ -5,7 +5,7 @@ import TCLIService from '../thrift/TCLIService'; import { TProtocolVersion } from '../thrift/TCLIService_types'; import IDBSQLClient, { ClientOptions, ConnectionOptions, OpenSessionRequest } from './contracts/IDBSQLClient'; import IDriver from './contracts/IDriver'; -import IClientContext from './contracts/IClientContext'; +import IClientContext, { ClientConfig } from './contracts/IClientContext'; import HiveDriver from './hive/HiveDriver'; import { Int64 } from './hive/Types'; import DBSQLSession from './DBSQLSession'; @@ -46,6 +46,8 @@ function getInitialNamespaceOptions(catalogName?: string, schemaName?: string) { export default class DBSQLClient extends EventEmitter implements IDBSQLClient, IClientContext { private static defaultLogger?: IDBSQLLogger; + private readonly config: ClientConfig; + private connectionProvider?: IConnectionProvider; private authProvider?: IAuthentication; @@ -69,8 +71,25 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I return this.defaultLogger; } + private static getDefaultConfig(): ClientConfig { + return { + arrowEnabled: true, + useArrowNativeTypes: true, + socketTimeout: 15 * 60 * 1000, // 15 minutes + + retryMaxAttempts: 30, + retriesTimeout: 900 * 1000, + retryDelayMin: 1 * 1000, + retryDelayMax: 60 * 1000, + + useCloudFetch: false, + cloudFetchConcurrentDownloads: 10, + }; + } + constructor(options?: ClientOptions) { super(); + this.config = DBSQLClient.getDefaultConfig(); this.logger = options?.logger ?? DBSQLClient.getDefaultLogger(); this.logger.log(LogLevel.info, 'Created DBSQLClient'); } @@ -129,7 +148,7 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I public async connect(options: ConnectionOptions, authProvider?: IAuthentication): Promise { this.authProvider = this.initAuthProvider(options, authProvider); - this.connectionProvider = new HttpConnection(this.getConnectionOptions(options)); + this.connectionProvider = new HttpConnection(this.getConnectionOptions(options), this); const thriftConnection = await this.connectionProvider.getThriftConnection(); @@ -196,6 +215,10 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I this.authProvider = undefined; } + public getConfig(): ClientConfig { + return this.config; + } + public getLogger(): IDBSQLLogger { return this.logger; } diff --git a/lib/DBSQLSession.ts b/lib/DBSQLSession.ts index ccba880a..cf14dcd9 100644 --- a/lib/DBSQLSession.ts +++ b/lib/DBSQLSession.ts @@ -33,11 +33,10 @@ import { definedOrError } from './utils'; import CloseableCollection from './utils/CloseableCollection'; import { LogLevel } from './contracts/IDBSQLLogger'; import HiveDriverError from './errors/HiveDriverError'; -import globalConfig from './globalConfig'; import StagingError from './errors/StagingError'; import { DBSQLParameter, DBSQLParameterValue } from './DBSQLParameter'; import ParameterError from './errors/ParameterError'; -import IClientContext from './contracts/IClientContext'; +import IClientContext, { ClientConfig } from './contracts/IClientContext'; const defaultMaxRows = 100000; @@ -59,11 +58,11 @@ function getDirectResultsOptions(maxRows: number | null = defaultMaxRows) { }; } -function getArrowOptions(): { +function getArrowOptions(config: ClientConfig): { canReadArrowResult: boolean; useArrowNativeTypes?: TSparkArrowTypes; } { - const { arrowEnabled = true, useArrowNativeTypes = true } = globalConfig; + const { arrowEnabled = true, useArrowNativeTypes = true } = config; if (!arrowEnabled) { return { @@ -187,14 +186,15 @@ export default class DBSQLSession implements IDBSQLSession { public async executeStatement(statement: string, options: ExecuteStatementOptions = {}): Promise { await this.failIfClosed(); const driver = await this.context.getDriver(); + const clientConfig = this.context.getConfig(); const operationPromise = driver.executeStatement({ sessionHandle: this.sessionHandle, statement, queryTimeout: options.queryTimeout, runAsync: true, ...getDirectResultsOptions(options.maxRows), - ...getArrowOptions(), - canDownloadResult: options.useCloudFetch ?? globalConfig.useCloudFetch, + ...getArrowOptions(clientConfig), + canDownloadResult: options.useCloudFetch ?? clientConfig.useCloudFetch, parameters: getQueryParameters(this.sessionHandle, options.namedParameters, options.ordinalParameters), }); const response = await this.handleResponse(operationPromise); diff --git a/lib/connection/connections/HttpConnection.ts b/lib/connection/connections/HttpConnection.ts index 79f24c3d..2543dbbf 100644 --- a/lib/connection/connections/HttpConnection.ts +++ b/lib/connection/connections/HttpConnection.ts @@ -6,21 +6,24 @@ import { ProxyAgent } from 'proxy-agent'; import IConnectionProvider from '../contracts/IConnectionProvider'; import IConnectionOptions, { ProxyOptions } from '../contracts/IConnectionOptions'; -import globalConfig from '../../globalConfig'; +import IClientContext from '../../contracts/IClientContext'; import ThriftHttpConnection from './ThriftHttpConnection'; export default class HttpConnection implements IConnectionProvider { private readonly options: IConnectionOptions; + private readonly context: IClientContext; + private headers: HeadersInit = {}; private connection?: ThriftHttpConnection; private agent?: http.Agent; - constructor(options: IConnectionOptions) { + constructor(options: IConnectionOptions, context: IClientContext) { this.options = options; + this.context = context; } public setHeaders(headers: HeadersInit) { @@ -44,11 +47,12 @@ export default class HttpConnection implements IConnectionProvider { } private getAgentDefaultOptions(): http.AgentOptions { + const clientConfig = this.context.getConfig(); return { keepAlive: true, maxSockets: 5, keepAliveMsecs: 10000, - timeout: this.options.socketTimeout ?? globalConfig.socketTimeout, + timeout: this.options.socketTimeout ?? clientConfig.socketTimeout, }; } @@ -89,6 +93,7 @@ export default class HttpConnection implements IConnectionProvider { public async getThriftConnection(): Promise { if (!this.connection) { const { options } = this; + const clientConfig = this.context.getConfig(); const agent = await this.getAgent(); this.connection = new ThriftHttpConnection( @@ -99,7 +104,7 @@ export default class HttpConnection implements IConnectionProvider { }, { agent, - timeout: options.socketTimeout ?? globalConfig.socketTimeout, + timeout: options.socketTimeout ?? clientConfig.socketTimeout, headers: { ...options.headers, ...this.headers, diff --git a/lib/contracts/IClientContext.ts b/lib/contracts/IClientContext.ts index 8df0f7e9..062d0795 100644 --- a/lib/contracts/IClientContext.ts +++ b/lib/contracts/IClientContext.ts @@ -3,7 +3,23 @@ import IDriver from './IDriver'; import IConnectionProvider from '../connection/contracts/IConnectionProvider'; import TCLIService from '../../thrift/TCLIService'; +export interface ClientConfig { + arrowEnabled?: boolean; + useArrowNativeTypes?: boolean; + socketTimeout: number; + + retryMaxAttempts: number; + retriesTimeout: number; // in milliseconds + retryDelayMin: number; // in milliseconds + retryDelayMax: number; // in milliseconds + + useCloudFetch: boolean; + cloudFetchConcurrentDownloads: number; +} + export default interface IClientContext { + getConfig(): ClientConfig; + getLogger(): IDBSQLLogger; getConnectionProvider(): Promise; diff --git a/lib/globalConfig.ts b/lib/globalConfig.ts deleted file mode 100644 index ad477d8b..00000000 --- a/lib/globalConfig.ts +++ /dev/null @@ -1,27 +0,0 @@ -interface GlobalConfig { - arrowEnabled?: boolean; - useArrowNativeTypes?: boolean; - socketTimeout: number; - - retryMaxAttempts: number; - retriesTimeout: number; // in milliseconds - retryDelayMin: number; // in milliseconds - retryDelayMax: number; // in milliseconds - - useCloudFetch: boolean; - cloudFetchConcurrentDownloads: number; -} - -export default { - arrowEnabled: true, - useArrowNativeTypes: true, - socketTimeout: 15 * 60 * 1000, // 15 minutes - - retryMaxAttempts: 30, - retriesTimeout: 900 * 1000, - retryDelayMin: 1 * 1000, - retryDelayMax: 60 * 1000, - - useCloudFetch: false, - cloudFetchConcurrentDownloads: 10, -} satisfies GlobalConfig; diff --git a/lib/hive/Commands/BaseCommand.ts b/lib/hive/Commands/BaseCommand.ts index 3fff1946..f059d9e1 100644 --- a/lib/hive/Commands/BaseCommand.ts +++ b/lib/hive/Commands/BaseCommand.ts @@ -1,16 +1,16 @@ import { Thrift } from 'thrift'; import TCLIService from '../../../thrift/TCLIService'; import HiveDriverError from '../../errors/HiveDriverError'; -import globalConfig from '../../globalConfig'; +import IClientContext, { ClientConfig } from '../../contracts/IClientContext'; interface CommandExecutionInfo { startTime: number; // in milliseconds attempt: number; } -function getRetryDelay(attempt: number): number { +function getRetryDelay(attempt: number, config: ClientConfig): number { const scale = Math.max(1, 1.5 ** (attempt - 1)); // ensure scale >= 1 - return Math.min(globalConfig.retryDelayMin * scale, globalConfig.retryDelayMax); + return Math.min(config.retryDelayMin * scale, config.retryDelayMax); } function delay(milliseconds: number): Promise { @@ -22,8 +22,11 @@ function delay(milliseconds: number): Promise { export default abstract class BaseCommand { protected client: TCLIService.Client; - constructor(client: TCLIService.Client) { + protected context: IClientContext; + + constructor(client: TCLIService.Client, context: IClientContext) { this.client = client; + this.context = context; } protected executeCommand(request: object, command: Function | void): Promise { @@ -49,19 +52,21 @@ export default abstract class BaseCommand { case 503: // Service Unavailable info.attempt += 1; + const clientConfig = this.context.getConfig(); + // Delay interval depends on current attempt - the more attempts we do // the longer the interval will be // TODO: Respect `Retry-After` header (PECO-729) - const retryDelay = getRetryDelay(info.attempt); + const retryDelay = getRetryDelay(info.attempt, clientConfig); - const attemptsExceeded = info.attempt >= globalConfig.retryMaxAttempts; + const attemptsExceeded = info.attempt >= clientConfig.retryMaxAttempts; if (attemptsExceeded) { throw new HiveDriverError( `Hive driver: ${error.statusCode} when connecting to resource. Max retry count exceeded.`, ); } - const timeoutExceeded = Date.now() - info.startTime + retryDelay >= globalConfig.retriesTimeout; + const timeoutExceeded = Date.now() - info.startTime + retryDelay >= clientConfig.retriesTimeout; if (timeoutExceeded) { throw new HiveDriverError( `Hive driver: ${error.statusCode} when connecting to resource. Retry timeout exceeded.`, diff --git a/lib/hive/HiveDriver.ts b/lib/hive/HiveDriver.ts index 9b7384b1..7afd03a5 100644 --- a/lib/hive/HiveDriver.ts +++ b/lib/hive/HiveDriver.ts @@ -58,127 +58,127 @@ export default class HiveDriver implements IDriver { async openSession(request: TOpenSessionReq) { const client = await this.context.getClient(); - const action = new OpenSessionCommand(client); + const action = new OpenSessionCommand(client, this.context); return action.execute(request); } async closeSession(request: TCloseSessionReq) { const client = await this.context.getClient(); - const command = new CloseSessionCommand(client); + const command = new CloseSessionCommand(client, this.context); return command.execute(request); } async executeStatement(request: TExecuteStatementReq) { const client = await this.context.getClient(); - const command = new ExecuteStatementCommand(client); + const command = new ExecuteStatementCommand(client, this.context); return command.execute(request); } async getResultSetMetadata(request: TGetResultSetMetadataReq) { const client = await this.context.getClient(); - const command = new GetResultSetMetadataCommand(client); + const command = new GetResultSetMetadataCommand(client, this.context); return command.execute(request); } async fetchResults(request: TFetchResultsReq) { const client = await this.context.getClient(); - const command = new FetchResultsCommand(client); + const command = new FetchResultsCommand(client, this.context); return command.execute(request); } async getInfo(request: TGetInfoReq) { const client = await this.context.getClient(); - const command = new GetInfoCommand(client); + const command = new GetInfoCommand(client, this.context); return command.execute(request); } async getTypeInfo(request: TGetTypeInfoReq) { const client = await this.context.getClient(); - const command = new GetTypeInfoCommand(client); + const command = new GetTypeInfoCommand(client, this.context); return command.execute(request); } async getCatalogs(request: TGetCatalogsReq) { const client = await this.context.getClient(); - const command = new GetCatalogsCommand(client); + const command = new GetCatalogsCommand(client, this.context); return command.execute(request); } async getSchemas(request: TGetSchemasReq) { const client = await this.context.getClient(); - const command = new GetSchemasCommand(client); + const command = new GetSchemasCommand(client, this.context); return command.execute(request); } async getTables(request: TGetTablesReq) { const client = await this.context.getClient(); - const command = new GetTablesCommand(client); + const command = new GetTablesCommand(client, this.context); return command.execute(request); } async getTableTypes(request: TGetTableTypesReq) { const client = await this.context.getClient(); - const command = new GetTableTypesCommand(client); + const command = new GetTableTypesCommand(client, this.context); return command.execute(request); } async getColumns(request: TGetColumnsReq) { const client = await this.context.getClient(); - const command = new GetColumnsCommand(client); + const command = new GetColumnsCommand(client, this.context); return command.execute(request); } async getFunctions(request: TGetFunctionsReq) { const client = await this.context.getClient(); - const command = new GetFunctionsCommand(client); + const command = new GetFunctionsCommand(client, this.context); return command.execute(request); } async getPrimaryKeys(request: TGetPrimaryKeysReq) { const client = await this.context.getClient(); - const command = new GetPrimaryKeysCommand(client); + const command = new GetPrimaryKeysCommand(client, this.context); return command.execute(request); } async getCrossReference(request: TGetCrossReferenceReq) { const client = await this.context.getClient(); - const command = new GetCrossReferenceCommand(client); + const command = new GetCrossReferenceCommand(client, this.context); return command.execute(request); } async getOperationStatus(request: TGetOperationStatusReq) { const client = await this.context.getClient(); - const command = new GetOperationStatusCommand(client); + const command = new GetOperationStatusCommand(client, this.context); return command.execute(request); } async cancelOperation(request: TCancelOperationReq) { const client = await this.context.getClient(); - const command = new CancelOperationCommand(client); + const command = new CancelOperationCommand(client, this.context); return command.execute(request); } async closeOperation(request: TCloseOperationReq) { const client = await this.context.getClient(); - const command = new CloseOperationCommand(client); + const command = new CloseOperationCommand(client, this.context); return command.execute(request); } async getDelegationToken(request: TGetDelegationTokenReq) { const client = await this.context.getClient(); - const command = new GetDelegationTokenCommand(client); + const command = new GetDelegationTokenCommand(client, this.context); return command.execute(request); } async cancelDelegationToken(request: TCancelDelegationTokenReq) { const client = await this.context.getClient(); - const command = new CancelDelegationTokenCommand(client); + const command = new CancelDelegationTokenCommand(client, this.context); return command.execute(request); } async renewDelegationToken(request: TRenewDelegationTokenReq) { const client = await this.context.getClient(); - const command = new RenewDelegationTokenCommand(client); + const command = new RenewDelegationTokenCommand(client, this.context); return command.execute(request); } } diff --git a/lib/result/CloudFetchResultHandler.ts b/lib/result/CloudFetchResultHandler.ts index a49e8714..db52b95f 100644 --- a/lib/result/CloudFetchResultHandler.ts +++ b/lib/result/CloudFetchResultHandler.ts @@ -4,7 +4,6 @@ import { TRowSet, TSparkArrowResultLink, TTableSchema } from '../../thrift/TCLIS import IClientContext from '../contracts/IClientContext'; import IResultsProvider from './IResultsProvider'; import ArrowResultHandler from './ArrowResultHandler'; -import globalConfig from '../globalConfig'; export default class CloudFetchResultHandler extends ArrowResultHandler { private pendingLinks: Array = []; @@ -30,7 +29,8 @@ export default class CloudFetchResultHandler extends ArrowResultHandler { }); if (this.downloadedBatches.length === 0) { - const links = this.pendingLinks.splice(0, globalConfig.cloudFetchConcurrentDownloads); + const clientConfig = this.context.getConfig(); + const links = this.pendingLinks.splice(0, clientConfig.cloudFetchConcurrentDownloads); const tasks = links.map((link) => this.downloadLink(link)); const batches = await Promise.all(tasks); this.downloadedBatches.push(...batches); diff --git a/tests/e2e/arrow.test.js b/tests/e2e/arrow.test.js index 4118a116..08fe17d8 100644 --- a/tests/e2e/arrow.test.js +++ b/tests/e2e/arrow.test.js @@ -4,7 +4,6 @@ const config = require('./utils/config'); const logger = require('./utils/logger')(config.logger); const { DBSQLClient } = require('../..'); const ArrowResultHandler = require('../../dist/result/ArrowResultHandler').default; -const globalConfig = require('../../dist/globalConfig').default; const fixtures = require('../fixtures/compatibility'); const { expected: expectedColumn } = require('../fixtures/compatibility/column'); @@ -12,9 +11,15 @@ const { expected: expectedArrow } = require('../fixtures/compatibility/arrow'); const { expected: expectedArrowNativeTypes } = require('../fixtures/compatibility/arrow_native_types'); const { fixArrowResult } = fixtures; -async function openSession() { +async function openSession(customConfig) { const client = new DBSQLClient(); + const clientConfig = client.getConfig(); + sinon.stub(client, 'getConfig').returns({ + ...clientConfig, + ...customConfig, + }); + const connection = await client.connect({ host: config.host, path: config.path, @@ -51,9 +56,9 @@ async function initializeTable(session, tableName) { describe('Arrow support', () => { const tableName = `dbsql_nodejs_sdk_e2e_arrow_${config.tableSuffix}`; - function createTest(testBody) { + function createTest(testBody, customConfig) { return async () => { - const session = await openSession(); + const session = await openSession(customConfig); try { await initializeTable(session, tableName); await testBody(session); @@ -69,60 +74,69 @@ describe('Arrow support', () => { it( 'should not use arrow if disabled', - createTest(async (session) => { - globalConfig.arrowEnabled = false; - - const operation = await session.executeStatement(`SELECT * FROM ${tableName}`); - const result = await operation.fetchAll(); - expect(result).to.deep.equal(expectedColumn); - - const resultHandler = await operation.getResultHandler(); - expect(resultHandler).to.be.not.instanceof(ArrowResultHandler); - - await operation.close(); - }), + createTest( + async (session) => { + const operation = await session.executeStatement(`SELECT * FROM ${tableName}`); + const result = await operation.fetchAll(); + expect(result).to.deep.equal(expectedColumn); + + const resultHandler = await operation.getResultHandler(); + expect(resultHandler).to.be.not.instanceof(ArrowResultHandler); + + await operation.close(); + }, + { + arrowEnabled: false, + }, + ), ); it( 'should use arrow with native types disabled', - createTest(async (session) => { - globalConfig.arrowEnabled = true; - globalConfig.useArrowNativeTypes = false; - - const operation = await session.executeStatement(`SELECT * FROM ${tableName}`); - const result = await operation.fetchAll(); - expect(fixArrowResult(result)).to.deep.equal(expectedArrow); - - const resultHandler = await operation.getResultHandler(); - expect(resultHandler).to.be.instanceof(ArrowResultHandler); - - await operation.close(); - }), + createTest( + async (session) => { + const operation = await session.executeStatement(`SELECT * FROM ${tableName}`); + const result = await operation.fetchAll(); + expect(fixArrowResult(result)).to.deep.equal(expectedArrow); + + const resultHandler = await operation.getResultHandler(); + expect(resultHandler).to.be.instanceof(ArrowResultHandler); + + await operation.close(); + }, + { + arrowEnabled: true, + useArrowNativeTypes: false, + }, + ), ); it( 'should use arrow with native types enabled', - createTest(async (session) => { - globalConfig.arrowEnabled = true; - globalConfig.useArrowNativeTypes = true; - - const operation = await session.executeStatement(`SELECT * FROM ${tableName}`); - const result = await operation.fetchAll(); - expect(fixArrowResult(result)).to.deep.equal(expectedArrowNativeTypes); - - const resultHandler = await operation.getResultHandler(); - expect(resultHandler).to.be.instanceof(ArrowResultHandler); - - await operation.close(); - }), + createTest( + async (session) => { + const operation = await session.executeStatement(`SELECT * FROM ${tableName}`); + const result = await operation.fetchAll(); + expect(fixArrowResult(result)).to.deep.equal(expectedArrowNativeTypes); + + const resultHandler = await operation.getResultHandler(); + expect(resultHandler).to.be.instanceof(ArrowResultHandler); + + await operation.close(); + }, + { + arrowEnabled: true, + useArrowNativeTypes: true, + }, + ), ); it('should handle multiple batches in response', async () => { - globalConfig.arrowEnabled = true; - const rowsCount = 10000; - const session = await openSession(); + const session = await openSession({ + arrowEnabled: true, + }); const operation = await session.executeStatement(` SELECT * FROM range(0, ${rowsCount}) AS t1 diff --git a/tests/e2e/batched_fetch.test.js b/tests/e2e/batched_fetch.test.js index 5218088e..2e3059a9 100644 --- a/tests/e2e/batched_fetch.test.js +++ b/tests/e2e/batched_fetch.test.js @@ -3,11 +3,16 @@ const sinon = require('sinon'); const config = require('./utils/config'); const logger = require('./utils/logger')(config.logger); const { DBSQLClient } = require('../..'); -const globalConfig = require('../../dist/globalConfig').default; -const openSession = async () => { +async function openSession(customConfig) { const client = new DBSQLClient(); + const clientConfig = client.getConfig(); + sinon.stub(client, 'getConfig').returns({ + ...clientConfig, + ...customConfig, + }); + const connection = await client.connect({ host: config.host, path: config.path, @@ -18,17 +23,9 @@ const openSession = async () => { initialCatalog: config.database[0], initialSchema: config.database[1], }); -}; +} describe('Data fetching', () => { - beforeEach(() => { - globalConfig.arrowEnabled = false; - }); - - afterEach(() => { - globalConfig.arrowEnabled = true; - }); - const query = ` SELECT * FROM range(0, 1000) AS t1 @@ -36,7 +33,7 @@ describe('Data fetching', () => { `; it('fetch chunks should return a max row set of chunkSize', async () => { - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); sinon.spy(session.context.driver, 'fetchResults'); try { // set `maxRows` to null to disable direct results so all the data are fetched through `driver.fetchResults` @@ -51,7 +48,7 @@ describe('Data fetching', () => { }); it('fetch all should fetch all records', async () => { - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); sinon.spy(session.context.driver, 'fetchResults'); try { // set `maxRows` to null to disable direct results so all the data are fetched through `driver.fetchResults` @@ -66,7 +63,7 @@ describe('Data fetching', () => { }); it('should fetch all records if they fit within directResults response', async () => { - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); sinon.spy(session.context.driver, 'fetchResults'); try { // here `maxRows` enables direct results with limit of the first batch @@ -81,7 +78,7 @@ describe('Data fetching', () => { }); it('should fetch all records if only part of them fit within directResults response', async () => { - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); sinon.spy(session.context.driver, 'fetchResults'); try { // here `maxRows` enables direct results with limit of the first batch diff --git a/tests/e2e/cloudfetch.test.js b/tests/e2e/cloudfetch.test.js index 03b2cb60..e2b564db 100644 --- a/tests/e2e/cloudfetch.test.js +++ b/tests/e2e/cloudfetch.test.js @@ -4,11 +4,16 @@ const config = require('./utils/config'); const logger = require('./utils/logger')(config.logger); const { DBSQLClient } = require('../..'); const CloudFetchResultHandler = require('../../dist/result/CloudFetchResultHandler').default; -const globalConfig = require('../../dist/globalConfig').default; -const openSession = async () => { +async function openSession(customConfig) { const client = new DBSQLClient(); + const clientConfig = client.getConfig(); + sinon.stub(client, 'getConfig').returns({ + ...clientConfig, + ...customConfig, + }); + const connection = await client.connect({ host: config.host, path: config.path, @@ -19,25 +24,14 @@ const openSession = async () => { initialCatalog: config.database[0], initialSchema: config.database[1], }); -}; +} // This suite takes a while to execute, and in this case it's expected. // If one day it starts to fail with timeouts - you may consider to just increase timeout for it describe('CloudFetch', () => { - let savedConcurrentDownloads; - - beforeEach(() => { - savedConcurrentDownloads = globalConfig.cloudFetchConcurrentDownloads; - }); - - afterEach(() => { - globalConfig.cloudFetchConcurrentDownloads = savedConcurrentDownloads; - }); - it('should fetch data', async () => { - globalConfig.cloudFetchConcurrentDownloads = 5; - - const session = await openSession(); + const cloudFetchConcurrentDownloads = 5; + const session = await openSession({ cloudFetchConcurrentDownloads }); const queriedRowsCount = 10000000; // result has to be quite big to enable CloudFetch const operation = await session.executeStatement( @@ -76,10 +70,8 @@ describe('CloudFetch', () => { expect(await resultHandler.hasMore()).to.be.true; // expected batches minus first 5 already fetched - expect(resultHandler.pendingLinks.length).to.be.equal( - resultLinksCount - globalConfig.cloudFetchConcurrentDownloads, - ); - expect(resultHandler.downloadedBatches.length).to.be.equal(globalConfig.cloudFetchConcurrentDownloads - 1); + expect(resultHandler.pendingLinks.length).to.be.equal(resultLinksCount - cloudFetchConcurrentDownloads); + expect(resultHandler.downloadedBatches.length).to.be.equal(cloudFetchConcurrentDownloads - 1); let fetchedRowCount = chunk.length; while (await operation.hasMoreRows()) { diff --git a/tests/e2e/data_types.test.js b/tests/e2e/data_types.test.js index 8308cc12..59c24856 100644 --- a/tests/e2e/data_types.test.js +++ b/tests/e2e/data_types.test.js @@ -1,12 +1,18 @@ const { expect } = require('chai'); +const sinon = require('sinon'); const config = require('./utils/config'); const logger = require('./utils/logger')(config.logger); const { DBSQLClient } = require('../..'); -const globalConfig = require('../../dist/globalConfig').default; -const openSession = async () => { +async function openSession(customConfig) { const client = new DBSQLClient(); + const clientConfig = client.getConfig(); + sinon.stub(client, 'getConfig').returns({ + ...clientConfig, + ...customConfig, + }); + const connection = await client.connect({ host: config.host, path: config.path, @@ -17,7 +23,7 @@ const openSession = async () => { initialCatalog: config.database[0], initialSchema: config.database[1], }); -}; +} const execute = async (session, statement) => { const operation = await session.executeStatement(statement); @@ -39,18 +45,10 @@ function removeTrailingMetadata(columns) { } describe('Data types', () => { - beforeEach(() => { - globalConfig.arrowEnabled = false; - }); - - afterEach(() => { - globalConfig.arrowEnabled = true; - }); - it('primitive data types should presented correctly', async () => { const table = `dbsql_nodejs_sdk_e2e_primitive_types_${config.tableSuffix}`; - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); try { await execute(session, `DROP TABLE IF EXISTS ${table}`); await execute( @@ -201,7 +199,7 @@ describe('Data types', () => { it('interval types should be presented correctly', async () => { const table = `dbsql_nodejs_sdk_e2e_interval_types_${config.tableSuffix}`; - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); try { await execute(session, `DROP TABLE IF EXISTS ${table}`); await execute( @@ -246,7 +244,7 @@ describe('Data types', () => { const table = `dbsql_nodejs_sdk_e2e_complex_types_${config.tableSuffix}`; const helperTable = `dbsql_nodejs_sdk_e2e_complex_types_helper_${config.tableSuffix}`; - const session = await openSession(); + const session = await openSession({ arrowEnabled: false }); try { await execute(session, `DROP TABLE IF EXISTS ${helperTable}`); await execute(session, `DROP TABLE IF EXISTS ${table}`); diff --git a/tests/e2e/timeouts.test.js b/tests/e2e/timeouts.test.js index fdf495c3..bea96c20 100644 --- a/tests/e2e/timeouts.test.js +++ b/tests/e2e/timeouts.test.js @@ -1,11 +1,17 @@ const { expect, AssertionError } = require('chai'); +const sinon = require('sinon'); const config = require('./utils/config'); const { DBSQLClient } = require('../..'); -const globalConfig = require('../../dist/globalConfig').default; -const openSession = async (socketTimeout) => { +async function openSession(socketTimeout, customConfig) { const client = new DBSQLClient(); + const clientConfig = client.getConfig(); + sinon.stub(client, 'getConfig').returns({ + ...clientConfig, + ...customConfig, + }); + const connection = await client.connect({ host: config.host, path: config.path, @@ -17,37 +23,26 @@ const openSession = async (socketTimeout) => { initialCatalog: config.database[0], initialSchema: config.database[1], }); -}; +} describe('Data fetching', () => { - const query = ` - SELECT * - FROM range(0, 100000) AS t1 - LEFT JOIN (SELECT 1) AS t2 - ORDER BY RANDOM() ASC - `; - const socketTimeout = 1; // minimum value to make sure any request will time out it('should use default socket timeout', async () => { - const savedTimeout = globalConfig.socketTimeout; - globalConfig.socketTimeout = socketTimeout; try { - await openSession(); + await openSession(undefined, { socketTimeout }); expect.fail('It should throw an error'); } catch (error) { if (error instanceof AssertionError) { throw error; } expect(error.message).to.be.eq('Request timed out'); - } finally { - globalConfig.socketTimeout = savedTimeout; } }); it('should use socket timeout from options', async () => { try { - await await openSession(socketTimeout); + await openSession(socketTimeout); expect.fail('It should throw an error'); } catch (error) { if (error instanceof AssertionError) { diff --git a/tests/unit/DBSQLClient.test.js b/tests/unit/DBSQLClient.test.js index 55231707..b1a1f3f2 100644 --- a/tests/unit/DBSQLClient.test.js +++ b/tests/unit/DBSQLClient.test.js @@ -184,7 +184,7 @@ describe('DBSQLClient.getClient', () => { const thriftClient = {}; client.authProvider = new AuthProviderMock(); - client.connectionProvider = new HttpConnection({ ...options }); + client.connectionProvider = new HttpConnection({ ...options }, client); client.thrift = { createClient: sinon.stub().returns(thriftClient), }; @@ -199,7 +199,7 @@ describe('DBSQLClient.getClient', () => { const thriftClient = {}; - client.connectionProvider = new HttpConnection({ ...options }); + client.connectionProvider = new HttpConnection({ ...options }, client); client.thrift = { createClient: sinon.stub().returns(thriftClient), }; diff --git a/tests/unit/DBSQLSession.test.js b/tests/unit/DBSQLSession.test.js index 9ff5172b..b172b29f 100644 --- a/tests/unit/DBSQLSession.test.js +++ b/tests/unit/DBSQLSession.test.js @@ -6,6 +6,7 @@ const InfoValue = require('../../dist/dto/InfoValue').default; const Status = require('../../dist/dto/Status').default; const DBSQLOperation = require('../../dist/DBSQLOperation').default; const HiveDriver = require('../../dist/hive/HiveDriver').default; +const DBSQLClient = require('../../dist/DBSQLClient').default; // Create logger that won't emit // @@ -37,9 +38,12 @@ function createDriverMock(customMethodHandler) { function createSession(customMethodHandler) { const driver = createDriverMock(customMethodHandler); + const clientConfig = DBSQLClient.getDefaultConfig(); + return new DBSQLSession({ handle: { sessionId: 'id' }, context: { + getConfig: () => clientConfig, getLogger: () => logger, getDriver: async () => driver, }, diff --git a/tests/unit/connection/connections/HttpConnection.test.js b/tests/unit/connection/connections/HttpConnection.test.js index a9a21136..261a3af8 100644 --- a/tests/unit/connection/connections/HttpConnection.test.js +++ b/tests/unit/connection/connections/HttpConnection.test.js @@ -2,14 +2,24 @@ const http = require('http'); const { expect } = require('chai'); const HttpConnection = require('../../../../dist/connection/connections/HttpConnection').default; const ThriftHttpConnection = require('../../../../dist/connection/connections/ThriftHttpConnection').default; +const DBSQLClient = require('../../../../dist/DBSQLClient').default; describe('HttpConnection.connect', () => { it('should create Thrift connection', async () => { - const connection = new HttpConnection({ - host: 'localhost', - port: 10001, - path: '/hive', - }); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + + const connection = new HttpConnection( + { + host: 'localhost', + port: 10001, + path: '/hive', + }, + context, + ); const thriftConnection = await connection.getThriftConnection(); @@ -22,15 +32,24 @@ describe('HttpConnection.connect', () => { }); it('should set SSL certificates and disable rejectUnauthorized', async () => { - const connection = new HttpConnection({ - host: 'localhost', - port: 10001, - path: '/hive', - https: true, - ca: 'ca', - cert: 'cert', - key: 'key', - }); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + + const connection = new HttpConnection( + { + host: 'localhost', + port: 10001, + path: '/hive', + https: true, + ca: 'ca', + cert: 'cert', + key: 'key', + }, + context, + ); const thriftConnection = await connection.getThriftConnection(); @@ -41,12 +60,21 @@ describe('HttpConnection.connect', () => { }); it('should initialize http agents', async () => { - const connection = new HttpConnection({ - host: 'localhost', - port: 10001, - https: false, - path: '/hive', - }); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + + const connection = new HttpConnection( + { + host: 'localhost', + port: 10001, + https: false, + path: '/hive', + }, + context, + ); const thriftConnection = await connection.getThriftConnection(); @@ -54,17 +82,26 @@ describe('HttpConnection.connect', () => { }); it('should update headers (case 1: Thrift connection not initialized)', async () => { + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + const initialHeaders = { a: 'test header A', b: 'test header B', }; - const connection = new HttpConnection({ - host: 'localhost', - port: 10001, - path: '/hive', - headers: initialHeaders, - }); + const connection = new HttpConnection( + { + host: 'localhost', + port: 10001, + path: '/hive', + headers: initialHeaders, + }, + context, + ); const extraHeaders = { b: 'new header B', @@ -82,17 +119,26 @@ describe('HttpConnection.connect', () => { }); it('should update headers (case 2: Thrift connection initialized)', async () => { + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + const initialHeaders = { a: 'test header A', b: 'test header B', }; - const connection = new HttpConnection({ - host: 'localhost', - port: 10001, - path: '/hive', - headers: initialHeaders, - }); + const connection = new HttpConnection( + { + host: 'localhost', + port: 10001, + path: '/hive', + headers: initialHeaders, + }, + context, + ); const thriftConnection = await connection.getThriftConnection(); diff --git a/tests/unit/hive/commands/BaseCommand.test.js b/tests/unit/hive/commands/BaseCommand.test.js index 1a20c303..d6b286cf 100644 --- a/tests/unit/hive/commands/BaseCommand.test.js +++ b/tests/unit/hive/commands/BaseCommand.test.js @@ -2,9 +2,7 @@ const { expect, AssertionError } = require('chai'); const { Thrift } = require('thrift'); const HiveDriverError = require('../../../../dist/errors/HiveDriverError').default; const BaseCommand = require('../../../../dist/hive/Commands/BaseCommand').default; -const globalConfig = require('../../../../dist/globalConfig').default; - -const savedGlobalConfig = { ...globalConfig }; +const DBSQLClient = require('../../../../dist/DBSQLClient').default; class ThriftClientMock { constructor(methodHandler) { @@ -26,18 +24,24 @@ ThriftClientMock.defaultResponse = { }; class CustomCommand extends BaseCommand { + constructor(...args) { + super(...args); + } + execute(request) { return this.executeCommand(request, this.client.CustomMethod); } } describe('BaseCommand', () => { - afterEach(() => { - Object.assign(globalConfig, savedGlobalConfig); - }); - it('should fail if trying to invoke non-existing command', async () => { - const command = new CustomCommand({}); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + + const command = new CustomCommand({}, context); try { await command.execute(); @@ -54,11 +58,20 @@ describe('BaseCommand', () => { it('should handle exceptions thrown by command', async () => { const errorMessage = 'Unexpected error'; - const command = new CustomCommand({ - CustomMethod() { - throw new Error(errorMessage); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + + const command = new CustomCommand( + { + CustomMethod() { + throw new Error(errorMessage); + }, }, - }); + context, + ); try { await command.execute(); @@ -75,10 +88,16 @@ describe('BaseCommand', () => { [429, 503].forEach((statusCode) => { describe(`HTTP ${statusCode} error`, () => { it('should fail on max retry attempts exceeded', async () => { - globalConfig.retriesTimeout = 200; // ms - globalConfig.retryDelayMin = 5; // ms - globalConfig.retryDelayMax = 20; // ms - globalConfig.retryMaxAttempts = 3; + const clientConfig = DBSQLClient.getDefaultConfig(); + + clientConfig.retriesTimeout = 200; // ms + clientConfig.retryDelayMin = 5; // ms + clientConfig.retryDelayMax = 20; // ms + clientConfig.retryMaxAttempts = 3; + + const context = { + getConfig: () => clientConfig, + }; let methodCallCount = 0; const command = new CustomCommand( @@ -88,6 +107,7 @@ describe('BaseCommand', () => { error.statusCode = statusCode; throw error; }), + context, ); try { @@ -100,15 +120,21 @@ describe('BaseCommand', () => { expect(error).to.be.instanceof(HiveDriverError); expect(error.message).to.contain(`${statusCode} when connecting to resource`); expect(error.message).to.contain('Max retry count exceeded'); - expect(methodCallCount).to.equal(globalConfig.retryMaxAttempts); + expect(methodCallCount).to.equal(clientConfig.retryMaxAttempts); } }); it('should fail on retry timeout exceeded', async () => { - globalConfig.retriesTimeout = 200; // ms - globalConfig.retryDelayMin = 5; // ms - globalConfig.retryDelayMax = 20; // ms - globalConfig.retryMaxAttempts = 50; + const clientConfig = DBSQLClient.getDefaultConfig(); + + clientConfig.retriesTimeout = 200; // ms + clientConfig.retryDelayMin = 5; // ms + clientConfig.retryDelayMax = 20; // ms + clientConfig.retryMaxAttempts = 50; + + const context = { + getConfig: () => clientConfig, + }; let methodCallCount = 0; const command = new CustomCommand( @@ -118,6 +144,7 @@ describe('BaseCommand', () => { error.statusCode = statusCode; throw error; }), + context, ); try { @@ -138,10 +165,16 @@ describe('BaseCommand', () => { }); it('should succeed after few attempts', async () => { - globalConfig.retriesTimeout = 200; // ms - globalConfig.retryDelayMin = 5; // ms - globalConfig.retryDelayMax = 20; // ms - globalConfig.retryMaxAttempts = 5; + const clientConfig = DBSQLClient.getDefaultConfig(); + + clientConfig.retriesTimeout = 200; // ms + clientConfig.retryDelayMin = 5; // ms + clientConfig.retryDelayMax = 20; // ms + clientConfig.retryMaxAttempts = 5; + + const context = { + getConfig: () => clientConfig, + }; let methodCallCount = 0; const command = new CustomCommand( @@ -154,6 +187,7 @@ describe('BaseCommand', () => { } return ThriftClientMock.defaultResponse; }), + context, ); const response = await command.execute(); @@ -166,12 +200,19 @@ describe('BaseCommand', () => { it(`should re-throw unrecognized HTTP errors`, async () => { const errorMessage = 'Unrecognized HTTP error'; + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + const command = new CustomCommand( new ThriftClientMock(() => { const error = new Thrift.TApplicationException(undefined, errorMessage); error.statusCode = 500; throw error; }), + context, ); try { @@ -189,10 +230,17 @@ describe('BaseCommand', () => { it(`should re-throw unrecognized Thrift errors`, async () => { const errorMessage = 'Unrecognized HTTP error'; + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + const command = new CustomCommand( new ThriftClientMock(() => { throw new Thrift.TApplicationException(undefined, errorMessage); }), + context, ); try { @@ -210,10 +258,17 @@ describe('BaseCommand', () => { it(`should re-throw unrecognized errors`, async () => { const errorMessage = 'Unrecognized error'; + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + const command = new CustomCommand( new ThriftClientMock(() => { throw new Error(errorMessage); }), + context, ); try { diff --git a/tests/unit/result/CloudFetchResultHandler.test.js b/tests/unit/result/CloudFetchResultHandler.test.js index e0a48151..29367c55 100644 --- a/tests/unit/result/CloudFetchResultHandler.test.js +++ b/tests/unit/result/CloudFetchResultHandler.test.js @@ -2,8 +2,8 @@ const { expect, AssertionError } = require('chai'); const sinon = require('sinon'); const Int64 = require('node-int64'); const CloudFetchResultHandler = require('../../../dist/result/CloudFetchResultHandler').default; -const globalConfig = require('../../../dist/globalConfig').default; const RowSetProviderMock = require('./fixtures/RowSetProviderMock'); +const DBSQLClient = require('../../../dist/DBSQLClient').default; const sampleThriftSchema = { columns: [ @@ -96,19 +96,14 @@ const sampleExpiredRowSet = { }; describe('CloudFetchResultHandler', () => { - let savedConcurrentDownloads; - - beforeEach(() => { - savedConcurrentDownloads = globalConfig.cloudFetchConcurrentDownloads; - }); - - afterEach(() => { - globalConfig.cloudFetchConcurrentDownloads = savedConcurrentDownloads; - }); - it('should report pending data if there are any', async () => { - const context = {}; const rowSetProvider = new RowSetProviderMock(); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; + const result = new CloudFetchResultHandler(context, rowSetProvider, sampleThriftSchema); case1: { @@ -131,10 +126,13 @@ describe('CloudFetchResultHandler', () => { }); it('should extract links from row sets', async () => { - globalConfig.cloudFetchConcurrentDownloads = 0; // this will prevent it from downloading batches + const clientConfig = DBSQLClient.getDefaultConfig(); + clientConfig.cloudFetchConcurrentDownloads = 0; // this will prevent it from downloading batches - const context = {}; const rowSetProvider = new RowSetProviderMock(); + const context = { + getConfig: () => clientConfig, + }; const result = new CloudFetchResultHandler(context, rowSetProvider, sampleThriftSchema); @@ -162,15 +160,18 @@ describe('CloudFetchResultHandler', () => { }); it('should download batches according to settings', async () => { - globalConfig.cloudFetchConcurrentDownloads = 2; + const clientConfig = DBSQLClient.getDefaultConfig(); + clientConfig.cloudFetchConcurrentDownloads = 2; - const context = {}; const rowSet = { startRowOffset: 0, resultLinks: [...sampleRowSet1.resultLinks, ...sampleRowSet2.resultLinks], }; const expectedLinksCount = rowSet.resultLinks.length; const rowSetProvider = new RowSetProviderMock([rowSet]); + const context = { + getConfig: () => clientConfig, + }; const result = new CloudFetchResultHandler(context, rowSetProvider, sampleThriftSchema); @@ -190,9 +191,9 @@ describe('CloudFetchResultHandler', () => { expect(items.length).to.be.gt(0); expect(await rowSetProvider.hasMore()).to.be.false; - expect(result.fetch.callCount).to.be.equal(globalConfig.cloudFetchConcurrentDownloads); - expect(result.pendingLinks.length).to.be.equal(expectedLinksCount - globalConfig.cloudFetchConcurrentDownloads); - expect(result.downloadedBatches.length).to.be.equal(globalConfig.cloudFetchConcurrentDownloads - 1); + expect(result.fetch.callCount).to.be.equal(clientConfig.cloudFetchConcurrentDownloads); + expect(result.pendingLinks.length).to.be.equal(expectedLinksCount - clientConfig.cloudFetchConcurrentDownloads); + expect(result.downloadedBatches.length).to.be.equal(clientConfig.cloudFetchConcurrentDownloads - 1); } secondFetch: { @@ -201,9 +202,9 @@ describe('CloudFetchResultHandler', () => { expect(items.length).to.be.gt(0); expect(await rowSetProvider.hasMore()).to.be.false; - expect(result.fetch.callCount).to.be.equal(globalConfig.cloudFetchConcurrentDownloads); // no new fetches - expect(result.pendingLinks.length).to.be.equal(expectedLinksCount - globalConfig.cloudFetchConcurrentDownloads); - expect(result.downloadedBatches.length).to.be.equal(globalConfig.cloudFetchConcurrentDownloads - 2); + expect(result.fetch.callCount).to.be.equal(clientConfig.cloudFetchConcurrentDownloads); // no new fetches + expect(result.pendingLinks.length).to.be.equal(expectedLinksCount - clientConfig.cloudFetchConcurrentDownloads); + expect(result.downloadedBatches.length).to.be.equal(clientConfig.cloudFetchConcurrentDownloads - 2); } thirdFetch: { @@ -212,19 +213,22 @@ describe('CloudFetchResultHandler', () => { expect(items.length).to.be.gt(0); expect(await rowSetProvider.hasMore()).to.be.false; - expect(result.fetch.callCount).to.be.equal(globalConfig.cloudFetchConcurrentDownloads * 2); + expect(result.fetch.callCount).to.be.equal(clientConfig.cloudFetchConcurrentDownloads * 2); expect(result.pendingLinks.length).to.be.equal( - expectedLinksCount - globalConfig.cloudFetchConcurrentDownloads * 2, + expectedLinksCount - clientConfig.cloudFetchConcurrentDownloads * 2, ); - expect(result.downloadedBatches.length).to.be.equal(globalConfig.cloudFetchConcurrentDownloads - 1); + expect(result.downloadedBatches.length).to.be.equal(clientConfig.cloudFetchConcurrentDownloads - 1); } }); it('should handle HTTP errors', async () => { - globalConfig.cloudFetchConcurrentDownloads = 1; + const clientConfig = DBSQLClient.getDefaultConfig(); + clientConfig.cloudFetchConcurrentDownloads = 1; - const context = {}; const rowSetProvider = new RowSetProviderMock([sampleRowSet1]); + const context = { + getConfig: () => clientConfig, + }; const result = new CloudFetchResultHandler(context, rowSetProvider, sampleThriftSchema); @@ -250,8 +254,12 @@ describe('CloudFetchResultHandler', () => { }); it('should handle expired links', async () => { - const context = {}; const rowSetProvider = new RowSetProviderMock([sampleExpiredRowSet]); + const clientConfig = DBSQLClient.getDefaultConfig(); + + const context = { + getConfig: () => clientConfig, + }; const result = new CloudFetchResultHandler(context, rowSetProvider, sampleThriftSchema);