'use strict'; const test = require('node:test'); const assert = require('node:assert'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { defaultConfig, normalizeConfig, validateConfig, copyConfig, STRATEGY, WATER_MODE } = require('../src/config/expansionConfig'); const { ConfigStore } = require('../src/config/configStore'); test('默认配置的关键默认值与原 Java 版一致', () => { const config = defaultConfig(); assert.strictEqual(config.strategy, STRATEGY.SQUARE); assert.strictEqual(config.targetCount, -1); assert.strictEqual(config.waitTimeoutSeconds, 60); assert.strictEqual(config.retries, 2); assert.strictEqual(config.returnThresholdSeconds, 60); assert.strictEqual(config.returnWaitSeconds, 12); assert.strictEqual(config.sideStepMaxRounds, 3); assert.strictEqual(config.durabilityThreshold, 15); assert.strictEqual(config.eatThreshold, 14); assert.strictEqual(config.refreshIntervalSeconds, 30); assert.strictEqual(config.apiRefreshIntervalSeconds, 180); assert.strictEqual(config.allowBreak, false); assert.strictEqual(config.allowPlace, false); assert.strictEqual(config.altitudeWeight, 0.1); }); test('Grim 合规默认值偏保守', () => { const config = defaultConfig(); assert.strictEqual(config.grim.enabled, true); assert.strictEqual(config.grim.attackReach, 3.0); assert.strictEqual(config.grim.requireLineOfSight, true); assert.strictEqual(config.grim.respectAttackCooldown, true); assert.strictEqual(config.grim.stopMovementWhileEating, true); assert.strictEqual(config.grim.blockConflictingActions, true); // 疾跑跳是原版合法动作,默认放行 assert.strictEqual(config.grim.blockSprintJump, false); // mineflayer 无载具物理,用船默认关闭 assert.strictEqual(config.autoBoat, false); assert.strictEqual(config.waterMode, WATER_MODE.AVOID); // 挖 / 放方块默认关闭(Grim 检查严格) assert.strictEqual(config.allowBreak, false); assert.strictEqual(config.allowPlace, false); assert.strictEqual(config.pathing.allowParkour, false); assert.strictEqual(config.pathing.allow1by1towers, false); }); test('normalizeConfig 只接受已知字段', () => { const merged = normalizeConfig({ strategy: 3, 未知字段: 'x', grim: { attackReach: 4 } }); assert.strictEqual(merged.strategy, 3); assert.strictEqual(merged.grim.attackReach, 4); assert.strictEqual(merged.未知字段, undefined); // 未提供的字段保持默认 assert.strictEqual(merged.retries, 2); assert.strictEqual(merged.grim.requireLineOfSight, true); }); test('normalizeConfig 对部分配置做深合并而非整体替换', () => { const merged = normalizeConfig({ pathing: { maxDropDown: 5 } }); assert.strictEqual(merged.pathing.maxDropDown, 5); assert.strictEqual(merged.pathing.liquidCost, 40, '未提供的子字段应保留默认值'); }); test('validateConfig 钳制越界值', () => { const config = validateConfig({ strategy: 99, targetCount: -5, retries: 0, waitTimeoutSeconds: 1, durabilityThreshold: 0, refreshIntervalSeconds: 1, apiRefreshIntervalSeconds: 5, altitudeWeight: -3, eatThreshold: 50, pathing: { maxDropDown: 999, liquidCost: 0 }, grim: { attackReach: 99, maxRotationPerTickDeg: 0 }, server: { commandCooldownMs: -1, reconnectDelayMs: 10 }, waterMode: '不存在的模式' }); assert.strictEqual(config.strategy, 1, '非法策略应回退到默认策略而不是钳制到 4'); assert.strictEqual(config.targetCount, -1); assert.strictEqual(config.retries, 1); assert.strictEqual(config.waitTimeoutSeconds, 5); assert.strictEqual(config.durabilityThreshold, 1); assert.strictEqual(config.refreshIntervalSeconds, 10); assert.strictEqual(config.apiRefreshIntervalSeconds, 60); assert.strictEqual(config.altitudeWeight, 0); assert.strictEqual(config.eatThreshold, 20); assert.strictEqual(config.pathing.maxDropDown, 64); assert.strictEqual(config.pathing.liquidCost, 1); assert.strictEqual(config.grim.attackReach, 6); assert.strictEqual(config.grim.maxRotationPerTickDeg, 1); assert.strictEqual(config.server.commandCooldownMs, 0); assert.strictEqual(config.server.reconnectDelayMs, 1000); assert.strictEqual(config.waterMode, WATER_MODE.AVOID); }); test('copyConfig 是深拷贝', () => { const original = defaultConfig(); const copy = copyConfig(original); copy.grim.attackReach = 5; copy.server.claimCommand = 'changed'; assert.strictEqual(original.grim.attackReach, 3.0); assert.strictEqual(original.server.claimCommand, 'u claimmode'); }); test('ConfigStore 读写往返一致,且能容忍损坏文件', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'autostore-cfg-')); const file = path.join(dir, 'nested', 'autostore.json'); const store = new ConfigStore(file); // 文件不存在 → 返回默认配置 const defaults = store.load(); assert.strictEqual(defaults.strategy, STRATEGY.SQUARE); // 保存后能读回 defaults.strategy = STRATEGY.PERIMETER; defaults.stateName = '测试国'; assert.strictEqual(store.save(defaults), true); const loaded = store.load(); assert.strictEqual(loaded.strategy, STRATEGY.PERIMETER); assert.strictEqual(loaded.stateName, '测试国'); // 损坏文件 → 回退到默认配置 fs.writeFileSync(file, '{ 这不是合法 JSON', 'utf8'); assert.strictEqual(store.load().strategy, STRATEGY.SQUARE); fs.rmSync(dir, { recursive: true, force: true }); }); test('ConfigStore 在配置文件缺失 API Key 时回退到环境变量', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'autostore-cfg-')); const file = path.join(dir, 'autostore.json'); const store = new ConfigStore(file); const config = defaultConfig(); config.apiKey = ''; store.save(config); const previous = process.env.SIMP_API_KEY; process.env.SIMP_API_KEY = 'env-key-for-test'; try { assert.strictEqual(store.load().apiKey, 'env-key-for-test'); } finally { if (previous === undefined) delete process.env.SIMP_API_KEY; else process.env.SIMP_API_KEY = previous; fs.rmSync(dir, { recursive: true, force: true }); } });