chore(tests): add setupDatabaseReset utility (#24786)

This commit is contained in:
markkaylor
2025-11-07 11:19:42 +01:00
committed by GitHub
parent 807d2d1373
commit 366cfb5c4e
3 changed files with 140 additions and 53 deletions
+1 -3
View File
@@ -1,11 +1,9 @@
version: '3'
services:
postgres:
image: postgres
restart: always
volumes:
- pgdata_test:/var/lib/postgresql/data
- pgdata_test:/var/lib/postgresql
environment:
POSTGRES_USER: strapi
POSTGRES_PASSWORD: strapi
@@ -3,7 +3,7 @@ import type { Core, Modules } from '@strapi/types';
import { omit } from 'lodash/fp';
import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper';
import { testInTransaction } from '../../../utils/index';
import { setupDatabaseReset } from '../../../utils/index';
import resources from './resources/index';
import { ARTICLE_UID, findArticleDb } from './utils';
@@ -25,8 +25,10 @@ describe('Document Service', () => {
await destroyTestSetup(testUtils);
});
setupDatabaseReset();
describe('Update', () => {
testInTransaction('Can update a draft', async () => {
it('Can update a draft', async () => {
const articleDb = await findArticleDb({ title: 'Article1-Draft-EN' });
const data = {
@@ -55,7 +57,7 @@ describe('Document Service', () => {
});
});
testInTransaction('Can update a draft article in dutch', async () => {
it('Can update a draft article in dutch', async () => {
const articleDb = await findArticleDb({ title: 'Article1-Draft-NL' });
const data = { title: 'updated document' };
@@ -78,7 +80,7 @@ describe('Document Service', () => {
expect(enLocale).toBeDefined();
});
testInTransaction('Create a new locale for an existing document', async () => {
it('Create a new locale for an existing document', async () => {
const articleDb = await findArticleDb({ title: 'Article1-Draft-EN' });
const newName = 'updated document';
@@ -106,7 +108,7 @@ describe('Document Service', () => {
expect(enLocale).toBeDefined();
});
testInTransaction('Can update a draft and publish it', async () => {
it('Can update a draft and publish it', async () => {
const articleDb = await findArticleDb({ title: 'Article1-Draft-EN' });
const article = await updateArticle({
@@ -121,7 +123,7 @@ describe('Document Service', () => {
});
});
testInTransaction('Returns null if document to update does not exist', async () => {
it('Returns null if document to update does not exist', async () => {
const article = await updateArticle({
documentId: 'does-not-exist',
data: { title: 'updated document' },
@@ -130,50 +132,47 @@ describe('Document Service', () => {
expect(article).toBeNull();
});
testInTransaction(
'Preserves non-localized fields when updating localized content for new locale',
async () => {
// Covers issue https://github.com/strapi/strapi/issues/21594
it('Preserves non-localized fields when updating localized content for new locale', async () => {
// Covers issue https://github.com/strapi/strapi/issues/21594
const MIXED_CONTENT_UID = 'api::mixed-content.mixed-content';
const MIXED_CONTENT_UID = 'api::mixed-content.mixed-content';
// Create a document with both localized and non-localized fields
const originalDoc = await strapi.documents(MIXED_CONTENT_UID).create({
data: {
localizedText: 'Original Text',
sharedText: 'Shared Content',
},
locale: 'en',
});
const updatedDoc = await strapi.documents(MIXED_CONTENT_UID).update({
documentId: originalDoc.documentId,
locale: 'es',
data: {
localizedText: 'Texto Español',
},
});
expect(updatedDoc).toMatchObject({
documentId: originalDoc.documentId,
locale: 'es',
localizedText: 'Texto Español',
// Non-localized field should remain unchanged
sharedText: 'Shared Content',
});
const originalEnDoc = await strapi.documents(MIXED_CONTENT_UID).findOne({
documentId: originalDoc.documentId,
locale: 'en',
});
expect(originalEnDoc).toMatchObject({
documentId: originalDoc.documentId,
locale: 'en',
// Create a document with both localized and non-localized fields
const originalDoc = await strapi.documents(MIXED_CONTENT_UID).create({
data: {
localizedText: 'Original Text',
sharedText: 'Shared Content',
});
}
);
},
locale: 'en',
});
const updatedDoc = await strapi.documents(MIXED_CONTENT_UID).update({
documentId: originalDoc.documentId,
locale: 'es',
data: {
localizedText: 'Texto Español',
},
});
expect(updatedDoc).toMatchObject({
documentId: originalDoc.documentId,
locale: 'es',
localizedText: 'Texto Español',
// Non-localized field should remain unchanged
sharedText: 'Shared Content',
});
const originalEnDoc = await strapi.documents(MIXED_CONTENT_UID).findOne({
documentId: originalDoc.documentId,
locale: 'en',
});
expect(originalEnDoc).toMatchObject({
documentId: originalDoc.documentId,
locale: 'en',
localizedText: 'Original Text',
sharedText: 'Shared Content',
});
});
});
});
+93 -3
View File
@@ -1,6 +1,89 @@
// Note: any tests that would cause writes to the db should be wrapped with this method to prevent changes
// Alternatively, we could truncate/insert the tables in afterEach which should be only marginally slower
// TODO: move to utils
/* -------------------------------------------------------------------------------------------------
* 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
* -----------------------------------------------------------------------------------------------*/
export const wrapInTransaction = (test) => {
return async (...args) => {
await strapi.db.transaction(async ({ trx, rollback }) => {
@@ -10,6 +93,13 @@ export const wrapInTransaction = (test) => {
};
};
/**
* Resets the database by leveragin a transaction rollback
*
* NOTE:
* Alternatively, use setupDatabaseReset() which is slower but avoids errors thrown by asnyc operations
* executed after the test's transaction context has closed.
*/
export const testInTransaction = (...args: Parameters<jest.It>) => {
if (args.length > 1) {
return it(