Files
Twitch-VOD-Manager/scripts/smoke-test-e2e-isolation-contract.js
T
Sucukdeluxe 47be523b41 feat(security): encrypt secrets and centralize app state
Store Twitch and Discord secrets as versioned safeStorage ciphertext behind explicit trusted IPC. Migrate legacy JSON transactionally into authoritative SQLite config and ordered queue repositories with rollback-safe markers and sanitized backups. Redact config exports and update renderer and release-harness contracts for secret-free config responses.
2026-08-11 23:44:20 +02:00

175 lines
6.3 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const HELPER_FILE = path.join(__dirname, 'e2e-test-environment.js');
const SMOKE_FILES = [
'scripts/smoke-test.js',
'scripts/smoke-test-template-guide.js',
'scripts/smoke-test-full.js',
'scripts/smoke-test-settings-autosave.js',
'scripts/smoke-test-workspace-ui.js'
];
function inspectSources() {
const failures = [];
if (!fs.existsSync(HELPER_FILE)) {
failures.push('Missing scripts/e2e-test-environment.js');
}
for (const relativePath of SMOKE_FILES) {
const source = fs.readFileSync(path.join(ROOT, relativePath), 'utf8');
const requiredPatterns = [
["require('./e2e-test-environment')", 'shared isolation helper import'],
['createE2eEnvironment(', 'isolated test root creation'],
['getElectronLaunchOptions(', 'isolated Electron launch options'],
['verifyE2eIsolation(', 'runtime isolation verification'],
['installOfflineFixtures(', 'offline IPC fixtures'],
['cleanupE2eEnvironment(', 'guaranteed isolated root cleanup']
];
for (const [pattern, label] of requiredPatterns) {
if (!source.includes(pattern)) {
failures.push(`${relativePath}: missing ${label}`);
}
}
if (/process\.exit\s*\(/.test(source)) {
failures.push(`${relativePath}: process.exit bypasses cleanup`);
}
if (/electron\.launch\s*\(\s*\{/.test(source)) {
failures.push(`${relativePath}: raw electron.launch options bypass shared isolation`);
}
}
const fullSource = fs.readFileSync(path.join(ROOT, 'scripts/smoke-test-full.js'), 'utf8');
const forbiddenFullPatterns = [
['backupFile(', 'real-file backup path'],
['restoreFile(', 'real-file restore path'],
["path.join(process.cwd(), 'tmp_e2e_full')", 'project-local media path'],
["process.env.PROGRAMDATA || 'C:\\\\ProgramData'", 'ambient ProgramData path']
];
for (const [pattern, label] of forbiddenFullPatterns) {
if (fullSource.includes(pattern)) {
failures.push(`scripts/smoke-test-full.js: contains ${label}`);
}
}
return failures;
}
function inspectHelper() {
if (!fs.existsSync(HELPER_FILE)) {
return [];
}
const failures = [];
const {
createE2eEnvironment,
getElectronLaunchOptions,
readE2eConfig,
cleanupE2eEnvironment
} = require(HELPER_FILE);
const environment = createE2eEnvironment('isolation-contract');
try {
const expectedDirectories = [
environment.rootDir,
environment.programDataDir,
environment.userDataDir,
environment.downloadsDir,
environment.appDataDir
];
for (const directory of expectedDirectories) {
if (!fs.statSync(directory).isDirectory()) {
failures.push(`Helper did not create directory: ${directory}`);
}
}
const config = JSON.parse(fs.readFileSync(environment.configFile, 'utf8'));
const queue = JSON.parse(fs.readFileSync(environment.queueFile, 'utf8'));
const launch = getElectronLaunchOptions(environment);
if (path.resolve(config.download_path) !== path.resolve(environment.downloadsDir)) {
failures.push('Seed config download_path is outside the isolated downloads directory');
}
if (!Array.isArray(config.streamers) || config.streamers.length !== 0) {
failures.push('Seed config contains streamers');
}
if (!Array.isArray(config.auto_record_streamers) || config.auto_record_streamers.length !== 0) {
failures.push('Seed config enables auto recording');
}
if (!Array.isArray(config.auto_vod_download_streamers) || config.auto_vod_download_streamers.length !== 0) {
failures.push('Seed config enables automatic VOD downloads');
}
if (config.auto_resume_queue_on_startup !== false || config.auto_resume_live_recording !== false) {
failures.push('Seed config enables automatic resume behavior');
}
if (config.auto_cleanup_enabled !== false) {
failures.push('Seed config enables automatic cleanup');
}
if ('client_secret' in config || 'discord_webhook_url' in config) {
failures.push('Seed config contains secret fields');
}
const Database = require('better-sqlite3');
const database = new Database(path.join(environment.appDataDir, 'app.db'));
database.exec('CREATE TABLE config_kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)');
database.prepare('INSERT INTO config_kv(key, value, updated_at) VALUES (?, ?, ?)').run('language', JSON.stringify('sqlite'), 1);
database.close();
if (readE2eConfig(environment).language !== 'sqlite') {
failures.push('Helper did not read authoritative SQLite config');
}
if (!Array.isArray(queue) || queue.length !== 0) {
failures.push('Seed queue is not empty');
}
if (path.resolve(launch.env.PROGRAMDATA) !== path.resolve(environment.programDataDir)) {
failures.push('Electron launch options do not isolate PROGRAMDATA');
}
if (launch.args[0] !== `--user-data-dir=${environment.userDataDir}` || launch.args.at(-1) !== '.') {
failures.push('Electron launch options do not place the isolated userData switch before the app path');
}
if (!String(launch.args[1] || '').startsWith('--proxy-server=http://127.0.0.1:')) {
failures.push('Electron launch options do not install the Chromium offline proxy guard');
}
if (launch.env.HTTP_PROXY !== launch.env.HTTPS_PROXY || launch.env.HTTP_PROXY !== launch.env.ALL_PROXY) {
failures.push('Electron launch options do not install consistent Node proxy guards');
}
if (!String(launch.env.HTTP_PROXY || '').startsWith('http://127.0.0.1:')) {
failures.push('Electron launch options do not route Node HTTP clients to an offline loopback proxy');
}
if (launch.env.NO_PROXY !== '') {
failures.push('Electron launch options allow proxy bypasses');
}
} finally {
cleanupE2eEnvironment(environment);
}
if (fs.existsSync(environment.rootDir)) {
failures.push('Helper cleanup left the isolated root on disk');
}
return failures;
}
function run() {
const failures = [...inspectSources(), ...inspectHelper()];
const summary = {
files: SMOKE_FILES,
failures
};
console.log(JSON.stringify(summary, null, 2));
process.exitCode = failures.length === 0 ? 0 : 1;
}
try {
run();
} catch (error) {
console.error(error);
process.exitCode = 1;
}