Repository: snowflakedb/snowflake-connector-nodejs
Pull request #1077: SNOW-2111082 Add sinon for mocking dependencies 
Author: login: sfc-gh-rsavenok
Created: 2025-05-14T13:48:25Z | Merged: 2025-05-23T07:44:03Z

### Description
Introduce sinon for mocking dependencies for 2 reasons:
* cleaner api
* We don't need to have function arguments only to be able to mock dependencies in tests

#### Security review
Resulted in LOW https://github.com/snowflakedb/security_reviews/pull/6847

#### Why sinon and not other library?
Sinon is a go-to solution when working with Mocha, it has 7+ million weekly downloads (other libraries has 10x less downloads). It has a rich api allowing to mock both npm dependencies and local dependencies and control the mock lifecycle.

#### Sinon migration
This PR doesn't update every file, because not all of them are straightforward; Would suggest to work on following things only when somebody would need to touch related code:
* s3 - sinon with cjs struggles to mock them because of locked objects. This is fixable with aws-sdk-mock package or typescript migration
* mock_file.js - the whole util needs to be refactored for sinon
* encrypt_util.js - temp file creation dependency needs to be refactored

### North star is to get rid `mock-require` completely in favor of sinon and maybe use proxyrequire (for rare cases where sinon wouldn't be enough)
`mock-require` doesn't do anything for us already. Since we pass dependencies as function arguments, we can just pass plain objects/functions there, but it's useful to keep in the code to find places that need to be migrated to sinon :P 

### Checklist
- [x] Format code according to the existing code style (run `npm run lint:check -- CHANGED_FILES` and fix problems in changed code)
- [x] Create tests which fail without the change (if possible)
- [x] Make all tests (unit and integration) pass (`npm run test:unit` and `npm run test:integration`)
- [x] Extend the types in index.d.ts file (if necessary)
- [x] Extend the README / documentation and ensure is properly displayed (if necessary)
- [x] Provide JIRA issue id (if possible) or GitHub issue id in commit message



Diff:
diff --git a/lib/authentication/auth_keypair.js b/lib/authentication/auth_keypair.js
index 15171addb..54f5a1096 100644
--- a/lib/authentication/auth_keypair.js
+++ b/lib/authentication/auth_keypair.js
@@ -1,22 +1,17 @@
+const crypto = require('crypto');
+const jwt = require('jsonwebtoken');
+const fs = require('fs');
 const util = require('../util');
 
 /**
  * Creates a key-pair authenticator.
  *
- * @param {String} privateKey
- * @param {String} privateKeyPath
- * @param {String} privateKeyPass
- * @param {module} cryptomod
- * @param {module} jwtmod
- * @param {module} filesystem
+ * @param {Object} connectionConfig
  *
  * @returns {Object}
  * @constructor
  */
-function AuthKeypair(connectionConfig, cryptomod, jwtmod, filesystem) {
-  const crypto = typeof cryptomod !== 'undefined' ? cryptomod : require('crypto');
-  const jwt = typeof jwtmod !== 'undefined' ? jwtmod : require('jsonwebtoken');
-  const fs = typeof filesystem !== 'undefined' ? filesystem : require('fs');
+function AuthKeypair(connectionConfig) {
   let privateKey = connectionConfig.getPrivateKey();
   const privateKeyPath = connectionConfig.getPrivateKeyPath();
   const privateKeyPass =  connectionConfig.getPrivateKeyPass();
diff --git a/lib/file_transfer_agent/azure_util.js b/lib/file_transfer_agent/azure_util.js
index 02de68353..fa0af6d5d 100644
--- a/lib/file_transfer_agent/azure_util.js
+++ b/lib/file_transfer_agent/azure_util.js
@@ -1,3 +1,5 @@
+const AZURE = require('@azure/storage-blob');
+const fs = require('fs');
 const EncryptionMetadata = require('./encrypt_util').EncryptionMetadata;
 const FileHeader = require('../file_util').FileHeader;
 const expandTilde = require('expand-tilde');
@@ -25,10 +27,7 @@ function AzureLocation(containerName, path) {
  * @returns {Object}
  * @constructor
  */
-function AzureUtil(connectionConfig, azure, filestream) {
-  const AZURE = typeof azure !== 'undefined' ? azure : require('@azure/storage-blob');
-  const fs = typeof filestream !== 'undefined' ? filestream : require('fs');
-
+function AzureUtil(connectionConfig) {
   /**
   * Create a blob service client using an Azure SAS token.
   *
diff --git a/lib/file_transfer_agent/gcs_util.js b/lib/file_transfer_agent/gcs_util.js
index ab64ebe8c..d266b687a 100644
--- a/lib/file_transfer_agent/gcs_util.js
+++ b/lib/file_transfer_agent/gcs_util.js
@@ -1,3 +1,4 @@
+const fs = require('fs');
 const EncryptionMetadata = require('./encrypt_util').EncryptionMetadata;
 const FileHeader = require('../file_util').FileHeader;
 const getProxyAgent = require('../http/node').getProxyAgent;
@@ -25,10 +26,10 @@ const EXPIRED_TOKEN = 'ExpiredToken';
 
 const ERRORNO_WSAECONNABORTED = 10053;  // network connection was aborted
 
-/** 
+/**
  * @typedef {object} GCSLocation
  * @property {string} bucketName
- * @property {string} path 
+ * @property {string} path
  */
 function GCSLocation(bucketName, path) {
   return {
@@ -46,9 +47,8 @@ function GCSLocation(bucketName, path) {
  * @returns {Object}
  * @constructor
  */
-function GCSUtil(connectionConfig, httpClient, fileStream) {
+function GCSUtil(connectionConfig, httpClient) {
   let axios = httpClient;
-  const fs = typeof fileStream !== 'undefined' ? fileStream : require('fs');
   let isProxyEnabled = false;
 
   /**
diff --git a/package.json b/package.json
index 107bb6305..e0b4b1909 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
     "mocha": "^11.1.0",
     "mock-require": "^3.0.3",
     "nyc": "^15.1.0",
+    "sinon": "^20.0.0",
     "test-console": "^2.0.0",
     "wiremock": "^3.10.0",
     "wiremock-rest-client": "^1.11.0"
diff --git a/test/unit/authentication/authentication_test.js b/test/unit/authentication/authentication_test.js
index 7a1ae9a58..7abae74b2 100644
--- a/test/unit/authentication/authentication_test.js
+++ b/test/unit/authentication/authentication_test.js
@@ -1,6 +1,9 @@
 const assert = require('assert');
-const mock = require('mock-require');
 const net = require('net');
+const crypto = require('crypto');
+const jsonwebtoken = require('jsonwebtoken');
+const fs = require('fs');
+const sinon = require('sinon');
 
 const authenticator = require('./../../../lib/authentication/authentication');
 const AuthDefault = require('./../../../lib/authentication/auth_default');
@@ -103,7 +106,6 @@ describe('default authentication', function () {
 });
 
 describe('external browser authentication', function () {
-  let webbrowser;
   let browserRedirectPort;
   let httpclient;
 
@@ -121,39 +123,40 @@ describe('external browser authentication', function () {
     getDisableConsoleLogin: () => true,
     host: 'fakehost'
   };
+  const browserOpenCallback = () => {
+    const client = net.createConnection({ port: browserRedirectPort }, () => {
+      client.write(`GET /?token=${mockToken} HTTP/1.1\r\n`);
+    });
+  };
+  const httpResponseStub = sinon.stub();
 
   before(function () {
-    mock('webbrowser', {
-      open: function () {
-        const client = net.createConnection({ port: browserRedirectPort }, () => {
-          client.write(`GET /?token=${mockToken} HTTP/1.1\r\n`);
-        });
-        return;
-      }
-    });
-    mock('httpclient', {
+    httpclient = {
       requestAsync: async function (options) {
-        const data =
-          {
-            data: {
-              data:
-                {
-                  ssoUrl: mockSsoURL,
-                  proofKey: mockProofKey
-                }
-            }
-          };
+        const response = {
+          data: {
+            data: httpResponseStub()
+          }
+        };
         browserRedirectPort = options.data['data']['BROWSER_MODE_REDIRECT_PORT'];
-        return data;
+        return response;
       }
+    };
+  });
+
+  beforeEach(() => {
+    httpResponseStub.returns({
+      ssoUrl: mockSsoURL,
+      proofKey: mockProofKey
     });
+  });
 
-    webbrowser = require('webbrowser');
-    httpclient = require('httpclient');
+  afterEach(() => {
+    httpResponseStub.reset();
   });
 
   it('external browser - authenticate method is thenable', done => {
-    const auth = new AuthWeb(connectionConfig, httpclient, webbrowser.open);
+    const auth = new AuthWeb(connectionConfig, httpclient, browserOpenCallback);
 
     auth.authenticate(credentials.authenticator, '', credentials.account, credentials.username)
       .then(done)
@@ -161,7 +164,7 @@ describe('external browser authentication', function () {
   });
 
   it('external browser - get success', async function () {
-    const auth = new AuthWeb(connectionConfig, httpclient, webbrowser.open);
+    const auth = new AuthWeb(connectionConfig, httpclient, browserOpenCallback);
     await auth.authenticate(credentials.authenticator, '', credentials.account, credentials.username);
 
     const body = { data: {} };
@@ -172,31 +175,7 @@ describe('external browser authentication', function () {
   });
 
   it('external browser - get fail', async function () {
-    mock('webbrowser', {
-      open: function () {
-        return;
-      }
-    });
-
-    mock('httpclient', {
-      requestAsync: async function (options) {
-        const data =
-          {
-            data: {
-              data:
-                {
-                  ssoUrl: mockSsoURL
-                }
-            }
-          };
-        browserRedirectPort = options.data['data']['BROWSER_MODE_REDIRECT_PORT'];
-        return data;
-      }
-    });
-
-    webbrowser = require('webbrowser');
-    httpclient = require('httpclient');
-
+    httpResponseStub.returns({ ssoUrl: mockSsoURL });
     const fastFailConnectionConfig = {
       getBrowserActionTimeout: () => 10,
       getProxy: () => {},
@@ -206,7 +185,7 @@ describe('external browser authentication', function () {
       host: 'fakehost'
     };
 
-    const auth = new AuthWeb(fastFailConnectionConfig, httpclient, webbrowser.open);
+    const auth = new AuthWeb(fastFailConnectionConfig, httpclient, () => null);
     await assert.rejects(async () => {
       await auth.authenticate(credentials.authenticator, '', credentials.account, credentials.username);
     }, {
@@ -236,7 +215,7 @@ describe('external browser authentication', function () {
   });
 
   it('external browser - id token, webbrowser cb provided', async function () {
-    const auth = new AuthIDToken(connectionOptionsIdToken, httpclient, webbrowser.open);
+    const auth = new AuthIDToken(connectionOptionsIdToken, httpclient, browserOpenCallback);
     await auth.authenticate(credentials.authenticator, '', credentials.account, credentials.username, credentials.host);
 
     const body = { data: {} };
@@ -248,84 +227,52 @@ describe('external browser authentication', function () {
 });
 
 describe('key-pair authentication', function () {
-  let cryptomod;
-  let jwtmod;
-  let filesystem;
+  let sinonSandbox;
 
   const mockToken = 'mockToken';
   const mockPrivateKeyFile = 'mockPrivateKeyFile';
   const mockPublicKeyObj = 'mockPublicKeyObj';
 
   before(function () {
-    mock('cryptomod', {
-      createPrivateKey: function (options) {
-        assert.strictEqual(options.key, mockPrivateKeyFile);
-
-        if (options.passphrase) {
-          assert.strictEqual(options.passphrase, connectionOptionsKeyPairPath.getPrivateKeyPass());
-        }
-
-        function privKeyObject() {
-          this.export = function () {
-            return connectionOptionsKeyPair.getPrivateKey();
-          };
-        }
-
-        return new privKeyObject;
-      },
-      createPublicKey: function (options) {
-        assert.strictEqual(options.key, connectionOptionsKeyPair.getPrivateKey());
-
-        function pubKeyObject() {
-          this.export = function () {
-            return mockPublicKeyObj;
-          };
-        }
-
-        return new pubKeyObject;
-      },
-      createHash: function () {
-        function createHash() {
-          this.update = function (publicKeyObj) {
-            function update() {
-              assert.strictEqual(publicKeyObj, mockPublicKeyObj);
-              this.digest = function () {};
-            }
-            return new update;
-          };
-        }
-        return new createHash;
+    sinonSandbox = sinon.createSandbox();
+    sinonSandbox.stub(crypto, 'createPrivateKey').callsFake((options) => {
+      assert.strictEqual(options.key, mockPrivateKeyFile);
+      if (options.passphrase) {
+        assert.strictEqual(options.passphrase, connectionOptionsKeyPairPath.getPrivateKeyPass());
       }
+      return {
+        export: () =>  connectionOptionsKeyPair.getPrivateKey()
+      };
     });
-    mock('jwtmod', {
-      sign: function () {
-        return mockToken;
-      }
+    sinonSandbox.stub(crypto, 'createPublicKey').callsFake((options) => {
+      assert.strictEqual(options.key, connectionOptionsKeyPair.getPrivateKey());
+      return {
+        export: () => mockPublicKeyObj
+      };
     });
-    mock('filesystem', {
-      readFileSync: function () {
-        return mockPrivateKeyFile;
+    sinonSandbox.stub(crypto, 'createHash').returns({
+      update: (publicKeyObj) => {
+        assert.strictEqual(publicKeyObj, mockPublicKeyObj);
+        return { digest: () => {} };
       }
     });
+    sinonSandbox.stub(jsonwebtoken, 'sign').returns(mockToken);
+    sinonSandbox.stub(fs, 'readFileSync').returns(mockPrivateKeyFile);
+  });
 
-    cryptomod = require('cryptomod');
-    jwtmod = require('jwtmod');
-    filesystem = require('filesystem');
+  after(() => {
+    sinonSandbox.restore();
   });
 
   it('key-pair - authenticate method is thenable', done => {
-    const auth = new AuthKeypair(connectionOptionsKeyPair,
-      cryptomod, jwtmod, filesystem);
-
+    const auth = new AuthKeypair(connectionOptionsKeyPair);
     auth.authenticate(connectionOptionsKeyPair.authenticator, '', connectionOptionsKeyPair.account, connectionOptionsKeyPair.username)
       .then(done)
       .catch(done);
   });
 
   it('key-pair - get token with private key', function () {
-    const auth = new AuthKeypair(connectionOptionsKeyPair,
-      cryptomod, jwtmod, filesystem);
-
+    const auth = new AuthKeypair(connectionOptionsKeyPair);
     auth.authenticate(connectionOptionsKeyPair.authenticator, '', connectionOptionsKeyPair.account, connectionOptionsKeyPair.username);
 
     const body = { data: {} };
@@ -336,8 +283,7 @@ describe('key-pair authentication', function () {
   });
 
   it('key-pair - get token with private key by reauthentication', async function () {
-    const auth = new AuthKeypair(connectionOptionsKeyPair,
-      cryptomod, jwtmod, filesystem);
+    const auth = new AuthKeypair(connectionOptionsKeyPair);
 
     const body = { data: { 'TOKEN': 'wrongToken' } };
     await auth.reauthenticate(body);
@@ -347,8 +293,7 @@ describe('key-pair authentication', function () {
   });
 
   it('key-pair - get token with private key path with passphrase', function () {
-    const auth = new AuthKeypair(connectionOptionsKeyPairPath,
-      cryptomod, jwtmod, filesystem);
+    const auth = new AuthKeypair(connectionOptionsKeyPairPath);
 
     auth.authenticate(connectionOptionsKeyPairPath.authenticator, '',
       connectionOptionsKeyPairPath.account,
@@ -362,8 +307,7 @@ describe('key-pair authentication', function () {
   });
 
   it('key-pair - get token with private key path without passphrase', function () {
-    const auth = new AuthKeypair(connectionOptionsKeyPairPath,
-      cryptomod, jwtmod, filesystem);
+    const auth = new AuthKeypair(connectionOptionsKeyPairPath);
 
     auth.authenticate(connectionOptionsKeyPairPath.authenticator, '',
       connectionOptionsKeyPairPath.account,
@@ -428,7 +372,7 @@ describe('okta authentication', function () {
   const mockSamlResponse = '<form action="https://' + connectionOptionsOkta.account + '.snowflakecomputing.com/fed/login">';
 
   before(function () {
-    mock('httpclient', {
+    httpclient = {
       post: async function (url) {
         let json;
         if (url.startsWith('https://' + connectionOptionsOkta.account)) {
@@ -458,9 +402,7 @@ describe('okta authentication', function () {
         };
         return json;
       }
-    });
-
-    httpclient = require('httpclient');
+    };
   });
 
   it('okta - authenticate method is thenable', done => {
@@ -486,10 +428,10 @@ describe('okta authentication', function () {
   it('okta - reauthenticate', async function () {
     const auth = authenticator.getAuthenticator(connectionOptionsOkta, httpclient);
     await auth.authenticate(connectionOptionsOkta.authenticator, '', connectionOptionsOkta.account, connectionOptionsOkta.username);
-    const body = { 
+    const body = {
       data: {
         RAW_SAML_RESPONSE: 'WRONG SAML'
-      } 
+      }
     };
 
     auth.reauthenticate(body, {
@@ -504,7 +446,7 @@ describe('okta authentication', function () {
   it('okta - reauthentication timeout error', async function () {
     const auth = authenticator.getAuthenticator(connectionOptionsOkta, httpclient);
     await auth.authenticate(connectionOptionsOkta.authenticator, '', connectionOptionsOkta.account, connectionOptionsOkta.username);
-    
+
     try {
       await auth.reauthenticate({ data: { RAW_SAML_RESPONSE: 'token' } }, { numRetries: 5, totalElapsedTime: 350 });
       assert.fail();
@@ -516,7 +458,7 @@ describe('okta authentication', function () {
   it('okta - reauthentication max retry error', async function () {
     const auth = authenticator.getAuthenticator(connectionOptionsOkta, httpclient);
     await auth.authenticate(connectionOptionsOkta.authenticator, '', connectionOptionsOkta.account, connectionOptionsOkta.username);
-    
+
     try {
       await auth.reauthenticate({ data: { RAW_SAML_RESPONSE: 'token' } }, { numRetries: 9, totalElapsedTime: 280 });
       assert.fail();
@@ -526,7 +468,7 @@ describe('okta authentication', function () {
   });
 
   it('okta - SAML response fail prefix', async function () {
-    mock('httpclient', {
+    httpclient = {
       post: async function (url) {
         let json;
         if (url.startsWith('https://' + connectionOptionsOkta.account)) {
@@ -544,10 +486,7 @@ describe('okta authentication', function () {
         }
         return json;
       }
-    });
-
-    httpclient = require('httpclient');
-
+    };
     const auth = new AuthOkta(connectionOptionsOkta, httpclient);
 
     try {
@@ -558,7 +497,7 @@ describe('okta authentication', function () {
   });
 
   it('okta - SAML response fail postback', async function () {
-    mock('httpclient', {
+    httpclient = {
       post: async function (url) {
         let json;
         if (url.startsWith('https://' + connectionOptionsOkta.account)) {
@@ -589,10 +528,7 @@ describe('okta authentication', function () {
         };
         return json;
       }
-    });
-
-    httpclient = require('httpclient');
-
+    };
     const auth = new AuthOkta(connectionOptionsOkta, httpclient);
 
     try {
@@ -699,4 +635,4 @@ describe('okta authentication', function () {
       });
     });
   });
-});
\ No newline at end of file
+});
diff --git a/test/unit/file_transfer_agent/azure_test.js b/test/unit/file_transfer_agent/azure_test.js
index f15a4cd5b..5891055b1 100644
--- a/test/unit/file_transfer_agent/azure_test.js
+++ b/test/unit/file_transfer_agent/azure_test.js
@@ -1,5 +1,7 @@
 const assert = require('assert');
-const mock = require('mock-require');
+const AZURE = require('@azure/storage-blob');
+const fs = require('fs');
+const sinon = require('sinon');
 const SnowflakeAzureUtil = require('./../../../lib/file_transfer_agent/azure_util');
 const resultStatus = require('../../../lib/file_util').resultStatus;
 
@@ -21,8 +23,6 @@ describe('Azure client', function () {
 
 
   let Azure = null;
-  let client = null;
-  let filestream = null;
   const dataFile = mockDataFile;
   const meta = {
     stageInfo: {
@@ -38,54 +38,9 @@ describe('Azure client', function () {
     matDesc: mockMatDesc
   };
 
-  function getClientMock(getPropertiesFn, uploadFn) {
-    if (getPropertiesFn !== null) {
-      return {
-        BlobServiceClient: function () {
-          function BlobServiceClient() {
-            this.getContainerClient = function () {
-              function getContainerClient() {
-                this.getBlobClient = function () {
-                  function getBlobClient() {
-                    this.getProperties = function () {
-                      function getProperties() { 
-                        this.then = getPropertiesFn;
-                      }
-                      return new getProperties;
-                    };
-                  }
-                  return new getBlobClient;
-                };
-              }
-              return new getContainerClient;
-            };
-          }
-          return new BlobServiceClient;
-        }
-      };
-    }
-
-    if (uploadFn !== null) {
-      return {
-        BlobServiceClient: function () {
-          function BlobServiceClient() {
-            this.getContainerClient = function () {
-              function getContainerClient() {
-                this.getBlockBlobClient = function () {
-                  function getBlockBlobClient() {
-                    this.upload = uploadFn;
-                  }
-                  return new getBlockBlobClient;
-                };
-              }
-              return new getContainerClient;
-            };
-          }
-          return new BlobServiceClient;
-        }
-      };
-    }
-  }
+  let sinonSandbox;
+  const getPropertiesStub = sinon.stub();
+  const uploadStub = sinon.stub();
 
   function verifyNameAndPath(bucketPath, containerName, path) {
     const result = Azure.extractContainerNameAndPath(bucketPath);
@@ -94,22 +49,28 @@ describe('Azure client', function () {
   }
 
   before(function () {
-    mock('client', getClientMock(
-      function (callback) {
-        callback({
-          metadata: {}
-        });
-      }));
-
-    mock('filestream', {
-      readFileSync: async function (data) {
-        return data;
-      }
+    sinonSandbox = sinon.createSandbox();
+    sinonSandbox.stub(AZURE, 'BlobServiceClient').returns({
+      getContainerClient: () => ({
+        getBlobClient: () => ({
+          getProperties: getPropertiesStub
+        }),
+        getBlockBlobClient: () => ({
+          upload: uploadStub
+        })
+      })
     });
-    
-    client = require('client');
-    filestream = require('filestream');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client, filestream);
+    sinonSandbox.stub(fs, 'readFileSync').returnsArg(0);
+    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig);
+  });
+
+  afterEach(() => {
+    getPropertiesStub.reset();
+    uploadStub.reset();
+  });
+
+  after(() => {
+    sinonSandbox.restore();
   });
 
   it('extract bucket name and path', async function () {
@@ -121,122 +82,72 @@ describe('Azure client', function () {
   });
 
   it('get file header - success', async function () {
+    getPropertiesStub.resolves({ metadata: {} });
     await Azure.getFileHeader(meta, dataFile);
     assert.strictEqual(meta['resultStatus'], resultStatus.UPLOADED);
   });
 
   it('get file header - fail expired token', async function () {
-    mock('client', getClientMock(
-      function () {
-        const err = new Error();
-        err.code = 'ExpiredToken';
-        throw err;
-      }, null));
-
-    client = require('client');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client);
-
+    getPropertiesStub.throws(() => {
+      const err = new Error();
+      err.code = 'ExpiredToken';
+      throw err;
+    });
     await Azure.getFileHeader(meta, dataFile);
     assert.strictEqual(meta['resultStatus'], resultStatus.RENEW_TOKEN);
   });
 
-  it('get file header - fail HTTP 400', async function () {
-    mock('client', getClientMock(
-      function () {
-        const err = new Error();
-        err.statusCode = 404;
-        throw err;
-      }, null));
-
-    client = require('client');
-    const Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client);
-
+  it('get file header - fail HTTP 404', async function () {
+    getPropertiesStub.throws(() => {
+      const err = new Error();
+      err.statusCode = 404;
+      return err;
+    });
     await Azure.getFileHeader(meta, dataFile);
     assert.strictEqual(meta['resultStatus'], resultStatus.NOT_FOUND_FILE);
   });
 
   it('get file header - fail HTTP 400', async function () {
-    mock('client', getClientMock(
-      function () {
-        const err = new Error();
-        err.statusCode = 400;
-        throw err;
-      }, null));
-
-    client = require('client');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client);
-
+    getPropertiesStub.throws(() => {
+      const err = new Error();
+      err.statusCode = 400;
+      return err;
+    });
     await Azure.getFileHeader(meta, dataFile);
     assert.strictEqual(meta['resultStatus'], resultStatus.RENEW_TOKEN);
   });
 
-  it('get file header - fail unknown', async function () { 
-    mock('client', getClientMock(
-      function () {
-        const err = new Error();
-        err.code = 'unknown';
-        throw err;
-      }, null));
-
-    client = require('client');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client);
-
+  it('get file header - fail unknown', async function () {
+    getPropertiesStub.throws(() => {
+      const err = new Error();
+      err.code = 'unknown';
+      return err;
+    });
     await Azure.getFileHeader(meta, dataFile);
     assert.strictEqual(meta['resultStatus'], resultStatus.ERROR);
   });
 
   it('upload - success', async function () {
-    mock('client', getClientMock (
-      null,
-      function () {
-        function upload() {}
-        return new upload;
-      }));
-    
-    client = require('client');
-    filestream = require('filestream');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client, filestream);
-
     await Azure.uploadFile(dataFile, meta, encryptionMetadata);
     assert.strictEqual(meta['resultStatus'], resultStatus.UPLOADED);
   });
 
   it('upload - fail expired token', async function () {
-    mock('client', getClientMock (
-      null,
-      function () {
-        function upload() {
-          const err = new Error('Server failed to authenticate the request.');
-          err.statusCode = 403;
-          throw err;
-        }
-        return new upload;
-      }));
-    
-    client = require('client');
-    filestream = require('filestream');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client, filestream);
-
+    uploadStub.throws(() => {
+      const err = new Error('Server failed to authenticate the request.');
+      err.statusCode = 403;
+      throw err;
+    });
     await Azure.uploadFile(dataFile, meta, encryptionMetadata);
     assert.strictEqual(meta['resultStatus'], resultStatus.RENEW_TOKEN);
   });
 
   it('upload - fail HTTP 400', async function () {
-    mock('client', getClientMock (
-      null,
-      function () {
-        function upload() {
-          const err = new Error();
-          err.statusCode = 400;
-          throw err;
-        }
-        return new upload;
-      }));
-    
-    client = require('client');
-    filestream = require('filestream');
-    Azure = new SnowflakeAzureUtil(noProxyConnectionConfig, client, filestream);
-
+    uploadStub.throws(() => {
+      const err = new Error();
+      err.statusCode = 400;
+      throw err;
+    });
     await Azure.uploadFile(dataFile, meta, encryptionMetadata);
     assert.strictEqual(meta['resultStatus'], resultStatus.NEED_RETRY);
   });
diff --git a/test/unit/file_transfer_agent/gcs_test.js b/test/unit/file_transfer_agent/gcs_test.js
index d443b5d44..3c810ea60 100644
--- a/test/unit/file_transfer_agent/gcs_test.js
+++ b/test/unit/file_transfer_agent/gcs_test.js
@@ -1,5 +1,6 @@
 const assert = require('assert');
-const mock = require('mock-require');
+const sinon = require('sinon');
+const fs = require('fs');
 const SnowflakeGCSUtil = require('./../../../lib/file_transfer_agent/gcs_util');
 const resultStatus = require('../../../lib/file_util').resultStatus;
 
@@ -23,8 +24,8 @@ describe('GCS client', function () {
   };
 
   let GCS;
+  let sinonSandbox;
   let httpClient;
-  let fileStream;
   const dataFile = mockDataFile;
   let meta;
   const encryptionMetadata = {
@@ -33,7 +34,9 @@ describe('GCS client', function () {
     matDesc: mockMatDesc
   };
 
-  this.beforeEach(function () {
+  beforeEach(() => {
+    sinonSandbox = sinon.createSandbox();
+    sinonSandbox.stub(fs, 'readFileSync').returnsArg(0);
     meta = {
       stageInfo: {
         location: mockLocation + '/' + mockTable + '/' + mockPath + '/',
@@ -45,28 +48,16 @@ describe('GCS client', function () {
       dstFileName: mockPresignedUrl,
       client: mockClient
     };
+    httpClient = {
+      put: async () => {},
+      get: async () => {},
+      head: async () => ({ headers: '' }),
+    };
+    GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
+  });
 
-    mock('httpClient', {
-      put: async function () {
-        return;
-      },
-      get: async function () {
-        return;
-      },
-      head: async function () {
-        return {
-          headers: ''
-        };
-      }
-    });
-    mock('fileStream', {
-      readFileSync: async function (data) {
-        return data;
-      }
-    });
-    httpClient = require('httpClient');
-    fileStream = require('fileStream');
-    GCS = new SnowflakeGCSUtil(connectionConfig, httpClient, fileStream);
+  afterEach(() => {
+    sinonSandbox.restore();
   });
 
   describe('GCS client endpoint testing', async function () {
@@ -234,17 +225,11 @@ describe('GCS client', function () {
   });
 
   it('get file header - fail not found file with presigned url', async function () {
-    mock('httpClient', {
-      put: async function () {
-        return;
-      },
-      get: async function () {
-        const err = new Error();
-        err.response = { status: 401 };
-        throw err;
-      }
-    });
-    const httpClient = require('httpClient');
+    httpClient.get = async () => {
+      const err = new Error();
+      err.response = { status: 401 };
+      throw err;
+    };
     const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     await GCS.getFileHeader(meta, dataFile);
@@ -252,14 +237,11 @@ describe('GCS client', function () {
   });
 
   it('get file header - fail need retry', async function () {
-    mock('httpClient', {
-      head: async function () {
-        const err = new Error();
-        err.response = { status: 403 };
-        throw err;
-      }
-    });
-    const httpClient = require('httpClient');
+    httpClient.head = async () => {
+      const err = new Error();
+      err.response = { status: 403 };
+      throw err;
+    };
     const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     meta.presignedUrl = '';
@@ -269,14 +251,11 @@ describe('GCS client', function () {
   });
 
   it('get file header - fail not found file without presigned url', async function () {
-    mock('httpClient', {
-      head: async function () {
-        const err = new Error();
-        err.response = { status: 404 };
-        throw err;
-      }
-    });
-    const httpClient = require('httpClient');
+    httpClient.head = async () => {
+      const err = new Error();
+      err.response = { status: 404 };
+      throw err;
+    };
     const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     meta.presignedUrl = '';
@@ -286,14 +265,11 @@ describe('GCS client', function () {
   });
 
   it('get file header - fail expired token', async function () {
-    mock('httpClient', {
-      head: async function () {
-        const err = new Error();
-        err.response = { status: 401 };
-        throw err;
-      }
-    });
-    const httpClient = require('httpClient');
+    httpClient.head = async () => {
+      const err = new Error();
+      err.response = { status: 401 };
+      throw err;
+    };
     const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     meta.presignedUrl = '';
@@ -303,15 +279,11 @@ describe('GCS client', function () {
   });
 
   it('get file header - fail unknown status', async function () {
-    let err;
-    mock('httpClient', {
-      head: async function () {
-        err = new Error();
-        err.response = { status: 0 };
-        throw err;
-      }
-    });
-    const httpClient = require('httpClient');
+    const err = new Error();
+    err.response = { status: 0 };
+    httpClient.head = async () => {
+      throw err;
+    };
     const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     meta.presignedUrl = '';
@@ -329,42 +301,24 @@ describe('GCS client', function () {
   });
 
   it('upload - fail need retry', async function () {
-    mock('httpClient', {
-      put: async function () {
-        const err = new Error();
-        err.code = 403;
-        throw err;
-      }
-    });
-    mock('fileStream', {
-      readFileSync: async function (data) {
-        return data;
-      }
-    });
-    httpClient = require('httpClient');
-    fileStream = require('fileStream');
-    const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient, fileStream);
+    httpClient.put = async () => {
+      const err = new Error();
+      err.code = 403;
+      throw err;
+    };
+    const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     await GCS.uploadFile(dataFile, meta, encryptionMetadata);
     assert.strictEqual(meta['resultStatus'], resultStatus.NEED_RETRY);
   });
 
   it('upload - fail renew presigned url', async function () {
-    mock('httpClient', {
-      put: async function () {
-        const err = new Error();
-        err.code = 400;
-        throw err;
-      }
-    });
-    mock('fileStream', {
-      readFileSync: async function (data) {
-        return data;
-      }
-    });
-    httpClient = require('httpClient');
-    fileStream = require('fileStream');
-    const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient, fileStream);
+    httpClient.put = async () => {
+      const err = new Error();
+      err.code = 400;
+      throw err;
+    };
+    const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     meta.client = '';
     meta.lastError = { code: 0 };
@@ -374,39 +328,23 @@ describe('GCS client', function () {
   });
 
   it('upload - fail expired token', async function () {
-    mock('httpClient', {
-      put: async function () {
-        const err = new Error();
-        err.code = 401;
-        throw err;
-      }
-    });
-    mock('fileStream', {
-      readFileSync: async function (data) {
-        return data;
-      }
-    });
-    mock('gcsClient', {
-      bucket: function () {
-        function bucket() {
-          this.file = function () {
-            function file() {
-              this.save = function () {
-                const err = new Error();
-                err.code = 401;
-                throw err;
-              };
-            }
-            return new file;
-          };
-        }
-        return new bucket;
-      }
-    });
-    httpClient = require('httpClient');
-    fileStream = require('fileStream');
-    const gcsClient = require('gcsClient');
-    const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient, fileStream);
+    httpClient.put = async () => {
+      const err = new Error();
+      err.code = 401;
+      throw err;
+    };
+    const gcsClient = {
+      bucket: () => ({
+        file: () => ({
+          save: () => {
+            const err = new Error();
+            err.code = 401;
+            throw err;
+          }
+        })
+      })
+    };
+    const GCS = new SnowflakeGCSUtil(connectionConfig, httpClient);
 
     meta.presignedUrl = '';
     meta.client = { gcsToken: mockAccessToken, gcsClient: gcsClient };
@@ -414,4 +352,4 @@ describe('GCS client', function () {
     await GCS.uploadFile(dataFile, meta, encryptionMetadata);
     assert.strictEqual(meta['resultStatus'], resultStatus.RENEW_TOKEN);
   });
-});
\ No newline at end of file
+});


Reviews:
user: login: sfc-gh-akolodziejczyk
body: LGTM! is there a plan to migrate other tests to use sinon?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:35:50Z
user: login: sfc-gh-pmotacki
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T09:41:42Z
user: login: sfc-gh-rsavenok
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T10:16:05Z
user: login: sfc-gh-dheyman
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-21T08:53:48Z
user: login: sfc-gh-pmotacki
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T12:56:00Z

Review comments:
diff_hunk: @@ -362,8 +307,7 @@ describe('key-pair authentication', function () {
   });
 
   it('key-pair - get token with private key path without passphrase', function () {
-    const auth = new AuthKeypair(connectionOptionsKeyPairPath,
-      cryptomod, jwtmod, filesystem);
+    const auth = new AuthKeypair(connectionOptionsKeyPairPath);
path: test/unit/authentication/authentication_test.js
user: login: sfc-gh-pmotacki
body: Wasn't it a BCR for customers? I know it's not treated as a public API because it is not in the types file. wdyt?
created_at: 2025-05-20T09:26:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -362,8 +307,7 @@ describe('key-pair authentication', function () {
   });
 
   it('key-pair - get token with private key path without passphrase', function () {
-    const auth = new AuthKeypair(connectionOptionsKeyPairPath,
-      cryptomod, jwtmod, filesystem);
+    const auth = new AuthKeypair(connectionOptionsKeyPairPath);
path: test/unit/authentication/authentication_test.js
user: login: sfc-gh-rsavenok
body: I don't think we should treat it as BCR because customers cannot import it into their codebase except through a "hackish" way to access `/lib/anyFile` directly. 


The only entry point we provide to customers is `index.js`, and it doesn't export anything except `core.js` instance. If we treat it as BCR, basically every internal function change would be a BCR
created_at: 2025-05-20T10:16:05Z
author_association: COLLABORATOR

PR comments:
user: login: sfc-gh-snowflakedb-snyk-sa
created_at: 2025-05-14T13:48:40Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/snowflakedb-sca-scanning-public-repo/pr-checks/82175e32-7da5-4a0d-b6c3-4681ada823d6?refs=d214a0d3be97d29dbd463b472752c534787c0d81&source=prChecksComment)

:white_check_mark: **license/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/snowflakedb-sca-scanning-public-repo/pr-checks/82175e32-7da5-4a0d-b6c3-4681ada823d6/license?refs=d214a0d3be97d29dbd463b472752c534787c0d81&source=prChecksComment)



author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-14T14:23:21Z
body: ## [Codecov](https://app.codecov.io/gh/snowflakedb/snowflake-connector-nodejs/pull/1077?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 89.53%. Comparing base [(`546a4f3`)](https://app.codecov.io/gh/snowflakedb/snowflake-connector-nodejs/commit/546a4f31ce1fd6650c46b1cf819f72562ee70ddf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) to head [(`0bf2470`)](https://app.codecov.io/gh/snowflakedb/snowflake-connector-nodejs/commit/0bf2470460c134d2303c353dc5551e5294defad2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1077      +/-   ##
==========================================
- Coverage   89.64%   89.53%   -0.11%     
==========================================
  Files          76       76              
  Lines        7558     7558              
==========================================
- Hits         6775     6767       -8     
- Misses        783      791       +8     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/snowflakedb/snowflake-connector-nodejs/pull/1077?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: sfc-gh-rsavenok
created_at: 2025-05-15T11:37:45Z
body: > LGTM! is there a plan to migrate other tests to use sinon?

I think it makes sense to migrate other parts only when somebody would touch that part of the code :) 
author_association: COLLABORATOR
user: login: sfc-gh-dheyman
created_at: 2025-05-21T08:54:07Z
body: Great improvement! LGTM
author_association: COLLABORATOR