@budibase/server 2.22.15 → 2.22.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@ import {
3
3
  generateMakeRequest,
4
4
  MakeRequestResponse,
5
5
  } from "../api/routes/public/tests/utils"
6
- import { v4 as uuidv4 } from "uuid"
7
6
  import * as setup from "../api/routes/tests/utilities"
8
7
  import {
9
8
  Datasource,
@@ -12,12 +11,23 @@ import {
12
11
  TableRequest,
13
12
  TableSourceType,
14
13
  } from "@budibase/types"
15
- import { databaseTestProviders } from "../integrations/tests/utils"
16
- import mysql from "mysql2/promise"
14
+ import {
15
+ DatabaseName,
16
+ getDatasource,
17
+ rawQuery,
18
+ } from "../integrations/tests/utils"
17
19
  import { builderSocket } from "../websockets"
20
+ import { generator } from "@budibase/backend-core/tests"
18
21
  // @ts-ignore
19
22
  fetch.mockSearch()
20
23
 
24
+ function uniqueTableName(length?: number): string {
25
+ return generator
26
+ .guid()
27
+ .replaceAll("-", "_")
28
+ .substring(0, length || 10)
29
+ }
30
+
21
31
  const config = setup.getConfig()!
22
32
 
23
33
  jest.mock("../websockets", () => ({
@@ -37,7 +47,8 @@ jest.mock("../websockets", () => ({
37
47
 
38
48
  describe("mysql integrations", () => {
39
49
  let makeRequest: MakeRequestResponse,
40
- mysqlDatasource: Datasource,
50
+ rawDatasource: Datasource,
51
+ datasource: Datasource,
41
52
  primaryMySqlTable: Table
42
53
 
43
54
  beforeAll(async () => {
@@ -46,18 +57,13 @@ describe("mysql integrations", () => {
46
57
 
47
58
  makeRequest = generateMakeRequest(apiKey, true)
48
59
 
49
- mysqlDatasource = await config.api.datasource.create(
50
- await databaseTestProviders.mysql.datasource()
51
- )
52
- })
53
-
54
- afterAll(async () => {
55
- await databaseTestProviders.mysql.stop()
60
+ rawDatasource = await getDatasource(DatabaseName.MYSQL)
61
+ datasource = await config.api.datasource.create(rawDatasource)
56
62
  })
57
63
 
58
64
  beforeEach(async () => {
59
65
  primaryMySqlTable = await config.createTable({
60
- name: uuidv4(),
66
+ name: uniqueTableName(),
61
67
  type: "table",
62
68
  primary: ["id"],
63
69
  schema: {
@@ -79,7 +85,7 @@ describe("mysql integrations", () => {
79
85
  type: FieldType.NUMBER,
80
86
  },
81
87
  },
82
- sourceId: mysqlDatasource._id,
88
+ sourceId: datasource._id,
83
89
  sourceType: TableSourceType.EXTERNAL,
84
90
  })
85
91
  })
@@ -87,18 +93,15 @@ describe("mysql integrations", () => {
87
93
  afterAll(config.end)
88
94
 
89
95
  it("validate table schema", async () => {
90
- const res = await makeRequest(
91
- "get",
92
- `/api/datasources/${mysqlDatasource._id}`
93
- )
96
+ const res = await makeRequest("get", `/api/datasources/${datasource._id}`)
94
97
 
95
98
  expect(res.status).toBe(200)
96
99
  expect(res.body).toEqual({
97
100
  config: {
98
- database: "mysql",
99
- host: mysqlDatasource.config!.host,
101
+ database: expect.any(String),
102
+ host: datasource.config!.host,
100
103
  password: "--secret-value--",
101
- port: mysqlDatasource.config!.port,
104
+ port: datasource.config!.port,
102
105
  user: "root",
103
106
  },
104
107
  plus: true,
@@ -117,7 +120,7 @@ describe("mysql integrations", () => {
117
120
  it("should be able to verify the connection", async () => {
118
121
  await config.api.datasource.verify(
119
122
  {
120
- datasource: await databaseTestProviders.mysql.datasource(),
123
+ datasource: rawDatasource,
121
124
  },
122
125
  {
123
126
  body: {
@@ -128,13 +131,12 @@ describe("mysql integrations", () => {
128
131
  })
129
132
 
130
133
  it("should state an invalid datasource cannot connect", async () => {
131
- const dbConfig = await databaseTestProviders.mysql.datasource()
132
134
  await config.api.datasource.verify(
133
135
  {
134
136
  datasource: {
135
- ...dbConfig,
137
+ ...rawDatasource,
136
138
  config: {
137
- ...dbConfig.config,
139
+ ...rawDatasource.config,
138
140
  password: "wrongpassword",
139
141
  },
140
142
  },
@@ -154,7 +156,7 @@ describe("mysql integrations", () => {
154
156
  it("should fetch information about mysql datasource", async () => {
155
157
  const primaryName = primaryMySqlTable.name
156
158
  const response = await makeRequest("post", "/api/datasources/info", {
157
- datasource: mysqlDatasource,
159
+ datasource: datasource,
158
160
  })
159
161
  expect(response.status).toBe(200)
160
162
  expect(response.body.tableNames).toBeDefined()
@@ -163,40 +165,38 @@ describe("mysql integrations", () => {
163
165
  })
164
166
 
165
167
  describe("Integration compatibility with mysql search_path", () => {
166
- let client: mysql.Connection, pathDatasource: Datasource
167
- const database = "test1"
168
- const database2 = "test-2"
168
+ let datasource: Datasource, rawDatasource: Datasource
169
+ const database = generator.guid()
170
+ const database2 = generator.guid()
169
171
 
170
172
  beforeAll(async () => {
171
- const dsConfig = await databaseTestProviders.mysql.datasource()
172
- const dbConfig = dsConfig.config!
173
+ rawDatasource = await getDatasource(DatabaseName.MYSQL)
173
174
 
174
- client = await mysql.createConnection(dbConfig)
175
- await client.query(`CREATE DATABASE \`${database}\`;`)
176
- await client.query(`CREATE DATABASE \`${database2}\`;`)
175
+ await rawQuery(rawDatasource, `CREATE DATABASE \`${database}\`;`)
176
+ await rawQuery(rawDatasource, `CREATE DATABASE \`${database2}\`;`)
177
177
 
178
178
  const pathConfig: any = {
179
- ...dsConfig,
179
+ ...rawDatasource,
180
180
  config: {
181
- ...dbConfig,
181
+ ...rawDatasource.config!,
182
182
  database,
183
183
  },
184
184
  }
185
- pathDatasource = await config.api.datasource.create(pathConfig)
185
+ datasource = await config.api.datasource.create(pathConfig)
186
186
  })
187
187
 
188
188
  afterAll(async () => {
189
- await client.query(`DROP DATABASE \`${database}\`;`)
190
- await client.query(`DROP DATABASE \`${database2}\`;`)
191
- await client.end()
189
+ await rawQuery(rawDatasource, `DROP DATABASE \`${database}\`;`)
190
+ await rawQuery(rawDatasource, `DROP DATABASE \`${database2}\`;`)
192
191
  })
193
192
 
194
193
  it("discovers tables from any schema in search path", async () => {
195
- await client.query(
194
+ await rawQuery(
195
+ rawDatasource,
196
196
  `CREATE TABLE \`${database}\`.table1 (id1 SERIAL PRIMARY KEY);`
197
197
  )
198
198
  const response = await makeRequest("post", "/api/datasources/info", {
199
- datasource: pathDatasource,
199
+ datasource: datasource,
200
200
  })
201
201
  expect(response.status).toBe(200)
202
202
  expect(response.body.tableNames).toBeDefined()
@@ -207,15 +207,17 @@ describe("mysql integrations", () => {
207
207
 
208
208
  it("does not mix columns from different tables", async () => {
209
209
  const repeated_table_name = "table_same_name"
210
- await client.query(
210
+ await rawQuery(
211
+ rawDatasource,
211
212
  `CREATE TABLE \`${database}\`.${repeated_table_name} (id SERIAL PRIMARY KEY, val1 TEXT);`
212
213
  )
213
- await client.query(
214
+ await rawQuery(
215
+ rawDatasource,
214
216
  `CREATE TABLE \`${database2}\`.${repeated_table_name} (id2 SERIAL PRIMARY KEY, val2 TEXT);`
215
217
  )
216
218
  const response = await makeRequest(
217
219
  "post",
218
- `/api/datasources/${pathDatasource._id}/schema`,
220
+ `/api/datasources/${datasource._id}/schema`,
219
221
  {
220
222
  tablesFilter: [repeated_table_name],
221
223
  }
@@ -231,30 +233,14 @@ describe("mysql integrations", () => {
231
233
  })
232
234
 
233
235
  describe("POST /api/tables/", () => {
234
- let client: mysql.Connection
235
236
  const emitDatasourceUpdateMock = jest.fn()
236
237
 
237
- beforeEach(async () => {
238
- client = await mysql.createConnection(
239
- (
240
- await databaseTestProviders.mysql.datasource()
241
- ).config!
242
- )
243
- mysqlDatasource = await config.api.datasource.create(
244
- await databaseTestProviders.mysql.datasource()
245
- )
246
- })
247
-
248
- afterEach(async () => {
249
- await client.end()
250
- })
251
-
252
238
  it("will emit the datasource entity schema with externalType to the front-end when adding a new column", async () => {
253
239
  const addColumnToTable: TableRequest = {
254
240
  type: "table",
255
241
  sourceType: TableSourceType.EXTERNAL,
256
- name: "table",
257
- sourceId: mysqlDatasource._id!,
242
+ name: uniqueTableName(),
243
+ sourceId: datasource._id!,
258
244
  primary: ["id"],
259
245
  schema: {
260
246
  id: {
@@ -301,14 +287,16 @@ describe("mysql integrations", () => {
301
287
  },
302
288
  },
303
289
  created: true,
304
- _id: `${mysqlDatasource._id}__table`,
290
+ _id: `${datasource._id}__${addColumnToTable.name}`,
305
291
  }
306
292
  delete expectedTable._add
307
293
 
308
294
  expect(emitDatasourceUpdateMock).toHaveBeenCalledTimes(1)
309
295
  const emittedDatasource: Datasource =
310
296
  emitDatasourceUpdateMock.mock.calls[0][1]
311
- expect(emittedDatasource.entities!["table"]).toEqual(expectedTable)
297
+ expect(emittedDatasource.entities![expectedTable.name]).toEqual(
298
+ expectedTable
299
+ )
312
300
  })
313
301
 
314
302
  it("will rename a column", async () => {
@@ -346,17 +334,18 @@ describe("mysql integrations", () => {
346
334
  "/api/tables/",
347
335
  renameColumnOnTable
348
336
  )
349
- mysqlDatasource = (
350
- await makeRequest(
351
- "post",
352
- `/api/datasources/${mysqlDatasource._id}/schema`
353
- )
337
+
338
+ const ds = (
339
+ await makeRequest("post", `/api/datasources/${datasource._id}/schema`)
354
340
  ).body.datasource
355
341
 
356
342
  expect(response.status).toEqual(200)
357
- expect(
358
- Object.keys(mysqlDatasource.entities![primaryMySqlTable.name].schema)
359
- ).toEqual(["id", "name", "description", "age"])
343
+ expect(Object.keys(ds.entities![primaryMySqlTable.name].schema)).toEqual([
344
+ "id",
345
+ "name",
346
+ "description",
347
+ "age",
348
+ ])
360
349
  })
361
350
  })
362
351
  })
@@ -16,8 +16,12 @@ import {
16
16
  import _ from "lodash"
17
17
  import { generator } from "@budibase/backend-core/tests"
18
18
  import { utils } from "@budibase/backend-core"
19
- import { databaseTestProviders } from "../integrations/tests/utils"
20
- import { Client } from "pg"
19
+ import {
20
+ DatabaseName,
21
+ getDatasource,
22
+ rawQuery,
23
+ } from "../integrations/tests/utils"
24
+
21
25
  // @ts-ignore
22
26
  fetch.mockSearch()
23
27
 
@@ -28,7 +32,8 @@ jest.mock("../websockets")
28
32
 
29
33
  describe("postgres integrations", () => {
30
34
  let makeRequest: MakeRequestResponse,
31
- postgresDatasource: Datasource,
35
+ rawDatasource: Datasource,
36
+ datasource: Datasource,
32
37
  primaryPostgresTable: Table,
33
38
  oneToManyRelationshipInfo: ForeignTableInfo,
34
39
  manyToOneRelationshipInfo: ForeignTableInfo,
@@ -40,19 +45,17 @@ describe("postgres integrations", () => {
40
45
 
41
46
  makeRequest = generateMakeRequest(apiKey, true)
42
47
 
43
- postgresDatasource = await config.api.datasource.create(
44
- await databaseTestProviders.postgres.datasource()
45
- )
46
- })
47
-
48
- afterAll(async () => {
49
- await databaseTestProviders.postgres.stop()
48
+ rawDatasource = await getDatasource(DatabaseName.POSTGRES)
49
+ datasource = await config.api.datasource.create(rawDatasource)
50
50
  })
51
51
 
52
52
  beforeEach(async () => {
53
53
  async function createAuxTable(prefix: string) {
54
54
  return await config.createTable({
55
- name: `${prefix}_${generator.word({ length: 6 })}`,
55
+ name: `${prefix}_${generator
56
+ .guid()
57
+ .replaceAll("-", "")
58
+ .substring(0, 6)}`,
56
59
  type: "table",
57
60
  primary: ["id"],
58
61
  primaryDisplay: "title",
@@ -67,7 +70,7 @@ describe("postgres integrations", () => {
67
70
  type: FieldType.STRING,
68
71
  },
69
72
  },
70
- sourceId: postgresDatasource._id,
73
+ sourceId: datasource._id,
71
74
  sourceType: TableSourceType.EXTERNAL,
72
75
  })
73
76
  }
@@ -89,7 +92,7 @@ describe("postgres integrations", () => {
89
92
  }
90
93
 
91
94
  primaryPostgresTable = await config.createTable({
92
- name: `p_${generator.word({ length: 6 })}`,
95
+ name: `p_${generator.guid().replaceAll("-", "").substring(0, 6)}`,
93
96
  type: "table",
94
97
  primary: ["id"],
95
98
  schema: {
@@ -144,7 +147,7 @@ describe("postgres integrations", () => {
144
147
  main: true,
145
148
  },
146
149
  },
147
- sourceId: postgresDatasource._id,
150
+ sourceId: datasource._id,
148
151
  sourceType: TableSourceType.EXTERNAL,
149
152
  })
150
153
  })
@@ -251,7 +254,7 @@ describe("postgres integrations", () => {
251
254
 
252
255
  async function createDefaultPgTable() {
253
256
  return await config.createTable({
254
- name: generator.word({ length: 10 }),
257
+ name: generator.guid().replaceAll("-", "").substring(0, 10),
255
258
  type: "table",
256
259
  primary: ["id"],
257
260
  schema: {
@@ -261,7 +264,7 @@ describe("postgres integrations", () => {
261
264
  autocolumn: true,
262
265
  },
263
266
  },
264
- sourceId: postgresDatasource._id,
267
+ sourceId: datasource._id,
265
268
  sourceType: TableSourceType.EXTERNAL,
266
269
  })
267
270
  }
@@ -299,19 +302,16 @@ describe("postgres integrations", () => {
299
302
  }
300
303
 
301
304
  it("validate table schema", async () => {
302
- const res = await makeRequest(
303
- "get",
304
- `/api/datasources/${postgresDatasource._id}`
305
- )
305
+ const res = await makeRequest("get", `/api/datasources/${datasource._id}`)
306
306
 
307
307
  expect(res.status).toBe(200)
308
308
  expect(res.body).toEqual({
309
309
  config: {
310
310
  ca: false,
311
- database: "postgres",
312
- host: postgresDatasource.config!.host,
311
+ database: expect.any(String),
312
+ host: datasource.config!.host,
313
313
  password: "--secret-value--",
314
- port: postgresDatasource.config!.port,
314
+ port: datasource.config!.port,
315
315
  rejectUnauthorized: false,
316
316
  schema: "public",
317
317
  ssl: false,
@@ -1043,7 +1043,7 @@ describe("postgres integrations", () => {
1043
1043
  it("should be able to verify the connection", async () => {
1044
1044
  await config.api.datasource.verify(
1045
1045
  {
1046
- datasource: await databaseTestProviders.postgres.datasource(),
1046
+ datasource: await getDatasource(DatabaseName.POSTGRES),
1047
1047
  },
1048
1048
  {
1049
1049
  body: {
@@ -1054,7 +1054,7 @@ describe("postgres integrations", () => {
1054
1054
  })
1055
1055
 
1056
1056
  it("should state an invalid datasource cannot connect", async () => {
1057
- const dbConfig = await databaseTestProviders.postgres.datasource()
1057
+ const dbConfig = await getDatasource(DatabaseName.POSTGRES)
1058
1058
  await config.api.datasource.verify(
1059
1059
  {
1060
1060
  datasource: {
@@ -1079,7 +1079,7 @@ describe("postgres integrations", () => {
1079
1079
  it("should fetch information about postgres datasource", async () => {
1080
1080
  const primaryName = primaryPostgresTable.name
1081
1081
  const response = await makeRequest("post", "/api/datasources/info", {
1082
- datasource: postgresDatasource,
1082
+ datasource: datasource,
1083
1083
  })
1084
1084
  expect(response.status).toBe(200)
1085
1085
  expect(response.body.tableNames).toBeDefined()
@@ -1088,86 +1088,88 @@ describe("postgres integrations", () => {
1088
1088
  })
1089
1089
 
1090
1090
  describe("POST /api/datasources/:datasourceId/schema", () => {
1091
- let client: Client
1091
+ let tableName: string
1092
1092
 
1093
1093
  beforeEach(async () => {
1094
- client = new Client(
1095
- (await databaseTestProviders.postgres.datasource()).config!
1096
- )
1097
- await client.connect()
1094
+ tableName = generator.guid().replaceAll("-", "").substring(0, 10)
1098
1095
  })
1099
1096
 
1100
1097
  afterEach(async () => {
1101
- await client.query(`DROP TABLE IF EXISTS "table"`)
1102
- await client.end()
1098
+ await rawQuery(rawDatasource, `DROP TABLE IF EXISTS "${tableName}"`)
1103
1099
  })
1104
1100
 
1105
1101
  it("recognises when a table has no primary key", async () => {
1106
- await client.query(`CREATE TABLE "table" (id SERIAL)`)
1102
+ await rawQuery(rawDatasource, `CREATE TABLE "${tableName}" (id SERIAL)`)
1107
1103
 
1108
1104
  const response = await makeRequest(
1109
1105
  "post",
1110
- `/api/datasources/${postgresDatasource._id}/schema`
1106
+ `/api/datasources/${datasource._id}/schema`
1111
1107
  )
1112
1108
 
1113
1109
  expect(response.body.errors).toEqual({
1114
- table: "Table must have a primary key.",
1110
+ [tableName]: "Table must have a primary key.",
1115
1111
  })
1116
1112
  })
1117
1113
 
1118
1114
  it("recognises when a table is using a reserved column name", async () => {
1119
- await client.query(`CREATE TABLE "table" (_id SERIAL PRIMARY KEY) `)
1115
+ await rawQuery(
1116
+ rawDatasource,
1117
+ `CREATE TABLE "${tableName}" (_id SERIAL PRIMARY KEY) `
1118
+ )
1120
1119
 
1121
1120
  const response = await makeRequest(
1122
1121
  "post",
1123
- `/api/datasources/${postgresDatasource._id}/schema`
1122
+ `/api/datasources/${datasource._id}/schema`
1124
1123
  )
1125
1124
 
1126
1125
  expect(response.body.errors).toEqual({
1127
- table: "Table contains invalid columns.",
1126
+ [tableName]: "Table contains invalid columns.",
1128
1127
  })
1129
1128
  })
1130
1129
  })
1131
1130
 
1132
1131
  describe("Integration compatibility with postgres search_path", () => {
1133
- let client: Client, pathDatasource: Datasource
1134
- const schema1 = "test1",
1135
- schema2 = "test-2"
1132
+ let rawDatasource: Datasource,
1133
+ datasource: Datasource,
1134
+ schema1: string,
1135
+ schema2: string
1136
1136
 
1137
- beforeAll(async () => {
1138
- const dsConfig = await databaseTestProviders.postgres.datasource()
1139
- const dbConfig = dsConfig.config!
1137
+ beforeEach(async () => {
1138
+ schema1 = generator.guid().replaceAll("-", "")
1139
+ schema2 = generator.guid().replaceAll("-", "")
1140
1140
 
1141
- client = new Client(dbConfig)
1142
- await client.connect()
1143
- await client.query(`CREATE SCHEMA "${schema1}";`)
1144
- await client.query(`CREATE SCHEMA "${schema2}";`)
1141
+ rawDatasource = await getDatasource(DatabaseName.POSTGRES)
1142
+ const dbConfig = rawDatasource.config!
1143
+
1144
+ await rawQuery(rawDatasource, `CREATE SCHEMA "${schema1}";`)
1145
+ await rawQuery(rawDatasource, `CREATE SCHEMA "${schema2}";`)
1145
1146
 
1146
1147
  const pathConfig: any = {
1147
- ...dsConfig,
1148
+ ...rawDatasource,
1148
1149
  config: {
1149
1150
  ...dbConfig,
1150
1151
  schema: `${schema1}, ${schema2}`,
1151
1152
  },
1152
1153
  }
1153
- pathDatasource = await config.api.datasource.create(pathConfig)
1154
+ datasource = await config.api.datasource.create(pathConfig)
1154
1155
  })
1155
1156
 
1156
- afterAll(async () => {
1157
- await client.query(`DROP SCHEMA "${schema1}" CASCADE;`)
1158
- await client.query(`DROP SCHEMA "${schema2}" CASCADE;`)
1159
- await client.end()
1157
+ afterEach(async () => {
1158
+ await rawQuery(rawDatasource, `DROP SCHEMA "${schema1}" CASCADE;`)
1159
+ await rawQuery(rawDatasource, `DROP SCHEMA "${schema2}" CASCADE;`)
1160
1160
  })
1161
1161
 
1162
1162
  it("discovers tables from any schema in search path", async () => {
1163
- await client.query(
1163
+ await rawQuery(
1164
+ rawDatasource,
1164
1165
  `CREATE TABLE "${schema1}".table1 (id1 SERIAL PRIMARY KEY);`
1165
1166
  )
1166
- await client.query(
1167
+ await rawQuery(
1168
+ rawDatasource,
1167
1169
  `CREATE TABLE "${schema2}".table2 (id2 SERIAL PRIMARY KEY);`
1168
1170
  )
1169
1171
  const response = await makeRequest("post", "/api/datasources/info", {
1170
- datasource: pathDatasource,
1172
+ datasource: datasource,
1171
1173
  })
1172
1174
  expect(response.status).toBe(200)
1173
1175
  expect(response.body.tableNames).toBeDefined()
@@ -1178,15 +1180,17 @@ describe("postgres integrations", () => {
1178
1180
 
1179
1181
  it("does not mix columns from different tables", async () => {
1180
1182
  const repeated_table_name = "table_same_name"
1181
- await client.query(
1183
+ await rawQuery(
1184
+ rawDatasource,
1182
1185
  `CREATE TABLE "${schema1}".${repeated_table_name} (id SERIAL PRIMARY KEY, val1 TEXT);`
1183
1186
  )
1184
- await client.query(
1187
+ await rawQuery(
1188
+ rawDatasource,
1185
1189
  `CREATE TABLE "${schema2}".${repeated_table_name} (id2 SERIAL PRIMARY KEY, val2 TEXT);`
1186
1190
  )
1187
1191
  const response = await makeRequest(
1188
1192
  "post",
1189
- `/api/datasources/${pathDatasource._id}/schema`,
1193
+ `/api/datasources/${datasource._id}/schema`,
1190
1194
  {
1191
1195
  tablesFilter: [repeated_table_name],
1192
1196
  }
@@ -1,25 +1,90 @@
1
1
  jest.unmock("pg")
2
2
 
3
- import { Datasource } from "@budibase/types"
3
+ import { Datasource, SourceName } from "@budibase/types"
4
4
  import * as postgres from "./postgres"
5
5
  import * as mongodb from "./mongodb"
6
6
  import * as mysql from "./mysql"
7
7
  import * as mssql from "./mssql"
8
8
  import * as mariadb from "./mariadb"
9
- import { StartedTestContainer } from "testcontainers"
9
+ import { GenericContainer } from "testcontainers"
10
+ import { testContainerUtils } from "@budibase/backend-core/tests"
10
11
 
11
- jest.setTimeout(30000)
12
+ export type DatasourceProvider = () => Promise<Datasource>
12
13
 
13
- export interface DatabaseProvider {
14
- start(): Promise<StartedTestContainer>
15
- stop(): Promise<void>
16
- datasource(): Promise<Datasource>
14
+ export enum DatabaseName {
15
+ POSTGRES = "postgres",
16
+ MONGODB = "mongodb",
17
+ MYSQL = "mysql",
18
+ SQL_SERVER = "mssql",
19
+ MARIADB = "mariadb",
17
20
  }
18
21
 
19
- export const databaseTestProviders = {
20
- postgres,
21
- mongodb,
22
- mysql,
23
- mssql,
24
- mariadb,
22
+ const providers: Record<DatabaseName, DatasourceProvider> = {
23
+ [DatabaseName.POSTGRES]: postgres.getDatasource,
24
+ [DatabaseName.MONGODB]: mongodb.getDatasource,
25
+ [DatabaseName.MYSQL]: mysql.getDatasource,
26
+ [DatabaseName.SQL_SERVER]: mssql.getDatasource,
27
+ [DatabaseName.MARIADB]: mariadb.getDatasource,
28
+ }
29
+
30
+ export function getDatasourceProviders(
31
+ ...sourceNames: DatabaseName[]
32
+ ): Promise<Datasource>[] {
33
+ return sourceNames.map(sourceName => providers[sourceName]())
34
+ }
35
+
36
+ export function getDatasourceProvider(
37
+ sourceName: DatabaseName
38
+ ): DatasourceProvider {
39
+ return providers[sourceName]
40
+ }
41
+
42
+ export function getDatasource(sourceName: DatabaseName): Promise<Datasource> {
43
+ return providers[sourceName]()
44
+ }
45
+
46
+ export async function getDatasources(
47
+ ...sourceNames: DatabaseName[]
48
+ ): Promise<Datasource[]> {
49
+ return Promise.all(sourceNames.map(sourceName => providers[sourceName]()))
50
+ }
51
+
52
+ export async function rawQuery(ds: Datasource, sql: string): Promise<any> {
53
+ switch (ds.source) {
54
+ case SourceName.POSTGRES: {
55
+ return postgres.rawQuery(ds, sql)
56
+ }
57
+ case SourceName.MYSQL: {
58
+ return mysql.rawQuery(ds, sql)
59
+ }
60
+ case SourceName.SQL_SERVER: {
61
+ return mssql.rawQuery(ds, sql)
62
+ }
63
+ default: {
64
+ throw new Error(`Unsupported source: ${ds.source}`)
65
+ }
66
+ }
67
+ }
68
+
69
+ export async function startContainer(container: GenericContainer) {
70
+ if (process.env.REUSE_CONTAINERS) {
71
+ container = container.withReuse()
72
+ }
73
+
74
+ const startedContainer = await container.start()
75
+
76
+ const info = testContainerUtils.getContainerById(startedContainer.getId())
77
+ if (!info) {
78
+ throw new Error("Container not found")
79
+ }
80
+
81
+ // Some Docker runtimes, when you expose a port, will bind it to both
82
+ // 127.0.0.1 and ::1, so ipv4 and ipv6. The port spaces of ipv4 and ipv6
83
+ // addresses are not shared, and testcontainers will sometimes give you back
84
+ // the ipv6 port. There's no way to know that this has happened, and if you
85
+ // try to then connect to `localhost:port` you may attempt to bind to the v4
86
+ // address which could be unbound or even an entirely different container. For
87
+ // that reason, we don't use testcontainers' `getExposedPort` function,
88
+ // preferring instead our own method that guaranteed v4 ports.
89
+ return testContainerUtils.getExposedV4Ports(info)
25
90
  }