Release Twitch VOD Manager 1.0.18

Harden update, system-check, queue, cutter, streamer and shutdown state transitions.

Add multi-user installer recovery, secret-safe config migration, provider fallback handling, managed-tool validation and real media export coverage.

Refresh the English public documentation, release notes and 1.0.18 product screenshot.
This commit is contained in:
Sucukdeluxe
2026-08-13 22:59:15 +02:00
parent f37142401f
commit 9f1b052afd
124 changed files with 21119 additions and 7777 deletions
+704
View File
@@ -0,0 +1,704 @@
const assert = require('node:assert/strict');
const nodeCrypto = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const test = require('node:test');
const {
assertOwnedPath,
buildStreamlinkArguments,
compareVersions,
parseGateMode,
parseLatestYaml,
readLiveConfiguration,
redactDiagnostic,
sanitizeChildEnvironment,
validateMediaProbe,
validateDownloadedReleaseArtifact,
validateProductionRelease,
validateUpdateCacheRecord,
validateTwitchToken
} = require('./smoke-test-live-integration-contract');
const {
checkPackagedUpdate,
closeElectronApp,
createUpdaterDownloadReadySummary,
getPackagedVersion,
inspectProductionRelease,
requestProductTwitchToken,
runWithElectronAppCleanup,
runWithOwnedRoot,
startUpdaterDownload,
verifyProductTwitchProviderFallbacks
} = require('./smoke-test-live-integration');
const PACKAGE_VERSION = require('../package.json').version;
const TWITCH_ENVIRONMENT = {
TWITCH_VOD_MANAGER_LIVE_INTEGRATION: '1',
TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_ID: 'client-id-value',
TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_SECRET: 'client-secret-value',
TWITCH_VOD_MANAGER_LIVE_TWITCH_LOGIN: 'example_channel',
TWITCH_VOD_MANAGER_LIVE_TWITCH_VOD_ID: '1234567890'
};
const UPDATE_SHA512 = Buffer.alloc(64, 7).toString('base64');
const UPDATE_COMMIT_SHA = 'b'.repeat(40);
function releaseYaml(version = PACKAGE_VERSION) {
return [
`version: ${version}`,
'files:',
` - url: Twitch-VOD-Manager-Setup-${version}.exe`,
` sha512: ${UPDATE_SHA512}`,
' size: 120000000',
`path: Twitch-VOD-Manager-Setup-${version}.exe`,
`sha512: ${UPDATE_SHA512}`,
"releaseDate: '2026-08-13T10:00:00.000Z'"
].join('\n');
}
function releaseInspectionDependencies(commitSha = UPDATE_COMMIT_SHA) {
const calls = [];
return {
calls,
requestJson: async (url) => {
calls.push(String(url));
if (String(url).endsWith('/releases/latest')) {
return {
assets: [
{ name: `Twitch-VOD-Manager-Setup-${PACKAGE_VERSION}.exe`, size: 120000000 },
{ name: 'latest.yml', size: 1024 }
],
draft: false,
prerelease: false,
tag_name: `v${PACKAGE_VERSION}`
};
}
if (String(url).endsWith(`/commits/v${PACKAGE_VERSION}`)) return { sha: commitSha };
throw new Error(`Unexpected JSON request: ${url}`);
},
requestLatestYaml: async (url) => {
calls.push(String(url));
return releaseYaml();
}
};
}
function updaterEnvironment(overrides = {}) {
return {
GITHUB_REF: `refs/tags/v${PACKAGE_VERSION}`,
GITHUB_SHA: UPDATE_COMMIT_SHA,
TWITCH_VOD_MANAGER_LIVE_INTEGRATION: '1',
TWITCH_VOD_MANAGER_LIVE_SOURCE_VERSION: '0.0.1',
TWITCH_VOD_MANAGER_LIVE_SOURCE_SHA256: 'a'.repeat(64),
TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA: UPDATE_COMMIT_SHA,
TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION: PACKAGE_VERSION,
TWITCH_VOD_MANAGER_LIVE_UPDATE_SHA512: UPDATE_SHA512,
...overrides
};
}
test('refuses every live mode until the explicit opt-in is set', () => {
assert.throws(
() => readLiveConfiguration('twitch', { ...TWITCH_ENVIRONMENT, TWITCH_VOD_MANAGER_LIVE_INTEGRATION: undefined }),
/TWITCH_VOD_MANAGER_LIVE_INTEGRATION=1/
);
assert.throws(
() => readLiveConfiguration('updater', {}),
/TWITCH_VOD_MANAGER_LIVE_INTEGRATION=1/
);
});
test('keeps Twitch credentials scoped to the Twitch gate while updater requires explicit release pins', () => {
const twitch = readLiveConfiguration('twitch', TWITCH_ENVIRONMENT);
assert.equal(twitch.twitch.clientId, 'client-id-value');
assert.equal(twitch.twitch.clientSecret, 'client-secret-value');
assert.equal(twitch.twitch.login, 'example_channel');
assert.equal(twitch.twitch.vodId, '1234567890');
assert.throws(
() => readLiveConfiguration('updater', { TWITCH_VOD_MANAGER_LIVE_INTEGRATION: '1' }),
/TWITCH_VOD_MANAGER_LIVE_SOURCE_VERSION/
);
const updater = readLiveConfiguration('updater', updaterEnvironment());
assert.equal(updater.twitch, undefined);
assert.equal(updater.updater.sourceVersion, '0.0.1');
assert.equal(updater.updater.sourceSha256, 'a'.repeat(64));
assert.equal(updater.updater.expectedVersion, PACKAGE_VERSION);
assert.equal(updater.updater.expectedSha512, UPDATE_SHA512);
assert.equal(updater.updater.expectedCommitSha, UPDATE_COMMIT_SHA);
assert.equal(updater.updater.packagedAppPath, undefined);
const override = readLiveConfiguration('updater', updaterEnvironment({
TWITCH_VOD_MANAGER_LIVE_PACKAGED_APP_PATH: 'C:\\fixtures\\Twitch VOD Manager.exe',
}));
assert.equal(override.updater.packagedAppPath, 'C:\\fixtures\\Twitch VOD Manager.exe');
assert.equal(override.updater.sourceVersion, '0.0.1');
assert.equal(override.updater.expectedVersion, PACKAGE_VERSION);
assert.equal(override.updater.expectedSha512, UPDATE_SHA512);
});
test('rejects every missing updater pin and binds the target to package version and release tag', () => {
for (const name of [
'TWITCH_VOD_MANAGER_LIVE_SOURCE_VERSION',
'TWITCH_VOD_MANAGER_LIVE_SOURCE_SHA256',
'TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA',
'TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION',
'TWITCH_VOD_MANAGER_LIVE_UPDATE_SHA512'
]) {
assert.throws(
() => readLiveConfiguration('updater', updaterEnvironment({ [name]: undefined })),
(error) => error instanceof Error && error.message.includes(name)
);
}
assert.throws(
() => readLiveConfiguration('updater', updaterEnvironment({ TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION: '99.0.0', GITHUB_REF: 'refs/tags/v99.0.0' })),
/package version/
);
assert.throws(
() => readLiveConfiguration('updater', updaterEnvironment({ GITHUB_REF: 'refs/heads/main' })),
/release tag/
);
assert.throws(
() => readLiveConfiguration('updater', updaterEnvironment({ TWITCH_VOD_MANAGER_LIVE_UPDATE_COMMIT_SHA: 'abc' })),
/UPDATE_COMMIT_SHA/
);
assert.throws(
() => readLiveConfiguration('updater', updaterEnvironment({ GITHUB_SHA: 'c'.repeat(40) })),
/current workflow commit/
);
for (const version of ['1.0.18-alpha', '1.0.18+build', '01.0.18', '1.00.18', '1.0.18.0', '1234567890.0.0']) {
assert.throws(
() => readLiveConfiguration('updater', updaterEnvironment({
GITHUB_REF: `refs/tags/v${version}`,
TWITCH_VOD_MANAGER_LIVE_UPDATE_VERSION: version
})),
/numeric release version/
);
}
});
test('obtains the live credential through the built Twitch token product path', async () => {
const product = require('../dist/main/twitch');
assert.equal(requestProductTwitchToken.length, 1);
assert.equal(typeof product.TwitchAppTokenService, 'function');
assert.equal(typeof product.requestTwitchAppAccessToken, 'function');
let requests = 0;
const request = async (url, options, timeoutMs) => {
requests += 1;
assert.equal(url.origin + url.pathname, 'https://id.twitch.tv/oauth2/token');
assert.deepEqual(Object.fromEntries(url.searchParams), {
client_id: 'client-id-value',
client_secret: 'client-secret-value',
grant_type: 'client_credentials'
});
assert.deepEqual(options, { method: 'POST' });
assert.equal(timeoutMs, 30000);
return { access_token: 'live-product-token', expires_in: 3600, token_type: 'bearer' };
};
const token = await requestProductTwitchToken(
{ clientId: 'client-id-value', clientSecret: 'client-secret-value' },
request
);
assert.deepEqual(token, {
accessToken: 'live-product-token',
tokenPayload: { access_token: 'live-product-token', expires_in: 3600, token_type: 'bearer' }
});
assert.equal(requests, 1);
});
test('runs Helix, public GQL, and offline last-good through the built Twitch provider product paths', async () => {
const calls = [];
const client = {
async get(url, config) {
calls.push({ config, method: 'GET', url });
assert.deepEqual(config.headers, {
'Client-ID': 'client-id-value',
Authorization: 'Bearer live-product-token'
});
assert.equal(config.timeout, 30000);
if (url === 'https://api.twitch.tv/helix/users') {
assert.deepEqual(config.params, { login: 'example_channel' });
return {
data: {
data: [{
broadcaster_type: 'partner',
description: 'Example broadcaster',
display_name: 'Example Channel',
id: '42',
login: 'example_channel',
profile_image_url: 'https://static-cdn.example.test/profile.png'
}]
}
};
}
assert.equal(url, 'https://api.twitch.tv/helix/videos');
assert.deepEqual(config.params, { first: 100, type: 'archive', user_id: '42' });
return {
data: {
data: [{
created_at: '2026-08-13T10:00:00Z',
duration: '2h3m4s',
id: '1234567890',
stream_id: 'stream-1',
thumbnail_url: 'https://static-cdn.example.test/vod.jpg',
title: 'A VOD',
url: 'https://www.twitch.tv/videos/1234567890',
user_login: 'example_channel',
view_count: 123
}],
pagination: {}
}
};
},
async post(url, body, config) {
calls.push({ body, config, method: 'POST', url });
if (config.timeout !== 30000) {
const error = new Error('Public product wrapper did not own its request signature');
error.response = { status: 400 };
throw error;
}
assert.equal(url, 'https://gql.twitch.tv/gql');
assert.equal(typeof body.query, 'string');
assert.deepEqual(body.variables, { first: 100, login: 'example_channel' });
assert.equal(config.headers['Content-Type'], 'application/json');
assert.equal(typeof config.headers['Client-ID'], 'string');
assert.notEqual(config.headers['Client-ID'], 'client-id-value');
return {
data: {
data: {
user: {
videos: {
edges: [{
node: {
id: '1234567890',
lengthSeconds: 7384,
previewThumbnailURL: 'https://static-cdn.example.test/vod.jpg',
publishedAt: '2026-08-13T10:00:00Z',
title: 'A VOD',
viewCount: 123
}
}]
}
}
}
}
};
}
};
const originalFetch = global.fetch;
global.fetch = async () => { throw new Error('provider contract bypassed its injected transport'); };
try {
const result = await verifyProductTwitchProviderFallbacks({
clientId: 'client-id-value',
login: 'example_channel',
vodId: '1234567890'
}, 'live-product-token', client);
assert.deepEqual(result, {
helix: { duration: '2h3m4s', source: 'helix', userId: '42', vodId: '1234567890' },
lastGood: { restoredFrom: 'helix', restoredVodId: '1234567890', source: 'last-good', stale: true },
public: { duration: '2h3m4s', login: 'example_channel', source: 'public', vodId: '1234567890' }
});
assert.deepEqual(calls.map((call) => `${call.method} ${call.url}`), [
'GET https://api.twitch.tv/helix/users',
'POST https://gql.twitch.tv/gql',
'GET https://api.twitch.tv/helix/videos'
]);
} finally {
global.fetch = originalFetch;
}
});
test('bounds the packaged version product call', async () => {
const window = { evaluate: async () => await new Promise(() => {}) };
await assert.rejects(getPackagedVersion(window, 10), /version query timed out/i);
});
test('bounds the packaged update-check product call independently', async () => {
const window = { evaluate: async () => await new Promise(() => {}) };
await assert.rejects(checkPackagedUpdate(window, 10), /update check timed out/i);
});
test('closes the packaged app gracefully without a hard kill', async () => {
const calls = [];
const process = { exitCode: null };
const app = {
close: async () => {
calls.push('close');
process.exitCode = 0;
}
};
await closeElectronApp(app, process, {
terminate: () => calls.push('terminate'),
timeoutMs: 10,
waitForExit: async () => calls.push('wait')
});
assert.deepEqual(calls, ['close']);
});
test('hard-kills the packaged app after graceful close times out', async () => {
const calls = [];
const process = { exitCode: null };
const app = { close: async () => { calls.push('close'); return await new Promise(() => {}); } };
await closeElectronApp(app, process, {
terminate: () => { calls.push('terminate'); process.exitCode = 1; },
timeoutMs: 10,
waitForExit: async () => calls.push('wait')
});
assert.deepEqual(calls, ['close', 'terminate', 'wait']);
});
test('hard-kills the packaged app after graceful close rejects', async () => {
const calls = [];
const process = { exitCode: null };
const app = { close: async () => { calls.push('close'); throw new Error('close rejected'); } };
await closeElectronApp(app, process, {
terminate: () => { calls.push('terminate'); process.exitCode = 1; },
timeoutMs: 10,
waitForExit: async () => calls.push('wait')
});
assert.deepEqual(calls, ['close', 'terminate', 'wait']);
});
test('closes the packaged app when the updater body fails', async () => {
const calls = [];
const process = { exitCode: null };
const lifecycle = {
electronApp: { close: async () => { calls.push('close'); process.exitCode = 0; } },
electronProcess: process
};
await assert.rejects(
runWithElectronAppCleanup(lifecycle, async () => { throw new Error('updater body failed'); }, { timeoutMs: 10 }),
/updater body failed/
);
assert.deepEqual(calls, ['close']);
});
test('opens the update popover before clicking its download action', async () => {
const calls = [];
const locators = {
'#workspaceUpdateButton': { hover: async () => { calls.push('hover'); } },
'#updateButton': {
waitFor: async (options) => { calls.push(`wait:${options.state}`); },
click: async () => { calls.push('click'); }
}
};
await startUpdaterDownload({ locator: (selector) => locators[selector] });
assert.deepEqual(calls, ['hover', 'wait:visible', 'click']);
});
test('rejects malformed public fixture identities before making network requests', () => {
assert.throws(
() => readLiveConfiguration('twitch', { ...TWITCH_ENVIRONMENT, TWITCH_VOD_MANAGER_LIVE_TWITCH_LOGIN: 'https://twitch.tv/name' }),
/LIVE_TWITCH_LOGIN/
);
assert.throws(
() => readLiveConfiguration('twitch', { ...TWITCH_ENVIRONMENT, TWITCH_VOD_MANAGER_LIVE_TWITCH_VOD_ID: '../123' }),
/LIVE_TWITCH_VOD_ID/
);
});
test('redacts credentials and access tokens from nested external errors', () => {
const diagnostic = redactDiagnostic(
new Error('request failed for client-id-value client-secret-value bearer-token-value'),
['client-id-value', 'client-secret-value', 'bearer-token-value']
);
assert.equal(diagnostic.includes('client-id-value'), false);
assert.equal(diagnostic.includes('client-secret-value'), false);
assert.equal(diagnostic.includes('bearer-token-value'), false);
assert.match(diagnostic, /\[REDACTED\]/);
});
test('validates a real client-credentials token contract without exposing the token', () => {
const result = validateTwitchToken(
{ access_token: 'bearer-token-value', expires_in: 3600, token_type: 'bearer' },
{ client_id: 'client-id-value', expires_in: 3590 },
'client-id-value'
);
assert.deepEqual(result, { expiresInSeconds: 3590, tokenType: 'bearer' });
assert.throws(
() => validateTwitchToken(
{ access_token: 'bearer-token-value', expires_in: 3600, token_type: 'bearer' },
{ client_id: 'other-client', expires_in: 3590 },
'client-id-value'
),
/client id/
);
});
test('builds a bounded lowest-quality Streamlink download command', () => {
const output = path.join('C:\\runner\\temp', 'sample.ts');
assert.deepEqual(buildStreamlinkArguments('1234567890', output, 8), [
'--no-config',
'--no-plugin-cache',
'--no-plugin-sideloading',
'--http-timeout',
'20',
'--stream-timeout',
'30',
'--stream-segment-attempts',
'2',
'--stream-segment-timeout',
'20',
'--stream-segmented-duration',
'8',
'--output',
output,
'https://www.twitch.tv/videos/1234567890',
'worst'
]);
assert.throws(() => buildStreamlinkArguments('abc', output, 8), /VOD id/);
assert.throws(() => buildStreamlinkArguments('1234567890', output, 61), /duration/);
});
test('accepts only a bounded ffprobe-confirmed video artifact', () => {
const result = validateMediaProbe({
format: { duration: '8.25', size: '1048576' },
streams: [{ codec_type: 'video', codec_name: 'h264' }, { codec_type: 'audio', codec_name: 'aac' }]
}, 1048576);
assert.deepEqual(result, { bytes: 1048576, codec: 'h264', durationSeconds: 8.25 });
assert.throws(
() => validateMediaProbe({ format: { duration: '0.4', size: '40' }, streams: [{ codec_type: 'audio', codec_name: 'aac' }] }, 40),
/video stream/
);
assert.throws(
() => validateMediaProbe({ format: { duration: '8', size: String(40 * 1024 * 1024) }, streams: [{ codec_type: 'video', codec_name: 'h264' }] }, 40 * 1024 * 1024),
/32 MiB/
);
});
test('parses and pins the production GitHub release feed metadata', () => {
const yaml = [
'version: 1.0.18',
'files:',
' - url: Twitch-VOD-Manager-Setup-1.0.18.exe',
` sha512: ${UPDATE_SHA512}`,
' size: 120000000',
'path: Twitch-VOD-Manager-Setup-1.0.18.exe',
`sha512: ${UPDATE_SHA512}`,
"releaseDate: '2026-08-13T10:00:00.000Z'"
].join('\n');
const metadata = parseLatestYaml(yaml);
const result = validateProductionRelease(metadata, {
expectedVersion: '1.0.18',
expectedSha512: UPDATE_SHA512,
latestTag: 'v1.0.18'
});
assert.deepEqual(result, {
artifactName: 'Twitch-VOD-Manager-Setup-1.0.18.exe',
artifactSize: 120000000,
feedUrl: 'https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download/v1.0.18/',
version: '1.0.18'
});
assert.throws(
() => validateProductionRelease({ ...metadata, path: '../outside.exe' }, {
expectedVersion: '1.0.18',
expectedSha512: UPDATE_SHA512,
latestTag: 'v1.0.18'
}),
/artifact path/
);
assert.throws(
() => validateProductionRelease(metadata, {
expectedVersion: '1.0.19',
expectedSha512: UPDATE_SHA512,
latestTag: 'v1.0.18'
}),
/version/
);
});
test('resolves the public release tag to the pinned workflow commit', async () => {
const dependencies = releaseInspectionDependencies();
const originalFetch = global.fetch;
global.fetch = async () => { throw new Error('inspectProductionRelease bypassed its injected transport'); };
try {
const result = await inspectProductionRelease({
expectedCommitSha: UPDATE_COMMIT_SHA,
expectedSha512: UPDATE_SHA512,
expectedVersion: PACKAGE_VERSION
}, dependencies);
assert.equal(result.commitSha, UPDATE_COMMIT_SHA);
assert.deepEqual(dependencies.calls, [
'https://api.github.com/repos/Sucukdeluxe/Twitch-VOD-Manager/releases/latest',
`https://api.github.com/repos/Sucukdeluxe/Twitch-VOD-Manager/commits/v${PACKAGE_VERSION}`,
`https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download/v${PACKAGE_VERSION}/latest.yml`
]);
} finally {
global.fetch = originalFetch;
}
});
test('rejects a public release tag that resolves to another commit', async () => {
const dependencies = releaseInspectionDependencies('c'.repeat(40));
const originalFetch = global.fetch;
global.fetch = async () => { throw new Error('inspectProductionRelease bypassed its injected transport'); };
try {
await assert.rejects(
inspectProductionRelease({
expectedCommitSha: UPDATE_COMMIT_SHA,
expectedSha512: UPDATE_SHA512,
expectedVersion: PACKAGE_VERSION
}, dependencies),
/release tag commit/i
);
} finally {
global.fetch = originalFetch;
}
});
test('describes updater success as download-ready without claiming installation', () => {
const result = createUpdaterDownloadReadySummary({
artifact: { bytes: 120000000, sha512Verified: true },
downloadedVersion: PACKAGE_VERSION,
progressEvents: 4,
release: {
artifactName: `Twitch-VOD-Manager-Setup-${PACKAGE_VERSION}.exe`,
commitSha: UPDATE_COMMIT_SHA,
feedUrl: `https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download/v${PACKAGE_VERSION}/`
},
source: { sourceInstallerVerified: true },
sourceVersion: '0.0.1'
});
assert.deepEqual(result, {
artifact: {
bytes: 120000000,
name: `Twitch-VOD-Manager-Setup-${PACKAGE_VERSION}.exe`,
sha512Verified: true
},
feed: `https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download/v${PACKAGE_VERSION}/`,
scope: 'download-ready',
source: { installerDigestVerified: true, version: '0.0.1' },
target: {
commitSha: UPDATE_COMMIT_SHA,
downloadReady: true,
progressEvents: 4,
version: PACKAGE_VERSION
}
});
});
test('independently hashes the downloaded PE artifact from the isolated updater cache', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tvm-live-update-artifact-'));
try {
const artifact = path.join(root, 'Twitch-VOD-Manager-Setup-1.0.18.exe');
const contents = Buffer.alloc(1024 * 1024, 9);
contents[0] = 0x4d;
contents[1] = 0x5a;
fs.writeFileSync(artifact, contents);
const expectedSha512 = nodeCrypto.createHash('sha512').update(contents).digest('base64');
const result = await validateDownloadedReleaseArtifact(artifact, {
artifactName: path.basename(artifact),
artifactSize: contents.length,
expectedSha512
});
assert.deepEqual(result, { bytes: contents.length, sha512Verified: true });
contents[0] = 0;
fs.writeFileSync(artifact, contents);
await assert.rejects(
validateDownloadedReleaseArtifact(artifact, {
artifactName: path.basename(artifact),
artifactSize: contents.length,
expectedSha512: nodeCrypto.createHash('sha512').update(contents).digest('base64')
}),
/Windows executable/
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('requires the packaged source to be older than the pinned release', () => {
assert.equal(compareVersions('1.0.17', '1.0.18'), -1);
assert.equal(compareVersions('1.0.18', '1.0.18'), 0);
assert.equal(compareVersions('1.0.19', '1.0.18'), 1);
for (const invalid of ['nightly', 'v1.0.18', '1.0.18-alpha', '1.0.18+build', '01.0.18', '1.00.18', '1.0.18.0', '1234567890.0.0']) {
assert.throws(() => compareVersions(invalid, '1.0.18'), /invalid/);
}
});
test('binds the updater cache record to the pinned artifact and digest', () => {
assert.deepEqual(validateUpdateCacheRecord({
fileName: 'Twitch-VOD-Manager-Setup-1.0.17.exe',
sha512: 'MkTghoBxIOnhP77tHV8szr8S1dbhItJId0atllZjWVrPwNLcvwnCyYjoUVEWV1czTqb5I+CUvqLiIjaamwglgw==',
isAdminRightsRequired: false
}, {
artifactName: 'Twitch-VOD-Manager-Setup-1.0.17.exe',
expectedSha512: 'MkTghoBxIOnhP77tHV8szr8S1dbhItJId0atllZjWVrPwNLcvwnCyYjoUVEWV1czTqb5I+CUvqLiIjaamwglgw=='
}), {
fileName: 'Twitch-VOD-Manager-Setup-1.0.17.exe',
sha512Verified: true
});
assert.throws(() => validateUpdateCacheRecord({
fileName: '..\\outside.exe',
sha512: UPDATE_SHA512
}, {
artifactName: 'Twitch-VOD-Manager-Setup-1.0.17.exe',
expectedSha512: UPDATE_SHA512
}), /file name/);
});
test('refuses cleanup at or outside the owned temporary root', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tvm-live-contract-'));
try {
const child = path.join(root, 'owned');
fs.mkdirSync(child);
assert.equal(assertOwnedPath(child, root), path.resolve(child));
assert.throws(() => assertOwnedPath(root, root), /outside/);
assert.throws(() => assertOwnedPath(path.dirname(root), root), /outside/);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('removes the owned runner root after a successful operation', async () => {
let ownedRoot = '';
const result = await runWithOwnedRoot('tvm-live-success-', async (context) => {
ownedRoot = context.ownedRoot;
assert.equal(fs.existsSync(ownedRoot), true);
fs.writeFileSync(path.join(ownedRoot, 'artifact.tmp'), 'owned');
return 'completed';
});
assert.equal(result, 'completed');
assert.equal(fs.existsSync(ownedRoot), false);
});
test('removes the owned runner root when the operation fails', async () => {
let ownedRoot = '';
await assert.rejects(
runWithOwnedRoot('tvm-live-failure-', async (context) => {
ownedRoot = context.ownedRoot;
fs.writeFileSync(path.join(ownedRoot, 'artifact.tmp'), 'owned');
throw new Error('runner failed');
}),
/runner failed/
);
assert.equal(fs.existsSync(ownedRoot), false);
});
test('accepts only the three explicit execution modes', () => {
assert.equal(parseGateMode([]), 'all');
assert.equal(parseGateMode(['twitch']), 'twitch');
assert.equal(parseGateMode(['updater']), 'updater');
assert.throws(() => parseGateMode(['local-feed']), /mode/);
});
test('removes live credentials and injection variables from the packaged app environment', () => {
const result = sanitizeChildEnvironment({
PATH: 'C:\\Windows',
SystemRoot: 'C:\\Windows',
GITHUB_TOKEN: 'github-secret',
HTTP_PROXY: ['http://user', ':', 'password', '@', 'proxy.example.test'].join(''),
NODE_OPTIONS: '--require malicious.js',
TWITCH_VOD_MANAGER_LIVE_TWITCH_CLIENT_SECRET: 'twitch-secret',
SAFE_SETTING: 'kept'
}, { LOCALAPPDATA: 'C:\\isolated' });
assert.deepEqual(result, {
LOCALAPPDATA: 'C:\\isolated',
PATH: 'C:\\Windows',
SYSTEMROOT: 'C:\\Windows'
});
});