import { describe, expect, test } from 'bun:test'; import { appExe, graceMs, openState, listCommand, logDir, matchByPath, mismatchMessage, notOpenedMessage, parseProcs, peVersion, startedHidden, stopCommand, versionMatches } from '../cli/lib/desktop-proc'; /** A tiny fake PE: "MZ", padding, then a VS_FIXEDFILEINFO with the given product version. */ function fakePe(a: number, b: number, c: number, d = 0, at = 128): Uint8Array { const buf = new Uint8Array(at + 52); const v = new DataView(buf.buffer); v.setUint16(0, 0x5a4d, true); v.setUint32(at, 0xfeef04bd, true); v.setUint32(at + 4, 0x00010000, true); v.setUint32(at + 8, (9 << 16) | 9, true); // file version differs on purpose v.setUint32(at + 16, (a << 16) | b, true); v.setUint32(at + 20, (c << 16) | d, true); return buf; } describe('desktop install: real version of the installed files', () => { test('PE product version from VS_FIXEDFILEINFO', () => { expect(peVersion(fakePe(0, 8, 46))).toBe('0.8.46'); expect(peVersion(fakePe(2, 0, 7, 5))?.split(".")).toEqual(["2", "0", "7", "5"]); expect(peVersion(new Uint8Array(200))).toBeNull(); // no MZ const noInfo = new Uint8Array(200); noInfo[0] = 0x4d; noInfo[1] = 0x5a; expect(peVersion(noInfo)).toBeNull(); }); test('the stale 0.8.38 exe is a mismatch for 0.8.46', () => { expect(versionMatches('0.8.38', '0.8.46', true)).toBe(false); expect(versionMatches('0.8.46', '0.8.46', true)).toBe(true); expect(versionMatches('0.8.46', '0.8.46-beta.1', true)).toBe(true); expect(versionMatches('0.8.46', '0.8.46')).toBe(true); expect(versionMatches('0.8.45', '0.8.46')).toBe(false); const m = mismatchMessage('0.8.38', '0.8.46', 'C:\\Users\\u\\AppData\\Local\\storebound\\storebound-desktop.exe'); expect(m).toContain('is 0.8.38, expected 0.8.46'); expect(m).toContain('Close storebound desktop'); expect(mismatchMessage(null, '0.8.46', 'x')).toContain('unreadable'); }); }); describe('desktop install: the running app by exact path', () => { const exe = 'C:\\Users\\u\\AppData\\Local\\storebound\\storebound-desktop.exe'; test('Windows: CIM JSON, one row or many, case-insensitive exact path only', () => { expect(listCommand('win32')[0]).toBe('powershell'); const one = parseProcs('win32', JSON.stringify({ ProcessId: 42, ExecutablePath: exe.toUpperCase(), CommandLine: `"${exe}" --hidden` })); expect(matchByPath('win32', one, exe).map(p => p.pid)).toEqual([42]); expect(startedHidden(one[0]!)).toBe(true); const many = parseProcs('win32', JSON.stringify([ { ProcessId: 1, ExecutablePath: 'D:\\other\\storebound-desktop.exe', CommandLine: '' }, { ProcessId: 2, ExecutablePath: exe, CommandLine: `"${exe}"` }, { ProcessId: 3, ExecutablePath: null, CommandLine: null }, ])); expect(matchByPath('win32', many, exe).map(p => p.pid)).toEqual([2]); expect(startedHidden(many[1]!)).toBe(false); expect(parseProcs('win32', '')).toEqual([]); expect(parseProcs('win32', 'not json')).toEqual([]); }); test('macOS / Linux: ps args, the bundle binary, no prefix lookalikes', () => { const mac = appExe('darwin', '/Applications/storebound.app'); expect(mac).toBe('/Applications/storebound.app/Contents/MacOS/storebound-desktop'); const procs = parseProcs('darwin', [ ` 101 ${mac}`, ` 102 ${mac} --hidden`, ` 103 ${mac}-helper`, ` 104 /tmp/h/Applications/storebound.app/Contents/MacOS/storebound-desktop`, '', ].join('\n')); expect(matchByPath('darwin', procs, mac).map(p => [p.pid, startedHidden(p)])).toEqual([[101, false], [102, true]]); expect(appExe('linux', '/tmp/h/.local/bin/storebound.AppImage')).toBe('/tmp/h/.local/bin/storebound.AppImage'); }); test('stop: graceful first, then forced, by PID', () => { expect(stopCommand('win32', 42, false)).toEqual(['taskkill', '/PID', '42']); expect(stopCommand('win32', 42, true)).toEqual(['taskkill', '/F', '/PID', '42']); expect(stopCommand('darwin', 7, false)).toEqual(['kill', '-TERM', '7']); expect(stopCommand('linux', 7, true)).toEqual(['kill', '-KILL', '7']); }); }); describe('desktop open: the log the message points to', () => { test('per OS', () => { expect(logDir('win32', { APPDATA: 'C:\\Users\\u\\AppData\\Roaming' }, 'C:\\Users\\u')).toBe('C:\\Users\\u\\AppData\\Roaming\\dev.storebound.desktop\\logs'); expect(logDir('darwin', {}, '/tmp/h')).toBe('/tmp/h/Library/Application Support/dev.storebound.desktop/logs'); expect(logDir('linux', {}, '/tmp/h')).toBe('/tmp/h/.local/share/dev.storebound.desktop/logs'); expect(notOpenedMessage('none', 'x.exe', 'L')).toContain('did not open: no process'); expect(notOpenedMessage('hidden', 'x.exe', 'L')).toContain('hidden (tray)'); }); }); describe('desktop open: a hidden tray copy that is already running', () => { const exe = "D:/app/storebound-desktop.exe"; const hidden = { pid: 1, exe, args: `"${exe}" --hidden` }; test('Windows: the visible window decides, read from the CIM/Get-Process JSON', () => { const rows = parseProcs('win32', JSON.stringify([{ ProcessId: 1, ExecutablePath: exe, CommandLine: `"${exe}" --hidden`, Window: false }, { ProcessId: 2, ExecutablePath: exe, CommandLine: `"${exe}"`, Window: true }])); expect(rows.map(r => r.window)).toEqual([false, true]); expect(openState('win32', [rows[0]!], true)).toBe('hidden'); expect(openState('win32', rows, false)).toBe('ok'); expect(openState('win32', [], true)).toBe('none'); expect(listCommand('win32').join(' ')).toContain('IsWindowVisible'); }); test('elsewhere: a visible start, or a hidden copy the new start handed over to', () => { expect(openState('linux', [hidden], false)).toBe('hidden'); expect(openState('linux', [hidden], true)).toBe('ok'); expect(openState('darwin', [{ pid: 3, exe: '', args: '/x/storebound-desktop' }], false)).toBe('ok'); expect(openState('darwin', [], true)).toBe('none'); }); test('a tray app only hides on close: short graceful wait on Windows', () => { expect(graceMs('win32')).toBeLessThan(graceMs('linux')); }); });