feat: add comprehensive tests for WorldBlockCache functionality
This commit is contained in:
127
server/__tests__/WorldBlockCache.test.js
Normal file
127
server/__tests__/WorldBlockCache.test.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { WorldBlockCache } from '../WorldBlockCache.js';
|
||||||
|
|
||||||
|
function makeDb() {
|
||||||
|
const blocks = new Map();
|
||||||
|
return {
|
||||||
|
getBlock: vi.fn((x, y, z) => {
|
||||||
|
const key = `${x},${y},${z}`;
|
||||||
|
return blocks.get(key) || null;
|
||||||
|
}),
|
||||||
|
getWorldBlocks: vi.fn((limit) => {
|
||||||
|
const all = [];
|
||||||
|
for (const [key, val] of blocks) {
|
||||||
|
const [x, y, z] = key.split(',').map(Number);
|
||||||
|
all.push({ x, y, z, block_name: val.name, metadata: val.metadata || 0, discovered_by: val.discoveredBy, discovered_at: val.timestamp });
|
||||||
|
if (all.length >= limit) break;
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}),
|
||||||
|
getWorldBlockCount: vi.fn(() => blocks.size),
|
||||||
|
getWorldBlocksInArea: vi.fn(() => []),
|
||||||
|
// Helper for test setup
|
||||||
|
_blocks: blocks,
|
||||||
|
_addBlock(x, y, z, name) {
|
||||||
|
blocks.set(`${x},${y},${z}`, { name, metadata: 0, discoveredBy: 1, timestamp: Date.now() });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('WorldBlockCache', () => {
|
||||||
|
let db, cache;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = makeDb();
|
||||||
|
cache = new WorldBlockCache(db, 5); // Small capacity for testing eviction
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for missing blocks', () => {
|
||||||
|
expect(cache.get('0,0,0')).toBeUndefined();
|
||||||
|
expect(db.getBlock).toHaveBeenCalledWith(0, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cache blocks from DB on first access', () => {
|
||||||
|
db._addBlock(1, 2, 3, 'minecraft:stone');
|
||||||
|
|
||||||
|
const block = cache.get('1,2,3');
|
||||||
|
expect(block).toBeDefined();
|
||||||
|
expect(block.name).toBe('minecraft:stone');
|
||||||
|
|
||||||
|
// Second access should not hit DB
|
||||||
|
cache.get('1,2,3');
|
||||||
|
expect(db.getBlock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set and retrieve blocks', () => {
|
||||||
|
cache.set('5,5,5', { name: 'minecraft:dirt', metadata: 0 });
|
||||||
|
|
||||||
|
const block = cache.get('5,5,5');
|
||||||
|
expect(block.name).toBe('minecraft:dirt');
|
||||||
|
// Should not hit DB since we just set it
|
||||||
|
expect(db.getBlock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete blocks from cache', () => {
|
||||||
|
cache.set('1,1,1', { name: 'minecraft:stone' });
|
||||||
|
expect(cache.delete('1,1,1')).toBe(true);
|
||||||
|
|
||||||
|
// Now should fall through to DB
|
||||||
|
const result = cache.get('1,1,1');
|
||||||
|
expect(db.getBlock).toHaveBeenCalledWith(1, 1, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should evict LRU entries when over capacity', () => {
|
||||||
|
// Fill cache to capacity (5)
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
cache.set(`${i},0,0`, { name: `block_${i}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access block_0 to make it recently used
|
||||||
|
cache.get('0,0,0');
|
||||||
|
|
||||||
|
// Add one more → should evict the LRU entry (block_1, since block_0 was just accessed)
|
||||||
|
cache.set('5,0,0', { name: 'block_5' });
|
||||||
|
|
||||||
|
// block_1 should have been evicted (we check internal cache size)
|
||||||
|
expect(cache._cache.size).toBe(5);
|
||||||
|
expect(cache._cache.has('1,0,0')).toBe(false);
|
||||||
|
expect(cache._cache.has('0,0,0')).toBe(true); // recently used
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should report size from DB count', () => {
|
||||||
|
db._addBlock(1, 1, 1, 'stone');
|
||||||
|
db._addBlock(2, 2, 2, 'dirt');
|
||||||
|
expect(cache.size).toBe(2);
|
||||||
|
expect(db.getWorldBlockCount).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Cached — no second call
|
||||||
|
const _ = cache.size;
|
||||||
|
expect(db.getWorldBlockCount).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should invalidate size cache on set/delete', () => {
|
||||||
|
const _ = cache.size;
|
||||||
|
expect(db.getWorldBlockCount).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
cache.set('1,1,1', { name: 'stone' });
|
||||||
|
const __ = cache.size;
|
||||||
|
expect(db.getWorldBlockCount).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should iterate all entries from DB', () => {
|
||||||
|
db._addBlock(1, 1, 1, 'stone');
|
||||||
|
db._addBlock(2, 2, 2, 'dirt');
|
||||||
|
|
||||||
|
const entries = [...cache.entries()];
|
||||||
|
expect(entries).toHaveLength(2);
|
||||||
|
expect(db.getWorldBlocks).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return blocks formatted for API', () => {
|
||||||
|
db._addBlock(3, 3, 3, 'minecraft:diamond_ore');
|
||||||
|
|
||||||
|
const blocks = cache.getAllBlocksForAPI(100);
|
||||||
|
expect(blocks).toHaveLength(1);
|
||||||
|
expect(blocks[0]).toMatchObject({ x: 3, y: 3, z: 3, name: 'minecraft:diamond_ore' });
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user