From f10a66cc421057e773281e73543633a3f48933f2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 May 2023 11:29:02 +0200 Subject: [PATCH 1/4] Type mysql configs --- packages/server/src/integrations/mysql.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 8d984ed402..ceec64fac7 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -20,18 +20,11 @@ import { NUMBER_REGEX } from "../utilities" import Sql from "./base/sql" import { MySQLColumn } from "./base/types" -const mysql = require("mysql2/promise") +import mysql from "mysql2/promise" -interface MySQLConfig { - host: string - port: number - user: string - password: string +export interface MySQLConfig extends mysql.ConnectionOptions { database: string - ssl?: { [key: string]: any } rejectUnauthorized: boolean - typeCast: Function - multipleStatements: boolean } const SCHEMA: Integration = { @@ -134,7 +127,8 @@ class MySQLIntegration extends Sql implements DatasourcePlus { if ( config.rejectUnauthorized != null && !config.rejectUnauthorized && - config.ssl + config.ssl && + typeof config.ssl !== "string" ) { config.ssl.rejectUnauthorized = config.rejectUnauthorized } From 721492e76d215b075e2f8952b0b87ef2b6545392 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 May 2023 13:25:35 +0200 Subject: [PATCH 2/4] 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)", + }) + }) + }) }) From 8a1564ef076d0120dcb5a9d7e86031488c22d75b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 12 May 2023 12:29:52 +0200 Subject: [PATCH 3/4] Implement the check as part of the integration --- packages/server/src/integrations/mysql.ts | 28 +++++++++---------- .../integrations/validators/postgres.spec.ts | 12 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 89a8297818..199ed6fc7d 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -22,7 +22,7 @@ import { MySQLColumn } from "./base/types" import mysql from "mysql2/promise" -export interface MySQLConfig extends mysql.ConnectionOptions { +interface MySQLConfig extends mysql.ConnectionOptions { database: string rejectUnauthorized: boolean } @@ -152,6 +152,18 @@ class MySQLIntegration extends Sql implements DatasourcePlus { } } + async testConnection() { + try { + const [result] = await this.internalQuery( + { sql: "SELECT 1+1 AS checkRes" }, + { connect: true } + ) + return result?.checkRes == 2 + } catch (e: any) { + return { error: e.message as string } + } + } + getBindingIdentifier(): string { return "?" } @@ -286,21 +298,7 @@ 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 c5f6fae1ad..07f2b80f1a 100644 --- a/qa-core/src/integrations/validators/postgres.spec.ts +++ b/qa-core/src/integrations/validators/postgres.spec.ts @@ -1,5 +1,6 @@ import { GenericContainer } from "testcontainers" import postgres from "../../../../packages/server/src/integrations/postgres" +import mysql from "../../../../packages/server/src/integrations/mysql" jest.unmock("pg") jest.unmock("mysql2/promise") @@ -53,8 +54,6 @@ describe("datasource validators", () => { }) describe("mysql", () => { - const validator = integrations.getValidator[SourceName.MYSQL]! - let host: string let port: number @@ -72,7 +71,7 @@ describe("datasource validators", () => { }) it("test valid connection string", async () => { - const result = await validator({ + const integration = new mysql.integration({ host, port, user: "user", @@ -80,11 +79,12 @@ describe("datasource validators", () => { password: "password", rejectUnauthorized: true, }) + const result = await integration.testConnection() expect(result).toBe(true) }) it("test invalid database", async () => { - const result = await validator({ + const integration = new mysql.integration({ host, port, user: "user", @@ -92,13 +92,14 @@ describe("datasource validators", () => { password: "password", rejectUnauthorized: true, }) + const result = await integration.testConnection() expect(result).toEqual({ error: "Access denied for user 'user'@'%' to database 'test'", }) }) it("test invalid password", async () => { - const result = await validator({ + const integration = new mysql.integration({ host, port, user: "root", @@ -106,6 +107,7 @@ describe("datasource validators", () => { password: "wrong", rejectUnauthorized: true, }) + const result = await integration.testConnection() expect(result).toEqual({ error: "Access denied for user 'root'@'172.17.0.1' (using password: YES)", From e92b9a8abca73822747d330d8faa46756bb45207 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 15 May 2023 13:39:31 +0200 Subject: [PATCH 4/4] Split testss --- .../src/integrations/validators/mysql.spec.ts | 68 +++++++++++++++++++ .../integrations/validators/postgres.spec.ts | 64 ----------------- 2 files changed, 68 insertions(+), 64 deletions(-) create mode 100644 qa-core/src/integrations/validators/mysql.spec.ts diff --git a/qa-core/src/integrations/validators/mysql.spec.ts b/qa-core/src/integrations/validators/mysql.spec.ts new file mode 100644 index 0000000000..166085fdd5 --- /dev/null +++ b/qa-core/src/integrations/validators/mysql.spec.ts @@ -0,0 +1,68 @@ +import { GenericContainer } from "testcontainers" +import mysql from "../../../../packages/server/src/integrations/mysql" + +jest.unmock("mysql2/promise") + +describe("datasource validators", () => { + describe("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 integration = new mysql.integration({ + host, + port, + user: "user", + database: "db", + password: "password", + rejectUnauthorized: true, + }) + const result = await integration.testConnection() + expect(result).toBe(true) + }) + + it("test invalid database", async () => { + const integration = new mysql.integration({ + host, + port, + user: "user", + database: "test", + password: "password", + rejectUnauthorized: true, + }) + const result = await integration.testConnection() + expect(result).toEqual({ + error: "Access denied for user 'user'@'%' to database 'test'", + }) + }) + + it("test invalid password", async () => { + const integration = new mysql.integration({ + host, + port, + user: "root", + database: "test", + password: "wrong", + rejectUnauthorized: true, + }) + const result = await integration.testConnection() + expect(result).toEqual({ + error: + "Access denied for user 'root'@'172.17.0.1' (using password: YES)", + }) + }) + }) +}) diff --git a/qa-core/src/integrations/validators/postgres.spec.ts b/qa-core/src/integrations/validators/postgres.spec.ts index 07f2b80f1a..5aa3250e2a 100644 --- a/qa-core/src/integrations/validators/postgres.spec.ts +++ b/qa-core/src/integrations/validators/postgres.spec.ts @@ -1,9 +1,7 @@ import { GenericContainer } from "testcontainers" import postgres from "../../../../packages/server/src/integrations/postgres" -import mysql from "../../../../packages/server/src/integrations/mysql" jest.unmock("pg") -jest.unmock("mysql2/promise") describe("datasource validators", () => { describe("postgres", () => { @@ -52,66 +50,4 @@ describe("datasource validators", () => { }) }) }) - - describe("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 integration = new mysql.integration({ - host, - port, - user: "user", - database: "db", - password: "password", - rejectUnauthorized: true, - }) - const result = await integration.testConnection() - expect(result).toBe(true) - }) - - it("test invalid database", async () => { - const integration = new mysql.integration({ - host, - port, - user: "user", - database: "test", - password: "password", - rejectUnauthorized: true, - }) - const result = await integration.testConnection() - expect(result).toEqual({ - error: "Access denied for user 'user'@'%' to database 'test'", - }) - }) - - it("test invalid password", async () => { - const integration = new mysql.integration({ - host, - port, - user: "root", - database: "test", - password: "wrong", - rejectUnauthorized: true, - }) - const result = await integration.testConnection() - expect(result).toEqual({ - error: - "Access denied for user 'root'@'172.17.0.1' (using password: YES)", - }) - }) - }) })