From 2b486ea9fcc649b7c25c1e331191e6df17028c3b Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sun, 7 Jun 2026 23:16:27 +0800 Subject: [PATCH 1/2] fix: correct +09:00 timezone test assertion and drop redundant guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MySQL integration test for the new `timezone` source option (#321) asserted the wrong UTC instant. KST is UTC+9, so the naive DATETIME `2025-09-29 02:31:23` interpreted as `+09:00` is `2025-09-28T17:31:23Z` (the previous day), not `2025-09-29T17:31:23Z`. mysql2's parseDateTime does `new Date(`${str}${timezone}`)`, which produces the previous-day instant — so the assertion would have failed against a real container, indicating the integration suite was not run before merge. Fix the expected value and the explanatory comment. Also drop the redundant `typeof source.timezone !== "string"` guard in the TOML timezone validation: the field is already narrowed to a string, and RegExp.test() coerces safely, so the guard can never be the sole cause of a throw (unlike search_path's guard, which is load-bearing for its .trim() call). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/config/toml-loader.ts | 5 +---- src/connectors/__tests__/mysql.integration.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/config/toml-loader.ts b/src/config/toml-loader.ts index 04f5d42d..8371bbf0 100644 --- a/src/config/toml-loader.ts +++ b/src/config/toml-loader.ts @@ -458,10 +458,7 @@ function validateSourceConfig(source: SourceConfig, configPath: string): void { ); } // Accepted by mysql2/mariadb drivers: "local", "Z", or "±HH:MM" (e.g., "+09:00") - if ( - typeof source.timezone !== "string" || - !/^(?:local|Z|[+-]\d\d:\d\d)$/.test(source.timezone) - ) { + if (!/^(?:local|Z|[+-]\d\d:\d\d)$/.test(source.timezone)) { throw new Error( `Configuration file ${configPath}: source '${source.id}' has invalid timezone '${source.timezone}'. ` + `Must be "local", "Z" (UTC), or an offset like "+09:00".` diff --git a/src/connectors/__tests__/mysql.integration.test.ts b/src/connectors/__tests__/mysql.integration.test.ts index 39575463..ff8cbee7 100644 --- a/src/connectors/__tests__/mysql.integration.test.ts +++ b/src/connectors/__tests__/mysql.integration.test.ts @@ -407,7 +407,8 @@ describe('MySQL Connector Integration Tests', () => { const connector = new MySQLConnector(); try { // With timezone "+09:00", the driver reads the naive DATETIME as KST and - // produces the correct UTC instant: 02:31:23 KST == 17:31:23 UTC. + // produces the correct UTC instant. KST is UTC+9, so 02:31:23 on Sep 29 + // is 17:31:23 UTC on the previous day (Sep 28). await connector.connect(mysqlTest.connectionString, undefined, { timezone: '+09:00', }); @@ -419,7 +420,7 @@ describe('MySQL Connector Integration Tests', () => { expect(result.rows).toHaveLength(1); const iso = new Date(result.rows[0].dt as string | Date).toISOString(); - expect(iso).toBe('2025-09-29T17:31:23.000Z'); + expect(iso).toBe('2025-09-28T17:31:23.000Z'); } finally { await connector.disconnect(); } From 112f182ccd36da8d1bb1d019153dc44c0ba3dc9a Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sun, 7 Jun 2026 23:21:34 +0800 Subject: [PATCH 2/2] fix: restore typeof guard in timezone validation Removing the typeof check opened a validation hole flagged in review: a single-element TOML array such as timezone = ["local"] coerces via RegExp.test() to the passing string "local", so the array would reach the driver as a non-string. Restore the typeof guard and add a regression test for the array case. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/config/__tests__/toml-loader.test.ts | 14 ++++++++++++++ src/config/toml-loader.ts | 10 ++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/config/__tests__/toml-loader.test.ts b/src/config/__tests__/toml-loader.test.ts index a201f028..84863e69 100644 --- a/src/config/__tests__/toml-loader.test.ts +++ b/src/config/__tests__/toml-loader.test.ts @@ -1091,6 +1091,20 @@ timezone = "Asia/Seoul" expect(() => loadTomlConfig()).toThrow('invalid timezone'); }); + it('should throw error for non-string timezone (TOML array)', () => { + // ["local"] coerces to the string "local" via RegExp.test(), so the + // typeof guard is required to reject it before it reaches the driver. + const tomlContent = ` +[[sources]] +id = "test_db" +dsn = "mysql://user:pass@localhost:3306/testdb" +timezone = ["local"] +`; + fs.writeFileSync(path.join(tempDir, 'dbhub.toml'), tomlContent); + + expect(() => loadTomlConfig()).toThrow('invalid timezone'); + }); + it('should work without timezone (optional field)', () => { const tomlContent = ` [[sources]] diff --git a/src/config/toml-loader.ts b/src/config/toml-loader.ts index 8371bbf0..07ccb8b3 100644 --- a/src/config/toml-loader.ts +++ b/src/config/toml-loader.ts @@ -457,8 +457,14 @@ function validateSourceConfig(source: SourceConfig, configPath: string): void { `Configuration file ${configPath}: source '${source.id}' has 'timezone' but it is only supported for MySQL and MariaDB sources.` ); } - // Accepted by mysql2/mariadb drivers: "local", "Z", or "±HH:MM" (e.g., "+09:00") - if (!/^(?:local|Z|[+-]\d\d:\d\d)$/.test(source.timezone)) { + // Accepted by mysql2/mariadb drivers: "local", "Z", or "±HH:MM" (e.g., "+09:00"). + // The typeof guard is required: TOML can yield non-strings (e.g. arrays), and + // RegExp.test() would coerce a single-element array like ["local"] to a passing + // string before it reaches the driver as a non-string value. + if ( + typeof source.timezone !== "string" || + !/^(?:local|Z|[+-]\d\d:\d\d)$/.test(source.timezone) + ) { throw new Error( `Configuration file ${configPath}: source '${source.id}' has invalid timezone '${source.timezone}'. ` + `Must be "local", "Z" (UTC), or an offset like "+09:00".`