implement wp testing
This commit is contained in:
518
chat/external-wp-testing.js
Normal file
518
chat/external-wp-testing.js
Normal file
@@ -0,0 +1,518 @@
|
||||
const fs = require('fs/promises');
|
||||
const fsSync = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { spawn } = require('child_process');
|
||||
const { randomUUID } = require('crypto');
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
wpHost: process.env.TEST_WP_HOST || process.env.EXTERNAL_WP_HOST || '',
|
||||
wpSshUser: process.env.TEST_WP_SSH_USER || process.env.EXTERNAL_WP_SSH_USER || 'wordpress',
|
||||
wpSshKey: process.env.TEST_WP_SSH_KEY || process.env.TEST_WP_SSH_KEY_PATH || process.env.EXTERNAL_WP_SSH_KEY || '',
|
||||
wpPath: process.env.TEST_WP_PATH || process.env.EXTERNAL_WP_PATH || '/var/www/html',
|
||||
wpBaseUrl: process.env.TEST_WP_BASE_URL || process.env.EXTERNAL_WP_BASE_URL || '',
|
||||
enableMultisite: (process.env.TEST_WP_MULTISITE || process.env.EXTERNAL_WP_MULTISITE || 'true') !== 'false',
|
||||
subsitePrefix: process.env.TEST_WP_SUBSITE_PREFIX || process.env.EXTERNAL_WP_SUBSITE_PREFIX || 'test',
|
||||
subsiteDomain: process.env.TEST_WP_SUBSITE_DOMAIN || process.env.EXTERNAL_WP_SUBSITE_DOMAIN || '',
|
||||
subsiteMode: (process.env.TEST_WP_SUBSITE_MODE || process.env.EXTERNAL_WP_SUBSITE_MODE || 'subdirectory').toLowerCase(),
|
||||
maxConcurrentTests: Number(process.env.TEST_MAX_CONCURRENT || process.env.EXTERNAL_WP_MAX_CONCURRENT || 20),
|
||||
queueTimeoutMs: Number(process.env.TEST_QUEUE_TIMEOUT || process.env.EXTERNAL_WP_QUEUE_TIMEOUT || 300000),
|
||||
testTimeoutMs: Number(process.env.TEST_TIMEOUT || process.env.EXTERNAL_WP_TEST_TIMEOUT || 600000),
|
||||
autoCleanup: (process.env.TEST_AUTO_CLEANUP || process.env.EXTERNAL_WP_AUTO_CLEANUP || 'true') !== 'false',
|
||||
cleanupDelayMs: Number(process.env.TEST_CLEANUP_DELAY || process.env.EXTERNAL_WP_CLEANUP_DELAY || 3600000),
|
||||
errorLogPath: process.env.TEST_WP_ERROR_LOG || process.env.EXTERNAL_WP_ERROR_LOG || '/var/log/wp-errors.log',
|
||||
strictHostKeyChecking: (process.env.TEST_SSH_STRICT || process.env.EXTERNAL_WP_SSH_STRICT || 'false') === 'true',
|
||||
};
|
||||
|
||||
function expandHome(p) {
|
||||
if (!p || typeof p !== 'string') return p;
|
||||
if (p.startsWith('~')) {
|
||||
return path.join(os.homedir(), p.slice(1));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
function getExternalTestingConfig(overrides = {}) {
|
||||
const config = { ...DEFAULT_CONFIG, ...(overrides || {}) };
|
||||
config.wpSshKey = expandHome(config.wpSshKey);
|
||||
config.wpBaseUrl = (config.wpBaseUrl || '').trim();
|
||||
if (!config.wpBaseUrl && config.wpHost) {
|
||||
config.wpBaseUrl = `https://${config.wpHost}`;
|
||||
}
|
||||
config.maxConcurrentTests = Number.isFinite(config.maxConcurrentTests) && config.maxConcurrentTests > 0
|
||||
? Math.floor(config.maxConcurrentTests)
|
||||
: 1;
|
||||
config.queueTimeoutMs = Number.isFinite(config.queueTimeoutMs) && config.queueTimeoutMs > 0
|
||||
? Math.floor(config.queueTimeoutMs)
|
||||
: 300000;
|
||||
config.testTimeoutMs = Number.isFinite(config.testTimeoutMs) && config.testTimeoutMs > 0
|
||||
? Math.floor(config.testTimeoutMs)
|
||||
: 600000;
|
||||
config.cleanupDelayMs = Number.isFinite(config.cleanupDelayMs) && config.cleanupDelayMs >= 0
|
||||
? Math.floor(config.cleanupDelayMs)
|
||||
: 3600000;
|
||||
config.subsiteMode = config.subsiteMode === 'subdomain' ? 'subdomain' : 'subdirectory';
|
||||
return config;
|
||||
}
|
||||
|
||||
function spawnCommand(command, args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = Date.now();
|
||||
const child = spawn(command, args, { ...options, windowsHide: true });
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
||||
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
||||
|
||||
child.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
resolve({ stdout, stderr, code: code ?? 0, durationMs: Date.now() - start });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buildSshBaseArgs(config) {
|
||||
const args = [];
|
||||
if (config.wpSshKey) {
|
||||
args.push('-i', config.wpSshKey);
|
||||
}
|
||||
if (!config.strictHostKeyChecking) {
|
||||
args.push('-o', 'StrictHostKeyChecking=no');
|
||||
}
|
||||
args.push('-o', 'BatchMode=yes');
|
||||
return args;
|
||||
}
|
||||
|
||||
async function runSshCommand(config, command, options = {}) {
|
||||
const target = `${config.wpSshUser}@${config.wpHost}`;
|
||||
const args = [...buildSshBaseArgs(config), target, command];
|
||||
return spawnCommand('ssh', args, { timeout: options.timeout || config.testTimeoutMs });
|
||||
}
|
||||
|
||||
async function runScpUpload(config, localPath, remotePath) {
|
||||
const args = [...buildSshBaseArgs(config), '-r', localPath, `${config.wpSshUser}@${config.wpHost}:${remotePath}`];
|
||||
return spawnCommand('scp', args, { timeout: config.testTimeoutMs });
|
||||
}
|
||||
|
||||
function buildWpCliCommand(config, url, command) {
|
||||
return `wp --path=${config.wpPath} --url=${url} ${command}`;
|
||||
}
|
||||
|
||||
function resolveSubsiteUrl(config, slug) {
|
||||
if (!config.enableMultisite) return config.wpBaseUrl;
|
||||
const domain = config.subsiteDomain || config.wpHost;
|
||||
if (config.subsiteMode === 'subdomain') {
|
||||
return `https://${slug}.${domain}`;
|
||||
}
|
||||
return `${config.wpBaseUrl.replace(/\/$/, '')}/${slug}`;
|
||||
}
|
||||
|
||||
async function createSubsite(config, sessionId) {
|
||||
const slug = `${config.subsitePrefix}-${sessionId.slice(0, 8)}`;
|
||||
const title = `Test Environment ${sessionId}`;
|
||||
const adminEmail = 'test@example.com';
|
||||
|
||||
const createCmd = buildWpCliCommand(
|
||||
config,
|
||||
config.wpBaseUrl,
|
||||
`site create --slug=${slug} --title="${title}" --email=${adminEmail}`
|
||||
);
|
||||
const createRes = await runSshCommand(config, createCmd, { timeout: config.testTimeoutMs });
|
||||
|
||||
if (createRes.code !== 0) {
|
||||
throw new Error(`Failed to create subsite: ${createRes.stderr || createRes.stdout}`);
|
||||
}
|
||||
|
||||
const url = resolveSubsiteUrl(config, slug);
|
||||
const configureCmd = buildWpCliCommand(
|
||||
config,
|
||||
url,
|
||||
'option update permalink_structure "/%postname%/" && wp rewrite flush'
|
||||
);
|
||||
await runSshCommand(config, configureCmd, { timeout: config.testTimeoutMs });
|
||||
|
||||
return { slug, url, adminUrl: `${url.replace(/\/$/, '')}/wp-admin` };
|
||||
}
|
||||
|
||||
async function deleteSubsite(config, slug) {
|
||||
if (!slug) return;
|
||||
const deleteCmd = buildWpCliCommand(
|
||||
config,
|
||||
config.wpBaseUrl,
|
||||
`site delete --slug=${slug} --yes`
|
||||
);
|
||||
await runSshCommand(config, deleteCmd, { timeout: config.testTimeoutMs });
|
||||
}
|
||||
|
||||
async function installPluginFromLocal(config, pluginPath, pluginSlug, sessionId) {
|
||||
if (!pluginPath || !pluginSlug) throw new Error('Plugin path and slug are required');
|
||||
const safeSlug = pluginSlug.replace(/[^a-zA-Z0-9\-_]/g, '');
|
||||
const remoteBase = path.posix.join(config.wpPath, 'wp-content', 'plugins');
|
||||
const tmpDir = path.posix.join(remoteBase, `${safeSlug}__tmp_${sessionId}`);
|
||||
const finalDir = path.posix.join(remoteBase, safeSlug);
|
||||
|
||||
const cleanupTmp = async () => {
|
||||
const cmd = `rm -rf ${tmpDir}`;
|
||||
await runSshCommand(config, cmd, { timeout: config.testTimeoutMs }).catch(() => {});
|
||||
};
|
||||
|
||||
await cleanupTmp();
|
||||
const scpResult = await runScpUpload(config, pluginPath, tmpDir);
|
||||
if (scpResult.code !== 0) {
|
||||
throw new Error(`Failed to upload plugin: ${scpResult.stderr || scpResult.stdout}`);
|
||||
}
|
||||
|
||||
const moveCmd = `rm -rf ${finalDir} && mv ${tmpDir} ${finalDir}`;
|
||||
const moveRes = await runSshCommand(config, moveCmd, { timeout: config.testTimeoutMs });
|
||||
if (moveRes.code !== 0) {
|
||||
throw new Error(`Failed to install plugin: ${moveRes.stderr || moveRes.stdout}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function installRequiredPlugins(config, subsiteUrl, plugins) {
|
||||
if (!Array.isArray(plugins)) return [];
|
||||
const installed = [];
|
||||
for (const spec of plugins) {
|
||||
if (!spec || !spec.plugin_slug) continue;
|
||||
const slug = spec.plugin_slug;
|
||||
const source = (spec.source || 'wordpress.org').toLowerCase();
|
||||
const activate = spec.activate !== false;
|
||||
let command;
|
||||
if (source === 'url' && spec.source_url) {
|
||||
command = `plugin install ${spec.source_url} ${activate ? '--activate' : ''}`;
|
||||
} else if (source === 'local' && spec.source_url) {
|
||||
await installPluginFromLocal(config, spec.source_url, slug, randomUUID());
|
||||
command = activate ? `plugin activate ${slug}` : `plugin deactivate ${slug}`;
|
||||
} else {
|
||||
command = `plugin install ${slug} ${activate ? '--activate' : ''}`;
|
||||
}
|
||||
|
||||
const res = await runSshCommand(config, buildWpCliCommand(config, subsiteUrl, command));
|
||||
installed.push({ slug, status: res.code === 0 ? (activate ? 'active' : 'installed') : 'error', output: res.stdout || res.stderr });
|
||||
}
|
||||
return installed;
|
||||
}
|
||||
|
||||
function normalizeScenarioAssertions(assertions = {}) {
|
||||
return {
|
||||
status_code: Number.isFinite(assertions.status_code) ? assertions.status_code : null,
|
||||
contains: Array.isArray(assertions.contains) ? assertions.contains : [],
|
||||
not_contains: Array.isArray(assertions.not_contains) ? assertions.not_contains : [],
|
||||
wp_cli_success: assertions.wp_cli_success === true,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTestScenarios(spec, pluginSlug, config) {
|
||||
const scenarios = Array.isArray(spec?.test_scenarios) ? spec.test_scenarios : [];
|
||||
const normalized = scenarios.map((s) => ({
|
||||
name: String(s?.name || 'Unnamed test'),
|
||||
type: String(s?.type || 'custom'),
|
||||
url: s?.url || '',
|
||||
selector: s?.selector || '',
|
||||
expected_text: s?.expected_text || '',
|
||||
wp_cli_command: s?.wp_cli_command || '',
|
||||
shortcode: s?.shortcode || '',
|
||||
hook: s?.hook || '',
|
||||
ajax_action: s?.ajax_action || '',
|
||||
method: s?.method || '',
|
||||
body: s?.body || '',
|
||||
assertions: normalizeScenarioAssertions(s?.assertions || {})
|
||||
}));
|
||||
|
||||
if (!normalized.length && pluginSlug) {
|
||||
normalized.push({
|
||||
name: 'Plugin activates without errors',
|
||||
type: 'custom',
|
||||
wp_cli_command: `plugin activate ${pluginSlug}`,
|
||||
assertions: { wp_cli_success: true, contains: [], not_contains: ['Fatal error', 'Parse error'] }
|
||||
});
|
||||
normalized.push({
|
||||
name: 'Plugin is listed as active',
|
||||
type: 'custom',
|
||||
wp_cli_command: `plugin status ${pluginSlug}`,
|
||||
assertions: { contains: ['Active'] }
|
||||
});
|
||||
}
|
||||
|
||||
if (spec?.auto_error_log_check !== false && config?.errorLogPath) {
|
||||
const safePath = config.errorLogPath.replace(/"/g, '').trim();
|
||||
const grepSlug = pluginSlug ? ` | grep -i "${pluginSlug}"` : '';
|
||||
normalized.push({
|
||||
name: 'Scan error log for PHP errors',
|
||||
type: 'custom',
|
||||
wp_cli_command: `eval "echo shell_exec('tail -n 200 ${safePath.replace(/'/g, "'\\''")}${grepSlug} || echo No errors found');"`,
|
||||
assertions: { not_contains: ['Fatal error', 'Parse error'] }
|
||||
});
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
async function runHttpScenario(config, subsiteUrl, scenario, baseFn) {
|
||||
const url = scenario.url || '/';
|
||||
const method = String(scenario.method || '').toUpperCase() || 'GET';
|
||||
const body = scenario.body || '';
|
||||
const bodyArg = body ? `, ["body" => ${JSON.stringify(body)}]` : '';
|
||||
const requestFn = method === 'POST' ? 'wp_remote_post' : 'wp_remote_get';
|
||||
const phpUrl = `${baseFn}("${url}")`;
|
||||
const command = buildWpCliCommand(
|
||||
config,
|
||||
subsiteUrl,
|
||||
`eval ' $res = ${requestFn}(${phpUrl}${bodyArg}); echo json_encode(["status" => wp_remote_retrieve_response_code($res), "body" => wp_remote_retrieve_body($res)]);'`
|
||||
);
|
||||
const result = await runSshCommand(config, command, { timeout: config.testTimeoutMs });
|
||||
let statusCode = null;
|
||||
let bodyOut = result.stdout || '';
|
||||
try {
|
||||
const parsed = JSON.parse((result.stdout || '').trim());
|
||||
statusCode = parsed.status;
|
||||
bodyOut = parsed.body || '';
|
||||
} catch (_) {
|
||||
bodyOut = result.stdout || result.stderr || '';
|
||||
}
|
||||
return { result, statusCode, body: bodyOut };
|
||||
}
|
||||
|
||||
function evaluateAssertions(output, statusCode, assertions) {
|
||||
const failures = [];
|
||||
if (Number.isFinite(assertions.status_code) && statusCode !== assertions.status_code) {
|
||||
failures.push(`status_code expected ${assertions.status_code} but got ${statusCode}`);
|
||||
}
|
||||
for (const needle of assertions.contains || []) {
|
||||
if (!output.includes(needle)) {
|
||||
failures.push(`missing text: ${needle}`);
|
||||
}
|
||||
}
|
||||
for (const needle of assertions.not_contains || []) {
|
||||
if (output.includes(needle)) {
|
||||
failures.push(`unexpected text: ${needle}`);
|
||||
}
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
async function runTestScenario(config, subsiteUrl, scenario) {
|
||||
const assertions = normalizeScenarioAssertions(scenario.assertions || {});
|
||||
let output = '';
|
||||
let statusCode = null;
|
||||
let command = scenario.wp_cli_command || '';
|
||||
let result;
|
||||
|
||||
if (scenario.type === 'endpoint' && !command) {
|
||||
const endpointRes = await runHttpScenario(config, subsiteUrl, scenario, 'home_url');
|
||||
result = endpointRes.result;
|
||||
statusCode = endpointRes.statusCode;
|
||||
output = endpointRes.body || '';
|
||||
} else if (scenario.type === 'admin_page' && !command) {
|
||||
const adminUrl = scenario.url || 'admin.php';
|
||||
const adminScenario = { ...scenario, url: adminUrl };
|
||||
const adminRes = await runHttpScenario(config, subsiteUrl, adminScenario, 'admin_url');
|
||||
result = adminRes.result;
|
||||
statusCode = adminRes.statusCode;
|
||||
output = adminRes.body || '';
|
||||
} else if (scenario.type === 'ajax' && !command) {
|
||||
const action = scenario.ajax_action || (scenario.url || '').replace(/^\/?/, '');
|
||||
const ajaxUrl = action.includes('admin-ajax.php') || action.includes('action=')
|
||||
? action
|
||||
: `admin-ajax.php?action=${action || 'ping'}`;
|
||||
const ajaxScenario = { ...scenario, url: ajaxUrl, method: scenario.method || 'POST' };
|
||||
const ajaxRes = await runHttpScenario(config, subsiteUrl, ajaxScenario, 'admin_url');
|
||||
result = ajaxRes.result;
|
||||
statusCode = ajaxRes.statusCode;
|
||||
output = ajaxRes.body || '';
|
||||
} else if (scenario.type === 'shortcode' && !command) {
|
||||
const shortcode = scenario.shortcode || scenario.url || '';
|
||||
const shortcodeInput = shortcode.includes('[') ? shortcode : `[${shortcode}]`;
|
||||
command = `eval "echo do_shortcode('${shortcodeInput.replace(/'/g, "'\\''")}');"`;
|
||||
result = await runSshCommand(config, buildWpCliCommand(config, subsiteUrl, command));
|
||||
output = (result.stdout || result.stderr || '').trim();
|
||||
} else if (scenario.type === 'hook' && !command) {
|
||||
const hook = scenario.hook || scenario.url || '';
|
||||
command = `eval "echo json_encode([\"action\" => has_action('${hook.replace(/'/g, "'\\''")}') ? 1 : 0, \"filter\" => has_filter('${hook.replace(/'/g, "'\\''")}') ? 1 : 0]);"`;
|
||||
result = await runSshCommand(config, buildWpCliCommand(config, subsiteUrl, command));
|
||||
output = (result.stdout || result.stderr || '').trim();
|
||||
} else {
|
||||
if (!command) {
|
||||
command = 'eval "echo \'No command provided\';"';
|
||||
}
|
||||
result = await runSshCommand(config, buildWpCliCommand(config, subsiteUrl, command));
|
||||
output = (result.stdout || result.stderr || '').trim();
|
||||
}
|
||||
|
||||
const failures = evaluateAssertions(output, statusCode, assertions);
|
||||
if (assertions.wp_cli_success && result.code !== 0) {
|
||||
failures.push(`wp_cli_command exited with ${result.code}`);
|
||||
}
|
||||
|
||||
return {
|
||||
name: scenario.name,
|
||||
status: failures.length ? 'failed' : 'passed',
|
||||
command,
|
||||
output,
|
||||
statusCode,
|
||||
duration: result.durationMs || 0,
|
||||
failures,
|
||||
};
|
||||
}
|
||||
|
||||
async function runTestScenarios(config, subsiteUrl, scenarios) {
|
||||
const results = [];
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
const result = await runTestScenario(config, subsiteUrl, scenario);
|
||||
results.push(result);
|
||||
if (result.status === 'passed') {
|
||||
passed += 1;
|
||||
} else {
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return { passed, failed, results };
|
||||
}
|
||||
|
||||
class ExternalTestQueue {
|
||||
constructor(maxConcurrent, queueTimeoutMs) {
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
this.queueTimeoutMs = queueTimeoutMs;
|
||||
this.active = 0;
|
||||
this.queue = [];
|
||||
}
|
||||
|
||||
enqueue(task) {
|
||||
return new Promise((resolve) => {
|
||||
const entry = { task, resolve, enqueuedAt: Date.now() };
|
||||
this.queue.push(entry);
|
||||
this.process();
|
||||
});
|
||||
}
|
||||
|
||||
process() {
|
||||
if (this.active >= this.maxConcurrent) return;
|
||||
const entry = this.queue.shift();
|
||||
if (!entry) return;
|
||||
|
||||
if (Date.now() - entry.enqueuedAt > this.queueTimeoutMs) {
|
||||
entry.resolve({ ok: false, error: 'Test queue timeout - too many concurrent tests' });
|
||||
this.process();
|
||||
return;
|
||||
}
|
||||
|
||||
this.active += 1;
|
||||
Promise.resolve()
|
||||
.then(entry.task)
|
||||
.then((result) => entry.resolve(result))
|
||||
.catch((error) => entry.resolve({ ok: false, error: error.message || String(error) }))
|
||||
.finally(() => {
|
||||
this.active -= 1;
|
||||
this.process();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createExternalWpTester(options = {}) {
|
||||
const logger = typeof options.logger === 'function' ? options.logger : null;
|
||||
const queue = new ExternalTestQueue(
|
||||
DEFAULT_CONFIG.maxConcurrentTests,
|
||||
DEFAULT_CONFIG.queueTimeoutMs
|
||||
);
|
||||
|
||||
const log = (msg, meta) => {
|
||||
if (logger) logger(msg, meta || {});
|
||||
};
|
||||
|
||||
async function runTest(input = {}, opts = {}) {
|
||||
const config = getExternalTestingConfig(opts.configOverrides);
|
||||
if (!config.wpHost || !config.wpSshUser) {
|
||||
return { ok: false, error: 'External WordPress test host is not configured' };
|
||||
}
|
||||
if (!config.wpSshKey || !fsSync.existsSync(config.wpSshKey)) {
|
||||
return { ok: false, error: 'SSH key for external WordPress testing is missing or invalid' };
|
||||
}
|
||||
|
||||
const sessionId = input.session_id || randomUUID();
|
||||
const pluginPath = input.plugin_path;
|
||||
const pluginSlug = input.plugin_slug || path.basename(pluginPath || '').replace(/\.zip$/i, '');
|
||||
const requiredPlugins = input.required_plugins || [];
|
||||
|
||||
return queue.enqueue(async () => {
|
||||
const start = Date.now();
|
||||
let subsite = { slug: null, url: config.wpBaseUrl, adminUrl: `${config.wpBaseUrl.replace(/\/$/, '')}/wp-admin` };
|
||||
const warnings = [];
|
||||
const errors = [];
|
||||
|
||||
try {
|
||||
if (config.enableMultisite) {
|
||||
subsite = await createSubsite(config, sessionId);
|
||||
}
|
||||
|
||||
if (pluginPath && pluginSlug) {
|
||||
await installPluginFromLocal(config, pluginPath, pluginSlug, sessionId);
|
||||
} else {
|
||||
warnings.push('Plugin path or slug missing; skipping plugin upload');
|
||||
}
|
||||
|
||||
const installedPlugins = await installRequiredPlugins(config, subsite.url, requiredPlugins);
|
||||
if (pluginSlug) {
|
||||
await runSshCommand(config, buildWpCliCommand(config, subsite.url, `plugin activate ${pluginSlug}`));
|
||||
}
|
||||
|
||||
const scenarios = normalizeTestScenarios(input, pluginSlug, config);
|
||||
const cliTests = await runTestScenarios(config, subsite.url, scenarios);
|
||||
|
||||
const result = {
|
||||
ok: cliTests.failed === 0,
|
||||
session_id: sessionId,
|
||||
subsite_url: subsite.url,
|
||||
test_results: {
|
||||
mode: 'cli',
|
||||
cli_tests: cliTests,
|
||||
},
|
||||
installed_plugins: installedPlugins,
|
||||
errors,
|
||||
warnings,
|
||||
duration: Date.now() - start,
|
||||
};
|
||||
|
||||
if (config.autoCleanup && subsite.slug) {
|
||||
setTimeout(() => {
|
||||
deleteSubsite(config, subsite.slug).catch(() => {});
|
||||
}, config.cleanupDelayMs);
|
||||
result.cleanup_scheduled = new Date(Date.now() + config.cleanupDelayMs).toISOString();
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
errors.push(error.message || String(error));
|
||||
if (config.autoCleanup && subsite.slug) {
|
||||
setTimeout(() => {
|
||||
deleteSubsite(config, subsite.slug).catch(() => {});
|
||||
}, config.cleanupDelayMs);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
session_id: sessionId,
|
||||
subsite_url: subsite.url,
|
||||
test_results: { mode: 'cli', cli_tests: { passed: 0, failed: 1, results: [] } },
|
||||
installed_plugins: [],
|
||||
errors,
|
||||
warnings,
|
||||
duration: Date.now() - start,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { runTest, getConfig: () => getExternalTestingConfig() };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createExternalWpTester,
|
||||
getExternalTestingConfig,
|
||||
};
|
||||
Reference in New Issue
Block a user