Map Traveler MCP
by mfukushim
- map-traveler-mcp
- test
// @vitest-environment node
import {Effect, Logger, LogLevel} from "effect";
import {describe, expect, it} from "@effect/vitest"
import {RunnerService, RunnerServiceLive} from "../src/RunnerService.js";
import {runPromise} from "effect/Effect";
import {FetchHttpClient} from "@effect/platform";
import {DbService, DbServiceLive, RunStatus} from "../src/DbService.js";
import {MapDef, MapServiceLive} from "../src/MapService.js";
import {ImageServiceLive} from "../src/ImageService.js";
import {StoryServiceLive} from "../src/StoryService.js";
import {McpLogService, McpLogServiceLive} from "../src/McpLogService.js";
import {AnswerError} from "../src/mapTraveler.js";
import * as fs from "node:fs";
import dayjs from "dayjs";
describe("Runner", () => {
it("getCurrentView_practice", async () => {
// vitest --run --testNamePattern=calcDomesticTravelRoute MapService.test.ts
const res = await Effect.gen(function* () {
return yield* RunnerService.getCurrentView(dayjs(), false, false, true) //
}).pipe(
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive,
FetchHttpClient.layer, McpLogServiceLive]), // layer
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(a => Effect.logError(a)),
Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed([])),
Effect.tap(a => Effect.log(a)),
runPromise
)
expect(res).toBeInstanceOf(Array)
})
it("sumDurationSec", async () => {
const s = fs.readFileSync('tools/test/routeSample.json', {encoding: 'utf-8'});
const res = await Effect.gen(function* () {
return yield* RunnerService.sumDurationSec(JSON.parse(s) as typeof MapDef.RouteArraySchema.Type)
}).pipe(
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive,
FetchHttpClient.layer, McpLogServiceLive]),
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.tap(a => Effect.log(a)),
runPromise
)
expect(res).toBe(41216)
})
it("routesToDirectionStep", async () => {
const s = fs.readFileSync('tools/test/routeSample.json', {encoding: 'utf-8'});
const res = await Effect.gen(function* () {
return yield* RunnerService.routesToDirectionStep(JSON.parse(s) as typeof MapDef.RouteArraySchema.Type)
}).pipe(
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive,
FetchHttpClient.layer, McpLogServiceLive]),
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.andThen(a => a.map(b => ({a: b.start, b: b.end}))),
runPromise
)
expect(res).toStrictEqual([
{a: 0, b: 104,}, {a: 104, b: 276,}, {a: 276, b: 764,}, {a: 764, b: 1624,}, {a: 1624, b: 1684,}, {
a: 1684,
b: 40180,
}, {a: 40180, b: 40316,}, {a: 40316, b: 40388,}, {a: 40388, b: 40560,}, {a: 40560, b: 40968,}, {
a: 40968,
b: 41216,
},
])
})
it("makeView", async () => {
// envコメントアウトでも通るがGoogle map apiを設定している状態が厳密
const now = dayjs()
const s = fs.readFileSync('tools/test/routeSample.json', {encoding: 'utf-8'});
// このサンプルの秒ステップ [{0,104},{104,276},{276,764},{764,1624},{1624,1684},{1684,40180},{40180,40316},{40316,40388},{40388,40560},{40560,40968},{40968,41216}]
const runStatus: RunStatus = {
id: 1,
avatarId: 1,
tripId: 1,
tilEndEpoch: now.add(41216, 'second').unix(), // このサンプルは41216秒 約11.5時間
status: "running",
from: 'from',
to: 'to', // 現在処理中の行き先,現在位置
destination: 'dest', // 計画中の行き先
startLat: 47.62017549999999,
startLng: 0,
endLat: 45.5190171,
endLng: -122.6798007,
durationSec: 1,
distanceM: 1,
startTime: now.toDate(),
endTime: null,
startCountry: null,
endCountry: null,
startTz: 'Asia/Tokyo',
endTz: 'Asia/Tokyo',
currentPathNo: -1,
currentStepNo: -1,
}
const pctList = [0,5,40,60,90,100]
const res = await Effect.gen(function* () {
return yield* Effect.forEach(pctList, a => RunnerService.makeView(runStatus, a / 100, false, true, true, false, s))
}).pipe(
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.tap(a => McpLogService.logTraceToolsRes(a.flat())),
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive, FetchHttpClient.layer]),
runPromise
)
expect(res).toBeInstanceOf(Array)
expect(res.flat().filter(a => a.type === 'text').every(b => ['current location is','\'I am in a hotel'].some(c => b.text?.includes(c)))).toBeTruthy()
})
it("getElapsedView_Running", async () => {
// envコメントアウトでも通るがGoogle map apiを設定している状態が厳密
const now = dayjs()
const s = fs.readFileSync('tools/test/routeSample.json', {encoding: 'utf-8'});
// このサンプルの秒ステップ [{0,104},{104,276},{276,764},{764,1624},{1624,1684},{1684,40180},{40180,40316},{40316,40388},{40388,40560},{40560,40968},{40968,41216}]
const runStatus: RunStatus = {
id: 1,
avatarId: 1,
tripId: 1,
tilEndEpoch: now.add(41216, 'second').unix(), // このサンプルは41216秒 約11.5時間
status: "running",
from: 'from',
to: 'to', // 現在処理中の行き先,現在位置
destination: 'dest', // 計画中の行き先
startLat: 47.62017549999999,
startLng: -122.3493189,
endLat: 45.5190171,
endLng: -122.6798007,
durationSec: 1,
distanceM: 1,
startTime: now.toDate(),
endTime: null,
startCountry: null,
endCountry: null,
startTz: 'Asia/Tokyo',
endTz: 'Asia/Tokyo',
currentPathNo: -1,
currentStepNo: -1,
}
const pctList = [0,5,40,60,90,100]
const res = await Effect.gen(function* () {
yield *DbService.saveRunStatus(runStatus)
return yield* RunnerService.getElapsedView(pctList[1] / 100,s)
// return yield* Effect.forEach(pctList, a => RunnerService.getElapsedView(a / 100))
}).pipe(
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.tap(a => McpLogService.logTraceToolsRes(a.flat())),
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive, FetchHttpClient.layer]),
runPromise
)
expect(res).toBeInstanceOf(Array)
expect(res.flat().filter(a => a.type === 'text').every(b => ['current location is','\'I am in a hotel'].some(c => b.text?.includes(c)))).toBeTruthy()
const res2 = await Effect.gen(function* () {
return yield *DbService.getRecentRunStatus()
}).pipe(
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.tap(a => McpLogService.logTrace('end runStatus:',a)),
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive, FetchHttpClient.layer]),
runPromise
)
console.log(res2)
expect(res2.status).toBe('stop')
})
it("getElapsedView_stop", async () => {
// vitest --run --testNamePattern=getElapsedView_stop RunnerService.test.ts
// envコメントアウトでも通るがGoogle map apiを設定している状態が厳密
const now = dayjs()
const s = fs.readFileSync('tools/test/routeSample.json', {encoding: 'utf-8'});
// このサンプルの秒ステップ [{0,104},{104,276},{276,764},{764,1624},{1624,1684},{1684,40180},{40180,40316},{40316,40388},{40388,40560},{40560,40968},{40968,41216}]
const runStatus: RunStatus = {
id: 1,
avatarId: 1,
tripId: 1,
tilEndEpoch: now.add(41216, 'second').unix(), // このサンプルは41216秒 約11.5時間
status: "stop",
from: 'from',
to: '365 Thomas St, Seattle, WA 98109, USA', // 現在処理中の行き先,現在位置
destination: 'dest', // 計画中の行き先
startLat: 0,
startLng: 0,
endLat: 47.62017549999999,
endLng: -122.3493189,
durationSec: 1,
distanceM: 1,
startTime: now.toDate(),
endTime: null,
startCountry: null,
endCountry: null,
startTz: 'Asia/Tokyo',
endTz: 'Asia/Tokyo',
currentPathNo: -1,
currentStepNo: -1,
}
const pctList = [0,5,40,60,90,100]
const res = await Effect.gen(function* () {
yield *DbService.saveEnv("destination","Pioneer Courthouse Square, 701 SW 6th Ave, Portland, OR 97204, USA")
yield *DbService.saveRunStatus(runStatus)
return yield* RunnerService.getElapsedView(pctList[5] / 100,s)
// return yield* Effect.forEach(pctList, a => RunnerService.getElapsedView(a / 100))
}).pipe(
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.tap(a => McpLogService.logTraceToolsRes(a.flat())),
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive, FetchHttpClient.layer]),
runPromise
)
expect(res).toBeInstanceOf(Array)
expect(res.flat().filter(a => a.type === 'text').every(b => ['current location is','\'I am in a hotel'].some(c => b.text?.includes(c)))).toBeTruthy()
const res2 = await Effect.gen(function* () {
return yield *DbService.getRecentRunStatus()
}).pipe(
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.tapError(Effect.logError),
// Effect.catchIf(a => a instanceof AnswerError, e => Effect.succeed({content: []})),
Effect.tap(a => McpLogService.logTrace('end runStatus:',a)),
Effect.provide([RunnerServiceLive, DbServiceLive, MapServiceLive, ImageServiceLive, StoryServiceLive, FetchHttpClient.layer]),
runPromise
)
console.log(res2)
expect(res2.status).toBe('stop')
})
})