fix(plugin): add lowercase linux binary fallbacks

This commit is contained in:
2026-03-16 01:51:02 -07:00
parent 659118c20c
commit 8123a145c0
7 changed files with 193 additions and 8 deletions

View File

@@ -2,11 +2,13 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import net from 'node:net';
import { EventEmitter } from 'node:events';
import type { Args } from './types';
import {
cleanupPlaybackSession,
findAppBinary,
runAppCommandCaptureOutput,
shouldResolveAniSkipMetadata,
startOverlay,
@@ -233,3 +235,72 @@ test('cleanupPlaybackSession preserves background app while stopping mpv-owned c
fs.rmSync(dir, { recursive: true, force: true });
}
});
// ── findAppBinary: Linux packaged path discovery ──────────────────────────────
function makeExecutable(filePath: string): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, '#!/bin/sh\nexit 0\n');
fs.chmodSync(filePath, 0o755);
}
test('findAppBinary resolves ~/.local/bin/SubMiner.AppImage when it exists', () => {
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-home-'));
const originalHomedir = os.homedir;
try {
os.homedir = () => baseDir;
const appImage = path.join(baseDir, '.local/bin/SubMiner.AppImage');
makeExecutable(appImage);
const result = findAppBinary('/some/other/path/subminer');
assert.equal(result, appImage);
} finally {
os.homedir = originalHomedir;
fs.rmSync(baseDir, { recursive: true, force: true });
}
});
test('findAppBinary resolves /opt/SubMiner/SubMiner.AppImage when ~/.local/bin candidate does not exist', () => {
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-home-'));
const originalHomedir = os.homedir;
const originalAccessSync = fs.accessSync;
try {
os.homedir = () => baseDir;
// No ~/.local/bin/SubMiner.AppImage; patch accessSync so only /opt path is executable
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fs as any).accessSync = (filePath: string, mode?: number): void => {
if (filePath === '/opt/SubMiner/SubMiner.AppImage') return;
throw Object.assign(new Error(`EACCES: ${filePath}`), { code: 'EACCES' });
};
const result = findAppBinary('/some/other/path/subminer');
assert.equal(result, '/opt/SubMiner/SubMiner.AppImage');
} finally {
os.homedir = originalHomedir;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fs as any).accessSync = originalAccessSync;
fs.rmSync(baseDir, { recursive: true, force: true });
}
});
test('findAppBinary finds subminer on PATH when AppImage candidates do not exist', () => {
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-path-'));
const originalHomedir = os.homedir;
const originalPath = process.env.PATH;
try {
os.homedir = () => baseDir;
// No AppImage candidates in empty home dir; place subminer wrapper on PATH
const binDir = path.join(baseDir, 'bin');
const wrapperPath = path.join(binDir, 'subminer');
makeExecutable(wrapperPath);
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ''}`;
// selfPath must differ from wrapperPath so the self-check does not exclude it
const result = findAppBinary(path.join(baseDir, 'launcher', 'subminer'));
assert.equal(result, wrapperPath);
} finally {
os.homedir = originalHomedir;
process.env.PATH = originalPath;
fs.rmSync(baseDir, { recursive: true, force: true });
}
});

90
launcher/picker.test.ts Normal file
View File

@@ -0,0 +1,90 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { findRofiTheme } from './picker';
// ── findRofiTheme: Linux packaged path discovery ──────────────────────────────
const ROFI_THEME_FILE = 'subminer.rasi';
function makeFile(filePath: string): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, '/* theme */');
}
test('findRofiTheme resolves /usr/local/share/SubMiner/themes/subminer.rasi when it exists', () => {
const originalExistsSync = fs.existsSync;
const targetPath = `/usr/local/share/SubMiner/themes/${ROFI_THEME_FILE}`;
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fs as any).existsSync = (filePath: unknown): boolean => {
if (filePath === targetPath) return true;
return false;
};
const result = findRofiTheme('/usr/local/bin/subminer');
assert.equal(result, targetPath);
} finally {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fs as any).existsSync = originalExistsSync;
}
});
test('findRofiTheme resolves /usr/share/SubMiner/themes/subminer.rasi when /usr/local/share one does not exist', () => {
const originalExistsSync = fs.existsSync;
const localSharePath = `/usr/local/share/SubMiner/themes/${ROFI_THEME_FILE}`;
const sharePath = `/usr/share/SubMiner/themes/${ROFI_THEME_FILE}`;
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fs as any).existsSync = (filePath: unknown): boolean => {
if (filePath === sharePath) return true;
if (filePath === localSharePath) return false;
return false;
};
const result = findRofiTheme('/usr/bin/subminer');
assert.equal(result, sharePath);
} finally {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fs as any).existsSync = originalExistsSync;
}
});
test('findRofiTheme resolves XDG_DATA_HOME/SubMiner/themes/subminer.rasi when set and file exists', () => {
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-xdg-'));
const originalXdgDataHome = process.env.XDG_DATA_HOME;
try {
process.env.XDG_DATA_HOME = baseDir;
const themePath = path.join(baseDir, `SubMiner/themes/${ROFI_THEME_FILE}`);
makeFile(themePath);
const result = findRofiTheme('/usr/bin/subminer');
assert.equal(result, themePath);
} finally {
process.env.XDG_DATA_HOME = originalXdgDataHome;
fs.rmSync(baseDir, { recursive: true, force: true });
}
});
test('findRofiTheme resolves ~/.local/share/SubMiner/themes/subminer.rasi when XDG_DATA_HOME unset', () => {
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-home-'));
const originalHomedir = os.homedir;
const originalXdgDataHome = process.env.XDG_DATA_HOME;
try {
os.homedir = () => baseDir;
delete process.env.XDG_DATA_HOME;
const themePath = path.join(baseDir, `.local/share/SubMiner/themes/${ROFI_THEME_FILE}`);
makeFile(themePath);
const result = findRofiTheme('/usr/bin/subminer');
assert.equal(result, themePath);
} finally {
os.homedir = originalHomedir;
if (originalXdgDataHome !== undefined) {
process.env.XDG_DATA_HOME = originalXdgDataHome;
}
fs.rmSync(baseDir, { recursive: true, force: true });
}
});