Files
SubMiner/src/main/runtime/linux-runtime-plugin-assets.test.ts
T

400 lines
15 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
ensureLinuxRuntimePluginAssets,
resolveManagedLinuxRuntimePluginPaths,
} from './linux-runtime-plugin-assets';
async function withTempDir<T>(fn: (dir: string) => Promise<T> | T): Promise<T> {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-linux-plugin-assets-test-'));
try {
return await fn(dir);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}
async function withProcessResourcesPath<T>(
resourcesPath: string,
fn: () => Promise<T> | T,
): Promise<T> {
const processWithResources = process as NodeJS.Process & { resourcesPath?: string };
const previousResourcesPath = processWithResources.resourcesPath;
processWithResources.resourcesPath = resourcesPath;
try {
return await fn();
} finally {
if (previousResourcesPath === undefined) {
Reflect.deleteProperty(processWithResources, 'resourcesPath');
} else {
processWithResources.resourcesPath = previousResourcesPath;
}
}
}
test('resolveManagedLinuxRuntimePluginPaths resolves XDG data target paths', () => {
const resolved = resolveManagedLinuxRuntimePluginPaths({
homeDir: '/home/tester',
xdgDataHome: '/tmp/xdg-data',
});
assert.deepEqual(resolved, {
dataDir: '/tmp/xdg-data/SubMiner',
rootDir: '/tmp/xdg-data/SubMiner/plugin',
pluginDir: '/tmp/xdg-data/SubMiner/plugin/subminer',
pluginEntrypointPath: '/tmp/xdg-data/SubMiner/plugin/subminer/main.lua',
pluginConfigPath: '/tmp/xdg-data/SubMiner/plugin/subminer.conf',
themePath: '/tmp/xdg-data/SubMiner/themes/subminer.rasi',
});
});
test('resolveManagedLinuxRuntimePluginPaths treats blank XDG data homes as unset', () => {
const previousXdgDataHome = process.env.XDG_DATA_HOME;
try {
delete process.env.XDG_DATA_HOME;
const emptyOption = resolveManagedLinuxRuntimePluginPaths({
homeDir: '/home/tester',
xdgDataHome: '',
});
assert.equal(emptyOption.dataDir, path.join('/home/tester', '.local', 'share', 'SubMiner'));
process.env.XDG_DATA_HOME = ' ';
const blankEnv = resolveManagedLinuxRuntimePluginPaths({
homeDir: '/home/tester',
});
assert.equal(blankEnv.dataDir, path.join('/home/tester', '.local', 'share', 'SubMiner'));
} finally {
if (previousXdgDataHome === undefined) {
delete process.env.XDG_DATA_HOME;
} else {
process.env.XDG_DATA_HOME = previousXdgDataHome;
}
}
});
test('ensureLinuxRuntimePluginAssets installs managed plugin dir, config, and rofi theme when missing', async () => {
await withTempDir(async (tempDir) => {
const sourceRoot = path.join(tempDir, 'source', 'plugin');
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
const targetRoot = path.join(tempDir, 'xdg-data', 'SubMiner', 'plugin');
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- plugin\n');
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'configured=true\n');
fs.writeFileSync(themeSourcePath, '/* theme */\n');
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome: path.join(tempDir, 'xdg-data'),
resolveBundledAssets: () => ({
pluginDirSource: path.join(sourceRoot, 'subminer'),
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
themeSourcePath,
}),
});
assert.deepEqual(result, {
ok: true,
status: 'installed',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
'-- plugin\n',
);
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
'configured=true\n',
);
assert.equal(
fs.readFileSync(
path.join(tempDir, 'xdg-data', 'SubMiner', 'themes', 'subminer.rasi'),
'utf8',
),
'/* theme */\n',
);
});
});
test('ensureLinuxRuntimePluginAssets installs managed theme when plugin assets already exist', async () => {
await withTempDir(async (tempDir) => {
const sourceRoot = path.join(tempDir, 'source', 'plugin');
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- new plugin\n');
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'new=true\n');
fs.writeFileSync(themeSourcePath, '/* theme */\n');
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing plugin\n');
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
resolveBundledAssets: () => ({
pluginDirSource: path.join(sourceRoot, 'subminer'),
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
themeSourcePath,
}),
});
assert.deepEqual(result, {
ok: true,
status: 'installed',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
'-- existing plugin\n',
);
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
'configured=true\n',
);
assert.equal(
fs.readFileSync(path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'), 'utf8'),
'/* theme */\n',
);
});
});
test('ensureLinuxRuntimePluginAssets installs managed theme without resolving plugin sources when plugin assets already exist', async () => {
await withTempDir(async (tempDir) => {
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
fs.writeFileSync(themeSourcePath, '/* theme */\n');
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing plugin\n');
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
resolveBundledAssets: () => ({
themeSourcePath,
}),
});
assert.deepEqual(result, {
ok: true,
status: 'installed',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
assert.equal(
fs.readFileSync(path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'), 'utf8'),
'/* theme */\n',
);
});
});
test('ensureLinuxRuntimePluginAssets default resolver can recover a missing theme when plugin sources are unavailable', async () => {
await withTempDir(async (tempDir) => {
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing plugin\n');
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
const result = await withProcessResourcesPath(process.cwd(), () =>
ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
existsSync: (candidate) => {
if (
!candidate.startsWith(tempDir) &&
(candidate.endsWith(path.join('plugin', 'subminer')) ||
candidate.endsWith(path.join('plugin', 'subminer.conf')) ||
candidate.endsWith(path.join('plugin', 'subminer', 'main.lua')))
) {
return false;
}
return fs.existsSync(candidate);
},
}),
);
assert.deepEqual(result, {
ok: true,
status: 'installed',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
assert.equal(
fs.readFileSync(path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'), 'utf8'),
fs.readFileSync(path.join(process.cwd(), 'assets', 'themes', 'subminer.rasi'), 'utf8'),
);
});
});
test('ensureLinuxRuntimePluginAssets installs managed plugin assets without resolving theme source when theme already exists', async () => {
await withTempDir(async (tempDir) => {
const sourceRoot = path.join(tempDir, 'source', 'plugin');
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
fs.mkdirSync(path.join(xdgDataHome, 'SubMiner', 'themes'), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- plugin\n');
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'configured=true\n');
fs.writeFileSync(
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
'/* existing theme */\n',
);
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
resolveBundledAssets: () => ({
pluginDirSource: path.join(sourceRoot, 'subminer'),
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
}),
});
assert.deepEqual(result, {
ok: true,
status: 'installed',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
'-- plugin\n',
);
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
'configured=true\n',
);
});
});
test('ensureLinuxRuntimePluginAssets default resolver can recover missing plugin assets when theme source is unavailable', async () => {
await withTempDir(async (tempDir) => {
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.join(xdgDataHome, 'SubMiner', 'themes'), { recursive: true });
fs.writeFileSync(
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
'/* existing theme */\n',
);
const result = await withProcessResourcesPath(process.cwd(), () =>
ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
existsSync: (candidate) => {
if (
!candidate.startsWith(tempDir) &&
candidate.endsWith(path.join('assets', 'themes', 'subminer.rasi'))
) {
return false;
}
return fs.existsSync(candidate);
},
}),
);
assert.deepEqual(result, {
ok: true,
status: 'installed',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer', 'main.lua'), 'utf8'),
fs.readFileSync(path.join(process.cwd(), 'plugin', 'subminer', 'main.lua'), 'utf8'),
);
assert.equal(
fs.readFileSync(path.join(targetRoot, 'subminer.conf'), 'utf8'),
fs.readFileSync(path.join(process.cwd(), 'plugin', 'subminer.conf'), 'utf8'),
);
});
});
test('ensureLinuxRuntimePluginAssets returns already-present when managed assets already exist', async () => {
await withTempDir(async (tempDir) => {
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.join(targetRoot, 'subminer'), { recursive: true });
fs.mkdirSync(path.join(xdgDataHome, 'SubMiner', 'themes'), { recursive: true });
fs.writeFileSync(path.join(targetRoot, 'subminer', 'main.lua'), '-- existing\n');
fs.writeFileSync(path.join(targetRoot, 'subminer.conf'), 'configured=true\n');
fs.writeFileSync(
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
'/* theme */\n',
);
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
resolveBundledAssets: () => {
throw new Error('should not resolve bundled assets when already installed');
},
});
assert.deepEqual(result, {
ok: true,
status: 'already-present',
path: path.join(targetRoot, 'subminer', 'main.lua'),
});
});
});
test('ensureLinuxRuntimePluginAssets fails when bundled assets cannot be resolved', async () => {
await withTempDir(async (tempDir) => {
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome: path.join(tempDir, 'xdg-data'),
resolveBundledAssets: () => ({}),
});
assert.equal(result.ok, false);
assert.equal(result.status, 'failed');
assert.match(result.error ?? '', /bundled.*plugin assets/i);
});
});
test('ensureLinuxRuntimePluginAssets leaves no final target tree on failed install', async () => {
await withTempDir(async (tempDir) => {
const sourceRoot = path.join(tempDir, 'source', 'plugin');
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
const xdgDataHome = path.join(tempDir, 'xdg-data');
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, 'subminer', 'main.lua'), '-- plugin\n');
fs.writeFileSync(path.join(sourceRoot, 'subminer.conf'), 'configured=true\n');
fs.writeFileSync(themeSourcePath, '/* theme */\n');
const result = await ensureLinuxRuntimePluginAssets({
platform: 'linux',
homeDir: path.join(tempDir, 'home'),
xdgDataHome,
resolveBundledAssets: () => ({
pluginDirSource: path.join(sourceRoot, 'subminer'),
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
themeSourcePath,
}),
copyFile: async () => {
throw new Error('copy failed');
},
});
assert.equal(result.ok, false);
assert.equal(result.status, 'failed');
assert.equal(fs.existsSync(path.join(targetRoot, 'subminer', 'main.lua')), false);
assert.equal(fs.existsSync(path.join(targetRoot, 'subminer.conf')), false);
});
});