| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | import { resolveServiceBinary } from "./serviceBinary.js"; |
| 4 | |
| 5 | const files = (...present: string[]) => (path: string) => present.includes(path); |
| 6 | |
| 7 | test("a configured service path wins without probing the install tree", () => { |
| 8 | const result = resolveServiceBinary({ |
| 9 | env: { REASONIX_DESKTOP_SERVICE: " C:\\dev\\reasonix-desktop.exe " }, |
| 10 | platform: "win32", |
| 11 | execPath: String.raw`C:\Apps\Reasonix\versions\v1.38.6\app\Reasonix.exe`, |
| 12 | resourcesPath: String.raw`C:\Apps\Reasonix\versions\v1.38.6\app\resources`, |
| 13 | isFile: () => { throw new Error("must not probe"); }, |
| 14 | }); |
| 15 | assert.deepEqual(result, { binary: String.raw`C:\dev\reasonix-desktop.exe`, probed: [] }); |
| 16 | }); |
| 17 | |
| 18 | test("Windows shells find the service beside app/ in versioned and flat installs", () => { |
| 19 | for (const [execPath, service] of [ |
| 20 | [String.raw`C:\Apps\Reasonix test\versions\v1.38.6\app\Reasonix.exe`, String.raw`C:\Apps\Reasonix test\versions\v1.38.6\reasonix-desktop.exe`], |
| 21 | [String.raw`C:\Apps\Reasonix test\app\Reasonix.exe`, String.raw`C:\Apps\Reasonix test\reasonix-desktop.exe`], |
| 22 | ]) { |
| 23 | const resourcesPath = win32Resources(execPath); |
| 24 | const result = resolveServiceBinary({ env: {}, platform: "win32", execPath, resourcesPath, isFile: files(service, win32Alias(execPath)) }); |
| 25 | assert.equal(result.binary, service); |
| 26 | assert.deepEqual(result.probed, [service, `${resourcesPath}\\service\\reasonix-desktop.exe`]); |
| 27 | } |
| 28 | }); |
| 29 | |
| 30 | test("macOS keeps the bundled Resources/service binary and never probes a sibling", () => { |
| 31 | const execPath = "/Applications/Reasonix.app/Contents/MacOS/Reasonix"; |
| 32 | const resourcesPath = "/Applications/Reasonix.app/Contents/Resources"; |
| 33 | const bundled = `${resourcesPath}/service/reasonix-desktop`; |
| 34 | const result = resolveServiceBinary({ env: {}, platform: "darwin", execPath, resourcesPath, isFile: files(bundled, "/Applications/Reasonix.app/Contents/MacOS/reasonix-desktop") }); |
| 35 | assert.deepEqual(result, { binary: bundled, probed: [bundled] }); |
| 36 | }); |
| 37 | |
| 38 | test("Linux shells mirror the Go bootstrap table for portable and packaged layouts", () => { |
| 39 | for (const [execPath, service] of [ |
| 40 | ["/opt/reasonix/app/Reasonix", "/opt/reasonix/reasonix-desktop"], |
| 41 | ["/opt/reasonix/versions/v1.39.0/app/Reasonix", "/opt/reasonix/versions/v1.39.0/reasonix-desktop"], |
| 42 | ["/usr/lib/reasonix/app/Reasonix", "/usr/bin/reasonix-desktop"], |
| 43 | ]) { |
| 44 | const resourcesPath = `${execPath.slice(0, -"/Reasonix".length)}/resources`; |
| 45 | const result = resolveServiceBinary({ env: {}, platform: "linux", execPath, resourcesPath, isFile: files(service) }); |
| 46 | assert.equal(result.binary, service, execPath); |
| 47 | assert.ok(result.probed.includes(service)); |
| 48 | } |
| 49 | }); |
| 50 | |
| 51 | test("a missing service keeps the bundled location and reports every probe", () => { |
| 52 | const execPath = String.raw`C:\Apps\Reasonix\versions\v1.38.6\app\Reasonix.exe`; |
| 53 | const resourcesPath = win32Resources(execPath); |
| 54 | const result = resolveServiceBinary({ env: { REASONIX_DESKTOP_SERVICE: "" }, platform: "win32", execPath, resourcesPath, isFile: () => false }); |
| 55 | assert.equal(result.binary, `${resourcesPath}\\service\\reasonix-desktop.exe`); |
| 56 | assert.deepEqual(result.probed, [String.raw`C:\Apps\Reasonix\versions\v1.38.6\reasonix-desktop.exe`, result.binary]); |
| 57 | }); |
| 58 | |
| 59 | test("an executable outside an app directory only checks the bundled location", () => { |
| 60 | const result = resolveServiceBinary({ env: {}, platform: "win32", execPath: String.raw`C:\Other\electron.exe`, resourcesPath: String.raw`C:\Other\resources`, isFile: () => false }); |
| 61 | assert.deepEqual(result.probed, [String.raw`C:\Other\resources\service\reasonix-desktop.exe`]); |
| 62 | }); |
| 63 | |
| 64 | function win32Resources(execPath: string): string { |
| 65 | return `${execPath.slice(0, -"\\Reasonix.exe".length)}\\resources`; |
| 66 | } |
| 67 | |
| 68 | // The install root also carries a launcher alias named Reasonix.exe; it must |
| 69 | // never be mistaken for the service. |
| 70 | function win32Alias(execPath: string): string { |
| 71 | const appDir = execPath.slice(0, -"\\Reasonix.exe".length); |
| 72 | return `${appDir.slice(0, -"\\app".length)}\\Reasonix.exe`; |
| 73 | } |
| 74 |