-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SNOW-1801434 add GUID to request in node.js driver (#965)
- Loading branch information
1 parent
bf3b4c2
commit 6d27e7d
Showing
8 changed files
with
297 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
exports.paramsNames = {}; | ||
exports.paramsNames.SF_REQUEST_GUID = 'request_guid'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* Copyright (c) 2015-2024 Snowflake Computing Inc. All rights reserved. | ||
*/ | ||
|
||
const testUtil = require('./testUtil'); | ||
const assert = require('assert'); | ||
const httpInterceptorUtils = require('./test_utils/httpInterceptorUtils'); | ||
const Core = require('../../lib/core'); | ||
const Util = require('../../lib/util'); | ||
|
||
describe('SF service tests', function () { | ||
const selectPiTxt = 'select PI();'; | ||
let interceptors; | ||
let coreInstance; | ||
|
||
before(async function () { | ||
interceptors = new httpInterceptorUtils.Interceptors(); | ||
const HttpClientClassWithInterceptors = httpInterceptorUtils.getHttpClientWithInterceptorsClass(interceptors); | ||
coreInstance = Core({ | ||
httpClientClass: HttpClientClassWithInterceptors, | ||
loggerClass: require('./../../lib/logger/node'), | ||
client: { | ||
version: Util.driverVersion, | ||
name: Util.driverName, | ||
environment: process.versions, | ||
}, | ||
}); | ||
}); | ||
|
||
it('GUID called for all', async function () { | ||
let guidAddedWhenExpected = true; | ||
let totalCallsWithGUIDCount = 0; | ||
let expectedCallsWithGUIDCount = 0; | ||
const pathsExpectedToIncludeGuid = [ | ||
'session?delete=true&requestId', | ||
'queries/v1/query-request', | ||
'session/v1/login-request' | ||
]; | ||
|
||
function countCallsWithGuid(requestOptions) { | ||
pathsExpectedToIncludeGuid.forEach((value) => { | ||
if (requestOptions.url.includes(value)) { | ||
// Counting is done instead of assertions, because we do not want to interrupt | ||
// the flow of operations inside the HttpClient. Retries and other custom exception handling could be triggered. | ||
if (!testUtil.isGuidInRequestOptions(requestOptions)) { | ||
guidAddedWhenExpected = false; | ||
} | ||
expectedCallsWithGUIDCount++; | ||
} | ||
}); | ||
|
||
if (testUtil.isGuidInRequestOptions(requestOptions)) { | ||
totalCallsWithGUIDCount++; | ||
} | ||
} | ||
interceptors.add('request', httpInterceptorUtils.HOOK_TYPE.FOR_ARGS, countCallsWithGuid); | ||
|
||
const connection = testUtil.createConnection({}, coreInstance); | ||
await testUtil.connectAsync(connection); | ||
await testUtil.executeCmdAsync(connection, selectPiTxt); | ||
|
||
const guidCallsOccurred = totalCallsWithGUIDCount > 0; | ||
assert.strictEqual(guidCallsOccurred, true, 'No GUID calls occurred'); | ||
assert.strictEqual(guidAddedWhenExpected, true, `GUID not found in all requests with paths: ${pathsExpectedToIncludeGuid}`); | ||
assert.strictEqual(expectedCallsWithGUIDCount === totalCallsWithGUIDCount, true, `GUID was added to requests not included in the expected paths: ${pathsExpectedToIncludeGuid}.` + | ||
`Total calls with guid: ${totalCallsWithGUIDCount}. Expected calls with guid: ${expectedCallsWithGUIDCount}.` | ||
); | ||
|
||
await testUtil.destroyConnectionAsync(connection); | ||
interceptors.clear(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
const Logger = require('../../../lib/logger'); | ||
const { NodeHttpClient } = require('../../../lib/http/node'); | ||
const Util = require('../../../lib/util'); | ||
|
||
const HOOK_TYPE = { | ||
FOR_ARGS: 'args', | ||
FOR_RETURNED_VALUE: 'returned', | ||
}; | ||
|
||
module.exports.HOOK_TYPE = HOOK_TYPE; | ||
|
||
class Interceptor { | ||
constructor(methodName, hookType, callback) { | ||
this.methodName = methodName; | ||
this.hookType = hookType || HOOK_TYPE.FOR_ARGS; | ||
this.callback = callback; | ||
} | ||
|
||
execute(...args) { | ||
this.callback(...args); | ||
} | ||
} | ||
|
||
class Interceptors { | ||
constructor(initialInterceptors) { | ||
this.interceptorsMap = this.createInterceptorsMap(initialInterceptors); | ||
} | ||
|
||
add(methodName, hookType, callback, interceptor = undefined) { | ||
if (!interceptor) { | ||
interceptor = new Interceptor(methodName, hookType, callback); | ||
} | ||
this.interceptorsMap[interceptor.methodName][interceptor.hookType] = interceptor; | ||
} | ||
|
||
get(methodName, hookType) { | ||
return this.interceptorsMap[methodName][hookType]; | ||
} | ||
|
||
intercept(methodName, hookType, ...args) { | ||
// When no interceptor registered - ignores and does not raise any error | ||
try { | ||
return this.get(methodName, hookType)?.execute(...args); | ||
} catch (e) { | ||
throw 'Unable to execute interceptor method in tests. Error: ' + e; | ||
} | ||
} | ||
|
||
clear() { | ||
this.interceptorsMap = this.createInterceptorsMap(); | ||
} | ||
|
||
createInterceptorsMap(initialInterceptors = {}) { | ||
if (initialInterceptors instanceof Interceptors) { | ||
return initialInterceptors.interceptorsMap; | ||
} | ||
// Map creating another map for each accessed key not present in the map | ||
// (analogy - DefaultDict from Python). | ||
return new Proxy(initialInterceptors, { | ||
get: (target, prop) => { | ||
if (prop in target) { | ||
return target[prop]; | ||
} else { | ||
// Create an empty object, store it in target, and return it | ||
const newObj = {}; | ||
target[prop] = newObj; | ||
return newObj; | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
module.exports.Interceptors = Interceptors; | ||
|
||
function HttpClientWithInterceptors(connectionConfig, initialInterceptors) { | ||
Logger.getInstance().trace('Initializing HttpClientWithInterceptors with Connection Config[%s]', | ||
connectionConfig.describeIdentityAttributes()); | ||
this.interceptors = new Interceptors(initialInterceptors); | ||
NodeHttpClient.apply(this, [connectionConfig]); | ||
} | ||
|
||
Util.inherits(HttpClientWithInterceptors, NodeHttpClient); | ||
|
||
|
||
HttpClientWithInterceptors.prototype.requestAsync = async function (url, options) { | ||
this.interceptors.intercept('requestAsync', HOOK_TYPE.FOR_ARGS, url, options); | ||
const response = await NodeHttpClient.prototype.requestAsync.call(this, url, options); | ||
this.interceptors.intercept('requestAsync', HOOK_TYPE.FOR_RETURNED_VALUE, response); | ||
return response; | ||
}; | ||
|
||
HttpClientWithInterceptors.prototype.request = function (url, options) { | ||
this.interceptors.intercept('request', HOOK_TYPE.FOR_ARGS, url, options); | ||
const response = NodeHttpClient.prototype.request.call(this, url, options); | ||
this.interceptors.intercept('request', HOOK_TYPE.FOR_RETURNED_VALUE, response); | ||
return response; | ||
}; | ||
|
||
// Factory method for HttpClientWithInterceptors to be able to partially initialize class | ||
// with interceptors used in fully instantiated object. | ||
function getHttpClientWithInterceptorsClass(interceptors) { | ||
function HttpClientWithInterceptorsWrapper(connectionConfig) { | ||
HttpClientWithInterceptors.apply(this, [connectionConfig, interceptors]); | ||
} | ||
Util.inherits(HttpClientWithInterceptorsWrapper, HttpClientWithInterceptors); | ||
|
||
return HttpClientWithInterceptorsWrapper; | ||
} | ||
|
||
|
||
module.exports.getHttpClientWithInterceptorsClass = getHttpClientWithInterceptorsClass; |
Oops, something went wrong.