diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e8b4aa58..e8c5cc16 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,16 +46,16 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7 + uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: languages: ${{ matrix.language }} # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually - name: Autobuild - uses: github/codeql-action/autobuild@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7 + uses: github/codeql-action/autobuild@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7 + uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3185b9be..1bc4bc5c 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -57,7 +57,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: SARIF file path: results.sarif @@ -65,6 +65,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7 + uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: sarif_file: resultsFiltered.sarif diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 44af4e02..aff7bbf4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -161,6 +161,8 @@ jobs: POSTGRES_DB:${{ vars.GOOGLE_CLOUD_PROJECT }}/POSTGRES_DB POSTGRES_CAS_CONNECTION_NAME:${{ vars.GOOGLE_CLOUD_PROJECT }}/POSTGRES_CAS_CONNECTION_NAME POSTGRES_CAS_PASS:${{ vars.GOOGLE_CLOUD_PROJECT }}/POSTGRES_CAS_PASS + POSTGRES_CUSTOMER_CAS_CONNECTION_NAME:${{ vars.GOOGLE_CLOUD_PROJECT }}/POSTGRES_CUSTOMER_CAS_CONNECTION_NAME + POSTGRES_CUSTOMER_CAS_PASS:${{ vars.GOOGLE_CLOUD_PROJECT }}/POSTGRES_CUSTOMER_CAS_PASS SQLSERVER_CONNECTION_NAME:${{ vars.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_CONNECTION_NAME SQLSERVER_USER:${{ vars.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_USER SQLSERVER_PASS:${{ vars.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_PASS @@ -188,6 +190,8 @@ jobs: POSTGRES_DB: "${{ steps.secrets.outputs.POSTGRES_DB }}" POSTGRES_CAS_CONNECTION_NAME: "${{ steps.secrets.outputs.POSTGRES_CAS_CONNECTION_NAME }}" POSTGRES_CAS_PASS: "${{ steps.secrets.outputs.POSTGRES_CAS_PASS }}" + POSTGRES_CUSTOMER_CAS_CONNECTION_NAME: "${{ steps.secrets.outputs.POSTGRES_CUSTOMER_CAS_CONNECTION_NAME }}" + POSTGRES_CUSTOMER_CAS_PASS: "${{ steps.secrets.outputs.POSTGRES_CUSTOMER_CAS_PASS }}" SQLSERVER_CONNECTION_NAME: "${{ steps.secrets.outputs.SQLSERVER_CONNECTION_NAME }}" SQLSERVER_USER: "${{ steps.secrets.outputs.SQLSERVER_USER }}" SQLSERVER_PASS: "${{ steps.secrets.outputs.SQLSERVER_PASS }}" diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 4812751a..00000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -lockfile-version=2 diff --git a/CHANGELOG.md b/CHANGELOG.md index d00eeb44..0d16d29d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.6.0](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/compare/v1.5.0...v1.6.0) (2025-01-13) + + +### Features + +* add support for custom user agent ([#410](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/issues/410)) ([25f5ccd](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/commit/25f5ccd9d4413b4baa13de3e9c060a1c92d5b63a)) +* Support Private CA for server certificates. ([#408](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/issues/408)) ([36f1304](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/commit/36f1304334770f0adae56da4f6dba6c0d5362fdc)) + ## [1.5.0](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/compare/v1.4.0...v1.5.0) (2024-12-11) diff --git a/package-lock.json b/package-lock.json index 12ea2ae6..74e8d427 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@google-cloud/cloud-sql-connector", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@google-cloud/cloud-sql-connector", - "version": "1.5.0", + "version": "1.6.0", "license": "Apache-2.0", "dependencies": { "@googleapis/sqladmin": "^24.0.0", @@ -18,7 +18,6 @@ "@sequelize/core": "^7.0.0-alpha.29", "@types/node": "^22.0.0", "@types/pg": "^8.10.1", - "@types/semver": "^7.5.0", "@types/tap": "^18.0.0", "@types/tedious": "^4.0.9", "@typescript-eslint/eslint-plugin": "^7.0.0", @@ -29,7 +28,6 @@ "mysql2": "^3.2.0", "nock": "^13.3.0", "pg": "^8.10.0", - "semver": "^7.5.1", "tap": "^21.0.0", "tedious": "^16.1.0", "typeorm": "^0.3.19", diff --git a/package.json b/package.json index fa22299c..ade3765e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@google-cloud/cloud-sql-connector", - "version": "1.5.0", + "version": "1.6.0", "description": "A JavaScript library for connecting securely to your Cloud SQL instances", "author": "Google Inc.", "main": "./dist/cjs/index.js", @@ -60,7 +60,6 @@ "@sequelize/core": "^7.0.0-alpha.29", "@types/node": "^22.0.0", "@types/pg": "^8.10.1", - "@types/semver": "^7.5.0", "@types/tap": "^18.0.0", "@types/tedious": "^4.0.9", "@typescript-eslint/eslint-plugin": "^7.0.0", @@ -71,7 +70,6 @@ "mysql2": "^3.2.0", "nock": "^13.3.0", "pg": "^8.10.0", - "semver": "^7.5.1", "tap": "^21.0.0", "tedious": "^16.1.0", "typeorm": "^0.3.19", diff --git a/scripts/tap16-adapter.js b/scripts/tap16-adapter.js deleted file mode 100644 index 565d4de8..00000000 --- a/scripts/tap16-adapter.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Test adapter used to bridge test files to tap16-compatible -// APIs in order to enable running tests in node@14 -// This file (and its usage in .github/tests.yml) can be safely -// removed once node14 is no longer supported -const originalJsHandler = require.extensions['.js'] -require.extensions['.js'] = function(module, filename) { - const compile = module._compile - module._compile = function (code, filename) { - const source = code.replace(/t\.mockRequire/g, 't.mock') - return compile.call(module, source, filename) - } - originalJsHandler(module, filename) -} diff --git a/src/connector.ts b/src/connector.ts index 4c306618..c152ebd5 100644 --- a/src/connector.ts +++ b/src/connector.ts @@ -148,6 +148,7 @@ interface ConnectorOptions { * Defaults to `googleapis.com`. */ universeDomain?: string; + userAgent?: string; } // The Connector class is the main public API to interact @@ -164,6 +165,7 @@ export class Connector { loginAuth: opts.auth, sqlAdminAPIEndpoint: opts.sqlAdminAPIEndpoint, universeDomain: opts.universeDomain, + userAgent: opts.userAgent, }); this.localProxies = new Set(); this.sockets = new Set(); diff --git a/src/crypto.ts b/src/crypto.ts index 751ed2ed..c459f5dd 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -18,54 +18,6 @@ import {SslCert} from './ssl-cert'; import {cryptoModule} from './node-crypto'; import {CloudSQLConnectorError} from './errors'; -// The following is a fallback certificate parser for node14 to work around -// its lack of support to the X509Certificate class parser, this block of code -// can be safely removed once node14 is no longer supported, along with any -// `node14ParseCert` call and its unit tests. -// --- node@14 cert parse fallback start -import net from 'node:net'; -import tls from 'node:tls'; - -const node14ParseCert = (cert: string): SslCert => { - const isPeerCertificate = ( - obj: object | tls.PeerCertificate - ): obj is tls.PeerCertificate => - (obj as tls.PeerCertificate).valid_to !== undefined; - - let socket; - let parsed; - try { - socket = new tls.TLSSocket(new net.Socket(), { - secureContext: tls.createSecureContext({cert}), - }); - parsed = socket.getCertificate(); - } catch (err: unknown) { - throw new CloudSQLConnectorError({ - message: 'Failed to parse as X.509 certificate.', - code: 'EPARSESQLADMINEPH', - errors: [err as Error], - }); - } - - if (parsed && isPeerCertificate(parsed)) { - const expirationTime = parsed.valid_to; - socket.destroy(); - socket = undefined; - parsed = undefined; - - return { - cert, - expirationTime, - }; - } - /* c8 ignore next 5 */ - throw new CloudSQLConnectorError({ - message: 'Could not read ephemeral certificate.', - code: 'EPARSESQLADMINEPH', - }); -}; -// --- node@14 cert parse fallback end - export async function generateKeys(): Promise { const crypto = await cryptoModule(); const keygen = promisify(crypto.generateKeyPair); @@ -90,27 +42,24 @@ export async function generateKeys(): Promise { export async function parseCert(cert: string): Promise { const {X509Certificate} = await cryptoModule(); - if (X509Certificate) { - try { - const parsed = new X509Certificate(cert); - if (parsed && parsed.validTo) { - return { - cert, - expirationTime: parsed.validTo, - }; - } - - throw new CloudSQLConnectorError({ - message: 'Could not read ephemeral certificate.', - code: 'EPARSESQLADMINEPH', - }); - } catch (err: unknown) { - throw new CloudSQLConnectorError({ - message: 'Failed to parse as X.509 certificate.', - code: 'EPARSESQLADMINEPH', - errors: [err as Error], - }); + try { + const parsed = new X509Certificate(cert); + if (parsed && parsed.validTo) { + return { + cert, + expirationTime: parsed.validTo, + }; } + + throw new CloudSQLConnectorError({ + message: 'Could not read ephemeral certificate.', + code: 'EPARSESQLADMINEPH', + }); + } catch (err: unknown) { + throw new CloudSQLConnectorError({ + message: 'Failed to parse as X.509 certificate.', + code: 'EPARSESQLADMINEPH', + errors: [err as Error], + }); } - return node14ParseCert(cert); } diff --git a/src/socket.ts b/src/socket.ts index 6c757ad4..a3c4e1cf 100644 --- a/src/socket.ts +++ b/src/socket.ts @@ -36,23 +36,26 @@ export function validateCertificate( dnsName: string ) { return (hostname: string, cert: tls.PeerCertificate): Error | undefined => { - if (serverCaMode === 'GOOGLE_MANAGED_CAS_CA') { + if (!serverCaMode || serverCaMode === 'GOOGLE_MANAGED_INTERNAL_CA') { + // Legacy CA Mode + if (!cert || !cert.subject) { + return new CloudSQLConnectorError({ + message: 'No certificate to verify', + code: 'ENOSQLADMINVERIFYCERT', + }); + } + const expectedCN = `${instanceInfo.projectId}:${instanceInfo.instanceId}`; + if (cert.subject.CN !== expectedCN) { + return new CloudSQLConnectorError({ + message: `Certificate had CN ${cert.subject.CN}, expected ${expectedCN}`, + code: 'EBADSQLADMINVERIFYCERT', + }); + } + return undefined; + } else { + // Standard TLS Verify Full hostname verification using SAN return tls.checkServerIdentity(dnsName, cert); } - if (!cert || !cert.subject) { - return new CloudSQLConnectorError({ - message: 'No certificate to verify', - code: 'ENOSQLADMINVERIFYCERT', - }); - } - const expectedCN = `${instanceInfo.projectId}:${instanceInfo.instanceId}`; - if (cert.subject.CN !== expectedCN) { - return new CloudSQLConnectorError({ - message: `Certificate had CN ${cert.subject.CN}, expected ${expectedCN}`, - code: 'EBADSQLADMINVERIFYCERT', - }); - } - return undefined; }; } diff --git a/src/sqladmin-fetcher.ts b/src/sqladmin-fetcher.ts index 152e011e..2a436e17 100644 --- a/src/sqladmin-fetcher.ts +++ b/src/sqladmin-fetcher.ts @@ -82,6 +82,7 @@ export interface SQLAdminFetcherOptions { loginAuth?: GoogleAuth | AuthClient; sqlAdminAPIEndpoint?: string; universeDomain?: string; + userAgent?: string; } export class SQLAdminFetcher { @@ -92,6 +93,7 @@ export class SQLAdminFetcher { loginAuth, sqlAdminAPIEndpoint, universeDomain, + userAgent, }: SQLAdminFetcherOptions = {}) { let auth: GoogleAuth; @@ -111,6 +113,7 @@ export class SQLAdminFetcher { { product: 'cloud-sql-nodejs-connector', version: 'LIBRARY_SEMVER_VERSION', + comment: userAgent, }, ], universeDomain: universeDomain, diff --git a/system-test/pg-connect.cjs b/system-test/pg-connect.cjs index 601b07ce..87781314 100644 --- a/system-test/pg-connect.cjs +++ b/system-test/pg-connect.cjs @@ -93,3 +93,29 @@ t.test( connector.close(); } ); + +t.test( + 'open connection to Customer Private CAS-based CA instance and retrieves standard pg tables', + async t => { + const connector = new Connector(); + const clientOpts = await connector.getOptions({ + instanceConnectionName: String( + process.env.POSTGRES_CUSTOMER_CAS_CONNECTION_NAME + ), + }); + const client = new Client({ + ...clientOpts, + user: String(process.env.POSTGRES_USER), + password: String(process.env.POSTGRES_CUSTOMER_CAS_PASS), + database: String(process.env.POSTGRES_DB), + }); + client.connect(); + const { + rows: [result], + } = await client.query('SELECT NOW();'); + const returnedDate = result['now']; + t.ok(returnedDate.getTime(), 'should have valid returned date object'); + await client.end(); + connector.close(); + } +); diff --git a/system-test/pg-connect.mjs b/system-test/pg-connect.mjs index 4b7fec05..7116a340 100644 --- a/system-test/pg-connect.mjs +++ b/system-test/pg-connect.mjs @@ -93,3 +93,29 @@ t.test( connector.close(); } ); + +t.test( + 'open connection to Customer Private CAS-based CA instance and retrieves standard pg tables', + async t => { + const connector = new Connector(); + const clientOpts = await connector.getOptions({ + instanceConnectionName: String( + process.env.POSTGRES_CUSTOMER_CAS_CONNECTION_NAME + ), + }); + const client = new Client({ + ...clientOpts, + user: String(process.env.POSTGRES_USER), + password: String(process.env.POSTGRES_CUSTOMER_CAS_PASS), + database: String(process.env.POSTGRES_DB), + }); + client.connect(); + const { + rows: [result], + } = await client.query('SELECT NOW();'); + const returnedDate = result['now']; + t.ok(returnedDate.getTime(), 'should have valid returned date object'); + await client.end(); + connector.close(); + } +); diff --git a/system-test/pg-connect.ts b/system-test/pg-connect.ts index de10d0b2..ff2385f8 100644 --- a/system-test/pg-connect.ts +++ b/system-test/pg-connect.ts @@ -93,3 +93,29 @@ t.test( connector.close(); } ); + +t.test( + 'open connection to Customer Private CAS-based CA instance and retrieves standard pg tables', + async t => { + const connector = new Connector(); + const clientOpts = await connector.getOptions({ + instanceConnectionName: String( + process.env.POSTGRES_CUSTOMER_CAS_CONNECTION_NAME + ), + }); + const client = new Client({ + ...clientOpts, + user: String(process.env.POSTGRES_USER), + password: String(process.env.POSTGRES_CUSTOMER_CAS_PASS), + database: String(process.env.POSTGRES_DB), + }); + client.connect(); + const { + rows: [result], + } = await client.query('SELECT NOW();'); + const returnedDate = result['now']; + t.ok(returnedDate.getTime(), 'should have valid returned date object'); + await client.end(); + connector.close(); + } +); diff --git a/system-test/tedious-connect.cjs b/system-test/tedious-connect.cjs index 1c7881c5..f2ead835 100644 --- a/system-test/tedious-connect.cjs +++ b/system-test/tedious-connect.cjs @@ -13,69 +13,62 @@ // limitations under the License. const t = require('tap'); -const semver = require('semver'); const {Connector} = require('@google-cloud/cloud-sql-connector'); +const {Connection, Request} = require('tedious'); -t.test( - 'open connection and run basic sqlserver commands', - // the connector-supported versions of tedious do not support node14 - {skip: semver.lt(process.versions.node, '16.0.0')}, - async t => { - // lazy-load tedious here in order to allow for skipping node14 - const {Connection, Request} = require('tedious'); - const connector = new Connector(); - const clientOpts = await connector.getTediousOptions({ - instanceConnectionName: process.env.SQLSERVER_CONNECTION_NAME, - ipType: 'PUBLIC', - }); - const connection = new Connection({ - server: '0.0.0.0', - authentication: { - type: 'default', - options: { - userName: process.env.SQLSERVER_USER, - password: process.env.SQLSERVER_PASS, - }, - }, +t.test('open connection and run basic sqlserver commands', async t => { + const connector = new Connector(); + const clientOpts = await connector.getTediousOptions({ + instanceConnectionName: process.env.SQLSERVER_CONNECTION_NAME, + ipType: 'PUBLIC', + }); + const connection = new Connection({ + server: '0.0.0.0', + authentication: { + type: 'default', options: { - ...clientOpts, - port: 9999, - database: process.env.SQLSERVER_DB, + userName: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASS, }, - }); + }, + options: { + ...clientOpts, + port: 9999, + database: process.env.SQLSERVER_DB, + }, + }); - await new Promise((res, rej) => { - connection.connect(err => { - if (err) { - return rej(err); - } - res(); - }); + await new Promise((res, rej) => { + connection.connect(err => { + if (err) { + return rej(err); + } + res(); }); + }); - const res = await new Promise((res, rej) => { - let result; - const req = new Request('SELECT GETUTCDATE()', err => { - if (err) { - throw err; - } - }); - req.on('error', err => { - rej(err); - }); - req.on('row', columns => { - result = columns; - }); - req.on('requestCompleted', () => { - res(result); - }); - connection.execSql(req); + const res = await new Promise((res, rej) => { + let result; + const req = new Request('SELECT GETUTCDATE()', err => { + if (err) { + throw err; + } + }); + req.on('error', err => { + rej(err); + }); + req.on('row', columns => { + result = columns; + }); + req.on('requestCompleted', () => { + res(result); }); + connection.execSql(req); + }); - const [{value: utcDateResult}] = res; - t.ok(utcDateResult.getTime(), 'should have valid returned date object'); + const [{value: utcDateResult}] = res; + t.ok(utcDateResult.getTime(), 'should have valid returned date object'); - connection.close(); - connector.close(); - } -); + connection.close(); + connector.close(); +}); diff --git a/system-test/tedious-connect.mjs b/system-test/tedious-connect.mjs index 76040e41..9fd0aa73 100644 --- a/system-test/tedious-connect.mjs +++ b/system-test/tedious-connect.mjs @@ -13,20 +13,14 @@ // limitations under the License. import t from 'tap'; -import semver from 'semver'; import {Connector} from '@google-cloud/cloud-sql-connector'; +import {Connection, Request} from 'tedious'; -t.test( - 'open connection and run basic sqlserver commands', - // the connector-supported versions of tedious do not support node14 - {skip: semver.lt(process.versions.node, '16.0.0')}, - async t => { - // dynamically load tedious in order to allow for skipping node14 - const {Connection, Request} = await import('tedious'); +t.test('open connection and run basic sqlserver commands', async t => { const connector = new Connector(); const clientOpts = await connector.getTediousOptions({ instanceConnectionName: process.env.SQLSERVER_CONNECTION_NAME, - ipType: 'PUBLIC' + ipType: 'PUBLIC', }); const connection = new Connection({ server: '0.0.0.0', @@ -42,29 +36,35 @@ t.test( port: 9999, database: process.env.SQLSERVER_DB, }, - }) + }); await new Promise((res, rej) => { connection.connect(err => { if (err) { - return rej(err) + return rej(err); } - res() - }) - }) + res(); + }); + }); const res = await new Promise((res, rej) => { let result; - const req = new Request('SELECT GETUTCDATE()', (err) => { + const req = new Request('SELECT GETUTCDATE()', err => { if (err) { throw err; } - }) - req.on('error', (err) => { rej(err); }); - req.on('row', (columns) => { result = columns; }); - req.on('requestCompleted', () => { res(result); }); + }); + req.on('error', err => { + rej(err); + }); + req.on('row', columns => { + result = columns; + }); + req.on('requestCompleted', () => { + res(result); + }); connection.execSql(req); - }) + }); const [{value: utcDateResult}] = res; t.ok(utcDateResult.getTime(), 'should have valid returned date object'); diff --git a/system-test/tedious-connect.ts b/system-test/tedious-connect.ts index 759bf3b4..e789e273 100644 --- a/system-test/tedious-connect.ts +++ b/system-test/tedious-connect.ts @@ -13,70 +13,63 @@ // limitations under the License. import t from 'tap'; -import semver from 'semver'; import {Connector, IpAddressTypes} from '@google-cloud/cloud-sql-connector'; +import {Connection, Request} from 'tedious'; -t.test( - 'open connection and run basic sqlserver commands', - // the connector-supported versions of tedious do not support node14 - {skip: semver.lt(process.versions.node, '16.0.0')}, - async t => { - // dynamically load tedious in order to allow for skipping node14 - const {Connection, Request} = await import('tedious'); - const connector = new Connector(); - const clientOpts = await connector.getTediousOptions({ - instanceConnectionName: String(process.env.SQLSERVER_CONNECTION_NAME), - ipType: IpAddressTypes.PUBLIC, - }); - const connection = new Connection({ - server: '0.0.0.0', - authentication: { - type: 'default', - options: { - userName: String(process.env.SQLSERVER_USER), - password: String(process.env.SQLSERVER_PASS), - }, - }, +t.test('open connection and run basic sqlserver commands', async t => { + const connector = new Connector(); + const clientOpts = await connector.getTediousOptions({ + instanceConnectionName: String(process.env.SQLSERVER_CONNECTION_NAME), + ipType: IpAddressTypes.PUBLIC, + }); + const connection = new Connection({ + server: '0.0.0.0', + authentication: { + type: 'default', options: { - ...clientOpts, - port: 9999, - database: String(process.env.SQLSERVER_DB), + userName: String(process.env.SQLSERVER_USER), + password: String(process.env.SQLSERVER_PASS), }, - }); + }, + options: { + ...clientOpts, + port: 9999, + database: String(process.env.SQLSERVER_DB), + }, + }); - await new Promise((res, rej) => { - connection.connect(err => { - if (err) { - return rej(err); - } - res(null); - }); + await new Promise((res, rej) => { + connection.connect(err => { + if (err) { + return rej(err); + } + res(null); }); + }); - type ColumnValue = import('tedious').ColumnValue; - const res: ColumnValue[] = await new Promise((res, rej) => { - let result: ColumnValue[]; - const req = new Request('SELECT GETUTCDATE()', err => { - if (err) { - throw err; - } - }); - req.on('error', err => { - rej(err); - }); - req.on('row', columns => { - result = columns; - }); - req.on('requestCompleted', () => { - res(result); - }); - connection.execSql(req); + type ColumnValue = import('tedious').ColumnValue; + const res: ColumnValue[] = await new Promise((res, rej) => { + let result: ColumnValue[]; + const req = new Request('SELECT GETUTCDATE()', err => { + if (err) { + throw err; + } + }); + req.on('error', err => { + rej(err); + }); + req.on('row', columns => { + result = columns; + }); + req.on('requestCompleted', () => { + res(result); }); + connection.execSql(req); + }); - const [{value: utcDateResult}] = res; - t.ok(utcDateResult.getTime(), 'should have valid returned date object'); + const [{value: utcDateResult}] = res; + t.ok(utcDateResult.getTime(), 'should have valid returned date object'); - connection.close(); - connector.close(); - } -); + connection.close(); + connector.close(); +}); diff --git a/test/connector.ts b/test/connector.ts index 9ce184ea..f7b8fa1f 100644 --- a/test/connector.ts +++ b/test/connector.ts @@ -545,3 +545,23 @@ t.test('Connector, custom universeDomain', async t => { t.same(actualUniverseDomain, expectedUniverseDomain); }); + +t.test('Connector, custom userAgent', async t => { + const expectedUserAgent = 'custom-agent'; + let actualUserAgent: string | undefined; + // mocks sql admin fetcher to check that the custom + // userAgent is correctly passed into it + const {Connector} = t.mockRequire('../src/connector', { + '../src/sqladmin-fetcher': { + SQLAdminFetcher: class { + constructor({userAgent}: SQLAdminFetcherOptions) { + actualUserAgent = userAgent; + } + }, + }, + }); + + new Connector({userAgent: expectedUserAgent}); + + t.same(actualUserAgent, expectedUserAgent); +}); diff --git a/test/crypto.ts b/test/crypto.ts index 25ff5333..b4870f3b 100644 --- a/test/crypto.ts +++ b/test/crypto.ts @@ -66,47 +66,3 @@ t.test('parseCert missing expiration time', async t => { 'should throw parse ephemeral cert error' ); }); - -t.test('parseCert no x509 parser fallback', async t => { - const {cryptoModule} = t.mockRequire('../src/node-crypto', {}); - const {parseCert} = t.mockRequire('../src/crypto', { - '../src/node-crypto': { - async cryptoModule() { - const mod = cryptoModule(); - return { - ...mod, - X509Certificate: undefined, - }; - }, - }, - }); - const {cert, expirationTime} = await parseCert(CLIENT_CERT); - t.same(cert, CLIENT_CERT, 'should return original cert in response'); - t.same( - expirationTime, - 'Jul 22 17:53:09 3022 GMT', - 'should return expiration time' - ); -}); - -t.test('parseCert no x509 parser fallback failure', async t => { - const {cryptoModule} = t.mockRequire('../src/node-crypto', {}); - const {parseCert} = t.mockRequire('../src/crypto', { - '../src/node-crypto': { - async cryptoModule() { - const mod = cryptoModule(); - return { - ...mod, - X509Certificate: undefined, - }; - }, - }, - }); - return t.rejects( - parseCert('-----BEGIN PUBLIC KEY-----'), - { - code: 'EPARSESQLADMINEPH', - }, - 'should throw parse ephemeral cert error' - ); -});