diff --git a/src/config/__tests__/toml-loader.test.ts b/src/config/__tests__/toml-loader.test.ts index a201f02..84863e6 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 04f5d42..07ccb8b 100644 --- a/src/config/toml-loader.ts +++ b/src/config/toml-loader.ts @@ -457,7 +457,10 @@ 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") + // 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) diff --git a/src/connectors/__tests__/mysql.integration.test.ts b/src/connectors/__tests__/mysql.integration.test.ts index 3957546..ff8cbee 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(); }