// tests/kb-update/test-cross-platform-paths.test.mjs // Unit tests for scripts/kb-update/lib/cross-platform-paths.mjs // Zero deps. Uses node:test + dependency-injection (homedir/platform overrides) to avoid filesystem and OS coupling. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, rmSync, existsSync, statSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { getCacheDir, getLogDir, getStateDir, getBackupDir, } from '../../scripts/kb-update/lib/cross-platform-paths.mjs'; const APP = 'ms-ai-architect-test'; function withTmp(fn) { const dir = mkdtempSync(join(tmpdir(), 'cpp-test-')); try { return fn(dir); } finally { rmSync(dir, { recursive: true, force: true }); } } test('getCacheDir — macOS returns ~/Library/Caches//', () => { withTmp((home) => { const result = getCacheDir(APP, { platform: 'darwin', homedir: () => home, env: {} }); assert.equal(result, join(home, 'Library', 'Caches', APP)); assert.equal(existsSync(result), true); assert.equal(statSync(result).isDirectory(), true); }); }); test('getCacheDir — Linux uses XDG_CACHE_HOME when set', () => { withTmp((home) => { const xdg = join(home, 'custom-cache'); const result = getCacheDir(APP, { platform: 'linux', homedir: () => home, env: { XDG_CACHE_HOME: xdg } }); assert.equal(result, join(xdg, APP)); assert.equal(existsSync(result), true); }); }); test('getCacheDir — Linux falls back to ~/.cache// when no XDG', () => { withTmp((home) => { const result = getCacheDir(APP, { platform: 'linux', homedir: () => home, env: {} }); assert.equal(result, join(home, '.cache', APP)); }); }); test('getCacheDir — Windows uses %LOCALAPPDATA%\\\\Cache', () => { withTmp((home) => { const lad = join(home, 'AppData', 'Local'); const result = getCacheDir(APP, { platform: 'win32', homedir: () => home, env: { LOCALAPPDATA: lad } }); assert.equal(result, join(lad, APP, 'Cache')); }); }); test('getLogDir — macOS returns ~/Library/Logs//', () => { withTmp((home) => { const result = getLogDir(APP, { platform: 'darwin', homedir: () => home, env: {} }); assert.equal(result, join(home, 'Library', 'Logs', APP)); }); }); test('getLogDir — Linux uses XDG_STATE_HOME//logs when set', () => { withTmp((home) => { const xdg = join(home, 'custom-state'); const result = getLogDir(APP, { platform: 'linux', homedir: () => home, env: { XDG_STATE_HOME: xdg } }); assert.equal(result, join(xdg, APP, 'logs')); }); }); test('getLogDir — Linux falls back to ~/.local/state//logs/', () => { withTmp((home) => { const result = getLogDir(APP, { platform: 'linux', homedir: () => home, env: {} }); assert.equal(result, join(home, '.local', 'state', APP, 'logs')); }); }); test('getLogDir — Windows uses %LOCALAPPDATA%\\\\Logs', () => { withTmp((home) => { const lad = join(home, 'AppData', 'Local'); const result = getLogDir(APP, { platform: 'win32', homedir: () => home, env: { LOCALAPPDATA: lad } }); assert.equal(result, join(lad, APP, 'Logs')); }); }); test('getStateDir — macOS uses ~/Library/Application Support//', () => { withTmp((home) => { const result = getStateDir(APP, { platform: 'darwin', homedir: () => home, env: {} }); assert.equal(result, join(home, 'Library', 'Application Support', APP)); }); }); test('getStateDir — Linux uses XDG_STATE_HOME when set', () => { withTmp((home) => { const xdg = join(home, 'custom-state'); const result = getStateDir(APP, { platform: 'linux', homedir: () => home, env: { XDG_STATE_HOME: xdg } }); assert.equal(result, join(xdg, APP)); }); }); test('getBackupDir — joins pluginRoot and .kb-backup, creates if missing', () => { withTmp((root) => { const result = getBackupDir(root); assert.equal(result, join(root, '.kb-backup')); assert.equal(existsSync(result), true); assert.equal(statSync(result).isDirectory(), true); }); }); test('default options — uses real os.homedir() and process.platform', () => { // Smoke-test: with no overrides, returns something sane (creates dir under real home). // Use an unusual app name to avoid colliding with anything real. const APP_REAL = 'ms-ai-architect-cpp-smoke-' + process.pid; const result = getCacheDir(APP_REAL); assert.ok(result.length > 0); assert.equal(existsSync(result), true); // Cleanup rmSync(result, { recursive: true, force: true }); }); test('getCacheDir — throws on missing app name', () => { assert.throws(() => getCacheDir(), /appName/); assert.throws(() => getCacheDir(''), /appName/); });