From 721492e76d215b075e2f8952b0b87ef2b6545392 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 May 2023 13:25:35 +0200 Subject: [PATCH] Validate mysql --- packages/server/src/integrations/mysql.ts | 24 +++++-- .../integrations/validators/postgres.spec.ts | 62 +++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index ceec64fac7..89a8297818 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -85,8 +85,6 @@ const SCHEMA: Integration = { }, } -const TimezoneAwareDateTypes = ["timestamp"] - function bindingTypeCoerce(bindings: any[]) { for (let i = 0; i < bindings.length; i++) { const binding = bindings[i] @@ -113,7 +111,7 @@ function bindingTypeCoerce(bindings: any[]) { class MySQLIntegration extends Sql implements DatasourcePlus { private config: MySQLConfig - private client: any + private client?: mysql.Connection public tables: Record = {} public schemaErrors: Record = {} @@ -167,7 +165,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus { } async disconnect() { - await this.client.end() + await this.client!.end() } async internalQuery( @@ -186,10 +184,10 @@ class MySQLIntegration extends Sql implements DatasourcePlus { ? baseBindings : bindingTypeCoerce(baseBindings) // Node MySQL is callback based, so we must wrap our call in a promise - const response = await this.client.query(query.sql, bindings) + const response = await this.client!.query(query.sql, bindings) return response[0] } finally { - if (opts?.connect) { + if (opts?.connect && this.client) { await this.disconnect() } } @@ -288,7 +286,21 @@ class MySQLIntegration extends Sql implements DatasourcePlus { } } +async function validateConnection(config: MySQLConfig) { + const integration = new MySQLIntegration(config) + try { + const [result] = await integration.internalQuery( + { sql: "SELECT 1+1 AS checkRes" }, + { connect: true } + ) + return result?.checkRes == 2 + } catch (e: any) { + return { error: e.message as string } + } +} + export default { schema: SCHEMA, integration: MySQLIntegration, + validateConnection, } diff --git a/qa-core/src/integrations/validators/postgres.spec.ts b/qa-core/src/integrations/validators/postgres.spec.ts index 5aa3250e2a..c5f6fae1ad 100644 --- a/qa-core/src/integrations/validators/postgres.spec.ts +++ b/qa-core/src/integrations/validators/postgres.spec.ts @@ -2,6 +2,7 @@ import { GenericContainer } from "testcontainers" import postgres from "../../../../packages/server/src/integrations/postgres" jest.unmock("pg") +jest.unmock("mysql2/promise") describe("datasource validators", () => { describe("postgres", () => { @@ -50,4 +51,65 @@ describe("datasource validators", () => { }) }) }) + + describe("mysql", () => { + const validator = integrations.getValidator[SourceName.MYSQL]! + + let host: string + let port: number + + beforeAll(async () => { + const container = await new GenericContainer("mysql") + .withExposedPorts(3306) + .withEnv("MYSQL_ROOT_PASSWORD", "admin") + .withEnv("MYSQL_DATABASE", "db") + .withEnv("MYSQL_USER", "user") + .withEnv("MYSQL_PASSWORD", "password") + .start() + + host = container.getContainerIpAddress() + port = container.getMappedPort(3306) + }) + + it("test valid connection string", async () => { + const result = await validator({ + host, + port, + user: "user", + database: "db", + password: "password", + rejectUnauthorized: true, + }) + expect(result).toBe(true) + }) + + it("test invalid database", async () => { + const result = await validator({ + host, + port, + user: "user", + database: "test", + password: "password", + rejectUnauthorized: true, + }) + expect(result).toEqual({ + error: "Access denied for user 'user'@'%' to database 'test'", + }) + }) + + it("test invalid password", async () => { + const result = await validator({ + host, + port, + user: "root", + database: "test", + password: "wrong", + rejectUnauthorized: true, + }) + expect(result).toEqual({ + error: + "Access denied for user 'root'@'172.17.0.1' (using password: YES)", + }) + }) + }) })