'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 { BigMapService, TILE_PIXELS, DEFAULT_MAX_ZOOM } = require('../src/core/bigMapService'); const { defaultConfig } = require('../src/config/expansionConfig'); /** 造一张假的 PNG 内容(只需非空即可,服务端只做字节透传) */ function fakePng(size) { const buffer = Buffer.alloc(size || 64); buffer[0] = 0x89; buffer[1] = 0x50; return buffer; } /** 建一个使用临时缓存目录、带假 fetch 的大地图服务 */ function setup(options) { const opts = options || {}; const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'autostore-bigmap-')); const config = defaultConfig(); Object.assign(config.bigMap, opts.bigMap || {}); const calls = []; const fetchImpl = opts.fetchImpl || (async (url) => { calls.push(url); if (opts.failAll) return { ok: false, status: 404, arrayBuffer: async () => new ArrayBuffer(0) }; const data = fakePng(opts.tileSize || 64); return { ok: true, status: 200, arrayBuffer: async () => data.buffer.slice(data.byteOffset, data.byteOffset + data.length) }; }); const service = new BigMapService(config, null, { baseUrl: 'http://bgjq.simpfun.cn', world: 'minecraft_overworld', cacheDir, fetchImpl }); return { service, config, cacheDir, calls }; } test('坐标系:最大层级 1 方块 = 1 像素,逐级翻倍', () => { const { service } = setup(); assert.strictEqual(service.maxZoom, DEFAULT_MAX_ZOOM); assert.strictEqual(service.blocksPerTile(3), TILE_PIXELS, '最大层级每张瓦片覆盖 512 方块'); assert.strictEqual(service.blocksPerTile(2), TILE_PIXELS * 2); assert.strictEqual(service.blocksPerTile(1), TILE_PIXELS * 4); assert.strictEqual(service.blocksPerTile(0), TILE_PIXELS * 8, '层级 0 每张瓦片覆盖 4096 方块'); }); test('坐标系:王城 (10722, 1547) 落在 z=3 的瓦片 (20, 3) 内(对源站实测标定)', () => { const { service } = setup(); const bounds = service.tileBlockBounds(3, 20, 3); assert.deepStrictEqual(bounds, { minBlockX: 10240, maxBlockX: 10752, minBlockZ: 1536, maxBlockZ: 2048 }); assert.ok(10722 >= bounds.minBlockX && 10722 < bounds.maxBlockX); assert.ok(1547 >= bounds.minBlockZ && 1547 < bounds.maxBlockZ); }); test('tileRange 覆盖 ±rangeBlocks:层级 0 全世界为 10×10=100 张', () => { const { service } = setup(); const range = service.tileRange(0, 20480); assert.deepStrictEqual(range, { minX: -5, maxX: 4, minZ: -5, maxZ: 4, count: 100 }); // 瓦片范围必须完整覆盖 -20480..20480 assert.strictEqual(range.minX * service.blocksPerTile(0), -20480); assert.strictEqual((range.maxX + 1) * service.blocksPerTile(0), 20480); }); test('tileUrl / tilePath 拼接源站地址与磁盘缓存路径', () => { const { service } = setup(); assert.strictEqual( service.tileUrl(2, -3, 7), 'http://bgjq.simpfun.cn/tiles/minecraft_overworld/2/-3_7.png' ); assert.ok(service.tilePath(2, -3, 7).endsWith(path.join('minecraft_overworld', '2', '-3_7.png'))); }); test('getTile 首次回源并落盘,第二次命中磁盘不再回源', async () => { const { service, calls } = setup(); const first = await service.getTile(1, 2, 3); assert.ok(first && first.length > 0); assert.strictEqual(calls.length, 1); // 清掉内存缓存,验证磁盘缓存生效 service._memory.clear(); const second = await service.getTile(1, 2, 3); assert.ok(second && second.length > 0); assert.strictEqual(calls.length, 1, '第二次不应再回源'); }); test('getTile 回源失败返回 null(控制台画占位色块)', async () => { const { service } = setup({ failAll: true }); assert.strictEqual(await service.getTile(0, 0, 0), null); }); test('crawlAll 批量爬取范围内全部瓦片并统计进度', async () => { const { service, calls } = setup({ bigMap: { rangeBlocks: 4096 } }); const result = await service.crawlAll(0); // rangeBlocks=4096、层级 0 每张 4096 方块 → 2×2=4 张 assert.strictEqual(result.total, 4); assert.strictEqual(result.done, 4); assert.strictEqual(result.failed, 0); assert.strictEqual(result.running, false); assert.strictEqual(calls.length, 4); assert.strictEqual(service.countCached(0), 4); }); test('crawlAll 已缓存的瓦片不重复下载', async () => { const { service, calls } = setup({ bigMap: { rangeBlocks: 4096 } }); await service.crawlAll(0); assert.strictEqual(calls.length, 4); await service.crawlAll(0); assert.strictEqual(calls.length, 4, '第二次爬取应全部命中缓存'); }); test('crawlAll 统计失败张数', async () => { const { service } = setup({ failAll: true, bigMap: { rangeBlocks: 4096 } }); const result = await service.crawlAll(0); assert.strictEqual(result.total, 4); assert.strictEqual(result.done, 0); assert.strictEqual(result.failed, 4); }); test('snapshot 暴露坐标系、瓦片范围与缓存数量', () => { const { service } = setup({ bigMap: { rangeBlocks: 20480, zoom: 0 } }); const snap = service.snapshot(); assert.strictEqual(snap.world, 'minecraft_overworld'); assert.strictEqual(snap.tilePixels, TILE_PIXELS); assert.strictEqual(snap.blocksPerTile, 4096); assert.strictEqual(snap.tiles.count, 100); assert.strictEqual(snap.cached, 0); assert.ok(snap.source.tileUrlTemplate.includes('{z}/{x}_{y}.png')); }); test('clearCache 清空磁盘与内存缓存', async () => { const { service } = setup({ bigMap: { rangeBlocks: 4096 } }); await service.crawlAll(0); assert.strictEqual(service.countCached(0), 4); const removed = service.clearCache(); assert.strictEqual(removed, 4); assert.strictEqual(service.countCached(0), 0); assert.strictEqual(service._memory.size, 0); }); test('配置里 zoom 大于 maxZoom 时被钳制(避免爬到源站不存在的瓦片)', () => { const { service, config } = setup({ bigMap: { zoom: 7, maxZoom: 3 } }); // 直接走一次配置归一化,验证钳制逻辑 const { normalizeConfig } = require('../src/config/expansionConfig'); const normalized = normalizeConfig(config); assert.strictEqual(normalized.bigMap.zoom, 3); assert.strictEqual(service.maxZoom, 3); });