Files
strapi/tests/utils/dts-import.ts
T

279 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { statSync } from 'fs';
import { resolve } from 'path';
import { ALLOWED_CONTENT_TYPES, CUSTOM_TRANSFER_TOKEN_ACCESS_KEY } from '../e2e/constants';
const {
file: {
providers: { createLocalFileSourceProvider },
},
directory: {
providers: { createLocalDirectorySourceProvider },
},
strapi: {
providers: { createRemoteStrapiDestinationProvider, createLocalStrapiDestinationProvider },
},
engine: { createTransferEngine },
} = require('@strapi/data-transfer');
interface RestoreConfiguration {
coreStore: boolean;
}
/**
* Base URL for the Strapi instance under test (HTTP DTS). **Requires a non-empty `process.env.PORT`.**
*
* **Normal e2e (`yarn test:e2e`):** `tests/utils/runners/browser-runner.js` passes `PORT` (and
* `HOST`, `TEST_APP_PATH`) into the Playwright subprocess **before** config loads; the generated
* per-app `playwright.config.js` assigns `process.env.PORT` again (`tests/scripts/run-tests.js`).
* `tests/utils/global-setup.ts` also fails fast if `PORT` is missing — same contract.
*
* **Manual CLI:** only `importData()` at the bottom of this file defaults `PORT` to `8000` when unset
* (first test-app slot). Other programmatic callers must set `PORT` themselves — we deliberately
* removed `?? 1337` so we never silently hit Strapis dev default while the runner uses `8000 + index`.
*/
const getStrapiTestBaseUrl = () => {
const port = process.env.PORT?.trim();
if (!port) {
throw new Error(
'getStrapiTestBaseUrl: PORT is not set. Use `yarn test:e2e` (runner sets PORT), or set PORT yourself (e.g. `PORT=8000` for the first e2e test app).'
);
}
return `http://127.0.0.1:${port}`;
};
/**
* Reset the DB and import data from a DTS backup
* This function ensures we keep all admin user's and roles in the DB
* see: https://docs.strapi.io/developer-docs/latest/developer-resources/data-management.html
*
* This version requires a running Strapi server (for e2e tests)
*/
export const resetDatabaseAndImportDataFromPath = async (
file: string,
modifiedContentTypesFn: (cts: string[]) => string[] = (cts) => cts,
configuration: RestoreConfiguration = { coreStore: true }
) => {
// If file is already an absolute path, use it; otherwise resolve relative to e2e/data
const filePath =
file.startsWith('/') || file.includes('\\') ? file : resolve(__dirname, '../e2e/data/', file);
const source = createSourceProvider(filePath);
const includedTypes = modifiedContentTypesFn(ALLOWED_CONTENT_TYPES);
const destination = createRemoteDestinationProvider(includedTypes, configuration);
const engine = createTransferEngine(source, destination, {
versionStrategy: 'ignore',
schemaStrategy: 'ignore',
transforms: {
links: [
{
// only transfer relations to+from requested content types
filter(link: any) {
return (
includedTypes.includes(link.left.type) &&
(includedTypes.includes(link.right.type) || link.right.type === undefined)
);
},
},
],
entities: [
{
// only include entities of requested content types
filter(entity: any) {
return includedTypes.includes(entity.type);
},
},
],
},
});
engine.diagnostics.onDiagnostic((diagnostic: any) => {
if (diagnostic.kind !== 'info') console.log(diagnostic);
});
try {
// reset the transfer token to allow the transfer if it's been wiped (that is, not included in previous import data)
await fetch(new URL('/api/config/resettransfertoken', getStrapiTestBaseUrl()), {
method: 'POST',
});
} catch (err) {
console.error('Token reset failed.' + JSON.stringify(err, null, 2));
process.exit(1);
}
try {
await engine.transfer();
} catch {
console.error('Import process failed.');
process.exit(1);
}
};
/**
* Reset the DB and import data from a DTS backup programmatically
* This version works with a loaded Strapi instance (no HTTP server required)
* Use this for CLI tests where Strapi is loaded but not running as a server
* The function handles loading and destroying the Strapi instance internally
*
* @param appPath - Path to the Strapi app directory
* @param file - Name or path of an unpacked export directory or `.tar` under tests/e2e/data (or absolute)
* @param modifiedContentTypesFn - Optional function to modify content types
* @param configuration - Restore configuration
*/
export const resetDatabaseAndImportDataFromPathProgrammatic = async (
appPath: string,
file: string,
modifiedContentTypesFn: (cts: string[]) => string[] = (cts) => cts,
configuration: RestoreConfiguration = { coreStore: true }
) => {
// Load environment variables from the test app's .env file
const { loadTestAppEnv } = require('./helpers');
await loadTestAppEnv(appPath);
// Load Strapi instance
const { createStrapi } = require('@strapi/core');
const strapiInstance = createStrapi({
appDir: appPath,
distDir: appPath,
});
await strapiInstance.load();
try {
// If file is already an absolute path, use it; otherwise resolve relative to e2e/data
const filePath =
file.startsWith('/') || file.includes('\\') ? file : resolve(__dirname, '../e2e/data/', file);
const source = createSourceProvider(filePath);
const includedTypes = modifiedContentTypesFn(ALLOWED_CONTENT_TYPES);
const destination = createLocalDestinationProvider(
strapiInstance,
includedTypes,
configuration
);
const engine = createTransferEngine(source, destination, {
versionStrategy: 'ignore',
schemaStrategy: 'ignore',
transforms: {
links: [
{
// only transfer relations to+from requested content types
filter(link: any) {
return (
includedTypes.includes(link.left.type) &&
(includedTypes.includes(link.right.type) || link.right.type === undefined)
);
},
},
],
entities: [
{
// only include entities of requested content types
filter(entity: any) {
return includedTypes.includes(entity.type);
},
},
],
},
});
engine.diagnostics.onDiagnostic((diagnostic: any) => {
if (diagnostic.kind !== 'info') console.log(diagnostic);
});
await engine.transfer();
} catch (err) {
console.error('Import process failed:', err);
throw err;
} finally {
await strapiInstance.destroy();
}
};
// Script execution: only run if this file is executed directly (not imported)
const importData = async () => {
const args = process.argv.slice(2);
const filePath = args[0];
if (!filePath) {
console.error('Please provide the name of the file you want to import from tests/e2e/data');
process.exit(1);
}
// Manual CLI only: default to the first e2e test app port so `getStrapiTestBaseUrl` does not guess.
if (!process.env.PORT?.trim()) {
process.env.PORT = '8000';
}
await resetDatabaseAndImportDataFromPath(filePath);
console.log('Data transfer succeeded');
process.exit(0);
};
const isTestEnvironment =
process.env.NODE_ENV === 'test' ||
process.env.JEST_WORKER_ID !== undefined ||
typeof jest !== 'undefined';
const isDirectExecution =
!isTestEnvironment &&
typeof require.main !== 'undefined' &&
require.main === module &&
process.argv.length > 2 &&
process.argv[2];
if (isDirectExecution) {
importData();
}
const createSourceProvider = (filePath: string) => {
const st = statSync(filePath);
if (st.isDirectory()) {
return createLocalDirectorySourceProvider({
directory: { path: filePath },
});
}
return createLocalFileSourceProvider({
file: { path: filePath },
encryption: { enabled: false },
compression: { enabled: false },
});
};
const createRemoteDestinationProvider = (
includedTypes: any[] = [],
configuration: RestoreConfiguration
) => {
return createRemoteStrapiDestinationProvider({
url: new URL('/admin', getStrapiTestBaseUrl()),
auth: { type: 'token', token: CUSTOM_TRANSFER_TOKEN_ACCESS_KEY },
strategy: 'restore',
restore: {
assets: true,
entities: {
include: includedTypes,
},
configuration,
},
});
};
const createLocalDestinationProvider = (
strapiInstance: any,
includedTypes: any[] = [],
configuration: RestoreConfiguration
) => {
return createLocalStrapiDestinationProvider({
async getStrapi() {
return strapiInstance;
},
autoDestroy: false,
strategy: 'restore',
restore: {
assets: true,
entities: {
include: includedTypes,
},
configuration,
},
});
};