tests: use describe.each for database test iteration
Refactors all test files that use TestDatabases/TestCaches with it.each(databases.eachSupportedId()) to instead use describe.each at the outer level. This ensures that all tests for one database engine complete before moving to the next, rather than interleaving engines across individual tests. This reduces the number of concurrent database connections and should help with test timeout issues in CI. The TestDatabases.create() call is hoisted to module scope so the describe.each can iterate over supported IDs at the top level. 40 files changed across packages/backend-defaults, packages/backend-test-utils, and multiple plugins. Signed-off-by: Fredrik Adelöw <freben@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
+53
-56
@@ -24,13 +24,13 @@ import { ConfigReader } from '@backstage/config';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('createPluginKeySource', () => {
|
||||
const databases = TestDatabases.create();
|
||||
const mockDir = createMockDirectory();
|
||||
const databases = TestDatabases.create();
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'works for implicit database (no config), %p',
|
||||
async databaseId => {
|
||||
describe.each(databases.eachSupportedId())(
|
||||
'createPluginKeySource, %p',
|
||||
databaseId => {
|
||||
it('works for implicit database (no config)', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
|
||||
const getClient = jest.fn(async () => knex);
|
||||
@@ -61,12 +61,9 @@ describe('createPluginKeySource', () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'works for explicit database, %p',
|
||||
async databaseId => {
|
||||
it('works for explicit database', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
|
||||
const getClient = jest.fn(async () => knex);
|
||||
@@ -99,11 +96,12 @@ describe('createPluginKeySource', () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('works for static', async () => {
|
||||
const privateKey = `
|
||||
it('works for static', async () => {
|
||||
const privateKey = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR8Ja2ppMEgOm1KeY
|
||||
Kpje00U1luybndt6yC263vcgeKqhRANCAAS+slUrS9JXgtHB1RcDnmlveuu4H3Zm
|
||||
@@ -111,60 +109,59 @@ describe('createPluginKeySource', () => {
|
||||
-----END PRIVATE KEY-----
|
||||
`.trim();
|
||||
|
||||
const publicKey = `
|
||||
const publicKey = `
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvrJVK0vSV4LRwdUXA55pb3rruB92
|
||||
ZoUEY72HTvjIP9xSbLOhWhMREk84T0q91/m3v3sWq5EzHIWw1zRHQisKZw==
|
||||
-----END PUBLIC KEY-----
|
||||
`.trim();
|
||||
|
||||
mockDir.setContent({
|
||||
'public.pem': publicKey,
|
||||
'private.pem': privateKey,
|
||||
});
|
||||
const publicKeyPath = mockDir.resolve('public.pem');
|
||||
const privateKeyPath = mockDir.resolve('private.pem');
|
||||
mockDir.setContent({
|
||||
'public.pem': publicKey,
|
||||
'private.pem': privateKey,
|
||||
});
|
||||
const publicKeyPath = mockDir.resolve('public.pem');
|
||||
const privateKeyPath = mockDir.resolve('private.pem');
|
||||
|
||||
const getClient = jest.fn();
|
||||
const getClient = jest.fn();
|
||||
|
||||
const source = await createPluginKeySource({
|
||||
config: new ConfigReader({
|
||||
backend: {
|
||||
auth: {
|
||||
pluginKeyStore: {
|
||||
type: 'static',
|
||||
static: {
|
||||
keys: [
|
||||
{
|
||||
publicKeyFile: publicKeyPath,
|
||||
privateKeyFile: privateKeyPath,
|
||||
keyId: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
const source = await createPluginKeySource({
|
||||
config: new ConfigReader({
|
||||
backend: {
|
||||
auth: {
|
||||
pluginKeyStore: {
|
||||
type: 'static',
|
||||
static: {
|
||||
keys: [
|
||||
{
|
||||
publicKeyFile: publicKeyPath,
|
||||
privateKeyFile: privateKeyPath,
|
||||
keyId: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
database: mockServices.database.mock({ getClient }),
|
||||
logger: mockServices.logger.mock(),
|
||||
keyDuration: { seconds: 10 },
|
||||
});
|
||||
},
|
||||
}),
|
||||
database: mockServices.database.mock({ getClient }),
|
||||
logger: mockServices.logger.mock(),
|
||||
keyDuration: { seconds: 10 },
|
||||
});
|
||||
|
||||
expect(getClient).not.toHaveBeenCalled();
|
||||
expect(getClient).not.toHaveBeenCalled();
|
||||
|
||||
const keys = await source.listKeys();
|
||||
expect(keys.keys.length).toEqual(1);
|
||||
expect(keys.keys[0].key).toMatchObject({
|
||||
kid: '1',
|
||||
alg: 'ES256',
|
||||
});
|
||||
const keys = await source.listKeys();
|
||||
expect(keys.keys.length).toEqual(1);
|
||||
expect(keys.keys[0].key).toMatchObject({
|
||||
kid: '1',
|
||||
alg: 'ES256',
|
||||
});
|
||||
|
||||
const pk = await source.getPrivateSigningKey();
|
||||
expect(pk).toMatchObject({
|
||||
kid: '1',
|
||||
alg: 'ES256',
|
||||
d: expect.any(String),
|
||||
});
|
||||
const pk = await source.getPrivateSigningKey();
|
||||
expect(pk).toMatchObject({
|
||||
kid: '1',
|
||||
alg: 'ES256',
|
||||
d: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,14 +54,16 @@ jest.mock('@keyv/memcache', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('CacheManager integration', () => {
|
||||
const caches = TestCaches.create();
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
const caches = TestCaches.create();
|
||||
|
||||
it.each(caches.eachSupportedId())(
|
||||
'only creates one underlying connection per plugin, %p',
|
||||
async cacheId => {
|
||||
describe.each(caches.eachSupportedId())(
|
||||
'CacheManager integration, %p',
|
||||
cacheId => {
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
it('only creates one underlying connection per plugin', async () => {
|
||||
const { store, connection } = await caches.init(cacheId);
|
||||
|
||||
const manager = CacheManager.fromConfig(
|
||||
@@ -85,12 +87,9 @@ describe('CacheManager integration', () => {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(KeyvValkey).toHaveBeenCalledTimes(3);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(caches.eachSupportedId())(
|
||||
'interacts correctly with store, %p',
|
||||
async cacheId => {
|
||||
it('interacts correctly with store', async () => {
|
||||
const { store, connection } = await caches.init(cacheId);
|
||||
|
||||
const manager = CacheManager.fromConfig(
|
||||
@@ -114,12 +113,9 @@ 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');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(caches.eachSupportedId())(
|
||||
'supports both milliseconds and human durations throughout, %p',
|
||||
async cacheId => {
|
||||
it('supports both milliseconds and human durations throughout', async () => {
|
||||
const { store, connection } = await caches.init(cacheId);
|
||||
|
||||
for (const defaultTtl of [200, { milliseconds: 200 }]) {
|
||||
@@ -175,39 +171,39 @@ describe('CacheManager integration', () => {
|
||||
await expect(defaultClient.get('e')).resolves.toBeUndefined();
|
||||
await expect(defaultClient.get('f')).resolves.toBeUndefined();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('rejects invalid defaultTtl', () => {
|
||||
expect(() =>
|
||||
CacheManager.fromConfig(
|
||||
mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memory',
|
||||
},
|
||||
it('rejects invalid defaultTtl', () => {
|
||||
expect(() =>
|
||||
CacheManager.fromConfig(
|
||||
mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memory',
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
).not.toThrow();
|
||||
},
|
||||
}),
|
||||
),
|
||||
).not.toThrow();
|
||||
|
||||
expect(() =>
|
||||
CacheManager.fromConfig(
|
||||
mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memory',
|
||||
defaultTtl: 'hello',
|
||||
},
|
||||
expect(() =>
|
||||
CacheManager.fromConfig(
|
||||
mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memory',
|
||||
defaultTtl: 'hello',
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toThrow(/Invalid duration 'hello' in config/);
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toThrow(/Invalid duration 'hello' in config/);
|
||||
});
|
||||
|
||||
describe('CacheManager store options', () => {
|
||||
|
||||
@@ -41,68 +41,65 @@ async function migrateUntilBefore(knex: Knex, target: string): Promise<void> {
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('migrations', () => {
|
||||
const databases = TestDatabases.create();
|
||||
const databases = TestDatabases.create();
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'20250411000000_last_run.js, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
describe.each(databases.eachSupportedId())('migrations, %p', databaseId => {
|
||||
it('20250411000000_last_run.js', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
|
||||
await migrateUntilBefore(knex, '20250411000000_last_run.js');
|
||||
await migrateUntilBefore(knex, '20250411000000_last_run.js');
|
||||
|
||||
await knex
|
||||
.insert({
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
})
|
||||
.into('backstage_backend_tasks__tasks');
|
||||
await knex
|
||||
.insert({
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
})
|
||||
.into('backstage_backend_tasks__tasks');
|
||||
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
|
||||
await migrateUpOnce(knex);
|
||||
await migrateUpOnce(knex);
|
||||
|
||||
await knex
|
||||
.table('backstage_backend_tasks__tasks')
|
||||
.update({ last_run_error_json: 'error' })
|
||||
.where({ id: 'i' });
|
||||
await knex
|
||||
.table('backstage_backend_tasks__tasks')
|
||||
.update({ last_run_error_json: 'error' })
|
||||
.where({ id: 'i' });
|
||||
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
last_run_ended_at: null,
|
||||
last_run_error_json: 'error',
|
||||
},
|
||||
]);
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
last_run_ended_at: null,
|
||||
last_run_error_json: 'error',
|
||||
},
|
||||
]);
|
||||
|
||||
await migrateDownOnce(knex);
|
||||
await migrateDownOnce(knex);
|
||||
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'i',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
|
||||
await knex.destroy();
|
||||
},
|
||||
);
|
||||
await knex.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
+49
-54
@@ -24,9 +24,10 @@ import { metricsServiceMock } from '@backstage/backend-test-utils/alpha';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('TaskScheduler', () => {
|
||||
const databases = TestDatabases.create();
|
||||
|
||||
describe.each(databases.eachSupportedId())('TaskScheduler, %p', databaseId => {
|
||||
const logger = mockServices.logger.mock();
|
||||
const databases = TestDatabases.create();
|
||||
const rootLifecycle = mockServices.rootLifecycle.mock();
|
||||
const httpRouter = mockServices.httpRouter.mock();
|
||||
const pluginMetadata = {
|
||||
@@ -35,63 +36,57 @@ describe('TaskScheduler', () => {
|
||||
const testScopedSignal = createTestScopedSignal();
|
||||
const metrics = metricsServiceMock.mock();
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can return a working v1 plugin impl, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
const database = mockServices.database({ knex });
|
||||
it('can return a working v1 plugin impl', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
const database = mockServices.database({ knex });
|
||||
|
||||
const manager = DefaultSchedulerService.create({
|
||||
database,
|
||||
logger,
|
||||
metrics,
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
});
|
||||
const fn = jest.fn();
|
||||
const manager = DefaultSchedulerService.create({
|
||||
database,
|
||||
logger,
|
||||
metrics,
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
});
|
||||
const fn = jest.fn();
|
||||
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromMillis(5000),
|
||||
signal: testScopedSignal(),
|
||||
fn,
|
||||
});
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromMillis(5000),
|
||||
signal: testScopedSignal(),
|
||||
fn,
|
||||
});
|
||||
|
||||
await waitForExpect(() => {
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
);
|
||||
await waitForExpect(() => {
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can return a working v2 plugin impl, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
const database = mockServices.database({ knex });
|
||||
it('can return a working v2 plugin impl', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
const database = mockServices.database({ knex });
|
||||
|
||||
const manager = DefaultSchedulerService.create({
|
||||
database,
|
||||
logger,
|
||||
metrics,
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
});
|
||||
const fn = jest.fn();
|
||||
const manager = DefaultSchedulerService.create({
|
||||
database,
|
||||
logger,
|
||||
metrics,
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
});
|
||||
const fn = jest.fn();
|
||||
|
||||
await manager.scheduleTask({
|
||||
id: 'task2',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: { cron: '* * * * * *' },
|
||||
signal: testScopedSignal(),
|
||||
fn,
|
||||
});
|
||||
await manager.scheduleTask({
|
||||
id: 'task2',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: { cron: '* * * * * *' },
|
||||
signal: testScopedSignal(),
|
||||
fn,
|
||||
});
|
||||
|
||||
await waitForExpect(() => {
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
);
|
||||
await waitForExpect(() => {
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+220
-246
@@ -31,48 +31,50 @@ import { metricsServiceMock } from '@backstage/backend-test-utils/alpha';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('PluginTaskManagerImpl', () => {
|
||||
const addShutdownHook = jest.fn();
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_18', 'POSTGRES_14', 'SQLITE_3'],
|
||||
});
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_18', 'POSTGRES_14', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// Make sure all databases are running before mocking timers, in case of testcontainers
|
||||
await Promise.all(
|
||||
databases.eachSupportedId().map(([id]) => databases.init(id)),
|
||||
);
|
||||
describe.each(databases.eachSupportedId())(
|
||||
'PluginTaskManagerImpl, %p',
|
||||
databaseId => {
|
||||
const addShutdownHook = jest.fn();
|
||||
|
||||
jest.useFakeTimers();
|
||||
}, 60_000);
|
||||
beforeAll(async () => {
|
||||
// Make sure the database is running before mocking timers, in case of testcontainers
|
||||
await databases.init(databaseId);
|
||||
jest.useFakeTimers();
|
||||
}, 60_000);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
async function init(databaseId: TestDatabaseId) {
|
||||
const knex = await databases.init(databaseId);
|
||||
await migrateBackendTasks(knex);
|
||||
const manager = new PluginTaskSchedulerImpl(
|
||||
'myplugin',
|
||||
async () => knex,
|
||||
mockServices.logger.mock(),
|
||||
metricsServiceMock.mock(),
|
||||
{
|
||||
addShutdownHook,
|
||||
addBeforeShutdownHook: jest.fn(),
|
||||
addStartupHook: jest.fn(),
|
||||
},
|
||||
);
|
||||
return { knex, manager };
|
||||
}
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// This is just to test the wrapper code; most of the actual tests are in
|
||||
// TaskWorker.test.ts
|
||||
describe('scheduleTask with global scope', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can run the v1 happy path, %p',
|
||||
async databaseId => {
|
||||
async function init(id: TestDatabaseId) {
|
||||
const knex = await databases.init(id);
|
||||
await migrateBackendTasks(knex);
|
||||
const manager = new PluginTaskSchedulerImpl(
|
||||
'myplugin',
|
||||
async () => knex,
|
||||
mockServices.logger.mock(),
|
||||
metricsServiceMock.mock(),
|
||||
{
|
||||
addShutdownHook,
|
||||
addBeforeShutdownHook: jest.fn(),
|
||||
addStartupHook: jest.fn(),
|
||||
},
|
||||
);
|
||||
return { knex, manager };
|
||||
}
|
||||
|
||||
// This is just to test the wrapper code; most of the actual tests are in
|
||||
// TaskWorker.test.ts
|
||||
describe('scheduleTask with global scope', () => {
|
||||
it('can run the v1 happy path', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const fn = jest.fn();
|
||||
@@ -87,12 +89,9 @@ describe('PluginTaskManagerImpl', () => {
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can run the v2 happy path, %p',
|
||||
async databaseId => {
|
||||
it('can run the v2 happy path', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const fn = jest.fn();
|
||||
@@ -107,12 +106,9 @@ describe('PluginTaskManagerImpl', () => {
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'aborts the task if shutdown hook is invoked, %p',
|
||||
async databaseId => {
|
||||
it('aborts the task if shutdown hook is invoked', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const fn = jest.fn();
|
||||
@@ -134,14 +130,11 @@ describe('PluginTaskManagerImpl', () => {
|
||||
// Should be aborted after the shutdown hook is invoked
|
||||
await shutdownHook();
|
||||
expect(abortSignal.aborted).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('triggerTask with global scope', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can manually trigger a task, %p',
|
||||
async databaseId => {
|
||||
describe('triggerTask with global scope', () => {
|
||||
it('can manually trigger a task', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const fn = jest.fn();
|
||||
@@ -160,12 +153,9 @@ describe('PluginTaskManagerImpl', () => {
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'cant trigger a non-existent task, %p',
|
||||
async databaseId => {
|
||||
it('cant trigger a non-existent task', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const fn = jest.fn();
|
||||
@@ -180,12 +170,9 @@ describe('PluginTaskManagerImpl', () => {
|
||||
await expect(() => manager.triggerTask('task2')).rejects.toThrow(
|
||||
NotFoundError,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'cant trigger a running task, %p',
|
||||
async databaseId => {
|
||||
it('cant trigger a running task', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const promise = createDeferred();
|
||||
@@ -205,140 +192,137 @@ describe('PluginTaskManagerImpl', () => {
|
||||
await expect(() => manager.triggerTask('task1')).rejects.toThrow(
|
||||
ConflictError,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// This is just to test the wrapper code; most of the actual tests are in
|
||||
// TaskWorker.test.ts
|
||||
describe('scheduleTask with local scope', () => {
|
||||
it('can run the v1 happy path', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise(resolve => fn.mockImplementation(resolve));
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: { milliseconds: 5000 },
|
||||
frequency: { milliseconds: 5000 },
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
});
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
}, 60_000);
|
||||
// This is just to test the wrapper code; most of the actual tests are in
|
||||
// TaskWorker.test.ts
|
||||
describe('scheduleTask with local scope', () => {
|
||||
it('can run the v1 happy path', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
it('can run the v2 happy path', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise(resolve => fn.mockImplementation(resolve));
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: { milliseconds: 5000 },
|
||||
frequency: { milliseconds: 5000 },
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise(resolve => fn.mockImplementation(resolve));
|
||||
await manager.scheduleTask({
|
||||
id: 'task2',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: { cron: '* * * * * *' },
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
}, 60_000);
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
}, 60_000);
|
||||
it('can run the v2 happy path', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
it('aborts the task if shutdown hook is invoked', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise(resolve => fn.mockImplementation(resolve));
|
||||
await manager.scheduleTask({
|
||||
id: 'task2',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: { cron: '* * * * * *' },
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise<AbortSignal>(resolve =>
|
||||
fn.mockImplementation(resolve),
|
||||
);
|
||||
await manager.scheduleTask({
|
||||
id: 'task3',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: { cron: '* * * * * *' },
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
}, 60_000);
|
||||
|
||||
const shutdownHook = addShutdownHook.mock.calls[0][0];
|
||||
const abortSignal = await promise;
|
||||
expect(abortSignal.aborted).toBe(false);
|
||||
it('aborts the task if shutdown hook is invoked', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
// Should be aborted after the shutdown hook is invoked
|
||||
await shutdownHook();
|
||||
expect(abortSignal.aborted).toBe(true);
|
||||
}, 60_000);
|
||||
});
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise<AbortSignal>(resolve =>
|
||||
fn.mockImplementation(resolve),
|
||||
);
|
||||
await manager.scheduleTask({
|
||||
id: 'task3',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: { cron: '* * * * * *' },
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
describe('triggerTask with local scope', () => {
|
||||
it('can manually trigger a task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
const shutdownHook = addShutdownHook.mock.calls[0][0];
|
||||
const abortSignal = await promise;
|
||||
expect(abortSignal.aborted).toBe(false);
|
||||
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise(resolve => fn.mockImplementation(resolve));
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
initialDelay: Duration.fromObject({ years: 1 }),
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
// Should be aborted after the shutdown hook is invoked
|
||||
await shutdownHook();
|
||||
expect(abortSignal.aborted).toBe(true);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
await manager.triggerTask('task1');
|
||||
jest.advanceTimersByTime(5000);
|
||||
describe('triggerTask with local scope', () => {
|
||||
it('can manually trigger a task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
}, 60_000);
|
||||
const fn = jest.fn();
|
||||
const promise = new Promise(resolve => fn.mockImplementation(resolve));
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
initialDelay: Duration.fromObject({ years: 1 }),
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
it('cant trigger a non-existent task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
await manager.triggerTask('task1');
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
const fn = jest.fn();
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
}, 60_000);
|
||||
|
||||
await expect(() => manager.triggerTask('task2')).rejects.toThrow(
|
||||
NotFoundError,
|
||||
);
|
||||
}, 60_000);
|
||||
it('cant trigger a non-existent task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
it('cant trigger a running task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
const fn = jest.fn();
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
const promise = createDeferred();
|
||||
await expect(() => manager.triggerTask('task2')).rejects.toThrow(
|
||||
NotFoundError,
|
||||
);
|
||||
}, 60_000);
|
||||
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
fn: async () => {
|
||||
promise.resolve();
|
||||
await new Promise(r => setTimeout(r, 20000));
|
||||
},
|
||||
scope: 'local',
|
||||
});
|
||||
it('cant trigger a running task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
await promise;
|
||||
await expect(() => manager.triggerTask('task1')).rejects.toThrow(
|
||||
ConflictError,
|
||||
);
|
||||
}, 60_000);
|
||||
});
|
||||
const promise = createDeferred();
|
||||
|
||||
// This is just to test the wrapper code; most of the actual tests are in
|
||||
// TaskWorker.test.ts
|
||||
describe('createScheduledTaskRunner', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can run the happy path, %p',
|
||||
async databaseId => {
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
fn: async () => {
|
||||
promise.resolve();
|
||||
await new Promise(r => setTimeout(r, 20000));
|
||||
},
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
await promise;
|
||||
await expect(() => manager.triggerTask('task1')).rejects.toThrow(
|
||||
ConflictError,
|
||||
);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
// This is just to test the wrapper code; most of the actual tests are in
|
||||
// TaskWorker.test.ts
|
||||
describe('createScheduledTaskRunner', () => {
|
||||
it('can run the happy path', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const fn = jest.fn();
|
||||
@@ -356,14 +340,11 @@ describe('PluginTaskManagerImpl', () => {
|
||||
|
||||
await promise;
|
||||
expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal));
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('can fetch task ids', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can fetch both global and local task ids, %p',
|
||||
async databaseId => {
|
||||
describe('can fetch task ids', () => {
|
||||
it('can fetch both global and local task ids', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
const fn = jest.fn();
|
||||
|
||||
@@ -395,52 +376,51 @@ describe('PluginTaskManagerImpl', () => {
|
||||
settings: expect.objectContaining({ cadence: 'PT5S' }),
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('cancelTask with local scope', () => {
|
||||
it('can cancel a running task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
const promise = createDeferred();
|
||||
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
fn: async () => {
|
||||
promise.resolve();
|
||||
await new Promise(r => setTimeout(r, 20000));
|
||||
},
|
||||
scope: 'local',
|
||||
});
|
||||
});
|
||||
|
||||
await promise;
|
||||
await expect(manager.cancelTask('task1')).resolves.toBeUndefined();
|
||||
}, 60_000);
|
||||
describe('cancelTask with local scope', () => {
|
||||
it('can cancel a running task', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
it('cannot cancel a task that is not running', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
const promise = createDeferred();
|
||||
|
||||
const fn = jest.fn();
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
initialDelay: Duration.fromObject({ years: 1 }),
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
fn: async () => {
|
||||
promise.resolve();
|
||||
await new Promise(r => setTimeout(r, 20000));
|
||||
},
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
await expect(manager.cancelTask('task1')).rejects.toThrow(ConflictError);
|
||||
}, 60_000);
|
||||
});
|
||||
await promise;
|
||||
await expect(manager.cancelTask('task1')).resolves.toBeUndefined();
|
||||
}, 60_000);
|
||||
|
||||
describe('cancelTask with global scope', () => {
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can cancel a running task, %p',
|
||||
async databaseId => {
|
||||
it('cannot cancel a task that is not running', async () => {
|
||||
const { manager } = await init('SQLITE_3');
|
||||
|
||||
const fn = jest.fn();
|
||||
await manager.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: Duration.fromMillis(5000),
|
||||
frequency: Duration.fromObject({ years: 1 }),
|
||||
initialDelay: Duration.fromObject({ years: 1 }),
|
||||
fn,
|
||||
scope: 'local',
|
||||
});
|
||||
|
||||
await expect(manager.cancelTask('task1')).rejects.toThrow(
|
||||
ConflictError,
|
||||
);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
describe('cancelTask with global scope', () => {
|
||||
it('can cancel a running task', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
const promise = createDeferred();
|
||||
@@ -458,23 +438,17 @@ describe('PluginTaskManagerImpl', () => {
|
||||
|
||||
await promise;
|
||||
await expect(manager.cancelTask('task1')).resolves.toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'cannot cancel a non-existent task, %p',
|
||||
async databaseId => {
|
||||
it('cannot cancel a non-existent task', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
await expect(manager.cancelTask('nonexistent')).rejects.toThrow(
|
||||
NotFoundError,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'cannot cancel a task that is not running, %p',
|
||||
async databaseId => {
|
||||
it('cannot cancel a task that is not running', async () => {
|
||||
const { manager } = await init(databaseId);
|
||||
|
||||
await manager.scheduleTask({
|
||||
@@ -489,16 +463,16 @@ describe('PluginTaskManagerImpl', () => {
|
||||
await expect(manager.cancelTask('task1')).rejects.toThrow(
|
||||
ConflictError,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('parseDuration', () => {
|
||||
it('should parse durations', () => {
|
||||
expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S');
|
||||
expect(parseDuration(Duration.fromMillis(5000))).toEqual('PT5S');
|
||||
expect(parseDuration({ cron: '1 * * * *' })).toEqual('1 * * * *');
|
||||
expect(parseDuration({ trigger: 'manual' })).toEqual('manual');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseDuration', () => {
|
||||
it('should parse durations', () => {
|
||||
expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S');
|
||||
expect(parseDuration(Duration.fromMillis(5000))).toEqual('PT5S');
|
||||
expect(parseDuration({ cron: '1 * * * *' })).toEqual('1 * * * *');
|
||||
expect(parseDuration({ trigger: 'manual' })).toEqual('manual');
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
+23
-22
@@ -34,28 +34,29 @@ const getTask = async (knex: Knex): Promise<DbTasksRow> => {
|
||||
return (await knex<DbTasksRow>(DB_TASKS_TABLE))[0];
|
||||
};
|
||||
|
||||
describe('PluginTaskSchedulerJanitor', () => {
|
||||
const logger = mockServices.logger.mock();
|
||||
const databases = TestDatabases.create({
|
||||
ids: [
|
||||
/* 'MYSQL_8' not supported yet */
|
||||
'POSTGRES_18',
|
||||
'POSTGRES_14',
|
||||
'SQLITE_3',
|
||||
'MYSQL_8',
|
||||
],
|
||||
});
|
||||
const testScopedSignal = createTestScopedSignal();
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
const databases = TestDatabases.create({
|
||||
ids: [
|
||||
/* 'MYSQL_8' not supported yet */
|
||||
'POSTGRES_18',
|
||||
'POSTGRES_14',
|
||||
'SQLITE_3',
|
||||
'MYSQL_8',
|
||||
],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
describe.each(databases.eachSupportedId())(
|
||||
'PluginTaskSchedulerJanitor, %p',
|
||||
databaseId => {
|
||||
const logger = mockServices.logger.mock();
|
||||
const testScopedSignal = createTestScopedSignal();
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'Should update date if current_run_expires_at expires, %p',
|
||||
async databaseId => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('Should update date if current_run_expires_at expires', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
await migrateBackendTasks(knex);
|
||||
|
||||
@@ -92,6 +93,6 @@ describe('PluginTaskSchedulerJanitor', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -41,83 +41,77 @@ async function migrateUntilBefore(knex: Knex, target: string): Promise<void> {
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('migrations', () => {
|
||||
const databases = TestDatabases.create();
|
||||
const databases = TestDatabases.create();
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'20210928160613_init.js, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
describe.each(databases.eachSupportedId())('migrations, %p', databaseId => {
|
||||
it('20210928160613_init.js', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
|
||||
await migrateUntilBefore(knex, '20210928160613_init.js');
|
||||
await migrateUpOnce(knex);
|
||||
await migrateUntilBefore(knex, '20210928160613_init.js');
|
||||
await migrateUpOnce(knex);
|
||||
|
||||
await knex('backstage_backend_tasks__tasks').insert({
|
||||
await knex('backstage_backend_tasks__tasks').insert({
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: knex.fn.now(),
|
||||
});
|
||||
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: knex.fn.now(),
|
||||
});
|
||||
next_run_start_at: expect.anything(),
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: expect.anything(),
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
await migrateDownOnce(knex);
|
||||
|
||||
await migrateDownOnce(knex);
|
||||
// This looks odd - you might expect a .toThrow at the end but that
|
||||
// actually is flaky for some reason specifically on sqlite when
|
||||
// performing multiple runs in sequence
|
||||
await expect(knex('backstage_backend_tasks__tasks')).rejects.toEqual(
|
||||
expect.anything(),
|
||||
);
|
||||
|
||||
// This looks odd - you might expect a .toThrow at the end but that
|
||||
// actually is flaky for some reason specifically on sqlite when
|
||||
// performing multiple runs in sequence
|
||||
await expect(knex('backstage_backend_tasks__tasks')).rejects.toEqual(
|
||||
expect.anything(),
|
||||
);
|
||||
await knex.destroy();
|
||||
});
|
||||
|
||||
await knex.destroy();
|
||||
},
|
||||
);
|
||||
it('20240712211735_nullable_next_run.js', async () => {
|
||||
const knex = await databases.init(databaseId);
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'20240712211735_nullable_next_run.js, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
await migrateUntilBefore(knex, '20240712211735_nullable_next_run.js');
|
||||
await migrateUpOnce(knex);
|
||||
|
||||
await migrateUntilBefore(knex, '20240712211735_nullable_next_run.js');
|
||||
await migrateUpOnce(knex);
|
||||
await knex('backstage_backend_tasks__tasks').insert({
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: knex.raw('null'),
|
||||
});
|
||||
|
||||
await knex('backstage_backend_tasks__tasks').insert({
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
|
||||
await migrateDownOnce(knex);
|
||||
|
||||
await expect(
|
||||
knex('backstage_backend_tasks__tasks').insert({
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: knex.raw('null'),
|
||||
});
|
||||
}),
|
||||
).rejects.toEqual(expect.anything());
|
||||
|
||||
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
|
||||
{
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: null,
|
||||
current_run_ticket: null,
|
||||
current_run_started_at: null,
|
||||
current_run_expires_at: null,
|
||||
},
|
||||
]);
|
||||
|
||||
await migrateDownOnce(knex);
|
||||
|
||||
await expect(
|
||||
knex('backstage_backend_tasks__tasks').insert({
|
||||
id: 'test',
|
||||
settings_json: '{}',
|
||||
next_run_start_at: knex.raw('null'),
|
||||
}),
|
||||
).rejects.toEqual(expect.anything());
|
||||
|
||||
await knex.destroy();
|
||||
},
|
||||
);
|
||||
await knex.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,10 +21,10 @@ const itIfDocker = isDockerDisabledForTests() ? it.skip : it;
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('TestCaches', () => {
|
||||
const caches = TestCaches.create();
|
||||
const caches = TestCaches.create();
|
||||
|
||||
it.each(caches.eachSupportedId())('fires up a cache, %p', async cacheId => {
|
||||
describe.each(caches.eachSupportedId())('TestCaches, %p', cacheId => {
|
||||
it('fires up a cache', async () => {
|
||||
const { keyv } = await caches.init(cacheId);
|
||||
await keyv.set('test', 'value');
|
||||
await expect(keyv.get('test')).resolves.toBe('value');
|
||||
|
||||
@@ -18,24 +18,14 @@ import { TestDatabases } from './TestDatabases';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('TestDatabases', () => {
|
||||
describe('each create', () => {
|
||||
const dbs = TestDatabases.create();
|
||||
const dbs = TestDatabases.create();
|
||||
|
||||
it.each(dbs.eachSupportedId())(
|
||||
'creates distinct %p databases',
|
||||
async databaseId => {
|
||||
if (!dbs.supports(databaseId)) {
|
||||
return;
|
||||
}
|
||||
const db1 = await dbs.init(databaseId);
|
||||
const db2 = await dbs.init(databaseId);
|
||||
await db1.schema.createTable('a', table => table.string('x').primary());
|
||||
await db2.schema.createTable('a', table => table.string('y').primary());
|
||||
await expect(db1.select({ a: db1.raw('1') })).resolves.toEqual([
|
||||
{ a: 1 },
|
||||
]);
|
||||
},
|
||||
);
|
||||
describe.each(dbs.eachSupportedId())('TestDatabases, %p', databaseId => {
|
||||
it('creates distinct databases', async () => {
|
||||
const db1 = await dbs.init(databaseId);
|
||||
const db2 = await dbs.init(databaseId);
|
||||
await db1.schema.createTable('a', table => table.string('x').primary());
|
||||
await db2.schema.createTable('a', table => table.string('y').primary());
|
||||
await expect(db1.select({ a: db1.raw('1') })).resolves.toEqual([{ a: 1 }]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user