import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { createEngine, WEATHER_ENTITY_ID } from "@nengine/core"; import { createEngineNamespaces, McpGateway } from "../src/index.js"; const TIME_YAML = [ "id: default", "dayLengthTicks: 480", "seasons:", " - { name: spring, dayCount: 28 }", " - { name: summer, dayCount: 28 }", "start: { day: 1, season: spring }", "", ].join("\n"); const FARM_YAML = [ "id: farm", "width: 2", "height: 2", 'tilesetRef: "tiles"', "spawn: { x: 0, y: 0 }", "layers:", " - name: ground", " data:", " - [a, b]", " - [b, a]", "", ].join("\n"); const WEATHER_YAML = [ "id: default", "defaults:", " sunny: { name: Sunny }", " rain: { name: Rain }", "seasons:", " - season: spring", " weathers:", " - { id: sunny, weight: 70 }", " - { id: rain, weight: 30 }", "", ].join("\n"); interface Setup { engine: ReturnType; gateway: McpGateway; tmp: string; close: () => Promise; } async function setup(): Promise { const tmp = await mkdtemp(join(tmpdir(), "farm-weather-ns-")); await mkdir(join(tmp, "maps"), { recursive: true }); await writeFile(join(tmp, "time.yaml"), TIME_YAML); await writeFile(join(tmp, "maps", "farm.yaml"), FARM_YAML); await writeFile(join(tmp, "weather.yaml"), WEATHER_YAML); const engine = createEngine({ contentDir: tmp }); const gateway = new McpGateway(); for (const namespace of createEngineNamespaces(engine, { gateway })) { gateway.register(namespace); } await engine.start(); return { engine, gateway, tmp, close: async () => { await engine.stop(); await rm(tmp, { recursive: true, force: true }); }, }; } async function callText(gateway: McpGateway, name: string, args: Record = {}): Promise<{ ok: boolean; text: string; }> { const result = await gateway.callTool(name, args); return { ok: result.isError !== true, text: result.content[0]?.text ?? "" }; } describe("weather namespace", () => { it("weather.get returns the initial state (current season, display name)", async () => { const { engine, gateway, close } = await setup(); try { expect(engine.world.get(WEATHER_ENTITY_ID)?.weatherId).toBe("sunny"); const got = await callText(gateway, "weather.get"); expect(got.ok).toBe(true); expect(JSON.parse(got.text)).toEqual({ weatherId: "sunny", name: "Sunny", season: "spring" }); } finally { await close(); } }); it("weather.set forces a declared weather and returns the new state with from", async () => { const { engine, gateway, close } = await setup(); try { const set = await callText(gateway, "weather.set", { weatherId: "rain" }); expect(set.ok).toBe(true); expect(JSON.parse(set.text)).toEqual({ weatherId: "rain", name: "Rain", season: "spring", from: "sunny" }); expect(engine.world.get(WEATHER_ENTITY_ID)?.weatherId).toBe("rain"); const same = await callText(gateway, "weather.set", { weatherId: "rain" }); expect(JSON.parse(same.text)).toEqual({ weatherId: "rain", name: "Rain", season: "spring" }); } finally { await close(); } }); it("weather.set rejects unknown ids and blank ids", async () => { const { gateway, close } = await setup(); try { const unknown = await callText(gateway, "weather.set", { weatherId: "blizzard" }); expect(unknown.ok).toBe(false); expect(unknown.text).toMatch(/unknown weather id: blizzard/); const blank = await callText(gateway, "weather.set", { weatherId: "" }); expect(blank.ok).toBe(false); expect(blank.text).toMatch(/non-empty string/); } finally { await close(); } }); it("weather://state resource reflects the current weather", async () => { const { gateway, close } = await setup(); try { const state = await gateway.readResource("weather://state"); expect(JSON.parse(state.contents[0]?.text ?? "{}")).toEqual({ weatherId: "sunny", name: "Sunny", season: "spring" }); } finally { await close(); } }); it("weather.get reports an error result when no weather def is registered", async () => { const tmp = await mkdtemp(join(tmpdir(), "farm-weather-none-")); await mkdir(join(tmp, "maps"), { recursive: true }); await writeFile(join(tmp, "time.yaml"), TIME_YAML); await writeFile(join(tmp, "maps", "farm.yaml"), FARM_YAML); const engine = createEngine({ contentDir: tmp }); const gateway = new McpGateway(); for (const namespace of createEngineNamespaces(engine, { gateway })) { gateway.register(namespace); } await engine.start(); try { const got = await callText(gateway, "weather.get"); expect(got.ok).toBe(false); expect(got.text).toMatch(/no weather def registered/); } finally { await engine.stop(); await rm(tmp, { recursive: true, force: true }); } }); });