文件
BotStore/test/targetLease.test.js
T
lyizai 51664c55b6 feat: 1.7.0 落地 TODO 全部待办(地面修正 / 手动执行器 / 自救 / 回城 / 租约 / 通知)
按 TODO.md 的优先级把待办全部落地为代码,详见 CHANGELOG 的 [1.7.0]。

- 「走不动」攻关:地面状态修正(syncGroundToPhysics / trustSetbackOnGround)、
  出站包类型分布、备选执行器 manualMover(只借 A* 规划、自己按键执行)
- 原点自救分两轮并统计成败;回城前评估「原地是否更差」并记录前后坐标
- 新增目标区块租约表(targetLease)避免多机器人撞同一块
- 定时消息支持 cron 与「期望回复」确认;新增人工介入 Webhook 通知(notifier)
- 大地图后台预热与视野裁剪;控制台日志按帧批量写入、配置面板支持搜索

顺带修掉两个影响部署的细节:
- 部署脚本(deploy.js / ssh-helper.js)改为也能从 .env 读 SSH 凭据 ——
  在命令行传密码会留在 shell 历史与进程列表里,写进已被忽略的 .env 更安全;
  显式环境变量仍然优先。.env.example 补上占位符并说明这些只在本机用。
- package.json / package-lock.json 版本对齐到 1.7.0(此前停留在 1.1.0,
  与 CHANGELOG 的语义化版本声明脱节)。

测试:368 → 436 个用例,`npm test` 与 `npm run check` 全绿。
2026-09-28 22:46:39 +08:00

83 行
3.7 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert');
const { TargetLeaseTable, DEFAULT_TTL_MS } = require('../src/core/targetLease');
test('tryAcquire:先抢到的机器人独占,其他机器人拿不到', () => {
const table = new TargetLeaseTable();
const chunk = { x: 10, z: 20 };
assert.strictEqual(table.tryAcquire(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'B'), false, '被别人占用时应失败');
assert.strictEqual(table.isLeasedByOther(chunk, 'B'), true);
assert.strictEqual(table.isLeasedByOther(chunk, 'A'), false, '自己占用不算「被别人占用」');
});
test('tryAcquire:同一机器人可重复占用(视为续租)', () => {
const table = new TargetLeaseTable({ ttlMs: 1000 });
const chunk = { x: 1, z: 1 };
assert.strictEqual(table.tryAcquire(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'A'), true, '自己再抢一次不应失败');
assert.strictEqual(table.size, 1, '不应产生第二条记录');
});
test('租约到期后自动失效,其他机器人可接手(不会永久锁死区块)', async () => {
const table = new TargetLeaseTable({ ttlMs: 30 });
const chunk = { x: 5, z: 5 };
assert.strictEqual(table.tryAcquire(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'B'), false);
await new Promise((resolve) => setTimeout(resolve, 45));
assert.strictEqual(table.isLeasedByOther(chunk, 'B'), false, '过期后应视为空闲');
assert.strictEqual(table.tryAcquire(chunk, 'B'), true, '过期后其他机器人可接手');
});
test('release:只有持有者能释放,释放后别人立刻可用', () => {
const table = new TargetLeaseTable();
const chunk = { x: 2, z: 3 };
table.tryAcquire(chunk, 'A');
assert.strictEqual(table.release(chunk, 'B'), false, '非持有者不能释放');
assert.strictEqual(table.tryAcquire(chunk, 'B'), false);
assert.strictEqual(table.release(chunk, 'A'), true);
assert.strictEqual(table.tryAcquire(chunk, 'B'), true);
});
test('releaseAll 释放某机器人的全部占用(停止任务 / 断线时用)', () => {
const table = new TargetLeaseTable();
table.tryAcquire({ x: 0, z: 0 }, 'A');
table.tryAcquire({ x: 1, z: 0 }, 'A');
table.tryAcquire({ x: 0, z: 1 }, 'B');
assert.strictEqual(table.releaseAll('A'), 2);
assert.strictEqual(table.size, 1, '只应剩下 B 的占用');
assert.strictEqual(table.isLeasedByOther({ x: 0, z: 0 }, 'B'), false);
});
test('renew 延长租约,防止长距离寻路中途被抢', async () => {
const table = new TargetLeaseTable({ ttlMs: 40 });
const chunk = { x: 7, z: 7 };
table.tryAcquire(chunk, 'A');
await new Promise((resolve) => setTimeout(resolve, 25));
assert.strictEqual(table.renew(chunk, 'A'), true);
await new Promise((resolve) => setTimeout(resolve, 25));
// 已超过原始 TTL,但续租过 → 仍是 A 的
assert.strictEqual(table.tryAcquire(chunk, 'B'), false, '续租后不应被抢走');
});
test('renew 对非持有者无效', () => {
const table = new TargetLeaseTable();
table.tryAcquire({ x: 9, z: 9 }, 'A');
assert.strictEqual(table.renew({ x: 9, z: 9 }, 'B'), false);
});
test('sweep 清理过期租约,snapshot 只列出有效占用', async () => {
const table = new TargetLeaseTable({ ttlMs: 20 });
table.tryAcquire({ x: 1, z: 1 }, 'A');
await new Promise((resolve) => setTimeout(resolve, 30));
assert.strictEqual(table.sweep(), 1);
assert.deepStrictEqual(table.snapshot(), []);
});
test('默认租约时长与配置常量一致', () => {
assert.strictEqual(new TargetLeaseTable().ttlMs, DEFAULT_TTL_MS);
assert.ok(DEFAULT_TTL_MS >= 60000, '默认不应太短,否则长距离寻路会被抢');
});