Merge pull request #34267 from backstage/freben/describe-each-databases

tests: use describe.each for database test iteration
This commit is contained in:
Fredrik Adelöw
2026-05-19 10:24:00 +02:00
committed by GitHub
40 changed files with 4406 additions and 4992 deletions
@@ -27,12 +27,12 @@ const keyBase = {
jest.setTimeout(60_000);
describe('DatabaseKeyStore', () => {
const databases = TestDatabases.create();
const databases = TestDatabases.create();
it.each(databases.eachSupportedId())(
'should store a key, %p',
async databaseId => {
describe.each(databases.eachSupportedId())(
'DatabaseKeyStore, %p',
databaseId => {
it('should store a key', async () => {
const knex = await databases.init(databaseId);
await AuthDatabase.runMigrations(knex);
@@ -53,12 +53,9 @@ describe('DatabaseKeyStore', () => {
DateTime.fromJSDate(items[0].createdAt).diffNow('seconds').seconds,
),
).toBeLessThan(10);
},
);
});
it.each(databases.eachSupportedId())(
'should remove stored keys, %p',
async databaseId => {
it('should remove stored keys', async () => {
const knex = await databases.init(databaseId);
await AuthDatabase.runMigrations(knex);
@@ -124,6 +121,6 @@ describe('DatabaseKeyStore', () => {
await expect(store.listKeys()).resolves.toEqual({
items: [],
});
},
);
});
});
},
);
@@ -24,89 +24,80 @@ import { mockServices, TestDatabases } from '@backstage/backend-test-utils';
jest.setTimeout(60_000);
describe('KeyStores', () => {
const databases = TestDatabases.create();
const databases = TestDatabases.create();
const defaultConfigOptions = {
auth: {
keyStore: {
provider: 'memory',
},
const defaultConfigOptions = {
auth: {
keyStore: {
provider: 'memory',
},
};
const defaultConfig = new ConfigReader(defaultConfigOptions);
},
};
const defaultConfig = new ConfigReader(defaultConfigOptions);
it.each(databases.eachSupportedId())(
'reads auth section from config, %p',
async databaseId => {
const knex = await databases.init(databaseId);
const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig');
const keyStore = await KeyStores.fromConfig(defaultConfig, {
logger: mockServices.logger.mock(),
database: AuthDatabase.create(mockServices.database({ knex })),
});
describe.each(databases.eachSupportedId())('KeyStores, %p', databaseId => {
it('reads auth section from config', async () => {
const knex = await databases.init(databaseId);
const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig');
const keyStore = await KeyStores.fromConfig(defaultConfig, {
logger: mockServices.logger.mock(),
database: AuthDatabase.create(mockServices.database({ knex })),
});
expect(keyStore).toBeInstanceOf(MemoryKeyStore);
expect(configSpy).toHaveBeenCalledWith('auth.keyStore');
expect(
defaultConfig
.getOptionalConfig('auth.keyStore')
?.getOptionalString('provider'),
).toBe(defaultConfigOptions.auth.keyStore.provider);
},
);
expect(keyStore).toBeInstanceOf(MemoryKeyStore);
expect(configSpy).toHaveBeenCalledWith('auth.keyStore');
expect(
defaultConfig
.getOptionalConfig('auth.keyStore')
?.getOptionalString('provider'),
).toBe(defaultConfigOptions.auth.keyStore.provider);
});
it.each(databases.eachSupportedId())(
'can handle without auth config, %p',
async databaseId => {
const knex = await databases.init(databaseId);
const keyStore = await KeyStores.fromConfig(new ConfigReader({}), {
logger: mockServices.logger.mock(),
database: AuthDatabase.create(mockServices.database({ knex })),
});
expect(keyStore).toBeInstanceOf(DatabaseKeyStore);
},
);
it('can handle without auth config', async () => {
const knex = await databases.init(databaseId);
const keyStore = await KeyStores.fromConfig(new ConfigReader({}), {
logger: mockServices.logger.mock(),
database: AuthDatabase.create(mockServices.database({ knex })),
});
expect(keyStore).toBeInstanceOf(DatabaseKeyStore);
});
it.each(databases.eachSupportedId())(
'can handle additional provider config, %p',
async databaseId => {
const knex = await databases.init(databaseId);
jest.spyOn(FirestoreKeyStore, 'verifyConnection').mockResolvedValue();
const createSpy = jest.spyOn(FirestoreKeyStore, 'create');
it('can handle additional provider config', async () => {
const knex = await databases.init(databaseId);
jest.spyOn(FirestoreKeyStore, 'verifyConnection').mockResolvedValue();
const createSpy = jest.spyOn(FirestoreKeyStore, 'create');
const configOptions = {
auth: {
keyStore: {
provider: 'firestore',
firestore: {
projectId: 'my-project',
keyFilename: 'cred.json',
path: 'my-path',
timeout: 100,
host: 'localhost',
port: 8088,
ssl: false,
},
const configOptions = {
auth: {
keyStore: {
provider: 'firestore',
firestore: {
projectId: 'my-project',
keyFilename: 'cred.json',
path: 'my-path',
timeout: 100,
host: 'localhost',
port: 8088,
ssl: false,
},
},
};
const config = new ConfigReader(configOptions);
const keyStore = await KeyStores.fromConfig(config, {
logger: mockServices.logger.mock(),
database: AuthDatabase.create(mockServices.database({ knex })),
});
},
};
const config = new ConfigReader(configOptions);
const keyStore = await KeyStores.fromConfig(config, {
logger: mockServices.logger.mock(),
database: AuthDatabase.create(mockServices.database({ knex })),
});
expect(keyStore).toBeInstanceOf(FirestoreKeyStore);
expect(createSpy).toHaveBeenCalledWith(
configOptions.auth.keyStore.firestore,
);
expect(
config
.getOptionalConfig('auth.keyStore')
?.getOptionalConfig('firestore')
?.getOptionalString('projectId'),
).toBe(configOptions.auth.keyStore.firestore.projectId);
},
);
expect(keyStore).toBeInstanceOf(FirestoreKeyStore);
expect(createSpy).toHaveBeenCalledWith(
configOptions.auth.keyStore.firestore,
);
expect(
config
.getOptionalConfig('auth.keyStore')
?.getOptionalConfig('firestore')
?.getOptionalString('projectId'),
).toBe(configOptions.auth.keyStore.firestore.projectId);
});
});
+330 -350
View File
@@ -41,179 +41,154 @@ 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())(
'20230428155633_sessions.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
describe.each(databases.eachSupportedId())('migrations, %p', databaseId => {
it('20230428155633_sessions.js', async () => {
const knex = await databases.init(databaseId);
await migrateUntilBefore(knex, '20230428155633_sessions.js');
await migrateUntilBefore(knex, '20230428155633_sessions.js');
await migrateUpOnce(knex);
// Ensure that large cookies are supported
const data = `{"cookie":"${'a'.repeat(100_000)}"}`;
await knex
.insert({ sid: 'abc', expired: knex.fn.now(), sess: data })
.into('sessions');
await knex
.insert({ sid: 'def', expired: knex.fn.now(), sess: data })
.into('sessions');
await expect(knex('sessions').orderBy('sid', 'asc')).resolves.toEqual([
{ sid: 'abc', expired: expect.anything(), sess: data },
{ sid: 'def', expired: expect.anything(), sess: data },
]);
await migrateDownOnce(knex);
await knex.destroy();
});
it('20240510120825_user_info.js', async () => {
const knex = await databases.init(databaseId);
await migrateUntilBefore(knex, '20240510120825_user_info.js');
await migrateUpOnce(knex);
const user_info = JSON.stringify({
claims: {
ent: ['group:default/group1', 'group:default/group2'],
},
});
await knex
.insert({
user_entity_ref: 'user:default/backstage-user',
user_info,
exp: knex.fn.now(),
})
.into('user_info');
await expect(knex('user_info')).resolves.toEqual([
{
user_entity_ref: 'user:default/backstage-user',
user_info,
exp: expect.anything(),
},
]);
await migrateDownOnce(knex);
await knex.destroy();
});
it('20250707164600_user_created_at.js', async () => {
const knex = await databases.init(databaseId);
await migrateUntilBefore(knex, '20250707164600_user_created_at.js');
if (knex.client.config.client.includes('sqlite')) {
// Sqlite doesn't support adding a column with non-constant default when table has data
// so we just test that the migration runs without errors
await migrateUpOnce(knex);
// Ensure that large cookies are supported
const data = `{"cookie":"${'a'.repeat(100_000)}"}`;
await knex
.insert({ sid: 'abc', expired: knex.fn.now(), sess: data })
.into('sessions');
await knex
.insert({ sid: 'def', expired: knex.fn.now(), sess: data })
.into('sessions');
return;
}
await expect(knex('sessions').orderBy('sid', 'asc')).resolves.toEqual([
{ sid: 'abc', expired: expect.anything(), sess: data },
{ sid: 'def', expired: expect.anything(), sess: data },
]);
const user_info = JSON.stringify({
claims: {
ent: ['group:default/group1', 'group:default/group2'],
},
});
await migrateDownOnce(knex);
await knex
.insert({
user_entity_ref: 'user:default/backstage-user',
user_info,
exp: knex.fn.now(),
})
.into('user_info');
await knex.destroy();
},
);
const { exp } = await knex('user_info').first();
it.each(databases.eachSupportedId())(
'20240510120825_user_info.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
await migrateUpOnce(knex);
await migrateUntilBefore(knex, '20240510120825_user_info.js');
await migrateUpOnce(knex);
const { created_at, updated_at } = await knex('user_info').first();
const user_info = JSON.stringify({
claims: {
ent: ['group:default/group1', 'group:default/group2'],
},
});
expect(updated_at).toEqual(exp);
expect(created_at).toBeDefined();
await knex
.insert({
user_entity_ref: 'user:default/backstage-user',
user_info,
exp: knex.fn.now(),
})
.into('user_info');
await knex
.insert({
user_entity_ref: 'user:default/backstage-user',
user_info,
updated_at: knex.fn.now(),
})
.into('user_info')
.onConflict(['user_entity_ref'])
.merge();
await expect(knex('user_info')).resolves.toEqual([
{
user_entity_ref: 'user:default/backstage-user',
user_info,
exp: expect.anything(),
},
]);
await knex
.insert({
user_entity_ref: 'user:default/backstage-user-2',
user_info,
updated_at: knex.fn.now(),
})
.into('user_info');
await migrateDownOnce(knex);
await expect(
knex('user_info').select('created_at', 'updated_at'),
).resolves.toEqual([
{
created_at: expect.any(Date),
updated_at: expect.any(Date),
},
{
created_at: expect.any(Date),
updated_at: expect.any(Date),
},
]);
await knex.destroy();
},
);
await migrateDownOnce(knex);
it.each(databases.eachSupportedId())(
'20250707164600_user_created_at.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
await migrateUntilBefore(knex, '20250707164600_user_created_at.js');
await expect(knex('user_info').select('exp')).resolves.toEqual([
{ exp: expect.any(Date) },
{ exp: expect.any(Date) },
]);
if (knex.client.config.client.includes('sqlite')) {
// Sqlite doesn't support adding a column with non-constant default when table has data
// so we just test that the migration runs without errors
await migrateUpOnce(knex);
await knex.destroy();
});
return;
}
it('20250909120000_oidc_client_registration.js', async () => {
const knex = await databases.init(databaseId);
const user_info = JSON.stringify({
claims: {
ent: ['group:default/group1', 'group:default/group2'],
},
});
await migrateUntilBefore(
knex,
'20250909120000_oidc_client_registration.js',
);
await migrateUpOnce(knex);
await knex
.insert({
user_entity_ref: 'user:default/backstage-user',
user_info,
exp: knex.fn.now(),
})
.into('user_info');
const { exp } = await knex('user_info').first();
await migrateUpOnce(knex);
const { created_at, updated_at } = await knex('user_info').first();
expect(updated_at).toEqual(exp);
expect(created_at).toBeDefined();
await knex
.insert({
user_entity_ref: 'user:default/backstage-user',
user_info,
updated_at: knex.fn.now(),
})
.into('user_info')
.onConflict(['user_entity_ref'])
.merge();
await knex
.insert({
user_entity_ref: 'user:default/backstage-user-2',
user_info,
updated_at: knex.fn.now(),
})
.into('user_info');
await expect(
knex('user_info').select('created_at', 'updated_at'),
).resolves.toEqual([
{
created_at: expect.any(Date),
updated_at: expect.any(Date),
},
{
created_at: expect.any(Date),
updated_at: expect.any(Date),
},
]);
await migrateDownOnce(knex);
await expect(knex('user_info').select('exp')).resolves.toEqual([
{ exp: expect.any(Date) },
{ exp: expect.any(Date) },
]);
await knex.destroy();
},
);
it.each(databases.eachSupportedId())(
'20250909120000_oidc_client_registration.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
await migrateUntilBefore(
knex,
'20250909120000_oidc_client_registration.js',
);
await migrateUpOnce(knex);
await knex
.insert({
client_id: 'test-client-id',
client_secret: 'test-client-secret',
client_name: 'Test Client',
response_types: JSON.stringify(['code']),
grant_types: JSON.stringify(['authorization_code']),
redirect_uris: JSON.stringify(['https://example.com/callback']),
scope: 'openid profile',
metadata: JSON.stringify({ description: 'Test client' }),
})
.into('oidc_clients');
await expect(
knex('oidc_clients').where('client_id', 'test-client-id').first(),
).resolves.toEqual({
await knex
.insert({
client_id: 'test-client-id',
client_secret: 'test-client-secret',
client_name: 'Test Client',
@@ -222,230 +197,235 @@ describe('migrations', () => {
redirect_uris: JSON.stringify(['https://example.com/callback']),
scope: 'openid profile',
metadata: JSON.stringify({ description: 'Test client' }),
});
})
.into('oidc_clients');
await knex
.insert({
id: 'test-session-id',
client_id: 'test-client-id',
user_entity_ref: 'user:default/test-user',
redirect_uri: 'https://example.com/callback',
scope: 'openid',
state: 'test-state',
response_type: 'code',
code_challenge: 'test-challenge',
code_challenge_method: 'S256',
nonce: 'test-nonce',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
await expect(
knex('oidc_clients').where('client_id', 'test-client-id').first(),
).resolves.toEqual({
client_id: 'test-client-id',
client_secret: 'test-client-secret',
client_name: 'Test Client',
response_types: JSON.stringify(['code']),
grant_types: JSON.stringify(['authorization_code']),
redirect_uris: JSON.stringify(['https://example.com/callback']),
scope: 'openid profile',
metadata: JSON.stringify({ description: 'Test client' }),
});
await expect(
knex('oauth_authorization_sessions')
.where('id', 'test-session-id')
.first(),
).resolves.toEqual(
expect.objectContaining({
id: 'test-session-id',
client_id: 'test-client-id',
user_entity_ref: 'user:default/test-user',
redirect_uri: 'https://example.com/callback',
scope: 'openid',
state: 'test-state',
response_type: 'code',
code_challenge: 'test-challenge',
code_challenge_method: 'S256',
nonce: 'test-nonce',
status: 'pending',
}),
);
await knex
.insert({
id: 'test-session-id',
client_id: 'test-client-id',
user_entity_ref: 'user:default/test-user',
redirect_uri: 'https://example.com/callback',
scope: 'openid',
state: 'test-state',
response_type: 'code',
code_challenge: 'test-challenge',
code_challenge_method: 'S256',
nonce: 'test-nonce',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
await knex
.insert({
code: 'test-auth-code',
session_id: 'test-session-id',
expires_at: new Date(Date.now() + 600000),
used: false,
})
.into('oidc_authorization_codes');
await expect(
knex('oidc_authorization_codes')
.where('code', 'test-auth-code')
.first(),
).resolves.toEqual(
expect.objectContaining({
code: 'test-auth-code',
session_id: 'test-session-id',
}),
);
await knex('oauth_authorization_sessions')
await expect(
knex('oauth_authorization_sessions')
.where('id', 'test-session-id')
.del();
.first(),
).resolves.toEqual(
expect.objectContaining({
id: 'test-session-id',
client_id: 'test-client-id',
user_entity_ref: 'user:default/test-user',
redirect_uri: 'https://example.com/callback',
scope: 'openid',
state: 'test-state',
response_type: 'code',
code_challenge: 'test-challenge',
code_challenge_method: 'S256',
nonce: 'test-nonce',
status: 'pending',
}),
);
await expect(
knex('oidc_authorization_codes').where('session_id', 'test-session-id'),
).resolves.toHaveLength(0);
await knex
.insert({
code: 'test-auth-code',
session_id: 'test-session-id',
expires_at: new Date(Date.now() + 600000),
used: false,
})
.into('oidc_authorization_codes');
await migrateDownOnce(knex);
await expect(
knex('oidc_authorization_codes').where('code', 'test-auth-code').first(),
).resolves.toEqual(
expect.objectContaining({
code: 'test-auth-code',
session_id: 'test-session-id',
}),
);
const tables = [
'oidc_clients',
'oauth_authorization_sessions',
'oidc_authorization_codes',
];
await knex('oauth_authorization_sessions')
.where('id', 'test-session-id')
.del();
for (const table of tables) {
await expect(knex.schema.hasTable(table)).resolves.toBe(false);
}
await expect(
knex('oidc_authorization_codes').where('session_id', 'test-session-id'),
).resolves.toHaveLength(0);
await knex.destroy();
},
);
await migrateDownOnce(knex);
it.each(databases.eachSupportedId())(
'20251118120000_oauth_state_text.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
const tables = [
'oidc_clients',
'oauth_authorization_sessions',
'oidc_authorization_codes',
];
await migrateUntilBefore(knex, '20251118120000_oauth_state_text.js');
for (const table of tables) {
await expect(knex.schema.hasTable(table)).resolves.toBe(false);
}
// First create a client for the foreign key constraint
await knex
.insert({
client_id: 'test-client-id',
client_secret: 'test-client-secret',
client_name: 'Test Client',
response_types: JSON.stringify(['code']),
grant_types: JSON.stringify(['authorization_code']),
redirect_uris: JSON.stringify(['https://example.com/callback']),
})
.into('oidc_clients');
await knex.destroy();
});
// Insert a session with state before migration
const existingState = 'existing-short-state';
await knex
.insert({
id: 'test-existing-session',
client_id: 'test-client-id',
redirect_uri: 'https://example.com/callback',
state: existingState,
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
it('20251118120000_oauth_state_text.js', async () => {
const knex = await databases.init(databaseId);
// Apply the migration that changes state to TEXT
await migrateUpOnce(knex);
await migrateUntilBefore(knex, '20251118120000_oauth_state_text.js');
// Verify existing state persists after migration
await expect(
knex('oauth_authorization_sessions')
.where('id', 'test-existing-session')
.first(),
).resolves.toEqual(
expect.objectContaining({
id: 'test-existing-session',
state: existingState,
}),
);
// First create a client for the foreign key constraint
await knex
.insert({
client_id: 'test-client-id',
client_secret: 'test-client-secret',
client_name: 'Test Client',
response_types: JSON.stringify(['code']),
grant_types: JSON.stringify(['authorization_code']),
redirect_uris: JSON.stringify(['https://example.com/callback']),
})
.into('oidc_clients');
// Test inserting a state parameter longer than 255 characters
// This is based on the real-world example from the issue
const longState = 'a'.repeat(280);
// Insert a session with state before migration
const existingState = 'existing-short-state';
await knex
.insert({
id: 'test-existing-session',
client_id: 'test-client-id',
redirect_uri: 'https://example.com/callback',
state: existingState,
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
await knex
.insert({
id: 'test-long-state-session',
client_id: 'test-client-id',
redirect_uri: 'https://example.com/callback',
state: longState,
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
// Apply the migration that changes state to TEXT
await migrateUpOnce(knex);
await expect(
knex('oauth_authorization_sessions')
.where('id', 'test-long-state-session')
.first(),
).resolves.toEqual(
expect.objectContaining({
id: 'test-long-state-session',
state: longState,
}),
);
// Verify existing state persists after migration
await expect(
knex('oauth_authorization_sessions')
.where('id', 'test-existing-session')
.first(),
).resolves.toEqual(
expect.objectContaining({
id: 'test-existing-session',
state: existingState,
}),
);
await migrateDownOnce(knex);
// Test inserting a state parameter longer than 255 characters
// This is based on the real-world example from the issue
const longState = 'a'.repeat(280);
await knex.destroy();
},
);
await knex
.insert({
id: 'test-long-state-session',
client_id: 'test-client-id',
redirect_uri: 'https://example.com/callback',
state: longState,
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
it.each(databases.eachSupportedId())(
'20251217120000_drop_oidc_clients_fk.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
await expect(
knex('oauth_authorization_sessions')
.where('id', 'test-long-state-session')
.first(),
).resolves.toEqual(
expect.objectContaining({
id: 'test-long-state-session',
state: longState,
}),
);
await migrateUntilBefore(knex, '20251217120000_drop_oidc_clients_fk.js');
await migrateDownOnce(knex);
// Create a client for DCR sessions
await knex
.insert({
client_id: 'dcr-client-id',
client_secret: 'test-client-secret',
client_name: 'DCR Client',
response_types: JSON.stringify(['code']),
grant_types: JSON.stringify(['authorization_code']),
redirect_uris: JSON.stringify(['https://example.com/callback']),
})
.into('oidc_clients');
await knex.destroy();
});
// Create a DCR session (has matching client in oidc_clients)
await knex
.insert({
id: 'dcr-session',
client_id: 'dcr-client-id',
redirect_uri: 'https://example.com/callback',
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
it('20251217120000_drop_oidc_clients_fk.js', async () => {
const knex = await databases.init(databaseId);
// Apply migration - drops FK constraint
await migrateUpOnce(knex);
await migrateUntilBefore(knex, '20251217120000_drop_oidc_clients_fk.js');
// Now we can insert a CIMD session (URL-based client_id not in oidc_clients)
await knex
.insert({
id: 'cimd-session',
client_id: 'https://example.com/.well-known/oauth-client/cli',
redirect_uri: 'http://localhost:8080/callback',
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
// Create a client for DCR sessions
await knex
.insert({
client_id: 'dcr-client-id',
client_secret: 'test-client-secret',
client_name: 'DCR Client',
response_types: JSON.stringify(['code']),
grant_types: JSON.stringify(['authorization_code']),
redirect_uris: JSON.stringify(['https://example.com/callback']),
})
.into('oidc_clients');
// Verify both sessions exist
await expect(
knex('oauth_authorization_sessions').select('id').orderBy('id'),
).resolves.toEqual([{ id: 'cimd-session' }, { id: 'dcr-session' }]);
// Create a DCR session (has matching client in oidc_clients)
await knex
.insert({
id: 'dcr-session',
client_id: 'dcr-client-id',
redirect_uri: 'https://example.com/callback',
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
// Rollback - should delete CIMD sessions and re-add FK
await migrateDownOnce(knex);
// Apply migration - drops FK constraint
await migrateUpOnce(knex);
// CIMD session should be deleted, DCR session should remain
await expect(
knex('oauth_authorization_sessions').select('id'),
).resolves.toEqual([{ id: 'dcr-session' }]);
// Now we can insert a CIMD session (URL-based client_id not in oidc_clients)
await knex
.insert({
id: 'cimd-session',
client_id: 'https://example.com/.well-known/oauth-client/cli',
redirect_uri: 'http://localhost:8080/callback',
response_type: 'code',
status: 'pending',
expires_at: new Date(Date.now() + 3600000),
})
.into('oauth_authorization_sessions');
await knex.destroy();
},
);
// Verify both sessions exist
await expect(
knex('oauth_authorization_sessions').select('id').orderBy('id'),
).resolves.toEqual([{ id: 'cimd-session' }, { id: 'dcr-session' }]);
// Rollback - should delete CIMD sessions and re-add FK
await migrateDownOnce(knex);
// CIMD session should be deleted, DCR session should remain
await expect(
knex('oauth_authorization_sessions').select('id'),
).resolves.toEqual([{ id: 'dcr-session' }]);
await knex.destroy();
});
});