|
1 | 1 | import { describe, expect, test } from "bun:test" |
2 | 2 | import { createStore } from "solid-js/store" |
3 | 3 | import { QueryClient } from "@tanstack/solid-query" |
4 | | -import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client" |
| 4 | +import type { Config, OpencodeClient, Project, Session } from "@opencode-ai/sdk/v2/client" |
5 | 5 | import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" |
6 | 6 | import { bootstrapDirectory, loadPathQuery, loadProvidersQuery } from "./bootstrap" |
7 | 7 | import type { State, VcsCache } from "./types" |
| 8 | +import { createServerSession } from "../server-session" |
8 | 9 | import { ServerScope } from "@/utils/server-scope" |
9 | 10 |
|
10 | 11 | const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse |
11 | 12 |
|
| 13 | +function directoryState() { |
| 14 | + return createStore<State>({ |
| 15 | + status: "loading", |
| 16 | + agent: [], |
| 17 | + command: [], |
| 18 | + reference: [], |
| 19 | + project: "", |
| 20 | + projectMeta: undefined, |
| 21 | + icon: undefined, |
| 22 | + provider_ready: true, |
| 23 | + provider, |
| 24 | + config: {}, |
| 25 | + path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" }, |
| 26 | + session: [], |
| 27 | + sessionTotal: 0, |
| 28 | + session_status: {}, |
| 29 | + session_working(id: string) { |
| 30 | + return this.session_status[id]?.type !== "idle" |
| 31 | + }, |
| 32 | + session_diff: {}, |
| 33 | + todo: {}, |
| 34 | + permission: {}, |
| 35 | + question: {}, |
| 36 | + mcp_ready: true, |
| 37 | + mcp: {}, |
| 38 | + mcp_resource: {}, |
| 39 | + lsp_ready: true, |
| 40 | + lsp: [], |
| 41 | + vcs: undefined, |
| 42 | + limit: 5, |
| 43 | + message: {}, |
| 44 | + part: {}, |
| 45 | + part_text_accum_delta: {}, |
| 46 | + }) |
| 47 | +} |
| 48 | + |
12 | 49 | describe("bootstrapDirectory", () => { |
13 | 50 | test("marks a loading directory partial during bootstrap and complete after success", async () => { |
14 | 51 | const mcpReads: string[] = [] |
15 | | - const [store, setStore] = createStore<State>({ |
16 | | - status: "loading", |
17 | | - agent: [], |
18 | | - command: [], |
19 | | - reference: [], |
20 | | - project: "", |
21 | | - projectMeta: undefined, |
22 | | - icon: undefined, |
23 | | - provider_ready: true, |
24 | | - provider, |
25 | | - config: {}, |
26 | | - path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" }, |
27 | | - session: [], |
28 | | - sessionTotal: 0, |
29 | | - session_status: {}, |
30 | | - session_working(id: string) { |
31 | | - return this.session_status[id]?.type !== "idle" |
32 | | - }, |
33 | | - session_diff: {}, |
34 | | - todo: {}, |
35 | | - permission: {}, |
36 | | - question: {}, |
37 | | - mcp_ready: true, |
38 | | - mcp: {}, |
39 | | - mcp_resource: {}, |
40 | | - lsp_ready: true, |
41 | | - lsp: [], |
42 | | - vcs: undefined, |
43 | | - limit: 5, |
44 | | - message: {}, |
45 | | - part: {}, |
46 | | - part_text_accum_delta: {}, |
47 | | - }) |
| 52 | + const [store, setStore] = directoryState() |
48 | 53 |
|
49 | 54 | await bootstrapDirectory({ |
50 | 55 | directory: "/project", |
@@ -93,6 +98,66 @@ describe("bootstrapDirectory", () => { |
93 | 98 | expect(store.status).toBe("complete") |
94 | 99 | expect(mcpReads).toEqual([]) |
95 | 100 | }) |
| 101 | + |
| 102 | + test("seeds session status even while warming session info stalls", async () => { |
| 103 | + const [store, setStore] = directoryState() |
| 104 | + const stalled = Promise.withResolvers<never>() |
| 105 | + const client = { |
| 106 | + app: { agents: async () => ({ data: [{ name: "build", mode: "primary" }] }) }, |
| 107 | + config: { get: async () => ({ data: {} }) }, |
| 108 | + session: { |
| 109 | + status: async () => ({ data: { ses_busy: { type: "busy" } } }), |
| 110 | + get: () => stalled.promise, |
| 111 | + }, |
| 112 | + vcs: { get: async () => ({ data: undefined }) }, |
| 113 | + command: { list: async () => ({ data: [] }) }, |
| 114 | + permission: { list: async () => ({ data: [] }) }, |
| 115 | + question: { list: async () => ({ data: [] }) }, |
| 116 | + v2: { reference: { list: async () => ({ data: { data: [] } }) } }, |
| 117 | + mcp: { status: async () => ({ data: {} }) }, |
| 118 | + provider: { list: async () => ({ data: { all: [], connected: [], default: {} } }) }, |
| 119 | + } as unknown as OpencodeClient |
| 120 | + const session = createServerSession(client) |
| 121 | + const stale: Session = { |
| 122 | + id: "ses_stale", |
| 123 | + slug: "ses_stale", |
| 124 | + projectID: "project", |
| 125 | + directory: "/project", |
| 126 | + title: "stale", |
| 127 | + version: "1", |
| 128 | + time: { created: 1, updated: 1 }, |
| 129 | + } |
| 130 | + session.remember(stale) |
| 131 | + session.set("session_status", stale.id, { type: "busy" }) |
| 132 | + |
| 133 | + await bootstrapDirectory({ |
| 134 | + directory: "/project", |
| 135 | + scope: ServerScope.local, |
| 136 | + mcp: false, |
| 137 | + global: { |
| 138 | + config: {} satisfies Config, |
| 139 | + path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" }, |
| 140 | + project: [{ id: "project", worktree: "/project" } as Project], |
| 141 | + provider, |
| 142 | + }, |
| 143 | + sdk: client, |
| 144 | + store, |
| 145 | + setStore, |
| 146 | + vcsCache: { setStore() {} } as unknown as VcsCache, |
| 147 | + loadSessions() {}, |
| 148 | + translate: (key) => key, |
| 149 | + queryClient: new QueryClient(), |
| 150 | + session, |
| 151 | + }) |
| 152 | + |
| 153 | + const deadline = Date.now() + 500 |
| 154 | + while (!session.data.session_working("ses_busy") && Date.now() < deadline) { |
| 155 | + await new Promise((resolve) => setTimeout(resolve, 10)) |
| 156 | + } |
| 157 | + |
| 158 | + expect(session.data.session_status["ses_busy"]?.type).toBe("busy") |
| 159 | + expect(session.data.session_status[stale.id]).toBeUndefined() |
| 160 | + }) |
96 | 161 | }) |
97 | 162 |
|
98 | 163 | describe("query keys", () => { |
|
0 commit comments