From a0857ee2ed8e26557539b73814da02f6bd4b01a0 Mon Sep 17 00:00:00 2001 From: wellwelwel <46850407+wellwelwel@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:50:52 -0300 Subject: [PATCH 1/4] ci: simplify tests --- .../config/test-connect-timeout.test.mts | 61 +- .../test-typecast-global-false.test.mts | 64 +- .../test-typecast-global-option.test.mts | 82 +- .../encoding/test-charset-results.test.mts | 155 ++-- .../encoding/test-client-encodings.test.mts | 64 +- .../encoding/test-non-bmp-chars.test.mts | 58 +- .../encoding/test-track-encodings.test.mts | 46 +- .../test-aurora-failover-readonly.test.mts | 2 +- ...est-backpressure-load-data-infile.test.mts | 2 +- ...est-backpressure-result-streaming.test.mts | 2 +- .../test-binary-charset-string.test.mts | 140 ++- .../connection/test-binary-longlong.test.mts | 209 ++--- .../test-binary-multiple-results.test.mts | 414 ++++----- .../test-binary-notnull-nulls.test.mts | 118 +-- .../connection/test-buffer-params.test.mts | 65 +- .../test-change-user-multi-factor.test.mts | 145 ++-- .../test-change-user-plugin-auth.test.mts | 101 ++- .../connection/test-change-user.test.mts | 101 ++- .../connection/test-charset-encoding.test.mts | 112 ++- ...st-connect-after-connection-error.test.mts | 72 +- .../test-connect-after-connection.test.mts | 36 +- ...t-connect-connection-closed-error.test.mts | 50 +- .../connection/test-connect-sha1.test.mts | 116 +-- .../test-connect-time-error.test.mts | 62 +- .../connection/test-connect-with-uri.test.mts | 33 +- ...st-connection-reset-while-closing.test.mts | 49 +- .../test-custom-date-parameter.test.mts | 59 +- .../connection/test-date-parameter.test.mts | 39 +- .../connection/test-datetime.test.mts | 629 +++++++------- .../test-decimals-as-numbers.test.mts | 69 +- .../connection/test-disconnects.test.mts | 132 +-- .../connection/test-error-events.test.mts | 50 +- .../connection/test-errors.test.mts | 101 +-- .../test-execute-and-unprepare.test.mts | 48 +- .../test-execute-bind-boolean.test.mts | 35 +- .../test-execute-bind-date.test.mts | 37 +- .../test-execute-bind-function.test.mts | 45 +- .../test-execute-bind-json.test.mts | 43 +- .../test-execute-bind-null.test.mts | 39 +- .../test-execute-bind-number.test.mts | 49 +- .../test-execute-bind-undefined.test.mts | 45 +- .../connection/test-execute-cached.test.mts | 64 +- .../test-execute-newdecimal.test.mts | 42 +- .../test-execute-nocolumndef.test.mts | 59 +- .../test-execute-null-bitmap.test.mts | 52 +- .../connection/test-execute-order.test.mts | 43 +- .../connection/test-execute-signed.test.mts | 70 +- .../test-execute-type-casting.test.mts | 136 ++- ...-insert-bigint-big-number-strings.test.mts | 139 +-- .../connection/test-insert-bigint.test.mts | 159 ++-- .../connection/test-insert-json.test.mts | 72 +- .../test-insert-large-blob.test.mts | 159 ++-- .../test-insert-negative-ai.test.mts | 97 +-- .../connection/test-insert-results.test.mts | 80 +- .../test-invalid-date-result.test.mts | 89 +- .../connection/test-load-infile.test.mts | 176 ++-- .../connection/test-multiple-results.test.mts | 444 +++++----- .../test-named-placeholders.test.mts | 187 ++-- .../test-nested-tables-query.test.mts | 315 ++++--- .../connection/test-null-buffer.test.mts | 54 +- .../connection/test-null-double.test.mts | 34 +- .../connection/test-null-int.test.mts | 34 +- .../integration/connection/test-null.test.mts | 64 +- .../test-parameters-questionmark.test.mts | 62 +- .../test-prepare-and-close.test.mts | 53 +- .../connection/test-prepare-simple.test.mts | 70 +- .../test-prepare-then-execute.test.mts | 57 +- .../connection/test-protocol-errors.test.mts | 134 +-- .../connection/test-query-timeout.test.mts | 213 +++-- .../connection/test-query-zero.test.mts | 32 +- .../integration/connection/test-quit.test.mts | 129 +-- .../connection/test-select-1.test.mts | 36 +- .../test-select-empty-string.test.mts | 32 +- .../connection/test-select-json.test.mts | 58 +- .../connection/test-select-negative.test.mts | 42 +- .../connection/test-select-ssl.test.mts | 66 +- .../connection/test-select-utf8.test.mts | 37 +- .../connection/test-server-listen.test.mts | 73 +- .../connection/test-signed-tinyint.test.mts | 52 +- .../connection/test-stream-errors.test.mts | 156 ++-- .../connection/test-stream.test.mts | 160 ++-- .../connection/test-then-on-query.test.mts | 37 +- .../connection/test-timestamp.test.mts | 110 +-- .../test-track-state-change.test.mts | 53 +- .../test-transaction-commit.test.mts | 83 +- .../test-transaction-rollback.test.mts | 83 +- ...est-type-cast-null-fields-execute.test.mts | 91 +- .../test-type-cast-null-fields.test.mts | 78 +- .../test-type-casting-execute.test.mts | 182 ++-- .../connection/test-type-casting.test.mts | 182 ++-- .../connection/test-typecast-execute.test.mts | 286 ++++--- .../test-typecast-geometry-execute.test.mts | 76 +- .../test-typecast-geometry.test.mts | 76 +- ...test-typecast-overwriting-execute.test.mts | 84 +- .../test-typecast-overwriting.test.mts | 84 +- .../connection/test-typecast.test.mts | 311 ++++--- .../test-update-changed-rows.test.mts | 88 +- .../test-end-with-default-config.test.mts | 42 +- ...test-end-with-graceful-end-config.test.mts | 32 +- ...-release-idle-with-default-config.test.mts | 58 +- ...ase-idle-with-graceful-end-config.test.mts | 58 +- .../test-promise-wrapper.test.mts | 4 +- .../test-async-stack.test.mts | 61 +- .../test-promise-wrappers.test.mts | 800 +++++++----------- .../regressions/test-#433.test.mts | 148 ++-- .../regressions/test-#442.test.mts | 104 ++- .../regressions/test-#485.test.mts | 52 +- .../regressions/test-#617.test.mts | 119 ++- .../regressions/test-#629.test.mts | 138 ++- .../integration/regressions/test-#82.test.mts | 111 +-- .../test-auth-switch-multi-factor.test.mts | 193 +++-- ...st-auth-switch-plugin-async-error.test.mts | 90 +- .../test-auth-switch-plugin-error.test.mts | 88 +- .../esm/integration/test-auth-switch.test.mts | 111 +-- ...st-handshake-unknown-packet-error.test.mts | 67 +- .../test-multi-result-streaming.test.mts | 96 ++- .../test-pool-connect-error.test.mts | 92 +- .../integration/test-pool-disconnect.test.mts | 108 +-- test/esm/integration/test-pool-end.test.mts | 41 +- ...release-idle-connection-replicate.test.mts | 135 +-- ...l-release-idle-connection-timeout.test.mts | 115 +-- ...test-pool-release-idle-connection.test.mts | 117 +-- .../integration/test-pool-release.test.mts | 55 +- test/esm/integration/test-pool.test.mts | 68 +- .../integration/test-rows-as-array.test.mts | 134 +-- .../integration/test-server-close.test.mts | 72 +- test/esm/unit/commands/test-query.test.mts | 30 +- test/esm/unit/commands/test-quit.test.mts | 12 +- .../test-connection_config.test.mts | 144 ++-- .../packets/test-column-definition.test.mts | 150 ++-- test/esm/unit/packets/test-datetime.test.mts | 48 +- .../esm/unit/packets/test-ok-autoinc.test.mts | 26 +- .../packets/test-ok-sessiontrack.test.mts | 30 +- test/esm/unit/packets/test-text-row.test.mts | 47 +- test/esm/unit/packets/test-time.test.mts | 38 +- .../unit/parsers/test-text-parser.test.mts | 64 +- .../test-connection-error-remove.test.mts | 119 +-- .../test-connection-order.test.mts | 82 +- .../test-connection-retry.test.mts | 85 +- .../pool-cluster/test-connection-rr.test.mts | 82 +- .../esm/unit/pool-cluster/test-query.test.mts | 42 +- .../pool-cluster/test-remove-by-name.test.mts | 111 +-- .../test-remove-by-pattern.test.mts | 79 +- .../pool-cluster/test-restore-events.test.mts | 147 ++-- .../unit/pool-cluster/test-restore.test.mts | 121 +-- test/esm/unit/test-packet-parser.test.mts | 184 ++-- 146 files changed, 7640 insertions(+), 7024 deletions(-) diff --git a/test/esm/integration/config/test-connect-timeout.test.mts b/test/esm/integration/config/test-connect-timeout.test.mts index 57528d6cf9..ee7570d85c 100644 --- a/test/esm/integration/config/test-connect-timeout.test.mts +++ b/test/esm/integration/config/test-connect-timeout.test.mts @@ -1,41 +1,44 @@ -import assert from 'node:assert'; import process from 'node:process'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -console.log('test connect timeout'); +await describe('Connect Timeout', async () => { + await it('should emit ETIMEDOUT error on connection timeout', async () => { + await new Promise((resolve) => { + portfinder.getPort((_, port) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + server.on('connection', (conn) => { + conn.on('error', (err: NodeJS.ErrnoException) => { + assert.equal( + err.message, + 'Connection lost: The server closed the connection.' + ); + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); + }); + }); -portfinder.getPort((_, port) => { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - server.on('connection', () => { - // Let connection time out - }); - - server.listen(port); + server.listen(port); - const connection = mysql.createConnection({ - host: 'localhost', - port: port, - connectTimeout: 1000, - }); + const connection = mysql.createConnection({ + host: 'localhost', + port: port, + connectTimeout: 1000, + }); - connection.on('error', (err) => { - assert.equal(err.code, 'ETIMEDOUT'); - connection.destroy(); - // @ts-expect-error: internal access - server._server.close(); - console.log('ok'); + connection.on('error', (err) => { + assert.equal(err.code, 'ETIMEDOUT'); + connection.destroy(); + // @ts-expect-error: internal access + server._server.close(() => { + resolve(); + }); + }); + }); + }); }); }); - -process.on('uncaughtException', (err: NodeJS.ErrnoException) => { - assert.equal( - err.message, - 'Connection lost: The server closed the connection.' - ); - assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); -}); diff --git a/test/esm/integration/config/test-typecast-global-false.test.mts b/test/esm/integration/config/test-typecast-global-false.test.mts index 87a3ad0401..25778a86ac 100644 --- a/test/esm/integration/config/test-typecast-global-false.test.mts +++ b/test/esm/integration/config/test-typecast-global-false.test.mts @@ -1,35 +1,43 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection({ - typeCast: false, -}); - -const COL_1_VALUE = 'col v1'; -const COL_2_VALUE = 'col v2'; +await describe('Typecast Global False', async () => { + const connection = createConnection({ + typeCast: false, + }); -function executeTests(res: RowDataPacket[]) { - assert.equal(res[0].v1.toString('ascii'), COL_1_VALUE); - assert.equal(res[0].n1, null); - assert.equal(res[0].v2.toString('ascii'), COL_2_VALUE); -} + const COL_1_VALUE = 'col v1'; + const COL_2_VALUE = 'col v2'; -connection.query( - 'CREATE TEMPORARY TABLE binpar_null_test (v1 VARCHAR(16) NOT NULL, n1 VARCHAR(16), v2 VARCHAR(16) NOT NULL)' -); -connection.query( - `INSERT INTO binpar_null_test (v1, n1, v2) VALUES ("${COL_1_VALUE}", NULL, "${COL_2_VALUE}")`, - (err) => { - if (err) throw err; + function executeTests(res: RowDataPacket[]) { + assert.equal(res[0].v1.toString('ascii'), COL_1_VALUE); + assert.equal(res[0].n1, null); + assert.equal(res[0].v2.toString('ascii'), COL_2_VALUE); } -); -connection.execute( - 'SELECT * FROM binpar_null_test', - (err, res: RowDataPacket[]) => { - if (err) throw err; - executeTests(res); - connection.end(); - } -); + connection.query( + 'CREATE TEMPORARY TABLE binpar_null_test (v1 VARCHAR(16) NOT NULL, n1 VARCHAR(16), v2 VARCHAR(16) NOT NULL)' + ); + connection.query( + `INSERT INTO binpar_null_test (v1, n1, v2) VALUES ("${COL_1_VALUE}", NULL, "${COL_2_VALUE}")`, + (err) => { + if (err) throw err; + } + ); + + await it('should return raw buffers when typeCast is false', async () => { + await new Promise((resolve, reject) => { + connection.execute( + 'SELECT * FROM binpar_null_test', + (err, res: RowDataPacket[]) => { + if (err) return reject(err); + executeTests(res); + resolve(); + } + ); + }); + }); + + connection.end(); +}); diff --git a/test/esm/integration/config/test-typecast-global-option.test.mts b/test/esm/integration/config/test-typecast-global-option.test.mts index 84ffbf1402..e9f73420e2 100644 --- a/test/esm/integration/config/test-typecast-global-option.test.mts +++ b/test/esm/integration/config/test-typecast-global-option.test.mts @@ -3,46 +3,58 @@ import type { TypeCastField, TypeCastNext, } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -type StringMethod = 'toUpperCase' | 'toLowerCase'; +await describe('Typecast Global Option', async () => { + type StringMethod = 'toUpperCase' | 'toLowerCase'; -const typeCastWrapper = function (stringMethod: StringMethod) { - return function (field: TypeCastField, next: TypeCastNext) { - if (field.type === 'VAR_STRING') { - const value = field.string(); - return value?.[stringMethod](); - } - return next(); + const typeCastWrapper = function (stringMethod: StringMethod) { + return function (field: TypeCastField, next: TypeCastNext) { + if (field.type === 'VAR_STRING') { + const value = field.string(); + return value?.[stringMethod](); + } + return next(); + }; }; -}; -const connection = createConnection({ - typeCast: typeCastWrapper('toUpperCase'), -}); + const connection = createConnection({ + typeCast: typeCastWrapper('toUpperCase'), + }); -// query option override global typeCast -connection.query( - { - sql: 'select "FOOBAR" as foo', - typeCast: typeCastWrapper('toLowerCase'), - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'foobar'); - } -); + // query option override global typeCast + await it('should override global typeCast with query option', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select "FOOBAR" as foo', + typeCast: typeCastWrapper('toLowerCase'), + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'foobar'); + resolve(); + } + ); + }); + }); -// global typecast works -connection.query( - { - sql: 'select "foobar" as foo', - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'FOOBAR'); - } -); + // global typecast works + await it('should apply global typeCast', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select "foobar" as foo', + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'FOOBAR'); + resolve(); + } + ); + }); + }); -connection.end(); + connection.end(); +}); diff --git a/test/esm/integration/connection/encoding/test-charset-results.test.mts b/test/esm/integration/connection/encoding/test-charset-results.test.mts index b607e7f809..0a5a7a7cf3 100644 --- a/test/esm/integration/connection/encoding/test-charset-results.test.mts +++ b/test/esm/integration/connection/encoding/test-charset-results.test.mts @@ -1,6 +1,6 @@ import type { RowDataPacket } from '../../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import mysql from '../../../../../index.js'; import { createConnection } from '../../../common.test.mjs'; @@ -11,83 +11,86 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); +await describe('Charset Results', async () => { + const connection = createConnection(); + const payload = 'привет, мир'; -const payload = 'привет, мир'; -function tryEncoding(encoding: string, cb: () => void) { - connection.query('set character_set_results = ?', [encoding], (err) => { - assert.ifError(err); - connection.query( - 'SELECT ?', - [payload], - (err, rows, fields) => { - assert.ifError(err); - if (!fields || fields.length === 0) { - assert.fail('Expected metadata fields'); - } - const firstField = fields[0]; - const characterSet = firstField.characterSet; - if (characterSet === undefined) { - assert.fail('Expected characterSet metadata'); - } - let iconvEncoding = encoding; - if (encoding === 'utf8mb4') { - iconvEncoding = 'utf8'; - } - assert.equal(mysql.CharsetToEncoding[characterSet], iconvEncoding); - assert.equal(firstField.name, payload); - assert.equal(rows[0][firstField.name], payload); - cb(); - } - ); - }); -} - -function tryEncodingExecute(encoding: string, cb: () => void) { - connection.execute('set character_set_results = ?', [encoding], (err) => { - assert.ifError(err); - connection.execute( - 'SELECT ? as n', - [payload], - (err, rows, fields) => { - assert.ifError(err); - if (!fields || fields.length === 0) { - assert.fail('Expected metadata fields'); - } - const firstField = fields[0]; - const characterSet = firstField.characterSet; - if (characterSet === undefined) { - assert.fail('Expected characterSet metadata'); - } - let iconvEncoding = encoding; - if (encoding === 'utf8mb4') { - iconvEncoding = 'utf8'; - } - assert.equal(mysql.CharsetToEncoding[characterSet], iconvEncoding); - // TODO: figure out correct metadata encodings setup for binary protocol - // assert.equal(firstField.name, payload); - assert.equal(rows[0][firstField.name], payload); - cb(); - } - ); - }); -} + function tryEncoding(encoding: string): Promise { + return new Promise((resolve, reject) => { + connection.query('set character_set_results = ?', [encoding], (err) => { + if (err) return reject(err); + connection.query( + 'SELECT ?', + [payload], + (err, rows, fields) => { + if (err) return reject(err); + if (!fields || fields.length === 0) { + assert.fail('Expected metadata fields'); + } + const firstField = fields[0]; + const characterSet = firstField.characterSet; + if (characterSet === undefined) { + assert.fail('Expected characterSet metadata'); + } + let iconvEncoding = encoding; + if (encoding === 'utf8mb4') { + iconvEncoding = 'utf8'; + } + assert.equal(mysql.CharsetToEncoding[characterSet], iconvEncoding); + assert.equal(firstField.name, payload); + assert.equal(rows[0][firstField.name], payload); + resolve(); + } + ); + }); + }); + } -// christmas tree!!! :) -tryEncoding('cp1251', () => { - tryEncoding('koi8r', () => { - tryEncoding('cp866', () => { - tryEncoding('utf8mb4', () => { - tryEncodingExecute('cp1251', () => { - tryEncodingExecute('koi8r', () => { - tryEncodingExecute('cp866', () => { - tryEncodingExecute('utf8mb4', () => { - connection.end(); - }); - }); - }); - }); + function tryEncodingExecute(encoding: string): Promise { + return new Promise((resolve, reject) => { + connection.execute('set character_set_results = ?', [encoding], (err) => { + if (err) return reject(err); + connection.execute( + 'SELECT ? as n', + [payload], + (err, rows, fields) => { + if (err) return reject(err); + if (!fields || fields.length === 0) { + assert.fail('Expected metadata fields'); + } + const firstField = fields[0]; + const characterSet = firstField.characterSet; + if (characterSet === undefined) { + assert.fail('Expected characterSet metadata'); + } + let iconvEncoding = encoding; + if (encoding === 'utf8mb4') { + iconvEncoding = 'utf8'; + } + assert.equal(mysql.CharsetToEncoding[characterSet], iconvEncoding); + // TODO: figure out correct metadata encodings setup for binary protocol + // assert.equal(firstField.name, payload); + assert.equal(rows[0][firstField.name], payload); + resolve(); + } + ); }); }); - }); + } + + const encodings = ['cp1251', 'koi8r', 'cp866', 'utf8mb4']; + + for (const encoding of encodings) { + await it(`query with ${encoding} encoding`, async () => { + await tryEncoding(encoding); + }); + } + + for (const encoding of encodings) { + await it(`execute with ${encoding} encoding`, async () => { + await tryEncodingExecute(encoding); + }); + } + + connection.end(); }); diff --git a/test/esm/integration/connection/encoding/test-client-encodings.test.mts b/test/esm/integration/connection/encoding/test-client-encodings.test.mts index 545ca29c0f..ef2b42ccf4 100644 --- a/test/esm/integration/connection/encoding/test-client-encodings.test.mts +++ b/test/esm/integration/connection/encoding/test-client-encodings.test.mts @@ -1,6 +1,6 @@ import type { RowDataPacket } from '../../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../../common.test.mjs'; if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { @@ -8,38 +8,48 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection({ charset: 'UTF8MB4_GENERAL_CI' }); -connection.query('drop table if exists __test_client_encodings'); -connection.query( - 'create table if not exists __test_client_encodings (name VARCHAR(200)) CHARACTER SET=utf8mb4', - (err) => { - assert.ifError(err); - connection.query('delete from __test_client_encodings', (err) => { - assert.ifError(err); - connection.end(); +await describe('Client Encodings', async () => { + const connection = createConnection({ charset: 'UTF8MB4_GENERAL_CI' }); - const connection1 = createConnection({ - charset: 'CP1251_GENERAL_CI', - }); + await new Promise((resolve, reject) => { + connection.query('drop table if exists __test_client_encodings'); + connection.query( + 'create table if not exists __test_client_encodings (name VARCHAR(200)) CHARACTER SET=utf8mb4', + (err) => { + if (err) return reject(err); + connection.query('delete from __test_client_encodings', (err) => { + if (err) return reject(err); + connection.end(); + resolve(); + }); + } + ); + }); + + await it('should preserve encoding across different client charsets', async () => { + const connection1 = createConnection({ charset: 'CP1251_GENERAL_CI' }); + await new Promise((resolve, reject) => { connection1.query( 'insert into __test_client_encodings values("привет, мир")', (err) => { - assert.ifError(err); + if (err) return reject(err); connection1.end(); + resolve(); + } + ); + }); - const connection2 = createConnection({ - charset: 'KOI8R_GENERAL_CI', - }); - connection2.query( - 'select * from __test_client_encodings', - (err, rows) => { - assert.ifError(err); - assert.equal(rows[0].name, 'привет, мир'); - connection2.end(); - } - ); + const connection2 = createConnection({ charset: 'KOI8R_GENERAL_CI' }); + await new Promise((resolve, reject) => { + connection2.query( + 'select * from __test_client_encodings', + (err, rows) => { + if (err) return reject(err); + assert.equal(rows[0].name, 'привет, мир'); + connection2.end(); + resolve(); } ); }); - } -); + }); +}); diff --git a/test/esm/integration/connection/encoding/test-non-bmp-chars.test.mts b/test/esm/integration/connection/encoding/test-non-bmp-chars.test.mts index 00990bcbc3..970ae346e1 100644 --- a/test/esm/integration/connection/encoding/test-non-bmp-chars.test.mts +++ b/test/esm/integration/connection/encoding/test-non-bmp-chars.test.mts @@ -1,6 +1,6 @@ import type { FieldPacket, RowDataPacket } from '../../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../../common.test.mjs'; type UtfRow = RowDataPacket & Record; @@ -10,27 +10,39 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -// 4 bytes in utf8 -const pileOfPoo = '💩'; +await describe('Non BMP Chars', async () => { + // 4 bytes in utf8 + const pileOfPoo = '💩'; -const connection = createConnection({ charset: 'UTF8_GENERAL_CI' }); -connection.query( - 'select "💩"', - (err, rows, fields: FieldPacket[]) => { - assert.ifError(err); - assert.equal(fields[0].name, pileOfPoo); - assert.equal(rows[0][fields[0].name], pileOfPoo); - connection.end(); - } -); + await it('should handle non-BMP chars with UTF8_GENERAL_CI', async () => { + const connection = createConnection({ charset: 'UTF8_GENERAL_CI' }); + await new Promise((resolve, reject) => { + connection.query( + 'select "💩"', + (err, rows, fields: FieldPacket[]) => { + if (err) return reject(err); + assert.equal(fields[0].name, pileOfPoo); + assert.equal(rows[0][fields[0].name], pileOfPoo); + connection.end(); + resolve(); + } + ); + }); + }); -const connection2 = createConnection({ charset: 'UTF8MB4_GENERAL_CI' }); -connection2.query( - 'select "💩"', - (err, rows, fields: FieldPacket[]) => { - assert.ifError(err); - assert.equal(fields[0].name, '?'); - assert.equal(rows[0]['?'], pileOfPoo); - connection2.end(); - } -); + await it('should handle non-BMP chars with UTF8MB4_GENERAL_CI', async () => { + const connection2 = createConnection({ charset: 'UTF8MB4_GENERAL_CI' }); + await new Promise((resolve, reject) => { + connection2.query( + 'select "💩"', + (err, rows, fields: FieldPacket[]) => { + if (err) return reject(err); + assert.equal(fields[0].name, '?'); + assert.equal(rows[0]['?'], pileOfPoo); + connection2.end(); + resolve(); + } + ); + }); + }); +}); diff --git a/test/esm/integration/connection/encoding/test-track-encodings.test.mts b/test/esm/integration/connection/encoding/test-track-encodings.test.mts index 552282c1bb..cc2c6b7b56 100644 --- a/test/esm/integration/connection/encoding/test-track-encodings.test.mts +++ b/test/esm/integration/connection/encoding/test-track-encodings.test.mts @@ -1,30 +1,44 @@ import type { RowDataPacket } from '../../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../../common.test.mjs'; -const connection = createConnection({ charset: 'UTF8MB4_GENERAL_CI' }); -const text = 'привет, мир'; +await describe('Track Encodings', async () => { + const connection = createConnection({ charset: 'UTF8MB4_GENERAL_CI' }); + const text = 'привет, мир'; -connection.query('SET character_set_client=koi8r', (err) => { - assert.ifError(err); - connection.query( - `SELECT ? as result`, - [text], - (err, rows) => { - assert.ifError(err); - assert.equal(rows[0].result, text); + await it('should track koi8r encoding', async () => { + await new Promise((resolve, reject) => { + connection.query('SET character_set_client=koi8r', (err) => { + if (err) return reject(err); + connection.query( + `SELECT ? as result`, + [text], + (err, rows) => { + if (err) return reject(err); + assert.equal(rows[0].result, text); + resolve(); + } + ); + }); + }); + }); + + await it('should track cp1251 encoding', async () => { + await new Promise((resolve, reject) => { connection.query('SET character_set_client=cp1251', (err) => { - assert.ifError(err); + if (err) return reject(err); connection.query( `SELECT ? as result`, [text], (err, rows) => { - assert.ifError(err); + if (err) return reject(err); assert.equal(rows[0].result, text); - connection.end(); + resolve(); } ); }); - } - ); + }); + }); + + connection.end(); }); diff --git a/test/esm/integration/connection/test-aurora-failover-readonly.test.mts b/test/esm/integration/connection/test-aurora-failover-readonly.test.mts index fa15307d13..d1d85e88b1 100644 --- a/test/esm/integration/connection/test-aurora-failover-readonly.test.mts +++ b/test/esm/integration/connection/test-aurora-failover-readonly.test.mts @@ -144,7 +144,7 @@ async function testReadOnlyError( }); } -describe('Aurora MySQL Failover - Read-Only Error Handling', async () => { +await describe('Aurora MySQL Failover - Read-Only Error Handling', async () => { for (const errorConfig of readOnlyErrors) { await it(`should discard connection on pool.query() when error ${errorConfig.code} occurs`, async () => { const result = await testReadOnlyError(errorConfig, 'query'); diff --git a/test/esm/integration/connection/test-backpressure-load-data-infile.test.mts b/test/esm/integration/connection/test-backpressure-load-data-infile.test.mts index 44d01397c5..844971edbb 100644 --- a/test/esm/integration/connection/test-backpressure-load-data-infile.test.mts +++ b/test/esm/integration/connection/test-backpressure-load-data-infile.test.mts @@ -32,7 +32,7 @@ class BigInput extends Readable { } } -test('load data infile backpressure on local stream', async () => { +await test('load data infile backpressure on local stream', async () => { const netStream = Net.connect(config.port, config.host); netStream.setNoDelay(true); await new Promise((resolve, reject) => diff --git a/test/esm/integration/connection/test-backpressure-result-streaming.test.mts b/test/esm/integration/connection/test-backpressure-result-streaming.test.mts index f7acdba053..ba4ab9fe3e 100644 --- a/test/esm/integration/connection/test-backpressure-result-streaming.test.mts +++ b/test/esm/integration/connection/test-backpressure-result-streaming.test.mts @@ -2,7 +2,7 @@ import type { RowDataPacket } from '../../../../index.js'; import { assert, skip, sleep, test } from 'poku'; import { createConnection, getMysqlVersion } from '../../common.test.mjs'; -test('result event backpressure with pause/resume', async () => { +await test('result event backpressure with pause/resume', async () => { const connection = createConnection({ multipleStatements: true, }); diff --git a/test/esm/integration/connection/test-binary-charset-string.test.mts b/test/esm/integration/connection/test-binary-charset-string.test.mts index 7b8016a98e..819db8bb35 100644 --- a/test/esm/integration/connection/test-binary-charset-string.test.mts +++ b/test/esm/integration/connection/test-binary-charset-string.test.mts @@ -1,96 +1,88 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); - // TODO - this could be re-enabled if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { console.log('skipping test for planetscale'); process.exit(0); } -let rows: RowDataPacket[] | undefined; -let fields: FieldPacket[] | undefined; -let rows1: RowDataPacket[] | undefined; -let fields1: FieldPacket[] | undefined; -let rows2: RowDataPacket[] | undefined; -let fields2: FieldPacket[] | undefined; -let rows3: RowDataPacket[] | undefined; -let fields3: FieldPacket[] | undefined; +await describe('Binary Charset String', async () => { + const connection = createConnection(); -let rows4: RowDataPacket[] | undefined; -let fields4: FieldPacket[] | undefined; -let rows5: RowDataPacket[] | undefined; -let fields5: FieldPacket[] | undefined; + await it('should return correct charset strings for query and execute', async () => { + let rows: RowDataPacket[] | undefined; + let fields: FieldPacket[] | undefined; + let rows1: RowDataPacket[] | undefined; + let fields1: FieldPacket[] | undefined; + let rows2: RowDataPacket[] | undefined; + let fields2: FieldPacket[] | undefined; + let rows3: RowDataPacket[] | undefined; + let fields3: FieldPacket[] | undefined; + let rows4: RowDataPacket[] | undefined; + let fields4: FieldPacket[] | undefined; + let rows5: RowDataPacket[] | undefined; + let fields5: FieldPacket[] | undefined; -const query = "SELECT x'010203'"; -const query1 = "SELECT '010203'"; + const query = "SELECT x'010203'"; + const query1 = "SELECT '010203'"; -connection.query(query, (err, _rows, _fields) => { - if (err) { - throw err; - } - rows = _rows; - fields = _fields; -}); + await new Promise((resolve, reject) => { + connection.query(query, (err, _rows, _fields) => { + if (err) return reject(err); + rows = _rows; + fields = _fields; + }); -connection.query(query, (err, _rows, _fields) => { - if (err) { - throw err; - } - rows5 = _rows; - fields5 = _fields; -}); + connection.query(query, (err, _rows, _fields) => { + if (err) return reject(err); + rows5 = _rows; + fields5 = _fields; + }); -connection.query(query1, (err, _rows, _fields) => { - if (err) { - throw err; - } - rows1 = _rows; - fields1 = _fields; -}); + connection.query(query1, (err, _rows, _fields) => { + if (err) return reject(err); + rows1 = _rows; + fields1 = _fields; + }); -connection.execute(query, [], (err, _rows, _fields) => { - if (err) { - throw err; - } - rows2 = _rows; - fields2 = _fields; -}); + connection.execute(query, [], (err, _rows, _fields) => { + if (err) return reject(err); + rows2 = _rows; + fields2 = _fields; + }); -// repeat same query - test cached fields and parser -connection.execute(query, [], (err, _rows, _fields) => { - if (err) { - throw err; - } - rows4 = _rows; - fields4 = _fields; -}); + // repeat same query - test cached fields and parser + connection.execute(query, [], (err, _rows, _fields) => { + if (err) return reject(err); + rows4 = _rows; + fields4 = _fields; + }); -connection.execute(query1, [], (err, _rows, _fields) => { - if (err) { - throw err; - } - rows3 = _rows; - fields3 = _fields; - connection.end(); -}); + connection.execute(query1, [], (err, _rows, _fields) => { + if (err) return reject(err); + rows3 = _rows; + fields3 = _fields; + connection.end(); + resolve(); + }); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); - assert.equal(fields?.[0].name, "x'010203'"); - assert.deepEqual(rows1, [{ '010203': '010203' }]); - assert.equal(fields1?.[0].name, '010203'); - assert.deepEqual(rows2, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); - assert.equal(fields2?.[0].name, "x'010203'"); - assert.deepEqual(rows3, [{ '010203': '010203' }]); - assert.equal(fields3?.[0].name, '010203'); + assert.deepEqual(rows, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); + assert.equal(fields?.[0].name, "x'010203'"); + assert.deepEqual(rows1, [{ '010203': '010203' }]); + assert.equal(fields1?.[0].name, '010203'); + assert.deepEqual(rows2, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); + assert.equal(fields2?.[0].name, "x'010203'"); + assert.deepEqual(rows3, [{ '010203': '010203' }]); + assert.equal(fields3?.[0].name, '010203'); - assert.deepEqual(rows4, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); - assert.equal(fields4?.[0].name, "x'010203'"); - assert.deepEqual(rows5, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); - assert.equal(fields5?.[0].name, "x'010203'"); + assert.deepEqual(rows4, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); + assert.equal(fields4?.[0].name, "x'010203'"); + assert.deepEqual(rows5, [{ "x'010203'": Buffer.from([1, 2, 3]) }]); + assert.equal(fields5?.[0].name, "x'010203'"); + }); }); diff --git a/test/esm/integration/connection/test-binary-longlong.test.mts b/test/esm/integration/connection/test-binary-longlong.test.mts index dced1d7f48..e942fab627 100644 --- a/test/esm/integration/connection/test-binary-longlong.test.mts +++ b/test/esm/integration/connection/test-binary-longlong.test.mts @@ -1,4 +1,4 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type LongLongRow = { @@ -7,119 +7,124 @@ type LongLongRow = { lu: number | string | null; }; -const conn = createConnection(); +await describe('Binary LongLong', async () => { + const conn = createConnection(); -conn.query( - 'CREATE TEMPORARY TABLE `tmp_longlong` ( ' + - ' `id` int(11) NOT NULL AUTO_INCREMENT, ' + - ' `ls` BIGINT SIGNED, ' + - ' `lu` BIGINT UNSIGNED, ' + - ' PRIMARY KEY (`id`) ' + - ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' -); + conn.query( + 'CREATE TEMPORARY TABLE `tmp_longlong` ( ' + + ' `id` int(11) NOT NULL AUTO_INCREMENT, ' + + ' `ls` BIGINT SIGNED, ' + + ' `lu` BIGINT UNSIGNED, ' + + ' PRIMARY KEY (`id`) ' + + ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' + ); -const values = [ - ['10', '10'], - ['-11', '11'], - ['965432100123456789', '1965432100123456789'], - ['-965432100123456789', '2965432100123456789'], - [null, null], -]; + const values = [ + ['10', '10'], + ['-11', '11'], + ['965432100123456789', '1965432100123456789'], + ['-965432100123456789', '2965432100123456789'], + [null, null], + ]; -conn.connect((err) => { - if (err) { - console.error(err); - return; - } + await it('should handle BIGINT values with supportBigNumbers and bigNumberStrings options', async () => { + await new Promise((resolve, reject) => { + conn.connect((err) => { + if (err) return reject(err); - for (let i = 0; i < values.length; ++i) { - conn.query('INSERT INTO `tmp_longlong` VALUES (?, ?, ?)', [ - i + 1, - values[i][0], - values[i][1], - ]); - } + for (let i = 0; i < values.length; ++i) { + conn.query('INSERT INTO `tmp_longlong` VALUES (?, ?, ?)', [ + i + 1, + values[i][0], + values[i][1], + ]); + } - const bigNums_bnStringsFalse = [ - { id: 1, ls: 10, lu: 10 }, - { id: 2, ls: -11, lu: 11 }, - { id: 3, ls: 965432100123456800, lu: 1965432100123456800 }, - { id: 4, ls: -965432100123456800, lu: 2965432100123457000 }, - { id: 5, ls: null, lu: null }, - ]; + const bigNums_bnStringsFalse = [ + { id: 1, ls: 10, lu: 10 }, + { id: 2, ls: -11, lu: 11 }, + { id: 3, ls: 965432100123456800, lu: 1965432100123456800 }, + { id: 4, ls: -965432100123456800, lu: 2965432100123457000 }, + { id: 5, ls: null, lu: null }, + ]; - const bigNums_bnStringsTrueFalse = [ - { id: 1, ls: 10, lu: 10 }, - { id: 2, ls: -11, lu: 11 }, - { id: 3, ls: '965432100123456789', lu: '1965432100123456789' }, - { id: 4, ls: '-965432100123456789', lu: '2965432100123456789' }, - { id: 5, ls: null, lu: null }, - ]; + const bigNums_bnStringsTrueFalse = [ + { id: 1, ls: 10, lu: 10 }, + { id: 2, ls: -11, lu: 11 }, + { id: 3, ls: '965432100123456789', lu: '1965432100123456789' }, + { id: 4, ls: '-965432100123456789', lu: '2965432100123456789' }, + { id: 5, ls: null, lu: null }, + ]; - const bigNums_bnStringsTrueTrue = [ - { id: 1, ls: 10, lu: 10 }, - { id: 2, ls: -11, lu: 11 }, - { id: 3, ls: '965432100123456789', lu: '1965432100123456789' }, - { id: 4, ls: '-965432100123456789', lu: '2965432100123456789' }, - { id: 5, ls: null, lu: null }, - ]; + const bigNums_bnStringsTrueTrue = [ + { id: 1, ls: 10, lu: 10 }, + { id: 2, ls: -11, lu: 11 }, + { id: 3, ls: '965432100123456789', lu: '1965432100123456789' }, + { id: 4, ls: '-965432100123456789', lu: '2965432100123456789' }, + { id: 5, ls: null, lu: null }, + ]; - let completed = 0; - let started = 0; + let completed = 0; + let started = 0; - function testQuery( - supportBigNumbers: boolean, - bigNumberStrings: boolean, - expectation: LongLongRow[] - ) { - started++; - conn.query( - { - sql: 'SELECT * from tmp_longlong', - // @ts-expect-error: TODO: implement typings - supportBigNumbers: supportBigNumbers, - bigNumberStrings: bigNumberStrings, - }, - (err, rows) => { - assert.ifError(err); - assert.deepEqual(rows, expectation); - completed++; - if (completed === started) { - conn.end(); + function testQuery( + supportBigNumbers: boolean, + bigNumberStrings: boolean, + expectation: LongLongRow[] + ) { + started++; + conn.query( + { + sql: 'SELECT * from tmp_longlong', + // @ts-expect-error: TODO: implement typings + supportBigNumbers: supportBigNumbers, + bigNumberStrings: bigNumberStrings, + }, + (err, rows) => { + if (err) return reject(err); + assert.deepEqual(rows, expectation); + completed++; + if (completed === started) { + conn.end(); + resolve(); + } + } + ); } - } - ); - } - function testExecute( - supportBigNumbers: boolean, - bigNumberStrings: boolean, - expectation: LongLongRow[] - ) { - started++; - conn.execute( - { - sql: 'SELECT * from tmp_longlong', - // @ts-expect-error: TODO: implement typings - supportBigNumbers: supportBigNumbers, - bigNumberStrings: bigNumberStrings, - }, - (err, rows) => { - assert.ifError(err); - assert.deepEqual(rows, expectation); - completed++; - if (completed === started) { - conn.end(); + function testExecute( + supportBigNumbers: boolean, + bigNumberStrings: boolean, + expectation: LongLongRow[] + ) { + started++; + conn.execute( + { + sql: 'SELECT * from tmp_longlong', + // @ts-expect-error: TODO: implement typings + supportBigNumbers: supportBigNumbers, + bigNumberStrings: bigNumberStrings, + }, + (err, rows) => { + if (err) return reject(err); + assert.deepEqual(rows, expectation); + completed++; + if (completed === started) { + conn.end(); + resolve(); + } + } + ); } - } - ); - } - testQuery(false, false, bigNums_bnStringsFalse); - testQuery(true, false, bigNums_bnStringsTrueFalse); - testQuery(true, true, bigNums_bnStringsTrueTrue); + testQuery(false, false, bigNums_bnStringsFalse); + testQuery(true, false, bigNums_bnStringsTrueFalse); + testQuery(true, true, bigNums_bnStringsTrueTrue); - testExecute(false, false, bigNums_bnStringsFalse); - testExecute(true, false, bigNums_bnStringsTrueFalse); - testExecute(true, true, bigNums_bnStringsTrueTrue); + testExecute(false, false, bigNums_bnStringsFalse); + testExecute(true, false, bigNums_bnStringsTrueFalse); + testExecute(true, true, bigNums_bnStringsTrueTrue); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-binary-multiple-results.test.mts b/test/esm/integration/connection/test-binary-multiple-results.test.mts index 3a46ffab47..4c8e4e8889 100644 --- a/test/esm/integration/connection/test-binary-multiple-results.test.mts +++ b/test/esm/integration/connection/test-binary-multiple-results.test.mts @@ -6,6 +6,7 @@ import process from 'node:process'; // @ts-expect-error: no typings available import assert from 'assert-diff'; +import { describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { @@ -13,211 +14,226 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const mysql = createConnection({ - multipleStatements: true, -}); +await describe('Binary Multiple Results', async () => { + const mysql = createConnection({ + multipleStatements: true, + }); -mysql.query('CREATE TEMPORARY TABLE no_rows (test int)'); -mysql.query('CREATE TEMPORARY TABLE some_rows (test int)'); -mysql.query('INSERT INTO some_rows values(0)'); -mysql.query('INSERT INTO some_rows values(42)'); -mysql.query('INSERT INTO some_rows values(314149)'); - -const clone = function (obj: T): T { - return JSON.parse(JSON.stringify(obj)) as T; -}; - -const rs1 = { - affectedRows: 0, - fieldCount: 0, - insertId: 0, - serverStatus: 10, - warningStatus: 0, - info: '', - changedRows: 0, -}; -const rs2 = clone(rs1); -rs2.serverStatus = 2; -const rs3 = clone(rs1); -rs3.serverStatus = 34; - -const select1 = [{ 1: '1' }]; -const select2 = [{ 2: '2' }]; -const fields1 = [ - { - catalog: 'def', - characterSet: 63, - encoding: 'binary', - type: 8, - decimals: 0, - flags: 129, - name: '1', - orgName: '', - orgTable: '', - schema: '', - table: '', - }, -]; -const nr_fields = [ - { - catalog: 'def', - characterSet: 63, - encoding: 'binary', - type: 3, - decimals: 0, - flags: 0, - name: 'test', - orgName: 'test', - orgTable: 'no_rows', - schema: mysql.config.database, - table: 'no_rows', - }, -]; - -const sr_fields = clone(nr_fields); -sr_fields[0].orgTable = 'some_rows'; -sr_fields[0].table = 'some_rows'; -const select3 = [{ test: 0 }, { test: 42 }, { test: 314149 }]; - -const fields2 = clone(fields1); -fields2[0].name = '2'; - -const tests: [string, unknown[]][] = [ - ['select * from some_rows', [[select3, rs3], [sr_fields, undefined], 2]], // select 3 rows - [ - 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS', - [rs2, undefined, 1], - ], - ['set @a = 1', [rs2, undefined, 1]], - ['set @a = 1; set @b = 2', [rs2, undefined, 1]], - [ - 'select 1; select 2', - [[select1, select2, rs2], [fields1, fields2, undefined], 3], - ], - ['set @a = 1; select 1', [[select1, rs2], [fields1, undefined], 2]], - ['select 1; set @a = 1', [[select1, rs2], [fields1, undefined], 2]], - ['select * from no_rows', [[[], rs3], [nr_fields, undefined], 2]], // select 0 rows" - ['set @a = 1; select * from no_rows', [[[], rs3], [nr_fields, undefined], 2]], // insert + select 0 rows - ['select * from no_rows; set @a = 1', [[[], rs3], [nr_fields, undefined], 2]], // select 0 rows + insert - [ - 'set @a = 1; select * from some_rows', - [[select3, rs3], [sr_fields, undefined], 2], - ], // insert + select 3 rows - [ - 'select * from some_rows; set @a = 1', - [[select3, rs3], [sr_fields, undefined], 2], - ], // select 3 rows + insert -]; - -function procedurise(sql: string) { - return [ - 'DROP PROCEDURE IF EXISTS _as_sp_call;', - 'CREATE PROCEDURE _as_sp_call()', - 'BEGIN', - `${sql};`, - 'END', - ].join('\n'); -} + mysql.query('CREATE TEMPORARY TABLE no_rows (test int)'); + mysql.query('CREATE TEMPORARY TABLE some_rows (test int)'); + mysql.query('INSERT INTO some_rows values(0)'); + mysql.query('INSERT INTO some_rows values(42)'); + mysql.query('INSERT INTO some_rows values(314149)'); -function do_test(testIndex: number) { - const next = function () { - if (testIndex + 1 < tests.length) { - do_test(testIndex + 1); - } else { - mysql.end(); - } + const clone = function (obj: T): T { + return JSON.parse(JSON.stringify(obj)) as T; }; - const entry = tests[testIndex]; - let sql = entry[0]; - const expectation = entry[1]; - // prepared statements do not support multiple statements itself, we need to wrap quey in a stored procedure - const sp = procedurise(sql); - mysql.query(sp, (err) => { - if (err) { - throw err; - } - - sql = 'CALL _as_sp_call()'; // this call is allowed with prepared statements, and result contain multiple statements - let _numResults = 0; - const textCmd = mysql.query(sql, (err, _rows, _columns) => { - if (err) { - throw err; - } - - const arrOrColumn = function (c: unknown): unknown { - if (Array.isArray(c)) { - return c.map(arrOrColumn); - } - - if (typeof c === 'undefined') { - return void 0; - } - - // @ts-expect-error: internal access - const column = c.inspect() as Record; - // "columnLength" is non-deterministic and the display width for integer - // data types was deprecated on MySQL 8.0.17. - // https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html - delete column.columnLength; - - return column; - }; - - assert.deepEqual(expectation[0], _rows); - assert.deepEqual(expectation[1], arrOrColumn(_columns)); - - const q = mysql.execute(sql); - let resIndex = 0; - let rowIndex = 0; - let fieldIndex = -1; - - function checkRow(row: { - constructor: { name: string }; - [key: string]: unknown; - }) { - const index = fieldIndex; - const multiRows = _rows as unknown[]; - if (_numResults === 1) { - assert.equal(index, 0); - if (row.constructor.name === 'ResultSetHeader') { - assert.deepEqual(_rows, row); - } else { - assert.deepEqual(multiRows[rowIndex], row); - } - } else { - if (resIndex !== index) { - rowIndex = 0; - resIndex = index; - } - if (row.constructor.name === 'ResultSetHeader') { - assert.deepEqual(multiRows[index], row); + const rs1 = { + affectedRows: 0, + fieldCount: 0, + insertId: 0, + serverStatus: 10, + warningStatus: 0, + info: '', + changedRows: 0, + }; + const rs2 = clone(rs1); + rs2.serverStatus = 2; + const rs3 = clone(rs1); + rs3.serverStatus = 34; + + const select1 = [{ 1: '1' }]; + const select2 = [{ 2: '2' }]; + const fields1 = [ + { + catalog: 'def', + characterSet: 63, + encoding: 'binary', + type: 8, + decimals: 0, + flags: 129, + name: '1', + orgName: '', + orgTable: '', + schema: '', + table: '', + }, + ]; + const nr_fields = [ + { + catalog: 'def', + characterSet: 63, + encoding: 'binary', + type: 3, + decimals: 0, + flags: 0, + name: 'test', + orgName: 'test', + orgTable: 'no_rows', + schema: mysql.config.database, + table: 'no_rows', + }, + ]; + + const sr_fields = clone(nr_fields); + sr_fields[0].orgTable = 'some_rows'; + sr_fields[0].table = 'some_rows'; + const select3 = [{ test: 0 }, { test: 42 }, { test: 314149 }]; + + const fields2 = clone(fields1); + fields2[0].name = '2'; + + const tests: [string, unknown[]][] = [ + ['select * from some_rows', [[select3, rs3], [sr_fields, undefined], 2]], // select 3 rows + [ + 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS', + [rs2, undefined, 1], + ], + ['set @a = 1', [rs2, undefined, 1]], + ['set @a = 1; set @b = 2', [rs2, undefined, 1]], + [ + 'select 1; select 2', + [[select1, select2, rs2], [fields1, fields2, undefined], 3], + ], + ['set @a = 1; select 1', [[select1, rs2], [fields1, undefined], 2]], + ['select 1; set @a = 1', [[select1, rs2], [fields1, undefined], 2]], + ['select * from no_rows', [[[], rs3], [nr_fields, undefined], 2]], // select 0 rows" + [ + 'set @a = 1; select * from no_rows', + [[[], rs3], [nr_fields, undefined], 2], + ], // insert + select 0 rows + [ + 'select * from no_rows; set @a = 1', + [[[], rs3], [nr_fields, undefined], 2], + ], // select 0 rows + insert + [ + 'set @a = 1; select * from some_rows', + [[select3, rs3], [sr_fields, undefined], 2], + ], // insert + select 3 rows + [ + 'select * from some_rows; set @a = 1', + [[select3, rs3], [sr_fields, undefined], 2], + ], // select 3 rows + insert + ]; + + function procedurise(sql: string) { + return [ + 'DROP PROCEDURE IF EXISTS _as_sp_call;', + 'CREATE PROCEDURE _as_sp_call()', + 'BEGIN', + `${sql};`, + 'END', + ].join('\n'); + } + + await it('should handle multiple result sets with prepared statements', async () => { + await new Promise((resolve, reject) => { + function do_test(testIndex: number) { + const next = function () { + if (testIndex + 1 < tests.length) { + do_test(testIndex + 1); } else { - assert.deepEqual((multiRows[index] as unknown[])[rowIndex], row); + mysql.end(); + resolve(); } - } - rowIndex++; - } - - function checkFields(fields: unknown) { - fieldIndex++; - const index = fieldIndex; - if (_numResults === 1) { - assert.equal(index, 0); - assert.deepEqual(arrOrColumn(_columns), arrOrColumn(fields)); - } else { - assert.deepEqual(arrOrColumn(_columns[index]), arrOrColumn(fields)); - } + }; + + const entry = tests[testIndex]; + let sql = entry[0]; + const expectation = entry[1]; + // prepared statements do not support multiple statements itself, we need to wrap quey in a stored procedure + const sp = procedurise(sql); + mysql.query(sp, (err) => { + if (err) return reject(err); + + sql = 'CALL _as_sp_call()'; // this call is allowed with prepared statements, and result contain multiple statements + let _numResults = 0; + const textCmd = mysql.query(sql, (err, _rows, _columns) => { + if (err) return reject(err); + + const arrOrColumn = function (c: unknown): unknown { + if (Array.isArray(c)) { + return c.map(arrOrColumn); + } + + if (typeof c === 'undefined') { + return void 0; + } + + // @ts-expect-error: internal access + const column = c.inspect() as Record; + // "columnLength" is non-deterministic and the display width for integer + // data types was deprecated on MySQL 8.0.17. + // https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html + delete column.columnLength; + + return column; + }; + + assert.deepEqual(expectation[0], _rows); + assert.deepEqual(expectation[1], arrOrColumn(_columns)); + + const q = mysql.execute(sql); + let resIndex = 0; + let rowIndex = 0; + let fieldIndex = -1; + + function checkRow(row: { + constructor: { name: string }; + [key: string]: unknown; + }) { + const index = fieldIndex; + const multiRows = _rows as unknown[]; + if (_numResults === 1) { + assert.equal(index, 0); + if (row.constructor.name === 'ResultSetHeader') { + assert.deepEqual(_rows, row); + } else { + assert.deepEqual(multiRows[rowIndex], row); + } + } else { + if (resIndex !== index) { + rowIndex = 0; + resIndex = index; + } + if (row.constructor.name === 'ResultSetHeader') { + assert.deepEqual(multiRows[index], row); + } else { + assert.deepEqual( + (multiRows[index] as unknown[])[rowIndex], + row + ); + } + } + rowIndex++; + } + + function checkFields(fields: unknown) { + fieldIndex++; + const index = fieldIndex; + if (_numResults === 1) { + assert.equal(index, 0); + assert.deepEqual(arrOrColumn(_columns), arrOrColumn(fields)); + } else { + assert.deepEqual( + arrOrColumn(_columns[index]), + arrOrColumn(fields) + ); + } + } + + q.on('result', checkRow); + q.on('fields', checkFields); + q.on('end', next); + }); + + textCmd.on('fields', () => { + _numResults++; + }); + }); } - - q.on('result', checkRow); - q.on('fields', checkFields); - q.on('end', next); - }); - - textCmd.on('fields', () => { - _numResults++; + do_test(0); }); }); -} -do_test(0); +}); diff --git a/test/esm/integration/connection/test-binary-notnull-nulls.test.mts b/test/esm/integration/connection/test-binary-notnull-nulls.test.mts index 07c87c5577..7d24da9a0a 100644 --- a/test/esm/integration/connection/test-binary-notnull-nulls.test.mts +++ b/test/esm/integration/connection/test-binary-notnull-nulls.test.mts @@ -1,71 +1,75 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const conn = createConnection(); +await describe('Binary NotNull Nulls', async () => { + const conn = createConnection(); -// it's possible to receive null values for columns marked with NOT_NULL flag -// see https://github.com/sidorares/node-mysql2/issues/178 for info + // it's possible to receive null values for columns marked with NOT_NULL flag + // see https://github.com/sidorares/node-mysql2/issues/178 for info -conn.query('set sql_mode=""'); + conn.query('set sql_mode=""'); -conn.query( - 'CREATE TEMPORARY TABLE `tmp_account` ( ' + - ' `id` int(11) NOT NULL AUTO_INCREMENT, ' + - ' `username` varchar(64) NOT NULL, ' + - ' `auth_code` varchar(30) NOT NULL, ' + - ' `access_token` varchar(30) NOT NULL, ' + - ' `refresh_token` tinytext NOT NULL, ' + - ' PRIMARY KEY (`id`) ' + - ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' -); -conn.query("INSERT INTO `tmp_account` VALUES ('1', 'xgredx', '', '', '')"); + conn.query( + 'CREATE TEMPORARY TABLE `tmp_account` ( ' + + ' `id` int(11) NOT NULL AUTO_INCREMENT, ' + + ' `username` varchar(64) NOT NULL, ' + + ' `auth_code` varchar(30) NOT NULL, ' + + ' `access_token` varchar(30) NOT NULL, ' + + ' `refresh_token` tinytext NOT NULL, ' + + ' PRIMARY KEY (`id`) ' + + ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' + ); + conn.query("INSERT INTO `tmp_account` VALUES ('1', 'xgredx', '', '', '')"); -conn.query( - 'CREATE TEMPORARY TABLE `tmp_account_flags` ( ' + - ' `account` int(11) NOT NULL, ' + - ' `flag` tinyint(3) NOT NULL, ' + - ' PRIMARY KEY (`account`,`flag`) ' + - ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' -); + conn.query( + 'CREATE TEMPORARY TABLE `tmp_account_flags` ( ' + + ' `account` int(11) NOT NULL, ' + + ' `flag` tinyint(3) NOT NULL, ' + + ' PRIMARY KEY (`account`,`flag`) ' + + ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' + ); -conn.query("INSERT INTO `tmp_account_flags` VALUES ('1', '100')"); + conn.query("INSERT INTO `tmp_account_flags` VALUES ('1', '100')"); -conn.query( - 'CREATE TEMPORARY TABLE `tmp_account_session` ( ' + - ' `account` int(11) NOT NULL, ' + - ' `ip` varchar(15) NOT NULL, ' + - ' `session` varchar(114) NOT NULL, ' + - ' `time` int(11) NOT NULL, ' + - ' PRIMARY KEY (`account`,`ip`) ' + - ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' -); + conn.query( + 'CREATE TEMPORARY TABLE `tmp_account_session` ( ' + + ' `account` int(11) NOT NULL, ' + + ' `ip` varchar(15) NOT NULL, ' + + ' `session` varchar(114) NOT NULL, ' + + ' `time` int(11) NOT NULL, ' + + ' PRIMARY KEY (`account`,`ip`) ' + + ' ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8' + ); -conn.query( - "INSERT INTO `tmp_account_session` VALUES ('1', '::1', '75efb145482ce22f4544390cad233c749c1b43e4', '1')" -); + conn.query( + "INSERT INTO `tmp_account_session` VALUES ('1', '::1', '75efb145482ce22f4544390cad233c749c1b43e4', '1')" + ); -conn.connect((err) => { - if (err) { - console.error(err); - return; - } + await it('should handle null values in NOT NULL columns', async () => { + await new Promise((resolve, reject) => { + conn.connect((err) => { + if (err) return reject(err); - conn.execute( - "SELECT `ac`.`username`, CONCAT('[', GROUP_CONCAT(DISTINCT `acf`.`flag` SEPARATOR ','), ']') flags FROM tmp_account ac LEFT JOIN tmp_account_flags acf ON `acf`.account = `ac`.id LEFT JOIN tmp_account_session acs ON `acs`.account = `ac`.id WHERE `acs`.`session`=?", - ['asid=75efb145482ce22f4544390cad233c749c1b43e4'], - (_err, rows, fields) => { - /* - this assertion is valid for mysql8 < 8.0.17 and not longer valid in 8.0.18 - TODO: investigate why and remove - const flagNotNull = fields[0].flags & FieldFlags.NOT_NULL; - const valueIsNull = rows[0][fields[0].name] === null; - assert(flagNotNull && valueIsNull); - */ + conn.execute( + "SELECT `ac`.`username`, CONCAT('[', GROUP_CONCAT(DISTINCT `acf`.`flag` SEPARATOR ','), ']') flags FROM tmp_account ac LEFT JOIN tmp_account_flags acf ON `acf`.account = `ac`.id LEFT JOIN tmp_account_session acs ON `acs`.account = `ac`.id WHERE `acs`.`session`=?", + ['asid=75efb145482ce22f4544390cad233c749c1b43e4'], + (_err, rows, fields) => { + /* + this assertion is valid for mysql8 < 8.0.17 and not longer valid in 8.0.18 + TODO: investigate why and remove + const flagNotNull = fields[0].flags & FieldFlags.NOT_NULL; + const valueIsNull = rows[0][fields[0].name] === null; + assert(flagNotNull && valueIsNull); + */ - const valueIsNull = rows[0][fields[0].name] === null; - assert(valueIsNull); - conn.end(); - } - ); + const valueIsNull = rows[0][fields[0].name] === null; + assert(valueIsNull); + conn.end(); + resolve(); + } + ); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-buffer-params.test.mts b/test/esm/integration/connection/test-buffer-params.test.mts index 41f18d0b17..4021808365 100644 --- a/test/esm/integration/connection/test-buffer-params.test.mts +++ b/test/esm/integration/connection/test-buffer-params.test.mts @@ -1,43 +1,44 @@ import type { QueryError, RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type BufRow = RowDataPacket & { buf: string }; -const connection = createConnection(); +await describe('Buffer Params', async () => { + const connection = createConnection(); -let rows: BufRow[] | undefined = undefined; -let rows1: BufRow[] | undefined = undefined; + await it('should handle buffer parameters in execute and query', async () => { + let rows: BufRow[] | undefined; + let rows1: BufRow[] | undefined; -const buf = Buffer.from([ - 0x80, 0x90, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100, 100, 255, 255, -]); -connection.execute( - 'SELECT HEX(?) as buf', - [buf], - (err: QueryError | null, _rows) => { - if (err) { - throw err; - } - rows = _rows; - } -); + const buf = Buffer.from([ + 0x80, 0x90, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100, 100, 255, 255, + ]); -connection.query( - 'SELECT HEX(?) as buf', - [buf], - (err: QueryError | null, _rows) => { - if (err) { - throw err; - } - rows1 = _rows; - connection.end(); - } -); + await new Promise((resolve, reject) => { + connection.execute( + 'SELECT HEX(?) as buf', + [buf], + (err: QueryError | null, _rows) => { + if (err) return reject(err); + rows = _rows; + } + ); -process.on('exit', () => { - assert.deepEqual(rows, [{ buf: buf.toString('hex').toUpperCase() }]); - assert.deepEqual(rows1, [{ buf: buf.toString('hex').toUpperCase() }]); + connection.query( + 'SELECT HEX(?) as buf', + [buf], + (err: QueryError | null, _rows) => { + if (err) return reject(err); + rows1 = _rows; + connection.end(); + resolve(); + } + ); + }); + + assert.deepEqual(rows, [{ buf: buf.toString('hex').toUpperCase() }]); + assert.deepEqual(rows1, [{ buf: buf.toString('hex').toUpperCase() }]); + }); }); diff --git a/test/esm/integration/connection/test-change-user-multi-factor.test.mts b/test/esm/integration/connection/test-change-user-multi-factor.test.mts index 7b8ddd9660..0431a63666 100644 --- a/test/esm/integration/connection/test-change-user-multi-factor.test.mts +++ b/test/esm/integration/connection/test-change-user-multi-factor.test.mts @@ -3,7 +3,7 @@ import type { Connection } from '../../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import Command from '../../../../lib/commands/command.js'; @@ -88,74 +88,85 @@ class TestChangeUserMultiFactor extends Command { } } -const server = mysql.createServer((conn: Connection) => { - // @ts-expect-error: TODO: implement typings - conn.serverConfig = {}; - // @ts-expect-error: TODO: implement typings - conn.serverConfig.encoding = 'cesu8'; - // @ts-expect-error: TODO: implement typings - conn.addCommand( - new TestChangeUserMultiFactor([ - { - // already covered by test-auth-switch - pluginName: 'auth_test_plugin1', - pluginData: Buffer.from('foo'), - }, - { - // 2nd factor auth plugin - pluginName: 'auth_test_plugin2', - pluginData: Buffer.from('bar'), - }, - ]) - ); -}); - -const completed: string[] = []; -const password1 = 'secret1'; -const password2 = 'secret2'; - -portfinder.getPort((_: Error | null, port: number) => { - server.listen(port); - const conn = mysql.createConnection({ - port: port, - authPlugins: { - auth_test_plugin1(options: AuthPluginMetadata) { - return () => { - if (options.connection.config.password !== password1) { - return assert.fail('Incorrect authentication factor password.'); - } - - const pluginName = 'auth_test_plugin1'; - completed.push(pluginName); - - return Buffer.from(pluginName); - }; - }, - auth_test_plugin2(options: AuthPluginMetadata) { - return () => { - if (options.connection.config.password !== password2) { - return assert.fail('Incorrect authentication factor password.'); - } - - const pluginName = 'auth_test_plugin2'; - completed.push(pluginName); - - return Buffer.from(pluginName); - }; - }, - }, +await describe('Change User Multi Factor', async () => { + const server = mysql.createServer((conn: Connection) => { + // @ts-expect-error: TODO: implement typings + conn.serverConfig = {}; + // @ts-expect-error: TODO: implement typings + conn.serverConfig.encoding = 'cesu8'; + // @ts-expect-error: TODO: implement typings + conn.addCommand( + new TestChangeUserMultiFactor([ + { + // already covered by test-auth-switch + pluginName: 'auth_test_plugin1', + pluginData: Buffer.from('foo'), + }, + { + // 2nd factor auth plugin + pluginName: 'auth_test_plugin2', + pluginData: Buffer.from('bar'), + }, + ]) + ); }); - conn.on('connect', () => { - conn.changeUser({ password1, password2 }, () => { - assert.deepStrictEqual(completed, [ - 'auth_test_plugin1', - 'auth_test_plugin2', - ]); - - conn.end(); - // @ts-expect-error: TODO: implement typings - server.close(); + const completed: string[] = []; + const password1 = 'secret1'; + const password2 = 'secret2'; + + await it('should handle multi-factor authentication during change user', async () => { + await new Promise((resolve) => { + portfinder.getPort((_: Error | null, port: number) => { + server.listen(port); + const conn = mysql.createConnection({ + port: port, + authPlugins: { + auth_test_plugin1(options: AuthPluginMetadata) { + return () => { + if (options.connection.config.password !== password1) { + return assert.fail( + 'Incorrect authentication factor password.' + ); + } + + const pluginName = 'auth_test_plugin1'; + completed.push(pluginName); + + return Buffer.from(pluginName); + }; + }, + auth_test_plugin2(options: AuthPluginMetadata) { + return () => { + if (options.connection.config.password !== password2) { + return assert.fail( + 'Incorrect authentication factor password.' + ); + } + + const pluginName = 'auth_test_plugin2'; + completed.push(pluginName); + + return Buffer.from(pluginName); + }; + }, + }, + }); + + conn.on('connect', () => { + conn.changeUser({ password1, password2 }, () => { + assert.deepStrictEqual(completed, [ + 'auth_test_plugin1', + 'auth_test_plugin2', + ]); + + conn.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + }); }); }); }); diff --git a/test/esm/integration/connection/test-change-user-plugin-auth.test.mts b/test/esm/integration/connection/test-change-user-plugin-auth.test.mts index 358a02e938..9b89e690da 100644 --- a/test/esm/integration/connection/test-change-user-plugin-auth.test.mts +++ b/test/esm/integration/connection/test-change-user-plugin-auth.test.mts @@ -1,7 +1,7 @@ import type { RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { @@ -9,69 +9,82 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); -const onlyUsername = function (name: string) { - return name.substring(0, name.indexOf('@')); -}; +await describe('Change User Plugin Auth', async () => { + const connection = createConnection(); + const onlyUsername = function (name: string) { + return name.substring(0, name.indexOf('@')); + }; -connection.query( - "CREATE USER IF NOT EXISTS 'changeuser1'@'%' IDENTIFIED BY 'changeuser1pass'" -); -connection.query( - "CREATE USER IF NOT EXISTS 'changeuser2'@'%' IDENTIFIED BY 'changeuser2pass'" -); -connection.query("GRANT ALL ON *.* TO 'changeuser1'@'%'"); -connection.query("GRANT ALL ON *.* TO 'changeuser2'@'%'"); -connection.query('FLUSH PRIVILEGES'); - -connection.changeUser( - { - user: 'changeuser1', - password: 'changeuser1pass', - }, - (err) => { - assert.ifError(err); - connection.query('select current_user()', (err, rows) => { - assert.ifError(err); - assert.deepEqual(onlyUsername(rows[0]['current_user()']), 'changeuser1'); + connection.query( + "CREATE USER IF NOT EXISTS 'changeuser1'@'%' IDENTIFIED BY 'changeuser1pass'" + ); + connection.query( + "CREATE USER IF NOT EXISTS 'changeuser2'@'%' IDENTIFIED BY 'changeuser2pass'" + ); + connection.query("GRANT ALL ON *.* TO 'changeuser1'@'%'"); + connection.query("GRANT ALL ON *.* TO 'changeuser2'@'%'"); + connection.query('FLUSH PRIVILEGES'); + await it('should switch users and verify current_user()', async () => { + await new Promise((resolve, reject) => { connection.changeUser( { - user: 'changeuser2', - password: 'changeuser2pass', + user: 'changeuser1', + password: 'changeuser1pass', }, (err) => { - assert.ifError(err); - + if (err) return reject(err); connection.query( 'select current_user()', (err, rows) => { - assert.ifError(err); + if (err) return reject(err); assert.deepEqual( onlyUsername(rows[0]['current_user()']), - 'changeuser2' + 'changeuser1' ); connection.changeUser( { - user: 'changeuser1', - password: 'changeuser1pass', - // @ts-expect-error: TODO: implement typings - passwordSha1: Buffer.from( - 'f961d39c82138dcec42b8d0dcb3e40a14fb7e8cd', - 'hex' - ), // sha1(changeuser1pass) + user: 'changeuser2', + password: 'changeuser2pass', }, - () => { + (err) => { + if (err) return reject(err); + connection.query( 'select current_user()', (err, rows) => { - assert.ifError(err); + if (err) return reject(err); assert.deepEqual( onlyUsername(rows[0]['current_user()']), - 'changeuser1' + 'changeuser2' + ); + + connection.changeUser( + { + user: 'changeuser1', + password: 'changeuser1pass', + // @ts-expect-error: TODO: implement typings + passwordSha1: Buffer.from( + 'f961d39c82138dcec42b8d0dcb3e40a14fb7e8cd', + 'hex' + ), // sha1(changeuser1pass) + }, + () => { + connection.query( + 'select current_user()', + (err, rows) => { + if (err) return reject(err); + assert.deepEqual( + onlyUsername(rows[0]['current_user()']), + 'changeuser1' + ); + connection.end(); + resolve(); + } + ); + } ); - connection.end(); } ); } @@ -81,5 +94,5 @@ connection.changeUser( } ); }); - } -); + }); +}); diff --git a/test/esm/integration/connection/test-change-user.test.mts b/test/esm/integration/connection/test-change-user.test.mts index 358a02e938..b163e9a143 100644 --- a/test/esm/integration/connection/test-change-user.test.mts +++ b/test/esm/integration/connection/test-change-user.test.mts @@ -1,7 +1,7 @@ import type { RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { @@ -9,69 +9,82 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); -const onlyUsername = function (name: string) { - return name.substring(0, name.indexOf('@')); -}; +await describe('Change User', async () => { + const connection = createConnection(); + const onlyUsername = function (name: string) { + return name.substring(0, name.indexOf('@')); + }; -connection.query( - "CREATE USER IF NOT EXISTS 'changeuser1'@'%' IDENTIFIED BY 'changeuser1pass'" -); -connection.query( - "CREATE USER IF NOT EXISTS 'changeuser2'@'%' IDENTIFIED BY 'changeuser2pass'" -); -connection.query("GRANT ALL ON *.* TO 'changeuser1'@'%'"); -connection.query("GRANT ALL ON *.* TO 'changeuser2'@'%'"); -connection.query('FLUSH PRIVILEGES'); - -connection.changeUser( - { - user: 'changeuser1', - password: 'changeuser1pass', - }, - (err) => { - assert.ifError(err); - connection.query('select current_user()', (err, rows) => { - assert.ifError(err); - assert.deepEqual(onlyUsername(rows[0]['current_user()']), 'changeuser1'); + connection.query( + "CREATE USER IF NOT EXISTS 'changeuser1'@'%' IDENTIFIED BY 'changeuser1pass'" + ); + connection.query( + "CREATE USER IF NOT EXISTS 'changeuser2'@'%' IDENTIFIED BY 'changeuser2pass'" + ); + connection.query("GRANT ALL ON *.* TO 'changeuser1'@'%'"); + connection.query("GRANT ALL ON *.* TO 'changeuser2'@'%'"); + connection.query('FLUSH PRIVILEGES'); + await it('should switch users and verify current_user()', async () => { + await new Promise((resolve, reject) => { connection.changeUser( { - user: 'changeuser2', - password: 'changeuser2pass', + user: 'changeuser1', + password: 'changeuser1pass', }, (err) => { - assert.ifError(err); - + if (err) return reject(err); connection.query( 'select current_user()', (err, rows) => { - assert.ifError(err); + if (err) return reject(err); assert.deepEqual( onlyUsername(rows[0]['current_user()']), - 'changeuser2' + 'changeuser1' ); connection.changeUser( { - user: 'changeuser1', - password: 'changeuser1pass', - // @ts-expect-error: TODO: implement typings - passwordSha1: Buffer.from( - 'f961d39c82138dcec42b8d0dcb3e40a14fb7e8cd', - 'hex' - ), // sha1(changeuser1pass) + user: 'changeuser2', + password: 'changeuser2pass', }, - () => { + (err) => { + if (err) return reject(err); + connection.query( 'select current_user()', (err, rows) => { - assert.ifError(err); + if (err) return reject(err); assert.deepEqual( onlyUsername(rows[0]['current_user()']), - 'changeuser1' + 'changeuser2' + ); + + connection.changeUser( + { + user: 'changeuser1', + password: 'changeuser1pass', + // @ts-expect-error: TODO: implement typings + passwordSha1: Buffer.from( + 'f961d39c82138dcec42b8d0dcb3e40a14fb7e8cd', + 'hex' + ), // sha1(changeuser1pass) + }, + () => { + connection.query( + 'select current_user()', + (err, rows) => { + if (err) return reject(err); + assert.deepEqual( + onlyUsername(rows[0]['current_user()']), + 'changeuser1' + ); + connection.end(); + resolve(); + } + ); + } ); - connection.end(); } ); } @@ -81,5 +94,5 @@ connection.changeUser( } ); }); - } -); + }); +}); diff --git a/test/esm/integration/connection/test-charset-encoding.test.mts b/test/esm/integration/connection/test-charset-encoding.test.mts index 20a9a368ad..757e39767b 100644 --- a/test/esm/integration/connection/test-charset-encoding.test.mts +++ b/test/esm/integration/connection/test-charset-encoding.test.mts @@ -1,64 +1,62 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type CharsetRow = RowDataPacket & { field: string }; -const connection = createConnection(); - -// test data stores -const testData = [ - 'ютф восемь', - 'Experimental', - 'परीक्षण', - 'test тест テスト փորձաsրկում পরীক্ষা kiểm tra', - 'ტესტი પરીક્ષણ מבחן פּרובירן اختبار', -]; - -let resultData: CharsetRow[] | null = null; - -// test inserting of non latin data if we are able to parse it - -const testEncoding = function (err: NodeJS.ErrnoException | null) { - assert.ifError(err); - - testData.forEach((data) => { - connection.query( - 'INSERT INTO `test-charset-encoding` (field) values(?)', - [data], - (err2) => { - assert.ifError(err2); - } - ); - }); - - connection.query( - 'SELECT * from `test-charset-encoding`', - (err, results) => { - assert.ifError(err); - resultData = results; - } - ); - connection.end(); -}; - -// init test sequence -(function () { - connection.query('DROP TABLE IF EXISTS `test-charset-encoding`', () => { - connection.query( - 'CREATE TABLE IF NOT EXISTS `test-charset-encoding` ' + - '( `field` VARCHAR(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci)', - (err) => { - assert.ifError(err); - connection.query('DELETE from `test-charset-encoding`', testEncoding); - } - ); - }); -})(); - -process.on('exit', () => { - resultData?.forEach((data, index) => { - assert.equal(data.field, testData[index]); +await describe('Charset Encoding', async () => { + const connection = createConnection(); + + // test data stores + const testData = [ + 'ютф восемь', + 'Experimental', + 'परीक्षण', + 'test тест テスト փորձաsրկում পরীক্ষা kiểm tra', + 'ტესტი પરીક્ષણ מבחן פּרובירן اختبار', + ]; + + // test inserting of non latin data if we are able to parse it + await it('should preserve non-latin character encoding', async () => { + let resultData: CharsetRow[] | undefined; + + await new Promise((resolve, reject) => { + connection.query('DROP TABLE IF EXISTS `test-charset-encoding`', () => { + connection.query( + 'CREATE TABLE IF NOT EXISTS `test-charset-encoding` ' + + '( `field` VARCHAR(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci)', + (err) => { + if (err) return reject(err); + connection.query('DELETE from `test-charset-encoding`', (err) => { + if (err) return reject(err); + + testData.forEach((data) => { + connection.query( + 'INSERT INTO `test-charset-encoding` (field) values(?)', + [data], + (err2) => { + if (err2) return reject(err2); + } + ); + }); + + connection.query( + 'SELECT * from `test-charset-encoding`', + (err, results) => { + if (err) return reject(err); + resultData = results; + connection.end(); + resolve(); + } + ); + }); + } + ); + }); + }); + + resultData?.forEach((data: CharsetRow, index: number) => { + assert.equal(data.field, testData[index]); + }); }); }); diff --git a/test/esm/integration/connection/test-connect-after-connection-error.test.mts b/test/esm/integration/connection/test-connect-after-connection-error.test.mts index 206640c3f8..9cdc02e8d2 100644 --- a/test/esm/integration/connection/test-connect-after-connection-error.test.mts +++ b/test/esm/integration/connection/test-connect-after-connection-error.test.mts @@ -1,47 +1,55 @@ import type { Connection, QueryError } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const ERROR_TEXT = 'Connection lost: The server closed the connection.'; +await describe('Connect After Connection Error', async () => { + const ERROR_TEXT = 'Connection lost: The server closed the connection.'; -portfinder.getPort((_err: Error | null, port: number) => { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - let serverConnection: Connection | undefined; - server.listen(port); - server.on('connection', (conn: Connection) => { - conn.serverHandshake({ - serverVersion: '5.6.10', - capabilityFlags: 2181036031, - }); - serverConnection = conn; - }); + await it('should return error when connecting after server close', async () => { + await new Promise((resolve) => { + portfinder.getPort((_err: Error | null, port: number) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + let serverConnection: Connection | undefined; + server.listen(port); + server.on('connection', (conn: Connection) => { + conn.serverHandshake({ + serverVersion: '5.6.10', + capabilityFlags: 2181036031, + }); + serverConnection = conn; + }); - const clientConnection = mysql.createConnection({ - host: 'localhost', - port: port, - user: 'testuser', - database: 'testdatabase', - password: 'testpassword', - }); + const clientConnection = mysql.createConnection({ + host: 'localhost', + port: port, + user: 'testuser', + database: 'testdatabase', + password: 'testpassword', + }); - clientConnection.on('connect', () => { - // @ts-expect-error: TODO: implement typings - serverConnection.close(); - }); + clientConnection.on('connect', () => { + // @ts-expect-error: TODO: implement typings + serverConnection.close(); + }); - clientConnection.once('error', () => { - clientConnection.connect((err: QueryError | null) => { - assert.equal(err?.message, ERROR_TEXT); - // @ts-expect-error: TODO: implement typings - clientConnection.close(); - // @ts-expect-error: internal access - server._server.close(); + clientConnection.once('error', () => { + clientConnection.connect((err: QueryError | null) => { + assert.equal(err?.message, ERROR_TEXT); + // @ts-expect-error: TODO: implement typings + clientConnection.close(); + // @ts-expect-error: internal access + server._server.close(() => { + resolve(); + }); + }); + }); + }); }); }); }); diff --git a/test/esm/integration/connection/test-connect-after-connection.test.mts b/test/esm/integration/connection/test-connect-after-connection.test.mts index d04286c242..27cf028ab5 100644 --- a/test/esm/integration/connection/test-connect-after-connection.test.mts +++ b/test/esm/integration/connection/test-connect-after-connection.test.mts @@ -1,23 +1,27 @@ import type { Connection, QueryError } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Connect After Connection', async () => { + const connection = createConnection(); -let connection2: Connection | undefined; + await it('should return same connection on second connect call', async () => { + let connection2: Connection | undefined; -connection.once('connect', () => { - // @ts-expect-error: TODO: implement typings - connection.connect((err: QueryError | null, _connection: Connection) => { - if (err) { - throw err; - } - connection2 = _connection; - connection.end(); - }); -}); + await new Promise((resolve, reject) => { + connection.once('connect', () => { + connection.connect( + // @ts-expect-error: TODO: implement typings + (err: QueryError | null, _connection: Connection) => { + if (err) return reject(err); + connection2 = _connection; + connection.end(); + resolve(); + } + ); + }); + }); -process.on('exit', () => { - assert.equal(connection, connection2); + assert.equal(connection, connection2); + }); }); diff --git a/test/esm/integration/connection/test-connect-connection-closed-error.test.mts b/test/esm/integration/connection/test-connect-connection-closed-error.test.mts index 48f1ae5595..045212c012 100644 --- a/test/esm/integration/connection/test-connect-connection-closed-error.test.mts +++ b/test/esm/integration/connection/test-connect-connection-closed-error.test.mts @@ -1,34 +1,42 @@ import type { Connection } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const ERROR_TEXT = 'Connection lost: The server closed the connection.'; +await describe('Connect Connection Closed Error', async () => { + const ERROR_TEXT = 'Connection lost: The server closed the connection.'; -portfinder.getPort((_err, port) => { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - server.listen(port); - server.on('connection', (conn: Connection) => { - // @ts-expect-error: TODO: implement typings - conn.close(); - }); + await it('should return error when server closes connection', async () => { + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + server.listen(port); + server.on('connection', (conn: Connection) => { + // @ts-expect-error: TODO: implement typings + conn.close(); + }); - const connection = mysql.createConnection({ - host: 'localhost', - port: port, - user: 'testuser', - database: 'testdatabase', - password: 'testpassword', - }); + const connection = mysql.createConnection({ + host: 'localhost', + port: port, + user: 'testuser', + database: 'testdatabase', + password: 'testpassword', + }); - connection.query('select 1', (err) => { - assert.equal(err?.message, ERROR_TEXT); - // @ts-expect-error: internal access - server._server.close(); + connection.query('select 1', (err) => { + assert.equal(err?.message, ERROR_TEXT); + // @ts-expect-error: internal access + server._server.close(() => { + resolve(); + }); + }); + }); + }); }); }); diff --git a/test/esm/integration/connection/test-connect-sha1.test.mts b/test/esm/integration/connection/test-connect-sha1.test.mts index e435ca0968..71c685f91f 100644 --- a/test/esm/integration/connection/test-connect-sha1.test.mts +++ b/test/esm/integration/connection/test-connect-sha1.test.mts @@ -1,7 +1,7 @@ import type { Connection, QueryError } from '../../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import auth from '../../../../lib/auth_41.js'; @@ -27,67 +27,71 @@ function authenticate(params: AuthParams, cb: (err: Error | null) => void) { cb(null); } -let _1_2 = false; -let _1_3 = false; +await describe('Connect SHA1', async () => { + await it('should authenticate with SHA1 password', async () => { + let _1_2 = false; + let _1_3 = false; + let queryCalls = 0; -let queryCalls = 0; + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + server.listen(port); + server.on('connection', (conn: Connection) => { + conn.serverHandshake({ + protocolVersion: 10, + serverVersion: 'node.js rocks', + connectionId: 1234, + statusFlags: 2, + characterSet: 8, + capabilityFlags: 0xffffff, + authCallback: authenticate, + }); + conn.on('query', (sql: string) => { + assert.equal(sql, 'select 1+1'); + queryCalls++; + // @ts-expect-error: TODO: implement typings + conn.close(); + }); + }); -portfinder.getPort((_err, port) => { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - server.listen(port); - server.on('connection', (conn: Connection) => { - conn.serverHandshake({ - protocolVersion: 10, - serverVersion: 'node.js rocks', - connectionId: 1234, - statusFlags: 2, - characterSet: 8, - capabilityFlags: 0xffffff, - authCallback: authenticate, - }); - conn.on('query', (sql: string) => { - assert.equal(sql, 'select 1+1'); - queryCalls++; - // @ts-expect-error: TODO: implement typings - conn.close(); - }); - }); + // @ts-expect-error: TODO: implement typings + const connection = mysql.createConnection({ + port: port, + user: 'testuser', + database: 'testdatabase', + passwordSha1: Buffer.from( + '8bb6118f8fd6935ad0876a3be34a717d32708ffd', + 'hex' + ), + }); - // @ts-expect-error: TODO: implement typings - const connection = mysql.createConnection({ - port: port, - user: 'testuser', - database: 'testdatabase', - passwordSha1: Buffer.from( - '8bb6118f8fd6935ad0876a3be34a717d32708ffd', - 'hex' - ), - }); + connection.on('error', (err: QueryError) => { + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); + }); - connection.on('error', (err: QueryError) => { - assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); - }); + connection.query('select 1+1', (err: QueryError | null) => { + assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); + // @ts-expect-error: internal access + server._server.close(); + }); - connection.query('select 1+1', (err: QueryError | null) => { - assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); - // @ts-expect-error: internal access - server._server.close(); - }); + connection.query('select 1+2', (err: QueryError | null) => { + assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); + _1_2 = true; + }); - connection.query('select 1+2', (err: QueryError | null) => { - assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); - _1_2 = true; - }); + connection.query('select 1+3', (err: QueryError | null) => { + assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); + _1_3 = true; + resolve(); + }); + }); + }); - connection.query('select 1+3', (err: QueryError | null) => { - assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); - _1_3 = true; + assert.equal(queryCalls, 1); + assert.equal(_1_2, true); + assert.equal(_1_3, true); }); }); - -process.on('exit', () => { - assert.equal(queryCalls, 1); - assert.equal(_1_2, true); - assert.equal(_1_3, true); -}); diff --git a/test/esm/integration/connection/test-connect-time-error.test.mts b/test/esm/integration/connection/test-connect-time-error.test.mts index d33554329a..af701fb5ae 100644 --- a/test/esm/integration/connection/test-connect-time-error.test.mts +++ b/test/esm/integration/connection/test-connect-time-error.test.mts @@ -1,41 +1,49 @@ import type { Connection } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const ERROR_TEXT = 'test error'; +await describe('Connect Time Error', async () => { + const ERROR_TEXT = 'test error'; -portfinder.getPort((_err, port) => { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - server.listen(port); - server.on('connection', (conn: Connection) => { - conn.writeError(new Error(ERROR_TEXT)); - // @ts-expect-error: TODO: implement typings - conn.close(); - }); + await it('should return error from server', async () => { + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + server.listen(port); + server.on('connection', (conn: Connection) => { + conn.writeError(new Error(ERROR_TEXT)); + // @ts-expect-error: TODO: implement typings + conn.close(); + }); - const connection = mysql.createConnection({ - host: 'localhost', - port: port, - user: 'testuser', - database: 'testdatabase', - password: 'testpassword', - }); + const connection = mysql.createConnection({ + host: 'localhost', + port: port, + user: 'testuser', + database: 'testdatabase', + password: 'testpassword', + }); - connection.query('select 1+1', (err) => { - assert.equal(err?.message, ERROR_TEXT); - }); + connection.query('select 1+1', (err) => { + assert.equal(err?.message, ERROR_TEXT); + }); - connection.query('select 1+2', (err) => { - assert.equal(err?.message, ERROR_TEXT); - // @ts-expect-error: TODO: implement typings - connection.close(); - // @ts-expect-error: internal access - server._server.close(); + connection.query('select 1+2', (err) => { + assert.equal(err?.message, ERROR_TEXT); + // @ts-expect-error: TODO: implement typings + connection.close(); + // @ts-expect-error: internal access + server._server.close(() => { + resolve(); + }); + }); + }); + }); }); }); diff --git a/test/esm/integration/connection/test-connect-with-uri.test.mts b/test/esm/integration/connection/test-connect-with-uri.test.mts index badcbad339..1489bedef1 100644 --- a/test/esm/integration/connection/test-connect-with-uri.test.mts +++ b/test/esm/integration/connection/test-connect-with-uri.test.mts @@ -1,6 +1,6 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnectionWithURI } from '../../common.test.mjs'; if (process.env.MYSQL_CONNECTION_URL) { @@ -10,21 +10,24 @@ if (process.env.MYSQL_CONNECTION_URL) { process.exit(0); } -const connection = createConnectionWithURI(); +await describe('Connect With URI', async () => { + const connection = createConnectionWithURI(); -let rows: RowDataPacket[] | undefined = undefined; -let fields: FieldPacket[] | undefined = undefined; -connection.query('SELECT 1', (err, _rows, _fields) => { - if (err) { - throw err; - } + await it('should connect and query using URI', async () => { + let rows: RowDataPacket[] | undefined; + let fields: FieldPacket[] | undefined; - rows = _rows; - fields = _fields; - connection.end(); -}); + await new Promise((resolve, reject) => { + connection.query('SELECT 1', (err, _rows, _fields) => { + if (err) return reject(err); + rows = _rows; + fields = _fields; + connection.end(); + resolve(); + }); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ 1: 1 }]); - assert.equal(fields?.[0].name, '1'); + assert.deepEqual(rows, [{ 1: 1 }]); + assert.equal(fields?.[0].name, '1'); + }); }); diff --git a/test/esm/integration/connection/test-connection-reset-while-closing.test.mts b/test/esm/integration/connection/test-connection-reset-while-closing.test.mts index 8cc090e3ef..d87ab1baac 100644 --- a/test/esm/integration/connection/test-connection-reset-while-closing.test.mts +++ b/test/esm/integration/connection/test-connection-reset-while-closing.test.mts @@ -1,29 +1,36 @@ import type { RowDataPacket } from '../../../../index.js'; import assert from 'node:assert'; -import process from 'node:process'; +import { describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const error = new Error('read ECONNRESET') as Error & { - code?: string; - errno?: number; - syscall?: string; -}; -error.code = 'ECONNRESET'; -error.errno = -54; -error.syscall = 'read'; +await describe('Connection Reset While Closing', async () => { + const error = new Error('read ECONNRESET') as Error & { + code?: string; + errno?: number; + syscall?: string; + }; + error.code = 'ECONNRESET'; + error.errno = -54; + error.syscall = 'read'; -const connection = createConnection(); + // Test that we ignore a ECONNRESET error if the connection + // is already closing, we close and then emit the error + await it('should ignore ECONNRESET when connection is closing', async () => { + const connection = createConnection(); -// Test that we ignore a ECONNRESET error if the connection -// is already closing, we close and then emit the error -connection.query(`select 1 as "1"`, (_err, rows) => { - assert.equal(rows[0]['1'], 1); - // @ts-expect-error: TODO: implement typings - connection.close(); - // @ts-expect-error: TODO: implement typings - connection.stream.emit('error', error); -}); + connection.on('error', (err: Error & { code?: string }) => { + assert.notEqual(err.code, 'ECONNRESET'); + }); -process.on('uncaughtException', (err: Error & { code?: string }) => { - assert.notEqual(err.code, 'ECONNRESET'); + await new Promise((resolve) => { + connection.query(`select 1 as "1"`, (_err, rows) => { + assert.equal(rows[0]['1'], 1); + // @ts-expect-error: TODO: implement typings + connection.close(); + // @ts-expect-error: TODO: implement typings + connection.stream.emit('error', error); + resolve(); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-custom-date-parameter.test.mts b/test/esm/integration/connection/test-custom-date-parameter.test.mts index 639f43d3b2..165ff431a9 100644 --- a/test/esm/integration/connection/test-custom-date-parameter.test.mts +++ b/test/esm/integration/connection/test-custom-date-parameter.test.mts @@ -1,36 +1,39 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection({ timezone: 'Z' }); +await describe('Custom Date Parameter', async () => { + const connection = createConnection({ timezone: 'Z' }); -let rows: RowDataPacket[] | undefined = undefined; + // @ts-expect-error: intentionally replacing global Date for testing + // eslint-disable-next-line no-global-assign + Date = (function () { + const NativeDate = Date; + function CustomDate(str: string) { + return new NativeDate(str); + } + CustomDate.now = Date.now; + return CustomDate; + })(); -// @ts-expect-error: intentionally replacing global Date for testing -// eslint-disable-next-line no-global-assign -Date = (function () { - const NativeDate = Date; - function CustomDate(str: string) { - return new NativeDate(str); - } - CustomDate.now = Date.now; - return CustomDate; -})(); + await it('should handle custom Date constructor', async () => { + let rows: RowDataPacket[] | undefined; -connection.query("set time_zone = '+00:00'"); -connection.execute( - 'SELECT UNIX_TIMESTAMP(?) t', - [new Date('1990-08-08 UTC')], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + connection.query("set time_zone = '+00:00'"); + + await new Promise((resolve, reject) => { + connection.execute( + 'SELECT UNIX_TIMESTAMP(?) t', + [new Date('1990-08-08 UTC')], + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + connection.end(); + resolve(); + } + ); + }); -process.on('exit', () => { - assert.equal(rows?.[0].t, 650073600); + assert.equal(rows?.[0].t, 650073600); + }); }); diff --git a/test/esm/integration/connection/test-date-parameter.test.mts b/test/esm/integration/connection/test-date-parameter.test.mts index 635873aae9..e4e24ba030 100644 --- a/test/esm/integration/connection/test-date-parameter.test.mts +++ b/test/esm/integration/connection/test-date-parameter.test.mts @@ -1,25 +1,28 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection({ timezone: 'Z' }); +await describe('Date Parameter', async () => { + const connection = createConnection({ timezone: 'Z' }); -let rows: RowDataPacket[] | undefined = undefined; + await it('should handle date parameter in execute', async () => { + let rows: RowDataPacket[] | undefined; -connection.query("set time_zone = '+00:00'"); -connection.execute( - 'SELECT UNIX_TIMESTAMP(?) t', - [new Date('1990-01-01 UTC')], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + connection.query("set time_zone = '+00:00'"); -process.on('exit', () => { - assert.deepEqual(rows, [{ t: 631152000 }]); + await new Promise((resolve, reject) => { + connection.execute( + 'SELECT UNIX_TIMESTAMP(?) t', + [new Date('1990-01-01 UTC')], + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + connection.end(); + resolve(); + } + ); + }); + + assert.deepEqual(rows, [{ t: 631152000 }]); + }); }); diff --git a/test/esm/integration/connection/test-datetime.test.mts b/test/esm/integration/connection/test-datetime.test.mts index 443d30fa3d..ae81a6a28a 100644 --- a/test/esm/integration/connection/test-datetime.test.mts +++ b/test/esm/integration/connection/test-datetime.test.mts @@ -1,337 +1,332 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); -const connection1 = createConnection({ dateStrings: true }); -const connection2 = createConnection({ dateStrings: ['DATE'] }); -const connectionZ = createConnection({ timezone: 'Z' }); -const connection0930 = createConnection({ timezone: '+09:30' }); - -let rows: RowDataPacket[], - rowsZ: RowDataPacket[], - rows0930: RowDataPacket[], - rows1: RowDataPacket[], - rows1Z: RowDataPacket[], - rows10930: RowDataPacket[], - rows2: RowDataPacket[], - rows3: RowDataPacket[], - rows4: RowDataPacket[], - rows5: RowDataPacket[], - rows6: RowDataPacket[], - rows7: RowDataPacket[], - rows8: RowDataPacket[]; - -const date = new Date('1990-01-01 08:15:11 UTC'); -const datetime = new Date('2010-12-10 14:12:09.019473'); - -const date1 = new Date('2000-03-03 08:15:11 UTC'); -const date2 = '2010-12-10 14:12:09.019473'; -const date3 = null; -const date4 = '2010-12-10 14:12:09.123456'; -const date5 = '2010-12-10 14:12:09.019'; -const date6 = '2024-11-10 00:00:00'; - -function adjustTZ(d: Date, offset?: number) { - if (offset === undefined) { - offset = d.getTimezoneOffset(); - } - return new Date(d.getTime() - offset * 60000); -} - -function toMidnight(d: Date, offset?: number) { - const t = d.getTime(); - if (offset === undefined) { - offset = d.getTimezoneOffset(); - } - return new Date(t - (t % (24 * 60 * 60 * 1000)) + offset * 60000); -} - -function formatUTCDate(d: Date) { - return d.toISOString().substring(0, 10); -} - -function formatUTCDateTime(d: Date, precision?: number) { - const raw = d.toISOString().replace('T', ' '); - if (precision === undefined) { - precision = 0; - } - return precision <= 3 - ? raw.substring(0, 19 + (precision && 1) + precision) - : raw.substring(0, 23) + '0'.repeat(precision - 3); -} - -connection.query( - 'CREATE TEMPORARY TABLE t (d1 DATE, d2 DATETIME(3), d3 DATETIME(6))' -); -connection.query('INSERT INTO t set d1=?, d2=?, d3=?', [ - date, - datetime, - datetime, -]); - -connection1.query( - 'CREATE TEMPORARY TABLE t (d1 DATE, d2 TIMESTAMP, d3 DATETIME, d4 DATETIME, d5 DATETIME(6), d6 DATETIME(3), d7 DATETIME)' -); -connection1.query( - 'INSERT INTO t set d1=?, d2=?, d3=?, d4=?, d5=?, d6=?, d7=?', - [date, date1, date2, date3, date4, date5, date6] -); - -connection2.query( - 'CREATE TEMPORARY TABLE t (d1 DATE, d2 TIMESTAMP, d3 DATETIME, d4 DATETIME, d5 DATETIME(6), d6 DATETIME(3), d7 DATETIME)' -); -connection2.query( - 'INSERT INTO t set d1=?, d2=?, d3=?, d4=?, d5=?, d6=?, d7=?', - [date, date1, date2, date3, date4, date5, date6] -); - -connectionZ.query( - 'CREATE TEMPORARY TABLE t (d1 DATE, d2 DATETIME(3), d3 DATETIME(6))' -); -connectionZ.query("set time_zone = '+00:00'"); -connectionZ.query('INSERT INTO t set d1=?, d2=?, d3=?', [ - date, - datetime, - datetime, -]); - -connection0930.query( - 'CREATE TEMPORARY TABLE t (d1 DATE, d2 DATETIME(3), d3 DATETIME(6))' -); -connection0930.query("set time_zone = '+09:30'"); -connection0930.query('INSERT INTO t set d1=?, d2=?, d3=?', [ - date, - datetime, - datetime, -]); - -const dateAsStringExpected = [ - { - d1: formatUTCDate(adjustTZ(date)), - d2: formatUTCDateTime(adjustTZ(date1)), - d3: date2.substring(0, 19), - d4: date3, - d5: date4, - d6: date5, - d7: date6, - }, -]; - -connection.execute( - 'select from_unixtime(?) t', - [(+date).valueOf() / 1000], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - } -); - -connectionZ.execute( - 'select from_unixtime(?) t', - [(+date).valueOf() / 1000], - (err, _rows) => { - if (err) { - throw err; - } - rowsZ = _rows; - } -); - -connection0930.execute( - 'select from_unixtime(?) t', - [(+date).valueOf() / 1000], - (err, _rows) => { - if (err) { - throw err; - } - rows0930 = _rows; - } -); - -connection.query( - 'select from_unixtime(631152000) t', - (err, _rows) => { - if (err) { - throw err; - } - rows1 = _rows; - } -); - -connectionZ.query( - 'select from_unixtime(631152000) t', - (err, _rows) => { - if (err) { - throw err; - } - rows1Z = _rows; - } -); - -connection0930.query( - 'select from_unixtime(631152000) t', - (err, _rows) => { - if (err) { - throw err; +await describe('Datetime', async () => { + const connection = createConnection(); + const connection1 = createConnection({ dateStrings: true }); + const connection2 = createConnection({ dateStrings: ['DATE'] }); + const connectionZ = createConnection({ timezone: 'Z' }); + const connection0930 = createConnection({ timezone: '+09:30' }); + + let rows: RowDataPacket[], + rowsZ: RowDataPacket[], + rows0930: RowDataPacket[], + rows1: RowDataPacket[], + rows1Z: RowDataPacket[], + rows10930: RowDataPacket[], + rows2: RowDataPacket[], + rows3: RowDataPacket[], + rows4: RowDataPacket[], + rows5: RowDataPacket[], + rows6: RowDataPacket[], + rows7: RowDataPacket[], + rows8: RowDataPacket[]; + + const date = new Date('1990-01-01 08:15:11 UTC'); + const datetime = new Date('2010-12-10 14:12:09.019473'); + + const date1 = new Date('2000-03-03 08:15:11 UTC'); + const date2 = '2010-12-10 14:12:09.019473'; + const date3 = null; + const date4 = '2010-12-10 14:12:09.123456'; + const date5 = '2010-12-10 14:12:09.019'; + const date6 = '2024-11-10 00:00:00'; + + function adjustTZ(d: Date, offset?: number) { + if (offset === undefined) { + offset = d.getTimezoneOffset(); } - rows10930 = _rows; + return new Date(d.getTime() - offset * 60000); } -); -connection.query( - 'select *, cast(d1 as char) as d4, cast(d2 as char) as d5, cast(d3 as char) as d6 from t', - (err, _rows) => { - if (err) { - throw err; + function toMidnight(d: Date, offset?: number) { + const t = d.getTime(); + if (offset === undefined) { + offset = d.getTimezoneOffset(); } - rows2 = _rows; - connection.end(); + return new Date(t - (t % (24 * 60 * 60 * 1000)) + offset * 60000); } -); -connectionZ.execute( - 'select *, cast(d1 as char) as d4, cast(d2 as char) as d5, cast(d3 as char) as d6 from t', - (err, _rows) => { - if (err) { - throw err; - } - rows3 = _rows; - connectionZ.end(); + function formatUTCDate(d: Date) { + return d.toISOString().substring(0, 10); } -); - -connection1.query('select * from t', (err, _rows) => { - if (err) { - throw err; - } - rows4 = _rows; -}); -connection1.execute('select * from t', (err, _rows) => { - if (err) { - throw err; - } - rows5 = _rows; -}); - -connection1.execute( - 'select * from t where d6 = ?', - [new Date(date5)], - (err, _rows) => { - if (err) { - throw err; + function formatUTCDateTime(d: Date, precision?: number) { + const raw = d.toISOString().replace('T', ' '); + if (precision === undefined) { + precision = 0; } - rows6 = _rows; - connection1.end(); - } -); - -connection2.execute('select * from t', (err, _rows) => { - if (err) { - throw err; + return precision <= 3 + ? raw.substring(0, 19 + (precision && 1) + precision) + : raw.substring(0, 23) + '0'.repeat(precision - 3); } - rows8 = _rows; - connection2.end(); -}); -connection0930.execute( - 'select *, cast(d1 as char) as d4, cast(d2 as char) as d5, cast(d3 as char) as d6 from t', - (err, _rows) => { - if (err) { - throw err; - } - rows7 = _rows; - connection0930.end(); - } -); - -process.on('exit', () => { - const connBadTz = createConnection({ timezone: 'utc' }); - assert.equal(connBadTz.config.timezone, 'Z'); - connBadTz.end(); - - // local TZ - assert.equal(rows[0].t.constructor, Date); - assert.equal(rows[0].t.getDate(), date.getDate()); - assert.equal(rows[0].t.getHours(), date.getHours()); - assert.equal(rows[0].t.getMinutes(), date.getMinutes()); - assert.equal(rows[0].t.getSeconds(), date.getSeconds()); - - // UTC - assert.equal(rowsZ[0].t.constructor, Date); - assert.equal(rowsZ[0].t.getDate(), date.getDate()); - assert.equal(rowsZ[0].t.getHours(), date.getHours()); - assert.equal(rowsZ[0].t.getMinutes(), date.getMinutes()); - assert.equal(rowsZ[0].t.getSeconds(), date.getSeconds()); - - // +09:30 - assert.equal(rows0930[0].t.constructor, Date); - assert.equal(rows0930[0].t.getDate(), date.getDate()); - assert.equal(rows0930[0].t.getHours(), date.getHours()); - assert.equal(rows0930[0].t.getMinutes(), date.getMinutes()); - assert.equal(rows0930[0].t.getSeconds(), date.getSeconds()); - - // local TZ - assert.equal(rows1[0].t.constructor, Date); - assert.equal( - rows1[0].t.getTime(), - new Date('Mon Jan 01 1990 00:00:00 UTC').getTime() + connection.query( + 'CREATE TEMPORARY TABLE t (d1 DATE, d2 DATETIME(3), d3 DATETIME(6))' ); - - // UTC - assert.equal(rows1Z[0].t.constructor, Date); - assert.equal( - rows1Z[0].t.getTime(), - new Date('Mon Jan 01 1990 00:00:00 UTC').getTime() + connection.query('INSERT INTO t set d1=?, d2=?, d3=?', [ + date, + datetime, + datetime, + ]); + + connection1.query( + 'CREATE TEMPORARY TABLE t (d1 DATE, d2 TIMESTAMP, d3 DATETIME, d4 DATETIME, d5 DATETIME(6), d6 DATETIME(3), d7 DATETIME)' + ); + connection1.query( + 'INSERT INTO t set d1=?, d2=?, d3=?, d4=?, d5=?, d6=?, d7=?', + [date, date1, date2, date3, date4, date5, date6] ); - // +09:30 - assert.equal(rows10930[0].t.constructor, Date); - assert.equal( - rows10930[0].t.getTime(), - new Date('Mon Jan 01 1990 00:00:00 UTC').getTime() + connection2.query( + 'CREATE TEMPORARY TABLE t (d1 DATE, d2 TIMESTAMP, d3 DATETIME, d4 DATETIME, d5 DATETIME(6), d6 DATETIME(3), d7 DATETIME)' + ); + connection2.query( + 'INSERT INTO t set d1=?, d2=?, d3=?, d4=?, d5=?, d6=?, d7=?', + [date, date1, date2, date3, date4, date5, date6] ); - // local TZ - assert.equal(rows2[0].d1.getTime(), toMidnight(date).getTime()); - assert.equal(rows2[0].d2.getTime(), datetime.getTime()); - assert.equal(rows2[0].d3.getTime(), datetime.getTime()); - assert.equal(rows2[0].d4, formatUTCDate(adjustTZ(date))); - assert.equal(rows2[0].d5, formatUTCDateTime(adjustTZ(datetime), 3)); - assert.equal(rows2[0].d6, formatUTCDateTime(adjustTZ(datetime), 6)); - - // UTC - assert.equal(rows3[0].d1.getTime(), toMidnight(date, 0).getTime()); - assert.equal(rows3[0].d2.getTime(), datetime.getTime()); - assert.equal(rows3[0].d3.getTime(), datetime.getTime()); - assert.equal(rows3[0].d4, formatUTCDate(date)); - assert.equal(rows3[0].d5, formatUTCDateTime(datetime, 3)); - assert.equal(rows3[0].d6, formatUTCDateTime(datetime, 6)); - - // dateStrings - assert.deepEqual(rows4, dateAsStringExpected); - assert.deepEqual(rows5, dateAsStringExpected); - assert.equal(rows6.length, 1); - - // dateStrings as array - assert.equal(rows8[0].d1, '1990-01-01'); - assert.equal(rows8[0].d1.constructor, String); - assert.equal(rows8[0].d2.constructor, Date); - assert.equal(rows8[0].d3.constructor, Date); - assert.equal(rows8[0].d4, null); - assert.equal(rows8[0].d5.constructor, Date); - assert.equal(rows8[0].d6.constructor, Date); - - // +09:30 - const tzOffset = -570; - assert.equal(rows7[0].d1.getTime(), toMidnight(date, tzOffset).getTime()); - assert.equal(rows7[0].d2.getTime(), datetime.getTime()); - assert.equal(rows7[0].d3.getTime(), datetime.getTime()); - assert.equal(rows7[0].d4, formatUTCDate(adjustTZ(date, tzOffset))); - assert.equal(rows7[0].d5, formatUTCDateTime(adjustTZ(datetime, tzOffset), 3)); - assert.equal(rows7[0].d6, formatUTCDateTime(adjustTZ(datetime, tzOffset), 6)); + connectionZ.query( + 'CREATE TEMPORARY TABLE t (d1 DATE, d2 DATETIME(3), d3 DATETIME(6))' + ); + connectionZ.query("set time_zone = '+00:00'"); + connectionZ.query('INSERT INTO t set d1=?, d2=?, d3=?', [ + date, + datetime, + datetime, + ]); + + connection0930.query( + 'CREATE TEMPORARY TABLE t (d1 DATE, d2 DATETIME(3), d3 DATETIME(6))' + ); + connection0930.query("set time_zone = '+09:30'"); + connection0930.query('INSERT INTO t set d1=?, d2=?, d3=?', [ + date, + datetime, + datetime, + ]); + + const dateAsStringExpected = [ + { + d1: formatUTCDate(adjustTZ(date)), + d2: formatUTCDateTime(adjustTZ(date1)), + d3: date2.substring(0, 19), + d4: date3, + d5: date4, + d6: date5, + d7: date6, + }, + ]; + + await it('should handle datetime values across timezones and formats', async () => { + await new Promise((resolve, reject) => { + let ended = 0; + const TOTAL = 5; + function checkDone() { + ended++; + if (ended === TOTAL) resolve(); + } + + connection.execute( + 'select from_unixtime(?) t', + [(+date).valueOf() / 1000], + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + } + ); + + connectionZ.execute( + 'select from_unixtime(?) t', + [(+date).valueOf() / 1000], + (err, _rows) => { + if (err) return reject(err); + rowsZ = _rows; + } + ); + + connection0930.execute( + 'select from_unixtime(?) t', + [(+date).valueOf() / 1000], + (err, _rows) => { + if (err) return reject(err); + rows0930 = _rows; + } + ); + + connection.query( + 'select from_unixtime(631152000) t', + (err, _rows) => { + if (err) return reject(err); + rows1 = _rows; + } + ); + + connectionZ.query( + 'select from_unixtime(631152000) t', + (err, _rows) => { + if (err) return reject(err); + rows1Z = _rows; + } + ); + + connection0930.query( + 'select from_unixtime(631152000) t', + (err, _rows) => { + if (err) return reject(err); + rows10930 = _rows; + } + ); + + connection.query( + 'select *, cast(d1 as char) as d4, cast(d2 as char) as d5, cast(d3 as char) as d6 from t', + (err, _rows) => { + if (err) return reject(err); + rows2 = _rows; + connection.end(); + checkDone(); + } + ); + + connectionZ.execute( + 'select *, cast(d1 as char) as d4, cast(d2 as char) as d5, cast(d3 as char) as d6 from t', + (err, _rows) => { + if (err) return reject(err); + rows3 = _rows; + connectionZ.end(); + checkDone(); + } + ); + + connection1.query('select * from t', (err, _rows) => { + if (err) return reject(err); + rows4 = _rows; + }); + + connection1.execute('select * from t', (err, _rows) => { + if (err) return reject(err); + rows5 = _rows; + }); + + connection1.execute( + 'select * from t where d6 = ?', + [new Date(date5)], + (err, _rows) => { + if (err) return reject(err); + rows6 = _rows; + connection1.end(); + checkDone(); + } + ); + + connection2.execute('select * from t', (err, _rows) => { + if (err) return reject(err); + rows8 = _rows; + connection2.end(); + checkDone(); + }); + + connection0930.execute( + 'select *, cast(d1 as char) as d4, cast(d2 as char) as d5, cast(d3 as char) as d6 from t', + (err, _rows) => { + if (err) return reject(err); + rows7 = _rows; + connection0930.end(); + checkDone(); + } + ); + }); + + const connBadTz = createConnection({ timezone: 'utc' }); + assert.equal(connBadTz.config.timezone, 'Z'); + connBadTz.end(); + + // local TZ + assert.equal(rows[0].t.constructor, Date); + assert.equal(rows[0].t.getDate(), date.getDate()); + assert.equal(rows[0].t.getHours(), date.getHours()); + assert.equal(rows[0].t.getMinutes(), date.getMinutes()); + assert.equal(rows[0].t.getSeconds(), date.getSeconds()); + + // UTC + assert.equal(rowsZ[0].t.constructor, Date); + assert.equal(rowsZ[0].t.getDate(), date.getDate()); + assert.equal(rowsZ[0].t.getHours(), date.getHours()); + assert.equal(rowsZ[0].t.getMinutes(), date.getMinutes()); + assert.equal(rowsZ[0].t.getSeconds(), date.getSeconds()); + + // +09:30 + assert.equal(rows0930[0].t.constructor, Date); + assert.equal(rows0930[0].t.getDate(), date.getDate()); + assert.equal(rows0930[0].t.getHours(), date.getHours()); + assert.equal(rows0930[0].t.getMinutes(), date.getMinutes()); + assert.equal(rows0930[0].t.getSeconds(), date.getSeconds()); + + // local TZ + assert.equal(rows1[0].t.constructor, Date); + assert.equal( + rows1[0].t.getTime(), + new Date('Mon Jan 01 1990 00:00:00 UTC').getTime() + ); + + // UTC + assert.equal(rows1Z[0].t.constructor, Date); + assert.equal( + rows1Z[0].t.getTime(), + new Date('Mon Jan 01 1990 00:00:00 UTC').getTime() + ); + + // +09:30 + assert.equal(rows10930[0].t.constructor, Date); + assert.equal( + rows10930[0].t.getTime(), + new Date('Mon Jan 01 1990 00:00:00 UTC').getTime() + ); + + // local TZ + assert.equal(rows2[0].d1.getTime(), toMidnight(date).getTime()); + assert.equal(rows2[0].d2.getTime(), datetime.getTime()); + assert.equal(rows2[0].d3.getTime(), datetime.getTime()); + assert.equal(rows2[0].d4, formatUTCDate(adjustTZ(date))); + assert.equal(rows2[0].d5, formatUTCDateTime(adjustTZ(datetime), 3)); + assert.equal(rows2[0].d6, formatUTCDateTime(adjustTZ(datetime), 6)); + + // UTC + assert.equal(rows3[0].d1.getTime(), toMidnight(date, 0).getTime()); + assert.equal(rows3[0].d2.getTime(), datetime.getTime()); + assert.equal(rows3[0].d3.getTime(), datetime.getTime()); + assert.equal(rows3[0].d4, formatUTCDate(date)); + assert.equal(rows3[0].d5, formatUTCDateTime(datetime, 3)); + assert.equal(rows3[0].d6, formatUTCDateTime(datetime, 6)); + + // dateStrings + assert.deepEqual(rows4, dateAsStringExpected); + assert.deepEqual(rows5, dateAsStringExpected); + assert.equal(rows6.length, 1); + + // dateStrings as array + assert.equal(rows8[0].d1, '1990-01-01'); + assert.equal(rows8[0].d1.constructor, String); + assert.equal(rows8[0].d2.constructor, Date); + assert.equal(rows8[0].d3.constructor, Date); + assert.equal(rows8[0].d4, null); + assert.equal(rows8[0].d5.constructor, Date); + assert.equal(rows8[0].d6.constructor, Date); + + // +09:30 + const tzOffset = -570; + assert.equal(rows7[0].d1.getTime(), toMidnight(date, tzOffset).getTime()); + assert.equal(rows7[0].d2.getTime(), datetime.getTime()); + assert.equal(rows7[0].d3.getTime(), datetime.getTime()); + assert.equal(rows7[0].d4, formatUTCDate(adjustTZ(date, tzOffset))); + assert.equal( + rows7[0].d5, + formatUTCDateTime(adjustTZ(datetime, tzOffset), 3) + ); + assert.equal( + rows7[0].d6, + formatUTCDateTime(adjustTZ(datetime, tzOffset), 6) + ); + }); }); diff --git a/test/esm/integration/connection/test-decimals-as-numbers.test.mts b/test/esm/integration/connection/test-decimals-as-numbers.test.mts index 9d273a1ef4..20ec1a41da 100644 --- a/test/esm/integration/connection/test-decimals-as-numbers.test.mts +++ b/test/esm/integration/connection/test-decimals-as-numbers.test.mts @@ -1,38 +1,49 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection1 = createConnection({ - decimalNumbers: false, -}); -const connection2 = createConnection({ - decimalNumbers: true, -}); +await describe('Decimals as Numbers', async () => { + const connection1 = createConnection({ + decimalNumbers: false, + }); + const connection2 = createConnection({ + decimalNumbers: true, + }); -const largeDecimal = 900719.547409; -const largeDecimalExpected = '900719.547409000000000000000000000000'; -const largeMoneyValue = 900719925474.99; + const largeDecimal = 900719.547409; + const largeDecimalExpected = '900719.547409000000000000000000000000'; + const largeMoneyValue = 900719925474.99; -connection1.query('CREATE TEMPORARY TABLE t1 (d1 DECIMAL(65, 30))'); -connection1.query('INSERT INTO t1 set d1=?', [largeDecimal]); + connection1.query('CREATE TEMPORARY TABLE t1 (d1 DECIMAL(65, 30))'); + connection1.query('INSERT INTO t1 set d1=?', [largeDecimal]); -connection2.query('CREATE TEMPORARY TABLE t2 (d1 DECIMAL(14, 2))'); -connection2.query('INSERT INTO t2 set d1=?', [largeMoneyValue]); + connection2.query('CREATE TEMPORARY TABLE t2 (d1 DECIMAL(14, 2))'); + connection2.query('INSERT INTO t2 set d1=?', [largeMoneyValue]); -connection1.execute('select d1 from t1', (err, _rows) => { - if (err) { - throw err; - } - assert.equal(_rows[0].d1.constructor, String); - assert.equal(_rows[0].d1, largeDecimalExpected); - connection1.end(); -}); + await it('should return decimals as strings when decimalNumbers is false', async () => { + await new Promise((resolve, reject) => { + connection1.execute( + 'select d1 from t1', + (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].d1.constructor, String); + assert.equal(_rows[0].d1, largeDecimalExpected); + connection1.end(); + resolve(); + } + ); + }); + }); -connection2.query('select d1 from t2', (err, _rows) => { - if (err) { - throw err; - } - assert.equal(_rows[0].d1.constructor, Number); - assert.equal(_rows[0].d1, largeMoneyValue); - connection2.end(); + await it('should return decimals as numbers when decimalNumbers is true', async () => { + await new Promise((resolve, reject) => { + connection2.query('select d1 from t2', (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].d1.constructor, Number); + assert.equal(_rows[0].d1, largeMoneyValue); + connection2.end(); + resolve(); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-disconnects.test.mts b/test/esm/integration/connection/test-disconnects.test.mts index 8d1739b1eb..17a6781975 100644 --- a/test/esm/integration/connection/test-disconnects.test.mts +++ b/test/esm/integration/connection/test-disconnects.test.mts @@ -11,79 +11,85 @@ import type { RowDataPacket, } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createServer } from '../../common.test.mjs'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -let rows: RowDataPacket[] | undefined; -let fields: FieldPacket[] | undefined; +await describe('Disconnects', async () => { + await it('should handle server disconnect', async () => { + let rows: RowDataPacket[] | undefined; + let fields: FieldPacket[] | undefined; -const connections: Connection[] = []; + const connections: Connection[] = []; -const server = createServer( - () => { - const connection = createConnection({ - // The mock server is running on the same host machine. - // We need to explicitly define the host to avoid connecting to a potential - // different host provided via MYSQL_HOST that identifies a real MySQL - // server instance. - host: 'localhost', - // @ts-expect-error: internal access - port: server._port, - // @ts-expect-error: TODO: implement typings - ssl: false, - }); - connection.query('SELECT 123', (err, _rows, _fields) => { - if (err) { - throw err; - } + await new Promise((resolve, reject) => { + const server = createServer( + () => { + const connection = createConnection({ + // The mock server is running on the same host machine. + // We need to explicitly define the host to avoid connecting to a potential + // different host provided via MYSQL_HOST that identifies a real MySQL + // server instance. + host: 'localhost', + // @ts-expect-error: internal access + port: server._port, + // @ts-expect-error: TODO: implement typings + ssl: false, + }); + connection.query( + 'SELECT 123', + (err, _rows, _fields) => { + if (err) return reject(err); - rows = _rows; - fields = _fields; - connection.on('error', (_err) => { - err = _err; - }); + rows = _rows; + fields = _fields; + connection.on('error', (_err) => { + err = _err; + }); - connections.forEach((conn) => { - // @ts-expect-error: TODO: implement typings - conn.stream.end(); - }); - // @ts-expect-error: internal access - server._server.close(() => { - assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); - }); - }); - // TODO: test connection.end() etc where we expect disconnect to happen - }, - (conn) => { - connections.push(conn); - conn.on('query', () => { - conn.writeTextResult( - [{ 1: '1' }], - [ - { - catalog: 'def', - schema: '', - table: '', - orgTable: '', - name: '1', - orgName: '', - characterSet: 63, - columnLength: 1, - columnType: 8, - type: 8, - flags: 129, - decimals: 0, - }, - ] + connections.forEach((conn) => { + // @ts-expect-error: TODO: implement typings + conn.stream.end(); + }); + // @ts-expect-error: internal access + server._server.close(() => { + assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); + resolve(); + }); + } + ); + // TODO: test connection.end() etc where we expect disconnect to happen + }, + (conn) => { + connections.push(conn); + conn.on('query', () => { + conn.writeTextResult( + [{ 1: '1' }], + [ + { + catalog: 'def', + schema: '', + table: '', + orgTable: '', + name: '1', + orgName: '', + characterSet: 63, + columnLength: 1, + columnType: 8, + type: 8, + flags: 129, + decimals: 0, + }, + ] + ); + }); + } ); }); - } -); -process.on('exit', () => { - assert.deepEqual(rows, [{ 1: 1 }]); - assert.equal(fields?.[0].name, '1'); + assert.deepEqual(rows, [{ 1: 1 }]); + assert.equal(fields?.[0].name, '1'); + }); }); diff --git a/test/esm/integration/connection/test-error-events.test.mts b/test/esm/integration/connection/test-error-events.test.mts index 02eb27fbc4..14dd6aca84 100644 --- a/test/esm/integration/connection/test-error-events.test.mts +++ b/test/esm/integration/connection/test-error-events.test.mts @@ -1,34 +1,32 @@ -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -let callCount = 0; -let exceptionCount = 0; +await describe('Error Events', async () => { + await it('should handle error events without uncaught exceptions', async () => { + let callCount = 0; -process.on('uncaughtException', (err) => { - assert.ifError(err); - exceptionCount++; -}); - -const connection1 = createConnection({ - password: 'lol', -}); + const connection1 = createConnection({ + password: 'lol', + }); -// error will NOT bubble up to process level if `on` is used -connection1.on('error', () => { - callCount++; -}); + const connection2 = createConnection({ + password: 'lol', + }); -const connection2 = createConnection({ - password: 'lol', -}); + await new Promise((resolve) => { + // error will NOT bubble up to process level if `on` is used + connection1.on('error', () => { + callCount++; + if (callCount === 2) resolve(); + }); -// error will bubble up to process level if `once` is used -connection2.once('error', () => { - callCount++; -}); + // error will bubble up to process level if `once` is used + connection2.once('error', () => { + callCount++; + if (callCount === 2) resolve(); + }); + }); -process.on('exit', () => { - assert.equal(callCount, 2); - assert.equal(exceptionCount, 0); + assert.equal(callCount, 2); + }); }); diff --git a/test/esm/integration/connection/test-errors.test.mts b/test/esm/integration/connection/test-errors.test.mts index c78f910f98..93f1baea55 100644 --- a/test/esm/integration/connection/test-errors.test.mts +++ b/test/esm/integration/connection/test-errors.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; // different error codes for PS, disabling for now @@ -8,54 +8,59 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); +await describe('Errors', async () => { + await it('should handle error events correctly', async () => { + const connection = createConnection(); -let onExecuteResultError: boolean | undefined = undefined; -let onQueryResultError: boolean | undefined = undefined; -let onExecuteErrorEvent: boolean | undefined = undefined; -let onQueryErrorEvent: boolean | undefined = undefined; -let onExecuteErrorEvent1: boolean | undefined = undefined; -let onQueryErrorEvent1: boolean | undefined = undefined; + let onExecuteResultError: boolean | undefined; + let onQueryResultError: boolean | undefined; + let onExecuteErrorEvent: boolean | undefined; + let onQueryErrorEvent: boolean | undefined; + let onExecuteErrorEvent1: boolean | undefined; + let onQueryErrorEvent1: boolean | undefined; -connection - .execute('error in execute', [], (err) => { - assert.equal(err?.errno, 1064); - assert.equal(err?.code, 'ER_PARSE_ERROR'); - // @ts-expect-error: TODO: implement typings - assert.equal(err?.sql, 'error in execute'); - if (err) { - onExecuteResultError = true; - } - }) - .on('error', () => { - onExecuteErrorEvent = true; - }); -connection - .query('error in query', [], (err) => { - assert.equal(err?.errno, 1064); - assert.equal(err?.code, 'ER_PARSE_ERROR'); - // @ts-expect-error: TODO: implement typings - assert.equal(err?.sql, 'error in query'); - if (err) { - onQueryResultError = true; - } - }) - .on('error', () => { - onQueryErrorEvent = true; - }); -connection.execute('error in execute 1', []).on('error', () => { - onExecuteErrorEvent1 = true; -}); -connection.query('error in query 1').on('error', () => { - onQueryErrorEvent1 = true; - connection.end(); -}); + await new Promise((resolve) => { + connection + .execute('error in execute', [], (err) => { + assert.equal(err?.errno, 1064); + assert.equal(err?.code, 'ER_PARSE_ERROR'); + // @ts-expect-error: TODO: implement typings + assert.equal(err?.sql, 'error in execute'); + if (err) { + onExecuteResultError = true; + } + }) + .on('error', () => { + onExecuteErrorEvent = true; + }); + connection + .query('error in query', [], (err) => { + assert.equal(err?.errno, 1064); + assert.equal(err?.code, 'ER_PARSE_ERROR'); + // @ts-expect-error: TODO: implement typings + assert.equal(err?.sql, 'error in query'); + if (err) { + onQueryResultError = true; + } + }) + .on('error', () => { + onQueryErrorEvent = true; + }); + connection.execute('error in execute 1', []).on('error', () => { + onExecuteErrorEvent1 = true; + }); + connection.query('error in query 1').on('error', () => { + onQueryErrorEvent1 = true; + connection.end(); + resolve(); + }); + }); -process.on('exit', () => { - assert.equal(onExecuteResultError, true); - assert.equal(onQueryResultError, true); - assert.equal(onExecuteErrorEvent, undefined); - assert.equal(onQueryErrorEvent, undefined); - assert.equal(onExecuteErrorEvent1, true); - assert.equal(onQueryErrorEvent1, true); + assert.equal(onExecuteResultError, true); + assert.equal(onQueryResultError, true); + assert.equal(onExecuteErrorEvent, undefined); + assert.equal(onQueryErrorEvent, undefined); + assert.equal(onExecuteErrorEvent1, true); + assert.equal(onQueryErrorEvent1, true); + }); }); diff --git a/test/esm/integration/connection/test-execute-and-unprepare.test.mts b/test/esm/integration/connection/test-execute-and-unprepare.test.mts index d6826090ab..821c507c20 100644 --- a/test/esm/integration/connection/test-execute-and-unprepare.test.mts +++ b/test/esm/integration/connection/test-execute-and-unprepare.test.mts @@ -1,25 +1,33 @@ +import { describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Execute and Unprepare', async () => { + await it('should execute and unprepare repeatedly', async () => { + const connection = createConnection(); -const max = 500; -function exec(i: number) { - const query = `select 1+${i}`; - connection.execute(query, (err) => { - connection.unprepare(query); - if (err) { - throw err; - } - if (i > max) { - connection.end(); - } else { - exec(i + 1); - } + await new Promise((resolve, reject) => { + const max = 500; + function exec(i: number) { + const query = `select 1+${i}`; + connection.execute(query, (err) => { + connection.unprepare(query); + if (err) { + return reject(err); + } + if (i > max) { + connection.end(); + resolve(); + } else { + exec(i + 1); + } + }); + } + connection.query('SET GLOBAL max_prepared_stmt_count=10', (err) => { + if (err) { + return reject(err); + } + exec(1); + }); + }); }); -} -connection.query('SET GLOBAL max_prepared_stmt_count=10', (err) => { - if (err) { - throw err; - } - exec(1); }); diff --git a/test/esm/integration/connection/test-execute-bind-boolean.test.mts b/test/esm/integration/connection/test-execute-bind-boolean.test.mts index 359c99dece..15ef31af06 100644 --- a/test/esm/integration/connection/test-execute-bind-boolean.test.mts +++ b/test/esm/integration/connection/test-execute-bind-boolean.test.mts @@ -1,23 +1,24 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Execute Bind Boolean', async () => { + const connection = createConnection(); -let rows: RowDataPacket[]; -connection.execute( - 'SELECT ? AS trueValue, ? AS falseValue', - [true, false], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + await it('should bind boolean values correctly', async () => { + const rows = await new Promise((resolve, reject) => { + connection.execute( + 'SELECT ? AS trueValue, ? AS falseValue', + [true, false], + (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ trueValue: 1, falseValue: 0 }]); + assert.deepEqual(rows, [{ trueValue: 1, falseValue: 0 }]); + }); + + connection.end(); }); diff --git a/test/esm/integration/connection/test-execute-bind-date.test.mts b/test/esm/integration/connection/test-execute-bind-date.test.mts index 3ea31461fe..b300f684ee 100644 --- a/test/esm/integration/connection/test-execute-bind-date.test.mts +++ b/test/esm/integration/connection/test-execute-bind-date.test.mts @@ -1,24 +1,25 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); -const date = new Date(2018, 2, 10, 15, 12, 34, 1234); +await describe('Execute Bind Date', async () => { + const connection = createConnection(); + const date = new Date(2018, 2, 10, 15, 12, 34, 1234); -let rows: RowDataPacket[]; -connection.execute( - 'SELECT CAST(? AS DATETIME(6)) AS result', - [date], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + await it('should bind date values correctly', async () => { + const rows = await new Promise((resolve, reject) => { + connection.execute( + 'SELECT CAST(? AS DATETIME(6)) AS result', + [date], + (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ result: date }]); + assert.deepEqual(rows, [{ result: date }]); + }); + + connection.end(); }); diff --git a/test/esm/integration/connection/test-execute-bind-function.test.mts b/test/esm/integration/connection/test-execute-bind-function.test.mts index fba81c3111..6b1fd18471 100644 --- a/test/esm/integration/connection/test-execute-bind-function.test.mts +++ b/test/esm/integration/connection/test-execute-bind-function.test.mts @@ -1,29 +1,30 @@ -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +describe('Execute Bind Function', () => { + const connection = createConnection(); -let error: Error | null = null; + let error: Error | null = null; -try { - connection.execute('SELECT ? AS result', [function () {}], () => {}); -} catch (err) { - if (err instanceof Error) { - error = err; - } else { - throw err; + try { + connection.execute('SELECT ? AS result', [function () {}], () => {}); + } catch (err) { + if (err instanceof Error) { + error = err; + } else { + throw err; + } + connection.end(); } - connection.end(); -} -process.on('exit', () => { - assert(error instanceof Error, 'Expected TypeError to be thrown'); - if (!error) { - return; - } - assert.equal(error.name, 'TypeError'); - if (!error.message.match(/function/)) { - assert.fail("Expected error.message to contain 'function'"); - } + it('should throw TypeError for function parameter', () => { + assert(error instanceof Error, 'Expected TypeError to be thrown'); + if (!error) { + return; + } + assert.equal(error.name, 'TypeError'); + if (!error.message.match(/function/)) { + assert.fail("Expected error.message to contain 'function'"); + } + }); }); diff --git a/test/esm/integration/connection/test-execute-bind-json.test.mts b/test/esm/integration/connection/test-execute-bind-json.test.mts index ea4200bff6..90ebb34d2c 100644 --- a/test/esm/integration/connection/test-execute-bind-json.test.mts +++ b/test/esm/integration/connection/test-execute-bind-json.test.mts @@ -1,25 +1,30 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); -const table = 'jsontable'; -const testJson = [{ a: 1, b: true, c: ['foo'] }]; +await describe('Execute Bind JSON', async () => { + const connection = createConnection(); + const table = 'jsontable'; + const testJson = [{ a: 1, b: true, c: ['foo'] }]; -let rows: RowDataPacket[]; -connection.query(`CREATE TEMPORARY TABLE ${table} (data JSON)`); -connection.query( - `INSERT INTO ${table} (data) VALUES ('${JSON.stringify(testJson)}')` -); -connection.execute(`SELECT * from ${table}`, (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); -}); + connection.query(`CREATE TEMPORARY TABLE ${table} (data JSON)`); + connection.query( + `INSERT INTO ${table} (data) VALUES ('${JSON.stringify(testJson)}')` + ); + + await it('should bind JSON values correctly', async () => { + const rows = await new Promise((resolve, reject) => { + connection.execute( + `SELECT * from ${table}`, + (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ data: testJson }]); + assert.deepEqual(rows, [{ data: testJson }]); + }); + + connection.end(); }); diff --git a/test/esm/integration/connection/test-execute-bind-null.test.mts b/test/esm/integration/connection/test-execute-bind-null.test.mts index 252c256511..21ad9e929a 100644 --- a/test/esm/integration/connection/test-execute-bind-null.test.mts +++ b/test/esm/integration/connection/test-execute-bind-null.test.mts @@ -1,25 +1,26 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Execute Bind Null', async () => { + const connection = createConnection(); -let rows: RowDataPacket[]; -connection.execute( - 'SELECT ? AS firstValue, ? AS nullValue, ? AS lastValue', - ['foo', null, 'bar'], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + await it('should bind null values correctly', async () => { + const rows = await new Promise((resolve, reject) => { + connection.execute( + 'SELECT ? AS firstValue, ? AS nullValue, ? AS lastValue', + ['foo', null, 'bar'], + (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [ - { firstValue: 'foo', nullValue: null, lastValue: 'bar' }, - ]); + assert.deepEqual(rows, [ + { firstValue: 'foo', nullValue: null, lastValue: 'bar' }, + ]); + }); + + connection.end(); }); diff --git a/test/esm/integration/connection/test-execute-bind-number.test.mts b/test/esm/integration/connection/test-execute-bind-number.test.mts index 5de3888686..32aa7d1e3a 100644 --- a/test/esm/integration/connection/test-execute-bind-number.test.mts +++ b/test/esm/integration/connection/test-execute-bind-number.test.mts @@ -1,30 +1,31 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Execute Bind Number', async () => { + const connection = createConnection(); -let rows: RowDataPacket[]; -connection.execute( - 'SELECT ? AS zeroValue, ? AS positiveValue, ? AS negativeValue, ? AS decimalValue', - [0, 123, -123, 1.25], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + await it('should bind number values correctly', async () => { + const rows = await new Promise((resolve, reject) => { + connection.execute( + 'SELECT ? AS zeroValue, ? AS positiveValue, ? AS negativeValue, ? AS decimalValue', + [0, 123, -123, 1.25], + (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [ - { - zeroValue: 0, - positiveValue: 123, - negativeValue: -123, - decimalValue: 1.25, - }, - ]); + assert.deepEqual(rows, [ + { + zeroValue: 0, + positiveValue: 123, + negativeValue: -123, + decimalValue: 1.25, + }, + ]); + }); + + connection.end(); }); diff --git a/test/esm/integration/connection/test-execute-bind-undefined.test.mts b/test/esm/integration/connection/test-execute-bind-undefined.test.mts index 6b8381f7f3..e1defae006 100644 --- a/test/esm/integration/connection/test-execute-bind-undefined.test.mts +++ b/test/esm/integration/connection/test-execute-bind-undefined.test.mts @@ -1,29 +1,30 @@ -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +describe('Execute Bind Undefined', () => { + const connection = createConnection(); -let error: Error | null = null; + let error: Error | null = null; -try { - connection.execute('SELECT ? AS result', [undefined], () => {}); -} catch (err) { - if (err instanceof Error) { - error = err; - } else { - throw err; + try { + connection.execute('SELECT ? AS result', [undefined], () => {}); + } catch (err) { + if (err instanceof Error) { + error = err; + } else { + throw err; + } + connection.end(); } - connection.end(); -} -process.on('exit', () => { - assert(error instanceof Error, 'Expected TypeError to be thrown'); - if (!error) { - return; - } - assert.equal(error.name, 'TypeError'); - if (!error.message.match(/undefined/)) { - assert.fail("Expected error.message to contain 'undefined'"); - } + it('should throw TypeError for undefined parameter', () => { + assert(error instanceof Error, 'Expected TypeError to be thrown'); + if (!error) { + return; + } + assert.equal(error.name, 'TypeError'); + if (!error.message.match(/undefined/)) { + assert.fail("Expected error.message to contain 'undefined'"); + } + }); }); diff --git a/test/esm/integration/connection/test-execute-cached.test.mts b/test/esm/integration/connection/test-execute-cached.test.mts index d4630c1680..669a1f31f5 100644 --- a/test/esm/integration/connection/test-execute-cached.test.mts +++ b/test/esm/integration/connection/test-execute-cached.test.mts @@ -1,47 +1,39 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type TestRow = RowDataPacket & { test: number }; -const connection = createConnection(); +await describe('Execute Cached', async () => { + const connection = createConnection(); -let rows: TestRow[] | undefined = undefined; -let rows1: TestRow[] | undefined = undefined; -let rows2: TestRow[] | undefined = undefined; + const q = 'select 1 + ? as test'; + const key = `undefined/undefined/undefined${q}`; -const q = 'select 1 + ? as test'; -const key = `undefined/undefined/undefined${q}`; + await it('should cache prepared statements', async () => { + await new Promise((resolve, reject) => { + connection.execute(q, [123], (err, _rows) => { + if (err) return reject(err); + connection.execute(q, [124], (err, _rows1) => { + if (err) return reject(err); + connection.execute(q, [125], (err, _rows2) => { + if (err) return reject(err); + // @ts-expect-error: internal access + assert(connection._statements.size === 1); + // @ts-expect-error: internal access + assert(connection._statements.get(key).query === q); + // @ts-expect-error: internal access + assert(connection._statements.get(key).parameters.length === 1); -connection.execute(q, [123], (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.execute(q, [124], (err, _rows) => { - if (err) { - throw err; - } - rows1 = _rows; - connection.execute(q, [125], (err, _rows) => { - if (err) { - throw err; - } - rows2 = _rows; - // @ts-expect-error: internal access - assert(connection._statements.size === 1); - // @ts-expect-error: internal access - assert(connection._statements.get(key).query === q); - // @ts-expect-error: internal access - assert(connection._statements.get(key).parameters.length === 1); - connection.end(); + assert.deepEqual(_rows, [{ test: 124 }]); + assert.deepEqual(_rows1, [{ test: 125 }]); + assert.deepEqual(_rows2, [{ test: 126 }]); + + connection.end(); + resolve(); + }); + }); + }); }); }); }); - -process.on('exit', () => { - assert.deepEqual(rows, [{ test: 124 }]); - assert.deepEqual(rows1, [{ test: 125 }]); - assert.deepEqual(rows2, [{ test: 126 }]); -}); diff --git a/test/esm/integration/connection/test-execute-newdecimal.test.mts b/test/esm/integration/connection/test-execute-newdecimal.test.mts index 76a538469d..1740766a96 100644 --- a/test/esm/integration/connection/test-execute-newdecimal.test.mts +++ b/test/esm/integration/connection/test-execute-newdecimal.test.mts @@ -1,27 +1,25 @@ -import type { FieldPacket, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import type { RowDataPacket } from '../../../../index.js'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Execute New Decimal', async () => { + const connection = createConnection(); -connection.query('CREATE TEMPORARY TABLE t (f DECIMAL(19,4))'); -connection.query('INSERT INTO t VALUES(12345.67)'); + connection.query('CREATE TEMPORARY TABLE t (f DECIMAL(19,4))'); + connection.query('INSERT INTO t VALUES(12345.67)'); -let rows: RowDataPacket[], fields: FieldPacket[]; -connection.execute( - 'SELECT f FROM t', - (err, _rows, _fields) => { - if (err) { - throw err; - } - rows = _rows; - fields = _fields; - connection.end(); - } -); - -process.on('exit', () => { - assert.deepEqual(rows, [{ f: '12345.6700' }]); - assert.equal(fields[0].name, 'f'); + await it('should return decimal values correctly', async () => { + await new Promise((resolve, reject) => { + connection.execute( + 'SELECT f FROM t', + (err, rows, fields) => { + if (err) return reject(err); + assert.deepEqual(rows, [{ f: '12345.6700' }]); + assert.equal(fields[0].name, 'f'); + connection.end(); + resolve(); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-execute-nocolumndef.test.mts b/test/esm/integration/connection/test-execute-nocolumndef.test.mts index a6b0dd7569..0ce9673a3f 100644 --- a/test/esm/integration/connection/test-execute-nocolumndef.test.mts +++ b/test/esm/integration/connection/test-execute-nocolumndef.test.mts @@ -7,6 +7,7 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; import process from 'node:process'; // @ts-expect-error: no typings available import assert from 'assert-diff'; +import { describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; // different error codes for PS, disabling for now @@ -15,28 +16,10 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); - // https://github.com/sidorares/node-mysql2/issues/130 // https://github.com/sidorares/node-mysql2/issues/37 // binary protocol examples where `prepare` returns no column definitions but execute() does return fields/rows -let rows: RowDataPacket[]; -let fields: FieldPacket[]; - -connection.execute( - 'explain SELECT 1', - (err, _rows, _fields) => { - if (err) { - throw err; - } - - rows = _rows; - fields = _fields; - connection.end(); - } -); - const expectedRows = [ { id: 1, @@ -213,18 +196,34 @@ const expectedFields = [ }, ]; -process.on('exit', () => { - assert.deepEqual(rows, expectedRows); - fields.forEach((f, index) => { - // @ts-expect-error: TODO: implement typings - const fi = f.inspect(); - // "columnLength" is non-deterministic - delete fi.columnLength; +await describe('Execute No Column Definition', async () => { + const connection = createConnection(); + + await it('should handle explain with no column definitions', async () => { + await new Promise((resolve, reject) => { + connection.execute( + 'explain SELECT 1', + (err, rows, fields) => { + if (err) return reject(err); + + assert.deepEqual(rows, expectedRows); + fields.forEach((f: FieldPacket, index: number) => { + // @ts-expect-error: TODO: implement typings + const fi = f.inspect(); + // "columnLength" is non-deterministic + delete fi.columnLength; + + assert.deepEqual( + Object.keys(fi).sort(), + Object.keys(expectedFields[index]).sort() + ); + assert.deepEqual(expectedFields[index], fi); + }); - assert.deepEqual( - Object.keys(fi).sort(), - Object.keys(expectedFields[index]).sort() - ); - assert.deepEqual(expectedFields[index], fi); + connection.end(); + resolve(); + } + ); + }); }); }); diff --git a/test/esm/integration/connection/test-execute-null-bitmap.test.mts b/test/esm/integration/connection/test-execute-null-bitmap.test.mts index 40e83365a1..c518a2f38e 100644 --- a/test/esm/integration/connection/test-execute-null-bitmap.test.mts +++ b/test/esm/integration/connection/test-execute-null-bitmap.test.mts @@ -1,29 +1,39 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type TestRow = RowDataPacket & { t: number }; -const connection = createConnection(); +await describe('Execute Null Bitmap', async () => { + const connection = createConnection(); -const params = [1, 2]; -let query = 'select ? + ?'; + await it('should handle growing parameter lists', async () => { + await new Promise((resolve, reject) => { + const params = [1, 2]; + let query = 'select ? + ?'; -function dotest() { - connection.execute(`${query} as t`, params, (err, _rows) => { - assert.equal(err, null); - if (params.length < 50) { - assert.equal( - _rows[0].t, - params.reduce((x: number, y: number) => x + y) - ); - query += ' + ?'; - params.push(params.length); - dotest(); - } else { - connection.end(); - } - }); -} + function dotest() { + connection.execute(`${query} as t`, params, (err, _rows) => { + if (err) return reject(err); + if (params.length < 50) { + assert.equal( + _rows[0].t, + params.reduce((x: number, y: number) => x + y) + ); + query += ' + ?'; + params.push(params.length); + dotest(); + } else { + connection.end(); + resolve(); + } + }); + } -connection.query('SET GLOBAL max_prepared_stmt_count=300', dotest); + connection.query('SET GLOBAL max_prepared_stmt_count=300', (err) => { + if (err) return reject(err); + dotest(); + }); + }); + }); +}); diff --git a/test/esm/integration/connection/test-execute-order.test.mts b/test/esm/integration/connection/test-execute-order.test.mts index 10a858a2c3..33be2e47a4 100644 --- a/test/esm/integration/connection/test-execute-order.test.mts +++ b/test/esm/integration/connection/test-execute-order.test.mts @@ -1,24 +1,29 @@ -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Execute Order', async () => { + const connection = createConnection(); -const order: number[] = []; -connection.execute('select 1+2', (err) => { - assert.ifError(err); - order.push(0); -}); -connection.execute('select 2+2', (err) => { - assert.ifError(err); - order.push(1); -}); -connection.query('select 1+1', (err) => { - assert.ifError(err); - order.push(2); - connection.end(); -}); + await it('should execute queries in order', async () => { + const order: number[] = []; + + await new Promise((resolve, reject) => { + connection.execute('select 1+2', (err) => { + if (err) return reject(err); + order.push(0); + }); + connection.execute('select 2+2', (err) => { + if (err) return reject(err); + order.push(1); + }); + connection.query('select 1+1', (err) => { + if (err) return reject(err); + order.push(2); + connection.end(); + resolve(); + }); + }); -process.on('exit', () => { - assert.deepEqual(order, [0, 1, 2]); + assert.deepEqual(order, [0, 1, 2]); + }); }); diff --git a/test/esm/integration/connection/test-execute-signed.test.mts b/test/esm/integration/connection/test-execute-signed.test.mts index a489322354..d66b866f35 100644 --- a/test/esm/integration/connection/test-execute-signed.test.mts +++ b/test/esm/integration/connection/test-execute-signed.test.mts @@ -1,44 +1,48 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type TestRow = RowDataPacket & { id: number; num: number; l: number }; -const connection = createConnection(); +await describe('Execute Signed', async () => { + const connection = createConnection(); -let rows: TestRow[] | undefined = undefined; + connection.query( + [ + 'CREATE TEMPORARY TABLE `test_table` (', + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`num` int(15),', + '`l` long,', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); -connection.query( - [ - 'CREATE TEMPORARY TABLE `test_table` (', - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`num` int(15),', - '`l` long,', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); + connection.query('insert into test_table(num,l) values(?, 3)', [1]); + connection.query('insert into test_table(num,l) values(3-?, -10)', [5]); + connection.query( + 'insert into test_table(num,l) values(4+?, 4000000-?)', + [-5, 8000000] + ); -connection.query('insert into test_table(num,l) values(?, 3)', [1]); -connection.query('insert into test_table(num,l) values(3-?, -10)', [5]); -connection.query( - 'insert into test_table(num,l) values(4+?, 4000000-?)', - [-5, 8000000] -); + await it('should handle signed integer values', async () => { + const rows = await new Promise((resolve, reject) => { + connection.execute( + 'SELECT * from test_table', + [], + (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + } + ); + }); -connection.execute('SELECT * from test_table', [], (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); -}); + assert.deepEqual(rows, [ + { id: 1, num: 1, l: 3 }, + { id: 2, num: -2, l: -10 }, + { id: 3, num: -1, l: -4000000 }, + ]); + }); -process.on('exit', () => { - assert.deepEqual(rows, [ - { id: 1, num: 1, l: 3 }, - { id: 2, num: -2, l: -10 }, - { id: 3, num: -1, l: -4000000 }, - ]); + connection.end(); }); diff --git a/test/esm/integration/connection/test-execute-type-casting.test.mts b/test/esm/integration/connection/test-execute-type-casting.test.mts index 3241631957..d8ff2cfde0 100644 --- a/test/esm/integration/connection/test-execute-type-casting.test.mts +++ b/test/esm/integration/connection/test-execute-type-casting.test.mts @@ -1,6 +1,5 @@ import type { RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import process from 'node:process'; import { assert, test } from 'poku'; import { createConnection, useTestDb } from '../../common.test.mjs'; import typeCastingTests from './type-casting-tests.test.mjs'; @@ -15,85 +14,84 @@ type TypeCastTest = { columnName?: string; }; -test(async () => { +await test(async () => { const connection = createConnection(); useTestDb(); - connection.query('select 1', async (waitConnectErr) => { - assert.ifError(waitConnectErr); - const tests = (await typeCastingTests(connection)) as TypeCastTest[]; + await new Promise((resolve, reject) => { + connection.query('select 1', async (waitConnectErr) => { + if (waitConnectErr) return reject(waitConnectErr); + const tests = (await typeCastingTests(connection)) as TypeCastTest[]; - const table = 'type_casting'; + const table = 'type_casting'; - const schema: string[] = []; - const inserts: string[] = []; + const schema: string[] = []; + const inserts: string[] = []; - tests.forEach((test, index) => { - const escaped = test.insertRaw || connection.escape(test.insert); + tests.forEach((test, index) => { + const escaped = test.insertRaw || connection.escape(test.insert); - test.columnName = `${test.type}_${index}`; + test.columnName = `${test.type}_${index}`; - schema.push(`\`${test.columnName}\` ${test.type},`); - inserts.push(`\`${test.columnName}\` = ${escaped}`); - }); - - const createTable = [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - ] - .concat(schema) - .concat(['PRIMARY KEY (`id`)', ') ENGINE=InnoDB DEFAULT CHARSET=utf8']) - .join('\n'); - - connection.query(createTable); - - connection.query(`INSERT INTO ${table} SET${inserts.join(',\n')}`); - - let row: RowDataPacket | undefined; - connection.execute( - `SELECT * FROM ${table} WHERE id = ?;`, - [1], - (err, rows) => { - if (err) { - throw err; - } - - row = rows[0]; - connection.end(); - } - ); - - process.on('exit', () => { - tests.forEach((test) => { - let expected: unknown = test.expect || test.insert; - let got: unknown = row?.[test.columnName ?? '']; - let message: string; - - if (expected instanceof Date) { - assert.equal(got instanceof Date, true, test.type); - - expected = String(expected); - got = String(got); - } else if (Buffer.isBuffer(expected)) { - assert.equal(Buffer.isBuffer(got), true, test.type); - - expected = String(Array.prototype.slice.call(expected)); - got = String(Array.prototype.slice.call(got)); - } + schema.push(`\`${test.columnName}\` ${test.type},`); + inserts.push(`\`${test.columnName}\` = ${escaped}`); + }); - if (test.deep) { - message = `got: "${JSON.stringify(got)}" expected: "${JSON.stringify( - expected - )}" test: ${test.type}`; - assert.deepEqual(expected, got, message); - } else { - message = `got: "${got}" (${typeof got}) expected: "${expected}" (${typeof expected}) test: ${ - test.type - }`; - assert.strictEqual(expected, got, message); + const createTable = [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + ] + .concat(schema) + .concat(['PRIMARY KEY (`id`)', ') ENGINE=InnoDB DEFAULT CHARSET=utf8']) + .join('\n'); + + connection.query(createTable); + + connection.query(`INSERT INTO ${table} SET${inserts.join(',\n')}`); + + connection.execute( + `SELECT * FROM ${table} WHERE id = ?;`, + [1], + (err, rows) => { + if (err) return reject(err); + + const row = rows[0]; + + tests.forEach((test) => { + let expected: unknown = test.expect || test.insert; + let got: unknown = row?.[test.columnName ?? '']; + let message: string; + + if (expected instanceof Date) { + assert.equal(got instanceof Date, true, test.type); + + expected = String(expected); + got = String(got); + } else if (Buffer.isBuffer(expected)) { + assert.equal(Buffer.isBuffer(got), true, test.type); + + expected = String(Array.prototype.slice.call(expected)); + got = String(Array.prototype.slice.call(got)); + } + + if (test.deep) { + message = `got: "${JSON.stringify(got)}" expected: "${JSON.stringify( + expected + )}" test: ${test.type}`; + assert.deepEqual(expected, got, message); + } else { + message = `got: "${got}" (${typeof got}) expected: "${expected}" (${typeof expected}) test: ${ + test.type + }`; + assert.strictEqual(expected, got, message); + } + }); + + connection.end(); + resolve(); } - }); + ); }); }); }); diff --git a/test/esm/integration/connection/test-insert-bigint-big-number-strings.test.mts b/test/esm/integration/connection/test-insert-bigint-big-number-strings.test.mts index 9259366c01..cf2d0dd2ea 100644 --- a/test/esm/integration/connection/test-insert-bigint-big-number-strings.test.mts +++ b/test/esm/integration/connection/test-insert-bigint-big-number-strings.test.mts @@ -1,72 +1,81 @@ import type { ResultSetHeader, RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type BigRow = RowDataPacket & { id: string; title: string }; -const connection = createConnection({ - supportBigNumbers: true, - bigNumberStrings: true, -}); +await describe('Insert BigInt Big Number Strings', async () => { + const connection = createConnection({ + supportBigNumbers: true, + bigNumberStrings: true, + }); -connection.query( - [ - 'CREATE TEMPORARY TABLE `bigs` (', - '`id` bigint NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); + connection.query( + [ + 'CREATE TEMPORARY TABLE `bigs` (', + '`id` bigint NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); -connection.query("INSERT INTO bigs SET title='test', id=123"); -connection.query( - "INSERT INTO bigs SET title='test1'", - (err, result) => { - if (err) { - throw err; - } - assert.strictEqual(result.insertId, 124); - // > 24 bits - connection.query("INSERT INTO bigs SET title='test', id=123456789"); - connection.query( - "INSERT INTO bigs SET title='test2'", - (_err, result) => { - assert.strictEqual(result.insertId, 123456790); - // big int - connection.query( - "INSERT INTO bigs SET title='test', id=9007199254740992" - ); - connection.query( - "INSERT INTO bigs SET title='test3'", - (_err, result) => { - assert.strictEqual(result.insertId, '9007199254740993'); - connection.query( - "INSERT INTO bigs SET title='test', id=90071992547409924" - ); - connection.query( - "INSERT INTO bigs SET title='test4'", - (_err, result) => { - assert.strictEqual(result.insertId, '90071992547409925'); - connection.query( - 'select * from bigs', - (_err, result) => { - assert.strictEqual(result[0].id, '123'); - assert.strictEqual(result[1].id, '124'); - assert.strictEqual(result[2].id, '123456789'); - assert.strictEqual(result[3].id, '123456790'); - assert.strictEqual(result[4].id, '9007199254740992'); - assert.strictEqual(result[5].id, '9007199254740993'); - assert.strictEqual(result[6].id, '90071992547409924'); - assert.strictEqual(result[7].id, '90071992547409925'); - connection.end(); - } - ); - } - ); - } - ); - } - ); - } -); + await it('should handle bigint with big number strings', async () => { + await new Promise((resolve, reject) => { + connection.query("INSERT INTO bigs SET title='test', id=123"); + connection.query( + "INSERT INTO bigs SET title='test1'", + (err, result) => { + if (err) return reject(err); + assert.strictEqual(result.insertId, 124); + // > 24 bits + connection.query("INSERT INTO bigs SET title='test', id=123456789"); + connection.query( + "INSERT INTO bigs SET title='test2'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result.insertId, 123456790); + // big int + connection.query( + "INSERT INTO bigs SET title='test', id=9007199254740992" + ); + connection.query( + "INSERT INTO bigs SET title='test3'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result.insertId, '9007199254740993'); + connection.query( + "INSERT INTO bigs SET title='test', id=90071992547409924" + ); + connection.query( + "INSERT INTO bigs SET title='test4'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result.insertId, '90071992547409925'); + connection.query( + 'select * from bigs', + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result[0].id, '123'); + assert.strictEqual(result[1].id, '124'); + assert.strictEqual(result[2].id, '123456789'); + assert.strictEqual(result[3].id, '123456790'); + assert.strictEqual(result[4].id, '9007199254740992'); + assert.strictEqual(result[5].id, '9007199254740993'); + assert.strictEqual(result[6].id, '90071992547409924'); + assert.strictEqual(result[7].id, '90071992547409925'); + connection.end(); + resolve(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); +}); diff --git a/test/esm/integration/connection/test-insert-bigint.test.mts b/test/esm/integration/connection/test-insert-bigint.test.mts index 9049e430c1..87e7667ffb 100644 --- a/test/esm/integration/connection/test-insert-bigint.test.mts +++ b/test/esm/integration/connection/test-insert-bigint.test.mts @@ -1,81 +1,94 @@ import type { ResultSetHeader, RowDataPacket } from '../../../../index.js'; import Long from 'long'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type BigRow = RowDataPacket & { id: number | string; title: string }; -const connection = createConnection(); +await describe('Insert BigInt', async () => { + const connection = createConnection(); -connection.query( - [ - 'CREATE TEMPORARY TABLE `bigs` (', - '`id` bigint NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); + connection.query( + [ + 'CREATE TEMPORARY TABLE `bigs` (', + '`id` bigint NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); -connection.query("INSERT INTO bigs SET title='test', id=123"); -connection.query( - "INSERT INTO bigs SET title='test1'", - (_err, result) => { - if (_err) { - throw _err; - } - assert.strictEqual(result.insertId, 124); - // > 24 bits - connection.query("INSERT INTO bigs SET title='test', id=123456789"); - connection.query( - "INSERT INTO bigs SET title='test2'", - (_err, result) => { - assert.strictEqual(result.insertId, 123456790); - // big int - connection.query( - "INSERT INTO bigs SET title='test', id=9007199254740992" - ); - connection.query( - "INSERT INTO bigs SET title='test3'", - (_err, result) => { - assert.strictEqual( - Long.fromString('9007199254740993').compare(result.insertId), - 0 - ); - connection.query( - "INSERT INTO bigs SET title='test', id=90071992547409924" - ); - connection.query( - "INSERT INTO bigs SET title='test4'", - (_err, result) => { - assert.strictEqual( - Long.fromString('90071992547409925').compare(result.insertId), - 0 - ); - connection.query( - { - sql: 'select * from bigs', - // @ts-expect-error: supportBigNumbers is not in QueryOptions typings - supportBigNumbers: true, - bigNumberString: false, - }, - (_err, result) => { - assert.strictEqual(result[0].id, 123); - assert.strictEqual(result[1].id, 124); - assert.strictEqual(result[2].id, 123456789); - assert.strictEqual(result[3].id, 123456790); - assert.strictEqual(result[4].id, 9007199254740992); - assert.strictEqual(result[5].id, '9007199254740993'); - assert.strictEqual(result[6].id, '90071992547409924'); - assert.strictEqual(result[7].id, '90071992547409925'); - connection.end(); - } - ); - } - ); - } - ); - } - ); - } -); + await it('should handle bigint insert IDs', async () => { + await new Promise((resolve, reject) => { + connection.query("INSERT INTO bigs SET title='test', id=123"); + connection.query( + "INSERT INTO bigs SET title='test1'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result.insertId, 124); + // > 24 bits + connection.query("INSERT INTO bigs SET title='test', id=123456789"); + connection.query( + "INSERT INTO bigs SET title='test2'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result.insertId, 123456790); + // big int + connection.query( + "INSERT INTO bigs SET title='test', id=9007199254740992" + ); + connection.query( + "INSERT INTO bigs SET title='test3'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual( + Long.fromString('9007199254740993').compare( + result.insertId + ), + 0 + ); + connection.query( + "INSERT INTO bigs SET title='test', id=90071992547409924" + ); + connection.query( + "INSERT INTO bigs SET title='test4'", + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual( + Long.fromString('90071992547409925').compare( + result.insertId + ), + 0 + ); + connection.query( + { + sql: 'select * from bigs', + // @ts-expect-error: supportBigNumbers is not in QueryOptions typings + supportBigNumbers: true, + bigNumberString: false, + }, + (_err, result) => { + if (_err) return reject(_err); + assert.strictEqual(result[0].id, 123); + assert.strictEqual(result[1].id, 124); + assert.strictEqual(result[2].id, 123456789); + assert.strictEqual(result[3].id, 123456790); + assert.strictEqual(result[4].id, 9007199254740992); + assert.strictEqual(result[5].id, '9007199254740993'); + assert.strictEqual(result[6].id, '90071992547409924'); + assert.strictEqual(result[7].id, '90071992547409925'); + connection.end(); + resolve(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); +}); diff --git a/test/esm/integration/connection/test-insert-json.test.mts b/test/esm/integration/connection/test-insert-json.test.mts index e88cf298e0..c938d67458 100644 --- a/test/esm/integration/connection/test-insert-json.test.mts +++ b/test/esm/integration/connection/test-insert-json.test.mts @@ -4,36 +4,52 @@ */ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type JsonRow = RowDataPacket & { data: { k: string } }; -const connection = createConnection(); - -let result: JsonRow[] | undefined; -let errorCodeInvalidJSON: string | undefined; -let errorNumInvalidJSON: number | undefined; - -connection.query('CREATE TEMPORARY TABLE json_test (data JSON)'); -connection.query('INSERT INTO json_test VALUES (?)', ['{"k": "v"'], (err) => { - errorCodeInvalidJSON = err?.code; - errorNumInvalidJSON = err?.errno; -}); - -connection.query('INSERT INTO json_test VALUES (?)', ['{"k": "v"}'], (err) => { - if (err) throw err; -}); - -connection.query('SELECT * FROM json_test;', [], (err, res) => { - if (err) throw err; - result = res; - connection.end(); -}); - -process.on('exit', () => { - assert.equal(errorCodeInvalidJSON, 'ER_INVALID_JSON_TEXT'); - assert.equal(errorNumInvalidJSON, 3140); - assert.equal(result?.[0].data.k, 'v'); +await describe('Insert JSON', async () => { + const connection = createConnection(); + + connection.query('CREATE TEMPORARY TABLE json_test (data JSON)'); + + let errorCodeInvalidJSON: string | undefined; + let errorNumInvalidJSON: number | undefined; + + await it('should handle JSON insert and invalid JSON error', async () => { + await new Promise((resolve, reject) => { + connection.query( + 'INSERT INTO json_test VALUES (?)', + ['{"k": "v"'], + (err) => { + errorCodeInvalidJSON = err?.code; + errorNumInvalidJSON = err?.errno; + } + ); + + connection.query( + 'INSERT INTO json_test VALUES (?)', + ['{"k": "v"}'], + (err) => { + if (err) return reject(err); + } + ); + + connection.query( + 'SELECT * FROM json_test;', + [], + (err, res) => { + if (err) return reject(err); + + assert.equal(errorCodeInvalidJSON, 'ER_INVALID_JSON_TEXT'); + assert.equal(errorNumInvalidJSON, 3140); + assert.equal(res?.[0].data.k, 'v'); + + connection.end(); + resolve(); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-insert-large-blob.test.mts b/test/esm/integration/connection/test-insert-large-blob.test.mts index 0cdfbfbc83..4a70e52d59 100644 --- a/test/esm/integration/connection/test-insert-large-blob.test.mts +++ b/test/esm/integration/connection/test-insert-large-blob.test.mts @@ -1,7 +1,6 @@ import type { ResultSetHeader, RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type BlobRow = RowDataPacket & { id: number; content: Buffer }; @@ -10,89 +9,93 @@ type BlobRow = RowDataPacket & { id: number; content: Buffer }; const _disabled = false as boolean; if (_disabled) { - const connection = createConnection(); + await describe('Insert Large Blob', async () => { + const connection = createConnection(); - /* - connection.query('SELECT repeat("a", 60000000) as qqq', function (err, res) { - console.log(err); - console.log(err, res[0].qqq.length); - connection.end(); - }); - return; -*/ + /* + connection.query('SELECT repeat("a", 60000000) as qqq', function (err, res) { + console.log(err); + console.log(err, res[0].qqq.length); + connection.end(); + }); + return; + */ - const table = 'insert_large_test'; - const length = 35777416; - const content = Buffer.allocUnsafe(length); // > 16 megabytes - const content1 = Buffer.allocUnsafe(length); // > 16 megabytes + const table = 'insert_large_test'; + const length = 35777416; + const content = Buffer.allocUnsafe(length); // > 16 megabytes + const content1 = Buffer.allocUnsafe(length); // > 16 megabytes - // this is to force compressed packed to be larger than uncompressed - for (let i = 0; i < content.length; ++i) { - content[i] = Math.floor(Math.random() * 256); - content1[i] = Math.floor(Math.random() * 256); + // this is to force compressed packed to be larger than uncompressed + for (let i = 0; i < content.length; ++i) { + content[i] = Math.floor(Math.random() * 256); + content1[i] = Math.floor(Math.random() * 256); - // low entropy version, compressed < uncompressed - if (i < length / 2) { - content1[i] = 100; + // low entropy version, compressed < uncompressed + if (i < length / 2) { + content1[i] = 100; + } } - } - let result: ResultSetHeader; - let result2: BlobRow[]; - let result3: ResultSetHeader; - let result4: BlobRow[]; + await it('should insert and retrieve large blobs', async () => { + await new Promise((resolve, reject) => { + connection.query( + `SET GLOBAL max_allowed_packet=${length * 2 + 2000}`, + (err) => { + if (err) return reject(err); + connection.end(); + const connection2 = createConnection(); + connection2.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`content` longblob NOT NULL,', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); + connection2.query( + `INSERT INTO ${table} (content) VALUES(?)`, + [content], + (err, result) => { + if (err) return reject(err); + connection2.query( + `SELECT * FROM ${table} WHERE id = ${result.insertId}`, + (_err, result2) => { + if (_err) return reject(_err); - connection.query( - `SET GLOBAL max_allowed_packet=${length * 2 + 2000}`, - (err) => { - assert.ifError(err); - connection.end(); - const connection2 = createConnection(); - connection2.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`content` longblob NOT NULL,', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') - ); - connection2.query( - `INSERT INTO ${table} (content) VALUES(?)`, - [content], - (err, _result) => { - assert.ifError(err); - result = _result; - connection2.query( - `SELECT * FROM ${table} WHERE id = ${result.insertId}`, - (_err, _result2) => { - result2 = _result2; - connection2.query( - `INSERT INTO ${table} (content) VALUES(?)`, - [content1], - (err, _result) => { - assert.ifError(err); - result3 = _result; - connection2.query( - `SELECT * FROM ${table} WHERE id = ${result3.insertId}`, - (err, _result) => { - assert.ifError(err); - result4 = _result; - connection2.end(); - } - ); - } - ); - } - ); - } - ); - } - ); + assert.equal(result2[0].id, String(result.insertId)); + assert.equal( + result2[0].content.toString('hex'), + content.toString('hex') + ); - process.on('exit', () => { - assert.equal(result2[0].id, String(result.insertId)); - assert.equal(result2[0].content.toString('hex'), content.toString('hex')); - assert.equal(result4[0].content.toString('hex'), content1.toString('hex')); + connection2.query( + `INSERT INTO ${table} (content) VALUES(?)`, + [content1], + (err, result3) => { + if (err) return reject(err); + connection2.query( + `SELECT * FROM ${table} WHERE id = ${result3.insertId}`, + (err, result4) => { + if (err) return reject(err); + assert.equal( + result4[0].content.toString('hex'), + content1.toString('hex') + ); + connection2.end(); + resolve(); + } + ); + } + ); + } + ); + } + ); + } + ); + }); + }); }); } diff --git a/test/esm/integration/connection/test-insert-negative-ai.test.mts b/test/esm/integration/connection/test-insert-negative-ai.test.mts index c03134f7cf..d980ef54a4 100644 --- a/test/esm/integration/connection/test-insert-negative-ai.test.mts +++ b/test/esm/integration/connection/test-insert-negative-ai.test.mts @@ -1,58 +1,55 @@ import type { ResultSetHeader, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type NegAIRow = RowDataPacket & { id: number; title: string }; -const connection = createConnection(); - -const testTable = 'neg-ai-test'; -const testData = 'test negative ai'; - -let selectResult: NegAIRow[]; -let insertResult: ResultSetHeader; - -const testNegativeAI = function (_err: Error | null) { - assert.ifError(_err); - // insert the negative AI - connection.query( - `INSERT INTO \`${testTable}\`` + - ` (id, title) values (-999, "${testData}")`, - (_err, result) => { - assert.ifError(_err); - insertResult = result; - - // select the row with negative AI - connection.query( - `SELECT * FROM \`${testTable}\`` + ` WHERE id = ${result.insertId}`, - (_err, result_) => { - assert.ifError(_err); - selectResult = result_; - connection.end(); +await describe('Insert Negative Auto Increment', async () => { + const connection = createConnection(); + + const testTable = 'neg-ai-test'; + const testData = 'test negative ai'; + + await it('should handle negative auto increment IDs', async () => { + await new Promise((resolve, reject) => { + connection.query( + `CREATE TEMPORARY TABLE \`${testTable}\` (` + + `\`id\` int(11) signed NOT NULL AUTO_INCREMENT,` + + `\`title\` varchar(255),` + + `PRIMARY KEY (\`id\`)` + + `) ENGINE=InnoDB DEFAULT CHARSET=utf8`, + (_err) => { + if (_err) return reject(_err); + // insert the negative AI + connection.query( + `INSERT INTO \`${testTable}\`` + + ` (id, title) values (-999, "${testData}")`, + (_err, insertResult) => { + if (_err) return reject(_err); + + // select the row with negative AI + connection.query( + `SELECT * FROM \`${testTable}\`` + + ` WHERE id = ${insertResult.insertId}`, + (_err, selectResult) => { + if (_err) return reject(_err); + + assert.strictEqual(insertResult.insertId, -999); + assert.strictEqual(selectResult.length, 1); + assert.equal( + selectResult[0].id, + String(insertResult.insertId) + ); + assert.equal(selectResult[0].title, testData); + + connection.end(); + resolve(); + } + ); + } + ); } ); - } - ); -}; - -const prepareAndTest = function () { - connection.query( - `CREATE TEMPORARY TABLE \`${testTable}\` (` + - `\`id\` int(11) signed NOT NULL AUTO_INCREMENT,` + - `\`title\` varchar(255),` + - `PRIMARY KEY (\`id\`)` + - `) ENGINE=InnoDB DEFAULT CHARSET=utf8`, - testNegativeAI - ); -}; - -prepareAndTest(); - -process.on('exit', () => { - assert.strictEqual(insertResult.insertId, -999); - assert.strictEqual(selectResult.length, 1); - - assert.equal(selectResult[0].id, String(insertResult.insertId)); - assert.equal(selectResult[0].title, testData); + }); + }); }); diff --git a/test/esm/integration/connection/test-insert-results.test.mts b/test/esm/integration/connection/test-insert-results.test.mts index fc3ccadc73..564ea633ce 100644 --- a/test/esm/integration/connection/test-insert-results.test.mts +++ b/test/esm/integration/connection/test-insert-results.test.mts @@ -1,50 +1,50 @@ import type { ResultSetHeader, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type InsertTestRow = RowDataPacket & { id: number; title: string }; -const connection = createConnection(); +await describe('Insert Results', async () => { + const connection = createConnection(); -// common.useTestDb(connection); + // common.useTestDb(connection); -const table = 'insert_test'; -// const text = "本日は晴天なり"; -const text = ' test test test '; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); + const table = 'insert_test'; + // const text = "本日は晴天なり"; + const text = ' test test test '; + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); -let result: ResultSetHeader; -let result2: InsertTestRow[]; -connection.query( - `INSERT INTO ${table} SET title="${text}"`, - (err, _result) => { - if (err) { - throw err; - } - result = _result; - connection.query( - `SELECT * FROM ${table} WHERE id = ${result.insertId}`, - (_err, _result2) => { - result2 = _result2; - connection.end(); - } - ); - } -); + await it('should return correct insert results', async () => { + await new Promise((resolve, reject) => { + connection.query( + `INSERT INTO ${table} SET title="${text}"`, + (err, result) => { + if (err) return reject(err); + connection.query( + `SELECT * FROM ${table} WHERE id = ${result.insertId}`, + (_err, result2) => { + if (_err) return reject(_err); -process.on('exit', () => { - assert.strictEqual(result.insertId, 1); - assert.strictEqual(result2.length, 1); - // TODO: type conversions - assert.equal(result2[0].id, String(result.insertId)); - assert.equal(result2[0].title, text); + assert.strictEqual(result.insertId, 1); + assert.strictEqual(result2.length, 1); + // TODO: type conversions + assert.equal(result2[0].id, String(result.insertId)); + assert.equal(result2[0].title, text); + + connection.end(); + resolve(); + } + ); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-invalid-date-result.test.mts b/test/esm/integration/connection/test-invalid-date-result.test.mts index 1d8f8ed423..3a5bf44faa 100644 --- a/test/esm/integration/connection/test-invalid-date-result.test.mts +++ b/test/esm/integration/connection/test-invalid-date-result.test.mts @@ -4,63 +4,58 @@ // Modifications copyright (c) 2021, Oracle and/or its affiliates. import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type SqlModeRow = RowDataPacket & { value: string }; type TimestampRow = RowDataPacket & { t: Date }; -const connection = createConnection(); +await describe('Invalid Date Result', async () => { + const connection = createConnection(); -let rows: TimestampRow[] | undefined = undefined; - -// Disable NO_ZERO_DATE mode and NO_ZERO_IN_DATE mode to ensure the old -// behaviour. -const strictModes = ['NO_ZERO_DATE', 'NO_ZERO_IN_DATE']; - -connection.query( - 'SELECT variable_value as value FROM performance_schema.session_variables where variable_name = ?', - ['sql_mode'], - (err, _rows) => { - if (err) { - throw err; - } - - const deprecatedSqlMode = _rows[0].value - .split(',') - .filter((mode) => strictModes.indexOf(mode) === -1) - .join(','); + function isInvalidTime(t: Date | undefined) { + return t ? isNaN(t.getTime()) : true; + } - connection.query(`SET sql_mode=?`, [deprecatedSqlMode], (err) => { - if (err) { - throw err; - } + // Disable NO_ZERO_DATE mode and NO_ZERO_IN_DATE mode to ensure the old + // behaviour. + const strictModes = ['NO_ZERO_DATE', 'NO_ZERO_IN_DATE']; - connection.execute( - 'SELECT TIMESTAMP(0000-00-00) t', - [], + await it('should handle invalid date values', async () => { + await new Promise((resolve, reject) => { + connection.query( + 'SELECT variable_value as value FROM performance_schema.session_variables where variable_name = ?', + ['sql_mode'], (err, _rows) => { - if (err) { - throw err; - } - - rows = _rows; - connection.end(); + if (err) return reject(err); + + const deprecatedSqlMode = _rows[0].value + .split(',') + .filter((mode) => strictModes.indexOf(mode) === -1) + .join(','); + + connection.query(`SET sql_mode=?`, [deprecatedSqlMode], (err) => { + if (err) return reject(err); + + connection.execute( + 'SELECT TIMESTAMP(0000-00-00) t', + [], + (err, rows) => { + if (err) return reject(err); + + assert.deepEqual( + Object.prototype.toString.call(rows?.[0].t), + '[object Date]' + ); + assert.deepEqual(isInvalidTime(rows?.[0].t), true); + + connection.end(); + resolve(); + } + ); + }); } ); }); - } -); - -function isInvalidTime(t: Date | undefined) { - return t ? isNaN(t.getTime()) : true; -} - -process.on('exit', () => { - assert.deepEqual( - Object.prototype.toString.call(rows?.[0].t), - '[object Date]' - ); - assert.deepEqual(isInvalidTime(rows?.[0].t), true); + }); }); diff --git a/test/esm/integration/connection/test-load-infile.test.mts b/test/esm/integration/connection/test-load-infile.test.mts index 16feb055f3..8273576104 100644 --- a/test/esm/integration/connection/test-load-infile.test.mts +++ b/test/esm/integration/connection/test-load-infile.test.mts @@ -6,7 +6,7 @@ import type { import fs from 'node:fs'; import process from 'node:process'; import { PassThrough } from 'node:stream'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { @@ -14,101 +14,103 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); +await describe('Load Infile', async () => { + const connection = createConnection(); -const table = 'load_data_test'; -connection.query('SET GLOBAL local_infile = true', assert.ifError); -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); + const table = 'load_data_test'; + connection.query('SET GLOBAL local_infile = true', assert.ifError); + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); -const path = './test/fixtures/data.csv'; -const sql = - `LOAD DATA LOCAL INFILE ? INTO TABLE ${table} ` + - `FIELDS TERMINATED BY ? (id, title)`; + const path = './test/fixtures/data.csv'; + const sql = + `LOAD DATA LOCAL INFILE ? INTO TABLE ${table} ` + + `FIELDS TERMINATED BY ? (id, title)`; -let ok: ResultSetHeader; -connection.query( - { - sql, - values: [path, ','], - infileStreamFactory: () => fs.createReadStream(path), - }, - (err, _ok) => { - if (err) { - throw err; - } - ok = _ok; - } -); + await it('should load data from file and stream', async () => { + await new Promise((resolve, reject) => { + let ok: ResultSetHeader; + let rows: RowDataPacket[]; + let loadErr: QueryError | null = null; + let loadResult: ResultSetHeader; -let rows: RowDataPacket[]; -connection.query(`SELECT * FROM ${table}`, (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; -}); + connection.query( + { + sql, + values: [path, ','], + infileStreamFactory: () => fs.createReadStream(path), + }, + (err, _ok) => { + if (err) return reject(err); + ok = _ok; + } + ); -// Try to load a file that does not exist to see if we handle this properly -let loadErr: QueryError | null = null; -let loadResult: ResultSetHeader; -const badPath = '/does_not_exist.csv'; + connection.query( + `SELECT * FROM ${table}`, + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + } + ); -connection.query(sql, [badPath, ','], (err, result) => { - loadErr = err; - loadResult = result; -}); + // Try to load a file that does not exist to see if we handle this properly + const badPath = '/does_not_exist.csv'; -// test path mapping -const createMyStream = function () { - const myStream = new PassThrough(); - setTimeout(() => { - myStream.write('11,Hello World\n'); - myStream.write('21,One '); - myStream.write('more row\n'); - myStream.end(); - }, 1000); - return myStream; -}; + connection.query(sql, [badPath, ','], (err, result) => { + loadErr = err; + loadResult = result; + }); -let streamResult: ResultSetHeader; -connection.query( - { - sql: sql, - values: [badPath, ','], - infileStreamFactory: createMyStream, - }, - (err, result) => { - if (err) { - throw err; - } - streamResult = result; - connection.end(); - } -); + // test path mapping + const createMyStream = function () { + const myStream = new PassThrough(); + setTimeout(() => { + myStream.write('11,Hello World\n'); + myStream.write('21,One '); + myStream.write('more row\n'); + myStream.end(); + }, 1000); + return myStream; + }; -process.on('exit', () => { - assert.equal(ok.affectedRows, 4); - assert.equal(rows.length, 4); - assert.equal(rows[0].id, 1); - assert.equal(rows[0].title.trim(), 'Hello World'); + connection.query( + { + sql: sql, + values: [badPath, ','], + infileStreamFactory: createMyStream, + }, + (err, streamResult) => { + if (err) return reject(err); - assert(loadErr, 'Expected LOAD DATA error'); - if (!loadErr) { - return; - } - assert.equal( - loadErr.message, - `As a result of LOCAL INFILE command server wants to read /does_not_exist.csv file, but as of v2.0 you must provide streamFactory option returning ReadStream.` - ); - assert.equal(loadResult.affectedRows, 0); + assert.equal(ok.affectedRows, 4); + assert.equal(rows.length, 4); + assert.equal(rows[0].id, 1); + assert.equal(rows[0].title.trim(), 'Hello World'); + + assert(loadErr, 'Expected LOAD DATA error'); + if (!loadErr) { + return; + } + assert.equal( + loadErr.message, + `As a result of LOCAL INFILE command server wants to read /does_not_exist.csv file, but as of v2.0 you must provide streamFactory option returning ReadStream.` + ); + assert.equal(loadResult.affectedRows, 0); + + assert.equal(streamResult.affectedRows, 2); - assert.equal(streamResult.affectedRows, 2); + connection.end(); + resolve(); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-multiple-results.test.mts b/test/esm/integration/connection/test-multiple-results.test.mts index 9e3b12ddf5..a8eb731d49 100644 --- a/test/esm/integration/connection/test-multiple-results.test.mts +++ b/test/esm/integration/connection/test-multiple-results.test.mts @@ -6,6 +6,7 @@ import process from 'node:process'; // @ts-expect-error: no typings available import assert from 'assert-diff'; +import { describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { @@ -13,225 +14,242 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const mysql = createConnection({ - multipleStatements: true, -}); -mysql.query('CREATE TEMPORARY TABLE no_rows (test int)'); -mysql.query('CREATE TEMPORARY TABLE some_rows (test int)'); -mysql.query('INSERT INTO some_rows values(0)'); -mysql.query('INSERT INTO some_rows values(42)'); -mysql.query('INSERT INTO some_rows values(314149)'); - -const clone = function (obj: T): T { - return JSON.parse(JSON.stringify(obj)) as T; -}; - -const rs1 = { - affectedRows: 0, - fieldCount: 0, - insertId: 0, - serverStatus: 10, - warningStatus: 0, - info: '', - changedRows: 0, -}; -const rs2 = clone(rs1); -rs2.serverStatus = 2; - -const twoInsertResult = [[rs1, rs2], [undefined, undefined], 2]; -const select1 = [{ 1: '1' }]; -const select2 = [{ 2: '2' }]; -const fields1 = [ - { - catalog: 'def', - characterSet: 63, - encoding: 'binary', - type: 8, - decimals: 0, - flags: 129, - name: '1', - orgName: '', - orgTable: '', - schema: '', - table: '', - }, -]; -const nr_fields = [ - { - catalog: 'def', - characterSet: 63, - encoding: 'binary', - type: 3, - decimals: 0, - flags: 0, - name: 'test', - orgName: 'test', - orgTable: 'no_rows', - schema: mysql.config.database, - table: 'no_rows', - }, -]; -const sr_fields = clone(nr_fields); -sr_fields[0].orgTable = 'some_rows'; -sr_fields[0].table = 'some_rows'; -const select3 = [{ test: 0 }, { test: 42 }, { test: 314149 }]; - -const fields2 = clone(fields1); -fields2[0].name = '2'; - -const tests: [string, unknown[]][] = [ - ['select * from some_rows', [select3, sr_fields, 1]], // select 3 rows - [ - 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;', - twoInsertResult, - ], - [ - '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;', - twoInsertResult, - ], // issue #26 - ['set @a = 1', [rs2, undefined, 1]], // one insert result - ['set @a = 1; set @b = 2', twoInsertResult], - ['select 1; select 2', [[select1, select2], [fields1, fields2], 2]], - ['set @a = 1; select 1', [[rs1, select1], [undefined, fields1], 2]], - ['select 1; set @a = 1', [[select1, rs2], [fields1, undefined], 2]], - ['select * from no_rows', [[], nr_fields, 1]], // select 0 rows" - ['set @a = 1; select * from no_rows', [[rs1, []], [undefined, nr_fields], 2]], // insert + select 0 rows - ['select * from no_rows; set @a = 1', [[[], rs2], [nr_fields, undefined], 2]], // select 0 rows + insert - [ - 'set @a = 1; select * from some_rows', - [[rs1, select3], [undefined, sr_fields], 2], - ], // insert + select 3 rows - [ - 'select * from some_rows; set @a = 1', - [[select3, rs2], [sr_fields, undefined], 2], - ], // select 3 rows + insert -]; - -const hasConstructorName = ( - value: unknown -): value is { constructor: { name: string } } => { - if (typeof value !== 'object' || value === null) { - return false; - } - - const candidate = value as { constructor?: { name?: unknown } }; - return typeof candidate.constructor?.name === 'string'; -}; - -// TODO: tests with error in the query with different index -// TODO: multiple results from single query - -function do_test(testIndex: number) { - const entry = tests[testIndex]; - const sql = entry[0]; - const expectation = entry[1]; - mysql.query(sql, (err, _rows, _columns) => { - if (err) { - console.log(err); - process.exit(-1); - } - - const rows = _rows; - let _numResults = 0; - if ( - hasConstructorName(rows) && - rows.constructor.name === 'ResultSetHeader' - ) { - _numResults = 1; - } else if (Array.isArray(rows) && rows.length === 0) { - // empty select - _numResults = 1; - } else if (Array.isArray(rows) && rows.length > 0) { - _numResults = 1; - const first = rows[0]; - if ( - Array.isArray(first) || - (hasConstructorName(first) && - first.constructor.name === 'ResultSetHeader') - ) { - _numResults = rows.length; - } +await describe('Multiple Results', async () => { + const mysql = createConnection({ + multipleStatements: true, + }); + mysql.query('CREATE TEMPORARY TABLE no_rows (test int)'); + mysql.query('CREATE TEMPORARY TABLE some_rows (test int)'); + mysql.query('INSERT INTO some_rows values(0)'); + mysql.query('INSERT INTO some_rows values(42)'); + mysql.query('INSERT INTO some_rows values(314149)'); + + const clone = function (obj: T): T { + return JSON.parse(JSON.stringify(obj)) as T; + }; + + const rs1 = { + affectedRows: 0, + fieldCount: 0, + insertId: 0, + serverStatus: 10, + warningStatus: 0, + info: '', + changedRows: 0, + }; + const rs2 = clone(rs1); + rs2.serverStatus = 2; + + const twoInsertResult = [[rs1, rs2], [undefined, undefined], 2]; + const select1 = [{ 1: '1' }]; + const select2 = [{ 2: '2' }]; + const fields1 = [ + { + catalog: 'def', + characterSet: 63, + encoding: 'binary', + type: 8, + decimals: 0, + flags: 129, + name: '1', + orgName: '', + orgTable: '', + schema: '', + table: '', + }, + ]; + const nr_fields = [ + { + catalog: 'def', + characterSet: 63, + encoding: 'binary', + type: 3, + decimals: 0, + flags: 0, + name: 'test', + orgName: 'test', + orgTable: 'no_rows', + schema: mysql.config.database, + table: 'no_rows', + }, + ]; + const sr_fields = clone(nr_fields); + sr_fields[0].orgTable = 'some_rows'; + sr_fields[0].table = 'some_rows'; + const select3 = [{ test: 0 }, { test: 42 }, { test: 314149 }]; + + const fields2 = clone(fields1); + fields2[0].name = '2'; + + const tests: [string, unknown[]][] = [ + ['select * from some_rows', [select3, sr_fields, 1]], // select 3 rows + [ + 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;', + twoInsertResult, + ], + [ + '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;', + twoInsertResult, + ], // issue #26 + ['set @a = 1', [rs2, undefined, 1]], // one insert result + ['set @a = 1; set @b = 2', twoInsertResult], + ['select 1; select 2', [[select1, select2], [fields1, fields2], 2]], + ['set @a = 1; select 1', [[rs1, select1], [undefined, fields1], 2]], + ['select 1; set @a = 1', [[select1, rs2], [fields1, undefined], 2]], + ['select * from no_rows', [[], nr_fields, 1]], // select 0 rows" + [ + 'set @a = 1; select * from no_rows', + [[rs1, []], [undefined, nr_fields], 2], + ], // insert + select 0 rows + [ + 'select * from no_rows; set @a = 1', + [[[], rs2], [nr_fields, undefined], 2], + ], // select 0 rows + insert + [ + 'set @a = 1; select * from some_rows', + [[rs1, select3], [undefined, sr_fields], 2], + ], // insert + select 3 rows + [ + 'select * from some_rows; set @a = 1', + [[select3, rs2], [sr_fields, undefined], 2], + ], // select 3 rows + insert + ]; + + const hasConstructorName = ( + value: unknown + ): value is { constructor: { name: string } } => { + if (typeof value !== 'object' || value === null) { + return false; } - const arrOrColumn = function (c: unknown): unknown { - if (Array.isArray(c)) { - return c.map(arrOrColumn); - } + const candidate = value as { constructor?: { name?: unknown } }; + return typeof candidate.constructor?.name === 'string'; + }; + + // TODO: tests with error in the query with different index + // TODO: multiple results from single query + + await it('should handle multiple result sets', async () => { + await new Promise((resolve) => { + function do_test(testIndex: number) { + const entry = tests[testIndex]; + const sql = entry[0]; + const expectation = entry[1]; + mysql.query(sql, (err, _rows, _columns) => { + if (err) { + console.log(err); + process.exit(-1); + } - if (typeof c === 'undefined') { - return void 0; - } + const rows = _rows; + let _numResults = 0; + if ( + hasConstructorName(rows) && + rows.constructor.name === 'ResultSetHeader' + ) { + _numResults = 1; + } else if (Array.isArray(rows) && rows.length === 0) { + // empty select + _numResults = 1; + } else if (Array.isArray(rows) && rows.length > 0) { + _numResults = 1; + const first = rows[0]; + if ( + Array.isArray(first) || + (hasConstructorName(first) && + first.constructor.name === 'ResultSetHeader') + ) { + _numResults = rows.length; + } + } - // @ts-expect-error: internal access - const column = c.inspect() as Record; - // "columnLength" is non-deterministic and the display width for integer - // data types was deprecated on MySQL 8.0.17. - // https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html - delete column.columnLength; - - return column; - }; - - assert.deepEqual(expectation, [_rows, arrOrColumn(_columns), _numResults]); - - const q = mysql.query(sql); - let resIndex = 0; - let rowIndex = 0; - - let fieldIndex = -1; - - const multiRows: unknown[] = Array.isArray(_rows) ? _rows : [_rows]; - - function checkRow(row: { - constructor: { name: string }; - [key: string]: unknown; - }) { - const index = fieldIndex; - if (_numResults === 1) { - assert.equal(fieldIndex, 0); - if (row.constructor.name === 'ResultSetHeader') { - assert.deepEqual(_rows, row); - } else { - assert.deepEqual(multiRows[rowIndex], row); - } - } else { - if (resIndex !== index) { - rowIndex = 0; - resIndex = index; - } - if (row.constructor.name === 'ResultSetHeader') { - assert.deepEqual(multiRows[index], row); - } else { - const resultRows = multiRows[index]; - if (Array.isArray(resultRows)) { - assert.deepEqual(resultRows[rowIndex], row); + const arrOrColumn = function (c: unknown): unknown { + if (Array.isArray(c)) { + return c.map(arrOrColumn); + } + + if (typeof c === 'undefined') { + return void 0; + } + + // @ts-expect-error: internal access + const column = c.inspect() as Record; + // "columnLength" is non-deterministic and the display width for integer + // data types was deprecated on MySQL 8.0.17. + // https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html + delete column.columnLength; + + return column; + }; + + assert.deepEqual(expectation, [ + _rows, + arrOrColumn(_columns), + _numResults, + ]); + + const q = mysql.query(sql); + let resIndex = 0; + let rowIndex = 0; + + let fieldIndex = -1; + + const multiRows: unknown[] = Array.isArray(_rows) ? _rows : [_rows]; + + function checkRow(row: { + constructor: { name: string }; + [key: string]: unknown; + }) { + const index = fieldIndex; + if (_numResults === 1) { + assert.equal(fieldIndex, 0); + if (row.constructor.name === 'ResultSetHeader') { + assert.deepEqual(_rows, row); + } else { + assert.deepEqual(multiRows[rowIndex], row); + } + } else { + if (resIndex !== index) { + rowIndex = 0; + resIndex = index; + } + if (row.constructor.name === 'ResultSetHeader') { + assert.deepEqual(multiRows[index], row); + } else { + const resultRows = multiRows[index]; + if (Array.isArray(resultRows)) { + assert.deepEqual(resultRows[rowIndex], row); + } + } + } + rowIndex++; } - } - } - rowIndex++; - } - function checkFields(fields: unknown) { - fieldIndex++; - if (_numResults === 1) { - assert.equal(fieldIndex, 0); - assert.deepEqual(arrOrColumn(_columns), arrOrColumn(fields)); - } else { - assert.deepEqual( - arrOrColumn(_columns[fieldIndex]), - arrOrColumn(fields) - ); - } - } - q.on('result', checkRow); - q.on('fields', checkFields); - q.on('end', () => { - if (testIndex + 1 < tests.length) { - do_test(testIndex + 1); - } else { - mysql.end(); + function checkFields(fields: unknown) { + fieldIndex++; + if (_numResults === 1) { + assert.equal(fieldIndex, 0); + assert.deepEqual(arrOrColumn(_columns), arrOrColumn(fields)); + } else { + assert.deepEqual( + arrOrColumn(_columns[fieldIndex]), + arrOrColumn(fields) + ); + } + } + q.on('result', checkRow); + q.on('fields', checkFields); + q.on('end', () => { + if (testIndex + 1 < tests.length) { + do_test(testIndex + 1); + } else { + mysql.end(); + resolve(); + } + }); + }); } + do_test(0); }); }); -} -do_test(0); +}); diff --git a/test/esm/integration/connection/test-named-placeholders.test.mts b/test/esm/integration/connection/test-named-placeholders.test.mts index bca0dd37a7..1fd706cba6 100644 --- a/test/esm/integration/connection/test-named-placeholders.test.mts +++ b/test/esm/integration/connection/test-named-placeholders.test.mts @@ -1,105 +1,112 @@ import type { Pool, RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createPool } from '../../common.test.mjs'; type SumRow = RowDataPacket & { sum: number }; -const connection = createConnection(); +await describe('Named Placeholders', async () => { + const connection = createConnection(); -connection.query( - [ - 'CREATE TEMPORARY TABLE `test_table` (', - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`num1` int(15),', - '`num2` int(15),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); + connection.query( + [ + 'CREATE TEMPORARY TABLE `test_table` (', + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`num1` int(15),', + '`num2` int(15),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); -connection.query('insert into test_table(num1,num2) values(?, 3)', [1]); -connection.query('insert into test_table(num1,num2) values(3-?, -10)', [5]); -connection.query( - 'insert into test_table(num1,num2) values(4+?, 4000000-?)', - [-5, 8000000] -); -connection.query( - 'insert into test_table(num1,num2) values(?, ?)', - [-5, 8000000] -); + connection.query('insert into test_table(num1,num2) values(?, 3)', [1]); + connection.query('insert into test_table(num1,num2) values(3-?, -10)', [5]); + connection.query( + 'insert into test_table(num1,num2) values(4+?, 4000000-?)', + [-5, 8000000] + ); + connection.query( + 'insert into test_table(num1,num2) values(?, ?)', + [-5, 8000000] + ); -connection.config.namedPlaceholders = true; -const cmd = connection.execute( - 'SELECT * from test_table where num1 < :numParam and num2 > :lParam', - { lParam: 100, numParam: 2 }, - (err, rows) => { - if (err) { - throw err; - } - assert.deepEqual(rows, [{ id: 4, num1: -5, num2: 8000000 }]); - } -); -assert.equal(cmd.sql, 'SELECT * from test_table where num1 < ? and num2 > ?'); -// @ts-expect-error: TODO: implement typings for Query.values -assert.deepEqual(cmd.values, [2, 100]); + connection.config.namedPlaceholders = true; -connection.execute('SELECT :a + :a as sum', { a: 2 }, (err, rows) => { - if (err) { - throw err; - } - assert.deepEqual(rows, [{ sum: 4 }]); -}); + await it('should handle named placeholders', async () => { + await new Promise((resolve, reject) => { + const cmd = connection.execute( + 'SELECT * from test_table where num1 < :numParam and num2 > :lParam', + { lParam: 100, numParam: 2 }, + (err, rows) => { + if (err) return reject(err); + assert.deepEqual(rows, [{ id: 4, num1: -5, num2: 8000000 }]); + } + ); + assert.equal( + cmd.sql, + 'SELECT * from test_table where num1 < ? and num2 > ?' + ); + // @ts-expect-error: TODO: implement typings for Query.values + assert.deepEqual(cmd.values, [2, 100]); -const qCmd = connection.query( - 'SELECT * from test_table where num1 < :numParam and num2 > :lParam', - { lParam: 100, numParam: 2 }, - (err, rows) => { - if (err) { - throw err; - } - assert.deepEqual(rows, [{ id: 4, num1: -5, num2: 8000000 }]); - } -); -assert.equal( - qCmd.sql, - 'SELECT * from test_table where num1 < 2 and num2 > 100' -); -// @ts-expect-error: TODO: implement typings for Query.values -assert.deepEqual(qCmd.values, [2, 100]); + connection.execute('SELECT :a + :a as sum', { a: 2 }, (err, rows) => { + if (err) return reject(err); + assert.deepEqual(rows, [{ sum: 4 }]); + }); -connection.query('SELECT :a + :a as sum', { a: 2 }, (err, rows) => { - if (err) { - throw err; - } - assert.deepEqual(rows, [{ sum: 4 }]); - connection.end(); -}); + const qCmd = connection.query( + 'SELECT * from test_table where num1 < :numParam and num2 > :lParam', + { lParam: 100, numParam: 2 }, + (err, rows) => { + if (err) return reject(err); + assert.deepEqual(rows, [{ id: 4, num1: -5, num2: 8000000 }]); + } + ); + assert.equal( + qCmd.sql, + 'SELECT * from test_table where num1 < 2 and num2 > 100' + ); + // @ts-expect-error: TODO: implement typings for Query.values + assert.deepEqual(qCmd.values, [2, 100]); + + connection.query('SELECT :a + :a as sum', { a: 2 }, (err, rows) => { + if (err) return reject(err); + assert.deepEqual(rows, [{ sum: 4 }]); + connection.end(); + resolve(); + }); + }); + + const namedSql = connection.format( + 'SELECT * from test_table where num1 < :numParam and num2 > :lParam', + { lParam: 100, numParam: 2 } + ); + assert.equal( + namedSql, + 'SELECT * from test_table where num1 < 2 and num2 > 100' + ); -const namedSql = connection.format( - 'SELECT * from test_table where num1 < :numParam and num2 > :lParam', - { lParam: 100, numParam: 2 } -); -assert.equal( - namedSql, - 'SELECT * from test_table where num1 < 2 and num2 > 100' -); + const unnamedSql = connection.format( + 'SELECT * from test_table where num1 < ? and num2 > ?', + [2, 100] + ); + assert.equal( + unnamedSql, + 'SELECT * from test_table where num1 < 2 and num2 > 100' + ); + }); -const unnamedSql = connection.format( - 'SELECT * from test_table where num1 < ? and num2 > ?', - [2, 100] -); -assert.equal( - unnamedSql, - 'SELECT * from test_table where num1 < 2 and num2 > 100' -); + await it('should handle named placeholders in pool', async () => { + const pool: Pool = createPool(); + // @ts-expect-error: TODO: implement typings + pool.config.connectionConfig.namedPlaceholders = true; -const pool: Pool = createPool(); -// @ts-expect-error: TODO: implement typings -pool.config.connectionConfig.namedPlaceholders = true; -pool.query('SELECT :a + :a as sum', { a: 2 }, (err, rows) => { - pool.end(); - if (err) { - throw err; - } - assert.deepEqual(rows, [{ sum: 4 }]); + await new Promise((resolve, reject) => { + pool.query('SELECT :a + :a as sum', { a: 2 }, (err, rows) => { + pool.end(); + if (err) return reject(err); + assert.deepEqual(rows, [{ sum: 4 }]); + resolve(); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-nested-tables-query.test.mts b/test/esm/integration/connection/test-nested-tables-query.test.mts index fdcaedc538..f81cd0c748 100644 --- a/test/esm/integration/connection/test-nested-tables-query.test.mts +++ b/test/esm/integration/connection/test-nested-tables-query.test.mts @@ -1,172 +1,161 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, useTestDb } from '../../common.test.mjs'; -const connection = createConnection(); - -useTestDb(); - -const table = 'nested_test'; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}1\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); - -connection.query(`INSERT INTO ${table} SET ?`, { title: 'test' }); -connection.query(`INSERT INTO ${table}1 SET ?`, { title: 'test1' }); - -const options1 = { - nestTables: true, - sql: `SELECT * FROM ${table}`, -}; -const options2 = { - nestTables: '_', - sql: `SELECT * FROM ${table}`, -}; -const options3 = { - rowsAsArray: true, - sql: `SELECT * FROM ${table}`, -}; -const options4 = { - nestTables: true, - sql: `SELECT notNested.id, notNested.title, nested.title FROM ${table} notNested LEFT JOIN ${table}1 nested ON notNested.id = nested.id`, -}; -const options5 = { - nestTables: true, - sql: `SELECT notNested.id, notNested.title, nested2.title FROM ${table} notNested LEFT JOIN ${table}1 nested2 ON notNested.id = nested2.id`, -}; - -let rows1: RowDataPacket[], - rows2: RowDataPacket[], - rows3: RowDataPacket[], - rows4: RowDataPacket[], - rows5: RowDataPacket[], - rows1e: RowDataPacket[], - rows2e: RowDataPacket[], - rows3e: RowDataPacket[]; - -connection.query(options1, (err, _rows) => { - if (err) { - throw err; - } - - rows1 = _rows; -}); - -connection.query(options2, (err, _rows) => { - if (err) { - throw err; - } - - rows2 = _rows; -}); - -connection.query(options3, (err, _rows) => { - if (err) { - throw err; - } - - rows3 = _rows; -}); - -connection.query(options4, (err, _rows) => { - if (err) { - throw err; - } - - rows4 = _rows; -}); - -connection.query(options5, (err, _rows) => { - if (err) { - throw err; - } +await describe('Nested Tables Query', async () => { + const connection = createConnection(); - rows5 = _rows; -}); - -connection.execute(options1, (err, _rows) => { - if (err) { - throw err; - } - - rows1e = _rows; -}); - -connection.execute(options2, (err, _rows) => { - if (err) { - throw err; - } - - rows2e = _rows; -}); - -connection.execute(options3, (err, _rows) => { - if (err) { - throw err; - } - - rows3e = _rows; - connection.end(); -}); + useTestDb(); -process.on('exit', () => { - assert.equal(rows1.length, 1, 'First row length'); - assert.equal(rows1[0].nested_test.id, 1, 'First row nested id'); - assert.equal(rows1[0].nested_test.title, 'test', 'First row nested title'); - assert.equal(rows2.length, 1, 'Second row length'); - assert.equal(rows2[0].nested_test_id, 1, 'Second row nested id'); - assert.equal(rows2[0].nested_test_title, 'test', 'Second row nested title'); - - assert.equal(Array.isArray(rows3[0]), true, 'Third row type'); - assert.equal(rows3[0][0], 1, 'Third row value 1'); - assert.equal(rows3[0][1], 'test', 'Third row value 2'); - - assert.equal(rows4.length, 1, 'Fourth row length'); - assert.deepEqual( - rows4[0], - { - nested: { - title: 'test1', - }, - notNested: { - id: 1, - title: 'test', - }, - }, - 'Fourth row value' + const table = 'nested_test'; + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') ); - assert.equal(rows5.length, 1, 'Fifth row length'); - assert.deepEqual( - rows5[0], - { - nested2: { - title: 'test1', - }, - notNested: { - id: 1, - title: 'test', - }, - }, - 'Fifth row value' + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}1\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') ); - assert.deepEqual(rows1, rows1e, 'Compare rows1 with rows1e'); - assert.deepEqual(rows2, rows2e, 'Compare rows2 with rows2e'); - assert.deepEqual(rows3, rows3e, 'Compare rows3 with rows3e'); + connection.query(`INSERT INTO ${table} SET ?`, { title: 'test' }); + connection.query(`INSERT INTO ${table}1 SET ?`, { title: 'test1' }); + + const options1 = { + nestTables: true, + sql: `SELECT * FROM ${table}`, + }; + const options2 = { + nestTables: '_', + sql: `SELECT * FROM ${table}`, + }; + const options3 = { + rowsAsArray: true, + sql: `SELECT * FROM ${table}`, + }; + const options4 = { + nestTables: true, + sql: `SELECT notNested.id, notNested.title, nested.title FROM ${table} notNested LEFT JOIN ${table}1 nested ON notNested.id = nested.id`, + }; + const options5 = { + nestTables: true, + sql: `SELECT notNested.id, notNested.title, nested2.title FROM ${table} notNested LEFT JOIN ${table}1 nested2 ON notNested.id = nested2.id`, + }; + + await it('should handle nested tables and row formats', async () => { + await new Promise((resolve, reject) => { + let rows1: RowDataPacket[]; + let rows2: RowDataPacket[]; + let rows3: RowDataPacket[]; + let rows4: RowDataPacket[]; + let rows5: RowDataPacket[]; + let rows1e: RowDataPacket[]; + let rows2e: RowDataPacket[]; + let rows3e: RowDataPacket[]; + + connection.query(options1, (err, _rows) => { + if (err) return reject(err); + rows1 = _rows; + }); + + connection.query(options2, (err, _rows) => { + if (err) return reject(err); + rows2 = _rows; + }); + + connection.query(options3, (err, _rows) => { + if (err) return reject(err); + rows3 = _rows; + }); + + connection.query(options4, (err, _rows) => { + if (err) return reject(err); + rows4 = _rows; + }); + + connection.query(options5, (err, _rows) => { + if (err) return reject(err); + rows5 = _rows; + }); + + connection.execute(options1, (err, _rows) => { + if (err) return reject(err); + rows1e = _rows; + }); + + connection.execute(options2, (err, _rows) => { + if (err) return reject(err); + rows2e = _rows; + }); + + connection.execute(options3, (err, _rows) => { + if (err) return reject(err); + rows3e = _rows; + + assert.equal(rows1.length, 1, 'First row length'); + assert.equal(rows1[0].nested_test.id, 1, 'First row nested id'); + assert.equal( + rows1[0].nested_test.title, + 'test', + 'First row nested title' + ); + assert.equal(rows2.length, 1, 'Second row length'); + assert.equal(rows2[0].nested_test_id, 1, 'Second row nested id'); + assert.equal( + rows2[0].nested_test_title, + 'test', + 'Second row nested title' + ); + + assert.equal(Array.isArray(rows3[0]), true, 'Third row type'); + assert.equal(rows3[0][0], 1, 'Third row value 1'); + assert.equal(rows3[0][1], 'test', 'Third row value 2'); + + assert.equal(rows4.length, 1, 'Fourth row length'); + assert.deepEqual( + rows4[0], + { + nested: { + title: 'test1', + }, + notNested: { + id: 1, + title: 'test', + }, + }, + 'Fourth row value' + ); + assert.equal(rows5.length, 1, 'Fifth row length'); + assert.deepEqual( + rows5[0], + { + nested2: { + title: 'test1', + }, + notNested: { + id: 1, + title: 'test', + }, + }, + 'Fifth row value' + ); + + assert.deepEqual(rows1, rows1e, 'Compare rows1 with rows1e'); + assert.deepEqual(rows2, rows2e, 'Compare rows2 with rows2e'); + assert.deepEqual(rows3, rows3e, 'Compare rows3 with rows3e'); + + connection.end(); + resolve(); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-null-buffer.test.mts b/test/esm/integration/connection/test-null-buffer.test.mts index 7f0d160a01..0729818e77 100644 --- a/test/esm/integration/connection/test-null-buffer.test.mts +++ b/test/esm/integration/connection/test-null-buffer.test.mts @@ -1,37 +1,33 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Null Buffer', async () => { + const connection = createConnection(); -let rowsTextProtocol: RowDataPacket[]; -let rowsBinaryProtocol: RowDataPacket[]; + connection.query('CREATE TEMPORARY TABLE binary_table (stuff BINARY(16));'); + connection.query('INSERT INTO binary_table VALUES(null)'); -connection.query('CREATE TEMPORARY TABLE binary_table (stuff BINARY(16));'); -connection.query('INSERT INTO binary_table VALUES(null)'); + await it('should handle null buffer values', async () => { + await new Promise((resolve, reject) => { + connection.query( + 'SELECT * from binary_table', + (err, rowsTextProtocol) => { + if (err) return reject(err); + connection.execute( + 'SELECT * from binary_table', + (err, rowsBinaryProtocol) => { + if (err) return reject(err); -connection.query( - 'SELECT * from binary_table', - (err, _rows) => { - if (err) { - throw err; - } - rowsTextProtocol = _rows; - connection.execute( - 'SELECT * from binary_table', - (err, _rows) => { - if (err) { - throw err; - } - rowsBinaryProtocol = _rows; - connection.end(); - } - ); - } -); + assert.deepEqual(rowsTextProtocol[0], { stuff: null }); + assert.deepEqual(rowsBinaryProtocol[0], { stuff: null }); -process.on('exit', () => { - assert.deepEqual(rowsTextProtocol[0], { stuff: null }); - assert.deepEqual(rowsBinaryProtocol[0], { stuff: null }); + connection.end(); + resolve(); + } + ); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-null-double.test.mts b/test/esm/integration/connection/test-null-double.test.mts index abe43ab4e2..aacd5d3ced 100644 --- a/test/esm/integration/connection/test-null-double.test.mts +++ b/test/esm/integration/connection/test-null-double.test.mts @@ -1,25 +1,25 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Null Double', async () => { + const connection = createConnection(); -let rows: RowDataPacket[]; + connection.query('CREATE TEMPORARY TABLE t (i int)'); + connection.query('INSERT INTO t VALUES(null)'); + connection.query('INSERT INTO t VALUES(123)'); -connection.query('CREATE TEMPORARY TABLE t (i int)'); -connection.query('INSERT INTO t VALUES(null)'); -connection.query('INSERT INTO t VALUES(123)'); + await it('should handle null and non-null values', async () => { + const rows = await new Promise((resolve, reject) => { + connection.query('SELECT * from t', (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + }); + }); -connection.query('SELECT * from t', (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); -}); + assert.deepEqual(rows[0], { i: null }); + assert.deepEqual(rows[1], { i: 123 }); + }); -process.on('exit', () => { - assert.deepEqual(rows[0], { i: null }); - assert.deepEqual(rows[1], { i: 123 }); + connection.end(); }); diff --git a/test/esm/integration/connection/test-null-int.test.mts b/test/esm/integration/connection/test-null-int.test.mts index abe43ab4e2..6b3c46c241 100644 --- a/test/esm/integration/connection/test-null-int.test.mts +++ b/test/esm/integration/connection/test-null-int.test.mts @@ -1,25 +1,25 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Null Int', async () => { + const connection = createConnection(); -let rows: RowDataPacket[]; + connection.query('CREATE TEMPORARY TABLE t (i int)'); + connection.query('INSERT INTO t VALUES(null)'); + connection.query('INSERT INTO t VALUES(123)'); -connection.query('CREATE TEMPORARY TABLE t (i int)'); -connection.query('INSERT INTO t VALUES(null)'); -connection.query('INSERT INTO t VALUES(123)'); + await it('should handle null and non-null int values', async () => { + const rows = await new Promise((resolve, reject) => { + connection.query('SELECT * from t', (err, _rows) => { + if (err) return reject(err); + resolve(_rows); + }); + }); -connection.query('SELECT * from t', (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); -}); + assert.deepEqual(rows[0], { i: null }); + assert.deepEqual(rows[1], { i: 123 }); + }); -process.on('exit', () => { - assert.deepEqual(rows[0], { i: null }); - assert.deepEqual(rows[1], { i: 123 }); + connection.end(); }); diff --git a/test/esm/integration/connection/test-null.test.mts b/test/esm/integration/connection/test-null.test.mts index a608fb09d1..91920df697 100644 --- a/test/esm/integration/connection/test-null.test.mts +++ b/test/esm/integration/connection/test-null.test.mts @@ -1,36 +1,42 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Null', async () => { + const connection = createConnection(); -let rows: RowDataPacket[], rows1: RowDataPacket[]; -let fields1: FieldPacket[]; + connection.query('CREATE TEMPORARY TABLE t (i int)'); + connection.query('INSERT INTO t VALUES(null)'); -connection.query('CREATE TEMPORARY TABLE t (i int)'); -connection.query('INSERT INTO t VALUES(null)'); -connection.query( - 'SELECT cast(NULL AS CHAR) as cast_result', - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - } -); -connection.query('SELECT * from t', (err, _rows, _fields) => { - if (err) { - throw err; - } - rows1 = _rows; - fields1 = _fields; - connection.end(); -}); + await it('should handle null values', async () => { + await new Promise((resolve, reject) => { + let rows: RowDataPacket[]; + let rows1: RowDataPacket[]; + let fields1: FieldPacket[]; + + connection.query( + 'SELECT cast(NULL AS CHAR) as cast_result', + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + } + ); + connection.query( + 'SELECT * from t', + (err, _rows, _fields) => { + if (err) return reject(err); + rows1 = _rows; + fields1 = _fields; + + assert.deepEqual(rows, [{ cast_result: null }]); + // assert.equal(fields[0].columnType, 253); // depeding on the server type could be 253 or 3, disabling this check for now + assert.deepEqual(rows1, [{ i: null }]); + assert.equal(fields1[0].columnType, 3); -process.on('exit', () => { - assert.deepEqual(rows, [{ cast_result: null }]); - // assert.equal(fields[0].columnType, 253); // depeding on the server type could be 253 or 3, disabling this check for now - assert.deepEqual(rows1, [{ i: null }]); - assert.equal(fields1[0].columnType, 3); + connection.end(); + resolve(); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-parameters-questionmark.test.mts b/test/esm/integration/connection/test-parameters-questionmark.test.mts index 5bee4910eb..b0a16fc903 100644 --- a/test/esm/integration/connection/test-parameters-questionmark.test.mts +++ b/test/esm/integration/connection/test-parameters-questionmark.test.mts @@ -1,34 +1,40 @@ import type { Pool, RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../../common.test.mjs'; type TestRow = RowDataPacket & { str: string }; -const pool: Pool = createPool(); -pool.config.connectionLimit = 1; +await describe('Parameters Questionmark', async () => { + const pool: Pool = createPool(); + pool.config.connectionLimit = 1; -pool.query( - [ - 'CREATE TEMPORARY TABLE `test_table` (', - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`str` varchar(64),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); -pool.query('insert into test_table(str) values(?)', ['abc?']); -pool.query('UPDATE test_table SET str = ? WHERE id = ?', [ - 'should not change ?', - 1, -]); -pool.query( - 'SELECT str FROM test_table WHERE id = ?', - [1], - (err, rows) => { - pool.end(); - if (err) { - throw err; - } - assert.deepEqual(rows, [{ str: 'should not change ?' }]); - } -); + pool.query( + [ + 'CREATE TEMPORARY TABLE `test_table` (', + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`str` varchar(64),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); + pool.query('insert into test_table(str) values(?)', ['abc?']); + pool.query('UPDATE test_table SET str = ? WHERE id = ?', [ + 'should not change ?', + 1, + ]); + + await it('should preserve questionmarks in values', async () => { + await new Promise((resolve, reject) => { + pool.query( + 'SELECT str FROM test_table WHERE id = ?', + [1], + (err, rows) => { + pool.end(); + if (err) return reject(err); + assert.deepEqual(rows, [{ str: 'should not change ?' }]); + resolve(); + } + ); + }); + }); +}); diff --git a/test/esm/integration/connection/test-prepare-and-close.test.mts b/test/esm/integration/connection/test-prepare-and-close.test.mts index 12f7576411..6b474e6e81 100644 --- a/test/esm/integration/connection/test-prepare-and-close.test.mts +++ b/test/esm/integration/connection/test-prepare-and-close.test.mts @@ -1,32 +1,35 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Prepare and Close', async () => { + await it('should prepare and close statements repeatedly', async () => { + const connection = createConnection(); -const max = 500; -const start = process.hrtime(); -function prepare(i: number) { - connection.prepare(`select 1+${i}`, (err, stmt) => { - assert.ifError(err); - stmt.close(); - if (!err) { - if (i > max) { - const end = process.hrtime(start); - const ns = end[0] * 1e9 + end[1]; - console.log(`${(max * 1e9) / ns} prepares/sec`); - connection.end(); - return; + await new Promise((resolve, reject) => { + const max = 500; + const start = process.hrtime(); + function prepare(i: number) { + connection.prepare(`select 1+${i}`, (err, stmt) => { + if (err) return reject(err); + stmt.close(); + if (i > max) { + const end = process.hrtime(start); + const ns = end[0] * 1e9 + end[1]; + console.log(`${(max * 1e9) / ns} prepares/sec`); + connection.end(); + resolve(); + return; + } + setTimeout(() => { + prepare(i + 1); + }, 2); + }); } - setTimeout(() => { - prepare(i + 1); - }, 2); - return; - } - assert(0, 'Error in prepare!'); + connection.query('SET GLOBAL max_prepared_stmt_count=10', (err) => { + if (err) return reject(err); + prepare(1); + }); + }); }); -} -connection.query('SET GLOBAL max_prepared_stmt_count=10', (err) => { - assert.ifError(err); - prepare(1); }); diff --git a/test/esm/integration/connection/test-prepare-simple.test.mts b/test/esm/integration/connection/test-prepare-simple.test.mts index 471917cef7..969e876bd9 100644 --- a/test/esm/integration/connection/test-prepare-simple.test.mts +++ b/test/esm/integration/connection/test-prepare-simple.test.mts @@ -1,41 +1,45 @@ import type { PrepareStatementInfo } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Prepare Simple', async () => { + const connection = createConnection(); -let _stmt1: PrepareStatementInfo | null = null; -const query1 = 'select 1 + ? + ? as test'; -const query2 = 'select 1 + 1'; // no parameters -const query3 = 'create temporary table aaa(i int);'; // no parameters, no result columns + const query1 = 'select 1 + ? + ? as test'; + const query2 = 'select 1 + 1'; // no parameters + const query3 = 'create temporary table aaa(i int);'; // no parameters, no result columns -connection.prepare(query1, (err1, stmt1) => { - assert.ifError(err1); - _stmt1 = stmt1; - _stmt1.close(); - connection.prepare(query2, (err2, stmt2) => { - assert.ifError(err2); - connection.prepare(query3, (err3, stmt3) => { - assert.ifError(err3); - stmt2.close(); - stmt3.close(); - connection.end(); + await it('should prepare statements with varying parameters', async () => { + const stmt1 = await new Promise((resolve, reject) => { + connection.prepare(query1, (err, stmt) => { + if (err) return reject(err); + resolve(stmt); + }); }); - }); -}); -process.on('exit', () => { - assert(_stmt1, 'Expected prepared statement'); - if (!_stmt1) { - return; - } - // @ts-expect-error: TODO: implement typings - assert.equal(_stmt1.query, query1); - // @ts-expect-error: TODO: implement typings - assert(_stmt1.id >= 0); - // @ts-expect-error: TODO: implement typings - assert.equal(_stmt1.columns.length, 1); - // @ts-expect-error: TODO: implement typings - assert.equal(_stmt1.parameters.length, 2); + stmt1.close(); + + await new Promise((resolve, reject) => { + connection.prepare(query2, (err2, stmt2) => { + if (err2) return reject(err2); + connection.prepare(query3, (err3, stmt3) => { + if (err3) return reject(err3); + stmt2.close(); + stmt3.close(); + connection.end(); + resolve(); + }); + }); + }); + + assert(stmt1, 'Expected prepared statement'); + // @ts-expect-error: TODO: implement typings + assert.equal(stmt1.query, query1); + // @ts-expect-error: TODO: implement typings + assert(stmt1.id >= 0); + // @ts-expect-error: TODO: implement typings + assert.equal(stmt1.columns.length, 1); + // @ts-expect-error: TODO: implement typings + assert.equal(stmt1.parameters.length, 2); + }); }); diff --git a/test/esm/integration/connection/test-prepare-then-execute.test.mts b/test/esm/integration/connection/test-prepare-then-execute.test.mts index caae5fe69e..d77f05fbf5 100644 --- a/test/esm/integration/connection/test-prepare-then-execute.test.mts +++ b/test/esm/integration/connection/test-prepare-then-execute.test.mts @@ -3,41 +3,36 @@ import type { PrepareStatementInfo, RowDataPacket, } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Prepare Then Execute', async () => { + const connection = createConnection(); -let _stmt: PrepareStatementInfo | null = null; -let _columns: FieldPacket[] | null = null; -let _rows: RowDataPacket[] | null = null; + await it('should prepare and then execute a statement', async () => { + const { stmt, columns, rows } = await new Promise<{ + stmt: PrepareStatementInfo; + columns: FieldPacket[]; + rows: RowDataPacket[]; + }>((resolve, reject) => { + connection.prepare('select 1 + ? + ? as test', (err, stmt) => { + if (err) return reject(err); + stmt.execute([111, 123], (err, rows, columns) => { + if (err) return reject(err); + resolve({ stmt, columns, rows }); + }); + }); + }); -connection.prepare('select 1 + ? + ? as test', (err, stmt) => { - if (err) { - throw err; - } - _stmt = stmt; - stmt.execute([111, 123], (err, rows, columns) => { - if (err) { - throw err; - } - _columns = columns; - _rows = rows; - connection.end(); + assert(stmt, 'Expected prepared statement'); + assert(columns, 'Expected statement metadata'); + // @ts-expect-error: TODO: implement typings + assert.equal(stmt.columns.length, 1); + // @ts-expect-error: TODO: implement typings + assert.equal(stmt.parameters.length, 2); + assert.deepEqual(rows, [{ test: 235 }]); + assert.equal(columns[0].name, 'test'); }); -}); -process.on('exit', () => { - assert(_stmt, 'Expected prepared statement'); - assert(_columns, 'Expected statement metadata'); - if (!_stmt || !_columns) { - return; - } - // @ts-expect-error: TODO: implement typings - assert.equal(_stmt.columns.length, 1); - // @ts-expect-error: TODO: implement typings - assert.equal(_stmt.parameters.length, 2); - assert.deepEqual(_rows, [{ test: 235 }]); - assert.equal(_columns[0].name, 'test'); + connection.end(); }); diff --git a/test/esm/integration/connection/test-protocol-errors.test.mts b/test/esm/integration/connection/test-protocol-errors.test.mts index 89b9173849..e1acac6c42 100644 --- a/test/esm/integration/connection/test-protocol-errors.test.mts +++ b/test/esm/integration/connection/test-protocol-errors.test.mts @@ -7,80 +7,84 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createServer } from '../../common.test.mjs'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -let fields: FieldPacket[] | undefined; -let error: (Error & { fatal?: boolean; code?: string }) | undefined; const query = 'SELECT 1'; -let rows: RowDataPacket[] | undefined; -const server = createServer( - () => { - const connection = createConnection({ - // The mock server is running on the same host machine. - // We need to explicitly define the host to avoid connecting to a potential - // different host provided via MYSQL_HOST that identifies a real MySQL - // server instance. - host: 'localhost', - // @ts-expect-error: internal access - port: server._port, - // @ts-expect-error: TODO: implement typings - ssl: false, - }); - connection.query(query, (err, _rows, _fields) => { - if (err) { - throw err; - } - rows = _rows; - fields = _fields; - }); +await describe('Protocol Errors', async () => { + await it('should handle unexpected packet errors', async () => { + let fields: FieldPacket[]; + let error: Error & { fatal?: boolean; code?: string }; + let rows: RowDataPacket[]; - connection.on('error', (err) => { - error = err; - // @ts-expect-error: internal access - if (server._server._handle) { - // @ts-expect-error: TODO: implement typings - server.close(); - } - }); - }, - (conn) => { - conn.on('query', () => { - conn.writeTextResult( - [{ 1: '1' }], - [ - { - catalog: 'def', - schema: '', - table: '', - orgTable: '', - name: '1', - orgName: '', - characterSet: 63, - columnLength: 1, - columnType: 8, - flags: 129, - decimals: 0, - }, - ] + await new Promise((resolve) => { + const server = createServer( + () => { + const connection = createConnection({ + // The mock server is running on the same host machine. + // We need to explicitly define the host to avoid connecting to a potential + // different host provided via MYSQL_HOST that identifies a real MySQL + // server instance. + host: 'localhost', + // @ts-expect-error: internal access + port: server._port, + // @ts-expect-error: TODO: implement typings + ssl: false, + }); + connection.query(query, (err, _rows, _fields) => { + if (err) return; + rows = _rows; + fields = _fields; + }); + + connection.on('error', (err) => { + error = err; + // @ts-expect-error: internal access + if (server._server._handle) { + // @ts-expect-error: TODO: implement typings + server.close(); + } + resolve(); + }); + }, + (conn) => { + conn.on('query', () => { + conn.writeTextResult( + [{ 1: '1' }], + [ + { + catalog: 'def', + schema: '', + table: '', + orgTable: '', + name: '1', + orgName: '', + characterSet: 63, + columnLength: 1, + columnType: 8, + flags: 129, + decimals: 0, + }, + ] + ); + // this is extra (incorrect) packet - client should emit error on receiving it + conn.writeOk(); + }); + } ); - // this is extra (incorrect) packet - client should emit error on receiving it - conn.writeOk(); }); - } -); -process.on('exit', () => { - assert.deepEqual(rows, [{ 1: 1 }]); - assert.equal(fields?.[0].name, '1'); - assert.equal( - error?.message, - 'Unexpected packet while no commands in the queue' - ); - assert.equal(error?.fatal, true); - assert.equal(error?.code, 'PROTOCOL_UNEXPECTED_PACKET'); + assert.deepEqual(rows!, [{ 1: 1 }]); + assert.equal(fields![0].name, '1'); + assert.equal( + error!.message, + 'Unexpected packet while no commands in the queue' + ); + assert.equal(error!.fatal, true); + assert.equal(error!.code, 'PROTOCOL_UNEXPECTED_PACKET'); + }); }); diff --git a/test/esm/integration/connection/test-query-timeout.test.mts b/test/esm/integration/connection/test-query-timeout.test.mts index 3bc475aba3..b7bfce898c 100644 --- a/test/esm/integration/connection/test-query-timeout.test.mts +++ b/test/esm/integration/connection/test-query-timeout.test.mts @@ -1,5 +1,6 @@ import assert from 'node:assert'; import process from 'node:process'; +import { describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createConnection } from '../../common.test.mjs'; @@ -7,95 +8,149 @@ import { createConnection } from '../../common.test.mjs'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const connection = createConnection({ debug: false }); +await describe('Query Timeout', async () => { + await it('should handle query and execute timeouts', async () => { + const connection = createConnection({ debug: false }); -connection.query({ sql: 'SELECT sleep(3) as a', timeout: 500 }, (err, res) => { - assert.equal(res, null); - assert.ok(err); - assert.equal(err.code, 'PROTOCOL_SEQUENCE_TIMEOUT'); - assert.equal(err.message, 'Query inactivity timeout'); -}); + connection.on('error', (err: NodeJS.ErrnoException) => { + assert.equal( + err.message, + 'Connection lost: The server closed the connection.' + ); + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); + }); -connection.query( - { sql: 'SELECT sleep(1) as a', timeout: 5000 }, - (_err, res) => { - assert.deepEqual(res, [{ a: 0 }]); - } -); + await new Promise((resolve, reject) => { + connection.query( + { sql: 'SELECT sleep(3) as a', timeout: 500 }, + (err, res) => { + try { + assert.equal(res, null); + assert.ok(err); + assert.equal(err.code, 'PROTOCOL_SEQUENCE_TIMEOUT'); + assert.equal(err.message, 'Query inactivity timeout'); + } catch (e) { + reject(e); + } + } + ); -connection.query('SELECT sleep(1) as a', (_err, res) => { - assert.deepEqual(res, [{ a: 0 }]); -}); + connection.query( + { sql: 'SELECT sleep(1) as a', timeout: 5000 }, + (_err, res) => { + try { + assert.deepEqual(res, [{ a: 0 }]); + } catch (e) { + reject(e); + } + } + ); -connection.execute( - { sql: 'SELECT sleep(3) as a', timeout: 500 }, - (err, res) => { - assert.equal(res, null); - assert.ok(err); - assert.equal(err.code, 'PROTOCOL_SEQUENCE_TIMEOUT'); - assert.equal(err.message, 'Query inactivity timeout'); - } -); + connection.query('SELECT sleep(1) as a', (_err, res) => { + try { + assert.deepEqual(res, [{ a: 0 }]); + } catch (e) { + reject(e); + } + }); -connection.execute( - { sql: 'SELECT sleep(1) as a', timeout: 5000 }, - (_err, res) => { - assert.deepEqual(res, [{ a: 0 }]); - } -); + connection.execute( + { sql: 'SELECT sleep(3) as a', timeout: 500 }, + (err, res) => { + try { + assert.equal(res, null); + assert.ok(err); + assert.equal(err.code, 'PROTOCOL_SEQUENCE_TIMEOUT'); + assert.equal(err.message, 'Query inactivity timeout'); + } catch (e) { + reject(e); + } + } + ); -connection.query( - { sql: 'select 1 from non_existing_table', timeout: 500 }, - (err, res) => { - assert.equal(res, null); - assert.ok(err); - assert.equal(err.code, 'ER_NO_SUCH_TABLE'); - } -); + connection.execute( + { sql: 'SELECT sleep(1) as a', timeout: 5000 }, + (_err, res) => { + try { + assert.deepEqual(res, [{ a: 0 }]); + } catch (e) { + reject(e); + } + } + ); -connection.execute('SELECT sleep(1) as a', (_err, res) => { - assert.deepEqual(res, [{ a: 0 }]); - connection.end(); -}); + connection.query( + { sql: 'select 1 from non_existing_table', timeout: 500 }, + (err, res) => { + try { + assert.equal(res, null); + assert.ok(err); + assert.equal(err.code, 'ER_NO_SUCH_TABLE'); + } catch (e) { + reject(e); + } + } + ); -/** - * if connect timeout - * we should return connect timeout error instead of query timeout error - */ -portfinder.getPort((_err, port) => { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - server.on('connection', () => { - // Let connection time out + connection.execute('SELECT sleep(1) as a', (_err, res) => { + try { + assert.deepEqual(res, [{ a: 0 }]); + connection.end(); + resolve(); + } catch (e) { + reject(e); + } + }); + }); }); - server.listen(port); - const connectionTimeout = mysql.createConnection({ - host: 'localhost', - port: port, - connectTimeout: 1000, - }); + /** + * if connect timeout + * we should return connect timeout error instead of query timeout error + */ + await it('should return connect timeout error instead of query timeout error', async () => { + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + server.on('connection', (conn) => { + conn.on('error', (err: NodeJS.ErrnoException) => { + assert.equal( + err.message, + 'Connection lost: The server closed the connection.' + ); + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); + }); + }); + server.listen(port); - // return connect timeout error first - connectionTimeout.query( - { sql: 'SELECT sleep(3) as a', timeout: 50 }, - (err, res) => { - console.log('ok'); - assert.equal(res, null); - assert.ok(err); - assert.equal(err.code, 'ETIMEDOUT'); - assert.equal(err.message, 'connect ETIMEDOUT'); - connectionTimeout.destroy(); - // @ts-expect-error: internal access - server._server.close(); - } - ); -}); + const connectionTimeout = mysql.createConnection({ + host: 'localhost', + port: port, + connectTimeout: 1000, + }); -process.on('uncaughtException', (err: NodeJS.ErrnoException) => { - assert.equal( - err.message, - 'Connection lost: The server closed the connection.' - ); - assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); + // return connect timeout error first + connectionTimeout.query( + { sql: 'SELECT sleep(3) as a', timeout: 50 }, + (err, res) => { + try { + console.log('ok'); + assert.equal(res, null); + assert.ok(err); + assert.equal(err.code, 'ETIMEDOUT'); + assert.equal(err.message, 'connect ETIMEDOUT'); + connectionTimeout.destroy(); + // @ts-expect-error: internal access + server._server.close(() => { + resolve(); + }); + } catch (e) { + reject(e); + } + } + ); + }); + }); + }); }); diff --git a/test/esm/integration/connection/test-query-zero.test.mts b/test/esm/integration/connection/test-query-zero.test.mts index 425ebd3580..fac6a64b94 100644 --- a/test/esm/integration/connection/test-query-zero.test.mts +++ b/test/esm/integration/connection/test-query-zero.test.mts @@ -1,19 +1,25 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Query Zero', async () => { + await it('should return 0 as query parameter result', async () => { + const connection = createConnection(); + let rows: RowDataPacket[]; -let rows: RowDataPacket[]; -connection.query('SELECT ? AS result', 0, (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); -}); + await new Promise((resolve, reject) => { + connection.query( + 'SELECT ? AS result', + 0, + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + connection.end(); + resolve(); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ result: 0 }]); + assert.deepEqual(rows!, [{ result: 0 }]); + }); }); diff --git a/test/esm/integration/connection/test-quit.test.mts b/test/esm/integration/connection/test-quit.test.mts index aaf500fa9e..c95f7a853e 100644 --- a/test/esm/integration/connection/test-quit.test.mts +++ b/test/esm/integration/connection/test-quit.test.mts @@ -6,78 +6,83 @@ // Modifications copyright (c) 2021, Oracle and/or its affiliates. import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createServer } from '../../common.test.mjs'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -let quitReceived = false; const queryCli = 'SELECT 1'; -let queryServ: string; -let rows: Array>; -let fields: Array<{ name: string }>; -const server = createServer( - () => { - const connection = createConnection({ - // The mock server is running on the same host machine. - // We need to explicitly define the host to avoid connecting to a potential - // different host provided via MYSQL_HOST that identifies a real MySQL - // server instance. - host: 'localhost', - // @ts-expect-error: internal access - port: server._port, - // @ts-expect-error: TODO: implement typings - ssl: false, - }); - connection.query(queryCli, (err, _rows, _fields) => { - if (err) { - throw err; - } - rows = _rows as Array>; - fields = _fields as Array<{ name: string }>; +await describe('Quit', async () => { + await it('should send COM_QUIT and receive quit event on server', async () => { + let quitReceived: boolean; + let queryServ: string; + let rows: Array>; + let fields: Array<{ name: string }>; - connection.end(); - }); - }, - (conn) => { - conn.on('quit', () => { - // COM_QUIT - quitReceived = true; - // @ts-expect-error: TODO: implement typings - conn.stream.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); + await new Promise((resolve) => { + const server = createServer( + () => { + const connection = createConnection({ + // The mock server is running on the same host machine. + // We need to explicitly define the host to avoid connecting to a potential + // different host provided via MYSQL_HOST that identifies a real MySQL + // server instance. + host: 'localhost', + // @ts-expect-error: internal access + port: server._port, + // @ts-expect-error: TODO: implement typings + ssl: false, + }); + + connection.query(queryCli, (err, _rows, _fields) => { + if (err) return; + rows = _rows as Array>; + fields = _fields as Array<{ name: string }>; + + connection.end(); + }); + }, + (conn) => { + conn.on('quit', () => { + // COM_QUIT + quitReceived = true; + // @ts-expect-error: TODO: implement typings + conn.stream.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); - conn.on('query', (q: string) => { - queryServ = q; - conn.writeTextResult( - [{ 1: '1' }], - [ - { - catalog: 'def', - schema: '', - table: '', - orgTable: '', - name: '1', - orgName: '', - characterSet: 63, - columnLength: 1, - columnType: 8, - flags: 129, - decimals: 0, - }, - ] + conn.on('query', (q: string) => { + queryServ = q; + conn.writeTextResult( + [{ 1: '1' }], + [ + { + catalog: 'def', + schema: '', + table: '', + orgTable: '', + name: '1', + orgName: '', + characterSet: 63, + columnLength: 1, + columnType: 8, + flags: 129, + decimals: 0, + }, + ] + ); + }); + } ); }); - } -); -process.on('exit', () => { - assert.deepEqual(rows, [{ 1: 1 }]); - assert.equal(fields[0].name, '1'); - assert.equal(quitReceived, true); - assert.equal(queryCli, queryServ); + assert.deepEqual(rows!, [{ 1: 1 }]); + assert.equal(fields![0].name, '1'); + assert.equal(quitReceived!, true); + assert.equal(queryCli, queryServ!); + }); }); diff --git a/test/esm/integration/connection/test-select-1.test.mts b/test/esm/integration/connection/test-select-1.test.mts index 58a54cf3e0..9d23dba6dd 100644 --- a/test/esm/integration/connection/test-select-1.test.mts +++ b/test/esm/integration/connection/test-select-1.test.mts @@ -1,22 +1,30 @@ -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Select 1', async () => { + await it('should query and execute SELECT 1', async () => { + const connection = createConnection(); -connection.query('SELECT 1 as result', (err, rows, fields) => { - assert.ifError(err); - assert.deepEqual(rows, [{ result: 1 }]); - assert.equal(fields[0].name, 'result'); + await new Promise((resolve, reject) => { + connection.query('SELECT 1 as result', (err, rows, fields) => { + if (err) return reject(err); + assert.ifError(err); + assert.deepEqual(rows, [{ result: 1 }]); + assert.equal(fields[0].name, 'result'); - connection.execute('SELECT 1 as result', (err, rows, fields) => { - assert.ifError(err); - assert.deepEqual(rows, [{ result: 1 }]); - assert.equal(fields[0].name, 'result'); + connection.execute('SELECT 1 as result', (err, rows, fields) => { + if (err) return reject(err); + assert.ifError(err); + assert.deepEqual(rows, [{ result: 1 }]); + assert.equal(fields[0].name, 'result'); - connection.end((err) => { - assert.ifError(err); - process.exit(0); + connection.end((err) => { + if (err) return reject(err); + assert.ifError(err); + resolve(); + }); + }); + }); }); }); }); diff --git a/test/esm/integration/connection/test-select-empty-string.test.mts b/test/esm/integration/connection/test-select-empty-string.test.mts index b09b8a6cbf..a22d7df569 100644 --- a/test/esm/integration/connection/test-select-empty-string.test.mts +++ b/test/esm/integration/connection/test-select-empty-string.test.mts @@ -1,21 +1,23 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Select Empty String', async () => { + await it('should return empty string from SELECT', async () => { + const connection = createConnection(); + let rows: RowDataPacket[]; + let fields: FieldPacket[]; -let rows: RowDataPacket[], fields: FieldPacket[]; -connection.query('SELECT ""', (err, _rows, _fields) => { - if (err) { - throw err; - } + await new Promise((resolve, reject) => { + connection.query('SELECT ""', (err, _rows, _fields) => { + if (err) return reject(err); + rows = _rows; + fields = _fields; + connection.end(); + resolve(); + }); + }); - rows = _rows; - fields = _fields; - connection.end(); -}); - -process.on('exit', () => { - assert.deepEqual(rows, [{ [fields[0].name]: '' }]); + assert.deepEqual(rows!, [{ [fields![0].name]: '' }]); + }); }); diff --git a/test/esm/integration/connection/test-select-json.test.mts b/test/esm/integration/connection/test-select-json.test.mts index 23cc3e306d..0167cf2747 100644 --- a/test/esm/integration/connection/test-select-json.test.mts +++ b/test/esm/integration/connection/test-select-json.test.mts @@ -3,37 +3,41 @@ * issue#409: https://github.com/sidorares/node-mysql2/issues/409 */ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Select JSON', async () => { + await it('should select JSON values correctly via text and binary protocol', async () => { + const connection = createConnection(); -let textFetchedRows: RowDataPacket[] = []; -let binaryFetchedRows: RowDataPacket[] = []; + let textFetchedRows: RowDataPacket[]; + let binaryFetchedRows: RowDataPacket[]; -const face = '\uD83D\uDE02'; + const face = '\uD83D\uDE02'; -connection.query('CREATE TEMPORARY TABLE json_test (json_test JSON)'); -connection.query('INSERT INTO json_test VALUES (?)', JSON.stringify(face)); -connection.query('SELECT * FROM json_test', (err, _rows) => { - if (err) { - throw err; - } - textFetchedRows = _rows; - connection.execute( - 'SELECT * FROM json_test', - (err, _rows) => { - if (err) { - throw err; - } - binaryFetchedRows = _rows; - connection.end(); - } - ); -}); + connection.query('CREATE TEMPORARY TABLE json_test (json_test JSON)'); + connection.query('INSERT INTO json_test VALUES (?)', JSON.stringify(face)); + + await new Promise((resolve, reject) => { + connection.query( + 'SELECT * FROM json_test', + (err, _rows) => { + if (err) return reject(err); + textFetchedRows = _rows; + connection.execute( + 'SELECT * FROM json_test', + (err, _rows) => { + if (err) return reject(err); + binaryFetchedRows = _rows; + connection.end(); + resolve(); + } + ); + } + ); + }); -process.on('exit', () => { - assert.equal(textFetchedRows[0].json_test, face); - assert.equal(binaryFetchedRows[0].json_test, face); + assert.equal(textFetchedRows![0].json_test, face); + assert.equal(binaryFetchedRows![0].json_test, face); + }); }); diff --git a/test/esm/integration/connection/test-select-negative.test.mts b/test/esm/integration/connection/test-select-negative.test.mts index 1617f04260..cbae5da010 100644 --- a/test/esm/integration/connection/test-select-negative.test.mts +++ b/test/esm/integration/connection/test-select-negative.test.mts @@ -1,29 +1,29 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Select Negative', async () => { + await it('should select negative values via execute and query', async () => { + const connection = createConnection(); -let rows: RowDataPacket[] = []; -let rows1: RowDataPacket[] = []; + let rows: RowDataPacket[]; + let rows1: RowDataPacket[]; -connection.execute('SELECT -1 v', [], (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; -}); + await new Promise((resolve, reject) => { + connection.execute('SELECT -1 v', [], (err, _rows) => { + if (err) return reject(err); + rows = _rows; + }); -connection.query('SELECT -1 v', (err, _rows) => { - if (err) { - throw err; - } - rows1 = _rows; - connection.end(); -}); + connection.query('SELECT -1 v', (err, _rows) => { + if (err) return reject(err); + rows1 = _rows; + connection.end(); + resolve(); + }); + }); -process.on('exit', () => { - assert.deepEqual(rows, [{ v: -1 }]); - assert.deepEqual(rows1, [{ v: -1 }]); + assert.deepEqual(rows!, [{ v: -1 }]); + assert.deepEqual(rows1!, [{ v: -1 }]); + }); }); diff --git a/test/esm/integration/connection/test-select-ssl.test.mts b/test/esm/integration/connection/test-select-ssl.test.mts index 79c305abfe..a62637ba49 100644 --- a/test/esm/integration/connection/test-select-ssl.test.mts +++ b/test/esm/integration/connection/test-select-ssl.test.mts @@ -1,6 +1,6 @@ import type { RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type SslCipherRow = RowDataPacket & { @@ -8,33 +8,43 @@ type SslCipherRow = RowDataPacket & { Value: string; }; -const connection = createConnection(); +await describe('Select SSL', async () => { + await it('should check SSL cipher status via query and execute', async () => { + const connection = createConnection(); -connection.query( - `SHOW STATUS LIKE 'Ssl_cipher'`, - (err, rows) => { - assert.ifError(err); - if (process.env.MYSQL_USE_TLS === '1') { - assert.equal(rows[0].Value.length > 0, true); - } else { - assert.deepEqual(rows, [{ Variable_name: 'Ssl_cipher', Value: '' }]); - } + await new Promise((resolve, reject) => { + connection.query( + `SHOW STATUS LIKE 'Ssl_cipher'`, + (err, rows) => { + if (err) return reject(err); + if (process.env.MYSQL_USE_TLS === '1') { + assert.equal(rows[0].Value.length > 0, true); + } else { + assert.deepEqual(rows, [ + { Variable_name: 'Ssl_cipher', Value: '' }, + ]); + } - connection.execute( - `SHOW STATUS LIKE 'Ssl_cipher'`, - (err, rows) => { - assert.ifError(err); - if (process.env.MYSQL_USE_TLS === '1') { - assert.equal(rows[0].Value.length > 0, true); - } else { - assert.deepEqual(rows, [{ Variable_name: 'Ssl_cipher', Value: '' }]); - } + connection.execute( + `SHOW STATUS LIKE 'Ssl_cipher'`, + (err, rows) => { + if (err) return reject(err); + if (process.env.MYSQL_USE_TLS === '1') { + assert.equal(rows[0].Value.length > 0, true); + } else { + assert.deepEqual(rows, [ + { Variable_name: 'Ssl_cipher', Value: '' }, + ]); + } - connection.end((err) => { - assert.ifError(err); - process.exit(0); - }); - } - ); - } -); + connection.end((err) => { + if (err) return reject(err); + resolve(); + }); + } + ); + } + ); + }); + }); +}); diff --git a/test/esm/integration/connection/test-select-utf8.test.mts b/test/esm/integration/connection/test-select-utf8.test.mts index 1f7079c514..21831a2e1a 100644 --- a/test/esm/integration/connection/test-select-utf8.test.mts +++ b/test/esm/integration/connection/test-select-utf8.test.mts @@ -1,23 +1,26 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Select UTF8', async () => { + await it('should select multibyte UTF8 text correctly', async () => { + const connection = createConnection(); -let rows: RowDataPacket[] = []; -const multibyteText = '本日は晴天なり'; -connection.query( - `SELECT '${multibyteText}' as result`, - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + let rows: RowDataPacket[]; + const multibyteText = '本日は晴天なり'; -process.on('exit', () => { - assert.equal(rows[0].result, multibyteText); + await new Promise((resolve, reject) => { + connection.query( + `SELECT '${multibyteText}' as result`, + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + connection.end(); + resolve(); + } + ); + }); + + assert.equal(rows![0].result, multibyteText); + }); }); diff --git a/test/esm/integration/connection/test-server-listen.test.mts b/test/esm/integration/connection/test-server-listen.test.mts index 77d5eeadfe..109781c02e 100644 --- a/test/esm/integration/connection/test-server-listen.test.mts +++ b/test/esm/integration/connection/test-server-listen.test.mts @@ -1,42 +1,53 @@ import type { Server } from '../../../../typings/mysql/lib/Server.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import mysql from '../../../../index.js'; // Verifies that the Server.listen can be called with any combination of // pararameters valid for net.Server.listen. -function testListen( - argsDescription: string, - listenCaller: (server: Server, callback: () => void) => void -) { - // @ts-expect-error: TODO: implement typings - const server = mysql.createServer(); - let listenCallbackFired = false; +await describe('Server Listen', async () => { + function testListen( + argsDescription: string, + listenCaller: (server: Server, callback: () => void) => void + ) { + return new Promise((resolve) => { + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + let listenCallbackFired = false; - listenCaller(server, () => { - listenCallbackFired = true; - }); - setTimeout(() => { - assert.ok( - listenCallbackFired, - `Callback for call with ${argsDescription} did not fire` - ); - // @ts-expect-error: internal access - server._server.close(); - }, 100); -} + listenCaller(server, () => { + listenCallbackFired = true; + }); + setTimeout(() => { + assert.ok( + listenCallbackFired, + `Callback for call with ${argsDescription} did not fire` + ); + // @ts-expect-error: internal access + server._server.close(); + resolve(); + }, 100); + }); + } -testListen('port', (server, callback) => { - // @ts-expect-error: TODO: implement typings - server.listen(0, callback); -}); + await it('should listen with port', async () => { + await testListen('port', (server, callback) => { + // @ts-expect-error: TODO: implement typings + server.listen(0, callback); + }); + }); -testListen('port, host', (server, callback) => { - // @ts-expect-error: TODO: implement typings - server.listen(0, '127.0.0.1', callback); -}); + await it('should listen with port and host', async () => { + await testListen('port, host', (server, callback) => { + // @ts-expect-error: TODO: implement typings + server.listen(0, '127.0.0.1', callback); + }); + }); -testListen('port, host, backlog', (server, callback) => { - // @ts-expect-error: TODO: implement typings - server.listen(0, '127.0.0.1', 50, callback); + await it('should listen with port, host, and backlog', async () => { + await testListen('port, host, backlog', (server, callback) => { + // @ts-expect-error: TODO: implement typings + server.listen(0, '127.0.0.1', 50, callback); + }); + }); }); diff --git a/test/esm/integration/connection/test-signed-tinyint.test.mts b/test/esm/integration/connection/test-signed-tinyint.test.mts index d15f3006cc..07682ea59e 100644 --- a/test/esm/integration/connection/test-signed-tinyint.test.mts +++ b/test/esm/integration/connection/test-signed-tinyint.test.mts @@ -1,33 +1,35 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Signed Tinyint', async () => { + await it('should handle signed tinyint and smallint values', async () => { + const connection = createConnection(); -let rows: RowDataPacket[] = []; + let rows: RowDataPacket[]; -connection.query( - 'CREATE TEMPORARY TABLE signed_ints (b11 tinyint NOT NULL, b12 tinyint NOT NULL, b21 smallint NOT NULL)' -); -connection.query('INSERT INTO signed_ints values (-3, -120, 500)'); -connection.query('INSERT INTO signed_ints values (3, -110, -500)'); + connection.query( + 'CREATE TEMPORARY TABLE signed_ints (b11 tinyint NOT NULL, b12 tinyint NOT NULL, b21 smallint NOT NULL)' + ); + connection.query('INSERT INTO signed_ints values (-3, -120, 500)'); + connection.query('INSERT INTO signed_ints values (3, -110, -500)'); -connection.execute( - 'SELECT * from signed_ints', - [5], - (err, _rows) => { - if (err) { - throw err; - } - rows = _rows; - connection.end(); - } -); + await new Promise((resolve, reject) => { + connection.execute( + 'SELECT * from signed_ints', + [5], + (err, _rows) => { + if (err) return reject(err); + rows = _rows; + connection.end(); + resolve(); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows, [ - { b11: -3, b12: -120, b21: 500 }, - { b11: 3, b12: -110, b21: -500 }, - ]); + assert.deepEqual(rows!, [ + { b11: -3, b12: -120, b21: 500 }, + { b11: 3, b12: -110, b21: -500 }, + ]); + }); }); diff --git a/test/esm/integration/connection/test-stream-errors.test.mts b/test/esm/integration/connection/test-stream-errors.test.mts index db4f25428a..761b19b556 100644 --- a/test/esm/integration/connection/test-stream-errors.test.mts +++ b/test/esm/integration/connection/test-stream-errors.test.mts @@ -7,7 +7,7 @@ import type { Connection } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createServer } from '../../common.test.mjs'; type TestError = Error & { code?: string; fatal?: boolean }; @@ -15,84 +15,92 @@ type TestError = Error & { code?: string; fatal?: boolean }; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -let clientConnection: Connection | undefined; -const err: Error & { code?: string } = new Error( - 'This socket has been ended by the other party' -); -err.code = 'EPIPE'; +await describe('Stream Errors', async () => { + await it('should handle stream errors correctly', async () => { + let clientConnection: Connection | undefined; + const err: Error & { code?: string } = new Error( + 'This socket has been ended by the other party' + ); + err.code = 'EPIPE'; -let receivedError1: TestError | undefined; -let receivedError2: TestError | undefined; -let receivedError3: TestError | undefined; + let receivedError1: TestError | undefined; + let receivedError2: TestError | undefined; + let receivedError3: TestError | undefined; -const query = 'SELECT 1'; + const query = 'SELECT 1'; -const server = createServer( - () => { - clientConnection = createConnection({ - // The mock server is running on the same host machine. - // We need to explicitly define the host to avoid connecting to a potential - // different host provided via MYSQL_HOST that identifies a real MySQL - // server instance. - host: 'localhost', - // @ts-expect-error: internal access - port: server._port, - // @ts-expect-error: TODO: implement typings - ssl: false, - }); - clientConnection?.query(query, (_err) => { - if (_err && _err.code === 'HANDSHAKE_NO_SSL_SUPPORT') { - clientConnection?.end(); - } - receivedError1 = _err ?? undefined; - }); - clientConnection?.query('second query, should not be executed', () => { - receivedError2 = err; - clientConnection?.query( - 'trying to enqueue command to a connection which is already in error state', - (_err1) => { - receivedError3 = _err1 ?? undefined; + await new Promise((resolve) => { + const server = createServer( + () => { + clientConnection = createConnection({ + // The mock server is running on the same host machine. + // We need to explicitly define the host to avoid connecting to a potential + // different host provided via MYSQL_HOST that identifies a real MySQL + // server instance. + host: 'localhost', + // @ts-expect-error: internal access + port: server._port, + // @ts-expect-error: TODO: implement typings + ssl: false, + }); + clientConnection?.query(query, (_err) => { + if (_err && _err.code === 'HANDSHAKE_NO_SSL_SUPPORT') { + clientConnection?.end(); + } + receivedError1 = _err ?? undefined; + }); + clientConnection?.query( + 'second query, should not be executed', + () => { + receivedError2 = err; + clientConnection?.query( + 'trying to enqueue command to a connection which is already in error state', + (_err1) => { + receivedError3 = _err1 ?? undefined; + resolve(); + } + ); + } + ); + }, + (conn) => { + conn.on('query', () => { + // @ts-expect-error: TODO: implement typings + conn.writeColumns([ + { + catalog: 'def', + schema: '', + table: '', + orgTable: '', + name: '1', + orgName: '', + characterSet: 63, + columnLength: 1, + columnType: 8, + flags: 129, + decimals: 0, + }, + ]); + // emulate stream error here + // @ts-expect-error: TODO: implement typings + clientConnection?.stream.emit('error', err); + // @ts-expect-error: TODO: implement typings + clientConnection?.stream.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + }); } ); }); - }, - (conn) => { - conn.on('query', () => { - // @ts-expect-error: TODO: implement typings - conn.writeColumns([ - { - catalog: 'def', - schema: '', - table: '', - orgTable: '', - name: '1', - orgName: '', - characterSet: 63, - columnLength: 1, - columnType: 8, - flags: 129, - decimals: 0, - }, - ]); - // emulate stream error here - // @ts-expect-error: TODO: implement typings - clientConnection?.stream.emit('error', err); - // @ts-expect-error: TODO: implement typings - clientConnection?.stream.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); - } -); -process.on('exit', () => { - assert.equal(receivedError1?.fatal, true); - assert.equal(receivedError1?.code, err.code); - assert.equal(receivedError2?.fatal, true); - assert.equal(receivedError2?.code, err.code); - assert.equal(receivedError3?.fatal, true); - assert.equal( - receivedError3?.message, - "Can't add new command when connection is in closed state" - ); + assert.equal(receivedError1?.fatal, true); + assert.equal(receivedError1?.code, err.code); + assert.equal(receivedError2?.fatal, true); + assert.equal(receivedError2?.code, err.code); + assert.equal(receivedError3?.fatal, true); + assert.equal( + receivedError3?.message, + "Can't add new command when connection is in closed state" + ); + }); }); diff --git a/test/esm/integration/connection/test-stream.test.mts b/test/esm/integration/connection/test-stream.test.mts index 07928b6154..c6ff38b7e2 100644 --- a/test/esm/integration/connection/test-stream.test.mts +++ b/test/esm/integration/connection/test-stream.test.mts @@ -1,83 +1,95 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Stream', async () => { + await it('should stream query and execute results correctly', async () => { + const connection = createConnection(); -let rows: RowDataPacket[]; -const rows1: RowDataPacket[] = []; -const rows2: RowDataPacket[] = []; -const rows3: RowDataPacket[] = []; -const rows4: RowDataPacket[] = []; + let rows: RowDataPacket[]; + const rows1: RowDataPacket[] = []; + const rows2: RowDataPacket[] = []; + const rows3: RowDataPacket[] = []; + const rows4: RowDataPacket[] = []; -connection.query( - [ - 'CREATE TEMPORARY TABLE `announcements` (', - '`id` int(11) NOT NULL AUTO_INCREMENT,', - '`title` varchar(255) DEFAULT NULL,', - '`text` varchar(255) DEFAULT NULL,', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n'), - (err) => { - if (err) { - throw err; - } - } -); + connection.query( + [ + 'CREATE TEMPORARY TABLE `announcements` (', + '`id` int(11) NOT NULL AUTO_INCREMENT,', + '`title` varchar(255) DEFAULT NULL,', + '`text` varchar(255) DEFAULT NULL,', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n'), + (err) => { + if (err) { + throw err; + } + } + ); -connection.execute( - 'INSERT INTO announcements(title, text) VALUES(?, ?)', - ['Есть место, где заканчивается тротуар', 'Расти борода, расти'], - (err) => { - if (err) { - throw err; - } - } -); -connection.execute( - 'INSERT INTO announcements(title, text) VALUES(?, ?)', - [ - 'Граждане Российской Федерации имеют право собираться мирно без оружия', - 'проводить собрания, митинги и демонстрации, шествия и пикетирование', - ], - (err) => { - if (err) { - throw err; - } - } -); -connection.execute('SELECT * FROM announcements', async (_err, _rows) => { - rows = _rows as RowDataPacket[]; - const s1 = connection.query('SELECT * FROM announcements').stream(); - s1.on('data', (row: RowDataPacket) => { - rows1.push(row); - }); - s1.on('end', () => { - const s2 = connection.execute('SELECT * FROM announcements').stream(); - s2.on('data', (row: RowDataPacket) => { - rows2.push(row); - }); - s2.on('end', () => { - connection.end(); + connection.execute( + 'INSERT INTO announcements(title, text) VALUES(?, ?)', + ['Есть место, где заканчивается тротуар', 'Расти борода, расти'], + (err) => { + if (err) { + throw err; + } + } + ); + connection.execute( + 'INSERT INTO announcements(title, text) VALUES(?, ?)', + [ + 'Граждане Российской Федерации имеют право собираться мирно без оружия', + 'проводить собрания, митинги и демонстрации, шествия и пикетирование', + ], + (err) => { + if (err) { + throw err; + } + } + ); + + await new Promise((resolve, reject) => { + connection.execute('SELECT * FROM announcements', async (_err, _rows) => { + try { + if (_err) return reject(_err); + rows = _rows as RowDataPacket[]; + const s1 = connection.query('SELECT * FROM announcements').stream(); + s1.on('data', (row: RowDataPacket) => { + rows1.push(row); + }); + s1.on('end', () => { + const s2 = connection + .execute('SELECT * FROM announcements') + .stream(); + s2.on('data', (row: RowDataPacket) => { + rows2.push(row); + }); + s2.on('end', () => { + connection.end(); + }); + }); + const s3 = connection.query('SELECT * FROM announcements').stream(); + for await (const row of s3) { + rows3.push(row as RowDataPacket); + } + const s4 = connection.query('SELECT * FROM announcements').stream(); + for await (const row of s4) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + rows4.push(row as RowDataPacket); + } + resolve(); + } catch (e) { + reject(e); + } + }); }); - }); - const s3 = connection.query('SELECT * FROM announcements').stream(); - for await (const row of s3) { - rows3.push(row as RowDataPacket); - } - const s4 = connection.query('SELECT * FROM announcements').stream(); - for await (const row of s4) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - rows4.push(row as RowDataPacket); - } -}); -process.on('exit', () => { - assert.deepEqual(rows.length, 2); - assert.deepEqual(rows, rows1); - assert.deepEqual(rows, rows2); - assert.deepEqual(rows, rows3); - assert.deepEqual(rows, rows4); + assert.deepEqual(rows!.length, 2); + assert.deepEqual(rows!, rows1); + assert.deepEqual(rows!, rows2); + assert.deepEqual(rows!, rows3); + assert.deepEqual(rows!, rows4); + }); }); diff --git a/test/esm/integration/connection/test-then-on-query.test.mts b/test/esm/integration/connection/test-then-on-query.test.mts index 85ee5e864b..3ea97076dc 100644 --- a/test/esm/integration/connection/test-then-on-query.test.mts +++ b/test/esm/integration/connection/test-then-on-query.test.mts @@ -1,22 +1,27 @@ -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Then on Query', async () => { + await it('should not have .then on query object', async () => { + const connection = createConnection(); -let error = true; + let error = true; -const q = connection.query('SELECT 1'); -try { - // @ts-expect-error: testing that .then does not exist on Query - if (q.then) q.then(); -} catch { - error = false; -} -q.on('end', () => { - connection.end(); -}); + const q = connection.query('SELECT 1'); + try { + // @ts-expect-error: testing that .then does not exist on Query + if (q.then) q.then(); + } catch { + error = false; + } + + await new Promise((resolve) => { + q.on('end', () => { + connection.end(); + resolve(); + }); + }); -process.on('exit', () => { - assert.equal(error, false); + assert.equal(error, false); + }); }); diff --git a/test/esm/integration/connection/test-timestamp.test.mts b/test/esm/integration/connection/test-timestamp.test.mts index d1d9433014..58f2bc111f 100644 --- a/test/esm/integration/connection/test-timestamp.test.mts +++ b/test/esm/integration/connection/test-timestamp.test.mts @@ -1,62 +1,66 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Timestamp', async () => { + const connection = createConnection(); -connection.query('SET SQL_MODE="ALLOW_INVALID_DATES";'); -connection.query('CREATE TEMPORARY TABLE t (f TIMESTAMP)'); -connection.query("INSERT INTO t VALUES('0000-00-00 00:00:00')"); -connection.query("INSERT INTO t VALUES('2013-01-22 01:02:03')"); + connection.query('SET SQL_MODE="ALLOW_INVALID_DATES";'); + connection.query('CREATE TEMPORARY TABLE t (f TIMESTAMP)'); + connection.query("INSERT INTO t VALUES('0000-00-00 00:00:00')"); + connection.query("INSERT INTO t VALUES('2013-01-22 01:02:03')"); -let rows: RowDataPacket[], fields: FieldPacket[]; -let rows1: RowDataPacket[], fields1: FieldPacket[]; -let rows2: RowDataPacket[]; -connection.query('SELECT f FROM t', (err, _rows, _fields) => { - if (err) { - throw err; - } - rows = _rows; - fields = _fields; -}); -connection.execute( - 'SELECT f FROM t', - (err, _rows, _fields) => { - if (err) { - throw err; - } - rows1 = _rows; - fields1 = _fields; - } -); + await it('should handle timestamp values correctly', async () => { + let rows!: RowDataPacket[]; + let fields!: FieldPacket[]; + let rows1!: RowDataPacket[]; + let fields1!: FieldPacket[]; + let rows2!: RowDataPacket[]; + + await new Promise((resolve, reject) => { + connection.query( + 'SELECT f FROM t', + (err, _rows, _fields) => { + if (err) return reject(err); + rows = _rows; + fields = _fields; + } + ); + connection.execute( + 'SELECT f FROM t', + (err, _rows, _fields) => { + if (err) return reject(err); + rows1 = _rows; + fields1 = _fields; + } + ); -// test 11-byte timestamp - https://github.com/sidorares/node-mysql2/issues/254 -connection.execute( - 'SELECT CURRENT_TIMESTAMP(6) as t11', - (err, _rows) => { - if (err) { - throw err; - } - rows2 = _rows; - connection.end(); - } -); + // test 11-byte timestamp - https://github.com/sidorares/node-mysql2/issues/254 + connection.execute( + 'SELECT CURRENT_TIMESTAMP(6) as t11', + (err, _rows) => { + if (err) return reject(err); + rows2 = _rows; + connection.end(); + resolve(); + } + ); + }); -process.on('exit', () => { - assert.deepEqual(rows[0].f.toString(), 'Invalid Date'); - assert(rows[0].f instanceof Date); - assert(rows[1].f instanceof Date); - assert.equal(rows[1].f.getYear(), 113); - assert.equal(rows[1].f.getMonth(), 0); - assert.equal(rows[1].f.getDate(), 22); - assert.equal(rows[1].f.getHours(), 1); - assert.equal(rows[1].f.getMinutes(), 2); - assert.equal(rows[1].f.getSeconds(), 3); - assert.equal(fields[0].name, 'f'); - assert.deepEqual(rows[1], rows1[1]); - // @ts-expect-error: TODO: implement typings - assert.deepEqual(fields[0].inspect(), fields1[0].inspect()); + assert.deepEqual(rows[0].f.toString(), 'Invalid Date'); + assert(rows[0].f instanceof Date); + assert(rows[1].f instanceof Date); + assert.equal(rows[1].f.getYear(), 113); + assert.equal(rows[1].f.getMonth(), 0); + assert.equal(rows[1].f.getDate(), 22); + assert.equal(rows[1].f.getHours(), 1); + assert.equal(rows[1].f.getMinutes(), 2); + assert.equal(rows[1].f.getSeconds(), 3); + assert.equal(fields[0].name, 'f'); + assert.deepEqual(rows[1], rows1[1]); + // @ts-expect-error: TODO: implement typings + assert.deepEqual(fields[0].inspect(), fields1[0].inspect()); - assert(rows2[0].t11 instanceof Date); + assert(rows2[0].t11 instanceof Date); + }); }); diff --git a/test/esm/integration/connection/test-track-state-change.test.mts b/test/esm/integration/connection/test-track-state-change.test.mts index b58a61edf1..54378bd09f 100644 --- a/test/esm/integration/connection/test-track-state-change.test.mts +++ b/test/esm/integration/connection/test-track-state-change.test.mts @@ -1,6 +1,6 @@ import type { ResultSetHeader } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type CharsetStateChangeResult = ResultSetHeader & { @@ -24,26 +24,35 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection(); - -let result1: CharsetStateChangeResult, result2: SchemaStateChangeResult; - -connection.query('SET NAMES koi8r', (err, _ok) => { - assert.ifError(err); - result1 = _ok; -}); - -connection.query('USE mysql', (err, _ok) => { - assert.ifError(err); - result2 = _ok; - connection.end(); -}); - -process.on('exit', () => { - assert.deepEqual(result1.stateChanges.systemVariables, { - character_set_connection: 'koi8r', - character_set_client: 'koi8r', - character_set_results: 'koi8r', +await describe('Track State Change', async () => { + const connection = createConnection(); + + await it('should track state changes for charset and schema', async () => { + let result1!: CharsetStateChangeResult; + let result2!: SchemaStateChangeResult; + + await new Promise((resolve, reject) => { + connection.query( + 'SET NAMES koi8r', + (err, _ok) => { + if (err) return reject(err); + result1 = _ok; + } + ); + + connection.query('USE mysql', (err, _ok) => { + if (err) return reject(err); + result2 = _ok; + connection.end(); + resolve(); + }); + }); + + assert.deepEqual(result1.stateChanges.systemVariables, { + character_set_connection: 'koi8r', + character_set_client: 'koi8r', + character_set_results: 'koi8r', + }); + assert.deepEqual(result2.stateChanges.schema, 'mysql'); }); - assert.deepEqual(result2.stateChanges.schema, 'mysql'); }); diff --git a/test/esm/integration/connection/test-transaction-commit.test.mts b/test/esm/integration/connection/test-transaction-commit.test.mts index 1d1499e2d6..ab106cc613 100644 --- a/test/esm/integration/connection/test-transaction-commit.test.mts +++ b/test/esm/integration/connection/test-transaction-commit.test.mts @@ -1,5 +1,5 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, useTestDb } from '../../common.test.mjs'; type TransactionRow = RowDataPacket & { @@ -7,43 +7,50 @@ type TransactionRow = RowDataPacket & { title: string; }; -const connection = createConnection(); - -useTestDb(); - -const table = 'transaction_test'; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); - -connection.beginTransaction((err) => { - assert.ifError(err); - - const row = { - id: 1, - title: 'Test row', - }; - - connection.query(`INSERT INTO ${table} SET ?`, row, (err) => { - assert.ifError(err); - - connection.commit((err) => { - assert.ifError(err); - - connection.query( - `SELECT * FROM ${table}`, - (err, rows) => { - assert.ifError(err); - connection.end(); - assert.equal(rows?.length, 1); - } - ); +await describe('Transaction Commit', async () => { + const connection = createConnection(); + + useTestDb(); + + const table = 'transaction_test'; + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); + + await it('should commit a transaction successfully', async () => { + await new Promise((resolve, reject) => { + connection.beginTransaction((err) => { + if (err) return reject(err); + + const row = { + id: 1, + title: 'Test row', + }; + + connection.query(`INSERT INTO ${table} SET ?`, row, (err) => { + if (err) return reject(err); + + connection.commit((err) => { + if (err) return reject(err); + + connection.query( + `SELECT * FROM ${table}`, + (err, rows) => { + if (err) return reject(err); + connection.end(); + assert.equal(rows?.length, 1); + resolve(); + } + ); + }); + }); + }); }); }); }); diff --git a/test/esm/integration/connection/test-transaction-rollback.test.mts b/test/esm/integration/connection/test-transaction-rollback.test.mts index eee5696122..690b6fb78e 100644 --- a/test/esm/integration/connection/test-transaction-rollback.test.mts +++ b/test/esm/integration/connection/test-transaction-rollback.test.mts @@ -1,44 +1,51 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, useTestDb } from '../../common.test.mjs'; -const connection = createConnection(); - -useTestDb(); - -const table = 'transaction_test'; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`title` varchar(255),', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); - -connection.beginTransaction((err) => { - assert.ifError(err); - - const row = { - id: 1, - title: 'Test row', - }; - - connection.query(`INSERT INTO ${table} SET ?`, row, (err) => { - assert.ifError(err); - - connection.rollback((err) => { - assert.ifError(err); - - connection.query( - `SELECT * FROM ${table}`, - (err, rows) => { - assert.ifError(err); - connection.end(); - assert.equal(rows.length, 0); - } - ); +await describe('Transaction Rollback', async () => { + const connection = createConnection(); + + useTestDb(); + + const table = 'transaction_test'; + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`title` varchar(255),', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); + + await it('should rollback a transaction successfully', async () => { + await new Promise((resolve, reject) => { + connection.beginTransaction((err) => { + if (err) return reject(err); + + const row = { + id: 1, + title: 'Test row', + }; + + connection.query(`INSERT INTO ${table} SET ?`, row, (err) => { + if (err) return reject(err); + + connection.rollback((err) => { + if (err) return reject(err); + + connection.query( + `SELECT * FROM ${table}`, + (err, rows) => { + if (err) return reject(err); + connection.end(); + assert.equal(rows.length, 0); + resolve(); + } + ); + }); + }); + }); }); }); }); diff --git a/test/esm/integration/connection/test-type-cast-null-fields-execute.test.mts b/test/esm/integration/connection/test-type-cast-null-fields-execute.test.mts index 743f81f098..fe0e7d18b6 100644 --- a/test/esm/integration/connection/test-type-cast-null-fields-execute.test.mts +++ b/test/esm/integration/connection/test-type-cast-null-fields-execute.test.mts @@ -1,6 +1,5 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, useTestDb } from '../../common.test.mjs'; type InsertTestRow = RowDataPacket & { @@ -9,47 +8,49 @@ type InsertTestRow = RowDataPacket & { number: number | null; }; -const connection = createConnection(); - -useTestDb(); - -const table = 'insert_test'; -connection.execute( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`date` DATETIME NULL,', - '`number` INT NULL,', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n'), - (err) => { - if (err) throw err; - } -); - -connection.execute( - `INSERT INTO ${table} (date, number) VALUES (?, ?)`, - [null, null], - (err) => { - if (err) throw err; - } -); - -let results: InsertTestRow[]; -connection.execute( - `SELECT * FROM ${table}`, - (err, _results) => { - if (err) { - throw err; - } - - results = _results; - connection.end(); - } -); - -process.on('exit', () => { - assert.strictEqual(results[0].date, null); - assert.strictEqual(results[0].number, null); +await describe('Type Cast Null Fields (execute)', async () => { + const connection = createConnection(); + + useTestDb(); + + const table = 'insert_test'; + + await it('should return null for null fields', async () => { + await new Promise((resolve, reject) => { + connection.execute( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`date` DATETIME NULL,', + '`number` INT NULL,', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n'), + (err) => { + if (err) return reject(err); + + connection.execute( + `INSERT INTO ${table} (date, number) VALUES (?, ?)`, + [null, null], + (err) => { + if (err) return reject(err); + + connection.execute( + `SELECT * FROM ${table}`, + (err, _results) => { + if (err) return reject(err); + + assert.strictEqual(_results[0].date, null); + assert.strictEqual(_results[0].number, null); + + connection.end(); + resolve(); + } + ); + } + ); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-type-cast-null-fields.test.mts b/test/esm/integration/connection/test-type-cast-null-fields.test.mts index 4db07e5177..15a70ba66d 100644 --- a/test/esm/integration/connection/test-type-cast-null-fields.test.mts +++ b/test/esm/integration/connection/test-type-cast-null-fields.test.mts @@ -1,6 +1,5 @@ import type { RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, useTestDb } from '../../common.test.mjs'; type InsertTestRow = RowDataPacket & { @@ -9,41 +8,42 @@ type InsertTestRow = RowDataPacket & { number: number | null; }; -const connection = createConnection(); - -useTestDb(); - -const table = 'insert_test'; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`date` DATETIME NULL,', - '`number` INT NULL,', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); - -connection.query(`INSERT INTO ${table} SET ?`, { - date: null, - number: null, -}); - -let results: InsertTestRow[]; -connection.query( - `SELECT * FROM ${table}`, - (_err, _results) => { - if (_err) { - throw _err; - } - - results = _results; - connection.end(); - } -); - -process.on('exit', () => { - assert.strictEqual(results[0].date, null); - assert.strictEqual(results[0].number, null); +await describe('Type Cast Null Fields', async () => { + const connection = createConnection(); + + useTestDb(); + + const table = 'insert_test'; + connection.query( + [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`date` DATETIME NULL,', + '`number` INT NULL,', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); + + connection.query(`INSERT INTO ${table} SET ?`, { + date: null, + number: null, + }); + + await it('should return null for null fields', async () => { + await new Promise((resolve, reject) => { + connection.query( + `SELECT * FROM ${table}`, + (_err, _results) => { + if (_err) return reject(_err); + + assert.strictEqual(_results[0].date, null); + assert.strictEqual(_results[0].number, null); + + connection.end(); + resolve(); + } + ); + }); + }); }); diff --git a/test/esm/integration/connection/test-type-casting-execute.test.mts b/test/esm/integration/connection/test-type-casting-execute.test.mts index 55a8f54758..207ab35f13 100644 --- a/test/esm/integration/connection/test-type-casting-execute.test.mts +++ b/test/esm/integration/connection/test-type-casting-execute.test.mts @@ -1,7 +1,6 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import process from 'node:process'; -import { assert, test } from 'poku'; +import { assert, describe, it } from 'poku'; import driver from '../../../../index.js'; import { createConnection, useTestDb } from '../../common.test.mjs'; import typeCastingTests from './type-casting-tests.test.mjs'; @@ -37,97 +36,104 @@ const getTypeNameByCode = ( return undefined; }; -test(async () => { +await describe('Type Casting (execute)', async () => { const connection = createConnection(); useTestDb(); - connection.execute('select 1', async (waitConnectErr) => { - assert.ifError(waitConnectErr); - - const tests = (await typeCastingTests(connection)) as TypeCastTest[]; - - const table = 'type_casting'; - - const schema: string[] = []; - const inserts: string[] = []; - - tests.forEach((test, index) => { - const escaped = test.insertRaw || connection.escape(test.insert); - - test.columnName = `${test.type}_${index}`; - - schema.push(`\`${test.columnName}\` ${test.type},`); - inserts.push(`\`${test.columnName}\` = ${escaped}`); - }); - - const createTable = [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - ] - .concat(schema) - .concat(['PRIMARY KEY (`id`)', ') ENGINE=InnoDB DEFAULT CHARSET=utf8']) - .join('\n'); - - connection.execute(createTable); - - connection.execute(`INSERT INTO ${table} SET ${inserts.join(',\n')}`); - - let row: RowDataPacket | undefined; - let fieldData: Record = {}; - connection.execute( - `SELECT * FROM ${table}`, - (err, rows, fields) => { - if (err) { - throw err; - } - - row = rows[0]; - // build a fieldName: fieldType lookup table - fieldData = (fields as FieldPacket[]).reduce( - (a: Record, v) => { - a[v['name']] = v['type']; - return a; - }, - {} + await it('should correctly cast types', async () => { + await new Promise((resolve, reject) => { + connection.execute('select 1', async (waitConnectErr) => { + if (waitConnectErr) return reject(waitConnectErr); + + const tests = (await typeCastingTests(connection)) as TypeCastTest[]; + + const table = 'type_casting'; + + const schema: string[] = []; + const inserts: string[] = []; + + tests.forEach((test, index) => { + const escaped = test.insertRaw || connection.escape(test.insert); + + test.columnName = `${test.type}_${index}`; + + schema.push(`\`${test.columnName}\` ${test.type},`); + inserts.push(`\`${test.columnName}\` = ${escaped}`); + }); + + const createTable = [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + ] + .concat(schema) + .concat([ + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ]) + .join('\n'); + + connection.execute(createTable); + + connection.execute(`INSERT INTO ${table} SET ${inserts.join(',\n')}`); + + connection.execute( + `SELECT * FROM ${table}`, + (err, rows, fields) => { + if (err) return reject(err); + + const row = rows[0]; + // build a fieldName: fieldType lookup table + const fieldData = (fields as FieldPacket[]).reduce( + (a: Record, v) => { + a[v['name']] = v['type']; + return a; + }, + {} + ); + + tests.forEach((test) => { + // check that the column type matches the type name stored in driver.Types + const columnType = fieldData[test.columnName ?? '']; + const columnTypeName = getTypeNameByCode(columnType); + assert.equal( + test.columnType === columnTypeName, + true, + test.columnName + ); + let expected: unknown = test.expect || test.insert; + let got: unknown = row?.[test.columnName ?? '']; + let message: string; + + if (expected instanceof Date) { + assert.equal(got instanceof Date, true, test.type); + + expected = String(expected); + got = String(got); + } else if (Buffer.isBuffer(expected)) { + assert.equal(Buffer.isBuffer(got), true, test.type); + + expected = String(Array.prototype.slice.call(expected)); + got = String(Array.prototype.slice.call(got)); + } + + if (test.deep) { + message = `got: "${JSON.stringify(got)}" expected: "${JSON.stringify( + expected + )}" test: ${test.type}`; + assert.deepEqual(expected, got, message); + } else { + message = `got: "${got}" (${typeof got}) expected: "${expected}" (${typeof expected}) test: ${ + test.type + }`; + assert.strictEqual(expected, got, message); + } + }); + + connection.end(); + resolve(); + } ); - connection.end(); - } - ); - - process.on('exit', () => { - tests.forEach((test) => { - // check that the column type matches the type name stored in driver.Types - const columnType = fieldData[test.columnName ?? '']; - const columnTypeName = getTypeNameByCode(columnType); - assert.equal(test.columnType === columnTypeName, true, test.columnName); - let expected: unknown = test.expect || test.insert; - let got: unknown = row?.[test.columnName ?? '']; - let message: string; - - if (expected instanceof Date) { - assert.equal(got instanceof Date, true, test.type); - - expected = String(expected); - got = String(got); - } else if (Buffer.isBuffer(expected)) { - assert.equal(Buffer.isBuffer(got), true, test.type); - - expected = String(Array.prototype.slice.call(expected)); - got = String(Array.prototype.slice.call(got)); - } - - if (test.deep) { - message = `got: "${JSON.stringify(got)}" expected: "${JSON.stringify( - expected - )}" test: ${test.type}`; - assert.deepEqual(expected, got, message); - } else { - message = `got: "${got}" (${typeof got}) expected: "${expected}" (${typeof expected}) test: ${ - test.type - }`; - assert.strictEqual(expected, got, message); - } }); }); }); diff --git a/test/esm/integration/connection/test-type-casting.test.mts b/test/esm/integration/connection/test-type-casting.test.mts index 159dd964de..67c06a993b 100644 --- a/test/esm/integration/connection/test-type-casting.test.mts +++ b/test/esm/integration/connection/test-type-casting.test.mts @@ -1,7 +1,6 @@ import type { FieldPacket, RowDataPacket } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import process from 'node:process'; -import { assert, test } from 'poku'; +import { assert, describe, it } from 'poku'; import driver from '../../../../index.js'; import { createConnection, useTestDb } from '../../common.test.mjs'; import typeCastingTests from './type-casting-tests.test.mjs'; @@ -37,97 +36,104 @@ const getTypeNameByCode = ( return undefined; }; -test(async () => { +await describe('Type Casting (query)', async () => { const connection = createConnection(); useTestDb(); - connection.query('select 1', async (waitConnectErr) => { - assert.ifError(waitConnectErr); - - const tests: TypeCastTest[] = await typeCastingTests(connection); - - const table = 'type_casting'; - - const schema: string[] = []; - const inserts: string[] = []; - - tests.forEach((test, index) => { - const escaped = test.insertRaw || connection.escape(test.insert); - - test.columnName = `${test.type}_${index}`; - - schema.push(`\`${test.columnName}\` ${test.type},`); - inserts.push(`\`${test.columnName}\` = ${escaped}`); - }); - - const createTable = [ - `CREATE TEMPORARY TABLE \`${table}\` (`, - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - ] - .concat(schema) - .concat(['PRIMARY KEY (`id`)', ') ENGINE=InnoDB DEFAULT CHARSET=utf8']) - .join('\n'); - - connection.query(createTable); - - connection.query(`INSERT INTO ${table} SET${inserts.join(',\n')}`); - - let row: RowDataPacket | undefined; - let fieldData: Record = {}; - connection.query( - `SELECT * FROM ${table}`, - (err, rows, fields: FieldPacket[]) => { - if (err) { - throw err; - } - - row = rows[0]; - // build a fieldName: fieldType lookup table - fieldData = fields.reduce>( - (a, v) => { - a[v['name']] = v['type']; - return a; - }, - {} + await it('should correctly cast types', async () => { + await new Promise((resolve, reject) => { + connection.query('select 1', async (waitConnectErr) => { + if (waitConnectErr) return reject(waitConnectErr); + + const tests: TypeCastTest[] = await typeCastingTests(connection); + + const table = 'type_casting'; + + const schema: string[] = []; + const inserts: string[] = []; + + tests.forEach((test, index) => { + const escaped = test.insertRaw || connection.escape(test.insert); + + test.columnName = `${test.type}_${index}`; + + schema.push(`\`${test.columnName}\` ${test.type},`); + inserts.push(`\`${test.columnName}\` = ${escaped}`); + }); + + const createTable = [ + `CREATE TEMPORARY TABLE \`${table}\` (`, + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + ] + .concat(schema) + .concat([ + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ]) + .join('\n'); + + connection.query(createTable); + + connection.query(`INSERT INTO ${table} SET${inserts.join(',\n')}`); + + connection.query( + `SELECT * FROM ${table}`, + (err, rows, fields: FieldPacket[]) => { + if (err) return reject(err); + + const row = rows[0]; + // build a fieldName: fieldType lookup table + const fieldData = fields.reduce>( + (a, v) => { + a[v['name']] = v['type']; + return a; + }, + {} + ); + + tests.forEach((test) => { + // check that the column type matches the type name stored in driver.Types + const columnType = fieldData[test.columnName ?? '']; + const columnTypeName = getTypeNameByCode(columnType); + assert.equal( + test.columnType === columnTypeName, + true, + test.columnName + ); + let expected: unknown = test.expect || test.insert; + let got: unknown = row?.[test.columnName ?? '']; + let _message; + + if (expected instanceof Date) { + assert.equal(got instanceof Date, true, test.type); + + expected = String(expected); + got = String(got); + } else if (Buffer.isBuffer(expected)) { + assert.equal(Buffer.isBuffer(got), true, test.type); + + expected = String(Array.prototype.slice.call(expected)); + got = String(Array.prototype.slice.call(got)); + } + + if (test.deep) { + _message = `got: "${JSON.stringify(got)}" expected: "${JSON.stringify( + expected + )}" test: ${test.type}`; + assert.deepEqual(expected, got, _message); + } else { + _message = `got: "${got}" (${typeof got}) expected: "${expected}" (${typeof expected}) test: ${ + test.type + }`; + assert.strictEqual(expected, got, _message); + } + }); + + connection.end(); + resolve(); + } ); - connection.end(); - } - ); - - process.on('exit', () => { - tests.forEach((test) => { - // check that the column type matches the type name stored in driver.Types - const columnType = fieldData[test.columnName ?? '']; - const columnTypeName = getTypeNameByCode(columnType); - assert.equal(test.columnType === columnTypeName, true, test.columnName); - let expected: unknown = test.expect || test.insert; - let got: unknown = row?.[test.columnName ?? '']; - let _message; - - if (expected instanceof Date) { - assert.equal(got instanceof Date, true, test.type); - - expected = String(expected); - got = String(got); - } else if (Buffer.isBuffer(expected)) { - assert.equal(Buffer.isBuffer(got), true, test.type); - - expected = String(Array.prototype.slice.call(expected)); - got = String(Array.prototype.slice.call(got)); - } - - if (test.deep) { - _message = `got: "${JSON.stringify(got)}" expected: "${JSON.stringify( - expected - )}" test: ${test.type}`; - assert.deepEqual(expected, got, _message); - } else { - _message = `got: "${got}" (${typeof got}) expected: "${expected}" (${typeof expected}) test: ${ - test.type - }`; - assert.strictEqual(expected, got, _message); - } }); }); }); diff --git a/test/esm/integration/connection/test-typecast-execute.test.mts b/test/esm/integration/connection/test-typecast-execute.test.mts index 111b30676e..dd500abdf8 100644 --- a/test/esm/integration/connection/test-typecast-execute.test.mts +++ b/test/esm/integration/connection/test-typecast-execute.test.mts @@ -4,7 +4,7 @@ import type { TypeCastNext, } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type FooRow = RowDataPacket & { foo: string }; @@ -16,142 +16,174 @@ type GeomTestRow = RowDataPacket & { g: { x: number; y: number }[]; }; -const connection = createConnection(); +await describe('Typecast Execute', async () => { + const connection = createConnection(); -connection.execute('CREATE TEMPORARY TABLE json_test (json_test JSON)'); -connection.execute('INSERT INTO json_test VALUES (?)', [ - JSON.stringify({ test: 42 }), -]); + connection.execute('CREATE TEMPORARY TABLE json_test (json_test JSON)'); + connection.execute('INSERT INTO json_test VALUES (?)', [ + JSON.stringify({ test: 42 }), + ]); -connection.execute( - 'CREATE TEMPORARY TABLE geom_test (p POINT, g GEOMETRY NOT NULL)' -); -connection.execute( - 'INSERT INTO geom_test VALUES (ST_GeomFromText(?), ST_GeomFromText(?))', - [ - 'POINT(1 1)', - 'LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)', - ] -); + connection.execute( + 'CREATE TEMPORARY TABLE geom_test (p POINT, g GEOMETRY NOT NULL)' + ); + connection.execute( + 'INSERT INTO geom_test VALUES (ST_GeomFromText(?), ST_GeomFromText(?))', + [ + 'POINT(1 1)', + 'LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)', + ] + ); -connection.execute( - { - sql: 'select "foo uppercase" as foo', - typeCast: function (field: TypeCastField, next: TypeCastNext) { - assert.equal('number', typeof field.length); - if (field.type === 'VAR_STRING') { - return field.string()?.toUpperCase(); - } - return next(); - }, - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'FOO UPPERCASE'); - } -); + await it('should typecast uppercase', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'select "foo uppercase" as foo', + typeCast: function (field: TypeCastField, next: TypeCastNext) { + assert.equal('number', typeof field.length); + if (field.type === 'VAR_STRING') { + return field.string()?.toUpperCase(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'FOO UPPERCASE'); + resolve(); + } + ); + }); + }); -connection.execute( - { - sql: 'select "foobar" as foo', - typeCast: false, - }, - (err, res) => { - assert.ifError(err); - assert(Buffer.isBuffer(res[0].foo)); - assert.equal(res[0].foo.toString('utf8'), 'foobar'); - } -); + await it('should return buffer when typeCast is false', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'select "foobar" as foo', + typeCast: false, + }, + (err, res) => { + if (err) return reject(err); + assert(Buffer.isBuffer(res[0].foo)); + assert.equal(res[0].foo.toString('utf8'), 'foobar'); + resolve(); + } + ); + }); + }); -connection.execute( - { - sql: 'SELECT NULL as test, 6 as value;', - typeCast: function (_field: TypeCastField, next: TypeCastNext) { - return next(); - }, - }, - (err, _rows) => { - assert.ifError(err); - assert.equal(_rows[0].test, null); - assert.equal(_rows[0].value, 6); - } -); + await it('should handle null and pass-through with next()', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'SELECT NULL as test, 6 as value;', + typeCast: function (_field: TypeCastField, next: TypeCastNext) { + return next(); + }, + }, + (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].test, null); + assert.equal(_rows[0].value, 6); + resolve(); + } + ); + }); + }); -connection.execute( - { - sql: 'SELECT * from json_test', - typeCast: function (_field: TypeCastField, next: TypeCastNext) { - return next(); - }, - }, - (err, _rows) => { - assert.ifError(err); - assert.equal(_rows[0].json_test.test, 42); - } -); + await it('should typecast JSON with execute', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'SELECT * from json_test', + typeCast: function (_field: TypeCastField, next: TypeCastNext) { + return next(); + }, + }, + (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].json_test.test, 42); + resolve(); + } + ); + }); + }); -// read geo fields -connection.execute( - { - sql: 'select * from geom_test', - }, - (err, res) => { - assert.ifError(err); - assert.deepEqual({ x: 1, y: 1 }, res[0].p); - assert.deepEqual( - [ - { x: -71.160281, y: 42.258729 }, - { x: -71.160837, y: 42.259113 }, - { x: -71.161144, y: 42.25932 }, - ], - res[0].g - ); - } -); + // read geo fields + await it('should read geometry fields', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'select * from geom_test', + }, + (err, res) => { + if (err) return reject(err); + assert.deepEqual({ x: 1, y: 1 }, res[0].p); + assert.deepEqual( + [ + { x: -71.160281, y: 42.258729 }, + { x: -71.160837, y: 42.259113 }, + { x: -71.161144, y: 42.25932 }, + ], + res[0].g + ); + resolve(); + } + ); + }); + }); -connection.execute( - { - sql: 'select * from geom_test', - typeCast: function (field: TypeCastField, _next: TypeCastNext) { - assert.equal('geom_test', field.table); + await it('should typecast geometry fields with custom typeCast', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'select * from geom_test', + typeCast: function (field: TypeCastField, _next: TypeCastNext) { + assert.equal('geom_test', field.table); - if (field.name === 'p' && field.type === 'GEOMETRY') { - assert.deepEqual({ x: 1, y: 1 }, field.geometry()); - return { x: 2, y: 2 }; - } + if (field.name === 'p' && field.type === 'GEOMETRY') { + assert.deepEqual({ x: 1, y: 1 }, field.geometry()); + return { x: 2, y: 2 }; + } - if (field.name === 'g' && field.type === 'GEOMETRY') { - assert.deepEqual( - [ - { x: -71.160281, y: 42.258729 }, - { x: -71.160837, y: 42.259113 }, - { x: -71.161144, y: 42.25932 }, - ], - field.geometry() - ); + if (field.name === 'g' && field.type === 'GEOMETRY') { + assert.deepEqual( + [ + { x: -71.160281, y: 42.258729 }, + { x: -71.160837, y: 42.259113 }, + { x: -71.161144, y: 42.25932 }, + ], + field.geometry() + ); - return [ - { x: -70, y: 40 }, - { x: -60, y: 50 }, - { x: -50, y: 60 }, - ]; - } + return [ + { x: -70, y: 40 }, + { x: -60, y: 50 }, + { x: -50, y: 60 }, + ]; + } - assert.fail('should not reach here'); - }, - }, - (err, res) => { - assert.ifError(err); - assert.deepEqual({ x: 2, y: 2 }, res[0].p); - assert.deepEqual( - [ - { x: -70, y: 40 }, - { x: -60, y: 50 }, - { x: -50, y: 60 }, - ], - res[0].g - ); - } -); + assert.fail('should not reach here'); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.deepEqual({ x: 2, y: 2 }, res[0].p); + assert.deepEqual( + [ + { x: -70, y: 40 }, + { x: -60, y: 50 }, + { x: -50, y: 60 }, + ], + res[0].g + ); + resolve(); + } + ); + }); + }); -connection.end(); + connection.end(); +}); diff --git a/test/esm/integration/connection/test-typecast-geometry-execute.test.mts b/test/esm/integration/connection/test-typecast-geometry-execute.test.mts index 7eb14f9d5f..0cd53ed0ea 100644 --- a/test/esm/integration/connection/test-typecast-geometry-execute.test.mts +++ b/test/esm/integration/connection/test-typecast-geometry-execute.test.mts @@ -1,52 +1,56 @@ import type { RowDataPacket, TypeCastGeometry } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import { assert, test } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, getMysqlVersion } from '../../common.test.mjs'; type GeometryRow = RowDataPacket & { foo: TypeCastGeometry }; type BufferRow = RowDataPacket & { foo: Buffer }; -test(async () => { +await describe('Typecast Geometry Execute', async () => { const connection = createConnection(); const mySQLVersion = await getMysqlVersion(connection); - connection.execute('select 1', () => { - // mysql8 renamed some standard functions - // see https://dev.mysql.com/doc/refman/8.0/en/gis-wkb-functions.html - const stPrefix = mySQLVersion.major >= 8 ? 'ST_' : ''; + await it('should typecast geometry fields with execute', async () => { + await new Promise((resolve, reject) => { + connection.execute('select 1', () => { + // mysql8 renamed some standard functions + // see https://dev.mysql.com/doc/refman/8.0/en/gis-wkb-functions.html + const stPrefix = mySQLVersion.major >= 8 ? 'ST_' : ''; - connection.execute( - { - sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, - typeCast: function (field, next) { - if (field.type === 'GEOMETRY') { - return field.geometry(); + connection.execute( + { + sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, + typeCast: function (field, next) { + if (field.type === 'GEOMETRY') { + return field.geometry(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.deepEqual(res[0].foo, { x: 11, y: 0 }); } - return next(); - }, - }, - (err, res) => { - assert.ifError(err); - assert.deepEqual(res[0].foo, { x: 11, y: 0 }); - } - ); + ); - connection.execute( - { - sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, - typeCast: function (field, next) { - if (field.type === 'GEOMETRY') { - return field.buffer(); + connection.execute( + { + sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, + typeCast: function (field, next) { + if (field.type === 'GEOMETRY') { + return field.buffer(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.equal(Buffer.isBuffer(res[0].foo), true); + connection.end(); + resolve(); } - return next(); - }, - }, - (err, res) => { - assert.ifError(err); - assert.equal(Buffer.isBuffer(res[0].foo), true); - } - ); - - connection.end(); + ); + }); + }); }); }); diff --git a/test/esm/integration/connection/test-typecast-geometry.test.mts b/test/esm/integration/connection/test-typecast-geometry.test.mts index 65727bc063..64834786cb 100644 --- a/test/esm/integration/connection/test-typecast-geometry.test.mts +++ b/test/esm/integration/connection/test-typecast-geometry.test.mts @@ -1,52 +1,56 @@ import type { RowDataPacket, TypeCastGeometry } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import { assert, test } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, getMysqlVersion } from '../../common.test.mjs'; type GeometryRow = RowDataPacket & { foo: TypeCastGeometry }; type BufferRow = RowDataPacket & { foo: Buffer }; -test(async () => { +await describe('Typecast Geometry', async () => { const connection = createConnection(); const mySQLVersion = await getMysqlVersion(connection); - connection.query('select 1', () => { - // mysql8 renamed some standard functions - // see https://dev.mysql.com/doc/refman/8.0/en/gis-wkb-functions.html - const stPrefix = mySQLVersion.major >= 8 ? 'ST_' : ''; + await it('should typecast geometry fields with query', async () => { + await new Promise((resolve, reject) => { + connection.query('select 1', () => { + // mysql8 renamed some standard functions + // see https://dev.mysql.com/doc/refman/8.0/en/gis-wkb-functions.html + const stPrefix = mySQLVersion.major >= 8 ? 'ST_' : ''; - connection.query( - { - sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, - typeCast: function (field, next) { - if (field.type === 'GEOMETRY') { - return field.geometry(); + connection.query( + { + sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, + typeCast: function (field, next) { + if (field.type === 'GEOMETRY') { + return field.geometry(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.deepEqual(res[0].foo, { x: 11, y: 0 }); } - return next(); - }, - }, - (err, res) => { - assert.ifError(err); - assert.deepEqual(res[0].foo, { x: 11, y: 0 }); - } - ); + ); - connection.query( - { - sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, - typeCast: function (field, next) { - if (field.type === 'GEOMETRY') { - return field.buffer(); + connection.query( + { + sql: `select ${stPrefix}GeomFromText('POINT(11 0)') as foo`, + typeCast: function (field, next) { + if (field.type === 'GEOMETRY') { + return field.buffer(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.equal(Buffer.isBuffer(res[0].foo), true); + connection.end(); + resolve(); } - return next(); - }, - }, - (err, res) => { - assert.ifError(err); - assert.equal(Buffer.isBuffer(res[0].foo), true); - } - ); - - connection.end(); + ); + }); + }); }); }); diff --git a/test/esm/integration/connection/test-typecast-overwriting-execute.test.mts b/test/esm/integration/connection/test-typecast-overwriting-execute.test.mts index ec4e5595e9..310e76abdd 100644 --- a/test/esm/integration/connection/test-typecast-overwriting-execute.test.mts +++ b/test/esm/integration/connection/test-typecast-overwriting-execute.test.mts @@ -1,36 +1,11 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type TypecastRow = RowDataPacket & { foo: string }; -const connection = createConnection({ - typeCast: function (field, next) { - assert.equal('number', typeof field.length); - if (field.type === 'VAR_STRING') { - const value = field.string(); - if (value === null) { - return value; - } - return value.toUpperCase(); - } - return next(); - }, -}); - -connection.execute( - { - sql: 'select "foo uppercase" as foo', - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'FOO UPPERCASE'); - } -); - -connection.execute( - { - sql: 'select "foo lowercase" as foo', +await describe('Typecast Overwriting Execute', async () => { + const connection = createConnection({ typeCast: function (field, next) { assert.equal('number', typeof field.length); if (field.type === 'VAR_STRING') { @@ -38,15 +13,52 @@ connection.execute( if (value === null) { return value; } - return value.toLowerCase(); + return value.toUpperCase(); } return next(); }, - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'foo lowercase'); - } -); + }); -connection.end(); + await it('should use connection-level typeCast', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'select "foo uppercase" as foo', + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'FOO UPPERCASE'); + resolve(); + } + ); + }); + }); + + await it('should override with execute-level typeCast', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'select "foo lowercase" as foo', + typeCast: function (field, next) { + assert.equal('number', typeof field.length); + if (field.type === 'VAR_STRING') { + const value = field.string(); + if (value === null) { + return value; + } + return value.toLowerCase(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'foo lowercase'); + resolve(); + } + ); + }); + }); + + connection.end(); +}); diff --git a/test/esm/integration/connection/test-typecast-overwriting.test.mts b/test/esm/integration/connection/test-typecast-overwriting.test.mts index aef4e3ec8d..3ba0022790 100644 --- a/test/esm/integration/connection/test-typecast-overwriting.test.mts +++ b/test/esm/integration/connection/test-typecast-overwriting.test.mts @@ -1,36 +1,11 @@ import type { RowDataPacket } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; type TypecastRow = RowDataPacket & { foo: string }; -const connection = createConnection({ - typeCast: function (field, next) { - assert.equal('number', typeof field.length); - if (field.type === 'VAR_STRING') { - const value = field.string(); - if (value === null) { - return value; - } - return value.toUpperCase(); - } - return next(); - }, -}); - -connection.query( - { - sql: 'select "foo uppercase" as foo', - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'FOO UPPERCASE'); - } -); - -connection.query( - { - sql: 'select "foo lowercase" as foo', +await describe('Typecast Overwriting', async () => { + const connection = createConnection({ typeCast: function (field, next) { assert.equal('number', typeof field.length); if (field.type === 'VAR_STRING') { @@ -38,15 +13,52 @@ connection.query( if (value === null) { return value; } - return value.toLowerCase(); + return value.toUpperCase(); } return next(); }, - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'foo lowercase'); - } -); + }); -connection.end(); + await it('should use connection-level typeCast', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select "foo uppercase" as foo', + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'FOO UPPERCASE'); + resolve(); + } + ); + }); + }); + + await it('should override with query-level typeCast', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select "foo lowercase" as foo', + typeCast: function (field, next) { + assert.equal('number', typeof field.length); + if (field.type === 'VAR_STRING') { + const value = field.string(); + if (value === null) { + return value; + } + return value.toLowerCase(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'foo lowercase'); + resolve(); + } + ); + }); + }); + + connection.end(); +}); diff --git a/test/esm/integration/connection/test-typecast.test.mts b/test/esm/integration/connection/test-typecast.test.mts index fd5ef4cf6c..c8cd8415bc 100644 --- a/test/esm/integration/connection/test-typecast.test.mts +++ b/test/esm/integration/connection/test-typecast.test.mts @@ -4,156 +4,193 @@ import type { TypeCastNext, } from '../../../../index.js'; import { Buffer } from 'node:buffer'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Typecast', async () => { + const connection = createConnection(); -connection.query('CREATE TEMPORARY TABLE json_test (json_test JSON)'); -connection.query( - 'INSERT INTO json_test VALUES (?)', - JSON.stringify({ test: 42 }) -); + connection.query('CREATE TEMPORARY TABLE json_test (json_test JSON)'); + connection.query( + 'INSERT INTO json_test VALUES (?)', + JSON.stringify({ test: 42 }) + ); -connection.query( - 'CREATE TEMPORARY TABLE geom_test (p POINT, g GEOMETRY NOT NULL)' -); -connection.query( - 'INSERT INTO geom_test VALUES (ST_GeomFromText("POINT(1 1)"), ' + - 'ST_GeomFromText("LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)"))' -); + connection.query( + 'CREATE TEMPORARY TABLE geom_test (p POINT, g GEOMETRY NOT NULL)' + ); + connection.query( + 'INSERT INTO geom_test VALUES (ST_GeomFromText("POINT(1 1)"), ' + + 'ST_GeomFromText("LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)"))' + ); -connection.query( - { - sql: 'select "foo uppercase" as foo', - typeCast: function (field: TypeCastField, next: TypeCastNext) { - assert.equal('number', typeof field.length); - if (field.type === 'VAR_STRING') { - return field.string()?.toUpperCase(); - } - return next(); - }, - }, - (err, res) => { - assert.ifError(err); - assert.equal(res[0].foo, 'FOO UPPERCASE'); - } -); + await it('should typecast uppercase', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select "foo uppercase" as foo', + typeCast: function (field: TypeCastField, next: TypeCastNext) { + assert.equal('number', typeof field.length); + if (field.type === 'VAR_STRING') { + return field.string()?.toUpperCase(); + } + return next(); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.equal(res[0].foo, 'FOO UPPERCASE'); + resolve(); + } + ); + }); + }); -connection.query( - { - sql: 'select "foobar" as foo', - typeCast: false, - }, - (err, res) => { - assert.ifError(err); - assert(Buffer.isBuffer(res[0].foo), 'Check for Buffer'); - assert.equal(res[0].foo.toString('utf8'), 'foobar'); - } -); + await it('should return buffer when typeCast is false', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select "foobar" as foo', + typeCast: false, + }, + (err, res) => { + if (err) return reject(err); + assert(Buffer.isBuffer(res[0].foo), 'Check for Buffer'); + assert.equal(res[0].foo.toString('utf8'), 'foobar'); + resolve(); + } + ); + }); + }); -connection.query( - { - sql: 'SELECT NULL as test, 6 as value;', - typeCast: function (_field: TypeCastField, next: TypeCastNext) { - return next(); - }, - }, - (err, _rows) => { - assert.ifError(err); - assert.equal(_rows[0].test, null); - assert.equal(_rows[0].value, 6); - } -); + await it('should handle null and pass-through with next()', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'SELECT NULL as test, 6 as value;', + typeCast: function (_field: TypeCastField, next: TypeCastNext) { + return next(); + }, + }, + (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].test, null); + assert.equal(_rows[0].value, 6); + resolve(); + } + ); + }); + }); -connection.query( - { - sql: 'SELECT * from json_test', - typeCast: function (_field: TypeCastField, next: TypeCastNext) { - return next(); - }, - }, - (err, _rows) => { - assert.ifError(err); - assert.equal(_rows[0].json_test.test, 42); - } -); + await it('should typecast JSON with query', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'SELECT * from json_test', + typeCast: function (_field: TypeCastField, next: TypeCastNext) { + return next(); + }, + }, + (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].json_test.test, 42); + resolve(); + } + ); + }); + }); -connection.execute( - { - sql: 'SELECT * from json_test', - typeCast: function (_field: TypeCastField, next: TypeCastNext) { - return next(); - }, - }, - (err, _rows) => { - assert.ifError(err); - assert.equal(_rows[0].json_test.test, 42); - } -); + await it('should typecast JSON with execute', async () => { + await new Promise((resolve, reject) => { + connection.execute( + { + sql: 'SELECT * from json_test', + typeCast: function (_field: TypeCastField, next: TypeCastNext) { + return next(); + }, + }, + (err, _rows) => { + if (err) return reject(err); + assert.equal(_rows[0].json_test.test, 42); + resolve(); + } + ); + }); + }); -// read geo fields -connection.query( - { - sql: 'select * from geom_test', - }, - (err, res) => { - assert.ifError(err); - assert.deepEqual({ x: 1, y: 1 }, res[0].p); - assert.deepEqual( - [ - { x: -71.160281, y: 42.258729 }, - { x: -71.160837, y: 42.259113 }, - { x: -71.161144, y: 42.25932 }, - ], - res[0].g - ); - } -); + // read geo fields + await it('should read geometry fields', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select * from geom_test', + }, + (err, res) => { + if (err) return reject(err); + assert.deepEqual({ x: 1, y: 1 }, res[0].p); + assert.deepEqual( + [ + { x: -71.160281, y: 42.258729 }, + { x: -71.160837, y: 42.259113 }, + { x: -71.161144, y: 42.25932 }, + ], + res[0].g + ); + resolve(); + } + ); + }); + }); -connection.query( - { - sql: 'select * from geom_test', - typeCast: function (field: TypeCastField, _next: TypeCastNext) { - assert.equal('geom_test', field.table); + await it('should typecast geometry fields with custom typeCast', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'select * from geom_test', + typeCast: function (field: TypeCastField, _next: TypeCastNext) { + assert.equal('geom_test', field.table); - if (field.name === 'p' && field.type === 'GEOMETRY') { - assert.deepEqual({ x: 1, y: 1 }, field.geometry()); - return { x: 2, y: 2 }; - } + if (field.name === 'p' && field.type === 'GEOMETRY') { + assert.deepEqual({ x: 1, y: 1 }, field.geometry()); + return { x: 2, y: 2 }; + } - if (field.name === 'g' && field.type === 'GEOMETRY') { - assert.deepEqual( - [ - { x: -71.160281, y: 42.258729 }, - { x: -71.160837, y: 42.259113 }, - { x: -71.161144, y: 42.25932 }, - ], - field.geometry() - ); + if (field.name === 'g' && field.type === 'GEOMETRY') { + assert.deepEqual( + [ + { x: -71.160281, y: 42.258729 }, + { x: -71.160837, y: 42.259113 }, + { x: -71.161144, y: 42.25932 }, + ], + field.geometry() + ); - return [ - { x: -70, y: 40 }, - { x: -60, y: 50 }, - { x: -50, y: 60 }, - ]; - } + return [ + { x: -70, y: 40 }, + { x: -60, y: 50 }, + { x: -50, y: 60 }, + ]; + } - assert.fail('should not reach here'); - }, - }, - (err, res) => { - assert.ifError(err); - assert.deepEqual({ x: 2, y: 2 }, res[0].p); - assert.deepEqual( - [ - { x: -70, y: 40 }, - { x: -60, y: 50 }, - { x: -50, y: 60 }, - ], - res[0].g - ); - } -); + assert.fail('should not reach here'); + }, + }, + (err, res) => { + if (err) return reject(err); + assert.deepEqual({ x: 2, y: 2 }, res[0].p); + assert.deepEqual( + [ + { x: -70, y: 40 }, + { x: -60, y: 50 }, + { x: -50, y: 60 }, + ], + res[0].g + ); + resolve(); + } + ); + }); + }); -connection.end(); + connection.end(); +}); diff --git a/test/esm/integration/connection/test-update-changed-rows.test.mts b/test/esm/integration/connection/test-update-changed-rows.test.mts index b3d826c3c5..c71a3c12f3 100644 --- a/test/esm/integration/connection/test-update-changed-rows.test.mts +++ b/test/esm/integration/connection/test-update-changed-rows.test.mts @@ -1,6 +1,6 @@ import type { ResultSetHeader } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; // "changedRows" is not part of the mysql protocol and extracted from "info string" response @@ -15,52 +15,54 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { * * issue#288: https://github.com/sidorares/node-mysql2/issues/288 */ -const connection = createConnection(); +await describe('Update Changed Rows', async () => { + const connection = createConnection(); -let result1: ResultSetHeader; -let result2: ResultSetHeader; + connection.query( + [ + 'CREATE TEMPORARY TABLE `changed_rows` (', + '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', + '`value` int(5) NOT NULL,', + 'PRIMARY KEY (`id`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join('\n') + ); + connection.query('insert into changed_rows(value) values(1)'); + connection.query('insert into changed_rows(value) values(1)'); + connection.query('insert into changed_rows(value) values(2)'); + connection.query('insert into changed_rows(value) values(3)'); -connection.query( - [ - 'CREATE TEMPORARY TABLE `changed_rows` (', - '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,', - '`value` int(5) NOT NULL,', - 'PRIMARY KEY (`id`)', - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join('\n') -); -connection.query('insert into changed_rows(value) values(1)'); -connection.query('insert into changed_rows(value) values(1)'); -connection.query('insert into changed_rows(value) values(2)'); -connection.query('insert into changed_rows(value) values(3)'); + await it('should track changed rows correctly', async () => { + await new Promise((resolve, reject) => { + let result1: ResultSetHeader; + let result2: ResultSetHeader; -connection.execute( - 'update changed_rows set value=1', - [], - (err, _result) => { - if (err) { - throw err; - } + connection.execute( + 'update changed_rows set value=1', + [], + (err, _result) => { + if (err) return reject(err); - result1 = _result; - connection.execute( - 'update changed_rows set value=1', - [], - (err, _result) => { - if (err) { - throw err; - } + result1 = _result; + connection.execute( + 'update changed_rows set value=1', + [], + (err, _result) => { + if (err) return reject(err); + + result2 = _result; - result2 = _result; - connection.end(); - } - ); - } -); + assert.equal(result1.affectedRows, 4); + assert.equal(result1.changedRows, 2); + assert.equal(result2.affectedRows, 4); + assert.equal(result2.changedRows, 0); -process.on('exit', () => { - assert.equal(result1.affectedRows, 4); - assert.equal(result1.changedRows, 2); - assert.equal(result2.affectedRows, 4); - assert.equal(result2.changedRows, 0); + connection.end(); + resolve(); + } + ); + } + ); + }); + }); }); diff --git a/test/esm/integration/graceful-end/test-end-with-default-config.test.mts b/test/esm/integration/graceful-end/test-end-with-default-config.test.mts index 4f262f16ad..2c396f1fc6 100644 --- a/test/esm/integration/graceful-end/test-end-with-default-config.test.mts +++ b/test/esm/integration/graceful-end/test-end-with-default-config.test.mts @@ -1,5 +1,5 @@ import type { PoolConnection } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../../common.test.mjs'; /** @@ -8,27 +8,27 @@ import { createPool } from '../../common.test.mjs'; * @see https://github.com/sidorares/node-mysql2/issues/3148 */ -/** - * By default, the end method of a pooled connection will just release it back to the pool. - * This is compatibility behavior with mysqljs/mysql. - */ -const pool = createPool(); -let warningEmitted = false; +await describe('Pool end with default config', async () => { + await it('should emit deprecation warning when calling conn.end()', async () => { + const pool = createPool(); + let warningEmitted = false; -pool.getConnection((_err1: Error | null, connection: PoolConnection) => { - connection.on('warn', (warning: Error) => { - warningEmitted = true; - assert( - warning.message.startsWith( - 'Calling conn.end() to release a pooled connection is deprecated' - ) - ); - }); + await new Promise((resolve) => { + pool.getConnection((_err1: Error | null, connection: PoolConnection) => { + connection.on('warn', (warning: Error) => { + warningEmitted = true; + assert( + warning.message.startsWith( + 'Calling conn.end() to release a pooled connection is deprecated' + ) + ); + }); - connection.end(); - pool.end(); -}); + connection.end(); + pool.end(() => resolve()); + }); + }); -process.on('exit', () => { - assert(warningEmitted, 'Warning should be emitted'); + assert(warningEmitted, 'Warning should be emitted'); + }); }); diff --git a/test/esm/integration/graceful-end/test-end-with-graceful-end-config.test.mts b/test/esm/integration/graceful-end/test-end-with-graceful-end-config.test.mts index c5cf0d22dc..591091b3a0 100644 --- a/test/esm/integration/graceful-end/test-end-with-graceful-end-config.test.mts +++ b/test/esm/integration/graceful-end/test-end-with-graceful-end-config.test.mts @@ -1,5 +1,5 @@ import type { PoolConnection } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../../common.test.mjs'; /** @@ -8,22 +8,22 @@ import { createPool } from '../../common.test.mjs'; * @see https://github.com/sidorares/node-mysql2/issues/3148 */ -/** - * By providing gracefulEnd when creating the pool, the end method of a pooled connection - * will actually close the connection instead of releasing it back to the pool. - */ -const pool = createPool({ gracefulEnd: true }); -let warningEmitted = false; +await describe('Pool end with gracefulEnd config', async () => { + await it('should not emit deprecation warning when gracefulEnd is true', async () => { + const pool = createPool({ gracefulEnd: true }); + let warningEmitted = false; -pool.getConnection((_err1: Error | null, connection: PoolConnection) => { - connection.on('warn', () => { - warningEmitted = true; - }); + await new Promise((resolve) => { + pool.getConnection((_err1: Error | null, connection: PoolConnection) => { + connection.on('warn', () => { + warningEmitted = true; + }); - connection.end(); - pool.end(); -}); + connection.end(); + pool.end(() => resolve()); + }); + }); -process.on('exit', () => { - assert(!warningEmitted, 'Warning should not be emitted'); + assert(!warningEmitted, 'Warning should not be emitted'); + }); }); diff --git a/test/esm/integration/graceful-end/test-pool-release-idle-with-default-config.test.mts b/test/esm/integration/graceful-end/test-pool-release-idle-with-default-config.test.mts index a6d3da964a..b84106959b 100644 --- a/test/esm/integration/graceful-end/test-pool-release-idle-with-default-config.test.mts +++ b/test/esm/integration/graceful-end/test-pool-release-idle-with-default-config.test.mts @@ -1,5 +1,5 @@ import type { PoolConnection } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../../common.test.mjs'; /** @@ -8,33 +8,39 @@ import { createPool } from '../../common.test.mjs'; * @see https://github.com/sidorares/node-mysql2/issues/3148 */ -const pool = createPool({ - connectionLimit: 2, - maxIdle: 1, - idleTimeout: 500, - debug: true, -}); +await describe('Pool release idle with default config', async () => { + await it('should not send quit command for idle connections', async () => { + const pool = createPool({ + connectionLimit: 2, + maxIdle: 1, + idleTimeout: 500, + debug: true, + }); -let quitCommandReceived = false; -const originalLog = console.log; -console.log = (message: string) => { - if (message === 'Add command: Quit') { - quitCommandReceived = true; - } -}; + let quitCommandReceived = false; + const originalLog = console.log; + console.log = (message: string) => { + if (message === 'Add command: Quit') { + quitCommandReceived = true; + } + }; -pool.getConnection((_err1: Error | null, connection1: PoolConnection) => { - pool.getConnection((_err2: Error | null, connection2: PoolConnection) => { - connection1.release(); - connection2.release(); + await new Promise((resolve) => { + pool.getConnection((_err1: Error | null, connection1: PoolConnection) => { + pool.getConnection( + (_err2: Error | null, connection2: PoolConnection) => { + connection1.release(); + connection2.release(); - setTimeout(() => { - pool.end(); - }, 2000); - }); -}); + setTimeout(() => { + pool.end(() => resolve()); + }, 2000); + } + ); + }); + }); -process.on('exit', () => { - assert(!quitCommandReceived, 'quit command should not have been received'); - console.log = originalLog; + assert(!quitCommandReceived, 'quit command should not have been received'); + console.log = originalLog; + }); }); diff --git a/test/esm/integration/graceful-end/test-pool-release-idle-with-graceful-end-config.test.mts b/test/esm/integration/graceful-end/test-pool-release-idle-with-graceful-end-config.test.mts index b92d003b70..f4e80cd307 100644 --- a/test/esm/integration/graceful-end/test-pool-release-idle-with-graceful-end-config.test.mts +++ b/test/esm/integration/graceful-end/test-pool-release-idle-with-graceful-end-config.test.mts @@ -1,4 +1,4 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../../common.test.mjs'; /** @@ -7,34 +7,38 @@ import { createPool } from '../../common.test.mjs'; * @see https://github.com/sidorares/node-mysql2/issues/3148 */ -const pool = createPool({ - connectionLimit: 2, - maxIdle: 1, - idleTimeout: 500, - debug: true, - gracefulEnd: true, -}); +await describe('Pool release idle with gracefulEnd config', async () => { + await it('should send quit command for idle connections', async () => { + const pool = createPool({ + connectionLimit: 2, + maxIdle: 1, + idleTimeout: 500, + debug: true, + gracefulEnd: true, + }); -let quitCommandReceived = false; -const originalLog = console.log; -console.log = (message: string) => { - if (message === 'Add command: Quit') { - quitCommandReceived = true; - } -}; + let quitCommandReceived = false; + const originalLog = console.log; + console.log = (message: string) => { + if (message === 'Add command: Quit') { + quitCommandReceived = true; + } + }; -pool.getConnection((_err1, connection1) => { - pool.getConnection((_err2, connection2) => { - connection1.release(); - connection2.release(); + await new Promise((resolve) => { + pool.getConnection((_err1, connection1) => { + pool.getConnection((_err2, connection2) => { + connection1.release(); + connection2.release(); - setTimeout(() => { - pool.end(); - }, 2000); - }); -}); + setTimeout(() => { + pool.end(() => resolve()); + }, 2000); + }); + }); + }); -process.on('exit', () => { - assert(quitCommandReceived, 'quit command should have been received'); - console.log = originalLog; + assert(quitCommandReceived, 'quit command should have been received'); + console.log = originalLog; + }); }); diff --git a/test/esm/integration/pool-cluster/test-promise-wrapper.test.mts b/test/esm/integration/pool-cluster/test-promise-wrapper.test.mts index b7a2d9610d..5a3f466466 100644 --- a/test/esm/integration/pool-cluster/test-promise-wrapper.test.mts +++ b/test/esm/integration/pool-cluster/test-promise-wrapper.test.mts @@ -5,9 +5,9 @@ import { config } from '../../common.test.mjs'; type TestRow = RowDataPacket & { a: number }; -const { createPoolCluster } = promiseDriver; - await describe('Test pool cluster', async () => { + const { createPoolCluster } = promiseDriver; + await it(async () => { const poolCluster = createPoolCluster(); diff --git a/test/esm/integration/promise-wrappers/test-async-stack.test.mts b/test/esm/integration/promise-wrappers/test-async-stack.test.mts index f5dd650949..01f734e03f 100644 --- a/test/esm/integration/promise-wrappers/test-async-stack.test.mts +++ b/test/esm/integration/promise-wrappers/test-async-stack.test.mts @@ -1,56 +1,51 @@ import type { ConnectionOptions } from '../../../../index.js'; import process from 'node:process'; import ErrorStackParser from 'error-stack-parser'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection as promiseCreateConnection } from '../../../../promise.js'; import { config } from '../../common.test.mjs'; // Uncaught Error: connect ECONNREFUSED 127.0.0.1:33066 - Local (undefined:undefined) if (typeof Deno !== 'undefined') process.exit(0); -const createConnection = async function (args?: ConnectionOptions) { - if (!args && process.env.MYSQL_CONNECTION_URL) { - return promiseCreateConnection({ uri: process.env.MYSQL_CONNECTION_URL }); - } - return promiseCreateConnection({ ...config, ...args }); -}; - -async function test() { - // TODO check this is actially required. This meant as a help for pre async/await node - // to load entire file and do isAsyncSupported check instead of failing with syntax error - - let e1: Error, e2: Error; +await describe('Async stack traces', async () => { + const createConnection = async function (args?: ConnectionOptions) { + if (!args && process.env.MYSQL_CONNECTION_URL) { + return promiseCreateConnection({ uri: process.env.MYSQL_CONNECTION_URL }); + } + return promiseCreateConnection({ ...config, ...args }); + }; // TODO: investigate why connection is still open after ENETUNREACH - async function test1() { - e1 = new Error(); - // expected not to connect - await createConnection({ host: '127.0.0.1', port: 33066 }); - } + await it('should include caller stack in connection error', async () => { + let e1: Error; + try { + e1 = new Error(); + // expected not to connect + await createConnection({ host: '127.0.0.1', port: 33066 }); + } catch (err) { + const stack = ErrorStackParser.parse(err as Error); + const stackExpected = ErrorStackParser.parse(e1!); + assert( + stack[2].getLineNumber() === (stackExpected[0].getLineNumber() ?? 0) + 2 + ); + } + }); - async function test2() { + await it('should include caller stack in query error', async () => { const conn = await createConnection(); + let e2: Error; try { e2 = new Error(); await Promise.all([conn.query('select 1+1'), conn.query('syntax error')]); } catch (err) { const stack = ErrorStackParser.parse(err as Error); - const stackExpected = ErrorStackParser.parse(e2); + const stackExpected = ErrorStackParser.parse(e2!); assert( stack[1].getLineNumber() === (stackExpected[0].getLineNumber() ?? 0) + 1 ); - conn.end(); + } finally { + await conn.end(); } - } - - test1().catch((err) => { - const stack = ErrorStackParser.parse(err); - const stackExpected = ErrorStackParser.parse(e1); - assert( - stack[2].getLineNumber() === (stackExpected[0].getLineNumber() ?? 0) + 2 - ); - test2(); }); -} - -test(); +}); diff --git a/test/esm/integration/promise-wrappers/test-promise-wrappers.test.mts b/test/esm/integration/promise-wrappers/test-promise-wrappers.test.mts index 03b538e13f..d238c60a05 100644 --- a/test/esm/integration/promise-wrappers/test-promise-wrappers.test.mts +++ b/test/esm/integration/promise-wrappers/test-promise-wrappers.test.mts @@ -1,7 +1,6 @@ import type { RowDataPacket } from '../../../../index.js'; -import type { Connection } from '../../../../promise.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool as createPoolPromise, createConnection as promiseCreateConnection, @@ -18,520 +17,341 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const createConnection = promiseCreateConnection; -const createPool = createPoolPromise; - -// it's lazy exported from main index.js as well. Test that it's same function -const mainModule = await import('../../../../index.js'); -// @ts-expect-error: TODO: implement typings -const mainExport = mainModule.default.createConnectionPromise; -assert.equal(mainExport, createConnection); - -let doneCalled = false; -let exceptionCaught = false; -let doneEventsConnect = false; - -let doneCalledPool = false; -let exceptionCaughtPool = false; -let doneEventsPool = false; -let doneChangeUser = false; - -function testBasic() { - let connResolved: Connection | undefined; - createConnection(config) - .then((conn) => { - connResolved = conn; - return conn.query('select 1+2 as ttt'); - }) - .then((result1) => { - assert.equal(result1[0][0].ttt, 3); - return connResolved?.query('select 2+2 as qqq'); - }) - .then((result2) => { - assert.equal(result2?.[0][0].qqq, 4); - return connResolved?.end(); - }) - .then(() => { - doneCalled = true; - }) - .catch((err) => { - throw err; - }); -} +await describe('Promise Wrappers', async () => { + const createConnection = promiseCreateConnection; + const createPool = createPoolPromise; -function testErrors() { - let connResolved: Connection | undefined; - const connPromise = createConnection(config); - - connPromise - .then((conn) => { - connResolved = conn; - return conn.query('select 1+2 as ttt'); - }) - .then((result1) => { - assert.equal(result1[0][0].ttt, 3); - return connResolved?.query('bad sql'); - }) - .then((result2) => { - assert.equal(result2?.[0][0].ttt, 3); - return connResolved?.query('select 2+2 as qqq'); - }) - .catch(() => { - exceptionCaught = true; - if (connResolved) { - connResolved.end(); - } else { - console.log('Warning: promise rejected before first query'); - } - }); -} + // it's lazy exported from main index.js as well. Test that it's same function + const mainModule = await import('../../../../index.js'); -function testObjParams() { - let connResolved: Connection | undefined; - createConnection(config) - .then((conn) => { - connResolved = conn; - return conn.query({ - sql: 'select ?-? as ttt', - values: [5, 2], - }); - }) - .then((result1) => { - assert.equal(result1[0][0].ttt, 3); - return connResolved?.execute({ - sql: 'select ?-? as ttt', - values: [8, 5], - }); - }) - .then((result2) => { - assert.equal(result2?.[0][0].ttt, 3); - return connResolved?.end(); - }) - .catch((err) => { - console.log(err); - }); -} + it(() => { + // @ts-expect-error: TODO: implement typings + const mainExport = mainModule.default.createConnectionPromise; + assert.equal(mainExport, createConnection); + }); -function testPrepared() { - let connResolved: Connection | undefined; - createConnection(config) - .then((conn) => { - connResolved = conn; - return conn.prepare('select ?-? as ttt, ? as uuu'); - }) - .then((statement) => statement.execute([11, 3, 'test'])) - .then((result) => { - const rows = result[0] as TttUuuRow[]; - assert.equal(rows[0].ttt, 8); - assert.equal(rows[0].uuu, 'test'); - return connResolved?.end(); - }) - .catch((err) => { - console.log(err); - if (connResolved) { - connResolved.end(); - } else { - console.log( - 'Warning: promise rejected before executing prepared statement' - ); - } - }); -} + await it('testBasic', async () => { + const conn = await createConnection(config); + const result1 = await conn.query('select 1+2 as ttt'); + assert.equal(result1[0][0].ttt, 3); + const result2 = await conn.query('select 2+2 as qqq'); + assert.equal(result2[0][0].qqq, 4); + await conn.end(); + }); -// REVIEW: Unused - -function testEventsConnect() { - let connResolved: Connection | undefined; - createConnection(config) - .then((conn) => { - connResolved = conn; - let events = 0; - - const expectedListeners: Record = { - error: 1, - drain: 0, - connect: 0, - enqueue: 0, - end: 0, - }; - for (const eventName in expectedListeners) { - assert.equal( - // @ts-expect-error: TODO: implement typings - conn.connection.listenerCount(eventName), - expectedListeners[eventName], - eventName - ); - } - - conn - .once( - 'error', - function () { - assert.equal(this, conn); - ++events; - }.bind(conn) - ) - .once( - 'drain', - function () { - assert.equal(this, conn); - ++events; - }.bind(conn) - ) - .once( - 'connect', - function () { - assert.equal(this, conn); - ++events; - }.bind(conn) - ) - .once( - 'enqueue', - function () { - assert.equal(this, conn); - ++events; - }.bind(conn) - ) - .once( - 'end', - function () { - assert.equal(this, conn); - ++events; - - doneEventsConnect = events === 5; - }.bind(conn) - ); - - // @ts-expect-error: TODO: implement typings - conn.connection.emit('error', new Error()); - // @ts-expect-error: TODO: implement typings - conn.connection.emit('drain'); - // @ts-expect-error: TODO: implement typings - conn.connection.emit('connect'); - // @ts-expect-error: TODO: implement typings - conn.connection.emit('enqueue'); - // @ts-expect-error: TODO: implement typings - conn.connection.emit('end'); - - expectedListeners.error = 0; - for (const eventName in expectedListeners) { - assert.equal( - // @ts-expect-error: TODO: implement typings - conn.connection.listenerCount(eventName), - expectedListeners[eventName], - eventName - ); - } - - conn.end(); - }) - .catch((err) => { - console.log(err); - if (connResolved) { - connResolved.end(); - } else { - console.log( - 'Warning: promise rejected before executing prepared statement' - ); - } - }); -} + await it('testErrors', async () => { + const conn = await createConnection(config); + const result1 = await conn.query('select 1+2 as ttt'); + assert.equal(result1[0][0].ttt, 3); + try { + await conn.query('bad sql'); + assert.fail('Expected query to fail'); + } catch { + // expected + } finally { + await conn.end(); + } + }); -function testBasicPool() { - const pool = createPool(config); - - pool - .getConnection() - .then((connResolved) => { - pool.releaseConnection(connResolved); - return pool.query('select 1+2 as ttt'); - }) - .then((result1) => { - assert.equal(result1[0][0].ttt, 3); - return pool.query('select 2+2 as qqq'); - }) - .then((result2) => { - assert.equal(result2[0][0].qqq, 4); - return pool.end(); - }) - .then(() => { - doneCalledPool = true; - }) - .catch((err) => { - throw err; + await it('testObjParams', async () => { + const conn = await createConnection(config); + const result1 = await conn.query({ + sql: 'select ?-? as ttt', + values: [5, 2], }); -} - -function testErrorsPool() { - const pool = createPool(config); - pool - .query('select 1+2 as ttt') - .then((result1) => { - assert.equal(result1[0][0].ttt, 3); - return pool.query('bad sql'); - }) - .then((result2) => { - assert.equal(result2[0][0].ttt, 3); - return pool.query('select 2+2 as qqq'); - }) - .catch(() => { - exceptionCaughtPool = true; - return pool.end(); + assert.equal(result1[0][0].ttt, 3); + const result2 = await conn.execute({ + sql: 'select ?-? as ttt', + values: [8, 5], }); -} + assert.equal(result2[0][0].ttt, 3); + await conn.end(); + }); + + await it('testPrepared', async () => { + const conn = await createConnection(config); + const statement = await conn.prepare('select ?-? as ttt, ? as uuu'); + const result = await statement.execute([11, 3, 'test']); + const rows = result[0] as TttUuuRow[]; + assert.equal(rows[0].ttt, 8); + assert.equal(rows[0].uuu, 'test'); + await conn.end(); + }); -function testObjParamsPool() { - const pool = createPool(config); - pool - .query({ + await it('testEventsConnect', async () => { + const conn = await createConnection(config); + let events = 0; + + const expectedListeners: Record = { + error: 1, + drain: 0, + connect: 0, + enqueue: 0, + end: 0, + }; + for (const eventName in expectedListeners) { + assert.equal( + // @ts-expect-error: TODO: implement typings + conn.connection.listenerCount(eventName), + expectedListeners[eventName], + eventName + ); + } + + conn + .once( + 'error', + function () { + assert.equal(this, conn); + ++events; + }.bind(conn) + ) + .once( + 'drain', + function () { + assert.equal(this, conn); + ++events; + }.bind(conn) + ) + .once( + 'connect', + function () { + assert.equal(this, conn); + ++events; + }.bind(conn) + ) + .once( + 'enqueue', + function () { + assert.equal(this, conn); + ++events; + }.bind(conn) + ) + .once( + 'end', + function () { + assert.equal(this, conn); + ++events; + }.bind(conn) + ); + + // @ts-expect-error: TODO: implement typings + conn.connection.emit('error', new Error()); + // @ts-expect-error: TODO: implement typings + conn.connection.emit('drain'); + // @ts-expect-error: TODO: implement typings + conn.connection.emit('connect'); + // @ts-expect-error: TODO: implement typings + conn.connection.emit('enqueue'); + // @ts-expect-error: TODO: implement typings + conn.connection.emit('end'); + + assert.equal(events, 5, 'wrong number of connection events'); + + expectedListeners.error = 0; + for (const eventName in expectedListeners) { + assert.equal( + // @ts-expect-error: TODO: implement typings + conn.connection.listenerCount(eventName), + expectedListeners[eventName], + eventName + ); + } + + await conn.end(); + }); + + await it('testBasicPool', async () => { + const pool = createPool(config); + const connResolved = await pool.getConnection(); + pool.releaseConnection(connResolved); + const result1 = await pool.query('select 1+2 as ttt'); + assert.equal(result1[0][0].ttt, 3); + const result2 = await pool.query('select 2+2 as qqq'); + assert.equal(result2[0][0].qqq, 4); + await pool.end(); + }); + + await it('testErrorsPool', async () => { + const pool = createPool(config); + const result1 = await pool.query('select 1+2 as ttt'); + assert.equal(result1[0][0].ttt, 3); + try { + await pool.query('bad sql'); + assert.fail('Expected query to fail'); + } catch { + // expected + } finally { + await pool.end(); + } + }); + + await it('testObjParamsPool', async () => { + const pool = createPool(config); + const result1 = await pool.query({ sql: 'select ?-? as ttt', values: [5, 2], - }) - .then((result1) => { - assert.equal(result1[0][0].ttt, 3); - return pool.execute({ - sql: 'select ?-? as ttt', - values: [8, 5], - }); - }) - .then((result2) => { - assert.equal(result2[0][0].ttt, 3); - return pool.end(); - }) - .catch((err) => { - console.log(err); }); -} -function testPromiseLibrary() { - const pool = createPool(config); - let promise: Promise = pool.execute({ - sql: 'select ?-? as ttt', - values: [8, 5], + assert.equal(result1[0][0].ttt, 3); + const result2 = await pool.execute({ + sql: 'select ?-? as ttt', + values: [8, 5], + }); + assert.equal(result2[0][0].ttt, 3); + await pool.end(); }); - promise - .then(() => { - // @ts-expect-error: TODO: implement typings - assert.ok(promise instanceof pool.Promise); - }) - .then(() => { - promise = pool.end(); - // @ts-expect-error: TODO: implement typings - assert.ok(promise instanceof pool.Promise); - }) - .catch((err) => { - console.log(err); + + await it('testPromiseLibrary', async () => { + const pool = createPool(config); + const promise = pool.execute({ + sql: 'select ?-? as ttt', + values: [8, 5], }); -} + // @ts-expect-error: TODO: implement typings + assert.ok(promise instanceof pool.Promise); + await promise; + const endPromise = pool.end(); + // @ts-expect-error: TODO: implement typings + assert.ok(endPromise instanceof pool.Promise); + await endPromise; + }); + + await it('testEventsPool', async () => { + const pool = createPool(config); + let events = 0; + + const expectedListeners: Record = { + acquire: 0, + connection: 0, + enqueue: 0, + release: 0, + }; + for (const eventName in expectedListeners) { + assert.equal( + pool.pool.listenerCount(eventName), + expectedListeners[eventName], + eventName + ); + } + + pool + .once( + 'acquire', + function () { + assert.equal(this, pool); + ++events; + }.bind(pool) + ) + .once( + 'connection', + function () { + assert.equal(this, pool); + ++events; + }.bind(pool) + ) + .once( + 'enqueue', + function () { + assert.equal(this, pool); + ++events; + }.bind(pool) + ) + .once( + 'release', + function () { + assert.equal(this, pool); + ++events; + }.bind(pool) + ); -function testEventsPool() { - const pool = createPool(config); - let events = 0; - - const expectedListeners: Record = { - acquire: 0, - connection: 0, - enqueue: 0, - release: 0, - }; - for (const eventName in expectedListeners) { - assert.equal( - pool.pool.listenerCount(eventName), - expectedListeners[eventName], - eventName + pool.pool.emit('acquire'); + pool.pool.emit('connection'); + pool.pool.emit('enqueue'); + pool.pool.emit('release'); + + assert.equal(events, 4, 'wrong number of pool connection events'); + + for (const eventName in expectedListeners) { + assert.equal( + pool.pool.listenerCount(eventName), + expectedListeners[eventName], + eventName + ); + } + }); + + await it('testChangeUser', async () => { + const onlyUsername = function (name: string) { + return name.substring(0, name.indexOf('@')); + }; + + const conn = await createConnection(config); + await conn.query( + "CREATE USER IF NOT EXISTS 'changeuser1'@'%' IDENTIFIED BY 'changeuser1pass'" ); - } - - pool - .once( - 'acquire', - function () { - assert.equal(this, pool); - ++events; - }.bind(pool) - ) - .once( - 'connection', - function () { - assert.equal(this, pool); - ++events; - }.bind(pool) - ) - .once( - 'enqueue', - function () { - assert.equal(this, pool); - ++events; - }.bind(pool) - ) - .once( - 'release', - function () { - assert.equal(this, pool); - ++events; - - doneEventsPool = events === 4; - }.bind(pool) + await conn.query( + "CREATE USER IF NOT EXISTS 'changeuser2'@'%' IDENTIFIED BY 'changeuser2pass'" ); + await conn.query("GRANT ALL ON *.* TO 'changeuser1'@'%'"); + await conn.query("GRANT ALL ON *.* TO 'changeuser2'@'%'"); + await conn.query('FLUSH PRIVILEGES'); - pool.pool.emit('acquire'); - pool.pool.emit('connection'); - pool.pool.emit('enqueue'); - pool.pool.emit('release'); - - for (const eventName in expectedListeners) { - assert.equal( - pool.pool.listenerCount(eventName), - expectedListeners[eventName], - eventName + await conn.changeUser({ + user: 'changeuser1', + password: 'changeuser1pass', + }); + const result1 = await conn.query('select current_user()'); + assert.deepEqual( + onlyUsername(result1[0][0]['current_user()']), + 'changeuser1' ); - } -} -function testChangeUser() { - const onlyUsername = function (name: string) { - return name.substring(0, name.indexOf('@')); - }; - let connResolved: Connection | undefined; - - createConnection(config) - .then((conn) => { - connResolved = conn; - return connResolved.query( - "CREATE USER IF NOT EXISTS 'changeuser1'@'%' IDENTIFIED BY 'changeuser1pass'" - ); - }) - .then(() => { - connResolved?.query( - "CREATE USER IF NOT EXISTS 'changeuser2'@'%' IDENTIFIED BY 'changeuser2pass'" - ); - connResolved?.query("GRANT ALL ON *.* TO 'changeuser1'@'%'"); - connResolved?.query("GRANT ALL ON *.* TO 'changeuser2'@'%'"); - return connResolved?.query('FLUSH PRIVILEGES'); - }) - .then(() => { - connResolved?.changeUser({ - user: 'changeuser1', - password: 'changeuser1pass', - }); - }) - .then(() => connResolved?.query('select current_user()')) - .then((result) => { - const rows = result?.[0]; - assert.deepEqual( - onlyUsername(rows?.[0]['current_user()'] ?? ''), - 'changeuser1' - ); - return connResolved?.changeUser({ - user: 'changeuser2', - password: 'changeuser2pass', - }); - }) - .then(() => connResolved?.query('select current_user()')) - .then((result) => { - const rows = result?.[0]; - assert.deepEqual( - onlyUsername(rows?.[0]['current_user()'] ?? ''), - 'changeuser2' - ); - return connResolved?.changeUser({ - user: 'changeuser1', - // TODO: re-enable testing passwordSha1 auth. Only works for mysql_native_password, so need to change test to create user with this auth method - password: 'changeuser1pass', - //passwordSha1: Buffer.from('f961d39c82138dcec42b8d0dcb3e40a14fb7e8cd', 'hex') // sha1(changeuser1pass) - }); - }) - .then(() => connResolved?.query('select current_user()')) - .then((result) => { - const rows = result?.[0]; - assert.deepEqual( - onlyUsername(rows?.[0]['current_user()'] ?? ''), - 'changeuser1' - ); - doneChangeUser = true; - return connResolved?.end(); - }) - .catch((err) => { - console.log(err); - if (connResolved) { - connResolved.end(); - } - throw err; + await conn.changeUser({ + user: 'changeuser2', + password: 'changeuser2pass', }); -} + const result2 = await conn.query('select current_user()'); + assert.deepEqual( + onlyUsername(result2[0][0]['current_user()']), + 'changeuser2' + ); -function testConnectionProperties() { - let connResolved: Connection | undefined; - createConnection(config) - .then((conn) => { - connResolved = conn; - assert.equal(typeof conn.config, 'object'); - assert.ok('queryFormat' in conn.config); - assert.equal(typeof conn.threadId, 'number'); - return connResolved.end(); - }) - .catch((err) => { - if (connResolved) { - connResolved.end(); - } - throw err; + await conn.changeUser({ + user: 'changeuser1', + // TODO: re-enable testing passwordSha1 auth. Only works for mysql_native_password, so need to change test to create user with this auth method + password: 'changeuser1pass', + //passwordSha1: Buffer.from('f961d39c82138dcec42b8d0dcb3e40a14fb7e8cd', 'hex') // sha1(changeuser1pass) }); -} + const result3 = await conn.query('select current_user()'); + assert.deepEqual( + onlyUsername(result3[0][0]['current_user()']), + 'changeuser1' + ); -function timebomb(fuse: number) { - let timebomb: ReturnType | undefined; - - return { - arm() { - timebomb = setTimeout(() => { - throw new Error(`Timebomb not defused within ${fuse}ms`); - }, fuse); - }, - defuse() { - clearTimeout(timebomb); - }, - }; -} + await conn.end(); + }); -function testPoolConnectionDestroy() { - // Only allow one connection - const options = Object.assign({ connectionLimit: 1 }, config); - const pool = createPool(options); + await it('testConnectionProperties', async () => { + const conn = await createConnection(config); + assert.equal(typeof conn.config, 'object'); + assert.ok('queryFormat' in conn.config); + assert.equal(typeof conn.threadId, 'number'); + await conn.end(); + }); - const bomb = timebomb(2000); + await it('testPoolConnectionDestroy', async () => { + const options = Object.assign({ connectionLimit: 1 }, config); + const pool = createPool(options); - pool - .getConnection() - .then((connection) => connection.destroy()) - .then(bomb.arm) - .then(() => pool.getConnection()) - .then(bomb.defuse) - .then(() => pool.end()); -} + const connection = await pool.getConnection(); + connection.destroy(); -testBasic(); -testErrors(); -testObjParams(); -testPrepared(); -testEventsConnect(); -testBasicPool(); -testErrorsPool(); -testObjParamsPool(); -testEventsPool(); -testChangeUser(); -testConnectionProperties(); -testPoolConnectionDestroy(); -testPromiseLibrary(); - -process.on('exit', () => { - assert.equal(doneCalled, true, 'done not called'); - assert.equal(exceptionCaught, true, 'exception not caught'); - assert.equal(doneEventsConnect, true, 'wrong number of connection events'); - assert.equal(doneCalledPool, true, 'pool done not called'); - assert.equal(exceptionCaughtPool, true, 'pool exception not caught'); - assert.equal(doneEventsPool, true, 'wrong number of pool connection events'); - assert.equal(doneChangeUser, true, 'user not changed'); -}); + const bomb = setTimeout(() => { + throw new Error('Timebomb not defused within 2000ms'); + }, 2000); -process.on('unhandledRejection', (err) => { - console.log('error:', (err as Error).stack); + await pool.getConnection(); + clearTimeout(bomb); + await pool.end(); + }); }); diff --git a/test/esm/integration/regressions/test-#433.test.mts b/test/esm/integration/regressions/test-#433.test.mts index b953dc77e0..0386d3b713 100644 --- a/test/esm/integration/regressions/test-#433.test.mts +++ b/test/esm/integration/regressions/test-#433.test.mts @@ -1,6 +1,6 @@ import type { QueryError, RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; // TODO: reach out to PlanetScale to clarify charset support @@ -9,86 +9,82 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection({ charset: 'KOI8R_GENERAL_CI' }); +await describe('Regression #433', async () => { + await it('should correctly handle KOI8R charset in table names and error messages', async () => { + const connection = createConnection({ charset: 'KOI8R_GENERAL_CI' }); -const tableName = 'МояТаблица'; -const testFields = ['поле1', 'поле2', 'поле3', 'поле4']; -const testRows = [ - ['привет', 'мир', 47, 7], - ['ура', 'тест', 11, 108], -]; + const tableName = 'МояТаблица'; + const testFields = ['поле1', 'поле2', 'поле3', 'поле4']; + const testRows = [ + ['привет', 'мир', 47, 7], + ['ура', 'тест', 11, 108], + ]; -let actualRows: RowDataPacket[] = []; -let actualError = ''; + const { actualRows, actualError } = await new Promise<{ + actualRows: RowDataPacket[]; + actualError: string; + }>((resolve, reject) => { + connection.query( + [ + `CREATE TEMPORARY TABLE \`${tableName}\` (`, + ` \`${testFields[0]}\` varchar(255) NOT NULL,`, + ` \`${testFields[1]}\` varchar(255) NOT NULL,`, + ` \`${testFields[2]}\` int(11) NOT NULL,`, + ` \`${testFields[3]}\` int(11) NOT NULL,`, + ` PRIMARY KEY (\`${testFields[0]}\`)`, + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.query( + [ + `INSERT INTO \`${tableName}\` VALUES`, + `("${testRows[0][0]}","${testRows[0][1]}", ${testRows[0][2]}, ${testRows[0][3]}),`, + `("${testRows[1][0]}","${testRows[1][1]}", ${testRows[1][2]}, ${testRows[1][3]})`, + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.query( + `SELECT * FROM \`${tableName}\``, + (err, rows) => { + if (err) return reject(err); + connection.query(`SELECT * FROM \`${tableName}`, (err) => { + if (!err) { + return reject(new Error('Expected query to fail')); + } + connection.end(() => + resolve({ actualRows: rows, actualError: err.message }) + ); + }); + } + ); + } + ); + } + ); + }); -function executeErrorMessageTest() { - // tableName does not have closing "`", we do this to have tableName in error string - // it is sent back in original encoding (koi8r), we are testing that it's decoded correctly - connection.query(`SELECT * FROM \`${tableName}`, (err) => { - if (!err) { - assert.fail('Expected query to fail'); - } - actualError = err.message; - connection.end(); - }); -} + testRows.map((tRow, index) => { + const cols = testFields; + const aRow = actualRows[index]; + assert.equal(aRow[cols[0]], tRow[0]); + assert.equal(aRow[cols[1]], tRow[1]); + assert.equal(aRow[cols[2]], tRow[2]); + assert.equal(aRow[cols[3]], tRow[3]); + }); -function executeTest(err: QueryError | null) { - assert.ifError(err); - connection.query( - `SELECT * FROM \`${tableName}\``, - (err, rows) => { - assert.ifError(err); - actualRows = rows; - executeErrorMessageTest(); - } - ); -} + /* eslint quotes: 0 */ + const expectedErrorMysql = + "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`МояТаблица' at line 1"; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${tableName}\` (`, - ` \`${testFields[0]}\` varchar(255) NOT NULL,`, - ` \`${testFields[1]}\` varchar(255) NOT NULL,`, - ` \`${testFields[2]}\` int(11) NOT NULL,`, - ` \`${testFields[3]}\` int(11) NOT NULL,`, - ` PRIMARY KEY (\`${testFields[0]}\`)`, - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join(' '), - (err) => { - assert.ifError(err); - connection.query( - [ - `INSERT INTO \`${tableName}\` VALUES`, - `("${testRows[0][0]}","${testRows[0][1]}", ${testRows[0][2]}, ${testRows[0][3]}),`, - `("${testRows[1][0]}","${testRows[1][1]}", ${testRows[1][2]}, ${testRows[1][3]})`, - ].join(' '), - executeTest - ); - } -); + const expectedErrorMariaDB = + "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '`МояТаблица' at line 1"; -/* eslint quotes: 0 */ -const expectedErrorMysql = - "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '`МояТаблица' at line 1"; - -const expectedErrorMariaDB = - "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '`МояТаблица' at line 1"; - -process.on('exit', () => { - testRows.map((tRow, index) => { - const cols = testFields; - const aRow = actualRows[index]; - assert.equal(aRow[cols[0]], tRow[0]); - assert.equal(aRow[cols[1]], tRow[1]); - assert.equal(aRow[cols[2]], tRow[2]); - assert.equal(aRow[cols[3]], tRow[3]); + // @ts-expect-error: internal access + if (connection._handshakePacket.serverVersion.match(/MariaDB/)) { + assert.equal(actualError, expectedErrorMariaDB); + } else { + assert.equal(actualError, expectedErrorMysql); + } }); - - // @ts-expect-error: internal access - if (connection._handshakePacket.serverVersion.match(/MariaDB/)) { - assert.equal(actualError, expectedErrorMariaDB); - } else { - assert.equal(actualError, expectedErrorMysql); - } }); diff --git a/test/esm/integration/regressions/test-#442.test.mts b/test/esm/integration/regressions/test-#442.test.mts index c603855f36..c75ff54e88 100644 --- a/test/esm/integration/regressions/test-#442.test.mts +++ b/test/esm/integration/regressions/test-#442.test.mts @@ -1,61 +1,59 @@ import type { QueryError, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Regression #442', async () => { + await it('should correctly handle Chinese characters in table and field names', async () => { + const connection = createConnection(); -const tableName = '商城'; -const testFields = ['商品类型', '商品说明', '价格', '剩余']; -const testRows = [ - ['商类型', '商品型', 47, 7], - ['类商型', '商型品', 11, 108], -]; + const tableName = '商城'; + const testFields = ['商品类型', '商品说明', '价格', '剩余']; + const testRows = [ + ['商类型', '商品型', 47, 7], + ['类商型', '商型品', 11, 108], + ]; -let actualRows: RowDataPacket[] = []; + const actualRows = await new Promise((resolve, reject) => { + connection.query( + [ + `CREATE TEMPORARY TABLE \`${tableName}\` (`, + ` \`${testFields[0]}\` varchar(255) NOT NULL,`, + ` \`${testFields[1]}\` varchar(255) NOT NULL,`, + ` \`${testFields[2]}\` int(11) NOT NULL,`, + ` \`${testFields[3]}\` int(11) NOT NULL,`, + ` PRIMARY KEY (\`${testFields[0]}\`)`, + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.query( + [ + `INSERT INTO \`${tableName}\` VALUES`, + `("${testRows[0][0]}","${testRows[0][1]}", ${testRows[0][2]}, ${testRows[0][3]}),`, + `("${testRows[1][0]}","${testRows[1][1]}", ${testRows[1][2]}, ${testRows[1][3]})`, + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.query( + `SELECT * FROM \`${tableName}\``, + (err, rows) => { + if (err) return reject(err); + connection.end(() => resolve(rows)); + } + ); + } + ); + } + ); + }); -function executeTest(err: QueryError | null) { - assert.ifError(err); - connection.query( - `SELECT * FROM \`${tableName}\``, - (err, rows) => { - assert.ifError(err); - actualRows = rows; - connection.end(); - } - ); -} - -connection.query( - [ - `CREATE TEMPORARY TABLE \`${tableName}\` (`, - ` \`${testFields[0]}\` varchar(255) NOT NULL,`, - ` \`${testFields[1]}\` varchar(255) NOT NULL,`, - ` \`${testFields[2]}\` int(11) NOT NULL,`, - ` \`${testFields[3]}\` int(11) NOT NULL,`, - ` PRIMARY KEY (\`${testFields[0]}\`)`, - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join(' '), - (err) => { - assert.ifError(err); - connection.query( - [ - `INSERT INTO \`${tableName}\` VALUES`, - `("${testRows[0][0]}","${testRows[0][1]}", ${testRows[0][2]}, ${testRows[0][3]}),`, - `("${testRows[1][0]}","${testRows[1][1]}", ${testRows[1][2]}, ${testRows[1][3]})`, - ].join(' '), - executeTest - ); - } -); - -process.on('exit', () => { - testRows.map((tRow, index) => { - const cols = testFields; - const aRow = actualRows[index]; - assert.equal(aRow[cols[0]], tRow[0]); - assert.equal(aRow[cols[1]], tRow[1]); - assert.equal(aRow[cols[2]], tRow[2]); - assert.equal(aRow[cols[3]], tRow[3]); + testRows.map((tRow, index) => { + const cols = testFields; + const aRow = actualRows[index]; + assert.equal(aRow[cols[0]], tRow[0]); + assert.equal(aRow[cols[1]], tRow[1]); + assert.equal(aRow[cols[2]], tRow[2]); + assert.equal(aRow[cols[3]], tRow[3]); + }); }); }); diff --git a/test/esm/integration/regressions/test-#485.test.mts b/test/esm/integration/regressions/test-#485.test.mts index 17811de459..0ba741dd7e 100644 --- a/test/esm/integration/regressions/test-#485.test.mts +++ b/test/esm/integration/regressions/test-#485.test.mts @@ -1,42 +1,38 @@ import type { PoolOptions, RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import PoolConnection from '../../../../lib/pool_connection.js'; import { createPool as createPoolPromise } from '../../../../promise.js'; import { config } from '../../common.test.mjs'; type TestRow = RowDataPacket & { ttt: number }; -function createPool(args?: PoolOptions) { - if (!args && process.env.MYSQL_CONNECTION_URL) { - return createPoolPromise({ uri: process.env.MYSQL_CONNECTION_URL }); +await describe('Regression #485', async () => { + function createPool(args?: PoolOptions) { + if (!args && process.env.MYSQL_CONNECTION_URL) { + return createPoolPromise({ uri: process.env.MYSQL_CONNECTION_URL }); + } + + return createPoolPromise({ ...config, ...args }); } - return createPoolPromise({ ...config, ...args }); -} -// stub -const release = PoolConnection.prototype.release; -let releaseCalls = 0; -PoolConnection.prototype.release = function () { - releaseCalls++; -}; + await it('should call PoolConnection.release after pool.execute', async () => { + const release = PoolConnection.prototype.release; + let releaseCalls = 0; + PoolConnection.prototype.release = function () { + releaseCalls++; + }; -function testPoolPromiseExecuteLeak() { - const pool = createPool(); - pool - .execute('select 1+2 as ttt') - .then((result) => { - assert.equal(result[0][0].ttt, 3); - return pool.end(); - }) - .catch((err) => { - assert.ifError(err); - }); -} + const pool = createPool(); -testPoolPromiseExecuteLeak(); + try { + const result = await pool.execute('select 1+2 as ttt'); + assert.equal(result[0][0].ttt, 3); + } finally { + await pool.end(); + PoolConnection.prototype.release = release; + } -process.on('exit', () => { - PoolConnection.prototype.release = release; - assert.equal(releaseCalls, 1, 'PoolConnection.release was not called'); + assert.equal(releaseCalls, 1, 'PoolConnection.release was not called'); + }); }); diff --git a/test/esm/integration/regressions/test-#617.test.mts b/test/esm/integration/regressions/test-#617.test.mts index 02ac1f21d7..506acea651 100644 --- a/test/esm/integration/regressions/test-#617.test.mts +++ b/test/esm/integration/regressions/test-#617.test.mts @@ -1,6 +1,6 @@ import type { QueryError, RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; // PlanetScale response has trailing 000 in 2017-07-26 09:36:42.000 @@ -10,68 +10,67 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const connection = createConnection({ dateStrings: true }); +await describe('Regression #617', async () => { + await it('should correctly handle TIMESTAMP(3) with dateStrings', async () => { + const connection = createConnection({ dateStrings: true }); -const tableName = 'dates'; -const testFields = ['id', 'date', 'name']; -const testRows = [ - [1, '2017-07-26 09:36:42.000', 'John'], - [2, '2017-07-26 09:36:42.123', 'Jane'], -]; -const expected = [ - { - id: 1, - date: '2017-07-26 09:36:42', - name: 'John', - }, - { - id: 2, - date: '2017-07-26 09:36:42.123', - name: 'Jane', - }, -]; + const tableName = 'dates'; + const testFields = ['id', 'date', 'name']; + const testRows = [ + [1, '2017-07-26 09:36:42.000', 'John'], + [2, '2017-07-26 09:36:42.123', 'Jane'], + ]; + const expected = [ + { + id: 1, + date: '2017-07-26 09:36:42', + name: 'John', + }, + { + id: 2, + date: '2017-07-26 09:36:42.123', + name: 'Jane', + }, + ]; -let actualRows: RowDataPacket[] = []; - -function executeTest(err: QueryError | null) { - assert.ifError(err); - connection.execute( - `SELECT * FROM \`${tableName}\``, - (err, rows) => { - assert.ifError(err); - actualRows = rows; - connection.end(); - } - ); -} - -connection.query( - [ - `CREATE TEMPORARY TABLE \`${tableName}\` (`, - ` \`${testFields[0]}\` int,`, - ` \`${testFields[1]}\` TIMESTAMP(3),`, - ` \`${testFields[2]}\` varchar(10)`, - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join(' '), - (err) => { - assert.ifError(err); - connection.query( - [ - `INSERT INTO \`${tableName}\` VALUES`, - `(${testRows[0][0]},"${testRows[0][1]}", "${testRows[0][2]}"),`, - `(${testRows[1][0]},"${testRows[1][1]}", "${testRows[1][2]}")`, - ].join(' '), - executeTest - ); - } -); + const actualRows = await new Promise((resolve, reject) => { + connection.query( + [ + `CREATE TEMPORARY TABLE \`${tableName}\` (`, + ` \`${testFields[0]}\` int,`, + ` \`${testFields[1]}\` TIMESTAMP(3),`, + ` \`${testFields[2]}\` varchar(10)`, + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.query( + [ + `INSERT INTO \`${tableName}\` VALUES`, + `(${testRows[0][0]},"${testRows[0][1]}", "${testRows[0][2]}"),`, + `(${testRows[1][0]},"${testRows[1][1]}", "${testRows[1][2]}")`, + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.execute( + `SELECT * FROM \`${tableName}\``, + (err, rows) => { + if (err) return reject(err); + connection.end(() => resolve(rows)); + } + ); + } + ); + } + ); + }); -process.on('exit', () => { - console.log(actualRows); - expected.map((exp, index) => { - const row = actualRows[index]; - Object.keys(exp).map((key) => { - assert.equal(exp[key as keyof typeof exp], row[key]); + console.log(actualRows); + expected.map((exp, index) => { + const row = actualRows[index]; + Object.keys(exp).map((key) => { + assert.equal(exp[key as keyof typeof exp], row[key]); + }); }); }); }); diff --git a/test/esm/integration/regressions/test-#629.test.mts b/test/esm/integration/regressions/test-#629.test.mts index 1c7a568582..0b4d0cd787 100644 --- a/test/esm/integration/regressions/test-#629.test.mts +++ b/test/esm/integration/regressions/test-#629.test.mts @@ -1,79 +1,77 @@ import type { QueryError, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection({ - dateStrings: false, - timezone: 'Z', -}); - -const tableName = 'dates'; -const testFields = ['id', 'date1', 'date2', 'name']; -const testRows = [ - [1, '2017-07-26 09:36:42.000', '2017-07-29 09:22:24.000', 'John'], - [2, '2017-07-26 09:36:42.123', '2017-07-29 09:22:24.321', 'Jane'], -]; -const expected = [ - { - id: 1, - date1: new Date('2017-07-26T09:36:42.000Z'), - date2: new Date('2017-07-29T09:22:24.000Z'), - name: 'John', - }, - { - id: 2, - date1: new Date('2017-07-26T09:36:42.123Z'), - date2: new Date('2017-07-29T09:22:24.321Z'), - name: 'Jane', - }, -]; - -let actualRows: RowDataPacket[] = []; +await describe('Regression #629', async () => { + await it('should correctly handle TIMESTAMP(3) and DATETIME(3) with timezone Z', async () => { + const connection = createConnection({ + dateStrings: false, + timezone: 'Z', + }); -function executeTest(err: QueryError | null) { - assert.ifError(err); - connection.execute( - `SELECT * FROM \`${tableName}\``, - (err, rows) => { - assert.ifError(err); - actualRows = rows; - connection.end(); - } - ); -} + const tableName = 'dates'; + const testFields = ['id', 'date1', 'date2', 'name']; + const testRows = [ + [1, '2017-07-26 09:36:42.000', '2017-07-29 09:22:24.000', 'John'], + [2, '2017-07-26 09:36:42.123', '2017-07-29 09:22:24.321', 'Jane'], + ]; + const expected = [ + { + id: 1, + date1: new Date('2017-07-26T09:36:42.000Z'), + date2: new Date('2017-07-29T09:22:24.000Z'), + name: 'John', + }, + { + id: 2, + date1: new Date('2017-07-26T09:36:42.123Z'), + date2: new Date('2017-07-29T09:22:24.321Z'), + name: 'Jane', + }, + ]; -connection.query( - [ - `CREATE TEMPORARY TABLE \`${tableName}\` (`, - ` \`${testFields[0]}\` int,`, - ` \`${testFields[1]}\` TIMESTAMP(3),`, - ` \`${testFields[2]}\` DATETIME(3),`, - ` \`${testFields[3]}\` varchar(10)`, - ') ENGINE=InnoDB DEFAULT CHARSET=utf8', - ].join(' '), - (err) => { - assert.ifError(err); - connection.query( - [ - `INSERT INTO \`${tableName}\` VALUES`, - `(${testRows[0][0]},"${testRows[0][1]}", "${testRows[0][2]}", "${testRows[0][3]}"),`, - `(${testRows[1][0]},"${testRows[1][1]}", "${testRows[1][2]}", "${testRows[1][3]}")`, - ].join(' '), - executeTest - ); - } -); + const actualRows = await new Promise((resolve, reject) => { + connection.query( + [ + `CREATE TEMPORARY TABLE \`${tableName}\` (`, + ` \`${testFields[0]}\` int,`, + ` \`${testFields[1]}\` TIMESTAMP(3),`, + ` \`${testFields[2]}\` DATETIME(3),`, + ` \`${testFields[3]}\` varchar(10)`, + ') ENGINE=InnoDB DEFAULT CHARSET=utf8', + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.query( + [ + `INSERT INTO \`${tableName}\` VALUES`, + `(${testRows[0][0]},"${testRows[0][1]}", "${testRows[0][2]}", "${testRows[0][3]}"),`, + `(${testRows[1][0]},"${testRows[1][1]}", "${testRows[1][2]}", "${testRows[1][3]}")`, + ].join(' '), + (err: QueryError | null) => { + if (err) return reject(err); + connection.execute( + `SELECT * FROM \`${tableName}\``, + (err, rows) => { + if (err) return reject(err); + connection.end(() => resolve(rows)); + } + ); + } + ); + } + ); + }); -process.on('exit', () => { - expected.map((exp, index) => { - const row = actualRows[index]; - Object.keys(exp).map((key) => { - if (key.startsWith('date')) { - assert.equal(+exp[key as keyof typeof exp], +row[key]); - } else { - assert.equal(exp[key as keyof typeof exp], row[key]); - } + expected.map((exp, index) => { + const row = actualRows[index]; + Object.keys(exp).map((key) => { + if (key.startsWith('date')) { + assert.equal(+exp[key as keyof typeof exp], +row[key]); + } else { + assert.equal(exp[key as keyof typeof exp], row[key]); + } + }); }); }); }); diff --git a/test/esm/integration/regressions/test-#82.test.mts b/test/esm/integration/regressions/test-#82.test.mts index e051f68766..30859f0bb0 100644 --- a/test/esm/integration/regressions/test-#82.test.mts +++ b/test/esm/integration/regressions/test-#82.test.mts @@ -1,63 +1,64 @@ import type { QueryError, RowDataPacket } from '../../../../index.js'; -import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; -const connection = createConnection(); +await describe('Regression #82', async () => { + await it('should correctly query views built on views', async () => { + const connection = createConnection(); -const config = { - table1: 'test82t1', - table2: 'test82t2', - view1: 'view82v1', - view2: 'view82v2', -}; -let results: RowDataPacket[] = []; + const config = { + table1: 'test82t1', + table2: 'test82t2', + view1: 'view82v1', + view2: 'view82v2', + }; -const prepareTestSet = function (cb: (err: QueryError | null) => void) { - connection.query(`drop table if exists ${config.table1}`); - connection.query(`drop table if exists ${config.table2}`); - connection.query(`drop view if exists ${config.view1}`); - connection.query(`drop view if exists ${config.view2}`); - connection.query( - `create table ${config.table1} (name1 varchar(20), linkId1 integer(11))` - ); - connection.query( - `create table ${config.table2} (name2 varchar(20), linkId2 integer(11))` - ); - connection.query( - `insert into ${config.table1} (name1, linkId1) values ("A", 1),("B", 2),("C", 3),("D", 4)` - ); - connection.query( - `insert into ${config.table2} (name2, linkId2) values ("AA", 1),("BB", 2),("CC", 3),("DD", 4)` - ); - connection.query( - `create view ${config.view1} as select name1, linkId1, name2 from ${config.table1} INNER JOIN ${config.table2} ON linkId1 = linkId2` - ); - connection.query( - `create view ${config.view2} as select name1, name2 from ${config.view1}`, - cb - ); -}; + const prepareTestSet = function (cb: (err: QueryError | null) => void) { + connection.query(`drop table if exists ${config.table1}`); + connection.query(`drop table if exists ${config.table2}`); + connection.query(`drop view if exists ${config.view1}`); + connection.query(`drop view if exists ${config.view2}`); + connection.query( + `create table ${config.table1} (name1 varchar(20), linkId1 integer(11))` + ); + connection.query( + `create table ${config.table2} (name2 varchar(20), linkId2 integer(11))` + ); + connection.query( + `insert into ${config.table1} (name1, linkId1) values ("A", 1),("B", 2),("C", 3),("D", 4)` + ); + connection.query( + `insert into ${config.table2} (name2, linkId2) values ("AA", 1),("BB", 2),("CC", 3),("DD", 4)` + ); + connection.query( + `create view ${config.view1} as select name1, linkId1, name2 from ${config.table1} INNER JOIN ${config.table2} ON linkId1 = linkId2` + ); + connection.query( + `create view ${config.view2} as select name1, name2 from ${config.view1}`, + cb + ); + }; -prepareTestSet((err) => { - assert.ifError(err); - connection.query( - `select * from ${config.view2} order by name2 desc`, - (err, rows) => { - assert.ifError(err); - results = rows; - connection.end(); - } - ); -}); + const results = await new Promise((resolve, reject) => { + prepareTestSet((err) => { + if (err) return reject(err); + connection.query( + `select * from ${config.view2} order by name2 desc`, + (err, rows) => { + if (err) return reject(err); + connection.end(() => resolve(rows)); + } + ); + }); + }); -process.on('exit', () => { - assert.equal(results[0].name1, 'D'); - assert.equal(results[1].name1, 'C'); - assert.equal(results[2].name1, 'B'); - assert.equal(results[3].name1, 'A'); - assert.equal(results[0].name2, 'DD'); - assert.equal(results[1].name2, 'CC'); - assert.equal(results[2].name2, 'BB'); - assert.equal(results[3].name2, 'AA'); + assert.equal(results[0].name1, 'D'); + assert.equal(results[1].name1, 'C'); + assert.equal(results[2].name1, 'B'); + assert.equal(results[3].name1, 'A'); + assert.equal(results[0].name2, 'DD'); + assert.equal(results[1].name2, 'CC'); + assert.equal(results[2].name2, 'BB'); + assert.equal(results[3].name2, 'AA'); + }); }); diff --git a/test/esm/integration/test-auth-switch-multi-factor.test.mts b/test/esm/integration/test-auth-switch-multi-factor.test.mts index cef51b4ff5..8407e5a7b7 100644 --- a/test/esm/integration/test-auth-switch-multi-factor.test.mts +++ b/test/esm/integration/test-auth-switch-multi-factor.test.mts @@ -3,7 +3,7 @@ import type { Connection } from '../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; import Command from '../../../lib/commands/command.js'; @@ -77,93 +77,110 @@ class TestAuthMultiFactor extends Command { } } -const server = mysql.createServer((conn: Connection) => { - // @ts-expect-error: TODO: implement typings - conn.serverConfig = {}; - // @ts-expect-error: TODO: implement typings - conn.serverConfig.encoding = 'cesu8'; - // @ts-expect-error: TODO: implement typings - conn.addCommand( - new TestAuthMultiFactor([ - { - // already covered by test-auth-switch - pluginName: 'auth_test_plugin1', - pluginData: Buffer.from('foo'), - }, - { - // 2nd factor auth plugin - pluginName: 'auth_test_plugin2', - pluginData: Buffer.from('bar'), - }, - { - // 3rd factor auth plugin - pluginName: 'auth_test_plugin3', - pluginData: Buffer.from('baz'), - }, - ]) - ); -}); - -const completed: string[] = []; - -portfinder.getPort((_err: Error | null, port: number) => { - server.listen(port); - const conn = mysql.createConnection({ - port: port, - password: 'secret1', - password2: 'secret2', - password3: 'secret3', - authPlugins: { - auth_test_plugin1() { - return () => { - const pluginName = 'auth_test_plugin1'; - completed.push(pluginName); - - return Buffer.from(pluginName); - }; - }, - auth_test_plugin2(options: { connection: Connection; command: string }) { - return () => { - if ( - options.connection.config.password !== - options.connection.config.password2 - ) { - return assert.fail('Incorrect authentication factor password.'); - } - - const pluginName = 'auth_test_plugin2'; - completed.push(pluginName); - - return Buffer.from(pluginName); - }; - }, - auth_test_plugin3(options: { connection: Connection; command: string }) { - return () => { - if ( - options.connection.config.password !== - options.connection.config.password3 - ) { - return assert.fail('Incorrect authentication factor password.'); - } - - const pluginName = 'auth_test_plugin3'; - completed.push(pluginName); - - return Buffer.from(pluginName); - }; - }, - }, - }); - - conn.on('connect', () => { - assert.deepStrictEqual(completed, [ - 'auth_test_plugin1', - 'auth_test_plugin2', - 'auth_test_plugin3', - ]); +await describe('Auth Switch Multi Factor', async () => { + await it('should handle multi-factor authentication', async () => { + const server = mysql.createServer((conn: Connection) => { + // @ts-expect-error: TODO: implement typings + conn.serverConfig = {}; + // @ts-expect-error: TODO: implement typings + conn.serverConfig.encoding = 'cesu8'; + // @ts-expect-error: TODO: implement typings + conn.addCommand( + new TestAuthMultiFactor([ + { + // already covered by test-auth-switch + pluginName: 'auth_test_plugin1', + pluginData: Buffer.from('foo'), + }, + { + // 2nd factor auth plugin + pluginName: 'auth_test_plugin2', + pluginData: Buffer.from('bar'), + }, + { + // 3rd factor auth plugin + pluginName: 'auth_test_plugin3', + pluginData: Buffer.from('baz'), + }, + ]) + ); + }); - conn.end(); - // @ts-expect-error: TODO: implement typings - server.close(); + const completed: string[] = []; + + await new Promise((resolve) => { + portfinder.getPort((_err: Error | null, port: number) => { + server.listen(port); + const conn = mysql.createConnection({ + port: port, + password: 'secret1', + password2: 'secret2', + password3: 'secret3', + authPlugins: { + auth_test_plugin1() { + return () => { + const pluginName = 'auth_test_plugin1'; + completed.push(pluginName); + + return Buffer.from(pluginName); + }; + }, + auth_test_plugin2(options: { + connection: Connection; + command: string; + }) { + return () => { + if ( + options.connection.config.password !== + options.connection.config.password2 + ) { + return assert.fail( + 'Incorrect authentication factor password.' + ); + } + + const pluginName = 'auth_test_plugin2'; + completed.push(pluginName); + + return Buffer.from(pluginName); + }; + }, + auth_test_plugin3(options: { + connection: Connection; + command: string; + }) { + return () => { + if ( + options.connection.config.password !== + options.connection.config.password3 + ) { + return assert.fail( + 'Incorrect authentication factor password.' + ); + } + + const pluginName = 'auth_test_plugin3'; + completed.push(pluginName); + + return Buffer.from(pluginName); + }; + }, + }, + }); + + conn.on('connect', () => { + assert.deepStrictEqual(completed, [ + 'auth_test_plugin1', + 'auth_test_plugin2', + 'auth_test_plugin3', + ]); + + conn.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + }); }); }); diff --git a/test/esm/integration/test-auth-switch-plugin-async-error.test.mts b/test/esm/integration/test-auth-switch-plugin-async-error.test.mts index a2b4cf3f90..4df32104cc 100644 --- a/test/esm/integration/test-auth-switch-plugin-async-error.test.mts +++ b/test/esm/integration/test-auth-switch-plugin-async-error.test.mts @@ -1,9 +1,9 @@ // Copyright (c) 2021, Oracle and/or its affiliates. import type { AuthPlugin } from '../../../index.js'; -import assert from 'node:assert'; import { Buffer } from 'node:buffer'; import process from 'node:process'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; import Command from '../../../lib/commands/command.js'; @@ -52,51 +52,55 @@ class TestAuthSwitchPluginError extends Command { } } -const server = mysql.createServer((conn) => { - // @ts-expect-error: TODO: implement typings - conn.addCommand( - new TestAuthSwitchPluginError({ - pluginName: 'auth_test_plugin', - pluginData: Buffer.allocUnsafe(0), - }) - ); -}); - -let error: { code?: string; message?: string; fatal?: boolean } | undefined; -let uncaughtExceptions = 0; +await describe('Auth Switch Plugin Async Error', async () => { + await it('should handle auth plugin async error', async () => { + let error: { code?: string; message?: string; fatal?: boolean } | undefined; -portfinder.getPort((_err, port) => { - server.listen(port); - const conn = mysql.createConnection({ - port: port, - authPlugins: { - auth_test_plugin: ((_plginMetadata) => - function (_pluginData) { - return Promise.reject(Error('boom')); - }) as AuthPlugin, - }, - }); + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + const server = mysql.createServer((conn) => { + conn.on('error', (err: NodeJS.ErrnoException) => { + // The server must close the connection + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); - conn.on('error', (err) => { - error = err as { code?: string; message?: string; fatal?: boolean }; - - conn.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); -}); + // The plugin reports a fatal error + assert.equal(error?.code, 'AUTH_SWITCH_PLUGIN_ERROR'); + assert.equal(error?.message, 'boom'); + assert.equal(error?.fatal, true); + resolve(); + }); + // @ts-expect-error: TODO: implement typings + conn.addCommand( + new TestAuthSwitchPluginError({ + pluginName: 'auth_test_plugin', + pluginData: Buffer.allocUnsafe(0), + }) + ); + }); -process.on('uncaughtException', (err) => { - // The plugin reports a fatal error - assert.equal(error?.code, 'AUTH_SWITCH_PLUGIN_ERROR'); - assert.equal(error?.message, 'boom'); - assert.equal(error?.fatal, true); - // The server must close the connection - assert.equal((err as { code?: string }).code, 'PROTOCOL_CONNECTION_LOST'); + server.listen(port); + const conn = mysql.createConnection({ + port: port, + authPlugins: { + auth_test_plugin: ((_plginMetadata) => + function (_pluginData) { + return Promise.reject(Error('boom')); + }) as AuthPlugin, + }, + }); - uncaughtExceptions += 1; -}); + conn.on('error', (err) => { + error = err as { + code?: string; + message?: string; + fatal?: boolean; + }; -process.on('exit', () => { - assert.equal(uncaughtExceptions, 1); + conn.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + }); + }); + }); + }); }); diff --git a/test/esm/integration/test-auth-switch-plugin-error.test.mts b/test/esm/integration/test-auth-switch-plugin-error.test.mts index e8f2fdfac0..f06e790067 100644 --- a/test/esm/integration/test-auth-switch-plugin-error.test.mts +++ b/test/esm/integration/test-auth-switch-plugin-error.test.mts @@ -1,8 +1,8 @@ // Copyright (c) 2021, Oracle and/or its affiliates. -import assert from 'node:assert'; import { Buffer } from 'node:buffer'; import process from 'node:process'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; import Command from '../../../lib/commands/command.js'; @@ -51,50 +51,54 @@ class TestAuthSwitchPluginError extends Command { } } -const server = mysql.createServer((conn) => { - // @ts-expect-error: TODO: implement typings - conn.addCommand( - new TestAuthSwitchPluginError({ - pluginName: 'auth_test_plugin', - pluginData: Buffer.allocUnsafe(0), - }) - ); -}); - -let error: { code?: string; message?: string; fatal?: boolean } | undefined; -let uncaughtExceptions = 0; +await describe('Auth Switch Plugin Error', async () => { + await it('should handle auth plugin sync error', async () => { + let error: { code?: string; message?: string; fatal?: boolean } | undefined; -portfinder.getPort((_err, port) => { - server.listen(port); - const conn = mysql.createConnection({ - port: port, - authPlugins: { - auth_test_plugin: () => { - throw new Error('boom'); - }, - }, - }); + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + const server = mysql.createServer((conn) => { + conn.on('error', (err: NodeJS.ErrnoException) => { + // The server must close the connection + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); - conn.on('error', (err) => { - error = err as { code?: string; message?: string; fatal?: boolean }; - - conn.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); -}); + // The plugin reports a fatal error + assert.equal(error?.code, 'AUTH_SWITCH_PLUGIN_ERROR'); + assert.equal(error?.message, 'boom'); + assert.equal(error?.fatal, true); + resolve(); + }); + // @ts-expect-error: TODO: implement typings + conn.addCommand( + new TestAuthSwitchPluginError({ + pluginName: 'auth_test_plugin', + pluginData: Buffer.allocUnsafe(0), + }) + ); + }); -process.on('uncaughtException', (err) => { - // The plugin reports a fatal error - assert.equal(error?.code, 'AUTH_SWITCH_PLUGIN_ERROR'); - assert.equal(error?.message, 'boom'); - assert.equal(error?.fatal, true); - // The server must close the connection - assert.equal((err as { code?: string }).code, 'PROTOCOL_CONNECTION_LOST'); + server.listen(port); + const conn = mysql.createConnection({ + port: port, + authPlugins: { + auth_test_plugin: () => { + throw new Error('boom'); + }, + }, + }); - uncaughtExceptions += 1; -}); + conn.on('error', (err) => { + error = err as { + code?: string; + message?: string; + fatal?: boolean; + }; -process.on('exit', () => { - assert.equal(uncaughtExceptions, 1); + conn.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + }); + }); + }); + }); }); diff --git a/test/esm/integration/test-auth-switch.test.mts b/test/esm/integration/test-auth-switch.test.mts index c650f3c9b8..4ea48b3e48 100644 --- a/test/esm/integration/test-auth-switch.test.mts +++ b/test/esm/integration/test-auth-switch.test.mts @@ -1,7 +1,7 @@ import type { Connection } from '../../../index.js'; import { Buffer } from 'node:buffer'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; import Command from '../../../lib/commands/command.js'; @@ -94,57 +94,64 @@ class TestAuthSwitchHandshake extends Command { } } -const server = mysql.createServer((conn: Connection) => { - // @ts-expect-error: TODO: implement typings - conn.serverConfig = {}; - // @ts-expect-error: TODO: implement typings - conn.serverConfig.encoding = 'cesu8'; - // @ts-expect-error: TODO: implement typings - conn.addCommand( - new TestAuthSwitchHandshake({ - pluginName: 'auth_test_plugin', - pluginData: Buffer.from('f{tU-{K@BhfHt/-4^Z,'), - }) - ); -}); +await describe('Auth Switch', async () => { + await it('should handle auth switch handshake', async () => { + const server = mysql.createServer((conn: Connection) => { + // @ts-expect-error: TODO: implement typings + conn.serverConfig = {}; + // @ts-expect-error: TODO: implement typings + conn.serverConfig.encoding = 'cesu8'; + // @ts-expect-error: TODO: implement typings + conn.addCommand( + new TestAuthSwitchHandshake({ + pluginName: 'auth_test_plugin', + pluginData: Buffer.from('f{tU-{K@BhfHt/-4^Z,'), + }) + ); + }); -portfinder.getPort((_err: Error | null, port: number) => { - const makeSwitchHandler = function () { - let count = 0; - return function ( - data: { pluginName: string; pluginData: Buffer }, - cb: (err: null, response: string) => void - ) { - if (count === 0) { - assert.equal(data.pluginName, 'auth_test_plugin'); - } else { - assert.equal(data.pluginData.toString(), `hahaha ${count}`); - } - - count++; - cb(null, `some data back${count}`); - }; - }; - - server.listen(port); - const conn = mysql.createConnection({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - authSwitchHandler: makeSwitchHandler(), - connectAttributes: connectAttributes, + await new Promise((resolve) => { + portfinder.getPort((_err: Error | null, port: number) => { + const makeSwitchHandler = function () { + let count = 0; + return function ( + data: { pluginName: string; pluginData: Buffer }, + cb: (err: null, response: string) => void + ) { + if (count === 0) { + assert.equal(data.pluginName, 'auth_test_plugin'); + } else { + assert.equal(data.pluginData.toString(), `hahaha ${count}`); + } + + count++; + cb(null, `some data back${count}`); + }; + }; + + server.listen(port); + const conn = mysql.createConnection({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + authSwitchHandler: makeSwitchHandler(), + connectAttributes: connectAttributes, + }); + + conn.on( + 'connect', + (data: { serverVersion: string; connectionId: number }) => { + assert.equal(data.serverVersion, 'node.js rocks'); + assert.equal(data.connectionId, 1234); + + conn.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + } + ); + }); + }); }); - - conn.on( - 'connect', - (data: { serverVersion: string; connectionId: number }) => { - assert.equal(data.serverVersion, 'node.js rocks'); - assert.equal(data.connectionId, 1234); - - conn.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - } - ); }); diff --git a/test/esm/integration/test-handshake-unknown-packet-error.test.mts b/test/esm/integration/test-handshake-unknown-packet-error.test.mts index d753138af5..e31d7e81fd 100644 --- a/test/esm/integration/test-handshake-unknown-packet-error.test.mts +++ b/test/esm/integration/test-handshake-unknown-packet-error.test.mts @@ -1,8 +1,8 @@ // Copyright (c) 2021, Oracle and/or its affiliates. -import assert from 'node:assert'; import { Buffer } from 'node:buffer'; import process from 'node:process'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; import Command from '../../../lib/commands/command.js'; @@ -56,40 +56,43 @@ class TestUnknownHandshakePacket extends Command { } } -const server = mysql.createServer((conn) => { - // @ts-expect-error: TODO: implement typings - conn.addCommand(new TestUnknownHandshakePacket(Buffer.alloc(0))); -}); +await describe('Handshake Unknown Packet Error', async () => { + await it('should handle unknown handshake packet error', async () => { + let error: { code?: string; message?: string; fatal?: boolean }; -let error: { code?: string; message?: string; fatal?: boolean }; -let uncaughtExceptions = 0; + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + const server = mysql.createServer((conn) => { + conn.on('error', (err: Error & { code?: string }) => { + // The server must close the connection + assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); -portfinder.getPort((_err, port) => { - server.listen(port); - const conn = mysql.createConnection({ - port: port, - }); + // The plugin reports a fatal error + assert.equal(error.code, 'HANDSHAKE_UNKNOWN_ERROR'); + assert.equal( + error.message, + 'Unexpected packet during handshake phase' + ); + assert.equal(error.fatal, true); + resolve(); + }); + // @ts-expect-error: TODO: implement typings + conn.addCommand(new TestUnknownHandshakePacket(Buffer.alloc(0))); + }); - conn.on('error', (err) => { - error = err; + server.listen(port); + const conn = mysql.createConnection({ + port: port, + }); - conn.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); -}); + conn.on('error', (err) => { + error = err; -process.on('uncaughtException', (err: Error & { code?: string }) => { - // The plugin reports a fatal error - assert.equal(error.code, 'HANDSHAKE_UNKNOWN_ERROR'); - assert.equal(error.message, 'Unexpected packet during handshake phase'); - assert.equal(error.fatal, true); - // The server must close the connection - assert.equal(err.code, 'PROTOCOL_CONNECTION_LOST'); - - uncaughtExceptions += 1; -}); - -process.on('exit', () => { - assert.equal(uncaughtExceptions, 1); + conn.end(); + // @ts-expect-error: TODO: implement typings + server.close(); + }); + }); + }); + }); }); diff --git a/test/esm/integration/test-multi-result-streaming.test.mts b/test/esm/integration/test-multi-result-streaming.test.mts index 753170f101..7707d82517 100644 --- a/test/esm/integration/test-multi-result-streaming.test.mts +++ b/test/esm/integration/test-multi-result-streaming.test.mts @@ -1,52 +1,56 @@ import type { RowDataPacket } from '../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../common.test.mjs'; -const conn = createConnection({ multipleStatements: true }); -const captured1: RowDataPacket[] = []; -const captured2: RowDataPacket[] = []; -const sql1 = - 'select * from information_schema.columns order by table_schema, table_name, column_name limit 1;'; -const sql2 = - 'select * from information_schema.columns order by table_schema, table_name, ordinal_position limit 1;'; - -await conn.promise().query('set global max_allowed_packet=524288000'); - -const compare1 = await conn.promise().query(sql1); -const compare2 = await conn.promise().query(sql2); - -if (!compare1 || compare1.length < 1) { - assert.fail('no results for comparison 1'); -} -if (!compare2 || compare2.length < 1) { - assert.fail('no results for comparison 2'); -} - -const stream = conn.query(`${sql1}\n${sql2}`).stream(); -stream.on('result', (row: RowDataPacket, datasetIndex: number) => { - if (datasetIndex === 0) { - captured1.push(row); - } else { - captured2.push(row); - } -}); -// note: this is very important: -// after each result set is complete, -// the stream will emit "readable" and if we don't -// read then 'end' won't be emitted and the -// test will hang. -stream.on('readable', () => { - stream.read(); -}); +await describe('Multi Result Streaming', async () => { + const conn = createConnection({ multipleStatements: true }); + const captured1: RowDataPacket[] = []; + const captured2: RowDataPacket[] = []; + const sql1 = + 'select * from information_schema.columns order by table_schema, table_name, column_name limit 1;'; + const sql2 = + 'select * from information_schema.columns order by table_schema, table_name, ordinal_position limit 1;'; -await new Promise((resolve, reject) => { - stream.on('error', (e: Error) => reject(e)); - stream.on('end', () => resolve()); -}); + await conn.promise().query('set global max_allowed_packet=524288000'); -assert.equal(captured1.length, 1); -assert.equal(captured2.length, 1); -assert.deepEqual(captured1[0], (compare1[0] as RowDataPacket[])[0]); -assert.deepEqual(captured2[0], (compare2[0] as RowDataPacket[])[0]); + const compare1 = await conn.promise().query(sql1); + const compare2 = await conn.promise().query(sql2); -conn.end(); + if (!compare1 || compare1.length < 1) { + assert.fail('no results for comparison 1'); + } + if (!compare2 || compare2.length < 1) { + assert.fail('no results for comparison 2'); + } + + await it('should stream multi-result sets correctly', async () => { + const stream = conn.query(`${sql1}\n${sql2}`).stream(); + stream.on('result', (row: RowDataPacket, datasetIndex: number) => { + if (datasetIndex === 0) { + captured1.push(row); + } else { + captured2.push(row); + } + }); + // note: this is very important: + // after each result set is complete, + // the stream will emit "readable" and if we don't + // read then 'end' won't be emitted and the + // test will hang. + stream.on('readable', () => { + stream.read(); + }); + + await new Promise((resolve, reject) => { + stream.on('error', (e: Error) => reject(e)); + stream.on('end', () => resolve()); + }); + + assert.equal(captured1.length, 1); + assert.equal(captured2.length, 1); + assert.deepEqual(captured1[0], (compare1[0] as RowDataPacket[])[0]); + assert.deepEqual(captured2[0], (compare2[0] as RowDataPacket[])[0]); + }); + + conn.end(); +}); diff --git a/test/esm/integration/test-pool-connect-error.test.mts b/test/esm/integration/test-pool-connect-error.test.mts index 0a9060fd68..db218d10c6 100644 --- a/test/esm/integration/test-pool-connect-error.test.mts +++ b/test/esm/integration/test-pool-connect-error.test.mts @@ -1,57 +1,61 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const server = mysql.createServer((conn) => { - conn.serverHandshake({ - protocolVersion: 10, - serverVersion: '5.6.10', - connectionId: 1234, - statusFlags: 2, - characterSet: 8, - capabilityFlags: 0xffffff, - // @ts-expect-error: TODO: implement typings - authCallback: function (_params, cb) { - cb(null, { message: 'too many connections', code: 1040 }); - }, - }); -}); +await describe('Pool Connect Error', async () => { + await it('should handle connection error in pool', async () => { + const server = mysql.createServer((conn) => { + conn.serverHandshake({ + protocolVersion: 10, + serverVersion: '5.6.10', + connectionId: 1234, + statusFlags: 2, + characterSet: 8, + capabilityFlags: 0xffffff, + // @ts-expect-error: TODO: implement typings + authCallback: function (_params, cb) { + cb(null, { message: 'too many connections', code: 1040 }); + }, + }); + }); -let err1: NodeJS.ErrnoException | undefined, - err2: NodeJS.ErrnoException | undefined; + let err1: NodeJS.ErrnoException | undefined; -portfinder.getPort((_err, port) => { - server.listen(port); - const conn = mysql.createConnection({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - }); - conn.on('error', (err) => { - err1 = err; - }); + await new Promise((resolve) => { + portfinder.getPort((_err, port) => { + server.listen(port); + const conn = mysql.createConnection({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + }); + conn.on('error', (err) => { + err1 = err; + }); - const pool = mysql.createPool({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - }); + const pool = mysql.createPool({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + }); - pool.query('test sql', (err) => { - err2 = err ?? undefined; - pool.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); -}); + pool.query('test sql', (err) => { + const err2 = err ?? undefined; + pool.end(); + // @ts-expect-error: TODO: implement typings + server.close(); -process.on('exit', () => { - assert.equal(err1?.errno, 1040); - assert.equal(err2?.errno, 1040); + assert.equal(err1?.errno, 1040); + assert.equal(err2?.errno, 1040); + resolve(); + }); + }); + }); + }); }); diff --git a/test/esm/integration/test-pool-disconnect.test.mts b/test/esm/integration/test-pool-disconnect.test.mts index a17e2b6b2b..9715d155ec 100644 --- a/test/esm/integration/test-pool-disconnect.test.mts +++ b/test/esm/integration/test-pool-disconnect.test.mts @@ -4,7 +4,7 @@ import type { RowDataPacket, } from '../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createPool } from '../common.test.mjs'; // planetscale does not support KILL, skipping this test @@ -14,60 +14,68 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const pool = createPool(); -const conn = createConnection({ multipleStatements: true }); -pool.config.connectionLimit = 5; +await describe('Pool Disconnect', async () => { + await it('should handle pool connection kills correctly', async () => { + const pool = createPool(); + const conn = createConnection({ multipleStatements: true }); + pool.config.connectionLimit = 5; -const numSelectToPerform = 10; -const tids: number[] = []; -let numSelects = 0; -let killCount = 0; + const numSelectToPerform = 10; + const tids: number[] = []; + let numSelects = 0; + let killCount = 0; -function kill() { - setTimeout(() => { - const id = tids.shift(); - if (typeof id !== 'undefined') { - // sleep required to give mysql time to close connection, - // and callback called after connection with id is really closed - conn.query('kill ?; select sleep(0.05)', [id], (err) => { - assert.ifError(err); - killCount++; - // TODO: this assertion needs to be fixed, after kill - // connection is removed from _allConnections but not at a point this callback is called - // - // assert.equal(pool._allConnections.length, tids.length); - }); - } else { - conn.end(); - pool.end(); - } - }, 5); -} + await new Promise((resolve, reject) => { + function kill() { + setTimeout(() => { + const id = tids.shift(); + if (typeof id !== 'undefined') { + // sleep required to give mysql time to close connection, + // and callback called after connection with id is really closed + conn.query('kill ?; select sleep(0.05)', [id], (err) => { + if (err) return reject(err); + killCount++; + // TODO: this assertion needs to be fixed, after kill + // connection is removed from _allConnections but not at a point this callback is called + // + // assert.equal(pool._allConnections.length, tids.length); + }); + } else { + // conn.end waits for pending queries to complete, + // ensuring the last kill callback (killCount++) has fired + conn.end(() => { + pool.end(); + resolve(); + }); + } + }, 5); + } -pool.on('connection', (conn: PoolConnection) => { - tids.push(conn.threadId); - conn.on('error', () => { - setTimeout(kill, 5); - }); -}); + pool.on('connection', (conn: PoolConnection) => { + tids.push(conn.threadId); + conn.on('error', () => { + setTimeout(kill, 5); + }); + }); -for (let i = 0; i < numSelectToPerform; i++) { - pool.query( - 'select 1 as value', - (err: QueryError | null, rows: RowDataPacket[]) => { - numSelects++; - assert.ifError(err); - assert.equal(rows[0].value, 1); + for (let i = 0; i < numSelectToPerform; i++) { + pool.query( + 'select 1 as value', + (err: QueryError | null, rows: RowDataPacket[]) => { + numSelects++; + if (err) return reject(err); + assert.equal(rows[0].value, 1); - // after all queries complete start killing connections - if (numSelects === numSelectToPerform) { - kill(); + // after all queries complete start killing connections + if (numSelects === numSelectToPerform) { + kill(); + } + } + ); } - } - ); -} + }); -process.on('exit', () => { - assert.equal(numSelects, numSelectToPerform); - assert.equal(killCount, pool.config.connectionLimit); + assert.equal(numSelects, numSelectToPerform); + assert.equal(killCount, pool.config.connectionLimit); + }); }); diff --git a/test/esm/integration/test-pool-end.test.mts b/test/esm/integration/test-pool-end.test.mts index d9ce84ff5f..92b58899fa 100644 --- a/test/esm/integration/test-pool-end.test.mts +++ b/test/esm/integration/test-pool-end.test.mts @@ -1,25 +1,32 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../common.test.mjs'; -const pool = createPool(); +await describe('Pool End', async () => { + await it('should handle pool end correctly', async () => { + const pool = createPool(); -pool.getConnection((err, conn) => { - assert.ifError(err); + await new Promise((resolve, reject) => { + pool.getConnection((err, conn) => { + if (err) return reject(err); - // @ts-expect-error: internal access - assert(pool._allConnections.length === 1); - // @ts-expect-error: internal access - assert(pool._freeConnections.length === 0); + // @ts-expect-error: internal access + assert(pool._allConnections.length === 1); + // @ts-expect-error: internal access + assert(pool._freeConnections.length === 0); - // emit the end event, so the connection gets removed from the pool - // @ts-expect-error: internal access - conn.stream.emit('end'); + // emit the end event, so the connection gets removed from the pool + // @ts-expect-error: internal access + conn.stream.emit('end'); - // @ts-expect-error: internal access - assert(pool._allConnections.length === 0); - // @ts-expect-error: internal access - assert(pool._freeConnections.length === 0); + // @ts-expect-error: internal access + assert(pool._allConnections.length === 0); + // @ts-expect-error: internal access + assert(pool._freeConnections.length === 0); - // As the connection has not really ended we need to do this ourselves - conn.destroy(); + // As the connection has not really ended we need to do this ourselves + conn.destroy(); + resolve(); + }); + }); + }); }); diff --git a/test/esm/integration/test-pool-release-idle-connection-replicate.test.mts b/test/esm/integration/test-pool-release-idle-connection-replicate.test.mts index 2a2b33243b..bee2f81a9d 100644 --- a/test/esm/integration/test-pool-release-idle-connection-replicate.test.mts +++ b/test/esm/integration/test-pool-release-idle-connection-replicate.test.mts @@ -1,5 +1,5 @@ import type { PoolConnection } from '../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../common.test.mjs'; /** @@ -13,69 +13,82 @@ import { createPool } from '../common.test.mjs'; * @see https://github.com/sidorares/node-mysql2/issues/3020 */ -/** - * This test case - */ -const pool = createPool({ - connectionLimit: 3, - maxIdle: 2, - idleTimeout: 1000, -}); - -/** - * Create the first connection and ensure it's in the pool as expected - */ -pool.getConnection( - (err1: NodeJS.ErrnoException | null, connection1: PoolConnection) => { - assert.ifError(err1); - assert.ok(connection1); - +await describe('Pool Release Idle Connection Replicate', async () => { + await it('should destroy idle connections after timeout', async () => { /** - * Create the second connection and ensure it's in the pool as expected + * This test case */ - pool.getConnection( - (err2: NodeJS.ErrnoException | null, connection2: PoolConnection) => { - assert.ifError(err2); - assert.ok(connection2); + const pool = createPool({ + connectionLimit: 3, + maxIdle: 2, + idleTimeout: 1000, + }); - /** - * Create the third connection and ensure it's in the pool as expected - */ - pool.getConnection( - (err3: NodeJS.ErrnoException | null, connection3: PoolConnection) => { - assert.ifError(err3); - assert.ok(connection3); + await new Promise((resolve, reject) => { + /** + * Create the first connection and ensure it's in the pool as expected + */ + pool.getConnection( + (err1: NodeJS.ErrnoException | null, connection1: PoolConnection) => { + if (err1) return reject(err1); + assert.ok(connection1); - /** - * Release all the connections - */ - connection1.release(); - connection2.release(); - connection3.release(); + /** + * Create the second connection and ensure it's in the pool as expected + */ + pool.getConnection( + ( + err2: NodeJS.ErrnoException | null, + connection2: PoolConnection + ) => { + if (err2) return reject(err2); + assert.ok(connection2); - /** - * After the idle timeout has passed, check that all items in the in the pool - * that have been released are destroyed as expected. - */ - setTimeout(() => { - assert( - // @ts-expect-error: internal access - pool._allConnections.length === 0, - // @ts-expect-error: internal access - `Expected all connections to be closed, but found ${pool._allConnections.length}` - ); - assert( - // @ts-expect-error: internal access - pool._freeConnections.length === 0, - // @ts-expect-error: internal access - `Expected all free connections to be closed, but found ${pool._freeConnections.length}` - ); + /** + * Create the third connection and ensure it's in the pool as expected + */ + pool.getConnection( + ( + err3: NodeJS.ErrnoException | null, + connection3: PoolConnection + ) => { + if (err3) return reject(err3); + assert.ok(connection3); + + /** + * Release all the connections + */ + connection1.release(); + connection2.release(); + connection3.release(); - pool.end(); - }, 5000); - } - ); - } - ); - } -); + /** + * After the idle timeout has passed, check that all items in the in the pool + * that have been released are destroyed as expected. + */ + setTimeout(() => { + assert( + // @ts-expect-error: internal access + pool._allConnections.length === 0, + // @ts-expect-error: internal access + `Expected all connections to be closed, but found ${pool._allConnections.length}` + ); + assert( + // @ts-expect-error: internal access + pool._freeConnections.length === 0, + // @ts-expect-error: internal access + `Expected all free connections to be closed, but found ${pool._freeConnections.length}` + ); + + pool.end(); + resolve(); + }, 5000); + } + ); + } + ); + } + ); + }); + }); +}); diff --git a/test/esm/integration/test-pool-release-idle-connection-timeout.test.mts b/test/esm/integration/test-pool-release-idle-connection-timeout.test.mts index cae8841057..c2639fa829 100644 --- a/test/esm/integration/test-pool-release-idle-connection-timeout.test.mts +++ b/test/esm/integration/test-pool-release-idle-connection-timeout.test.mts @@ -1,62 +1,75 @@ import type { PoolConnection } from '../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../common.test.mjs'; -const pool = createPool({ - connectionLimit: 3, // 5 connections - maxIdle: 1, // 1 idle connection - idleTimeout: 1000, // remove idle connections after 1 second -}); +await describe('Pool Release Idle Connection Timeout', async () => { + await it('should destroy all idle connections after timeout', async () => { + const pool = createPool({ + connectionLimit: 3, + maxIdle: 1, + idleTimeout: 1000, + }); -pool.getConnection( - (err1: NodeJS.ErrnoException | null, connection1: PoolConnection) => { - assert.ifError(err1); - assert.ok(connection1); - pool.getConnection( - (err2: NodeJS.ErrnoException | null, connection2: PoolConnection) => { - assert.ifError(err2); - assert.ok(connection2); - assert.notStrictEqual(connection1, connection2); - pool.getConnection( - (err3: NodeJS.ErrnoException | null, connection3: PoolConnection) => { - assert.ifError(err3); - assert.ok(connection3); - assert.notStrictEqual(connection1, connection3); - assert.notStrictEqual(connection2, connection3); - connection1.release(); - connection2.release(); - connection3.release(); - // @ts-expect-error: internal access - assert(pool._allConnections.length === 3); - // @ts-expect-error: internal access - assert(pool._freeConnections.length === 3); - // after two seconds, the above 3 connection should have been destroyed - setTimeout(() => { - // @ts-expect-error: internal access - assert(pool._allConnections.length === 0); - // @ts-expect-error: internal access - assert(pool._freeConnections.length === 0); - // Creating a new connection should create a fresh one + await new Promise((resolve, reject) => { + pool.getConnection( + (err1: NodeJS.ErrnoException | null, connection1: PoolConnection) => { + if (err1) return reject(err1); + assert.ok(connection1); + pool.getConnection( + ( + err2: NodeJS.ErrnoException | null, + connection2: PoolConnection + ) => { + if (err2) return reject(err2); + assert.ok(connection2); + assert.notStrictEqual(connection1, connection2); pool.getConnection( ( - err4: NodeJS.ErrnoException | null, - connection4: PoolConnection + err3: NodeJS.ErrnoException | null, + connection3: PoolConnection ) => { - assert.ifError(err4); - assert.ok(connection4); + if (err3) return reject(err3); + assert.ok(connection3); + assert.notStrictEqual(connection1, connection3); + assert.notStrictEqual(connection2, connection3); + connection1.release(); + connection2.release(); + connection3.release(); // @ts-expect-error: internal access - assert(pool._allConnections.length === 1); + assert(pool._allConnections.length === 3); // @ts-expect-error: internal access - assert(pool._freeConnections.length === 0); - connection4.release(); - connection4.destroy(); - pool.end(); + assert(pool._freeConnections.length === 3); + // after two seconds, the above 3 connection should have been destroyed + setTimeout(() => { + // @ts-expect-error: internal access + assert(pool._allConnections.length === 0); + // @ts-expect-error: internal access + assert(pool._freeConnections.length === 0); + // Creating a new connection should create a fresh one + pool.getConnection( + ( + err4: NodeJS.ErrnoException | null, + connection4: PoolConnection + ) => { + if (err4) return reject(err4); + assert.ok(connection4); + // @ts-expect-error: internal access + assert(pool._allConnections.length === 1); + // @ts-expect-error: internal access + assert(pool._freeConnections.length === 0); + connection4.release(); + connection4.destroy(); + pool.end(); + resolve(); + } + ); + }, 2000); } ); - }, 2000); - } - ); - } - ); - } -); + } + ); + } + ); + }); + }); +}); diff --git a/test/esm/integration/test-pool-release-idle-connection.test.mts b/test/esm/integration/test-pool-release-idle-connection.test.mts index 5a567c239c..c998101d28 100644 --- a/test/esm/integration/test-pool-release-idle-connection.test.mts +++ b/test/esm/integration/test-pool-release-idle-connection.test.mts @@ -1,63 +1,76 @@ import type { PoolConnection } from '../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../common.test.mjs'; -const pool = createPool({ - connectionLimit: 5, // 5 connections - maxIdle: 1, // 1 idle connection - idleTimeout: 5000, // 5 seconds -}); +await describe('Pool Release Idle Connection', async () => { + await it('should release idle connections after timeout', async () => { + const pool = createPool({ + connectionLimit: 5, + maxIdle: 1, + idleTimeout: 5000, + }); -pool.getConnection( - (err1: NodeJS.ErrnoException | null, connection1: PoolConnection) => { - assert.ifError(err1); - assert.ok(connection1); - pool.getConnection( - (err2: NodeJS.ErrnoException | null, connection2: PoolConnection) => { - assert.ifError(err2); - assert.ok(connection2); - assert.notStrictEqual(connection1, connection2); - pool.getConnection( - (err3: NodeJS.ErrnoException | null, connection3: PoolConnection) => { - assert.ifError(err3); - assert.ok(connection3); - assert.notStrictEqual(connection1, connection3); - assert.notStrictEqual(connection2, connection3); - connection1.release(); - connection2.release(); - connection3.release(); - // @ts-expect-error: internal access - assert(pool._allConnections.length === 3); - // @ts-expect-error: internal access - assert(pool._freeConnections.length === 3); - setTimeout(() => { - // @ts-expect-error: internal access - assert(pool._allConnections.length === 1); - // @ts-expect-error: internal access - assert(pool._freeConnections.length === 1); + await new Promise((resolve, reject) => { + pool.getConnection( + (err1: NodeJS.ErrnoException | null, connection1: PoolConnection) => { + if (err1) return reject(err1); + assert.ok(connection1); + pool.getConnection( + ( + err2: NodeJS.ErrnoException | null, + connection2: PoolConnection + ) => { + if (err2) return reject(err2); + assert.ok(connection2); + assert.notStrictEqual(connection1, connection2); pool.getConnection( ( - err4: NodeJS.ErrnoException | null, - connection4: PoolConnection + err3: NodeJS.ErrnoException | null, + connection3: PoolConnection ) => { - assert.ifError(err4); - assert.ok(connection4); - assert.strictEqual(connection3, connection4); + if (err3) return reject(err3); + assert.ok(connection3); + assert.notStrictEqual(connection1, connection3); + assert.notStrictEqual(connection2, connection3); + connection1.release(); + connection2.release(); + connection3.release(); // @ts-expect-error: internal access - assert(pool._allConnections.length === 1); + assert(pool._allConnections.length === 3); // @ts-expect-error: internal access - assert(pool._freeConnections.length === 0); - connection4.release(); - connection4.destroy(); - pool.end(); + assert(pool._freeConnections.length === 3); + setTimeout(() => { + // @ts-expect-error: internal access + assert(pool._allConnections.length === 1); + // @ts-expect-error: internal access + assert(pool._freeConnections.length === 1); + pool.getConnection( + ( + err4: NodeJS.ErrnoException | null, + connection4: PoolConnection + ) => { + if (err4) return reject(err4); + assert.ok(connection4); + assert.strictEqual(connection3, connection4); + // @ts-expect-error: internal access + assert(pool._allConnections.length === 1); + // @ts-expect-error: internal access + assert(pool._freeConnections.length === 0); + connection4.release(); + connection4.destroy(); + pool.end(); + resolve(); + } + ); + // Setting the time to a lower value than idleTimeout will ensure that the connection is not considered idle + // during our assertions + }, 4000); } ); - // Setting the time to a lower value than idleTimeout will ensure that the connection is not considered idle - // during our assertions - }, 4000); - } - ); - } - ); - } -); + } + ); + } + ); + }); + }); +}); diff --git a/test/esm/integration/test-pool-release.test.mts b/test/esm/integration/test-pool-release.test.mts index 717c962791..aecd79bd59 100644 --- a/test/esm/integration/test-pool-release.test.mts +++ b/test/esm/integration/test-pool-release.test.mts @@ -1,32 +1,39 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPool } from '../common.test.mjs'; -const pool = createPool({ - idleTimeout: 15000, -}); +await describe('Pool Release', async () => { + await it('should release connections back to the pool', async () => { + const pool = createPool({ + idleTimeout: 15000, + }); -pool.query('test sql', () => { - pool.query('test sql', [], () => { - pool.query('test sql', [], () => { - pool.query('test sql', [], () => { - pool.query('test sql', () => { - pool.query('test sql').on('error', () => { - pool.query('test sql', () => { - pool.execute('test sql', () => { - pool.execute('test sql', () => { - pool.execute('test sql', [], () => { + await new Promise((resolve) => { + pool.query('test sql', () => { + pool.query('test sql', [], () => { + pool.query('test sql', [], () => { + pool.query('test sql', [], () => { + pool.query('test sql', () => { + pool.query('test sql').on('error', () => { + pool.query('test sql', () => { pool.execute('test sql', () => { pool.execute('test sql', () => { - // TODO change order events are fires so that connection is released before callback - // that way this number will be more deterministic - // @ts-expect-error: internal access - assert(pool._allConnections.length < 3); - // on some setups with small CLIENT_INTERACTION_TIMEOUT value connection might be closed by the time we get here, hence "one or zero" - // @ts-expect-error: internal access - assert(pool._freeConnections.length <= 1); - // @ts-expect-error: internal access - assert(pool._connectionQueue.length === 0); - pool.end(); + pool.execute('test sql', [], () => { + pool.execute('test sql', () => { + pool.execute('test sql', () => { + // TODO change order events are fires so that connection is released before callback + // that way this number will be more deterministic + // @ts-expect-error: internal access + assert(pool._allConnections.length < 3); + // on some setups with small CLIENT_INTERACTION_TIMEOUT value connection might be closed by the time we get here, hence "one or zero" + // @ts-expect-error: internal access + assert(pool._freeConnections.length <= 1); + // @ts-expect-error: internal access + assert(pool._connectionQueue.length === 0); + pool.end(); + resolve(); + }); + }); + }); }); }); }); diff --git a/test/esm/integration/test-pool.test.mts b/test/esm/integration/test-pool.test.mts index dc3ef9a75c..31586a9aae 100644 --- a/test/esm/integration/test-pool.test.mts +++ b/test/esm/integration/test-pool.test.mts @@ -6,15 +6,19 @@ const poolConfig = {}; // config: { connectionConfig: {} }; const pool = mysql.createPool(poolConfig); describe('Pool methods tests', () => { - // @ts-expect-error: TODO: implement typings - assert.equal(pool.escape(123), '123', 'escape method works correctly'); - - assert.equal( + it(() => { // @ts-expect-error: TODO: implement typings - pool.escapeId('table name'), - '`table name`', - 'escapeId method works correctly' - ); + assert.equal(pool.escape(123), '123', 'escape method works correctly'); + }); + + it(() => { + assert.equal( + // @ts-expect-error: TODO: implement typings + pool.escapeId('table name'), + '`table name`', + 'escapeId method works correctly' + ); + }); it(() => { const params = ['table name', 'thing']; @@ -30,17 +34,21 @@ describe('Pool methods tests', () => { const poolDotPromise = pool.promise(); describe('Pool.promise() methods tests', () => { - assert.equal( - poolDotPromise.escape(123), - '123', - 'promise escape method works correctly' - ); + it(() => { + assert.equal( + poolDotPromise.escape(123), + '123', + 'promise escape method works correctly' + ); + }); - assert.equal( - poolDotPromise.escapeId('table name'), - '`table name`', - 'promise escapeId method works correctly' - ); + it(() => { + assert.equal( + poolDotPromise.escapeId('table name'), + '`table name`', + 'promise escapeId method works correctly' + ); + }); it(() => { const params = ['table name', 'thing']; @@ -56,17 +64,21 @@ describe('Pool.promise() methods tests', () => { const promisePool = mysql.createPoolPromise(poolConfig); describe('PromisePool methods tests', () => { - assert.equal( - promisePool.escape(123), - '123', - 'PromisePool escape method works correctly' - ); + it(() => { + assert.equal( + promisePool.escape(123), + '123', + 'PromisePool escape method works correctly' + ); + }); - assert.equal( - promisePool.escapeId('table name'), - '`table name`', - 'PromisePool escapeId method works correctly' - ); + it(() => { + assert.equal( + promisePool.escapeId('table name'), + '`table name`', + 'PromisePool escapeId method works correctly' + ); + }); it(() => { const params = ['table name', 'thing']; diff --git a/test/esm/integration/test-rows-as-array.test.mts b/test/esm/integration/test-rows-as-array.test.mts index 112d02a538..d90715b395 100644 --- a/test/esm/integration/test-rows-as-array.test.mts +++ b/test/esm/integration/test-rows-as-array.test.mts @@ -1,67 +1,87 @@ import type { QueryError, RowDataPacket } from '../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../common.test.mjs'; -// enabled in initial config, disable in some tets -const c = createConnection({ rowsAsArray: true }); -c.query('select 1+1 as a', (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0][0], 2); -}); +await describe('Rows As Array', async () => { + // enabled in initial config, disable in some tets + await it('should return rows as arrays when enabled', async () => { + const c = createConnection({ rowsAsArray: true }); -c.query( - { sql: 'select 1+2 as a', rowsAsArray: false }, - (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0].a, 3); - } -); + await new Promise((resolve, reject) => { + c.query( + 'select 1+1 as a', + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0][0], 2); + } + ); -c.execute( - 'select 1+1 as a', - (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0][0], 2); - } -); + c.query( + { sql: 'select 1+2 as a', rowsAsArray: false }, + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0].a, 3); + } + ); -c.execute( - { sql: 'select 1+2 as a', rowsAsArray: false }, - (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0].a, 3); - c.end(); - } -); + c.execute( + 'select 1+1 as a', + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0][0], 2); + } + ); -// disabled in initial config, enable in some tets -const c1 = createConnection({ rowsAsArray: false }); -c1.query('select 1+1 as a', (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0].a, 2); -}); + c.execute( + { sql: 'select 1+2 as a', rowsAsArray: false }, + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0].a, 3); + c.end(); + resolve(); + } + ); + }); + }); + + // disabled in initial config, enable in some tets + await it('should return rows as objects when disabled', async () => { + const c1 = createConnection({ rowsAsArray: false }); -c1.query( - { sql: 'select 1+2 as a', rowsAsArray: true }, - (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0][0], 3); - } -); + await new Promise((resolve, reject) => { + c1.query( + 'select 1+1 as a', + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0].a, 2); + } + ); -c1.execute( - 'select 1+1 as a', - (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0].a, 2); - } -); + c1.query( + { sql: 'select 1+2 as a', rowsAsArray: true }, + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0][0], 3); + } + ); -c1.execute( - { sql: 'select 1+2 as a', rowsAsArray: true }, - (err: QueryError | null, rows: RowDataPacket[]) => { - assert.ifError(err); - assert.equal(rows[0][0], 3); - c1.end(); - } -); + c1.execute( + 'select 1+1 as a', + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0].a, 2); + } + ); + + c1.execute( + { sql: 'select 1+2 as a', rowsAsArray: true }, + (err: QueryError | null, rows: RowDataPacket[]) => { + if (err) return reject(err); + assert.equal(rows[0][0], 3); + c1.end(); + resolve(); + } + ); + }); + }); +}); diff --git a/test/esm/integration/test-server-close.test.mts b/test/esm/integration/test-server-close.test.mts index 9811fc9697..d91b8c4a97 100644 --- a/test/esm/integration/test-server-close.test.mts +++ b/test/esm/integration/test-server-close.test.mts @@ -1,8 +1,8 @@ // Copyright (c) 2021, Oracle and/or its affiliates. import type { QueryError } from '../../../index.js'; -import assert from 'node:assert'; import process from 'node:process'; +import { assert, describe, it } from 'poku'; import errors from '../../../lib/constants/errors.js'; import { createConnection } from '../common.test.mjs'; @@ -14,41 +14,37 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { // Uncaught AssertionError: Connection lost: The server closed the connection. == The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior. if (typeof Deno !== 'undefined') process.exit(0); -const connection = createConnection(); - -const customWaitTimeout = 1; // seconds - -let error: QueryError; - -connection.on('error', (err) => { - error = err as QueryError; - - // @ts-expect-error: TODO: implement typings - connection.close(); -}); - -connection.query(`set wait_timeout=${customWaitTimeout}`, () => { - setTimeout(() => {}, customWaitTimeout * 1000 * 2); -}); - -process.on('uncaughtException', (err) => { - // The ERR Packet is only sent by MySQL server 8.0.24 or higher, so we - // need to account for the fact it is not sent by older server versions. - if ((err as NodeJS.ErrnoException).code !== 'ERR_ASSERTION') { - throw err; - } - - assert.equal( - error.message, - 'Connection lost: The server closed the connection.' - ); - assert.equal(error.code, 'PROTOCOL_CONNECTION_LOST'); -}); - -process.on('exit', () => { - assert.equal( - error.message, - 'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.' - ); - assert.equal(error.code, errors.ER_CLIENT_INTERACTION_TIMEOUT); +await describe('Server Close', async () => { + await it('should detect server-initiated connection close', async () => { + const connection = createConnection(); + const customWaitTimeout = 1; + + await new Promise((resolve) => { + connection.on('error', (error: QueryError) => { + // @ts-expect-error: TODO: implement typings + connection.close(); + + // The ERR Packet is only sent by MySQL server 8.0.24 or higher, so we + // need to account for the fact it is not sent by older server versions. + if (Number(error.code) === errors.ER_CLIENT_INTERACTION_TIMEOUT) { + assert.equal( + error.message, + 'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.' + ); + } else { + assert.equal( + error.message, + 'Connection lost: The server closed the connection.' + ); + assert.equal(error.code, 'PROTOCOL_CONNECTION_LOST'); + } + + resolve(); + }); + + connection.query(`set wait_timeout=${customWaitTimeout}`, () => { + setTimeout(() => {}, customWaitTimeout * 1000 * 2); + }); + }); + }); }); diff --git a/test/esm/unit/commands/test-query.test.mts b/test/esm/unit/commands/test-query.test.mts index 0f00ec1f39..e2aeb084f9 100644 --- a/test/esm/unit/commands/test-query.test.mts +++ b/test/esm/unit/commands/test-query.test.mts @@ -1,18 +1,22 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import Query from '../../../../lib/commands/query.js'; -const testError = new Error('something happened'); -const testQuery = new Query({}, (err: Error | null, res: unknown) => { - assert.equal(err, testError); - assert.equal(res, null); -}); +describe('Query command', () => { + it('should pass error to callback when row parser throws', () => { + const testError = new Error('something happened'); + const testQuery = new Query({}, (err: Error | null, res: unknown) => { + assert.equal(err, testError); + assert.equal(res, null); + }); -testQuery._rowParser = new (class FailingRowParser { - next() { - throw testError; - } -})(); + testQuery._rowParser = new (class FailingRowParser { + next() { + throw testError; + } + })(); -testQuery.row({ - isEOF: () => false, + testQuery.row({ + isEOF: () => false, + }); + }); }); diff --git a/test/esm/unit/commands/test-quit.test.mts b/test/esm/unit/commands/test-quit.test.mts index 5ccf78f8fb..b0151c7487 100644 --- a/test/esm/unit/commands/test-quit.test.mts +++ b/test/esm/unit/commands/test-quit.test.mts @@ -1,7 +1,11 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import Quit from '../../../../lib/commands/quit.js'; -const testCallback = (err: Error) => console.info(err.message); -const testQuit = new Quit(testCallback); +describe('Quit command', () => { + it('should store callback as onResult', () => { + const testCallback = (err: Error) => console.info(err.message); + const testQuit = new Quit(testCallback); -assert.strictEqual(testQuit.onResult, testCallback); + assert.strictEqual(testQuit.onResult, testCallback); + }); +}); diff --git a/test/esm/unit/connection/test-connection_config.test.mts b/test/esm/unit/connection/test-connection_config.test.mts index d8313f4ca5..a2443305b4 100644 --- a/test/esm/unit/connection/test-connection_config.test.mts +++ b/test/esm/unit/connection/test-connection_config.test.mts @@ -1,76 +1,100 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import ConnectionConfig from '../../../../lib/connection_config.js'; import SSLProfiles from '../../../../lib/constants/ssl_profiles.js'; -const expectedMessage = "SSL profile must be an object, instead it's a boolean"; +describe('ConnectionConfig', () => { + it('should throw on boolean ssl', () => { + const expectedMessage = + "SSL profile must be an object, instead it's a boolean"; -assert.throws( - () => - new ConnectionConfig({ - ssl: true, - }), - (err: unknown) => err instanceof TypeError && err.message === expectedMessage, - 'Error, the constructor accepts a boolean without throwing the right exception' -); + assert.throws( + () => + new ConnectionConfig({ + ssl: true, + }), + (err: unknown) => + err instanceof TypeError && err.message === expectedMessage, + 'Error, the constructor accepts a boolean without throwing the right exception' + ); + }); -assert.doesNotThrow( - () => - new ConnectionConfig({ - ssl: {}, - }), - 'Error, the constructor accepts an object but throws an exception' -); + it('should accept object ssl', () => { + assert.doesNotThrow( + () => + new ConnectionConfig({ + ssl: {}, + }), + 'Error, the constructor accepts an object but throws an exception' + ); + }); -assert.doesNotThrow(() => { - const sslProfile = Object.keys(SSLProfiles)[0]; - new ConnectionConfig({ - ssl: sslProfile, + it('should accept string ssl profile', () => { + assert.doesNotThrow(() => { + const sslProfile = Object.keys(SSLProfiles)[0]; + new ConnectionConfig({ + ssl: sslProfile, + }); + }, 'Error, the constructor accepts a string but throws an exception'); }); -}, 'Error, the constructor accepts a string but throws an exception'); -assert.doesNotThrow(() => { - new ConnectionConfig({ - flags: '-FOUND_ROWS', + it('should accept flags string', () => { + assert.doesNotThrow(() => { + new ConnectionConfig({ + flags: '-FOUND_ROWS', + }); + }, 'Error, the constructor threw an exception due to a flags string'); }); -}, 'Error, the constructor threw an exception due to a flags string'); -assert.doesNotThrow(() => { - new ConnectionConfig({ - flags: ['-FOUND_ROWS'], + it('should accept flags array', () => { + assert.doesNotThrow(() => { + new ConnectionConfig({ + flags: ['-FOUND_ROWS'], + }); + }, 'Error, the constructor threw an exception due to a flags array'); }); -}, 'Error, the constructor threw an exception due to a flags array'); -assert.strictEqual( - ConnectionConfig.parseUrl( - String.raw`fml://test:pass!%40%24%25%5E%26*()word%3A@www.example.com/database` - ).password, - 'pass!@$%^&*()word:' -); + it('should parse password from URL', () => { + assert.strictEqual( + ConnectionConfig.parseUrl( + String.raw`fml://test:pass!%40%24%25%5E%26*()word%3A@www.example.com/database` + ).password, + 'pass!@$%^&*()word:' + ); + }); -assert.strictEqual( - ConnectionConfig.parseUrl( - String.raw`fml://user%40test.com:pass!%40%24%25%5E%26*()word%3A@www.example.com/database` - ).user, - 'user@test.com' -); + it('should parse user from URL', () => { + assert.strictEqual( + ConnectionConfig.parseUrl( + String.raw`fml://user%40test.com:pass!%40%24%25%5E%26*()word%3A@www.example.com/database` + ).user, + 'user@test.com' + ); + }); -assert.strictEqual( - ConnectionConfig.parseUrl( - String.raw`fml://test:pass@wordA@fe80%3A3438%3A7667%3A5c77%3Ace27%2518/database` - ).host, - 'fe80:3438:7667:5c77:ce27%18' -); + it('should parse IPv6 host from URL', () => { + assert.strictEqual( + ConnectionConfig.parseUrl( + String.raw`fml://test:pass@wordA@fe80%3A3438%3A7667%3A5c77%3Ace27%2518/database` + ).host, + 'fe80:3438:7667:5c77:ce27%18' + ); + }); -assert.strictEqual( - ConnectionConfig.parseUrl( - String.raw`fml://test:pass@wordA@www.example.com/database` - ).host, - 'www.example.com' -); + it('should parse host from URL', () => { + assert.strictEqual( + ConnectionConfig.parseUrl( + String.raw`fml://test:pass@wordA@www.example.com/database` + ).host, + 'www.example.com' + ); + }); -assert.strictEqual( - ConnectionConfig.parseUrl( - String.raw`fml://test:pass@wordA@www.example.com/database%24` - ).database, - 'database$' -); + it('should parse database from URL', () => { + assert.strictEqual( + ConnectionConfig.parseUrl( + String.raw`fml://test:pass@wordA@www.example.com/database%24` + ).database, + 'database$' + ); + }); +}); diff --git a/test/esm/unit/packets/test-column-definition.test.mts b/test/esm/unit/packets/test-column-definition.test.mts index 64d64eda46..cd1c1f948d 100644 --- a/test/esm/unit/packets/test-column-definition.test.mts +++ b/test/esm/unit/packets/test-column-definition.test.mts @@ -1,81 +1,87 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import ColumnDefinition from '../../../../lib/packets/column_definition.js'; const sequenceId = 5; -// simple -let packet = ColumnDefinition.toPacket( - { - catalog: 'def', - schema: 'some_db', - name: 'some_col', - orgName: 'some_col', - table: 'some_tbl', - orgTable: 'some_tbl', +describe('ColumnDefinition', () => { + it('should serialize simple column definition', () => { + const packet = ColumnDefinition.toPacket( + { + catalog: 'def', + schema: 'some_db', + name: 'some_col', + orgName: 'some_col', + table: 'some_tbl', + orgTable: 'some_tbl', - characterSet: 0x21, - columnLength: 500, - flags: 32896, - columnType: 0x8, - decimals: 1, - }, - sequenceId -); -assert.equal( - packet.buffer.toString('hex', 4), - '0364656607736f6d655f646208736f6d655f74626c08736f6d655f74626c08736f6d655f636f6c08736f6d655f636f6c0c2100f4010000088080010000' -); + characterSet: 0x21, + columnLength: 500, + flags: 32896, + columnType: 0x8, + decimals: 1, + }, + sequenceId + ); + assert.equal( + packet.buffer.toString('hex', 4), + '0364656607736f6d655f646208736f6d655f74626c08736f6d655f74626c08736f6d655f636f6c08736f6d655f636f6c0c2100f4010000088080010000' + ); + }); -// Russian -packet = ColumnDefinition.toPacket( - { - catalog: 'def', - schema: 's_погоди', - name: 'n_погоди', - orgName: 'on_погоди', - table: 't_погоди', - orgTable: 'ot_погоди', - characterSet: 0x21, - columnLength: 500, - flags: 32896, - columnType: 0x8, - decimals: 1, - }, - sequenceId -); -assert.equal( - packet.buffer.toString('hex', 4), - '036465660e735fd0bfd0bed0b3d0bed0b4d0b80e745fd0bfd0bed0b3d0bed0b4d0b80f6f745fd0bfd0bed0b3d0bed0b4d0b80e6e5fd0bfd0bed0b3d0bed0b4d0b80f6f6e5fd0bfd0bed0b3d0bed0b4d0b80c2100f4010000088080010000' -); + it('should serialize Russian (unicode) column definition', () => { + const packet = ColumnDefinition.toPacket( + { + catalog: 'def', + schema: 's_погоди', + name: 'n_погоди', + orgName: 'on_погоди', + table: 't_погоди', + orgTable: 'ot_погоди', + characterSet: 0x21, + columnLength: 500, + flags: 32896, + columnType: 0x8, + decimals: 1, + }, + sequenceId + ); + assert.equal( + packet.buffer.toString('hex', 4), + '036465660e735fd0bfd0bed0b3d0bed0b4d0b80e745fd0bfd0bed0b3d0bed0b4d0b80f6f745fd0bfd0bed0b3d0bed0b4d0b80e6e5fd0bfd0bed0b3d0bed0b4d0b80f6f6e5fd0bfd0bed0b3d0bed0b4d0b80c2100f4010000088080010000' + ); + }); -// Spec (from example: https://dev.mysql.com/doc/internals/en/protocoltext-resultset.html) -const inputColDef = { - catalog: 'def', - schema: '', - name: '@@version_comment', - orgName: '', - table: '', - orgTable: '', + // Spec (from example: https://dev.mysql.com/doc/internals/en/protocoltext-resultset.html) + it('should serialize and deserialize spec example', () => { + const inputColDef = { + catalog: 'def', + schema: '', + name: '@@version_comment', + orgName: '', + table: '', + orgTable: '', - characterSet: 0x08, // latin1_swedish_ci - columnLength: 0x1c, - flags: 0, - columnType: 0xfd, - type: 0xfd, - encoding: 'latin1', - decimals: 0x1f, -}; -packet = ColumnDefinition.toPacket(inputColDef, sequenceId); -assert.equal( - packet.buffer.toString('hex', 4), - '0364656600000011404076657273696f6e5f636f6d6d656e74000c08001c000000fd00001f0000' -); + characterSet: 0x08, // latin1_swedish_ci + columnLength: 0x1c, + flags: 0, + columnType: 0xfd, + type: 0xfd, + encoding: 'latin1', + decimals: 0x1f, + }; + const packet = ColumnDefinition.toPacket(inputColDef, sequenceId); + assert.equal( + packet.buffer.toString('hex', 4), + '0364656600000011404076657273696f6e5f636f6d6d656e74000c08001c000000fd00001f0000' + ); -packet.offset = 4; -const colDef = new ColumnDefinition(packet, 'utf8'); -// inspect omits the "colulumnType" property because type is an alias for it -// but ColumnDefinition.toPacket reads type from "columnType" -// TODO: think how to make this more consistent -const inspect = { columnType: 253, ...colDef.inspect() }; -assert.deepEqual(inspect, inputColDef); -assert.equal(colDef.db, inputColDef.schema); + packet.offset = 4; + const colDef = new ColumnDefinition(packet, 'utf8'); + // inspect omits the "colulumnType" property because type is an alias for it + // but ColumnDefinition.toPacket reads type from "columnType" + // TODO: think how to make this more consistent + const inspect = { columnType: 253, ...colDef.inspect() }; + assert.deepEqual(inspect, inputColDef); + assert.equal(colDef.db, inputColDef.schema); + }); +}); diff --git a/test/esm/unit/packets/test-datetime.test.mts b/test/esm/unit/packets/test-datetime.test.mts index a03c9efc1f..1fcd0a5e34 100644 --- a/test/esm/unit/packets/test-datetime.test.mts +++ b/test/esm/unit/packets/test-datetime.test.mts @@ -1,28 +1,34 @@ import { Buffer } from 'node:buffer'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import Packets from '../../../../lib/packets/index.js'; -let buf = Buffer.from('0a000004000007dd070116010203', 'hex'); +describe('DateTime packet parsing', () => { + it('should parse a simple datetime packet', () => { + const buf = Buffer.from('0a000004000007dd070116010203', 'hex'); -let packet = new Packets.Packet(4, buf, 0, buf.length); -packet.readInt16(); // unused -let d = packet.readDateTime('Z'); -if (d === null) throw new Error('expected d to be non-null'); -assert.equal(+d, 1358816523000); + const packet = new Packets.Packet(4, buf, 0, buf.length); + packet.readInt16(); // unused + const d = packet.readDateTime('Z'); + if (d === null) throw new Error('expected d to be non-null'); + assert.equal(+d, 1358816523000); + }); -buf = Buffer.from( - '18000006000004666f6f310be00702090f01095d7f06000462617231', - 'hex' -); -packet = new Packets.Packet(6, buf, 0, buf.length); + it('should parse a datetime packet with mixed string fields', () => { + const buf = Buffer.from( + '18000006000004666f6f310be00702090f01095d7f06000462617231', + 'hex' + ); + const packet = new Packets.Packet(6, buf, 0, buf.length); -packet.readInt16(); // ignore -const s = packet.readLengthCodedString('cesu8'); -assert.equal(s, 'foo1'); -d = packet.readDateTime('Z'); -if (d === null) throw new Error('expected d to be non-null'); -assert.equal(+d, 1455030069425); + packet.readInt16(); // ignore + const s = packet.readLengthCodedString('cesu8'); + assert.equal(s, 'foo1'); + const d = packet.readDateTime('Z'); + if (d === null) throw new Error('expected d to be non-null'); + assert.equal(+d, 1455030069425); -const s1 = packet.readLengthCodedString('cesu8'); -assert.equal(s1, 'bar1'); -assert.equal(packet.offset, packet.end); + const s1 = packet.readLengthCodedString('cesu8'); + assert.equal(s1, 'bar1'); + assert.equal(packet.offset, packet.end); + }); +}); diff --git a/test/esm/unit/packets/test-ok-autoinc.test.mts b/test/esm/unit/packets/test-ok-autoinc.test.mts index e2d30a0d9a..94667dc969 100644 --- a/test/esm/unit/packets/test-ok-autoinc.test.mts +++ b/test/esm/unit/packets/test-ok-autoinc.test.mts @@ -1,14 +1,18 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import Packets from '../../../../lib/packets/index.js'; -const packet = Packets.OK.toPacket({ affectedRows: 0, insertId: 1 }); +describe('OK packet with auto-increment', () => { + it('should have correct length for 0 affectedRows and minimal insertId', () => { + const packet = Packets.OK.toPacket({ affectedRows: 0, insertId: 1 }); -// 5 bytes for an OK packet, plus one byte to store affectedRows plus one byte to store the insertId -assert.equal( - packet.length(), - 11, - `${ - 'OK packets with 0 affectedRows and a minimal insertId should be ' + - '11 bytes long, got ' - }${packet.length()} byte(s)` -); + // 5 bytes for an OK packet, plus one byte to store affectedRows plus one byte to store the insertId + assert.equal( + packet.length(), + 11, + `${ + 'OK packets with 0 affectedRows and a minimal insertId should be ' + + '11 bytes long, got ' + }${packet.length()} byte(s)` + ); + }); +}); diff --git a/test/esm/unit/packets/test-ok-sessiontrack.test.mts b/test/esm/unit/packets/test-ok-sessiontrack.test.mts index a23035f459..76f351c780 100644 --- a/test/esm/unit/packets/test-ok-sessiontrack.test.mts +++ b/test/esm/unit/packets/test-ok-sessiontrack.test.mts @@ -1,5 +1,5 @@ import { Buffer } from 'node:buffer'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import clientConstants from '../../../../lib/constants/client.js'; import Packet from '../../../../lib/packets/packet.js'; import ResultSetHeader from '../../../../lib/packets/resultset_header.js'; @@ -20,19 +20,25 @@ const mkpacket = (str: string) => { // regression examples from https://github.com/sidorares/node-mysql2/issues/989 -assert.doesNotThrow(() => { - const packet = mkpacket( - `1b 00 00 01 +describe('OK packet with session track', () => { + it('should parse session track packet (regression #989, case 1)', () => { + assert.doesNotThrow(() => { + const packet = mkpacket( + `1b 00 00 01 00 01 fe 65 96 fc 02 00 00 00 00 03 40 00 00 00 0a 14 08 fe 60 63 9b 05 00 00 00 ` - ); - new ResultSetHeader(packet, mockConnection); -}); + ); + new ResultSetHeader(packet, mockConnection); + }); + }); -assert.doesNotThrow(() => { - const packet = mkpacket( - `13 00 00 01 00 01 00 02 40 00 00 00 0a 14 08 fe 18 25 e7 06 00 00 00` - ); - new ResultSetHeader(packet, mockConnection); + it('should parse session track packet (regression #989, case 2)', () => { + assert.doesNotThrow(() => { + const packet = mkpacket( + `13 00 00 01 00 01 00 02 40 00 00 00 0a 14 08 fe 18 25 e7 06 00 00 00` + ); + new ResultSetHeader(packet, mockConnection); + }); + }); }); diff --git a/test/esm/unit/packets/test-text-row.test.mts b/test/esm/unit/packets/test-text-row.test.mts index e627c6c293..4e15d7e3f9 100644 --- a/test/esm/unit/packets/test-text-row.test.mts +++ b/test/esm/unit/packets/test-text-row.test.mts @@ -1,25 +1,30 @@ -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import TextRow from '../../../../lib/packets/text_row.js'; -// simple -let packet = TextRow.toPacket(['Hello', 'World'], 'cesu8'); -assert.equal(packet.buffer.toString('hex', 4), '0548656c6c6f05576f726c64'); +describe('TextRow', () => { + it('should serialize simple text row', () => { + const packet = TextRow.toPacket(['Hello', 'World'], 'cesu8'); + assert.equal(packet.buffer.toString('hex', 4), '0548656c6c6f05576f726c64'); + }); -// Russian (unicode) -packet = TextRow.toPacket(['Ну,', 'погоди!'], 'cesu8'); -assert.equal( - packet.buffer.toString('hex', 4), - '05d09dd1832c0dd0bfd0bed0b3d0bed0b4d0b821' -); + it('should serialize Russian (unicode) text row', () => { + const packet = TextRow.toPacket(['Ну,', 'погоди!'], 'cesu8'); + assert.equal( + packet.buffer.toString('hex', 4), + '05d09dd1832c0dd0bfd0bed0b3d0bed0b4d0b821' + ); + }); -// Long > 256 byte -packet = TextRow.toPacket( - [ - 'Пушкин родился 26 мая (6 июня) 1799 г. в Москве. В метрической книге церкви Богоявления в Елохове (сейчас на её месте находится Богоявленский собор в Елохове) на дату 8 июня 1799 г.', - ], - 'cesu8' -); -assert.equal( - packet.buffer.toString('hex', 4), - 'fc3801d09fd183d188d0bad0b8d0bd20d180d0bed0b4d0b8d0bbd181d18f20323620d0bcd0b0d18f20283620d0b8d18ed0bdd18f29203137393920d0b32e20d0b220d09cd0bed181d0bad0b2d0b52e20d09220d0bcd0b5d182d180d0b8d187d0b5d181d0bad0bed0b920d0bad0bdd0b8d0b3d0b520d186d0b5d180d0bad0b2d0b820d091d0bed0b3d0bed18fd0b2d0bbd0b5d0bdd0b8d18f20d0b220d095d0bbd0bed185d0bed0b2d0b52028d181d0b5d0b9d187d0b0d18120d0bdd0b020d0b5d19120d0bcd0b5d181d182d0b520d0bdd0b0d185d0bed0b4d0b8d182d181d18f20d091d0bed0b3d0bed18fd0b2d0bbd0b5d0bdd181d0bad0b8d0b920d181d0bed0b1d0bed18020d0b220d095d0bbd0bed185d0bed0b2d0b52920d0bdd0b020d0b4d0b0d182d183203820d0b8d18ed0bdd18f203137393920d0b32e' -); + it('should serialize long > 256 byte text row', () => { + const packet = TextRow.toPacket( + [ + 'Пушкин родился 26 мая (6 июня) 1799 г. в Москве. В метрической книге церкви Богоявления в Елохове (сейчас на её месте находится Богоявленский собор в Елохове) на дату 8 июня 1799 г.', + ], + 'cesu8' + ); + assert.equal( + packet.buffer.toString('hex', 4), + 'fc3801d09fd183d188d0bad0b8d0bd20d180d0bed0b4d0b8d0bbd181d18f20323620d0bcd0b0d18f20283620d0b8d18ed0bdd18f29203137393920d0b32e20d0b220d09cd0bed181d0bad0b2d0b52e20d09220d0bcd0b5d182d180d0b8d187d0b5d181d0bad0bed0b920d0bad0bdd0b8d0b3d0b520d186d0b5d180d0bad0b2d0b820d091d0bed0b3d0bed18fd0b2d0bbd0b5d0bdd0b8d18f20d0b220d095d0bbd0bed185d0bed0b2d0b52028d181d0b5d0b9d187d0b0d18120d0bdd0b020d0b5d19120d0bcd0b5d181d182d0b520d0bdd0b0d185d0bed0b4d0b8d182d181d18f20d091d0bed0b3d0bed18fd0b2d0bbd0b5d0bdd181d0bad0b8d0b920d181d0bed0b1d0bed18020d0b220d095d0bbd0bed185d0bed0b2d0b52920d0bdd0b020d0b4d0b0d182d183203820d0b8d18ed0bdd18f203137393920d0b32e' + ); + }); +}); diff --git a/test/esm/unit/packets/test-time.test.mts b/test/esm/unit/packets/test-time.test.mts index 19c3085a2b..f06773b5ee 100644 --- a/test/esm/unit/packets/test-time.test.mts +++ b/test/esm/unit/packets/test-time.test.mts @@ -1,20 +1,26 @@ import { Buffer } from 'node:buffer'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import Packets from '../../../../lib/packets/index.js'; -[ - ['01:23:45', '0b000004000008000000000001172d'], // CONVERT('01:23:45', TIME) - ['01:23:45.123456', '0f00000400000c000000000001172d40e20100'], // DATE_ADD(CONVERT('01:23:45', TIME), INTERVAL 0.123456 SECOND) - ['-01:23:44.876544', '0f00000400000c010000000001172c00600d00'], // DATE_ADD(CONVERT('-01:23:45', TIME), INTERVAL 0.123456 SECOND) - ['-81:23:44.876544', '0f00000400000c010300000009172c00600d00'], // DATE_ADD(CONVERT('-81:23:45', TIME), INTERVAL 0.123456 SECOND) - ['81:23:45', '0b000004000008000300000009172d'], // CONVERT('81:23:45', TIME) - ['123:23:45.123456', '0f00000400000c000500000003172d40e20100'], // DATE_ADD(CONVERT('123:23:45', TIME), INTERVAL 0.123456 SECOND) - ['-121:23:45', '0b000004000008010500000001172d'], // CONVERT('-121:23:45', TIME) - ['-01:23:44.88', '0f00000400000c010000000001172c806d0d00'], //DATE_ADD(CONVERT('-01:23:45', TIME), INTERVAL 0.12 SECOND) -].forEach(([expected, buffer]) => { - const buf = Buffer.from(buffer as string, 'hex'); - const packet = new Packets.Packet(4, buf, 0, buf.length); - packet.readInt16(); // unused - const d = packet.readTimeString(false); - assert.equal(d, expected); +describe('Time packet parsing', () => { + const testCases = [ + ['01:23:45', '0b000004000008000000000001172d'], // CONVERT('01:23:45', TIME) + ['01:23:45.123456', '0f00000400000c000000000001172d40e20100'], // DATE_ADD(CONVERT('01:23:45', TIME), INTERVAL 0.123456 SECOND) + ['-01:23:44.876544', '0f00000400000c010000000001172c00600d00'], // DATE_ADD(CONVERT('-01:23:45', TIME), INTERVAL 0.123456 SECOND) + ['-81:23:44.876544', '0f00000400000c010300000009172c00600d00'], // DATE_ADD(CONVERT('-81:23:45', TIME), INTERVAL 0.123456 SECOND) + ['81:23:45', '0b000004000008000300000009172d'], // CONVERT('81:23:45', TIME) + ['123:23:45.123456', '0f00000400000c000500000003172d40e20100'], // DATE_ADD(CONVERT('123:23:45', TIME), INTERVAL 0.123456 SECOND) + ['-121:23:45', '0b000004000008010500000001172d'], // CONVERT('-121:23:45', TIME) + ['-01:23:44.88', '0f00000400000c010000000001172c806d0d00'], //DATE_ADD(CONVERT('-01:23:45', TIME), INTERVAL 0.12 SECOND) + ] as const; + + for (const [expected, buffer] of testCases) { + it(`should parse time string ${expected}`, () => { + const buf = Buffer.from(buffer, 'hex'); + const packet = new Packets.Packet(4, buf, 0, buf.length); + packet.readInt16(); // unused + const d = packet.readTimeString(false); + assert.equal(d, expected); + }); + } }); diff --git a/test/esm/unit/parsers/test-text-parser.test.mts b/test/esm/unit/parsers/test-text-parser.test.mts index 04ebca9428..a72f5b138f 100644 --- a/test/esm/unit/parsers/test-text-parser.test.mts +++ b/test/esm/unit/parsers/test-text-parser.test.mts @@ -3,7 +3,7 @@ import type { TypeCastField, TypeCastNext, } from '../../../../index.js'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection } from '../../common.test.mjs'; const typeCastWrapper = function ( @@ -18,32 +18,42 @@ const typeCastWrapper = function ( }; }; -const connection = createConnection(); -connection.query('CREATE TEMPORARY TABLE t (i JSON)'); -connection.query('INSERT INTO t values(\'{ "test": "😀" }\')'); +await describe('Text Parser: typeCast with JSON fields', async () => { + const connection = createConnection(); + connection.query('CREATE TEMPORARY TABLE t (i JSON)'); + connection.query('INSERT INTO t values(\'{ "test": "😀" }\')'); -// JSON without encoding options - should result in unexpected behaviors -connection.query( - { - sql: 'SELECT * FROM t', - typeCast: typeCastWrapper(), - }, - (err, rows) => { - assert.ifError(err); - assert.notEqual(rows[0].i.test, '😀'); - } -); + await it('JSON without encoding options - should result in unexpected behaviors', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'SELECT * FROM t', + typeCast: typeCastWrapper(), + }, + (err, rows) => { + if (err) return reject(err); + assert.notEqual(rows[0].i.test, '😀'); + resolve(); + } + ); + }); + }); -// JSON with encoding explicitly set to utf8 -connection.query( - { - sql: 'SELECT * FROM t', - typeCast: typeCastWrapper('utf8'), - }, - (err, rows) => { - assert.ifError(err); - assert.equal(rows[0].i.test, '😀'); - } -); + await it('JSON with encoding explicitly set to utf8', async () => { + await new Promise((resolve, reject) => { + connection.query( + { + sql: 'SELECT * FROM t', + typeCast: typeCastWrapper('utf8'), + }, + (err, rows) => { + if (err) return reject(err); + assert.equal(rows[0].i.test, '😀'); + resolve(); + } + ); + }); + }); -connection.end(); + connection.end(); +}); diff --git a/test/esm/unit/pool-cluster/test-connection-error-remove.test.mts b/test/esm/unit/pool-cluster/test-connection-error-remove.test.mts index 6e06505c72..9c1882276c 100644 --- a/test/esm/unit/pool-cluster/test-connection-error-remove.test.mts +++ b/test/esm/unit/pool-cluster/test-connection-error-remove.test.mts @@ -1,5 +1,5 @@ import process, { exit } from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createPoolCluster } from '../../common.test.mjs'; @@ -18,71 +18,72 @@ if (process.platform === 'win32') { exit(0); } -const cluster = createPoolCluster({ - removeNodeErrorCount: 1, -}); - -let connCount = 0; - -// @ts-expect-error: TODO: implement typings -const server1 = mysql.createServer(); -// @ts-expect-error: TODO: implement typings -const server2 = mysql.createServer(); - -console.log('test pool cluster error remove'); - -portfinder.getPort((_err, port) => { - cluster.add('SLAVE1', { port: port + 0 }); - cluster.add('SLAVE2', { port: port + 1 }); +await describe('pool cluster error remove', async () => { + await it('should remove node on connection error', async () => { + const cluster = createPoolCluster({ + removeNodeErrorCount: 1, + }); - // @ts-expect-error: TODO: implement typings - server1.listen(port + 0, (err) => { - assert.ifError(err); + let connCount = 0; // @ts-expect-error: TODO: implement typings - server2.listen(port + 1, (err) => { - assert.ifError(err); - - const pool = cluster.of('*', 'ORDER'); - let removedNodeId: string | number; + const server1 = mysql.createServer(); + // @ts-expect-error: TODO: implement typings + const server2 = mysql.createServer(); + + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + cluster.add('SLAVE1', { port: port + 0 }); + cluster.add('SLAVE2', { port: port + 1 }); + + // @ts-expect-error: TODO: implement typings + server1.listen(port + 0, (err) => { + if (err) return reject(err); + + // @ts-expect-error: TODO: implement typings + server2.listen(port + 1, (err) => { + if (err) return reject(err); + + const pool = cluster.of('*', 'ORDER'); + let removedNodeId: string | number; + + cluster.on('remove', (nodeId) => { + removedNodeId = nodeId; + }); + + pool.getConnection((err, connection) => { + if (err) return reject(err); + + assert.equal(connCount, 2); + // @ts-expect-error: internal access + assert.equal(connection._clusterId, 'SLAVE2'); + assert.equal(removedNodeId, 'SLAVE1'); + // @ts-expect-error: internal access + assert.deepEqual(cluster._serviceableNodeIds, ['SLAVE2']); + + connection.release(); + + cluster.end((err) => { + if (err) return reject(err); + resolve(); + exit(); + }); + }); + }); + }); - cluster.on('remove', (nodeId) => { - removedNodeId = nodeId; - }); + server1.on('connection', (conn) => { + connCount += 1; + conn.close(); + }); - pool.getConnection((err, connection) => { - assert.ifError(err); - - assert.equal(connCount, 2); - // @ts-expect-error: internal access - assert.equal(connection._clusterId, 'SLAVE2'); - assert.equal(removedNodeId, 'SLAVE1'); - // @ts-expect-error: internal access - assert.deepEqual(cluster._serviceableNodeIds, ['SLAVE2']); - console.log('done'); - - connection.release(); - - cluster.end((err) => { - assert.ifError(err); - // throw error if no exit() - exit(); - // server1.close(); - // server2.close(); + server2.on('connection', (conn) => { + connCount += 1; + conn.serverHandshake({ + serverVersion: 'node.js rocks', + }); }); }); }); }); - - server1.on('connection', (conn) => { - connCount += 1; - conn.close(); - }); - - server2.on('connection', (conn) => { - connCount += 1; - conn.serverHandshake({ - serverVersion: 'node.js rocks', - }); - }); }); diff --git a/test/esm/unit/pool-cluster/test-connection-order.test.mts b/test/esm/unit/pool-cluster/test-connection-order.test.mts index e0dcdc3944..0ab0aab3a7 100644 --- a/test/esm/unit/pool-cluster/test-connection-order.test.mts +++ b/test/esm/unit/pool-cluster/test-connection-order.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPoolCluster, getConfig } from '../../common.test.mjs'; // TODO: config poolCluster to work with MYSQL_CONNECTION_URL run @@ -8,41 +8,47 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const cluster = createPoolCluster(); - -const order: string[] = []; - -const poolConfig = getConfig(); -cluster.add('SLAVE1', poolConfig); -cluster.add('SLAVE2', poolConfig); - -const done = function () { - assert.deepEqual(order, ['SLAVE1', 'SLAVE1', 'SLAVE1', 'SLAVE1', 'SLAVE1']); - cluster.end(); - console.log('done'); -}; - -const pool = cluster.of('SLAVE*', 'ORDER'); - -console.log('test pool cluster connection ORDER'); - -let count = 0; - -function getConnection(i: number) { - pool.getConnection((err, conn) => { - assert.ifError(err); - // @ts-expect-error: internal access - order[i] = conn._clusterId; - conn.release(); - - count += 1; - - if (count <= 4) { - getConnection(count); - } else { - done(); - } +await describe('pool cluster connection ORDER', async () => { + await it('should get connections in ORDER', async () => { + const cluster = createPoolCluster(); + + const order: string[] = []; + + const poolConfig = getConfig(); + cluster.add('SLAVE1', poolConfig); + cluster.add('SLAVE2', poolConfig); + + const pool = cluster.of('SLAVE*', 'ORDER'); + + await new Promise((resolve, reject) => { + let count = 0; + + function getConnection(i: number) { + pool.getConnection((err, conn) => { + if (err) return reject(err); + // @ts-expect-error: internal access + order[i] = conn._clusterId; + conn.release(); + + count += 1; + + if (count <= 4) { + getConnection(count); + } else { + assert.deepEqual(order, [ + 'SLAVE1', + 'SLAVE1', + 'SLAVE1', + 'SLAVE1', + 'SLAVE1', + ]); + cluster.end(); + resolve(); + } + }); + } + + getConnection(0); + }); }); -} - -getConnection(0); +}); diff --git a/test/esm/unit/pool-cluster/test-connection-retry.test.mts b/test/esm/unit/pool-cluster/test-connection-retry.test.mts index 0bf96af214..255e5874c6 100644 --- a/test/esm/unit/pool-cluster/test-connection-retry.test.mts +++ b/test/esm/unit/pool-cluster/test-connection-retry.test.mts @@ -1,5 +1,5 @@ import process, { exit } from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createPoolCluster } from '../../common.test.mjs'; @@ -18,54 +18,59 @@ if (process.platform === 'win32') { exit(0); } -const cluster = createPoolCluster({ - canRetry: true, - removeNodeErrorCount: 5, -}); - -let connCount = 0; +await describe('pool cluster retry', async () => { + await it('should retry connection on failure', async () => { + const cluster = createPoolCluster({ + canRetry: true, + removeNodeErrorCount: 5, + }); -// @ts-expect-error: TODO: implement typings -const server = mysql.createServer(); + let connCount = 0; -console.log('test pool cluster retry'); + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); -portfinder.getPort((_err, port) => { - cluster.add('MASTER', { port }); + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + cluster.add('MASTER', { port }); - // @ts-expect-error: TODO: implement typings - server.listen(port + 0, (err) => { - assert.ifError(err); + // @ts-expect-error: TODO: implement typings + server.listen(port + 0, (err) => { + if (err) return reject(err); - cluster.getConnection('MASTER', (err, connection) => { - assert.ifError(err); - assert.equal(connCount, 2); - // @ts-expect-error: internal access - assert.equal(connection._clusterId, 'MASTER'); + cluster.getConnection('MASTER', (err, connection) => { + if (err) return reject(err); + assert.equal(connCount, 2); + // @ts-expect-error: internal access + assert.equal(connection._clusterId, 'MASTER'); - connection.release(); + connection.release(); - cluster.end((err) => { - assert.ifError(err); - // @ts-expect-error: TODO: implement typings - server.close(); - }); - }); - }); + cluster.end((err) => { + if (err) return reject(err); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + }); - server.on('connection', (conn) => { - connCount += 1; + server.on('connection', (conn) => { + connCount += 1; - if (connCount < 2) { - conn.close(); - } else { - conn.serverHandshake({ - serverVersion: 'node.js rocks', + if (connCount < 2) { + conn.close(); + } else { + conn.serverHandshake({ + serverVersion: 'node.js rocks', + }); + conn.on('error', () => { + // server side of the connection + // ignore disconnects + }); + } + }); }); - conn.on('error', () => { - // server side of the connection - // ignore disconnects - }); - } + }); }); }); diff --git a/test/esm/unit/pool-cluster/test-connection-rr.test.mts b/test/esm/unit/pool-cluster/test-connection-rr.test.mts index b384502095..2fe55c852b 100644 --- a/test/esm/unit/pool-cluster/test-connection-rr.test.mts +++ b/test/esm/unit/pool-cluster/test-connection-rr.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPoolCluster, getConfig } from '../../common.test.mjs'; // TODO: config poolCluster to work with MYSQL_CONNECTION_URL run @@ -8,41 +8,47 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const cluster = createPoolCluster(); - -const order: string[] = []; - -const poolConfig = getConfig(); -cluster.add('SLAVE1', poolConfig); -cluster.add('SLAVE2', poolConfig); - -const done = function () { - assert.deepEqual(order, ['SLAVE1', 'SLAVE2', 'SLAVE1', 'SLAVE2', 'SLAVE1']); - cluster.end(); - console.log('done'); -}; - -const pool = cluster.of('SLAVE*', 'RR'); - -console.log('test pool cluster connection RR'); - -let count = 0; - -function getConnection(i: number) { - pool.getConnection((err, conn) => { - assert.ifError(err); - // @ts-expect-error: internal access - order[i] = conn._clusterId; - conn.release(); - - count += 1; - - if (count <= 4) { - getConnection(count); - } else { - done(); - } +await describe('pool cluster connection RR', async () => { + await it('should get connections in round-robin order', async () => { + const cluster = createPoolCluster(); + + const order: string[] = []; + + const poolConfig = getConfig(); + cluster.add('SLAVE1', poolConfig); + cluster.add('SLAVE2', poolConfig); + + const pool = cluster.of('SLAVE*', 'RR'); + + await new Promise((resolve, reject) => { + let count = 0; + + function getConnection(i: number) { + pool.getConnection((err, conn) => { + if (err) return reject(err); + // @ts-expect-error: internal access + order[i] = conn._clusterId; + conn.release(); + + count += 1; + + if (count <= 4) { + getConnection(count); + } else { + assert.deepEqual(order, [ + 'SLAVE1', + 'SLAVE2', + 'SLAVE1', + 'SLAVE2', + 'SLAVE1', + ]); + cluster.end(); + resolve(); + } + }); + } + + getConnection(0); + }); }); -} - -getConnection(0); +}); diff --git a/test/esm/unit/pool-cluster/test-query.test.mts b/test/esm/unit/pool-cluster/test-query.test.mts index 8f180d4a99..2c60765100 100644 --- a/test/esm/unit/pool-cluster/test-query.test.mts +++ b/test/esm/unit/pool-cluster/test-query.test.mts @@ -1,6 +1,6 @@ import type { RowDataPacket } from '../../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createPoolCluster, getConfig } from '../../common.test.mjs'; // TODO: config poolCluster to work with MYSQL_CONNECTION_URL run @@ -9,24 +9,32 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const cluster = createPoolCluster(); -const poolConfig = getConfig(); +await describe('pool cluster connection query', async () => { + await it('should execute query through pool cluster', async () => { + const cluster = createPoolCluster(); + const poolConfig = getConfig(); -cluster.add('MASTER', poolConfig); -cluster.add('SLAVE1', poolConfig); -cluster.add('SLAVE2', poolConfig); + cluster.add('MASTER', poolConfig); + cluster.add('SLAVE1', poolConfig); + cluster.add('SLAVE2', poolConfig); -const connection = cluster.of('*'); + const connection = cluster.of('*'); -console.log('test pool cluster connection query'); + await new Promise((resolve, reject) => { + connection.query('SELECT 1', (err, rows) => { + if (err) return reject(err); + assert.equal(rows.length, 1); + assert.equal(rows[0]['1'], 1); + // @ts-expect-error: internal access + assert.deepEqual(cluster._serviceableNodeIds, [ + 'MASTER', + 'SLAVE1', + 'SLAVE2', + ]); -connection.query('SELECT 1', (err, rows) => { - assert.ifError(err); - assert.equal(rows.length, 1); - assert.equal(rows[0]['1'], 1); - // @ts-expect-error: internal access - assert.deepEqual(cluster._serviceableNodeIds, ['MASTER', 'SLAVE1', 'SLAVE2']); - - cluster.end(); - console.log('done'); + cluster.end(); + resolve(); + }); + }); + }); }); diff --git a/test/esm/unit/pool-cluster/test-remove-by-name.test.mts b/test/esm/unit/pool-cluster/test-remove-by-name.test.mts index e865d3aaa1..87b8849d4b 100644 --- a/test/esm/unit/pool-cluster/test-remove-by-name.test.mts +++ b/test/esm/unit/pool-cluster/test-remove-by-name.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createPoolCluster } from '../../common.test.mjs'; @@ -18,62 +18,67 @@ if (process.platform === 'win32') { // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const cluster = createPoolCluster(); -// @ts-expect-error: TODO: implement typings -const server = mysql.createServer(); - -console.log('test pool cluster remove by name'); - -portfinder.getPort((_err, port) => { - cluster.add('SLAVE1', { port }); - cluster.add('SLAVE2', { port }); - - // @ts-expect-error: TODO: implement typings - server.listen(port + 0, (err) => { - assert.ifError(err); - - const pool = cluster.of('SLAVE*', 'ORDER'); - - pool.getConnection((err, conn) => { - assert.ifError(err); - // @ts-expect-error: internal access - assert.strictEqual(conn._clusterId, 'SLAVE1'); - - conn.release(); - cluster.remove('SLAVE1'); - - pool.getConnection((err, conn) => { - assert.ifError(err); - // @ts-expect-error: internal access - assert.strictEqual(conn._clusterId, 'SLAVE2'); - - conn.release(); - cluster.remove('SLAVE2'); - - pool.getConnection((err) => { - assert.ok(err); - assert.equal(err?.code, 'POOL_NOEXIST'); - - cluster.remove('SLAVE1'); - cluster.remove('SLAVE2'); +await describe('pool cluster remove by name', async () => { + await it('should remove nodes by name', async () => { + const cluster = createPoolCluster(); + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); + + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + cluster.add('SLAVE1', { port }); + cluster.add('SLAVE2', { port }); + + // @ts-expect-error: TODO: implement typings + server.listen(port + 0, (err) => { + if (err) return reject(err); + + const pool = cluster.of('SLAVE*', 'ORDER'); + + pool.getConnection((err, conn) => { + if (err) return reject(err); + // @ts-expect-error: internal access + assert.strictEqual(conn._clusterId, 'SLAVE1'); + + conn.release(); + cluster.remove('SLAVE1'); + + pool.getConnection((err, conn) => { + if (err) return reject(err); + // @ts-expect-error: internal access + assert.strictEqual(conn._clusterId, 'SLAVE2'); + + conn.release(); + cluster.remove('SLAVE2'); + + pool.getConnection((err) => { + assert.ok(err); + assert.equal(err?.code, 'POOL_NOEXIST'); + + cluster.remove('SLAVE1'); + cluster.remove('SLAVE2'); + + cluster.end((err) => { + if (err) return reject(err); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + }); + }); + }); - cluster.end((err) => { - assert.ifError(err); - // @ts-expect-error: TODO: implement typings - server.close(); + server.on('connection', (conn) => { + conn.serverHandshake({ + serverVersion: 'node.js rocks', + }); + conn.on('error', () => { + // server side of the connection + // ignore disconnects }); }); }); }); }); - - server.on('connection', (conn) => { - conn.serverHandshake({ - serverVersion: 'node.js rocks', - }); - conn.on('error', () => { - // server side of the connection - // ignore disconnects - }); - }); }); diff --git a/test/esm/unit/pool-cluster/test-remove-by-pattern.test.mts b/test/esm/unit/pool-cluster/test-remove-by-pattern.test.mts index ac811f2b10..179f198d69 100644 --- a/test/esm/unit/pool-cluster/test-remove-by-pattern.test.mts +++ b/test/esm/unit/pool-cluster/test-remove-by-pattern.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createPoolCluster } from '../../common.test.mjs'; @@ -18,53 +18,58 @@ if (process.platform === 'win32') { // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const cluster = createPoolCluster(); -// @ts-expect-error: TODO: implement typings -const server = mysql.createServer(); +await describe('pool cluster remove by pattern', async () => { + await it('should remove nodes by pattern', async () => { + const cluster = createPoolCluster(); + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); -console.log('test pool cluster remove by pattern'); + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + cluster.add('SLAVE1', { port }); + cluster.add('SLAVE2', { port }); -portfinder.getPort((_err, port) => { - cluster.add('SLAVE1', { port }); - cluster.add('SLAVE2', { port }); + // @ts-expect-error: TODO: implement typings + server.listen(port + 0, (err) => { + if (err) return reject(err); - // @ts-expect-error: TODO: implement typings - server.listen(port + 0, (err) => { - assert.ifError(err); + const pool = cluster.of('SLAVE*', 'ORDER'); - const pool = cluster.of('SLAVE*', 'ORDER'); + pool.getConnection((err, conn) => { + if (err) return reject(err); + // @ts-expect-error: internal access + assert.strictEqual(conn._clusterId, 'SLAVE1'); - pool.getConnection((err, conn) => { - assert.ifError(err); - // @ts-expect-error: internal access - assert.strictEqual(conn._clusterId, 'SLAVE1'); + conn.release(); + cluster.remove('SLAVE*'); - conn.release(); - cluster.remove('SLAVE*'); + pool.getConnection((err) => { + assert.ok(err); + assert.equal(err?.code, 'POOL_NOEXIST'); - pool.getConnection((err) => { - assert.ok(err); - assert.equal(err?.code, 'POOL_NOEXIST'); + cluster.remove('SLAVE*'); + cluster.remove('SLAVE2'); - cluster.remove('SLAVE*'); - cluster.remove('SLAVE2'); + cluster.end((err) => { + if (err) return reject(err); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + }); + }); - cluster.end((err) => { - assert.ifError(err); - // @ts-expect-error: TODO: implement typings - server.close(); + server.on('connection', (conn) => { + conn.serverHandshake({ + serverVersion: 'node.js rocks', + }); + conn.on('error', () => { + // server side of the connection + // ignore disconnects + }); }); }); }); }); - - server.on('connection', (conn) => { - conn.serverHandshake({ - serverVersion: 'node.js rocks', - }); - conn.on('error', () => { - // server side of the connection - // ignore disconnects - }); - }); }); diff --git a/test/esm/unit/pool-cluster/test-restore-events.test.mts b/test/esm/unit/pool-cluster/test-restore-events.test.mts index c44b58a4cc..2cdfcbd6ed 100644 --- a/test/esm/unit/pool-cluster/test-restore-events.test.mts +++ b/test/esm/unit/pool-cluster/test-restore-events.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createPoolCluster } from '../../common.test.mjs'; @@ -18,83 +18,88 @@ if (process.platform === 'win32') { // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const cluster = createPoolCluster({ - canRetry: true, - removeNodeErrorCount: 2, - restoreNodeTimeout: 100, -}); - -let connCount = 0; -let offline = true; -let offlineEvents = 0; -let onlineEvents = 0; - -// @ts-expect-error: TODO: implement typings -const server = mysql.createServer(); - -console.log('test pool cluster restore events'); - -portfinder.getPort((_err, port) => { - cluster.add('MASTER', { port }); - - // @ts-expect-error: TODO: implement typings - server.listen(port + 0, (err) => { - assert.ifError(err); +await describe('pool cluster restore events', async () => { + await it('should emit offline and online events', async () => { + const cluster = createPoolCluster({ + canRetry: true, + removeNodeErrorCount: 2, + restoreNodeTimeout: 100, + }); - cluster.on('offline', (id) => { - assert.equal(++offlineEvents, 1); - assert.equal(id, 'MASTER'); - assert.equal(connCount, 2); + let connCount = 0; + let offline = true; + let offlineEvents = 0; + let onlineEvents = 0; - cluster.getConnection('MASTER', (err) => { - assert.ok(err); - assert.equal(err?.code, 'POOL_NONEONLINE'); + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); - offline = false; - }); + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + cluster.add('MASTER', { port }); - setTimeout(() => { - cluster.getConnection('MASTER', (err, conn) => { - assert.ifError(err); - conn.release(); + // @ts-expect-error: TODO: implement typings + server.listen(port + 0, (err) => { + if (err) return reject(err); + + cluster.on('offline', (id) => { + assert.equal(++offlineEvents, 1); + assert.equal(id, 'MASTER'); + assert.equal(connCount, 2); + + cluster.getConnection('MASTER', (err) => { + assert.ok(err); + assert.equal(err?.code, 'POOL_NONEONLINE'); + + offline = false; + }); + + setTimeout(() => { + cluster.getConnection('MASTER', (err, conn) => { + if (err) return reject(err); + conn.release(); + }); + }, 200); + }); + + cluster.on('online', (id) => { + assert.equal(++onlineEvents, 1); + assert.equal(id, 'MASTER'); + assert.equal(connCount, 3); + + cluster.end((err) => { + if (err) return reject(err); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + + cluster.getConnection('MASTER', (err) => { + assert.ok(err); + assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); + // @ts-expect-error: TODO: implement typings + assert.equal(err?.fatal, true); + assert.equal(connCount, 2); + }); }); - }, 200); - }); - cluster.on('online', (id) => { - assert.equal(++onlineEvents, 1); - assert.equal(id, 'MASTER'); - assert.equal(connCount, 3); - - cluster.end((err) => { - assert.ifError(err); - // @ts-expect-error: TODO: implement typings - server.close(); + server.on('connection', (conn) => { + connCount += 1; + + if (offline) { + conn.close(); + } else { + conn.serverHandshake({ + serverVersion: 'node.js rocks', + }); + conn.on('error', () => { + // server side of the connection + // ignore disconnects + }); + } + }); }); }); - - cluster.getConnection('MASTER', (err) => { - assert.ok(err); - assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); - // @ts-expect-error: TODO: implement typings - assert.equal(err?.fatal, true); - assert.equal(connCount, 2); - }); - }); - - server.on('connection', (conn) => { - connCount += 1; - - if (offline) { - conn.close(); - } else { - conn.serverHandshake({ - serverVersion: 'node.js rocks', - }); - conn.on('error', () => { - // server side of the connection - // ignore disconnects - }); - } }); }); diff --git a/test/esm/unit/pool-cluster/test-restore.test.mts b/test/esm/unit/pool-cluster/test-restore.test.mts index dfa15712e9..37f26bee54 100644 --- a/test/esm/unit/pool-cluster/test-restore.test.mts +++ b/test/esm/unit/pool-cluster/test-restore.test.mts @@ -1,5 +1,5 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../../index.js'; import { createPoolCluster } from '../../common.test.mjs'; @@ -18,72 +18,77 @@ if (process.platform === 'win32') { // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const cluster = createPoolCluster({ - canRetry: true, - removeNodeErrorCount: 2, - restoreNodeTimeout: 100, -}); - -let connCount = 0; -let offline = true; - -// @ts-expect-error: TODO: implement typings -const server = mysql.createServer(); - -console.log('test pool cluster restore'); - -portfinder.getPort((_err, port) => { - cluster.add('MASTER', { port }); - - // @ts-expect-error: TODO: implement typings - server.listen(port + 0, (err) => { - assert.ifError(err); +await describe('pool cluster restore', async () => { + await it('should restore node after timeout', async () => { + const cluster = createPoolCluster({ + canRetry: true, + removeNodeErrorCount: 2, + restoreNodeTimeout: 100, + }); - cluster.getConnection('MASTER', (err) => { - assert.ok(err); - assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); - // @ts-expect-error: TODO: implement typings - assert.equal(err?.fatal, true); - assert.equal(connCount, 2); + let connCount = 0; + let offline = true; - cluster.getConnection('MASTER', (err) => { - assert.ok(err); - assert.equal(err?.code, 'POOL_NONEONLINE'); + // @ts-expect-error: TODO: implement typings + const server = mysql.createServer(); - // @ts-expect-error: internal access - cluster._nodes.MASTER.errorCount = 3; + await new Promise((resolve, reject) => { + portfinder.getPort((_err, port) => { + cluster.add('MASTER', { port }); - offline = false; - }); + // @ts-expect-error: TODO: implement typings + server.listen(port + 0, (err) => { + if (err) return reject(err); - setTimeout(() => { - cluster.getConnection('MASTER', (err, conn) => { - assert.ifError(err); - conn.release(); - - cluster.end((err) => { - assert.ifError(err); + cluster.getConnection('MASTER', (err) => { + assert.ok(err); + assert.equal(err?.code, 'PROTOCOL_CONNECTION_LOST'); // @ts-expect-error: TODO: implement typings - server.close(); + assert.equal(err?.fatal, true); + assert.equal(connCount, 2); + + cluster.getConnection('MASTER', (err) => { + assert.ok(err); + assert.equal(err?.code, 'POOL_NONEONLINE'); + + // @ts-expect-error: internal access + cluster._nodes.MASTER.errorCount = 3; + + offline = false; + }); + + setTimeout(() => { + cluster.getConnection('MASTER', (err, conn) => { + if (err) return reject(err); + conn.release(); + + cluster.end((err) => { + if (err) return reject(err); + // @ts-expect-error: TODO: implement typings + server.close(); + resolve(); + }); + }); + }, 200); }); }); - }, 200); - }); - }); - - server.on('connection', (conn) => { - connCount += 1; - if (offline) { - conn.close(); - } else { - conn.serverHandshake({ - serverVersion: 'node.js rocks', - }); - conn.on('error', () => { - // server side of the connection - // ignore disconnects + server.on('connection', (conn) => { + connCount += 1; + + if (offline) { + conn.close(); + } else { + conn.serverHandshake({ + serverVersion: 'node.js rocks', + }); + conn.on('error', () => { + // server side of the connection + // ignore disconnects + }); + } + }); }); - } + }); }); }); diff --git a/test/esm/unit/test-packet-parser.test.mts b/test/esm/unit/test-packet-parser.test.mts index 8489b12437..3a5585f6f6 100644 --- a/test/esm/unit/test-packet-parser.test.mts +++ b/test/esm/unit/test-packet-parser.test.mts @@ -1,5 +1,5 @@ import { Buffer } from 'node:buffer'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import PacketParser from '../../../lib/packet_parser.js'; import Packet from '../../../lib/packets/packet.js'; @@ -41,60 +41,12 @@ function p120_121() { assert(packets[1].sequenceId === 121); } -execute('10,0,0,123,1,2,3,4,5,6,7,8,9,0', p123); -execute('10,0,0,123|1,2,3,4,5,6,7,8,9,0', p123); -execute('10,0,0|123,1,2,3,4,5,6,7,8,9,0', p123); -execute('10|0,0|123,1,2,3,4,5,6,7,8,9,0', p123); -execute('10,0,0,123,1|2,3,4,5,6|7,8,9,0', p123); -execute('10,0,0,123,1,2|,3,4,5,6|7,8,9,0', p123); - function p42() { assert(packets.length === 1); assert(packets[0].length() === 4); assert(packets[0].sequenceId === 42); } -execute('0,0,0,42', p42); -execute('0|0,0,42', p42); -execute('0,0|0,42', p42); -execute('0,0|0|42', p42); -execute('0,0,0|42', p42); -execute('0|0|0|42', p42); -execute('0|0,0|42', p42); - -// two zero length packets -execute('0,0,0,120,0,0,0,121', p120_121); -execute('0,0,0|120|0|0|0|121', p120_121); - -const p122_123 = function () { - assert(packets.length === 2); - assert(packets[0].length() === 9); - assert(packets[0].sequenceId === 122); - assert(packets[1].length() === 10); - assert(packets[1].sequenceId === 123); -}; -// two non-zero length packets -execute('5,0,0,122,1,2,3,4,5,6,0,0,123,1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5|6,0,0,123,1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4|5|6|0,0,123,1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5,6|0,0,123,1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5,6,0|0,123,1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5,6,0,0|123,1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5,6,0,0,123|1,2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5,6,0,0,123,1|2,3,4,5,6', p122_123); -execute('5,0,0,122,1,2,3,4,5,6,0,0,123,1|2,3|4,5,6', p122_123); - -// test packet > 65536 lengt -// TODO combine with "execute" function - -const length = 123000; -const pbuff = Buffer.alloc(length + 4); -pbuff[4] = 123; -pbuff[5] = 124; -pbuff[6] = 125; -const p = new Packet(144, pbuff, 4, pbuff.length - 4); -p.writeHeader(42); - function testBigPackets( chunks: Buffer[], cb: (packets: PacketInstance[]) => void @@ -109,42 +61,100 @@ function testBigPackets( cb(packets); } -function assert2FullPackets(packets: PacketInstance[]) { - function assertPacket(p: PacketInstance) { - assert.equal(p.length(), length + 4); - assert.equal(p.sequenceId, 42); - assert.equal(p.readInt8(), 123); - assert.equal(p.readInt8(), 124); - assert.equal(p.readInt8(), 125); - } - // assert.equal(packets[0].buffer.slice(0, 8).toString('hex'), expectation); - // assert.equal(packets[1].buffer.slice(0, 8).toString('hex'), expectation); - assert.equal(packets.length, 2); - assertPacket(packets[0]); - assertPacket(packets[1]); -} +describe('PacketParser', () => { + it('should parse single packet with various chunk splits', () => { + execute('10,0,0,123,1,2,3,4,5,6,7,8,9,0', p123); + execute('10,0,0,123|1,2,3,4,5,6,7,8,9,0', p123); + execute('10,0,0|123,1,2,3,4,5,6,7,8,9,0', p123); + execute('10|0,0|123,1,2,3,4,5,6,7,8,9,0', p123); + execute('10,0,0,123,1|2,3,4,5,6|7,8,9,0', p123); + execute('10,0,0,123,1,2|,3,4,5,6|7,8,9,0', p123); + }); -// 2 full packets in 2 chunks -testBigPackets([pbuff, pbuff], assert2FullPackets); - -testBigPackets( - [pbuff.slice(0, 120000), pbuff.slice(120000, 123004), pbuff], - assert2FullPackets -); -const frameEnd = 120000; -testBigPackets( - [ - pbuff.slice(0, frameEnd), - Buffer.concat([pbuff.slice(frameEnd, 123004), pbuff]), - ], - assert2FullPackets -); -for (let frameStart = 1; frameStart < 100; frameStart++) { - testBigPackets( - [ - Buffer.concat([pbuff, pbuff.slice(0, frameStart)]), - pbuff.slice(frameStart, 123004), - ], - assert2FullPackets - ); -} + it('should parse zero-length packet with various chunk splits', () => { + execute('0,0,0,42', p42); + execute('0|0,0,42', p42); + execute('0,0|0,42', p42); + execute('0,0|0|42', p42); + execute('0,0,0|42', p42); + execute('0|0|0|42', p42); + execute('0|0,0|42', p42); + }); + + it('should parse two zero length packets', () => { + execute('0,0,0,120,0,0,0,121', p120_121); + execute('0,0,0|120|0|0|0|121', p120_121); + }); + + it('should parse two non-zero length packets with various chunk splits', () => { + const p122_123 = function () { + assert(packets.length === 2); + assert(packets[0].length() === 9); + assert(packets[0].sequenceId === 122); + assert(packets[1].length() === 10); + assert(packets[1].sequenceId === 123); + }; + execute('5,0,0,122,1,2,3,4,5,6,0,0,123,1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5|6,0,0,123,1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4|5|6|0,0,123,1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5,6|0,0,123,1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5,6,0|0,123,1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5,6,0,0|123,1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5,6,0,0,123|1,2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5,6,0,0,123,1|2,3,4,5,6', p122_123); + execute('5,0,0,122,1,2,3,4,5,6,0,0,123,1|2,3|4,5,6', p122_123); + }); + + it('should parse packets larger than 65536 bytes', () => { + // test packet > 65536 lengt + // TODO combine with "execute" function + + const length = 123000; + const pbuff = Buffer.alloc(length + 4); + pbuff[4] = 123; + pbuff[5] = 124; + pbuff[6] = 125; + const p = new Packet(144, pbuff, 4, pbuff.length - 4); + p.writeHeader(42); + + function assert2FullPackets(packets: PacketInstance[]) { + function assertPacket(p: PacketInstance) { + assert.equal(p.length(), length + 4); + assert.equal(p.sequenceId, 42); + assert.equal(p.readInt8(), 123); + assert.equal(p.readInt8(), 124); + assert.equal(p.readInt8(), 125); + } + // assert.equal(packets[0].buffer.slice(0, 8).toString('hex'), expectation); + // assert.equal(packets[1].buffer.slice(0, 8).toString('hex'), expectation); + assert.equal(packets.length, 2); + assertPacket(packets[0]); + assertPacket(packets[1]); + } + + // 2 full packets in 2 chunks + testBigPackets([pbuff, pbuff], assert2FullPackets); + + testBigPackets( + [pbuff.slice(0, 120000), pbuff.slice(120000, 123004), pbuff], + assert2FullPackets + ); + const frameEnd = 120000; + testBigPackets( + [ + pbuff.slice(0, frameEnd), + Buffer.concat([pbuff.slice(frameEnd, 123004), pbuff]), + ], + assert2FullPackets + ); + for (let frameStart = 1; frameStart < 100; frameStart++) { + testBigPackets( + [ + Buffer.concat([pbuff, pbuff.slice(0, frameStart)]), + pbuff.slice(frameStart, 123004), + ], + assert2FullPackets + ); + } + }); +}); From 716c4fa8112edcd4d00fface63d434756574418e Mon Sep 17 00:00:00 2001 From: wellwelwel <46850407+wellwelwel@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:10:27 -0300 Subject: [PATCH 2/4] debug: revert tests --- .../test-pool-connect-error.test.mts | 92 +++++++-------- .../integration/test-pool-disconnect.test.mts | 108 ++++++++---------- 2 files changed, 94 insertions(+), 106 deletions(-) diff --git a/test/esm/integration/test-pool-connect-error.test.mts b/test/esm/integration/test-pool-connect-error.test.mts index db218d10c6..0a9060fd68 100644 --- a/test/esm/integration/test-pool-connect-error.test.mts +++ b/test/esm/integration/test-pool-connect-error.test.mts @@ -1,61 +1,57 @@ import process from 'node:process'; -import { assert, describe, it } from 'poku'; +import { assert } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -await describe('Pool Connect Error', async () => { - await it('should handle connection error in pool', async () => { - const server = mysql.createServer((conn) => { - conn.serverHandshake({ - protocolVersion: 10, - serverVersion: '5.6.10', - connectionId: 1234, - statusFlags: 2, - characterSet: 8, - capabilityFlags: 0xffffff, - // @ts-expect-error: TODO: implement typings - authCallback: function (_params, cb) { - cb(null, { message: 'too many connections', code: 1040 }); - }, - }); - }); - - let err1: NodeJS.ErrnoException | undefined; +const server = mysql.createServer((conn) => { + conn.serverHandshake({ + protocolVersion: 10, + serverVersion: '5.6.10', + connectionId: 1234, + statusFlags: 2, + characterSet: 8, + capabilityFlags: 0xffffff, + // @ts-expect-error: TODO: implement typings + authCallback: function (_params, cb) { + cb(null, { message: 'too many connections', code: 1040 }); + }, + }); +}); - await new Promise((resolve) => { - portfinder.getPort((_err, port) => { - server.listen(port); - const conn = mysql.createConnection({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - }); - conn.on('error', (err) => { - err1 = err; - }); +let err1: NodeJS.ErrnoException | undefined, + err2: NodeJS.ErrnoException | undefined; - const pool = mysql.createPool({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - }); +portfinder.getPort((_err, port) => { + server.listen(port); + const conn = mysql.createConnection({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + }); + conn.on('error', (err) => { + err1 = err; + }); - pool.query('test sql', (err) => { - const err2 = err ?? undefined; - pool.end(); - // @ts-expect-error: TODO: implement typings - server.close(); + const pool = mysql.createPool({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + }); - assert.equal(err1?.errno, 1040); - assert.equal(err2?.errno, 1040); - resolve(); - }); - }); - }); + pool.query('test sql', (err) => { + err2 = err ?? undefined; + pool.end(); + // @ts-expect-error: TODO: implement typings + server.close(); }); }); + +process.on('exit', () => { + assert.equal(err1?.errno, 1040); + assert.equal(err2?.errno, 1040); +}); diff --git a/test/esm/integration/test-pool-disconnect.test.mts b/test/esm/integration/test-pool-disconnect.test.mts index 9715d155ec..a17e2b6b2b 100644 --- a/test/esm/integration/test-pool-disconnect.test.mts +++ b/test/esm/integration/test-pool-disconnect.test.mts @@ -4,7 +4,7 @@ import type { RowDataPacket, } from '../../../index.js'; import process from 'node:process'; -import { assert, describe, it } from 'poku'; +import { assert } from 'poku'; import { createConnection, createPool } from '../common.test.mjs'; // planetscale does not support KILL, skipping this test @@ -14,68 +14,60 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -await describe('Pool Disconnect', async () => { - await it('should handle pool connection kills correctly', async () => { - const pool = createPool(); - const conn = createConnection({ multipleStatements: true }); - pool.config.connectionLimit = 5; +const pool = createPool(); +const conn = createConnection({ multipleStatements: true }); +pool.config.connectionLimit = 5; - const numSelectToPerform = 10; - const tids: number[] = []; - let numSelects = 0; - let killCount = 0; +const numSelectToPerform = 10; +const tids: number[] = []; +let numSelects = 0; +let killCount = 0; - await new Promise((resolve, reject) => { - function kill() { - setTimeout(() => { - const id = tids.shift(); - if (typeof id !== 'undefined') { - // sleep required to give mysql time to close connection, - // and callback called after connection with id is really closed - conn.query('kill ?; select sleep(0.05)', [id], (err) => { - if (err) return reject(err); - killCount++; - // TODO: this assertion needs to be fixed, after kill - // connection is removed from _allConnections but not at a point this callback is called - // - // assert.equal(pool._allConnections.length, tids.length); - }); - } else { - // conn.end waits for pending queries to complete, - // ensuring the last kill callback (killCount++) has fired - conn.end(() => { - pool.end(); - resolve(); - }); - } - }, 5); - } - - pool.on('connection', (conn: PoolConnection) => { - tids.push(conn.threadId); - conn.on('error', () => { - setTimeout(kill, 5); - }); +function kill() { + setTimeout(() => { + const id = tids.shift(); + if (typeof id !== 'undefined') { + // sleep required to give mysql time to close connection, + // and callback called after connection with id is really closed + conn.query('kill ?; select sleep(0.05)', [id], (err) => { + assert.ifError(err); + killCount++; + // TODO: this assertion needs to be fixed, after kill + // connection is removed from _allConnections but not at a point this callback is called + // + // assert.equal(pool._allConnections.length, tids.length); }); + } else { + conn.end(); + pool.end(); + } + }, 5); +} + +pool.on('connection', (conn: PoolConnection) => { + tids.push(conn.threadId); + conn.on('error', () => { + setTimeout(kill, 5); + }); +}); - for (let i = 0; i < numSelectToPerform; i++) { - pool.query( - 'select 1 as value', - (err: QueryError | null, rows: RowDataPacket[]) => { - numSelects++; - if (err) return reject(err); - assert.equal(rows[0].value, 1); +for (let i = 0; i < numSelectToPerform; i++) { + pool.query( + 'select 1 as value', + (err: QueryError | null, rows: RowDataPacket[]) => { + numSelects++; + assert.ifError(err); + assert.equal(rows[0].value, 1); - // after all queries complete start killing connections - if (numSelects === numSelectToPerform) { - kill(); - } - } - ); + // after all queries complete start killing connections + if (numSelects === numSelectToPerform) { + kill(); } - }); + } + ); +} - assert.equal(numSelects, numSelectToPerform); - assert.equal(killCount, pool.config.connectionLimit); - }); +process.on('exit', () => { + assert.equal(numSelects, numSelectToPerform); + assert.equal(killCount, pool.config.connectionLimit); }); From 10fd8240d3b4834cfc712db7087549af549a0b98 Mon Sep 17 00:00:00 2001 From: wellwelwel <46850407+wellwelwel@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:34:13 -0300 Subject: [PATCH 3/4] debug: remove debug 1 --- .../test-pool-connect-error.test.mts | 99 ++++++++++--------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/test/esm/integration/test-pool-connect-error.test.mts b/test/esm/integration/test-pool-connect-error.test.mts index 0a9060fd68..33e9759889 100644 --- a/test/esm/integration/test-pool-connect-error.test.mts +++ b/test/esm/integration/test-pool-connect-error.test.mts @@ -1,57 +1,68 @@ import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import portfinder from 'portfinder'; import mysql from '../../../index.js'; // The process is not terminated in Deno if (typeof Deno !== 'undefined') process.exit(0); -const server = mysql.createServer((conn) => { - conn.serverHandshake({ - protocolVersion: 10, - serverVersion: '5.6.10', - connectionId: 1234, - statusFlags: 2, - characterSet: 8, - capabilityFlags: 0xffffff, - // @ts-expect-error: TODO: implement typings - authCallback: function (_params, cb) { - cb(null, { message: 'too many connections', code: 1040 }); - }, - }); -}); +await describe('Pool Connect Error', async () => { + await it('should emit error code 1040 for connection and pool', async () => { + await new Promise((resolve) => { + const server = mysql.createServer((conn) => { + conn.serverHandshake({ + protocolVersion: 10, + serverVersion: '5.6.10', + connectionId: 1234, + statusFlags: 2, + characterSet: 8, + capabilityFlags: 0xffffff, + // @ts-expect-error: TODO: implement typings + authCallback: function (_params, cb) { + cb(null, { message: 'too many connections', code: 1040 }); + }, + }); + }); -let err1: NodeJS.ErrnoException | undefined, - err2: NodeJS.ErrnoException | undefined; + let err1: NodeJS.ErrnoException | undefined, + err2: NodeJS.ErrnoException | undefined; + let done = false; -portfinder.getPort((_err, port) => { - server.listen(port); - const conn = mysql.createConnection({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - }); - conn.on('error', (err) => { - err1 = err; - }); + portfinder.getPort((_err, port) => { + server.listen(port); - const pool = mysql.createPool({ - user: 'test_user', - password: 'test', - database: 'test_database', - port: port, - }); + const checkDone = () => { + if (done || err1 === undefined || err2 === undefined) return; + done = true; + assert.equal(err1?.errno, 1040); + assert.equal(err2?.errno, 1040); + server.close(() => resolve()); + }; - pool.query('test sql', (err) => { - err2 = err ?? undefined; - pool.end(); - // @ts-expect-error: TODO: implement typings - server.close(); - }); -}); + const conn = mysql.createConnection({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + }); + conn.on('error', (err) => { + err1 = err; + checkDone(); + }); -process.on('exit', () => { - assert.equal(err1?.errno, 1040); - assert.equal(err2?.errno, 1040); + const pool = mysql.createPool({ + user: 'test_user', + password: 'test', + database: 'test_database', + port: port, + }); + + pool.query('test sql', (err) => { + err2 = err ?? undefined; + pool.end(); + checkDone(); + }); + }); + }); + }); }); From efed4cbab708bc053a14e98709439f420747be31 Mon Sep 17 00:00:00 2001 From: wellwelwel <46850407+wellwelwel@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:56:19 -0300 Subject: [PATCH 4/4] debug: remove debug 2 --- .../integration/test-pool-disconnect.test.mts | 107 ++++++++++-------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/test/esm/integration/test-pool-disconnect.test.mts b/test/esm/integration/test-pool-disconnect.test.mts index a17e2b6b2b..32afd57693 100644 --- a/test/esm/integration/test-pool-disconnect.test.mts +++ b/test/esm/integration/test-pool-disconnect.test.mts @@ -4,7 +4,7 @@ import type { RowDataPacket, } from '../../../index.js'; import process from 'node:process'; -import { assert } from 'poku'; +import { assert, describe, it } from 'poku'; import { createConnection, createPool } from '../common.test.mjs'; // planetscale does not support KILL, skipping this test @@ -14,60 +14,67 @@ if (`${process.env.MYSQL_CONNECTION_URL}`.includes('pscale_pw_')) { process.exit(0); } -const pool = createPool(); -const conn = createConnection({ multipleStatements: true }); -pool.config.connectionLimit = 5; +await describe('Pool Disconnect', async () => { + await it('should handle pool connection kills', async () => { + const pool = createPool(); + const conn = createConnection({ multipleStatements: true }); + pool.config.connectionLimit = 5; -const numSelectToPerform = 10; -const tids: number[] = []; -let numSelects = 0; -let killCount = 0; + const numSelectToPerform = 10; + const tids: number[] = []; + let numSelects = 0; + let killCount = 0; -function kill() { - setTimeout(() => { - const id = tids.shift(); - if (typeof id !== 'undefined') { - // sleep required to give mysql time to close connection, - // and callback called after connection with id is really closed - conn.query('kill ?; select sleep(0.05)', [id], (err) => { - assert.ifError(err); - killCount++; - // TODO: this assertion needs to be fixed, after kill - // connection is removed from _allConnections but not at a point this callback is called - // - // assert.equal(pool._allConnections.length, tids.length); - }); - } else { - conn.end(); - pool.end(); - } - }, 5); -} + await new Promise((resolve) => { + const kill = () => { + setTimeout(() => { + const id = tids.shift(); + if (typeof id !== 'undefined') { + // sleep required to give mysql time to close connection, + // and callback called after connection with id is really closed + conn.query('kill ?; select sleep(0.05)', [id], (err) => { + assert.ifError(err); + killCount++; + // TODO: this assertion needs to be fixed, after kill + // connection is removed from _allConnections but not at a point this callback is called + // + // assert.equal(pool._allConnections.length, tids.length); + if (killCount === pool.config.connectionLimit) { + resolve(); + } + }); + } else { + conn.end(); + pool.end(); + } + }, 5); + }; -pool.on('connection', (conn: PoolConnection) => { - tids.push(conn.threadId); - conn.on('error', () => { - setTimeout(kill, 5); - }); -}); + pool.on('connection', (conn: PoolConnection) => { + tids.push(conn.threadId); + conn.on('error', () => { + setTimeout(kill, 5); + }); + }); -for (let i = 0; i < numSelectToPerform; i++) { - pool.query( - 'select 1 as value', - (err: QueryError | null, rows: RowDataPacket[]) => { - numSelects++; - assert.ifError(err); - assert.equal(rows[0].value, 1); + for (let i = 0; i < numSelectToPerform; i++) { + pool.query( + 'select 1 as value', + (err: QueryError | null, rows: RowDataPacket[]) => { + numSelects++; + assert.ifError(err); + assert.equal(rows[0].value, 1); - // after all queries complete start killing connections - if (numSelects === numSelectToPerform) { - kill(); + // after all queries complete start killing connections + if (numSelects === numSelectToPerform) { + kill(); + } + } + ); } - } - ); -} + }); -process.on('exit', () => { - assert.equal(numSelects, numSelectToPerform); - assert.equal(killCount, pool.config.connectionLimit); + assert.equal(numSelects, numSelectToPerform); + assert.equal(killCount, pool.config.connectionLimit); + }); });