Merge pull request #24873 from backstage/freben/cache-test

backend-test-utils: add cache testing utilities
This commit is contained in:
Fredrik Adelöw
2024-05-23 15:51:00 +02:00
committed by GitHub
18 changed files with 678 additions and 42 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-test-utils': minor
---
Added `TestCaches` that functions just like `TestDatabases`
+2 -2
View File
@@ -188,7 +188,7 @@ jobs:
ports:
- 3306/tcp
redis:
image: redis
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
@@ -240,7 +240,7 @@ jobs:
BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }}
BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }}
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }}
BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }}
# We run the test cases before verifying the specs to prevent any failing tests from causing errors.
- name: verify openapi specs against test cases
+10
View File
@@ -55,6 +55,15 @@ jobs:
--health-retries 5
ports:
- 3306/tcp
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379/tcp
env:
CI: true
@@ -115,6 +124,7 @@ jobs:
BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }}
BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }}
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }}
- name: Discord notification
if: ${{ failure() }}
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import { mockServices } from '@backstage/backend-test-utils';
import { mockServices, TestCaches } from '@backstage/backend-test-utils';
import KeyvRedis from '@keyv/redis';
import KeyvMemcache from '@keyv/memcache';
import { CacheManager } from './CacheManager';
// This test is in a separate file because the main test file uses other mocking
@@ -23,28 +24,32 @@ import { CacheManager } from './CacheManager';
// Contrived code because it's hard to spy on a default export
jest.mock('@keyv/redis', () => {
const ActualKeyvRedis = jest.requireActual('@keyv/redis');
const Actual = jest.requireActual('@keyv/redis');
return jest.fn((...args: any[]) => {
return new ActualKeyvRedis(...args);
return new Actual(...args);
});
});
jest.mock('@keyv/memcache', () => {
const Actual = jest.requireActual('@keyv/memcache');
return jest.fn((...args: any[]) => {
return new Actual(...args);
});
});
describe('CacheManager integration', () => {
describe('redis', () => {
it('only creates one underlying connection', async () => {
const connection =
process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING;
if (!connection) {
return;
}
const caches = TestCaches.create();
afterEach(jest.clearAllMocks);
it.each(caches.eachSupportedId())(
'only creates one underlying connection, %p',
async cacheId => {
const { store, connection } = await caches.init(cacheId);
const manager = CacheManager.fromConfig(
mockServices.rootConfig({
data: {
backend: { cache: { store: 'redis', connection } },
},
data: { backend: { cache: { store, connection } } },
}),
{ onError: e => expect(e).not.toBeDefined() },
);
manager.forPlugin('p1').getClient();
@@ -52,25 +57,27 @@ describe('CacheManager integration', () => {
manager.forPlugin('p2').getClient();
manager.forPlugin('p3').getClient({});
expect(KeyvRedis).toHaveBeenCalledTimes(1);
});
it('interacts correctly with redis', async () => {
// TODO(freben): This could be frameworkified as TestCaches just like
// TestDatabases, but that will have to come some other day
const connection =
process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING;
if (!connection) {
return;
if (store === 'redis') {
// eslint-disable-next-line jest/no-conditional-expect
expect(KeyvRedis).toHaveBeenCalledTimes(1);
} else if (store === 'memcache') {
// eslint-disable-next-line jest/no-conditional-expect
expect(KeyvMemcache).toHaveBeenCalledTimes(1);
}
},
);
it.each(caches.eachSupportedId())(
'interacts correctly with store, %p',
async cacheId => {
const { store, connection } = await caches.init(cacheId);
const manager = CacheManager.fromConfig(
mockServices.rootConfig({
data: {
backend: { cache: { store: 'redis', connection } },
backend: { cache: { store, connection } },
},
}),
{ onError: e => expect(e).not.toBeDefined() },
);
const plugin1 = manager.forPlugin('p1').getClient();
@@ -84,6 +91,6 @@ describe('CacheManager integration', () => {
await expect(plugin1.get('a')).resolves.toBe('plugin1');
await expect(plugin2a.get('a')).resolves.toBe('plugin2b');
await expect(plugin2b.get('a')).resolves.toBe('plugin2b');
});
});
},
);
});
+23
View File
@@ -26,6 +26,7 @@ import { HttpRouterFactoryOptions } from '@backstage/backend-app-api';
import { HttpRouterService } from '@backstage/backend-plugin-api';
import { IdentityService } from '@backstage/backend-plugin-api';
import { JsonObject } from '@backstage/types';
import Keyv from 'keyv';
import { Knex } from 'knex';
import { LifecycleService } from '@backstage/backend-plugin-api';
import { LoggerService } from '@backstage/backend-plugin-api';
@@ -425,6 +426,28 @@ export interface TestBackendOptions<TExtensionPoints extends any[]> {
>;
}
// @public
export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1';
// @public
export class TestCaches {
static create(options?: {
ids?: TestCacheId[];
disableDocker?: boolean;
}): TestCaches;
// (undocumented)
eachSupportedId(): [TestCacheId][];
init(id: TestCacheId): Promise<{
store: string;
connection: string;
keyv: Keyv;
}>;
// (undocumented)
static setDefaults(options: { ids?: TestCacheId[] }): void;
// (undocumented)
supports(id: TestCacheId): boolean;
}
// @public
export type TestDatabaseId =
| 'POSTGRES_16'
+4
View File
@@ -53,10 +53,14 @@
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"@backstage/types": "workspace:^",
"@keyv/memcache": "^1.3.5",
"@keyv/redis": "^2.5.3",
"@types/keyv": "^4.2.0",
"better-sqlite3": "^9.0.0",
"cookie": "^0.6.0",
"express": "^4.17.1",
"fs-extra": "^11.0.0",
"keyv": "^4.5.2",
"knex": "^3.0.0",
"msw": "^1.0.0",
"mysql2": "^3.0.0",
@@ -0,0 +1,53 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isDockerDisabledForTests } from '../util';
import { TestCaches } from './TestCaches';
const itIfDocker = isDockerDisabledForTests() ? it.skip : it;
jest.setTimeout(60_000);
describe('TestCaches', () => {
const caches = TestCaches.create();
it.each(caches.eachSupportedId())('fires up a cache, %p', async cacheId => {
const { keyv } = await caches.init(cacheId);
await keyv.set('test', 'value');
await expect(keyv.get('test')).resolves.toBe('value');
});
itIfDocker('clears between tests, part 1', async () => {
const { keyv } = await caches.init('REDIS_7');
// eslint-disable-next-line jest/no-standalone-expect
await expect(keyv.get('collision')).resolves.toBeUndefined();
await keyv.set('collision', 'something');
});
itIfDocker('clears between tests, part 2', async () => {
const { keyv } = await caches.init('REDIS_7');
// eslint-disable-next-line jest/no-standalone-expect
await expect(keyv.get('collision')).resolves.toBeUndefined();
await keyv.set('collision', 'something');
});
itIfDocker('clears between tests, part 3', async () => {
const { keyv } = await caches.init('REDIS_7');
// eslint-disable-next-line jest/no-standalone-expect
await expect(keyv.get('collision')).resolves.toBeUndefined();
await keyv.set('collision', 'something');
});
});
+210
View File
@@ -0,0 +1,210 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Keyv from 'keyv';
import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
import { connectToExternalMemcache, startMemcachedContainer } from './memcache';
import { connectToExternalRedis, startRedisContainer } from './redis';
import { Instance, TestCacheId, TestCacheProperties, allCaches } from './types';
/**
* Encapsulates the creation of ephemeral test cache instances for use inside
* unit or integration tests.
*
* @public
*/
export class TestCaches {
private readonly instanceById: Map<string, Instance>;
private readonly supportedIds: TestCacheId[];
private static defaultIds?: TestCacheId[];
/**
* Creates an empty `TestCaches` instance, and sets up Jest to clean up all of
* its acquired resources after all tests finish.
*
* You typically want to create just a single instance like this at the top of
* your test file or `describe` block, and then call `init` many times on that
* instance inside the individual tests. Spinning up a "physical" cache
* instance takes a considerable amount of time, slowing down tests. But
* wiping the contents of an instance using `init` is very fast.
*/
static create(options?: {
ids?: TestCacheId[];
disableDocker?: boolean;
}): TestCaches {
const ids = options?.ids;
const disableDocker = options?.disableDocker ?? isDockerDisabledForTests();
let testCacheIds: TestCacheId[];
if (ids) {
testCacheIds = ids;
} else if (TestCaches.defaultIds) {
testCacheIds = TestCaches.defaultIds;
} else {
testCacheIds = Object.keys(allCaches) as TestCacheId[];
}
const supportedIds = testCacheIds.filter(id => {
const properties = allCaches[id];
if (!properties) {
return false;
}
// If the caller has set up the env with an explicit connection string,
// we'll assume that this target will work
if (
properties.connectionStringEnvironmentVariableName &&
process.env[properties.connectionStringEnvironmentVariableName]
) {
return true;
}
// If the cache doesn't require docker at all, there's nothing to worry
// about
if (!properties.dockerImageName) {
return true;
}
// If the cache requires docker, but docker is disabled, we will fail.
if (disableDocker) {
return false;
}
return true;
});
const caches = new TestCaches(supportedIds);
if (supportedIds.length > 0) {
afterAll(async () => {
await caches.shutdown();
});
}
return caches;
}
static setDefaults(options: { ids?: TestCacheId[] }) {
TestCaches.defaultIds = options.ids;
}
private constructor(supportedIds: TestCacheId[]) {
this.instanceById = new Map();
this.supportedIds = supportedIds;
}
supports(id: TestCacheId): boolean {
return this.supportedIds.includes(id);
}
eachSupportedId(): [TestCacheId][] {
return this.supportedIds.map(id => [id]);
}
/**
* Returns a fresh, empty cache for the given driver.
*
* @param id - The ID of the cache to use, e.g. 'REDIS_7'
* @returns Cache connection properties
*/
async init(
id: TestCacheId,
): Promise<{ store: string; connection: string; keyv: Keyv }> {
const properties = allCaches[id];
if (!properties) {
const candidates = Object.keys(allCaches).join(', ');
throw new Error(
`Unknown test cache ${id}, possible values are ${candidates}`,
);
}
if (!this.supportedIds.includes(id)) {
const candidates = this.supportedIds.join(', ');
throw new Error(
`Unsupported test cache ${id} for this environment, possible values are ${candidates}`,
);
}
// Ensure that a testcontainers instance is up for this ID
let instance: Instance | undefined = this.instanceById.get(id);
if (!instance) {
instance = await this.initAny(properties);
this.instanceById.set(id, instance);
}
// Ensure that it's cleared of data from previous tests
await instance.keyv.clear();
return {
store: instance.store,
connection: instance.connection,
keyv: instance.keyv,
};
}
private async initAny(properties: TestCacheProperties): Promise<Instance> {
switch (properties.store) {
case 'memcache':
return this.initMemcached(properties);
case 'redis':
return this.initRedis(properties);
case 'memory':
return {
store: 'memory',
connection: 'memory',
keyv: new Keyv(),
stop: async () => {},
};
default:
throw new Error(`Unknown cache store '${properties.store}'`);
}
}
private async initMemcached(
properties: TestCacheProperties,
): Promise<Instance> {
// Use the connection string if provided
const envVarName = properties.connectionStringEnvironmentVariableName;
if (envVarName) {
const connectionString = process.env[envVarName];
if (connectionString) {
return connectToExternalMemcache(connectionString);
}
}
return await startMemcachedContainer(properties.dockerImageName!);
}
private async initRedis(properties: TestCacheProperties): Promise<Instance> {
// Use the connection string if provided
const envVarName = properties.connectionStringEnvironmentVariableName;
if (envVarName) {
const connectionString = process.env[envVarName];
if (connectionString) {
return connectToExternalRedis(connectionString);
}
}
return await startRedisContainer(properties.dockerImageName!);
}
private async shutdown() {
const instances = [...this.instanceById.values()];
this.instanceById.clear();
await Promise.all(
instances.map(({ stop }) =>
stop().catch(error => {
console.warn(`TestCaches: Failed to stop container`, { error });
}),
),
);
}
}
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { TestCaches } from './TestCaches';
export type { TestCacheId } from './types';
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
import { startMemcachedContainer } from './memcache';
import { v4 as uuid } from 'uuid';
const itIfDocker = isDockerDisabledForTests() ? it.skip : it;
jest.setTimeout(60_000);
describe('startMemcachedContainer', () => {
itIfDocker('successfully launches the container', async () => {
const { stop, keyv } = await startMemcachedContainer('memcached:1');
const value = uuid();
await keyv.set('test', value);
// eslint-disable-next-line jest/no-standalone-expect
await expect(keyv.get('test')).resolves.toBe(value);
await stop();
});
});
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Keyv from 'keyv';
import KeyvMemcache from '@keyv/memcache';
import { v4 as uuid } from 'uuid';
import { Instance } from './types';
async function attemptMemcachedConnection(connection: string): Promise<Keyv> {
const startTime = Date.now();
for (;;) {
try {
const store = new KeyvMemcache(connection);
const keyv = new Keyv({ store });
const value = uuid();
await keyv.set('test', value);
if ((await keyv.get('test')) === value) {
return keyv;
}
} catch (e) {
if (Date.now() - startTime > 30_000) {
throw new Error(
`Timed out waiting for memcached to be ready for connections, ${e}`,
);
}
}
await new Promise(resolve => setTimeout(resolve, 100));
}
}
export async function connectToExternalMemcache(
connection: string,
): Promise<Instance> {
const keyv = await attemptMemcachedConnection(connection);
return {
store: 'memcache',
connection,
keyv,
stop: async () => await keyv.disconnect(),
};
}
export async function startMemcachedContainer(
image: string,
): Promise<Instance> {
// Lazy-load to avoid side-effect of importing testcontainers
const { GenericContainer } = await import('testcontainers');
const container = await new GenericContainer(image)
.withExposedPorts(11211)
.start();
const host = container.getHost();
const port = container.getMappedPort(11211);
const connection = `${host}:${port}`;
const keyv = await attemptMemcachedConnection(connection);
return {
store: 'memcache',
connection,
keyv,
stop: async () => {
await keyv.disconnect();
await container.stop({ timeout: 10_000 });
},
};
}
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
import { startRedisContainer } from './redis';
import { v4 as uuid } from 'uuid';
const itIfDocker = isDockerDisabledForTests() ? it.skip : it;
jest.setTimeout(60_000);
describe('startRedisContainer', () => {
itIfDocker('successfully launches the container', async () => {
const { stop, keyv } = await startRedisContainer('redis:7');
const value = uuid();
await keyv.set('test', value);
// eslint-disable-next-line jest/no-standalone-expect
await expect(keyv.get('test')).resolves.toBe(value);
await stop();
});
});
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';
import { v4 as uuid } from 'uuid';
import { Instance } from './types';
async function attemptRedisConnection(connection: string): Promise<Keyv> {
const startTime = Date.now();
for (;;) {
try {
const store = new KeyvRedis(connection);
const keyv = new Keyv({ store });
const value = uuid();
await keyv.set('test', value);
if ((await keyv.get('test')) === value) {
return keyv;
}
} catch (e) {
if (Date.now() - startTime > 30_000) {
throw new Error(
`Timed out waiting for redis to be ready for connections, ${e}`,
);
}
}
await new Promise(resolve => setTimeout(resolve, 100));
}
}
export async function connectToExternalRedis(
connection: string,
): Promise<Instance> {
const keyv = await attemptRedisConnection(connection);
return {
store: 'redis',
connection,
keyv,
stop: async () => await keyv.disconnect(),
};
}
export async function startRedisContainer(image: string): Promise<Instance> {
// Lazy-load to avoid side-effect of importing testcontainers
const { GenericContainer } = await import('testcontainers');
const container = await new GenericContainer(image)
.withExposedPorts(6379)
.start();
const host = container.getHost();
const port = container.getMappedPort(6379);
const connection = `redis://${host}:${port}`;
const keyv = await attemptRedisConnection(connection);
return {
store: 'redis',
connection,
keyv,
stop: async () => {
await keyv.disconnect();
await container.stop({ timeout: 10_000 });
},
};
}
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Keyv from 'keyv';
import { getDockerImageForName } from '../util/getDockerImageForName';
/**
* The possible caches to test against.
*
* @public
*/
export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1';
export type TestCacheProperties = {
name: string;
store: string;
dockerImageName?: string;
connectionStringEnvironmentVariableName?: string;
};
export type Instance = {
store: string;
connection: string;
keyv: Keyv;
stop: () => Promise<void>;
};
export const allCaches: Record<TestCacheId, TestCacheProperties> =
Object.freeze({
REDIS_7: {
name: 'Redis 7.x',
store: 'redis',
dockerImageName: getDockerImageForName('redis:7'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING',
},
MEMCACHED_1: {
name: 'Memcached 1.x',
store: 'memcache',
dockerImageName: getDockerImageForName('memcached:1'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_CACHE_MEMCACHED1_CONNECTION_STRING',
},
MEMORY: {
name: 'In-memory',
store: 'memory',
},
});
@@ -14,6 +14,5 @@
* limitations under the License.
*/
export { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
export { TestDatabases } from './TestDatabases';
export type { TestDatabaseId } from './types';
+1
View File
@@ -20,6 +20,7 @@
* @packageDocumentation
*/
export * from './cache';
export * from './database';
export * from './msw';
export * from './filesystem';
@@ -20,7 +20,7 @@ export function isDockerDisabledForTests() {
// the (relatively heavy, long running) docker based tests. If you want to
// still run local tests for all databases, just pass either the CI=1 env
// parameter to your test runner, or individual connection strings per
// database.
// database or cache.
return (
Boolean(process.env.BACKSTAGE_TEST_DISABLE_DOCKER) ||
!Boolean(process.env.CI)
+23 -10
View File
@@ -3582,11 +3582,15 @@ __metadata:
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-events-node": "workspace:^"
"@backstage/types": "workspace:^"
"@keyv/memcache": ^1.3.5
"@keyv/redis": ^2.5.3
"@types/keyv": ^4.2.0
"@types/supertest": ^2.0.8
better-sqlite3: ^9.0.0
cookie: ^0.6.0
express: ^4.17.1
fs-extra: ^11.0.0
keyv: ^4.5.2
knex: ^3.0.0
msw: ^1.0.0
mysql2: ^3.0.0
@@ -17514,7 +17518,16 @@ __metadata:
languageName: node
linkType: hard
"@types/keyv@npm:*, @types/keyv@npm:^3.1.1":
"@types/keyv@npm:*, @types/keyv@npm:^4.2.0":
version: 4.2.0
resolution: "@types/keyv@npm:4.2.0"
dependencies:
keyv: "*"
checksum: 8713da9382b9346d664866a6cab2f91b0fd479f61379af891303a618e9a2abad6f347adc38a0850540e3f2dad278427de24e7555339264fddb04d1d17d3b50e0
languageName: node
linkType: hard
"@types/keyv@npm:^3.1.1":
version: 3.1.4
resolution: "@types/keyv@npm:3.1.4"
dependencies:
@@ -31027,6 +31040,15 @@ __metadata:
languageName: node
linkType: hard
"keyv@npm:*, keyv@npm:^4.0.0, keyv@npm:^4.5.2":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
dependencies:
json-buffer: 3.0.1
checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72
languageName: node
linkType: hard
"keyv@npm:^3.0.0":
version: 3.1.0
resolution: "keyv@npm:3.1.0"
@@ -31036,15 +31058,6 @@ __metadata:
languageName: node
linkType: hard
"keyv@npm:^4.0.0, keyv@npm:^4.5.2":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
dependencies:
json-buffer: 3.0.1
checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72
languageName: node
linkType: hard
"kind-of@npm:^6.0.2, kind-of@npm:^6.0.3":
version: 6.0.3
resolution: "kind-of@npm:6.0.3"