Skip to main content
Glama
spawn-populated-location.test.ts14.8 kB
/** * Tests for spawn_populated_location composite tool */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'fs'; import { initDB } from '../../src/storage/db.js'; import { migrate } from '../../src/storage/migrations.js'; import { handleSpawnPopulatedLocation } from '../../src/server/composite-tools.js'; import { POIRepository } from '../../src/storage/repos/poi.repo.js'; import { SpatialRepository } from '../../src/storage/repos/spatial.repo.js'; import { CharacterRepository } from '../../src/storage/repos/character.repo.js'; import { ItemRepository } from '../../src/storage/repos/item.repo.js'; const TEST_DB_PATH = 'test-spawn-populated-location.db'; describe('spawn_populated_location', () => { let db: ReturnType<typeof initDB>; let poiRepo: POIRepository; let spatialRepo: SpatialRepository; let charRepo: CharacterRepository; let itemRepo: ItemRepository; beforeEach(() => { // Clean up any existing test DB if (fs.existsSync(TEST_DB_PATH)) { fs.unlinkSync(TEST_DB_PATH); } // Use file-based DB for tests (handler uses getDb which handles :memory: for NODE_ENV=test) process.env.NODE_ENV = 'test'; db = initDB(TEST_DB_PATH); migrate(db); poiRepo = new POIRepository(db); spatialRepo = new SpatialRepository(db); charRepo = new CharacterRepository(db); itemRepo = new ItemRepository(db); }); afterEach(() => { db.close(); if (fs.existsSync(TEST_DB_PATH)) { fs.unlinkSync(TEST_DB_PATH); } }); const mockCtx = { sessionId: 'test-session', startedAt: new Date(), lastActiveAt: new Date() }; describe('POI Creation', () => { it('creates a basic POI without rooms', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Ancient Monument', category: 'landmark', icon: 'monument', position: '50,30', description: 'An ancient monument marking a forgotten battle', tags: ['ancient', 'monument'] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.poi.name).toBe('Ancient Monument'); expect(response.poi.category).toBe('landmark'); expect(response.poi.icon).toBe('monument'); expect(response.poi.position).toEqual({ x: 50, y: 30 }); expect(response.network).toBeNull(); expect(response.rooms).toHaveLength(0); }); it('creates POI with object position', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Test Location', category: 'settlement', icon: 'village', position: { x: 100, y: 75 } }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.poi.position).toEqual({ x: 100, y: 75 }); }); it('creates POI with discovery settings', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Hidden Cave', category: 'hidden', icon: 'cave', position: '10,10', discoveryState: 'unknown', discoveryDC: 15 }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.poi.discoveryState).toBe('unknown'); }); }); describe('Room Network Creation', () => { it('creates room network with auto-linked rooms', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Small Dungeon', category: 'dungeon', icon: 'dungeon', position: '50,50', rooms: [ { name: 'Entrance Hall', description: 'A dusty entrance with cobwebs covering the walls.' }, { name: 'Guard Room', description: 'A small room with old weapon racks along the walls.' }, { name: 'Treasure Chamber', description: 'A chamber filled with glittering coins and gems.' } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.network).not.toBeNull(); expect(response.network.roomCount).toBe(3); expect(response.rooms).toHaveLength(3); expect(response.rooms[0].name).toBe('Entrance Hall'); }); it('uses specified biome for rooms', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Tavern', category: 'commercial', icon: 'inn', position: '50,50', rooms: [ { name: 'Common Room', description: 'A warm tavern common room with tables.', biome: 'urban' } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.rooms).toHaveLength(1); }); }); describe('Inhabitant Spawning', () => { it('spawns creatures from templates', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Goblin Cave', category: 'dungeon', icon: 'cave', position: '50,50', rooms: [ { name: 'Cave Entrance', description: 'A dark cave entrance with wet stone.' } ], inhabitants: [ { template: 'goblin', room: 0, count: 2 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.inhabitants).toHaveLength(2); expect(response.inhabitants[0].name).toBe('Goblin 1'); expect(response.inhabitants[1].name).toBe('Goblin 2'); }); it('spawns named NPCs', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Village Inn', category: 'commercial', icon: 'inn', position: '50,50', rooms: [ { name: 'Common Room', description: 'A warm common room with fireplace.' } ], inhabitants: [ { name: 'Bartholomew', race: 'Human', characterType: 'npc', room: 0 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.inhabitants).toHaveLength(1); expect(response.inhabitants[0].name).toBe('Bartholomew'); }); it('spawns boss creatures with custom names', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Dragon Lair', category: 'dungeon', icon: 'cave', position: '50,50', inhabitants: [ { template: 'hobgoblin:captain', name: 'Grishnak the Terrible' } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.inhabitants[0].name).toBe('Grishnak the Terrible'); }); it('places inhabitants in correct rooms', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Bandit Hideout', category: 'dungeon', icon: 'cave', position: '50,50', rooms: [ { name: 'Entrance', description: 'A hidden entrance behind bushes.' }, { name: 'Barracks', description: 'Sleeping quarters with bedrolls.' } ], inhabitants: [ { template: 'bandit', room: 0 }, { template: 'bandit', room: 1, count: 3 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); // Check room assignments const entranceInhabitants = response.inhabitants.filter((i: any) => i.roomName === 'Entrance'); const barracksInhabitants = response.inhabitants.filter((i: any) => i.roomName === 'Barracks'); expect(entranceInhabitants).toHaveLength(1); expect(barracksInhabitants).toHaveLength(3); }); }); describe('Loot Placement', () => { it('places loot items in rooms', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Treasure Vault', category: 'dungeon', icon: 'dungeon', position: '50,50', rooms: [ { name: 'Vault', description: 'A treasure vault with stone walls.' } ], loot: [ { preset: 'longsword', room: 0 }, { preset: 'potion_healing', room: 0, count: 3 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.loot).toHaveLength(2); expect(response.loot[0].name).toBe('Longsword'); expect(response.loot[1].count).toBe(3); }); }); describe('Complete Location Setup', () => { it('creates a complete dungeon with rooms, creatures, and loot', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Shadowfang Cave', category: 'dungeon', icon: 'cave', position: '50,30', description: 'A dark cave system rumored to house goblin raiders', level: 3, tags: ['goblin', 'cave', 'treasure'], rooms: [ { name: 'Cave Entrance', description: 'A shadowy opening in the hillside with fresh tracks in the mud.', biome: 'cavern' }, { name: 'Guard Chamber', description: 'A small alcove where guards keep watch over the entrance.', biome: 'cavern' }, { name: 'Boss Lair', description: 'A larger chamber with crude furniture and stolen goods.', biome: 'cavern' } ], inhabitants: [ { template: 'goblin:warrior', room: 0, count: 2 }, { template: 'goblin:archer', room: 1 }, { template: 'hobgoblin:captain', name: 'Skullcrusher', room: 2 } ], loot: [ { preset: 'longsword', room: 2 }, { preset: 'potion_healing', room: 1, count: 2 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); // Verify POI expect(response.poi.name).toBe('Shadowfang Cave'); expect(response.poi.level).toBe(3); // Verify network and rooms expect(response.network.roomCount).toBe(3); expect(response.rooms).toHaveLength(3); // Verify inhabitants expect(response.summary.totalInhabitants).toBe(4); // 2 warriors + 1 archer + 1 captain expect(response.inhabitants.some((i: any) => i.name === 'Skullcrusher')).toBe(true); // Verify loot expect(response.summary.totalLootItems).toBe(3); // 1 sword + 2 potions // Verify message expect(response.message).toContain('Shadowfang Cave'); expect(response.message).toContain('3 rooms'); expect(response.message).toContain('4 inhabitants'); }); it('creates an inn with NPCs', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'The Prancing Pony', category: 'commercial', icon: 'inn', position: '100,75', population: 15, discoveryState: 'discovered', rooms: [ { name: 'Common Room', description: 'A warm tavern with a crackling fireplace and several occupied tables.', biome: 'urban' }, { name: 'Kitchen', description: 'The busy kitchen smells of fresh bread and roasting meat.', biome: 'urban' } ], inhabitants: [ { name: 'Barliman Butterbur', race: 'Human', characterType: 'npc', room: 0 }, { template: 'bandit', name: 'Suspicious Stranger', characterType: 'neutral', room: 0 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.poi.population).toBe(15); expect(response.poi.discoveryState).toBe('discovered'); expect(response.inhabitants).toHaveLength(2); }); }); describe('Edge Cases', () => { it('handles location without rooms, inhabitants, or loot', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Empty Landmark', category: 'landmark', icon: 'ruins', position: '50,50' }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.network).toBeNull(); expect(response.rooms).toHaveLength(0); expect(response.inhabitants).toHaveLength(0); expect(response.loot).toHaveLength(0); }); it('handles inhabitants without rooms (no placement)', async () => { const result = await handleSpawnPopulatedLocation({ worldId: 'world-123', name: 'Wandering Monsters', category: 'natural', icon: 'unknown', position: '50,50', inhabitants: [ { template: 'wolf', count: 2 } ] }, mockCtx); const response = JSON.parse(result.content[0].text); expect(response.inhabitants).toHaveLength(2); expect(response.inhabitants[0].roomId).toBeUndefined(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/rpg-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server