opencode/packages/app/src/context/server-sync.test.ts
2026-07-24 11:39:40 +08:00

231 lines
7 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
import type {
McpApi,
McpListInput,
McpResourceCatalogInput,
SessionApi,
SessionInfo,
SessionListInput,
} from "@opencode-ai/client/promise"
import { QueryClient } from "@tanstack/solid-query"
import { canDisposeDirectory, pickDirectoriesToEvict } from "./global-sync/eviction"
import { estimateRootSessionTotal, loadRootSessions } from "./global-sync/session-load"
import { loadActiveSessionsQuery, loadMcpQuery, loadMcpResourcesQuery, seedActiveSessionStatuses } from "./server-sync"
import { ServerScope } from "@/utils/server-scope"
import { createServerSession } from "./server-session"
describe("MCP queries", () => {
test("loads current servers for the requested location", async () => {
const calls: unknown[] = []
const queryClient = new QueryClient()
const result = await queryClient.fetchQuery(
loadMcpQuery(ServerScope.local, "/project", {
list: async (input: McpListInput = {}) => {
calls.push(input)
return {
location: { directory: "/project", project: { id: "project", directory: "/project" } },
data: [
{ name: "docs", status: { status: "connected" } },
{ name: "search", status: { status: "pending" } },
],
}
},
} as unknown as McpApi),
)
expect(calls).toEqual([{ location: { directory: "/project" } }])
expect(result).toEqual({ docs: { status: "connected" }, search: { status: "pending" } })
})
test("loads and keys the current resource catalog", async () => {
const calls: unknown[] = []
const queryClient = new QueryClient()
const result = await queryClient.fetchQuery(
loadMcpResourcesQuery(ServerScope.local, "/project", {
resource: {
catalog: async (input: McpResourceCatalogInput = {}) => {
calls.push(input)
return {
location: { directory: "/project", project: { id: "project", directory: "/project" } },
data: {
resources: [{ server: "docs", name: "Guide", uri: "docs://guide" }],
templates: [],
},
}
},
},
} as unknown as McpApi),
)
expect(calls).toEqual([{ location: { directory: "/project" } }])
expect(result).toEqual({ "docs:docs://guide": { server: "docs", name: "Guide", uri: "docs://guide" } })
})
})
describe("active session query", () => {
test("loads active sessions immediately and once per server cache", async () => {
let calls = 0
const queryClient = new QueryClient()
const options = loadActiveSessionsQuery(ServerScope.local, {
active: async () => {
calls++
return { ses_running: { type: "running" } }
},
})
expect(await queryClient.fetchQuery(options)).toEqual({ ses_running: { type: "running" } })
expect(await queryClient.fetchQuery(options)).toEqual({ ses_running: { type: "running" } })
expect(calls).toBe(1)
expect(options.enabled).toBe(true)
expect([...options.queryKey]).toEqual([ServerScope.local, "activeSessions"])
})
test("does not overwrite statuses already written by events", () => {
const session = createServerSession({} as OpencodeClient)
session.set("session_status", "ses_retry", { type: "retry", attempt: 2, message: "retrying", next: 10 })
seedActiveSessionStatuses(session, {
ses_running: { type: "running" },
ses_retry: { type: "running" },
})
expect(session.data.session_status.ses_running).toEqual({ type: "busy" })
expect(session.data.session_status.ses_retry).toEqual({
type: "retry",
attempt: 2,
message: "retrying",
next: 10,
})
})
})
describe("pickDirectoriesToEvict", () => {
test("keeps pinned stores and evicts idle stores", () => {
const now = 5_000
const picks = pickDirectoriesToEvict({
stores: ["a", "b", "c", "d"],
state: new Map([
["a", { lastAccessAt: 1_000 }],
["b", { lastAccessAt: 4_900 }],
["c", { lastAccessAt: 4_800 }],
["d", { lastAccessAt: 3_000 }],
]),
pins: new Set(["a"]),
max: 2,
ttl: 1_500,
now,
})
expect(picks).toEqual(["d", "c"])
})
})
describe("loadRootSessions", () => {
test("loads and normalizes a limited page of root sessions", async () => {
const calls: SessionListInput[] = []
const result = await loadRootSessions({
api: {
list: async (query = {}) => {
calls.push(query)
return { data: [sessionInfo("session-1")], cursor: {} }
},
} satisfies Pick<SessionApi, "list">,
directory: "dir",
limit: 10,
})
expect(result.data).toEqual([
expect.objectContaining({ id: "session-1", directory: "dir", slug: "session-1", version: "" }),
])
expect(result.limited).toBe(true)
expect(calls).toEqual([{ directory: "dir", parentID: null, limit: 10, order: "desc" }])
})
test("propagates list failures", () => {
expect(
loadRootSessions({
api: {
list: async () => {
throw new Error("failed")
},
} satisfies Pick<SessionApi, "list">,
directory: "dir",
limit: 25,
}),
).rejects.toThrow("failed")
})
})
function sessionInfo(id: string) {
return {
id,
projectID: "project-1",
agent: "build",
model: { id: "model-1", providerID: "provider-1" },
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
time: { created: 1, updated: 1 },
title: id,
location: { directory: "dir" },
} as SessionInfo
}
describe("estimateRootSessionTotal", () => {
test("keeps exact total for full fetches", () => {
expect(estimateRootSessionTotal({ count: 42, limit: 10, limited: false })).toBe(42)
})
test("marks has-more for full-limit limited fetches", () => {
expect(estimateRootSessionTotal({ count: 10, limit: 10, limited: true })).toBe(11)
})
test("keeps exact total when limited fetch is under limit", () => {
expect(estimateRootSessionTotal({ count: 9, limit: 10, limited: true })).toBe(9)
})
})
describe("canDisposeDirectory", () => {
test("rejects pinned or inflight directories", () => {
expect(
canDisposeDirectory({
directory: "dir",
hasStore: true,
pinned: true,
booting: false,
loadingSessions: false,
}),
).toBe(false)
expect(
canDisposeDirectory({
directory: "dir",
hasStore: true,
pinned: false,
booting: true,
loadingSessions: false,
}),
).toBe(false)
expect(
canDisposeDirectory({
directory: "dir",
hasStore: true,
pinned: false,
booting: false,
loadingSessions: true,
}),
).toBe(false)
})
test("accepts idle unpinned directory store", () => {
expect(
canDisposeDirectory({
directory: "dir",
hasStore: true,
pinned: false,
booting: false,
loadingSessions: false,
}),
).toBe(true)
})
})