@backstage/backend-test-utils 1.10.3 → 1.10.4-next.0
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.
- package/CHANGELOG.md +18 -0
- package/dist/database/mysql.cjs.js +2 -2
- package/dist/database/mysql.cjs.js.map +1 -1
- package/dist/database/postgres.cjs.js +2 -2
- package/dist/database/postgres.cjs.js.map +1 -1
- package/dist/filesystem/MockDirectory.cjs.js +14 -14
- package/dist/filesystem/MockDirectory.cjs.js.map +1 -1
- package/dist/wiring/TestBackend.cjs.js +3 -3
- package/dist/wiring/TestBackend.cjs.js.map +1 -1
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @backstage/backend-test-utils
|
|
2
2
|
|
|
3
|
+
## 1.10.4-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f1d29b4: Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format.
|
|
8
|
+
- 7455dae: Use node prefix on native imports
|
|
9
|
+
- 69d880e: Bump to latest zod to ensure it has the latest features
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
- @backstage/backend-plugin-api@1.7.0-next.0
|
|
12
|
+
- @backstage/backend-defaults@0.15.1-next.0
|
|
13
|
+
- @backstage/plugin-auth-node@0.6.12-next.0
|
|
14
|
+
- @backstage/backend-app-api@1.5.0-next.0
|
|
15
|
+
- @backstage/plugin-permission-common@0.9.5-next.0
|
|
16
|
+
- @backstage/plugin-events-node@0.4.19-next.0
|
|
17
|
+
- @backstage/config@1.3.6
|
|
18
|
+
- @backstage/errors@1.2.7
|
|
19
|
+
- @backstage/types@1.2.2
|
|
20
|
+
|
|
3
21
|
## 1.10.3
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var errors = require('@backstage/errors');
|
|
4
|
-
var
|
|
4
|
+
var node_crypto = require('node:crypto');
|
|
5
5
|
var knexFactory = require('knex');
|
|
6
6
|
var uuid = require('uuid');
|
|
7
7
|
var yn = require('yn');
|
|
@@ -132,7 +132,7 @@ class MysqlEngine {
|
|
|
132
132
|
async createDatabaseInstance() {
|
|
133
133
|
const adminConnection = this.#connectAdmin();
|
|
134
134
|
try {
|
|
135
|
-
const databaseName = `db${
|
|
135
|
+
const databaseName = `db${node_crypto.randomBytes(16).toString("hex")}`;
|
|
136
136
|
await adminConnection.raw("CREATE DATABASE ??", [databaseName]);
|
|
137
137
|
this.#databaseNames.push(databaseName);
|
|
138
138
|
const knexInstance = knexFactory__default.default({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mysql.cjs.js","sources":["../../src/database/mysql.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyError } from '@backstage/errors';\nimport { randomBytes } from 'crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport yn from 'yn';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForMysqlReady(\n connection: Knex.MySqlConnectionConfig,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: Error | undefined;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n let knex: Knex | undefined;\n try {\n knex = knexFactory({\n client: 'mysql2',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n const result = await knex.select(knex.raw('version() AS version'));\n if (Array.isArray(result) && result[0]?.version) {\n return;\n }\n } catch (e) {\n lastError = e;\n } finally {\n await knex?.destroy();\n }\n\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function startMysqlContainer(image: string): Promise<{\n connection: Knex.MySqlConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'root';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(3306)\n .withEnvironment({ MYSQL_ROOT_PASSWORD: password })\n .withTmpFs({ '/var/lib/mysql': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(3306);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForMysqlReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport function parseMysqlConnectionString(\n connectionString: string,\n): Knex.MySqlConnectionConfig {\n try {\n const {\n protocol,\n username,\n password,\n port,\n hostname,\n pathname,\n searchParams,\n } = new URL(connectionString);\n\n if (protocol !== 'mysql:') {\n throw new Error(`Unknown protocol ${protocol}`);\n } else if (!username || !password) {\n throw new Error(`Missing username/password`);\n } else if (!pathname.match(/^\\/[^/]+$/)) {\n throw new Error(`Expected single path segment`);\n }\n\n const result: Knex.MySqlConnectionConfig = {\n user: username,\n password,\n host: hostname,\n port: Number(port || 3306),\n database: decodeURIComponent(pathname.substring(1)),\n };\n\n const ssl = searchParams.get('ssl');\n if (ssl) {\n result.ssl = ssl;\n }\n\n const debug = searchParams.get('debug');\n if (debug) {\n result.debug = yn(debug);\n }\n\n return result;\n } catch (e) {\n throw new Error(`Error while parsing MySQL connection string, ${e}`, e);\n }\n}\n\nexport class MysqlEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<MysqlEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parseMysqlConnectionString(connectionString);\n return new MysqlEngine(\n properties,\n connection as Knex.MySqlConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startMysqlContainer(\n dockerImageName,\n );\n return new MysqlEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.MySqlConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.MySqlConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n const connection = {\n ...this.#connection,\n database: null as unknown as string,\n };\n return knexFactory({\n client: this.#properties.driver,\n connection,\n pool: {\n min: 0,\n max: 1,\n acquireTimeoutMillis: 20_000,\n createTimeoutMillis: 20_000,\n createRetryIntervalMillis: 1_000,\n },\n });\n }\n}\n"],"names":["knexFactory","stringifyError","uuid","yn","randomBytes","LARGER_POOL_CONFIG"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mysql.cjs.js","sources":["../../src/database/mysql.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyError } from '@backstage/errors';\nimport { randomBytes } from 'node:crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport yn from 'yn';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForMysqlReady(\n connection: Knex.MySqlConnectionConfig,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: Error | undefined;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n let knex: Knex | undefined;\n try {\n knex = knexFactory({\n client: 'mysql2',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n const result = await knex.select(knex.raw('version() AS version'));\n if (Array.isArray(result) && result[0]?.version) {\n return;\n }\n } catch (e) {\n lastError = e;\n } finally {\n await knex?.destroy();\n }\n\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function startMysqlContainer(image: string): Promise<{\n connection: Knex.MySqlConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'root';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(3306)\n .withEnvironment({ MYSQL_ROOT_PASSWORD: password })\n .withTmpFs({ '/var/lib/mysql': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(3306);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForMysqlReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport function parseMysqlConnectionString(\n connectionString: string,\n): Knex.MySqlConnectionConfig {\n try {\n const {\n protocol,\n username,\n password,\n port,\n hostname,\n pathname,\n searchParams,\n } = new URL(connectionString);\n\n if (protocol !== 'mysql:') {\n throw new Error(`Unknown protocol ${protocol}`);\n } else if (!username || !password) {\n throw new Error(`Missing username/password`);\n } else if (!pathname.match(/^\\/[^/]+$/)) {\n throw new Error(`Expected single path segment`);\n }\n\n const result: Knex.MySqlConnectionConfig = {\n user: username,\n password,\n host: hostname,\n port: Number(port || 3306),\n database: decodeURIComponent(pathname.substring(1)),\n };\n\n const ssl = searchParams.get('ssl');\n if (ssl) {\n result.ssl = ssl;\n }\n\n const debug = searchParams.get('debug');\n if (debug) {\n result.debug = yn(debug);\n }\n\n return result;\n } catch (e) {\n throw new Error(`Error while parsing MySQL connection string, ${e}`, e);\n }\n}\n\nexport class MysqlEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<MysqlEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parseMysqlConnectionString(connectionString);\n return new MysqlEngine(\n properties,\n connection as Knex.MySqlConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startMysqlContainer(\n dockerImageName,\n );\n return new MysqlEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.MySqlConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.MySqlConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n const connection = {\n ...this.#connection,\n database: null as unknown as string,\n };\n return knexFactory({\n client: this.#properties.driver,\n connection,\n pool: {\n min: 0,\n max: 1,\n acquireTimeoutMillis: 20_000,\n createTimeoutMillis: 20_000,\n createRetryIntervalMillis: 1_000,\n },\n });\n }\n}\n"],"names":["knexFactory","stringifyError","uuid","yn","randomBytes","LARGER_POOL_CONFIG"],"mappings":";;;;;;;;;;;;;;AAuBA,eAAe,kBACb,UAAA,EACe;AACf,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,WAAS;AACP,IAAA,QAAA,IAAY,CAAA;AAEZ,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI;AACF,MAAA,IAAA,GAAOA,4BAAA,CAAY;AAAA,QACjB,MAAA,EAAQ,QAAA;AAAA,QACR,UAAA,EAAY;AAAA;AAAA,UAEV,GAAG;AAAA;AACL,OACD,CAAA;AACD,MAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAO,IAAA,CAAK,GAAA,CAAI,sBAAsB,CAAC,CAAA;AACjE,MAAA,IAAI,MAAM,OAAA,CAAQ,MAAM,KAAK,MAAA,CAAO,CAAC,GAAG,OAAA,EAAS;AAC/C,QAAA;AAAA,MACF;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,SAAA,GAAY,CAAA;AAAA,IACd,CAAA,SAAE;AACA,MAAA,MAAM,MAAM,OAAA,EAAQ;AAAA,IACtB;AAEA,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAA,EAAQ;AACnC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gEAAA,EAAmE,QAAQ,CAAA,WAAA,EACzE,SAAA,GACI,kBAAkBC,qBAAA,CAAe,SAAS,CAAC,CAAA,CAAA,GAC3C,oBACN,CAAA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAAA,EACvD;AACF;AAEA,eAAsB,oBAAoB,KAAA,EAGvC;AACD,EAAA,MAAM,IAAA,GAAO,MAAA;AACb,EAAA,MAAM,WAAWC,OAAA,EAAK;AAGtB,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,IAAI,CAAA,CACrB,eAAA,CAAgB,EAAE,mBAAA,EAAqB,QAAA,EAAU,CAAA,CACjD,SAAA,CAAU,EAAE,gBAAA,EAAkB,IAAA,EAAM,CAAA,CACpC,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,IAAI,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,QAAA,EAAS;AAChD,EAAA,MAAM,gBAAgB,YAAY;AAChC,IAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,EAC1C,CAAA;AAEA,EAAA,MAAM,kBAAkB,UAAU,CAAA;AAElC,EAAA,OAAO,EAAE,YAAY,aAAA,EAAc;AACrC;AAEO,SAAS,2BACd,gBAAA,EAC4B;AAC5B,EAAA,IAAI;AACF,IAAA,MAAM;AAAA,MACJ,QAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA;AAAA,MACA,IAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF,GAAI,IAAI,GAAA,CAAI,gBAAgB,CAAA;AAE5B,IAAA,IAAI,aAAa,QAAA,EAAU;AACzB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,QAAQ,CAAA,CAAE,CAAA;AAAA,IAChD,CAAA,MAAA,IAAW,CAAC,QAAA,IAAY,CAAC,QAAA,EAAU;AACjC,MAAA,MAAM,IAAI,MAAM,CAAA,yBAAA,CAA2B,CAAA;AAAA,IAC7C,CAAA,MAAA,IAAW,CAAC,QAAA,CAAS,KAAA,CAAM,WAAW,CAAA,EAAG;AACvC,MAAA,MAAM,IAAI,MAAM,CAAA,4BAAA,CAA8B,CAAA;AAAA,IAChD;AAEA,IAAA,MAAM,MAAA,GAAqC;AAAA,MACzC,IAAA,EAAM,QAAA;AAAA,MACN,QAAA;AAAA,MACA,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,MAAA,CAAO,IAAA,IAAQ,IAAI,CAAA;AAAA,MACzB,QAAA,EAAU,kBAAA,CAAmB,QAAA,CAAS,SAAA,CAAU,CAAC,CAAC;AAAA,KACpD;AAEA,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA;AAClC,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,MAAA,CAAO,GAAA,GAAM,GAAA;AAAA,IACf;AAEA,IAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AACtC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAA,CAAO,KAAA,GAAQC,oBAAG,KAAK,CAAA;AAAA,IACzB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,CAAA,EAAG;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6CAAA,EAAgD,CAAC,IAAI,CAAC,CAAA;AAAA,EACxE;AACF;AAEO,MAAM,WAAA,CAA8B;AAAA,EACzC,aAAa,OACX,UAAA,EACsB;AACtB,IAAA,MAAM,EAAE,uCAAA,EAAyC,eAAA,EAAgB,GAC/D,UAAA;AAEF,IAAA,IAAI,uCAAA,EAAyC;AAC3C,MAAA,MAAM,gBAAA,GACJ,OAAA,CAAQ,GAAA,CAAI,uCAAuC,CAAA;AACrD,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,MAAM,UAAA,GAAa,2BAA2B,gBAAgB,CAAA;AAC9D,QAAA,OAAO,IAAI,WAAA;AAAA,UACT,UAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,MAAM,EAAE,UAAA,EAAY,aAAA,EAAc,GAAI,MAAM,mBAAA;AAAA,QAC1C;AAAA,OACF;AACA,MAAA,OAAO,IAAI,WAAA,CAAY,UAAA,EAAY,UAAA,EAAY,aAAa,CAAA;AAAA,IAC9D;AAEA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,UAAA,CAAW,IAAI,CAAA,eAAA,CAAiB,CAAA;AAAA,EACxE;AAAA,EAES,WAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EAET,WAAA,CACE,UAAA,EACA,UAAA,EACA,aAAA,EACA;AACA,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AACvB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AACvB,IAAA,IAAA,CAAK,cAAA,GAAiB,aAAA;AAAA,EACxB;AAAA,EAEA,MAAM,sBAAA,GAAwC;AAC5C,IAAA,MAAM,eAAA,GAAkB,KAAK,aAAA,EAAc;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,eAAe,CAAA,EAAA,EAAKC,uBAAA,CAAY,EAAE,CAAA,CAAE,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AAEzD,MAAA,MAAM,eAAA,CAAgB,GAAA,CAAI,oBAAA,EAAsB,CAAC,YAAY,CAAC,CAAA;AAC9D,MAAA,IAAA,CAAK,cAAA,CAAe,KAAK,YAAY,CAAA;AAErC,MAAA,MAAM,eAAeJ,4BAAA,CAAY;AAAA,QAC/B,MAAA,EAAQ,KAAK,WAAA,CAAY,MAAA;AAAA,QACzB,UAAA,EAAY;AAAA,UACV,GAAG,IAAA,CAAK,WAAA;AAAA,UACR,QAAA,EAAU;AAAA,SACZ;AAAA,QACA,GAAGK;AAAA,OACJ,CAAA;AACD,MAAA,IAAA,CAAK,cAAA,CAAe,KAAK,YAAY,CAAA;AAErC,MAAA,OAAO,YAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,MAAM,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,MAAM,QAAA,GAA0B;AAC9B,IAAA,KAAA,MAAW,QAAA,IAAY,KAAK,cAAA,EAAgB;AAC1C,MAAA,MAAM,SAAS,OAAA,EAAQ;AAAA,IACzB;AAEA,IAAA,MAAM,eAAA,GAAkB,KAAK,aAAA,EAAc;AAC3C,IAAA,IAAI;AACF,MAAA,KAAA,MAAW,YAAA,IAAgB,KAAK,cAAA,EAAgB;AAC9C,QAAA,MAAM,eAAA,CAAgB,GAAA,CAAI,kBAAA,EAAoB,CAAC,YAAY,CAAC,CAAA;AAAA,MAC9D;AAAA,IACF,CAAA,SAAE;AACA,MAAA,MAAM,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAEA,IAAA,MAAM,KAAK,cAAA,IAAiB;AAAA,EAC9B;AAAA,EAEA,aAAA,GAAsB;AACpB,IAAA,MAAM,UAAA,GAAa;AAAA,MACjB,GAAG,IAAA,CAAK,WAAA;AAAA,MACR,QAAA,EAAU;AAAA,KACZ;AACA,IAAA,OAAOL,4BAAA,CAAY;AAAA,MACjB,MAAA,EAAQ,KAAK,WAAA,CAAY,MAAA;AAAA,MACzB,UAAA;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,GAAA,EAAK,CAAA;AAAA,QACL,GAAA,EAAK,CAAA;AAAA,QACL,oBAAA,EAAsB,GAAA;AAAA,QACtB,mBAAA,EAAqB,GAAA;AAAA,QACrB,yBAAA,EAA2B;AAAA;AAC7B,KACD,CAAA;AAAA,EACH;AACF;;;;;;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var errors = require('@backstage/errors');
|
|
4
|
-
var
|
|
4
|
+
var node_crypto = require('node:crypto');
|
|
5
5
|
var knexFactory = require('knex');
|
|
6
6
|
var pgConnectionString = require('pg-connection-string');
|
|
7
7
|
var uuid = require('uuid');
|
|
@@ -97,7 +97,7 @@ class PostgresEngine {
|
|
|
97
97
|
async createDatabaseInstance() {
|
|
98
98
|
const adminConnection = this.#connectAdmin();
|
|
99
99
|
try {
|
|
100
|
-
const databaseName = `db${
|
|
100
|
+
const databaseName = `db${node_crypto.randomBytes(16).toString("hex")}`;
|
|
101
101
|
await adminConnection.raw("CREATE DATABASE ??", [databaseName]);
|
|
102
102
|
this.#databaseNames.push(databaseName);
|
|
103
103
|
const knexInstance = knexFactory__default.default({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.cjs.js","sources":["../../src/database/postgres.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyError } from '@backstage/errors';\nimport { randomBytes } from 'crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { parse as parsePgConnectionString } from 'pg-connection-string';\nimport { v4 as uuid } from 'uuid';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForPostgresReady(\n connection: Knex.PgConnectionConfig,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: Error | undefined;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n let knex: Knex | undefined;\n try {\n knex = knexFactory({\n client: 'pg',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n const result = await knex.select(knex.raw('version()'));\n if (Array.isArray(result) && result[0]?.version) {\n return;\n }\n } catch (e) {\n lastError = e;\n } finally {\n await knex?.destroy();\n }\n\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function startPostgresContainer(image: string): Promise<{\n connection: Knex.PgConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'postgres';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(5432)\n .withEnvironment({\n // Since postgres 18, the default directory changed - so we pin it here\n PGDATA: '/var/lib/postgresql/data',\n POSTGRES_PASSWORD: password,\n })\n .withTmpFs({ '/var/lib/postgresql/data': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(5432);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForPostgresReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport class PostgresEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<PostgresEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parsePgConnectionString(connectionString);\n return new PostgresEngine(\n properties,\n connection as Knex.PgConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startPostgresContainer(\n dockerImageName,\n );\n return new PostgresEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.PgConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.PgConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n return knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: 'postgres',\n },\n pool: {\n acquireTimeoutMillis: 10000,\n },\n });\n }\n}\n"],"names":["knexFactory","stringifyError","uuid","parsePgConnectionString","randomBytes","LARGER_POOL_CONFIG"],"mappings":";;;;;;;;;;;;;AAuBA,eAAe,qBACb,UAAA,EACe;AACf,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,WAAS;AACP,IAAA,QAAA,IAAY,CAAA;AAEZ,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI;AACF,MAAA,IAAA,GAAOA,4BAAA,CAAY;AAAA,QACjB,MAAA,EAAQ,IAAA;AAAA,QACR,UAAA,EAAY;AAAA;AAAA,UAEV,GAAG;AAAA;AACL,OACD,CAAA;AACD,MAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAO,IAAA,CAAK,GAAA,CAAI,WAAW,CAAC,CAAA;AACtD,MAAA,IAAI,MAAM,OAAA,CAAQ,MAAM,KAAK,MAAA,CAAO,CAAC,GAAG,OAAA,EAAS;AAC/C,QAAA;AAAA,MACF;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,SAAA,GAAY,CAAA;AAAA,IACd,CAAA,SAAE;AACA,MAAA,MAAM,MAAM,OAAA,EAAQ;AAAA,IACtB;AAEA,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAA,EAAQ;AACnC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gEAAA,EAAmE,QAAQ,CAAA,WAAA,EACzE,SAAA,GACI,kBAAkBC,qBAAA,CAAe,SAAS,CAAC,CAAA,CAAA,GAC3C,oBACN,CAAA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,GAAG,CAAC,CAAA;AAAA,EACvD;AACF;AAEA,eAAsB,uBAAuB,KAAA,EAG1C;AACD,EAAA,MAAM,IAAA,GAAO,UAAA;AACb,EAAA,MAAM,WAAWC,OAAA,EAAK;AAGtB,EAAA,MAAM,EAAE,gBAAA,EAAiB,GACvB,OAAA,CAAQ,gBAAgB,CAAA;AAE1B,EAAA,MAAM,SAAA,GAAY,MAAM,IAAI,gBAAA,CAAiB,KAAK,CAAA,CAC/C,gBAAA,CAAiB,IAAI,CAAA,CACrB,eAAA,CAAgB;AAAA;AAAA,IAEf,MAAA,EAAQ,0BAAA;AAAA,IACR,iBAAA,EAAmB;AAAA,GACpB,EACA,SAAA,CAAU,EAAE,4BAA4B,IAAA,EAAM,EAC9C,KAAA,EAAM;AAET,EAAA,MAAM,IAAA,GAAO,UAAU,OAAA,EAAQ;AAC/B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,aAAA,CAAc,IAAI,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,QAAA,EAAS;AAChD,EAAA,MAAM,gBAAgB,YAAY;AAChC,IAAA,MAAM,SAAA,CAAU,IAAA,CAAK,EAAE,OAAA,EAAS,KAAQ,CAAA;AAAA,EAC1C,CAAA;AAEA,EAAA,MAAM,qBAAqB,UAAU,CAAA;AAErC,EAAA,OAAO,EAAE,YAAY,aAAA,EAAc;AACrC;AAEO,MAAM,cAAA,CAAiC;AAAA,EAC5C,aAAa,OACX,UAAA,EACyB;AACzB,IAAA,MAAM,EAAE,uCAAA,EAAyC,eAAA,EAAgB,GAC/D,UAAA;AAEF,IAAA,IAAI,uCAAA,EAAyC;AAC3C,MAAA,MAAM,gBAAA,GACJ,OAAA,CAAQ,GAAA,CAAI,uCAAuC,CAAA;AACrD,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,MAAM,UAAA,GAAaC,yBAAwB,gBAAgB,CAAA;AAC3D,QAAA,OAAO,IAAI,cAAA;AAAA,UACT,UAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,MAAM,EAAE,UAAA,EAAY,aAAA,EAAc,GAAI,MAAM,sBAAA;AAAA,QAC1C;AAAA,OACF;AACA,MAAA,OAAO,IAAI,cAAA,CAAe,UAAA,EAAY,UAAA,EAAY,aAAa,CAAA;AAAA,IACjE;AAEA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,UAAA,CAAW,IAAI,CAAA,eAAA,CAAiB,CAAA;AAAA,EACxE;AAAA,EAES,WAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EAET,WAAA,CACE,UAAA,EACA,UAAA,EACA,aAAA,EACA;AACA,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AACnB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AACvB,IAAA,IAAA,CAAK,iBAAiB,EAAC;AACvB,IAAA,IAAA,CAAK,cAAA,GAAiB,aAAA;AAAA,EACxB;AAAA,EAEA,MAAM,sBAAA,GAAwC;AAC5C,IAAA,MAAM,eAAA,GAAkB,KAAK,aAAA,EAAc;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,eAAe,CAAA,EAAA,EAAKC,kBAAA,CAAY,EAAE,CAAA,CAAE,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AAEzD,MAAA,MAAM,eAAA,CAAgB,GAAA,CAAI,oBAAA,EAAsB,CAAC,YAAY,CAAC,CAAA;AAC9D,MAAA,IAAA,CAAK,cAAA,CAAe,KAAK,YAAY,CAAA;AAErC,MAAA,MAAM,eAAeJ,4BAAA,CAAY;AAAA,QAC/B,MAAA,EAAQ,KAAK,WAAA,CAAY,MAAA;AAAA,QACzB,UAAA,EAAY;AAAA,UACV,GAAG,IAAA,CAAK,WAAA;AAAA,UACR,QAAA,EAAU;AAAA,SACZ;AAAA,QACA,GAAGK;AAAA,OACJ,CAAA;AACD,MAAA,IAAA,CAAK,cAAA,CAAe,KAAK,YAAY,CAAA;AAErC,MAAA,OAAO,YAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,MAAM,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,MAAM,QAAA,GAA0B;AAC9B,IAAA,KAAA,MAAW,QAAA,IAAY,KAAK,cAAA,EAAgB;AAC1C,MAAA,MAAM,SAAS,OAAA,EAAQ;AAAA,IACzB;AAEA,IAAA,MAAM,eAAA,GAAkB,KAAK,aAAA,EAAc;AAC3C,IAAA,IAAI;AACF,MAAA,KAAA,MAAW,YAAA,IAAgB,KAAK,cAAA,EAAgB;AAC9C,QAAA,MAAM,eAAA,CAAgB,GAAA,CAAI,kBAAA,EAAoB,CAAC,YAAY,CAAC,CAAA;AAAA,MAC9D;AAAA,IACF,CAAA,SAAE;AACA,MAAA,MAAM,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAEA,IAAA,MAAM,KAAK,cAAA,IAAiB;AAAA,EAC9B;AAAA,EAEA,aAAA,GAAsB;AACpB,IAAA,OAAOL,4BAAA,CAAY;AAAA,MACjB,MAAA,EAAQ,KAAK,WAAA,CAAY,MAAA;AAAA,MACzB,UAAA,EAAY;AAAA,QACV,GAAG,IAAA,CAAK,WAAA;AAAA,QACR,QAAA,EAAU;AAAA,OACZ;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,oBAAA,EAAsB;AAAA;AACxB,KACD,CAAA;AAAA,EACH;AACF;;;;;"}
|
|
1
|
+
{"version":3,"file":"postgres.cjs.js","sources":["../../src/database/postgres.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyError } from '@backstage/errors';\nimport { randomBytes } from 'node:crypto';\nimport knexFactory, { Knex } from 'knex';\nimport { parse as parsePgConnectionString } from 'pg-connection-string';\nimport { v4 as uuid } from 'uuid';\nimport { Engine, LARGER_POOL_CONFIG, TestDatabaseProperties } from './types';\n\nasync function waitForPostgresReady(\n connection: Knex.PgConnectionConfig,\n): Promise<void> {\n const startTime = Date.now();\n\n let lastError: Error | undefined;\n let attempts = 0;\n for (;;) {\n attempts += 1;\n\n let knex: Knex | undefined;\n try {\n knex = knexFactory({\n client: 'pg',\n connection: {\n // make a copy because the driver mutates this\n ...connection,\n },\n });\n const result = await knex.select(knex.raw('version()'));\n if (Array.isArray(result) && result[0]?.version) {\n return;\n }\n } catch (e) {\n lastError = e;\n } finally {\n await knex?.destroy();\n }\n\n if (Date.now() - startTime > 30_000) {\n throw new Error(\n `Timed out waiting for the database to be ready for connections, ${attempts} attempts, ${\n lastError\n ? `last error was ${stringifyError(lastError)}`\n : '(no errors thrown)'\n }`,\n );\n }\n\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n}\n\nexport async function startPostgresContainer(image: string): Promise<{\n connection: Knex.PgConnectionConfig;\n stopContainer: () => Promise<void>;\n}> {\n const user = 'postgres';\n const password = uuid();\n\n // Lazy-load to avoid side-effect of importing testcontainers\n const { GenericContainer } =\n require('testcontainers') as typeof import('testcontainers');\n\n const container = await new GenericContainer(image)\n .withExposedPorts(5432)\n .withEnvironment({\n // Since postgres 18, the default directory changed - so we pin it here\n PGDATA: '/var/lib/postgresql/data',\n POSTGRES_PASSWORD: password,\n })\n .withTmpFs({ '/var/lib/postgresql/data': 'rw' })\n .start();\n\n const host = container.getHost();\n const port = container.getMappedPort(5432);\n const connection = { host, port, user, password };\n const stopContainer = async () => {\n await container.stop({ timeout: 10_000 });\n };\n\n await waitForPostgresReady(connection);\n\n return { connection, stopContainer };\n}\n\nexport class PostgresEngine implements Engine {\n static async create(\n properties: TestDatabaseProperties,\n ): Promise<PostgresEngine> {\n const { connectionStringEnvironmentVariableName, dockerImageName } =\n properties;\n\n if (connectionStringEnvironmentVariableName) {\n const connectionString =\n process.env[connectionStringEnvironmentVariableName];\n if (connectionString) {\n const connection = parsePgConnectionString(connectionString);\n return new PostgresEngine(\n properties,\n connection as Knex.PgConnectionConfig,\n );\n }\n }\n\n if (dockerImageName) {\n const { connection, stopContainer } = await startPostgresContainer(\n dockerImageName,\n );\n return new PostgresEngine(properties, connection, stopContainer);\n }\n\n throw new Error(`Test databasee for ${properties.name} not configured`);\n }\n\n readonly #properties: TestDatabaseProperties;\n readonly #connection: Knex.PgConnectionConfig;\n readonly #knexInstances: Knex[];\n readonly #databaseNames: string[];\n readonly #stopContainer?: () => Promise<void>;\n\n constructor(\n properties: TestDatabaseProperties,\n connection: Knex.PgConnectionConfig,\n stopContainer?: () => Promise<void>,\n ) {\n this.#properties = properties;\n this.#connection = connection;\n this.#knexInstances = [];\n this.#databaseNames = [];\n this.#stopContainer = stopContainer;\n }\n\n async createDatabaseInstance(): Promise<Knex> {\n const adminConnection = this.#connectAdmin();\n try {\n const databaseName = `db${randomBytes(16).toString('hex')}`;\n\n await adminConnection.raw('CREATE DATABASE ??', [databaseName]);\n this.#databaseNames.push(databaseName);\n\n const knexInstance = knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: databaseName,\n },\n ...LARGER_POOL_CONFIG,\n });\n this.#knexInstances.push(knexInstance);\n\n return knexInstance;\n } finally {\n await adminConnection.destroy();\n }\n }\n\n async shutdown(): Promise<void> {\n for (const instance of this.#knexInstances) {\n await instance.destroy();\n }\n\n const adminConnection = this.#connectAdmin();\n try {\n for (const databaseName of this.#databaseNames) {\n await adminConnection.raw('DROP DATABASE ??', [databaseName]);\n }\n } finally {\n await adminConnection.destroy();\n }\n\n await this.#stopContainer?.();\n }\n\n #connectAdmin(): Knex {\n return knexFactory({\n client: this.#properties.driver,\n connection: {\n ...this.#connection,\n database: 'postgres',\n },\n pool: {\n acquireTimeoutMillis: 10000,\n },\n });\n }\n}\n"],"names":["knexFactory","stringifyError","uuid","parsePgConnectionString","randomBytes","LARGER_POOL_CONFIG"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var os = require('os');
|
|
3
|
+
var os = require('node:os');
|
|
4
4
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
5
5
|
var fs = require('fs-extra');
|
|
6
6
|
var textextensions = require('text-extensions');
|
|
7
|
-
var
|
|
7
|
+
var node_path = require('node:path');
|
|
8
8
|
|
|
9
9
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
10
10
|
|
|
@@ -22,7 +22,7 @@ class MockDirectoryImpl {
|
|
|
22
22
|
return this.#root;
|
|
23
23
|
}
|
|
24
24
|
resolve(...paths) {
|
|
25
|
-
return
|
|
25
|
+
return node_path.resolve(this.#root, ...paths);
|
|
26
26
|
}
|
|
27
27
|
setContent(root) {
|
|
28
28
|
this.remove();
|
|
@@ -31,7 +31,7 @@ class MockDirectoryImpl {
|
|
|
31
31
|
addContent(root) {
|
|
32
32
|
const entries = this.#transformInput(root);
|
|
33
33
|
for (const entry of entries) {
|
|
34
|
-
const fullPath =
|
|
34
|
+
const fullPath = node_path.resolve(this.#root, entry.path);
|
|
35
35
|
if (!backendPluginApi.isChildPath(this.#root, fullPath)) {
|
|
36
36
|
throw new Error(
|
|
37
37
|
`Provided path must resolve to a child path of the mock directory, got '${fullPath}'`
|
|
@@ -40,10 +40,10 @@ class MockDirectoryImpl {
|
|
|
40
40
|
if (entry.type === "dir") {
|
|
41
41
|
fs__default.default.ensureDirSync(fullPath);
|
|
42
42
|
} else if (entry.type === "file") {
|
|
43
|
-
fs__default.default.ensureDirSync(
|
|
43
|
+
fs__default.default.ensureDirSync(node_path.dirname(fullPath));
|
|
44
44
|
fs__default.default.writeFileSync(fullPath, entry.content);
|
|
45
45
|
} else if (entry.type === "callback") {
|
|
46
|
-
fs__default.default.ensureDirSync(
|
|
46
|
+
fs__default.default.ensureDirSync(node_path.dirname(fullPath));
|
|
47
47
|
entry.callback({
|
|
48
48
|
path: fullPath,
|
|
49
49
|
symlink(target) {
|
|
@@ -54,26 +54,26 @@ class MockDirectoryImpl {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
content(options) {
|
|
57
|
-
const shouldReadAsText = (typeof options?.shouldReadAsText === "boolean" ? () => options?.shouldReadAsText : options?.shouldReadAsText) ?? ((path
|
|
58
|
-
const root =
|
|
57
|
+
const shouldReadAsText = (typeof options?.shouldReadAsText === "boolean" ? () => options?.shouldReadAsText : options?.shouldReadAsText) ?? ((path) => textextensions__default.default.includes(node_path.extname(path).slice(1)));
|
|
58
|
+
const root = node_path.resolve(this.#root, options?.path ?? "");
|
|
59
59
|
if (!backendPluginApi.isChildPath(this.#root, root)) {
|
|
60
60
|
throw new Error(
|
|
61
61
|
`Provided path must resolve to a child path of the mock directory, got '${root}'`
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
|
-
function read(path
|
|
65
|
-
if (!fs__default.default.pathExistsSync(path
|
|
64
|
+
function read(path) {
|
|
65
|
+
if (!fs__default.default.pathExistsSync(path)) {
|
|
66
66
|
return void 0;
|
|
67
67
|
}
|
|
68
|
-
const entries = fs__default.default.readdirSync(path
|
|
68
|
+
const entries = fs__default.default.readdirSync(path, { withFileTypes: true });
|
|
69
69
|
return Object.fromEntries(
|
|
70
70
|
entries.map((entry) => {
|
|
71
|
-
const fullPath =
|
|
71
|
+
const fullPath = node_path.resolve(path, entry.name);
|
|
72
72
|
if (entry.isDirectory()) {
|
|
73
73
|
return [entry.name, read(fullPath)];
|
|
74
74
|
}
|
|
75
75
|
const content = fs__default.default.readFileSync(fullPath);
|
|
76
|
-
const relativePosixPath =
|
|
76
|
+
const relativePosixPath = node_path.relative(root, fullPath).split(node_path.win32.sep).join(node_path.posix.sep);
|
|
77
77
|
if (shouldReadAsText(relativePosixPath, content)) {
|
|
78
78
|
return [entry.name, content.toString("utf8")];
|
|
79
79
|
}
|
|
@@ -139,7 +139,7 @@ function registerTestHooks() {
|
|
|
139
139
|
registerTestHooks();
|
|
140
140
|
function createMockDirectory(options) {
|
|
141
141
|
const tmpDir = process.env.RUNNER_TEMP || os__default.default.tmpdir();
|
|
142
|
-
const root = fs__default.default.mkdtempSync(
|
|
142
|
+
const root = fs__default.default.mkdtempSync(node_path.join(tmpDir, "backstage-tmp-test-dir-"));
|
|
143
143
|
const mocker = new MockDirectoryImpl(root);
|
|
144
144
|
const origTmpdir = options?.mockOsTmpDir ? os__default.default.tmpdir : void 0;
|
|
145
145
|
if (origTmpdir) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockDirectory.cjs.js","sources":["../../src/filesystem/MockDirectory.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport os from 'os';\nimport { isChildPath } from '@backstage/backend-plugin-api';\nimport fs from 'fs-extra';\nimport textextensions from 'text-extensions';\nimport {\n dirname,\n extname,\n join as joinPath,\n resolve as resolvePath,\n relative as relativePath,\n win32,\n posix,\n} from 'path';\n\nconst tmpdirMarker = Symbol('os-tmpdir-mock');\n\n/**\n * A context that allows for more advanced file system operations when writing mock directory content.\n *\n * @public\n */\nexport interface MockDirectoryContentCallbackContext {\n /** Absolute path to the location of this piece of content on the filesystem */\n path: string;\n\n /** Creates a symbolic link at the current location */\n symlink(target: string): void;\n}\n\n/**\n * A callback that allows for more advanced file system operations when writing mock directory content.\n *\n * @public\n */\nexport type MockDirectoryContentCallback = (\n ctx: MockDirectoryContentCallbackContext,\n) => void;\n\n/**\n * The content of a mock directory represented by a nested object structure.\n *\n * @remarks\n *\n * When used as input, the keys may contain forward slashes to indicate nested directories.\n * Then returned as output, each directory will always be represented as a separate object.\n *\n * @example\n * ```ts\n * {\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir/file.txt': 'content',\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * }\n * ```\n *\n * @public\n */\nexport type MockDirectoryContent = {\n [name in string]:\n | MockDirectoryContent\n | string\n | Buffer\n | MockDirectoryContentCallback;\n};\n\n/**\n * Options for {@link MockDirectory.content}.\n *\n * @public\n */\nexport interface MockDirectoryContentOptions {\n /**\n * The path to read content from. Defaults to the root of the mock directory.\n *\n * An absolute path can also be provided, as long as it is a child path of the mock directory.\n */\n path?: string;\n\n /**\n * Whether or not to return files as text rather than buffers.\n *\n * Defaults to checking the file extension against a list of known text extensions.\n */\n shouldReadAsText?: boolean | ((path: string, buffer: Buffer) => boolean);\n}\n\n/**\n * A utility for creating a mock directory that is automatically cleaned up.\n *\n * @public\n */\nexport interface MockDirectory {\n /**\n * The path to the root of the mock directory\n */\n readonly path: string;\n\n /**\n * Resolves a path relative to the root of the mock directory.\n */\n resolve(...paths: string[]): string;\n\n /**\n * Sets the content of the mock directory. This will remove any existing content.\n *\n * @example\n * ```ts\n * mockDir.setContent({\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir/file.txt': 'content',\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * });\n * ```\n */\n setContent(root: MockDirectoryContent): void;\n\n /**\n * Adds content of the mock directory. This will overwrite existing files.\n *\n * @example\n * ```ts\n * mockDir.addContent({\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir/file.txt': 'content',\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * });\n * ```\n */\n addContent(root: MockDirectoryContent): void;\n\n /**\n * Reads the content of the mock directory.\n *\n * @remarks\n *\n * Text files will be returned as strings, while binary files will be returned as buffers.\n * By default the file extension is used to determine whether a file should be read as text.\n *\n * @example\n * ```ts\n * expect(mockDir.content()).toEqual({\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir': {\n * 'file.txt': 'content',\n * },\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * });\n * ```\n */\n content(\n options?: MockDirectoryContentOptions,\n ): MockDirectoryContent | undefined;\n\n /**\n * Clears the content of the mock directory, ensuring that the directory itself exists.\n */\n clear(): void;\n\n /**\n * Removes the mock directory and all its contents.\n */\n remove(): void;\n}\n\n/** @internal */\ntype MockEntry =\n | {\n type: 'file';\n path: string;\n content: Buffer;\n }\n | {\n type: 'dir';\n path: string;\n }\n | {\n type: 'callback';\n path: string;\n callback: MockDirectoryContentCallback;\n };\n\n/** @internal */\nclass MockDirectoryImpl {\n readonly #root: string;\n\n constructor(root: string) {\n this.#root = root;\n }\n\n get path(): string {\n return this.#root;\n }\n\n resolve(...paths: string[]): string {\n return resolvePath(this.#root, ...paths);\n }\n\n setContent(root: MockDirectoryContent): void {\n this.remove();\n\n return this.addContent(root);\n }\n\n addContent(root: MockDirectoryContent): void {\n const entries = this.#transformInput(root);\n\n for (const entry of entries) {\n const fullPath = resolvePath(this.#root, entry.path);\n if (!isChildPath(this.#root, fullPath)) {\n throw new Error(\n `Provided path must resolve to a child path of the mock directory, got '${fullPath}'`,\n );\n }\n\n if (entry.type === 'dir') {\n fs.ensureDirSync(fullPath);\n } else if (entry.type === 'file') {\n fs.ensureDirSync(dirname(fullPath));\n fs.writeFileSync(fullPath, entry.content);\n } else if (entry.type === 'callback') {\n fs.ensureDirSync(dirname(fullPath));\n entry.callback({\n path: fullPath,\n symlink(target: string) {\n fs.symlinkSync(target, fullPath);\n },\n });\n }\n }\n }\n\n content(\n options?: MockDirectoryContentOptions,\n ): MockDirectoryContent | undefined {\n const shouldReadAsText =\n (typeof options?.shouldReadAsText === 'boolean'\n ? () => options?.shouldReadAsText\n : options?.shouldReadAsText) ??\n ((path: string) => textextensions.includes(extname(path).slice(1)));\n\n const root = resolvePath(this.#root, options?.path ?? '');\n if (!isChildPath(this.#root, root)) {\n throw new Error(\n `Provided path must resolve to a child path of the mock directory, got '${root}'`,\n );\n }\n\n function read(path: string): MockDirectoryContent | undefined {\n if (!fs.pathExistsSync(path)) {\n return undefined;\n }\n\n const entries = fs.readdirSync(path, { withFileTypes: true });\n return Object.fromEntries(\n entries.map(entry => {\n const fullPath = resolvePath(path, entry.name);\n\n if (entry.isDirectory()) {\n return [entry.name, read(fullPath)];\n }\n const content = fs.readFileSync(fullPath);\n const relativePosixPath = relativePath(root, fullPath)\n .split(win32.sep)\n .join(posix.sep);\n\n if (shouldReadAsText(relativePosixPath, content)) {\n return [entry.name, content.toString('utf8')];\n }\n return [entry.name, content];\n }),\n );\n }\n\n return read(root);\n }\n\n clear = (): void => {\n this.setContent({});\n };\n\n remove = (): void => {\n fs.rmSync(this.#root, { recursive: true, force: true, maxRetries: 10 });\n };\n\n #transformInput(input: MockDirectoryContent[string]): MockEntry[] {\n const entries: MockEntry[] = [];\n\n function traverse(node: MockDirectoryContent[string], path: string) {\n if (typeof node === 'string') {\n entries.push({\n type: 'file',\n path,\n content: Buffer.from(node, 'utf8'),\n });\n } else if (node instanceof Buffer) {\n entries.push({ type: 'file', path, content: node });\n } else if (typeof node === 'function') {\n entries.push({ type: 'callback', path, callback: node });\n } else {\n entries.push({ type: 'dir', path });\n for (const [name, child] of Object.entries(node)) {\n traverse(child, path ? `${path}/${name}` : name);\n }\n }\n }\n\n traverse(input, '');\n\n return entries;\n }\n}\n\n/**\n * Options for {@link createMockDirectory}.\n *\n * @public\n */\nexport interface CreateMockDirectoryOptions {\n /**\n * In addition to creating a temporary directory, also mock `os.tmpdir()` to\n * return the mock directory path until the end of the test suite.\n *\n * When this option is provided the `createMockDirectory` call must happen in\n * a scope where calling `afterAll` from Jest is allowed\n *\n * @returns\n */\n mockOsTmpDir?: boolean;\n\n /**\n * Initializes the directory with the given content, see {@link MockDirectory.setContent}.\n */\n content?: MockDirectoryContent;\n}\n\nconst cleanupCallbacks = new Array<() => void>();\n\nlet registered = false;\nfunction registerTestHooks() {\n if (typeof afterAll !== 'function') {\n return;\n }\n if (registered) {\n return;\n }\n registered = true;\n\n afterAll(async () => {\n for (const callback of cleanupCallbacks) {\n try {\n callback();\n } catch (error) {\n console.error(\n `Failed to clean up mock directory after tests, ${error}`,\n );\n }\n }\n cleanupCallbacks.length = 0;\n });\n}\n\nregisterTestHooks();\n\n/**\n * Creates a new temporary mock directory that will be removed after the tests have completed.\n *\n * @public\n * @remarks\n *\n * This method is intended to be called outside of any test, either at top-level or\n * within a `describe` block. It will call `afterAll` to make sure that the mock directory\n * is removed after the tests have run.\n *\n * @example\n * ```ts\n * describe('MySubject', () => {\n * const mockDir = createMockDirectory();\n *\n * beforeEach(mockDir.clear);\n *\n * it('should work', () => {\n * // ... use mockDir\n * })\n * })\n * ```\n */\nexport function createMockDirectory(\n options?: CreateMockDirectoryOptions,\n): MockDirectory {\n const tmpDir = process.env.RUNNER_TEMP || os.tmpdir(); // GitHub Actions\n const root = fs.mkdtempSync(joinPath(tmpDir, 'backstage-tmp-test-dir-'));\n\n const mocker = new MockDirectoryImpl(root);\n\n const origTmpdir = options?.mockOsTmpDir ? os.tmpdir : undefined;\n if (origTmpdir) {\n if (Object.hasOwn(origTmpdir, tmpdirMarker)) {\n throw new Error(\n 'Cannot mock os.tmpdir() when it has already been mocked',\n );\n }\n const mock = Object.assign(() => mocker.path, { [tmpdirMarker]: true });\n os.tmpdir = mock;\n }\n\n // In CI we expect there to be no need to clean up temporary directories\n const needsCleanup = !process.env.CI;\n if (needsCleanup) {\n process.on('beforeExit', mocker.remove);\n }\n\n if (needsCleanup) {\n cleanupCallbacks.push(() => mocker.remove());\n }\n\n if (origTmpdir) {\n afterAll(() => {\n os.tmpdir = origTmpdir;\n });\n }\n\n if (options?.content) {\n mocker.setContent(options.content);\n }\n\n return mocker;\n}\n"],"names":["resolvePath","isChildPath","fs","dirname","path","textextensions","extname","relativePath","win32","posix","os","joinPath"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MockDirectory.cjs.js","sources":["../../src/filesystem/MockDirectory.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport os from 'node:os';\nimport { isChildPath } from '@backstage/backend-plugin-api';\nimport fs from 'fs-extra';\nimport textextensions from 'text-extensions';\nimport {\n dirname,\n extname,\n join as joinPath,\n resolve as resolvePath,\n relative as relativePath,\n win32,\n posix,\n} from 'node:path';\n\nconst tmpdirMarker = Symbol('os-tmpdir-mock');\n\n/**\n * A context that allows for more advanced file system operations when writing mock directory content.\n *\n * @public\n */\nexport interface MockDirectoryContentCallbackContext {\n /** Absolute path to the location of this piece of content on the filesystem */\n path: string;\n\n /** Creates a symbolic link at the current location */\n symlink(target: string): void;\n}\n\n/**\n * A callback that allows for more advanced file system operations when writing mock directory content.\n *\n * @public\n */\nexport type MockDirectoryContentCallback = (\n ctx: MockDirectoryContentCallbackContext,\n) => void;\n\n/**\n * The content of a mock directory represented by a nested object structure.\n *\n * @remarks\n *\n * When used as input, the keys may contain forward slashes to indicate nested directories.\n * Then returned as output, each directory will always be represented as a separate object.\n *\n * @example\n * ```ts\n * {\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir/file.txt': 'content',\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * }\n * ```\n *\n * @public\n */\nexport type MockDirectoryContent = {\n [name in string]:\n | MockDirectoryContent\n | string\n | Buffer\n | MockDirectoryContentCallback;\n};\n\n/**\n * Options for {@link MockDirectory.content}.\n *\n * @public\n */\nexport interface MockDirectoryContentOptions {\n /**\n * The path to read content from. Defaults to the root of the mock directory.\n *\n * An absolute path can also be provided, as long as it is a child path of the mock directory.\n */\n path?: string;\n\n /**\n * Whether or not to return files as text rather than buffers.\n *\n * Defaults to checking the file extension against a list of known text extensions.\n */\n shouldReadAsText?: boolean | ((path: string, buffer: Buffer) => boolean);\n}\n\n/**\n * A utility for creating a mock directory that is automatically cleaned up.\n *\n * @public\n */\nexport interface MockDirectory {\n /**\n * The path to the root of the mock directory\n */\n readonly path: string;\n\n /**\n * Resolves a path relative to the root of the mock directory.\n */\n resolve(...paths: string[]): string;\n\n /**\n * Sets the content of the mock directory. This will remove any existing content.\n *\n * @example\n * ```ts\n * mockDir.setContent({\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir/file.txt': 'content',\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * });\n * ```\n */\n setContent(root: MockDirectoryContent): void;\n\n /**\n * Adds content of the mock directory. This will overwrite existing files.\n *\n * @example\n * ```ts\n * mockDir.addContent({\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir/file.txt': 'content',\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * });\n * ```\n */\n addContent(root: MockDirectoryContent): void;\n\n /**\n * Reads the content of the mock directory.\n *\n * @remarks\n *\n * Text files will be returned as strings, while binary files will be returned as buffers.\n * By default the file extension is used to determine whether a file should be read as text.\n *\n * @example\n * ```ts\n * expect(mockDir.content()).toEqual({\n * 'test.txt': 'content',\n * 'sub-dir': {\n * 'file.txt': 'content',\n * 'nested-dir': {\n * 'file.txt': 'content',\n * },\n * },\n * 'empty-dir': {},\n * 'binary-file': Buffer.from([0, 1, 2]),\n * });\n * ```\n */\n content(\n options?: MockDirectoryContentOptions,\n ): MockDirectoryContent | undefined;\n\n /**\n * Clears the content of the mock directory, ensuring that the directory itself exists.\n */\n clear(): void;\n\n /**\n * Removes the mock directory and all its contents.\n */\n remove(): void;\n}\n\n/** @internal */\ntype MockEntry =\n | {\n type: 'file';\n path: string;\n content: Buffer;\n }\n | {\n type: 'dir';\n path: string;\n }\n | {\n type: 'callback';\n path: string;\n callback: MockDirectoryContentCallback;\n };\n\n/** @internal */\nclass MockDirectoryImpl {\n readonly #root: string;\n\n constructor(root: string) {\n this.#root = root;\n }\n\n get path(): string {\n return this.#root;\n }\n\n resolve(...paths: string[]): string {\n return resolvePath(this.#root, ...paths);\n }\n\n setContent(root: MockDirectoryContent): void {\n this.remove();\n\n return this.addContent(root);\n }\n\n addContent(root: MockDirectoryContent): void {\n const entries = this.#transformInput(root);\n\n for (const entry of entries) {\n const fullPath = resolvePath(this.#root, entry.path);\n if (!isChildPath(this.#root, fullPath)) {\n throw new Error(\n `Provided path must resolve to a child path of the mock directory, got '${fullPath}'`,\n );\n }\n\n if (entry.type === 'dir') {\n fs.ensureDirSync(fullPath);\n } else if (entry.type === 'file') {\n fs.ensureDirSync(dirname(fullPath));\n fs.writeFileSync(fullPath, entry.content);\n } else if (entry.type === 'callback') {\n fs.ensureDirSync(dirname(fullPath));\n entry.callback({\n path: fullPath,\n symlink(target: string) {\n fs.symlinkSync(target, fullPath);\n },\n });\n }\n }\n }\n\n content(\n options?: MockDirectoryContentOptions,\n ): MockDirectoryContent | undefined {\n const shouldReadAsText =\n (typeof options?.shouldReadAsText === 'boolean'\n ? () => options?.shouldReadAsText\n : options?.shouldReadAsText) ??\n ((path: string) => textextensions.includes(extname(path).slice(1)));\n\n const root = resolvePath(this.#root, options?.path ?? '');\n if (!isChildPath(this.#root, root)) {\n throw new Error(\n `Provided path must resolve to a child path of the mock directory, got '${root}'`,\n );\n }\n\n function read(path: string): MockDirectoryContent | undefined {\n if (!fs.pathExistsSync(path)) {\n return undefined;\n }\n\n const entries = fs.readdirSync(path, { withFileTypes: true });\n return Object.fromEntries(\n entries.map(entry => {\n const fullPath = resolvePath(path, entry.name);\n\n if (entry.isDirectory()) {\n return [entry.name, read(fullPath)];\n }\n const content = fs.readFileSync(fullPath);\n const relativePosixPath = relativePath(root, fullPath)\n .split(win32.sep)\n .join(posix.sep);\n\n if (shouldReadAsText(relativePosixPath, content)) {\n return [entry.name, content.toString('utf8')];\n }\n return [entry.name, content];\n }),\n );\n }\n\n return read(root);\n }\n\n clear = (): void => {\n this.setContent({});\n };\n\n remove = (): void => {\n fs.rmSync(this.#root, { recursive: true, force: true, maxRetries: 10 });\n };\n\n #transformInput(input: MockDirectoryContent[string]): MockEntry[] {\n const entries: MockEntry[] = [];\n\n function traverse(node: MockDirectoryContent[string], path: string) {\n if (typeof node === 'string') {\n entries.push({\n type: 'file',\n path,\n content: Buffer.from(node, 'utf8'),\n });\n } else if (node instanceof Buffer) {\n entries.push({ type: 'file', path, content: node });\n } else if (typeof node === 'function') {\n entries.push({ type: 'callback', path, callback: node });\n } else {\n entries.push({ type: 'dir', path });\n for (const [name, child] of Object.entries(node)) {\n traverse(child, path ? `${path}/${name}` : name);\n }\n }\n }\n\n traverse(input, '');\n\n return entries;\n }\n}\n\n/**\n * Options for {@link createMockDirectory}.\n *\n * @public\n */\nexport interface CreateMockDirectoryOptions {\n /**\n * In addition to creating a temporary directory, also mock `os.tmpdir()` to\n * return the mock directory path until the end of the test suite.\n *\n * When this option is provided the `createMockDirectory` call must happen in\n * a scope where calling `afterAll` from Jest is allowed\n *\n * @returns\n */\n mockOsTmpDir?: boolean;\n\n /**\n * Initializes the directory with the given content, see {@link MockDirectory.setContent}.\n */\n content?: MockDirectoryContent;\n}\n\nconst cleanupCallbacks = new Array<() => void>();\n\nlet registered = false;\nfunction registerTestHooks() {\n if (typeof afterAll !== 'function') {\n return;\n }\n if (registered) {\n return;\n }\n registered = true;\n\n afterAll(async () => {\n for (const callback of cleanupCallbacks) {\n try {\n callback();\n } catch (error) {\n console.error(\n `Failed to clean up mock directory after tests, ${error}`,\n );\n }\n }\n cleanupCallbacks.length = 0;\n });\n}\n\nregisterTestHooks();\n\n/**\n * Creates a new temporary mock directory that will be removed after the tests have completed.\n *\n * @public\n * @remarks\n *\n * This method is intended to be called outside of any test, either at top-level or\n * within a `describe` block. It will call `afterAll` to make sure that the mock directory\n * is removed after the tests have run.\n *\n * @example\n * ```ts\n * describe('MySubject', () => {\n * const mockDir = createMockDirectory();\n *\n * beforeEach(mockDir.clear);\n *\n * it('should work', () => {\n * // ... use mockDir\n * })\n * })\n * ```\n */\nexport function createMockDirectory(\n options?: CreateMockDirectoryOptions,\n): MockDirectory {\n const tmpDir = process.env.RUNNER_TEMP || os.tmpdir(); // GitHub Actions\n const root = fs.mkdtempSync(joinPath(tmpDir, 'backstage-tmp-test-dir-'));\n\n const mocker = new MockDirectoryImpl(root);\n\n const origTmpdir = options?.mockOsTmpDir ? os.tmpdir : undefined;\n if (origTmpdir) {\n if (Object.hasOwn(origTmpdir, tmpdirMarker)) {\n throw new Error(\n 'Cannot mock os.tmpdir() when it has already been mocked',\n );\n }\n const mock = Object.assign(() => mocker.path, { [tmpdirMarker]: true });\n os.tmpdir = mock;\n }\n\n // In CI we expect there to be no need to clean up temporary directories\n const needsCleanup = !process.env.CI;\n if (needsCleanup) {\n process.on('beforeExit', mocker.remove);\n }\n\n if (needsCleanup) {\n cleanupCallbacks.push(() => mocker.remove());\n }\n\n if (origTmpdir) {\n afterAll(() => {\n os.tmpdir = origTmpdir;\n });\n }\n\n if (options?.content) {\n mocker.setContent(options.content);\n }\n\n return mocker;\n}\n"],"names":["resolvePath","isChildPath","fs","dirname","textextensions","extname","relativePath","win32","posix","os","joinPath"],"mappings":"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"}
|
|
@@ -48,9 +48,9 @@ function createPluginsForOrphanModules(features) {
|
|
|
48
48
|
if (isInternalBackendRegistrations(feature)) {
|
|
49
49
|
const registrations = feature.getRegistrations();
|
|
50
50
|
for (const registration of registrations) {
|
|
51
|
-
if (registration.type === "plugin") {
|
|
51
|
+
if (registration.type === "plugin" || registration.type === "plugin-v1.1") {
|
|
52
52
|
pluginIds.add(registration.pluginId);
|
|
53
|
-
} else if (registration.type === "module") {
|
|
53
|
+
} else if (registration.type === "module" || registration.type === "module-v1.1") {
|
|
54
54
|
modulePluginIds.add(registration.pluginId);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -85,7 +85,7 @@ function createExtensionPointTestModules(features, extensionPointTuples) {
|
|
|
85
85
|
const extensionPointsToSort = new Set(extensionPointMap.keys());
|
|
86
86
|
const extensionPointsByPlugin = /* @__PURE__ */ new Map();
|
|
87
87
|
for (const registration of registrations) {
|
|
88
|
-
if (registration.type === "module") {
|
|
88
|
+
if (registration.type === "module" || registration.type === "module-v1.1") {
|
|
89
89
|
const testDep = Object.values(registration.init.deps).filter(
|
|
90
90
|
(dep) => extensionPointsToSort.has(dep.id)
|
|
91
91
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestBackend.cjs.js","sources":["../../src/wiring/TestBackend.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Backend, createSpecializedBackend } from '@backstage/backend-app-api';\nimport {\n createServiceFactory,\n BackendFeature,\n ExtensionPoint,\n coreServices,\n createBackendModule,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { mockServices } from '../services';\nimport { ConfigReader } from '@backstage/config';\nimport express from 'express';\n// Direct internal import to avoid duplication\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n InternalBackendFeature,\n InternalBackendRegistrations,\n} from '../../../backend-plugin-api/src/wiring/types';\nimport {\n DefaultRootHttpRouter,\n ExtendedHttpServer,\n MiddlewareFactory,\n createHealthRouter,\n createHttpServer,\n} from '@backstage/backend-defaults/rootHttpRouter';\nimport { HostDiscovery } from '@backstage/backend-defaults/discovery';\nimport {\n actionsRegistryServiceMock,\n actionsServiceMock,\n} from '../alpha/services';\n\n/** @public */\nexport interface TestBackendOptions<TExtensionPoints extends any[]> {\n extensionPoints?: readonly [\n ...{\n [index in keyof TExtensionPoints]: [\n ExtensionPoint<TExtensionPoints[index]>,\n Partial<TExtensionPoints[index]>,\n ];\n },\n ];\n features?: Array<BackendFeature | Promise<{ default: BackendFeature }>>;\n}\n\n/** @public */\nexport interface TestBackend extends Backend {\n /**\n * Provides access to the underling HTTP server for use with utilities\n * such as `supertest`.\n *\n * If the root http router service has been replaced, this will throw an error.\n */\n readonly server: ExtendedHttpServer;\n}\n\nexport const defaultServiceFactories = [\n mockServices.auth.factory(),\n mockServices.auditor.factory(),\n mockServices.cache.factory(),\n mockServices.rootConfig.factory(),\n mockServices.database.factory(),\n mockServices.httpAuth.factory(),\n mockServices.httpRouter.factory(),\n mockServices.lifecycle.factory(),\n mockServices.logger.factory(),\n mockServices.permissions.factory(),\n mockServices.permissionsRegistry.factory(),\n mockServices.rootHealth.factory(),\n mockServices.rootLifecycle.factory(),\n mockServices.rootLogger.factory(),\n mockServices.scheduler.factory(),\n mockServices.userInfo.factory(),\n mockServices.urlReader.factory(),\n mockServices.events.factory(),\n\n // Alpha services\n actionsRegistryServiceMock.factory(),\n actionsServiceMock.factory(),\n];\n\n/**\n * Given a set of features, return an array of plugins that ensures that each\n * module in the provided set of features has a corresponding plugin.\n * @internal\n */\nfunction createPluginsForOrphanModules(features: Array<BackendFeature>) {\n const pluginIds = new Set<string>();\n const modulePluginIds = new Set<string>();\n\n for (const feature of features) {\n if (isInternalBackendRegistrations(feature)) {\n const registrations = feature.getRegistrations();\n for (const registration of registrations) {\n if (registration.type === 'plugin') {\n pluginIds.add(registration.pluginId);\n } else if (registration.type === 'module') {\n modulePluginIds.add(registration.pluginId);\n }\n }\n }\n }\n\n for (const pluginId of pluginIds) {\n modulePluginIds.delete(pluginId);\n }\n\n return Array.from(modulePluginIds).map(pluginId =>\n createBackendPlugin({\n pluginId,\n register(reg) {\n reg.registerInit({ deps: {}, async init() {} });\n },\n }),\n );\n}\n\n/**\n * Given a set of extension points and features, find the extension\n * points that we mock and tie them to the correct plugin ID.\n * @returns\n */\nfunction createExtensionPointTestModules(\n features: Array<BackendFeature>,\n extensionPointTuples?: readonly [\n ref: ExtensionPoint<unknown>,\n impl: unknown,\n ][],\n): Array<BackendFeature> {\n if (!extensionPointTuples) {\n return [];\n }\n\n const registrations = features.flatMap(feature => {\n if (isInternalBackendRegistrations(feature)) {\n return feature.getRegistrations();\n }\n return [];\n });\n\n const extensionPointMap = new Map(\n extensionPointTuples.map(ep => [ep[0].id, ep]),\n );\n const extensionPointsToSort = new Set(extensionPointMap.keys());\n const extensionPointsByPlugin = new Map<string, string[]>();\n\n for (const registration of registrations) {\n if (registration.type === 'module') {\n const testDep = Object.values(registration.init.deps).filter(dep =>\n extensionPointsToSort.has(dep.id),\n );\n if (testDep.length > 0) {\n let points = extensionPointsByPlugin.get(registration.pluginId);\n if (!points) {\n points = [];\n extensionPointsByPlugin.set(registration.pluginId, points);\n }\n for (const { id } of testDep) {\n points.push(id);\n extensionPointsToSort.delete(id);\n }\n }\n }\n }\n\n if (extensionPointsToSort.size > 0) {\n const list = Array.from(extensionPointsToSort)\n .map(id => `'${id}'`)\n .join(', ');\n throw new Error(\n `Unable to determine the plugin ID of extension point(s) ${list}. ` +\n 'Tested extension points must be depended on by one or more tested modules.',\n );\n }\n\n const modules = [];\n\n for (const [pluginId, pluginExtensionPointIds] of extensionPointsByPlugin) {\n modules.push(\n createBackendModule({\n pluginId,\n moduleId: 'test-extension-point-registration',\n register(reg) {\n for (const id of pluginExtensionPointIds) {\n const tuple = extensionPointMap.get(id)!;\n reg.registerExtensionPoint(...tuple);\n }\n\n reg.registerInit({ deps: {}, async init() {} });\n },\n }),\n );\n }\n\n return modules;\n}\n\nfunction isPromise<T>(value: unknown | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n );\n}\n\n// Same as in the backend-app-api, handles double defaults from dynamic imports\nfunction unwrapFeature(\n feature: BackendFeature | { default: BackendFeature },\n): BackendFeature {\n if ('$$type' in feature) {\n return feature;\n }\n\n if ('default' in feature) {\n return feature.default;\n }\n\n return feature;\n}\n\nconst backendInstancesToCleanUp = new Array<Backend>();\n\n/** @public */\nexport async function startTestBackend<TExtensionPoints extends any[]>(\n options: TestBackendOptions<TExtensionPoints>,\n): Promise<TestBackend> {\n const { extensionPoints, ...otherOptions } = options;\n\n // Unpack input into awaited plain BackendFeatures\n const features: BackendFeature[] = await Promise.all(\n options.features?.map(async val => {\n if (isPromise(val)) {\n const { default: feature } = await val;\n return unwrapFeature(feature);\n }\n return unwrapFeature(val);\n }) ?? [],\n );\n\n let server: ExtendedHttpServer;\n\n const rootHttpRouterFactory = createServiceFactory({\n service: coreServices.rootHttpRouter,\n deps: {\n config: coreServices.rootConfig,\n lifecycle: coreServices.rootLifecycle,\n rootLogger: coreServices.rootLogger,\n health: coreServices.rootHealth,\n },\n async factory({ config, lifecycle, rootLogger, health }) {\n const router = DefaultRootHttpRouter.create();\n const logger = rootLogger.child({ service: 'rootHttpRouter' });\n\n const app = express();\n\n const middleware = MiddlewareFactory.create({ config, logger });\n const healthRouter = createHealthRouter({ config, health });\n\n app.use(healthRouter);\n app.use(router.handler());\n app.use(middleware.notFound());\n app.use(middleware.error());\n\n server = await createHttpServer(\n app,\n { listen: { host: '', port: 0 } },\n { logger },\n );\n\n lifecycle.addShutdownHook(() => server.stop(), { logger });\n\n await server.start();\n\n return router;\n },\n });\n\n const discoveryFactory = createServiceFactory({\n service: coreServices.discovery,\n deps: {\n rootHttpRouter: coreServices.rootHttpRouter,\n },\n async factory() {\n if (!server) {\n throw new Error('Test server not started yet');\n }\n const port = server.port();\n const discovery = HostDiscovery.fromConfig(\n new ConfigReader({\n backend: { baseUrl: `http://localhost:${port}`, listen: { port } },\n }),\n );\n return discovery;\n },\n });\n\n const backend = createSpecializedBackend({\n ...otherOptions,\n defaultServiceFactories: [\n ...defaultServiceFactories,\n rootHttpRouterFactory,\n discoveryFactory,\n ],\n });\n\n backendInstancesToCleanUp.push(backend);\n\n for (const m of createExtensionPointTestModules(features, extensionPoints)) {\n backend.add(m);\n }\n for (const p of createPluginsForOrphanModules(features)) {\n backend.add(p);\n }\n for (const feature of features) {\n backend.add(feature);\n }\n\n await backend.start();\n\n return Object.assign(backend, {\n get server() {\n if (!server) {\n throw new Error('TestBackend server is not available');\n }\n return server;\n },\n });\n}\n\nlet registered = false;\nfunction registerTestHooks() {\n if (typeof afterAll !== 'function') {\n return;\n }\n if (registered) {\n return;\n }\n registered = true;\n\n afterAll(async () => {\n await Promise.all(\n backendInstancesToCleanUp.map(async backend => {\n try {\n await backend.stop();\n } catch (error) {\n console.error(`Failed to stop backend after tests, ${error}`);\n }\n }),\n );\n backendInstancesToCleanUp.length = 0;\n });\n}\n\nregisterTestHooks();\n\nfunction toInternalBackendFeature(\n feature: BackendFeature,\n): InternalBackendFeature {\n if (feature.$$type !== '@backstage/BackendFeature') {\n throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`);\n }\n const internal = feature as InternalBackendFeature;\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid BackendFeature, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n\nfunction isInternalBackendRegistrations(\n feature: BackendFeature,\n): feature is InternalBackendRegistrations {\n const internal = toInternalBackendFeature(feature);\n if (internal.featureType === 'registrations') {\n return true;\n }\n // Backwards compatibility for v1 registrations that use duck typing\n return 'getRegistrations' in internal;\n}\n"],"names":["mockServices","actionsRegistryServiceMock","actionsServiceMock","createBackendPlugin","createBackendModule","createServiceFactory","coreServices","DefaultRootHttpRouter","express","MiddlewareFactory","createHealthRouter","createHttpServer","discovery","HostDiscovery","ConfigReader","createSpecializedBackend"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TestBackend.cjs.js","sources":["../../src/wiring/TestBackend.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Backend, createSpecializedBackend } from '@backstage/backend-app-api';\nimport {\n createServiceFactory,\n BackendFeature,\n ExtensionPoint,\n coreServices,\n createBackendModule,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { mockServices } from '../services';\nimport { ConfigReader } from '@backstage/config';\nimport express from 'express';\n// Direct internal import to avoid duplication\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n InternalBackendFeature,\n InternalBackendRegistrations,\n} from '../../../backend-plugin-api/src/wiring/types';\nimport {\n DefaultRootHttpRouter,\n ExtendedHttpServer,\n MiddlewareFactory,\n createHealthRouter,\n createHttpServer,\n} from '@backstage/backend-defaults/rootHttpRouter';\nimport { HostDiscovery } from '@backstage/backend-defaults/discovery';\nimport {\n actionsRegistryServiceMock,\n actionsServiceMock,\n} from '../alpha/services';\n\n/** @public */\nexport interface TestBackendOptions<TExtensionPoints extends any[]> {\n extensionPoints?: readonly [\n ...{\n [index in keyof TExtensionPoints]: [\n ExtensionPoint<TExtensionPoints[index]>,\n Partial<TExtensionPoints[index]>,\n ];\n },\n ];\n features?: Array<BackendFeature | Promise<{ default: BackendFeature }>>;\n}\n\n/** @public */\nexport interface TestBackend extends Backend {\n /**\n * Provides access to the underling HTTP server for use with utilities\n * such as `supertest`.\n *\n * If the root http router service has been replaced, this will throw an error.\n */\n readonly server: ExtendedHttpServer;\n}\n\nexport const defaultServiceFactories = [\n mockServices.auth.factory(),\n mockServices.auditor.factory(),\n mockServices.cache.factory(),\n mockServices.rootConfig.factory(),\n mockServices.database.factory(),\n mockServices.httpAuth.factory(),\n mockServices.httpRouter.factory(),\n mockServices.lifecycle.factory(),\n mockServices.logger.factory(),\n mockServices.permissions.factory(),\n mockServices.permissionsRegistry.factory(),\n mockServices.rootHealth.factory(),\n mockServices.rootLifecycle.factory(),\n mockServices.rootLogger.factory(),\n mockServices.scheduler.factory(),\n mockServices.userInfo.factory(),\n mockServices.urlReader.factory(),\n mockServices.events.factory(),\n\n // Alpha services\n actionsRegistryServiceMock.factory(),\n actionsServiceMock.factory(),\n];\n\n/**\n * Given a set of features, return an array of plugins that ensures that each\n * module in the provided set of features has a corresponding plugin.\n * @internal\n */\nfunction createPluginsForOrphanModules(features: Array<BackendFeature>) {\n const pluginIds = new Set<string>();\n const modulePluginIds = new Set<string>();\n\n for (const feature of features) {\n if (isInternalBackendRegistrations(feature)) {\n const registrations = feature.getRegistrations();\n for (const registration of registrations) {\n if (\n registration.type === 'plugin' ||\n registration.type === 'plugin-v1.1'\n ) {\n pluginIds.add(registration.pluginId);\n } else if (\n registration.type === 'module' ||\n registration.type === 'module-v1.1'\n ) {\n modulePluginIds.add(registration.pluginId);\n }\n }\n }\n }\n\n for (const pluginId of pluginIds) {\n modulePluginIds.delete(pluginId);\n }\n\n return Array.from(modulePluginIds).map(pluginId =>\n createBackendPlugin({\n pluginId,\n register(reg) {\n reg.registerInit({ deps: {}, async init() {} });\n },\n }),\n );\n}\n\n/**\n * Given a set of extension points and features, find the extension\n * points that we mock and tie them to the correct plugin ID.\n * @returns\n */\nfunction createExtensionPointTestModules(\n features: Array<BackendFeature>,\n extensionPointTuples?: readonly [\n ref: ExtensionPoint<unknown>,\n impl: unknown,\n ][],\n): Array<BackendFeature> {\n if (!extensionPointTuples) {\n return [];\n }\n\n const registrations = features.flatMap(feature => {\n if (isInternalBackendRegistrations(feature)) {\n return feature.getRegistrations();\n }\n return [];\n });\n\n const extensionPointMap = new Map(\n extensionPointTuples.map(ep => [ep[0].id, ep]),\n );\n const extensionPointsToSort = new Set(extensionPointMap.keys());\n const extensionPointsByPlugin = new Map<string, string[]>();\n\n for (const registration of registrations) {\n if (registration.type === 'module' || registration.type === 'module-v1.1') {\n const testDep = Object.values(registration.init.deps).filter(dep =>\n extensionPointsToSort.has(dep.id),\n );\n if (testDep.length > 0) {\n let points = extensionPointsByPlugin.get(registration.pluginId);\n if (!points) {\n points = [];\n extensionPointsByPlugin.set(registration.pluginId, points);\n }\n for (const { id } of testDep) {\n points.push(id);\n extensionPointsToSort.delete(id);\n }\n }\n }\n }\n\n if (extensionPointsToSort.size > 0) {\n const list = Array.from(extensionPointsToSort)\n .map(id => `'${id}'`)\n .join(', ');\n throw new Error(\n `Unable to determine the plugin ID of extension point(s) ${list}. ` +\n 'Tested extension points must be depended on by one or more tested modules.',\n );\n }\n\n const modules = [];\n\n for (const [pluginId, pluginExtensionPointIds] of extensionPointsByPlugin) {\n modules.push(\n createBackendModule({\n pluginId,\n moduleId: 'test-extension-point-registration',\n register(reg) {\n for (const id of pluginExtensionPointIds) {\n const tuple = extensionPointMap.get(id)!;\n reg.registerExtensionPoint(...tuple);\n }\n\n reg.registerInit({ deps: {}, async init() {} });\n },\n }),\n );\n }\n\n return modules;\n}\n\nfunction isPromise<T>(value: unknown | Promise<T>): value is Promise<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function'\n );\n}\n\n// Same as in the backend-app-api, handles double defaults from dynamic imports\nfunction unwrapFeature(\n feature: BackendFeature | { default: BackendFeature },\n): BackendFeature {\n if ('$$type' in feature) {\n return feature;\n }\n\n if ('default' in feature) {\n return feature.default;\n }\n\n return feature;\n}\n\nconst backendInstancesToCleanUp = new Array<Backend>();\n\n/** @public */\nexport async function startTestBackend<TExtensionPoints extends any[]>(\n options: TestBackendOptions<TExtensionPoints>,\n): Promise<TestBackend> {\n const { extensionPoints, ...otherOptions } = options;\n\n // Unpack input into awaited plain BackendFeatures\n const features: BackendFeature[] = await Promise.all(\n options.features?.map(async val => {\n if (isPromise(val)) {\n const { default: feature } = await val;\n return unwrapFeature(feature);\n }\n return unwrapFeature(val);\n }) ?? [],\n );\n\n let server: ExtendedHttpServer;\n\n const rootHttpRouterFactory = createServiceFactory({\n service: coreServices.rootHttpRouter,\n deps: {\n config: coreServices.rootConfig,\n lifecycle: coreServices.rootLifecycle,\n rootLogger: coreServices.rootLogger,\n health: coreServices.rootHealth,\n },\n async factory({ config, lifecycle, rootLogger, health }) {\n const router = DefaultRootHttpRouter.create();\n const logger = rootLogger.child({ service: 'rootHttpRouter' });\n\n const app = express();\n\n const middleware = MiddlewareFactory.create({ config, logger });\n const healthRouter = createHealthRouter({ config, health });\n\n app.use(healthRouter);\n app.use(router.handler());\n app.use(middleware.notFound());\n app.use(middleware.error());\n\n server = await createHttpServer(\n app,\n { listen: { host: '', port: 0 } },\n { logger },\n );\n\n lifecycle.addShutdownHook(() => server.stop(), { logger });\n\n await server.start();\n\n return router;\n },\n });\n\n const discoveryFactory = createServiceFactory({\n service: coreServices.discovery,\n deps: {\n rootHttpRouter: coreServices.rootHttpRouter,\n },\n async factory() {\n if (!server) {\n throw new Error('Test server not started yet');\n }\n const port = server.port();\n const discovery = HostDiscovery.fromConfig(\n new ConfigReader({\n backend: { baseUrl: `http://localhost:${port}`, listen: { port } },\n }),\n );\n return discovery;\n },\n });\n\n const backend = createSpecializedBackend({\n ...otherOptions,\n defaultServiceFactories: [\n ...defaultServiceFactories,\n rootHttpRouterFactory,\n discoveryFactory,\n ],\n });\n\n backendInstancesToCleanUp.push(backend);\n\n for (const m of createExtensionPointTestModules(features, extensionPoints)) {\n backend.add(m);\n }\n for (const p of createPluginsForOrphanModules(features)) {\n backend.add(p);\n }\n for (const feature of features) {\n backend.add(feature);\n }\n\n await backend.start();\n\n return Object.assign(backend, {\n get server() {\n if (!server) {\n throw new Error('TestBackend server is not available');\n }\n return server;\n },\n });\n}\n\nlet registered = false;\nfunction registerTestHooks() {\n if (typeof afterAll !== 'function') {\n return;\n }\n if (registered) {\n return;\n }\n registered = true;\n\n afterAll(async () => {\n await Promise.all(\n backendInstancesToCleanUp.map(async backend => {\n try {\n await backend.stop();\n } catch (error) {\n console.error(`Failed to stop backend after tests, ${error}`);\n }\n }),\n );\n backendInstancesToCleanUp.length = 0;\n });\n}\n\nregisterTestHooks();\n\nfunction toInternalBackendFeature(\n feature: BackendFeature,\n): InternalBackendFeature {\n if (feature.$$type !== '@backstage/BackendFeature') {\n throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`);\n }\n const internal = feature as InternalBackendFeature;\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid BackendFeature, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n\nfunction isInternalBackendRegistrations(\n feature: BackendFeature,\n): feature is InternalBackendRegistrations {\n const internal = toInternalBackendFeature(feature);\n if (internal.featureType === 'registrations') {\n return true;\n }\n // Backwards compatibility for v1 registrations that use duck typing\n return 'getRegistrations' in internal;\n}\n"],"names":["mockServices","actionsRegistryServiceMock","actionsServiceMock","createBackendPlugin","createBackendModule","createServiceFactory","coreServices","DefaultRootHttpRouter","express","MiddlewareFactory","createHealthRouter","createHttpServer","discovery","HostDiscovery","ConfigReader","createSpecializedBackend"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/backend-test-utils",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.4-next.0",
|
|
4
4
|
"description": "Test helpers library for Backstage backends",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "node-library"
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
"test": "backstage-cli package test"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@backstage/backend-app-api": "
|
|
61
|
-
"@backstage/backend-defaults": "
|
|
62
|
-
"@backstage/backend-plugin-api": "
|
|
63
|
-
"@backstage/config": "
|
|
64
|
-
"@backstage/errors": "
|
|
65
|
-
"@backstage/plugin-auth-node": "
|
|
66
|
-
"@backstage/plugin-events-node": "
|
|
67
|
-
"@backstage/plugin-permission-common": "
|
|
68
|
-
"@backstage/types": "
|
|
60
|
+
"@backstage/backend-app-api": "1.5.0-next.0",
|
|
61
|
+
"@backstage/backend-defaults": "0.15.1-next.0",
|
|
62
|
+
"@backstage/backend-plugin-api": "1.7.0-next.0",
|
|
63
|
+
"@backstage/config": "1.3.6",
|
|
64
|
+
"@backstage/errors": "1.2.7",
|
|
65
|
+
"@backstage/plugin-auth-node": "0.6.12-next.0",
|
|
66
|
+
"@backstage/plugin-events-node": "0.4.19-next.0",
|
|
67
|
+
"@backstage/plugin-permission-common": "0.9.5-next.0",
|
|
68
|
+
"@backstage/types": "1.2.2",
|
|
69
69
|
"@keyv/memcache": "^2.0.1",
|
|
70
70
|
"@keyv/redis": "^4.0.1",
|
|
71
71
|
"@keyv/valkey": "^1.0.1",
|
|
@@ -87,11 +87,11 @@
|
|
|
87
87
|
"text-extensions": "^2.4.0",
|
|
88
88
|
"uuid": "^11.0.0",
|
|
89
89
|
"yn": "^4.0.0",
|
|
90
|
-
"zod": "^3.
|
|
90
|
+
"zod": "^3.25.76",
|
|
91
91
|
"zod-to-json-schema": "^3.25.1"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@backstage/cli": "
|
|
94
|
+
"@backstage/cli": "0.35.3-next.0",
|
|
95
95
|
"@types/jest": "*",
|
|
96
96
|
"@types/lodash": "^4.14.151",
|
|
97
97
|
"@types/supertest": "^2.0.8",
|