Files
Boaz Poolman ec72af97f0 Fix polymorphic relations in the document service API (#26099)
* chore: remove early exit for morph relation types

* fix: issues with creating morphToOne relations

* chore: add tests

* test: add polymorphic relations tests

---------

Co-authored-by: Ben Irvin <ben@innerdvations.com>
2026-04-30 11:58:02 +02:00

136 lines
4.1 KiB
TypeScript

/* -------------------------------------------------------------------------------------------------
* setupDatabaseReset
* -----------------------------------------------------------------------------------------------*/
// Store initial database state for reset
let initialTestData = {};
let isTestDataCaptured = false;
let allTableNames = [];
/**
* Capture the initial state of all database tables for later restoration
*/
async function captureInitialTestData() {
if (isTestDataCaptured) return;
initialTestData = {};
// Use Strapi's built-in dialect system to get table names
allTableNames = await strapi.db.dialect.schemaInspector.getTables();
for (const tableName of allTableNames) {
try {
const data = await strapi.db.connection(tableName).select('*');
initialTestData[tableName] = data;
} catch (error) {
console.warn(`Could not capture data for table ${tableName}:`, error.message);
initialTestData[tableName] = [];
}
}
isTestDataCaptured = true;
}
async function resetTestDatabase() {
// Use Strapi's built-in schema update mechanism to disable constraints (e.g. foreign key constraints)
await strapi.db.dialect.startSchemaUpdate();
try {
for (const tableName of allTableNames) {
try {
// Clear the table
await strapi.db.connection(tableName).del();
// Restore initial data if any
const initialData = initialTestData[tableName];
if (initialData && initialData.length > 0) {
await strapi.db.connection(tableName).insert(initialData);
}
} catch (error) {
console.warn(`Could not reset table ${tableName}:`, error.message);
}
}
} finally {
// Always re-enable constraints
await strapi.db.dialect.endSchemaUpdate();
}
}
/**
* Setup database reset for a test suite
* Call this in your describe block to automatically reset after each test
*
* NOTE:
* Only use sparingly where needed as the operation is slower than testInTransaction
*/
export function setupDatabaseReset() {
let isDataCaptured = false;
beforeEach(async () => {
if (!isDataCaptured) {
await captureInitialTestData();
isDataCaptured = true;
}
});
afterEach(async () => {
if (isDataCaptured) {
await resetTestDatabase();
}
});
}
/* -------------------------------------------------------------------------------------------------
* testInTransaction
* -----------------------------------------------------------------------------------------------*/
/**
* Thrown at the end of a successful test to force the Strapi `transaction` catch path
* (single `rollback()`) and avoid a second `commit()` on an already-rolled-back Knex
* transactor. See `test-in-transaction-strapi-db.md`.
*/
const testInTransactionRollback = Symbol('testInTransaction.rollback');
export const wrapInTransaction = (test) => {
return async (...args) => {
try {
await strapi.db.transaction(async ({ trx }) => {
await test(trx, ...args);
// Do not `await rollback()` and return: Strapi's transaction() always calls
// `commit()` on successful return, which double-finalises the transactor
// (hang / "Transaction query already complete").
throw testInTransactionRollback;
});
} catch (e) {
if (e === testInTransactionRollback) {
return;
}
throw e;
}
};
};
/**
* Resets the database by rolling back a transaction after each test.
*
* See `test-in-transaction-strapi-db.md` for how this interacts with `strapi.db.transaction`.
*
* NOTE: Alternatively, use `setupDatabaseReset()` which is slower but avoids async work
* that runs after the test's transaction context has closed.
*/
export const testInTransaction = (...args: Parameters<jest.It>) => {
if (args.length > 1) {
return it(
args[0], // name
wrapInTransaction(args[1]), // fn
args[2] // timeout
);
}
return it(...args);
};
testInTransaction.skip = it.skip as jest.It['skip'];
testInTransaction.only = it.only as jest.It['only'];
testInTransaction.todo = it.todo as jest.It['todo'];
testInTransaction.each = it.each as jest.It['each'];