'use strict' const assert = require('node:assert/strict') const fs = require('node:fs') const os = require('node:os') const path = require('node:path') const test = require('node:test') const { resolveUserRoot, planUserPaths, prepareUserPaths, withConfigArg } = require('./userpaths') // 用绝对路径字符串拼测试目录,避免依赖运行平台的盘符写法 const INSTALL = path.resolve('/scoreboard-install') const LOCAL = path.resolve('/localappdata') const USER_ROOT = path.join(LOCAL, 'ScoreboardConsole') function makeRoot(t) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'scoreboard-userpaths-')) t.after(() => fs.rmSync(root, { recursive: true, force: true })) return root } function writeFile(file, text) { fs.mkdirSync(path.dirname(file), { recursive: true }) fs.writeFileSync(file, text) } function readFile(file) { return fs.readFileSync(file, 'utf8') } // 打包态 + 有 %LOCALAPPDATA% 是「接管」的唯一组合 function planFor({ installDir = INSTALL, env = { LOCALAPPDATA: LOCAL }, isPackaged = true } = {}) { return planUserPaths({ installDir, userRoot: resolveUserRoot(env), isPackaged }) } test('打包态三项都落到用户根下,与数据目录同一基座', () => { const plan = planFor() assert.equal(plan.mode, 'user') assert.equal(plan.root, USER_ROOT) assert.equal(plan.items.consoleConfig.target, path.join(USER_ROOT, 'console', 'config.json')) assert.equal(plan.items.serverConfig.target, path.join(USER_ROOT, 'config.yaml')) assert.equal(plan.items.rulesDir.target, path.join(USER_ROOT, 'rules')) }) test('开发态不接管,三项都指向安装目录里随包的那份', () => { const plan = planFor({ isPackaged: false }) assert.equal(plan.mode, 'install') assert.equal(plan.reason, 'dev') assert.equal(plan.items.consoleConfig.target, path.join(INSTALL, 'config.json')) assert.equal(plan.items.serverConfig.target, path.join(INSTALL, 'config.yaml')) assert.equal(plan.items.rulesDir.target, path.join(INSTALL, 'rules')) }) test('拿不到 %LOCALAPPDATA% 时回落安装目录并记日志', () => { const plan = planFor({ env: {} }) assert.equal(plan.mode, 'install') assert.equal(plan.reason, 'no-localappdata') const lines = [] const paths = prepareUserPaths({ plan, log: (line) => lines.push(line) }) assert.equal(paths.consoleConfig, path.join(INSTALL, 'config.json')) assert.equal(paths.rulesDir, path.join(INSTALL, 'rules')) assert.equal(paths.serverConfig, null) assert.match(lines.join('\n'), /拿不到 %LOCALAPPDATA%/) }) test('首次运行把随包的三项复制过去,安装目录那份留作模板', (t) => { const root = makeRoot(t) const installDir = path.join(root, 'install') const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole') writeFile(path.join(installDir, 'config.json'), '{"server":{"port":19541}}') writeFile(path.join(installDir, 'config.yaml'), 'server:\n port: 19541\n') writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'format: scoreboard-rules\n') writeFile(path.join(installDir, 'rules', 'README.md'), '说明\n') const lines = [] const paths = prepareUserPaths({ plan: planUserPaths({ installDir, userRoot, isPackaged: true }), log: (line) => lines.push(line) }) assert.equal(paths.consoleConfig, path.join(userRoot, 'console', 'config.json')) assert.equal(paths.serverConfig, path.join(userRoot, 'config.yaml')) assert.equal(paths.rulesDir, path.join(userRoot, 'rules')) assert.equal(readFile(paths.consoleConfig), '{"server":{"port":19541}}') assert.equal(readFile(paths.serverConfig), 'server:\n port: 19541\n') assert.equal(readFile(path.join(userRoot, 'rules', 'cup.yaml')), 'format: scoreboard-rules\n') assert.equal(readFile(path.join(userRoot, 'rules', 'README.md')), '说明\n') // 复制不是移动:安装目录那三份必须原样留着当模板 assert.equal(readFile(path.join(installDir, 'config.json')), '{"server":{"port":19541}}') assert.equal(readFile(path.join(installDir, 'config.yaml')), 'server:\n port: 19541\n') assert.equal(fs.existsSync(path.join(installDir, 'rules', 'cup.yaml')), true) assert.equal(lines.filter((line) => line.startsWith('已把 ')).length, 3) }) test('用户目录已有那份就一律用它,不覆盖也不重拷', (t) => { const root = makeRoot(t) const installDir = path.join(root, 'install') const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole') writeFile(path.join(installDir, 'config.json'), '{"server":{"port":1111}}') writeFile(path.join(installDir, 'config.yaml'), 'server:\n port: 1111\n') writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'template\n') writeFile(path.join(userRoot, 'console', 'config.json'), '{"server":{"port":2222}}') writeFile(path.join(userRoot, 'config.yaml'), 'server:\n port: 2222\n') writeFile(path.join(userRoot, 'rules', 'mine.yaml'), 'mine\n') const lines = [] const paths = prepareUserPaths({ plan: planUserPaths({ installDir, userRoot, isPackaged: true }), log: (line) => lines.push(line) }) assert.equal(readFile(paths.consoleConfig), '{"server":{"port":2222}}') assert.equal(readFile(paths.serverConfig), 'server:\n port: 2222\n') assert.equal(readFile(path.join(userRoot, 'rules', 'mine.yaml')), 'mine\n') // 用户目录里的 rules\ 已存在(哪怕内容和模板不同)→ 不补拷模板里的 cup.yaml assert.equal(fs.existsSync(path.join(userRoot, 'rules', 'cup.yaml')), false) assert.equal(lines.filter((line) => line.startsWith('已把 ')).length, 0) }) test('用户目录写不进去时记日志并回落到安装目录,不抛异常', (t) => { const root = makeRoot(t) const installDir = path.join(root, 'install') writeFile(path.join(installDir, 'config.json'), '{}') writeFile(path.join(installDir, 'config.yaml'), 'server: {}\n') writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'template\n') // 用户根的位置被一个文件占着:mkdir/rename 必然失败(ENOTDIR) const userRoot = path.join(root, 'blocker') writeFile(userRoot, 'not a dir') const lines = [] const paths = prepareUserPaths({ plan: planUserPaths({ installDir, userRoot, isPackaged: true }), log: (line) => lines.push(line) }) assert.equal(paths.consoleConfig, path.join(installDir, 'config.json')) assert.equal(paths.serverConfig, null) assert.equal(paths.rulesDir, path.join(installDir, 'rules')) assert.equal(readFile(paths.consoleConfig), '{}') assert.equal(readFile(path.join(installDir, 'rules', 'cup.yaml')), 'template\n') assert.equal(lines.filter((line) => line.includes('继续用安装目录里的')).length, 3) }) test('两边都没有的那一份跳过复制,路径仍指向用户目录', (t) => { const root = makeRoot(t) const installDir = path.join(root, 'install') const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole') fs.mkdirSync(installDir, { recursive: true }) const lines = [] const paths = prepareUserPaths({ plan: planUserPaths({ installDir, userRoot, isPackaged: true }), log: (line) => lines.push(line) }) // 不接管就没人去读用户目录,服务端会按 config.Load 的默认值跑 assert.equal(paths.serverConfig, path.join(userRoot, 'config.yaml')) assert.equal(fs.existsSync(paths.serverConfig), false) assert.equal(lines.filter((line) => line.includes('本次跳过复制')).length, 3) }) test('规则目录拷贝到位后不留临时目录,重复运行不重做', (t) => { const root = makeRoot(t) const installDir = path.join(root, 'install') const userRoot = path.join(root, 'localappdata', 'ScoreboardConsole') writeFile(path.join(installDir, 'config.json'), '{}\n') writeFile(path.join(installDir, 'config.yaml'), 'server: {}\n') writeFile(path.join(installDir, 'rules', 'cup.yaml'), 'template\n') const plan = planUserPaths({ installDir, userRoot, isPackaged: true }) const first = prepareUserPaths({ plan, log: () => {} }) assert.deepEqual(fs.readdirSync(userRoot).sort(), ['config.yaml', 'console', 'rules']) assert.deepEqual(fs.readdirSync(first.rulesDir), ['cup.yaml']) // 第二次启动:目标都在了,不再复制(用户改过的内容不会被模板顶掉) writeFile(path.join(userRoot, 'config.yaml'), 'server:\n port: 3333\n') const second = prepareUserPaths({ plan, log: () => {} }) assert.deepEqual(second, first) assert.equal(readFile(path.join(userRoot, 'config.yaml')), 'server:\n port: 3333\n') }) test('-config 的值换成解析出的绝对路径,其余参数原样保留', () => { assert.deepEqual(withConfigArg(['-config', 'config.yaml'], '/u/config.yaml'), ['-config', '/u/config.yaml']) assert.deepEqual( withConfigArg(['-config', 'config.yaml', '-project', 'cup'], '/u/config.yaml'), ['-config', '/u/config.yaml', '-project', 'cup'] ) // 参数里没写 -config 就补一条,否则用户目录里那份永远不会被读到 assert.deepEqual(withConfigArg(['-project', 'cup'], '/u/config.yaml'), ['-project', 'cup', '-config', '/u/config.yaml']) // 不接管(开发态)原样返回 assert.deepEqual(withConfigArg(['-config', 'config.yaml'], null), ['-config', 'config.yaml']) assert.deepEqual(withConfigArg(null, '/u/config.yaml'), ['-config', '/u/config.yaml']) })