From 209ada0c389809c8edb464eb6bf1d04c71455832 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 May 2023 15:58:51 +0200 Subject: [PATCH 1/5] Validate couchdb --- .../backend-core/src/db/couch/DatabaseImpl.ts | 14 ++++++++++---- packages/backend-core/src/db/couch/utils.ts | 16 +++++++++++++++- packages/server/src/integrations/couchdb.ts | 19 ++++++++++++++----- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 94d78e94ff..29ca4123f5 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -12,7 +12,7 @@ import { isDocument, } from "@budibase/types" import { getCouchInfo } from "./connections" -import { directCouchCall } from "./utils" +import { directCouchUrlCall } from "./utils" import { getPouchDB } from "./pouchDB" import { WriteStream, ReadStream } from "fs" import { newid } from "../../docIds/newid" @@ -46,6 +46,8 @@ export class DatabaseImpl implements Database { private readonly instanceNano?: Nano.ServerScope private readonly pouchOpts: DatabaseOpts + private readonly couchInfo = getCouchInfo() + constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) { if (dbName == null) { throw new Error("Database name cannot be undefined.") @@ -53,8 +55,8 @@ export class DatabaseImpl implements Database { this.name = dbName this.pouchOpts = opts || {} if (connection) { - const couchInfo = getCouchInfo(connection) - this.instanceNano = buildNano(couchInfo) + this.couchInfo = getCouchInfo(connection) + this.instanceNano = buildNano(this.couchInfo) } if (!DatabaseImpl.nano) { DatabaseImpl.init() @@ -67,7 +69,11 @@ export class DatabaseImpl implements Database { } async exists() { - let response = await directCouchCall(`/${this.name}`, "HEAD") + const response = await directCouchUrlCall({ + url: `${this.couchInfo.url}/${this.name}`, + method: "HEAD", + cookie: this.couchInfo.cookie, + }) return response.status === 200 } diff --git a/packages/backend-core/src/db/couch/utils.ts b/packages/backend-core/src/db/couch/utils.ts index 426bf92158..51b2a38998 100644 --- a/packages/backend-core/src/db/couch/utils.ts +++ b/packages/backend-core/src/db/couch/utils.ts @@ -9,6 +9,20 @@ export async function directCouchCall( ) { let { url, cookie } = getCouchInfo() const couchUrl = `${url}/${path}` + return await directCouchUrlCall({ url: couchUrl, cookie, method, body }) +} + +export async function directCouchUrlCall({ + url, + cookie, + method, + body, +}: { + url: string + cookie: string + method: string + body?: any +}) { const params: any = { method: method, headers: { @@ -19,7 +33,7 @@ export async function directCouchCall( params.body = JSON.stringify(body) params.headers["Content-Type"] = "application/json" } - return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params) + return await fetch(checkSlashesInUrl(encodeURI(url)), params) } export async function directCouchQuery( diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index 257b84ca13..1c6c978612 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -7,7 +7,7 @@ import { } from "@budibase/types" import { db as dbCore } from "@budibase/backend-core" -interface CouchDBConfig { +export interface CouchDBConfig { url: string database: string } @@ -61,11 +61,9 @@ const SCHEMA: Integration = { } class CouchDBIntegration implements IntegrationBase { - private config: CouchDBConfig - private readonly client: any + private readonly client: dbCore.DatabaseImpl constructor(config: CouchDBConfig) { - this.config = config this.client = dbCore.DatabaseWithConnection(config.database, config.url) } @@ -75,7 +73,7 @@ class CouchDBIntegration implements IntegrationBase { query: { json?: object; id?: string } ) { try { - return await this.client[command](query.id || query.json) + return await (this.client as any)[command](query.id || query.json) } catch (err) { console.error(errorMsg, err) throw err @@ -127,7 +125,18 @@ class CouchDBIntegration implements IntegrationBase { } } +async function validateConnection(config: CouchDBConfig) { + const integration = new CouchDBIntegration(config) + try { + const result = await integration.query("exists", "validation error", {}) + return result === true + } catch (e: any) { + return { error: e.message as string } + } +} + export default { schema: SCHEMA, integration: CouchDBIntegration, + validateConnection, } From 5456866c45ed0f5d47ad022f1449f9e1e9fc03a1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 May 2023 17:14:51 +0200 Subject: [PATCH 2/5] Fix tests to use connection string instead of env --- .../backend-core/src/db/couch/connections.ts | 16 ++++++++-------- .../src/integrations/validators/postgres.spec.ts | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/backend-core/src/db/couch/connections.ts b/packages/backend-core/src/db/couch/connections.ts index 06c661f350..4214c7cdc6 100644 --- a/packages/backend-core/src/db/couch/connections.ts +++ b/packages/backend-core/src/db/couch/connections.ts @@ -4,21 +4,21 @@ export const getCouchInfo = (connection?: string) => { const urlInfo = getUrlInfo(connection) let username let password - if (env.COUCH_DB_USERNAME) { - // set from env - username = env.COUCH_DB_USERNAME - } else if (urlInfo.auth.username) { + if (urlInfo.auth?.username) { // set from url username = urlInfo.auth.username + } else if (env.COUCH_DB_USERNAME) { + // set from env + username = env.COUCH_DB_USERNAME } else if (!env.isTest()) { throw new Error("CouchDB username not set") } - if (env.COUCH_DB_PASSWORD) { - // set from env - password = env.COUCH_DB_PASSWORD - } else if (urlInfo.auth.password) { + if (urlInfo.auth?.password) { // set from url password = urlInfo.auth.password + } else if (env.COUCH_DB_PASSWORD) { + // set from env + password = env.COUCH_DB_PASSWORD } else if (!env.isTest()) { throw new Error("CouchDB password not set") } diff --git a/qa-core/src/integrations/validators/postgres.spec.ts b/qa-core/src/integrations/validators/postgres.spec.ts index 5aa3250e2a..6e1219540b 100644 --- a/qa-core/src/integrations/validators/postgres.spec.ts +++ b/qa-core/src/integrations/validators/postgres.spec.ts @@ -1,6 +1,8 @@ import { GenericContainer } from "testcontainers" import postgres from "../../../../packages/server/src/integrations/postgres" +import { generator } from "@budibase/backend-core/tests" + jest.unmock("pg") describe("datasource validators", () => { From d656edad4014014d259cf897b32c57734c40a4ea Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 12 May 2023 12:35:07 +0200 Subject: [PATCH 3/5] Implement the check as part of the integration --- packages/server/src/integrations/couchdb.ts | 20 +++---- .../integrations/validators/postgres.spec.ts | 60 +++++++++++++++++++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index 1c6c978612..a4692c7cd6 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -67,6 +67,15 @@ class CouchDBIntegration implements IntegrationBase { this.client = dbCore.DatabaseWithConnection(config.database, config.url) } + async testConnection() { + try { + const result = await this.query("exists", "validation error", {}) + return result === true + } catch (e: any) { + return { error: e.message as string } + } + } + async query( command: string, errorMsg: string, @@ -125,18 +134,7 @@ class CouchDBIntegration implements IntegrationBase { } } -async function validateConnection(config: CouchDBConfig) { - const integration = new CouchDBIntegration(config) - try { - const result = await integration.query("exists", "validation error", {}) - return result === true - } catch (e: any) { - return { error: e.message as string } - } -} - export default { schema: SCHEMA, integration: CouchDBIntegration, - validateConnection, } diff --git a/qa-core/src/integrations/validators/postgres.spec.ts b/qa-core/src/integrations/validators/postgres.spec.ts index 6e1219540b..78bda902d2 100644 --- a/qa-core/src/integrations/validators/postgres.spec.ts +++ b/qa-core/src/integrations/validators/postgres.spec.ts @@ -3,6 +3,8 @@ import postgres from "../../../../packages/server/src/integrations/postgres" import { generator } from "@budibase/backend-core/tests" +import couchdb from "../../../../packages/server/src/integrations/couchdb" + jest.unmock("pg") describe("datasource validators", () => { @@ -52,4 +54,62 @@ describe("datasource validators", () => { }) }) }) + + describe("couchdb", () => { + let url: string + + beforeAll(async () => { + const user = generator.first() + const password = generator.hash() + + const container = await new GenericContainer("budibase/couchdb") + .withExposedPorts(5984) + .withEnv("COUCHDB_USER", user) + .withEnv("COUCHDB_PASSWORD", password) + .start() + + const host = container.getContainerIpAddress() + const port = container.getMappedPort(5984) + + await container.exec([ + `curl`, + `-u`, + `${user}:${password}`, + `-X`, + `PUT`, + `localhost:5984/db`, + ]) + url = `http://${user}:${password}@${host}:${port}` + }) + + it("test valid connection string", async () => { + const integration = new couchdb.integration({ + url, + database: "db", + }) + const result = await integration.testConnection() + expect(result).toBe(true) + }) + + it("test invalid database", async () => { + const integration = new couchdb.integration({ + url, + database: "random_db", + }) + const result = await integration.testConnection() + expect(result).toBe(false) + }) + + it("test invalid url", async () => { + const integration = new couchdb.integration({ + url: "http://invalid:123", + database: "any", + }) + const result = await integration.testConnection() + expect(result).toEqual({ + error: + "request to http://invalid:123/any failed, reason: getaddrinfo ENOTFOUND invalid", + }) + }) + }) }) From 33988428ea24894db285529c88821bc9b60b3297 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 15 May 2023 13:52:55 +0200 Subject: [PATCH 4/5] Split tests --- .../src/integrations/validators/couch.spec.ts | 64 +++++++++++++++++++ .../integrations/validators/postgres.spec.ts | 62 ------------------ 2 files changed, 64 insertions(+), 62 deletions(-) create mode 100644 qa-core/src/integrations/validators/couch.spec.ts diff --git a/qa-core/src/integrations/validators/couch.spec.ts b/qa-core/src/integrations/validators/couch.spec.ts new file mode 100644 index 0000000000..18fbea8c43 --- /dev/null +++ b/qa-core/src/integrations/validators/couch.spec.ts @@ -0,0 +1,64 @@ +import { GenericContainer } from "testcontainers" +import { generator } from "@budibase/backend-core/tests" + +import couchdb from "../../../../packages/server/src/integrations/couchdb" + +describe("datasource validators", () => { + describe("couchdb", () => { + let url: string + + beforeAll(async () => { + const user = generator.first() + const password = generator.hash() + + const container = await new GenericContainer("budibase/couchdb") + .withExposedPorts(5984) + .withEnv("COUCHDB_USER", user) + .withEnv("COUCHDB_PASSWORD", password) + .start() + + const host = container.getContainerIpAddress() + const port = container.getMappedPort(5984) + + await container.exec([ + `curl`, + `-u`, + `${user}:${password}`, + `-X`, + `PUT`, + `localhost:5984/db`, + ]) + url = `http://${user}:${password}@${host}:${port}` + }) + + it("test valid connection string", async () => { + const integration = new couchdb.integration({ + url, + database: "db", + }) + const result = await integration.testConnection() + expect(result).toBe(true) + }) + + it("test invalid database", async () => { + const integration = new couchdb.integration({ + url, + database: "random_db", + }) + const result = await integration.testConnection() + expect(result).toBe(false) + }) + + it("test invalid url", async () => { + const integration = new couchdb.integration({ + url: "http://invalid:123", + database: "any", + }) + const result = await integration.testConnection() + expect(result).toEqual({ + error: + "request to http://invalid:123/any failed, reason: getaddrinfo ENOTFOUND invalid", + }) + }) + }) +}) diff --git a/qa-core/src/integrations/validators/postgres.spec.ts b/qa-core/src/integrations/validators/postgres.spec.ts index 78bda902d2..5aa3250e2a 100644 --- a/qa-core/src/integrations/validators/postgres.spec.ts +++ b/qa-core/src/integrations/validators/postgres.spec.ts @@ -1,10 +1,6 @@ import { GenericContainer } from "testcontainers" import postgres from "../../../../packages/server/src/integrations/postgres" -import { generator } from "@budibase/backend-core/tests" - -import couchdb from "../../../../packages/server/src/integrations/couchdb" - jest.unmock("pg") describe("datasource validators", () => { @@ -54,62 +50,4 @@ describe("datasource validators", () => { }) }) }) - - describe("couchdb", () => { - let url: string - - beforeAll(async () => { - const user = generator.first() - const password = generator.hash() - - const container = await new GenericContainer("budibase/couchdb") - .withExposedPorts(5984) - .withEnv("COUCHDB_USER", user) - .withEnv("COUCHDB_PASSWORD", password) - .start() - - const host = container.getContainerIpAddress() - const port = container.getMappedPort(5984) - - await container.exec([ - `curl`, - `-u`, - `${user}:${password}`, - `-X`, - `PUT`, - `localhost:5984/db`, - ]) - url = `http://${user}:${password}@${host}:${port}` - }) - - it("test valid connection string", async () => { - const integration = new couchdb.integration({ - url, - database: "db", - }) - const result = await integration.testConnection() - expect(result).toBe(true) - }) - - it("test invalid database", async () => { - const integration = new couchdb.integration({ - url, - database: "random_db", - }) - const result = await integration.testConnection() - expect(result).toBe(false) - }) - - it("test invalid url", async () => { - const integration = new couchdb.integration({ - url: "http://invalid:123", - database: "any", - }) - const result = await integration.testConnection() - expect(result).toEqual({ - error: - "request to http://invalid:123/any failed, reason: getaddrinfo ENOTFOUND invalid", - }) - }) - }) }) From bd736836f0267d8418de26ca6d3bb8b3514f195a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 15 May 2023 13:55:22 +0200 Subject: [PATCH 5/5] Remove export --- packages/server/src/integrations/couchdb.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index a4692c7cd6..24100f9c9f 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -7,7 +7,7 @@ import { } from "@budibase/types" import { db as dbCore } from "@budibase/backend-core" -export interface CouchDBConfig { +interface CouchDBConfig { url: string database: string }