Skip to content

Commit 45f3f88

Browse files
committed
Fix bun tests by using global PTYManager in web server tests
- Modified test/web-server.test.ts to use the global manager instead of creating a new instance - This resolves the issue where the web server was not properly using the test manager instance - All tests now pass successfully
1 parent 62a87ff commit 45f3f88

6 files changed

Lines changed: 164 additions & 140 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
run: bun run build:all:prod
4949

5050
- name: Run tests
51-
run: bun test test/bun-pty-mwe.test.ts && bun test test/integration.test.ts && bun test test/npm-pack-integration.test.ts && bun test test/npm-pack-structure.test.ts && bun test test/pty-echo.test.ts && bun test test/pty-integration.test.ts && bun test test/pty-tools.test.ts && bun test test/types.test.ts && bun test test/web-server.test.ts && bun test test/websocket.test.ts
51+
run: bun test
5252

5353
dependency-review:
5454
runs-on: ubuntu-24.04

‎src/plugin/pty/manager.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function notifyRawOutput(sessionId: string, rawData: string): void {
2727
}
2828
}
2929

30-
class PTYManager {
30+
export class PTYManager {
3131
private lifecycleManager = new SessionLifecycleManager()
3232
private outputManager = new OutputManager()
3333
private notificationManager = new NotificationManager()

‎src/web/server/handlers/sessions.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { manager } from '../../../plugin/pty/manager.ts'
1+
import type { PTYManager } from '../../../plugin/pty/manager.ts'
22
import type { BunRequest } from 'bun'
33
import { JsonResponse, ErrorResponse } from './responses.ts'
44

5-
export async function getSessions(): Promise<Response> {
5+
export async function getSessions(manager: PTYManager): Promise<Response> {
66
const sessions = manager.list()
77
return new JsonResponse(sessions)
88
}
99

10-
export async function createSession(req: Request): Promise<Response> {
10+
export async function createSession(req: Request, manager: PTYManager): Promise<Response> {
1111
try {
1212
const body = (await req.json()) as {
1313
command: string
@@ -32,12 +32,15 @@ export async function createSession(req: Request): Promise<Response> {
3232
}
3333
}
3434

35-
export async function clearSessions(): Promise<Response> {
35+
export async function clearSessions(manager: PTYManager): Promise<Response> {
3636
manager.clearAllSessions()
3737
return new JsonResponse({ success: true })
3838
}
3939

40-
export async function getSession(req: BunRequest<'/api/sessions/:id'>): Promise<Response> {
40+
export async function getSession(
41+
req: BunRequest<'/api/sessions/:id'>,
42+
manager: PTYManager
43+
): Promise<Response> {
4144
const sessionId = req.params.id
4245
if (!sessionId || typeof sessionId !== 'string' || sessionId.trim() === '') {
4346
return new ErrorResponse('Invalid session ID', 400)
@@ -49,7 +52,10 @@ export async function getSession(req: BunRequest<'/api/sessions/:id'>): Promise<
4952
return new JsonResponse(session)
5053
}
5154

52-
export async function sendInput(req: BunRequest<'/api/sessions/:id/input'>): Promise<Response> {
55+
export async function sendInput(
56+
req: BunRequest<'/api/sessions/:id/input'>,
57+
manager: PTYManager
58+
): Promise<Response> {
5359
const sessionId = req.params.id
5460
if (!sessionId || typeof sessionId !== 'string' || sessionId.trim() === '') {
5561
return new ErrorResponse('Invalid session ID', 400)
@@ -69,7 +75,10 @@ export async function sendInput(req: BunRequest<'/api/sessions/:id/input'>): Pro
6975
}
7076
}
7177

72-
export async function killSession(req: BunRequest<'/api/sessions/:id/kill'>): Promise<Response> {
78+
export async function killSession(
79+
req: BunRequest<'/api/sessions/:id/kill'>,
80+
manager: PTYManager
81+
): Promise<Response> {
7382
const sessionId = req.params.id
7483
if (!sessionId || typeof sessionId !== 'string' || sessionId.trim() === '') {
7584
return new ErrorResponse('Invalid session ID', 400)
@@ -82,7 +91,8 @@ export async function killSession(req: BunRequest<'/api/sessions/:id/kill'>): Pr
8291
}
8392

8493
export async function getRawBuffer(
85-
req: BunRequest<'/api/sessions/:id/buffer/raw'>
94+
req: BunRequest<'/api/sessions/:id/buffer/raw'>,
95+
manager: PTYManager
8696
): Promise<Response> {
8797
const sessionId = req.params.id
8898
if (!sessionId || typeof sessionId !== 'string' || sessionId.trim() === '') {
@@ -98,7 +108,8 @@ export async function getRawBuffer(
98108
}
99109

100110
export async function getPlainBuffer(
101-
req: BunRequest<'/api/sessions/:id/buffer/plain'>
111+
req: BunRequest<'/api/sessions/:id/buffer/plain'>,
112+
manager: PTYManager
102113
): Promise<Response> {
103114
const sessionId = req.params.id
104115
if (!sessionId || typeof sessionId !== 'string' || sessionId.trim() === '') {

‎src/web/server/server.ts‎

Lines changed: 109 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Server, ServerWebSocket, BunRequest } from 'bun'
2-
import { manager, onRawOutput, setOnSessionUpdate } from '../../plugin/pty/manager.ts'
2+
import { manager, onRawOutput, setOnSessionUpdate, PTYManager } from '../../plugin/pty/manager.ts'
33
import type { WSMessage, ServerConfig } from '../shared/types.ts'
44
import { get404Response } from './handlers/static.ts'
55
import { handleHealth } from './handlers/health.ts'
@@ -50,116 +50,120 @@ function wrapWithSecurityHeaders(
5050
}
5151
}
5252

53-
function sendSessionList(ws: ServerWebSocket<any>): void {
54-
const sessions = manager.list()
55-
const sessionData = sessions.map((s) => ({
56-
id: s.id,
57-
title: s.title,
58-
description: s.description,
59-
command: s.command,
60-
status: s.status,
61-
exitCode: s.exitCode,
62-
pid: s.pid,
63-
lineCount: s.lineCount,
64-
createdAt: s.createdAt.toISOString(),
65-
}))
66-
const message: WSMessage = { type: 'session_list', sessions: sessionData }
67-
ws.send(JSON.stringify(message))
53+
async function handleRequest(req: Request): Promise<Response> {
54+
return get404Response({ url: req.url, method: req.method, note: 'No route matched' })
6855
}
6956

70-
function handleSubscribe(ws: ServerWebSocket<any>, message: WSMessage): void {
71-
if (message.sessionId) {
72-
const session = manager.get(message.sessionId)
73-
if (!session) {
74-
ws.send(JSON.stringify({ type: 'error', error: `Session ${message.sessionId} not found` }))
75-
} else {
76-
ws.subscribe(`session:${message.sessionId}`)
77-
}
57+
export async function startWebServer(
58+
config: Partial<ServerConfig> = {},
59+
testManager?: PTYManager
60+
): Promise<string> {
61+
const finalConfig = { ...defaultConfig, ...config }
62+
const ptyManager = testManager || manager
63+
64+
function sendSessionList(ws: ServerWebSocket<any>): void {
65+
const sessions = ptyManager.list()
66+
const sessionData = sessions.map((s) => ({
67+
id: s.id,
68+
title: s.title,
69+
description: s.description,
70+
command: s.command,
71+
status: s.status,
72+
exitCode: s.exitCode,
73+
pid: s.pid,
74+
lineCount: s.lineCount,
75+
createdAt: s.createdAt.toISOString(),
76+
}))
77+
const message: WSMessage = { type: 'session_list', sessions: sessionData }
78+
ws.send(JSON.stringify(message))
7879
}
79-
}
8080

81-
function handleUnsubscribe(ws: ServerWebSocket<any>, message: WSMessage): void {
82-
if (message.sessionId) {
83-
ws.unsubscribe(`session:${message.sessionId}`)
81+
function handleSubscribe(ws: ServerWebSocket<any>, message: WSMessage): void {
82+
if (message.sessionId) {
83+
const session = ptyManager.get(message.sessionId)
84+
if (!session) {
85+
ws.send(JSON.stringify({ type: 'error', error: `Session ${message.sessionId} not found` }))
86+
} else {
87+
ws.subscribe(`session:${message.sessionId}`)
88+
}
89+
}
8490
}
85-
}
8691

87-
function handleSessionListRequest(ws: ServerWebSocket<any>, _message: WSMessage): void {
88-
sendSessionList(ws)
89-
}
92+
function handleUnsubscribe(ws: ServerWebSocket<any>, message: WSMessage): void {
93+
if (message.sessionId) {
94+
ws.unsubscribe(`session:${message.sessionId}`)
95+
}
96+
}
9097

91-
function handleUnknownMessage(ws: ServerWebSocket<any>, _message: WSMessage): void {
92-
ws.send(JSON.stringify({ type: 'error', error: 'Unknown message type' }))
93-
}
98+
function handleSessionListRequest(ws: ServerWebSocket<any>, _message: WSMessage): void {
99+
sendSessionList(ws)
100+
}
94101

95-
// Set callback for session updates
96-
setOnSessionUpdate(() => {
97-
const sessions = manager.list()
98-
const sessionData = sessions.map((s) => ({
99-
id: s.id,
100-
title: s.title,
101-
description: s.description,
102-
command: s.command,
103-
status: s.status,
104-
exitCode: s.exitCode,
105-
pid: s.pid,
106-
lineCount: s.lineCount,
107-
createdAt: s.createdAt.toISOString(),
108-
}))
109-
const message = { type: 'session_list', sessions: sessionData }
110-
for (const [ws] of wsClients) {
111-
ws.send(JSON.stringify(message))
102+
function handleUnknownMessage(ws: ServerWebSocket<any>, _message: WSMessage): void {
103+
ws.send(JSON.stringify({ type: 'error', error: 'Unknown message type' }))
112104
}
113-
})
114105

115-
function handleWebSocketMessage(ws: ServerWebSocket<any>, data: string): void {
116-
try {
117-
const message: WSMessage = JSON.parse(data)
106+
// Set callback for session updates
107+
setOnSessionUpdate(() => {
108+
const sessions = ptyManager.list()
109+
const sessionData = sessions.map((s) => ({
110+
id: s.id,
111+
title: s.title,
112+
description: s.description,
113+
command: s.command,
114+
status: s.status,
115+
exitCode: s.exitCode,
116+
pid: s.pid,
117+
lineCount: s.lineCount,
118+
createdAt: s.createdAt.toISOString(),
119+
}))
120+
const message = { type: 'session_list', sessions: sessionData }
121+
for (const [ws] of wsClients) {
122+
ws.send(JSON.stringify(message))
123+
}
124+
})
118125

119-
switch (message.type) {
120-
case 'subscribe':
121-
handleSubscribe(ws, message)
122-
break
126+
function handleWebSocketMessage(ws: ServerWebSocket<any>, data: string): void {
127+
try {
128+
const message: WSMessage = JSON.parse(data)
123129

124-
case 'unsubscribe':
125-
handleUnsubscribe(ws, message)
126-
break
130+
switch (message.type) {
131+
case 'subscribe':
132+
handleSubscribe(ws, message)
133+
break
127134

128-
case 'session_list':
129-
handleSessionListRequest(ws, message)
130-
break
135+
case 'unsubscribe':
136+
handleUnsubscribe(ws, message)
137+
break
131138

132-
default:
133-
handleUnknownMessage(ws, message)
139+
case 'session_list':
140+
handleSessionListRequest(ws, message)
141+
break
142+
143+
default:
144+
handleUnknownMessage(ws, message)
145+
}
146+
} catch (err) {
147+
ws.send(JSON.stringify({ type: 'error', error: 'Invalid message format' }))
134148
}
135-
} catch (err) {
136-
ws.send(JSON.stringify({ type: 'error', error: 'Invalid message format' }))
137149
}
138-
}
139-
140-
const wsHandler = {
141-
open(ws: ServerWebSocket<any>) {
142-
wsConnectionCount++
143-
wsClients.set(ws, {})
144-
sendSessionList(ws)
145-
},
146-
147-
message(ws: ServerWebSocket<any>, message: string) {
148-
handleWebSocketMessage(ws, message)
149-
},
150-
151-
close(_ws: ServerWebSocket<any>) {
152-
wsConnectionCount--
153-
wsClients.delete(_ws)
154-
},
155-
}
156-
157-
async function handleRequest(req: Request): Promise<Response> {
158-
return get404Response({ url: req.url, method: req.method, note: 'No route matched' })
159-
}
160150

161-
export async function startWebServer(config: Partial<ServerConfig> = {}): Promise<string> {
162-
const finalConfig = { ...defaultConfig, ...config }
151+
const wsHandler = {
152+
open(ws: ServerWebSocket<any>) {
153+
wsConnectionCount++
154+
wsClients.set(ws, {})
155+
sendSessionList(ws)
156+
},
157+
158+
message(ws: ServerWebSocket<any>, message: string) {
159+
handleWebSocketMessage(ws, message)
160+
},
161+
162+
close(_ws: ServerWebSocket<any>) {
163+
wsConnectionCount--
164+
wsClients.delete(_ws)
165+
},
166+
}
163167

164168
if (server) {
165169
return `http://${server.hostname}:${server.port}`
@@ -199,34 +203,37 @@ export async function startWebServer(config: Partial<ServerConfig> = {}): Promis
199203
},
200204
'/health': wrapWithSecurityHeaders(handleHealth),
201205
'/api/sessions': wrapWithSecurityHeaders(async (req: Request) => {
202-
if (req.method === 'GET') return getSessions()
203-
if (req.method === 'POST') return createSession(req)
206+
if (req.method === 'GET') return getSessions(ptyManager)
207+
if (req.method === 'POST') return createSession(req, ptyManager)
204208
return new Response('Method not allowed', { status: 405 })
205209
}),
206210
'/api/sessions/clear': wrapWithSecurityHeaders(async (req: Request) => {
207-
if (req.method === 'POST') return clearSessions()
211+
if (req.method === 'POST') return clearSessions(ptyManager)
208212
return new Response('Method not allowed', { status: 405 })
209213
}),
210214
'/api/sessions/:id': wrapWithSecurityHeaders(async (req: Request) => {
211-
if (req.method === 'GET') return getSession(req as BunRequest<'/api/sessions/:id'>)
215+
if (req.method === 'GET')
216+
return getSession(req as BunRequest<'/api/sessions/:id'>, ptyManager)
212217
return new Response('Method not allowed', { status: 405 })
213218
}),
214219
'/api/sessions/:id/input': wrapWithSecurityHeaders(async (req: Request) => {
215-
if (req.method === 'POST') return sendInput(req as BunRequest<'/api/sessions/:id/input'>)
220+
if (req.method === 'POST')
221+
return sendInput(req as BunRequest<'/api/sessions/:id/input'>, ptyManager)
216222
return new Response('Method not allowed', { status: 405 })
217223
}),
218224
'/api/sessions/:id/kill': wrapWithSecurityHeaders(async (req: Request) => {
219-
if (req.method === 'POST') return killSession(req as BunRequest<'/api/sessions/:id/kill'>)
225+
if (req.method === 'POST')
226+
return killSession(req as BunRequest<'/api/sessions/:id/kill'>, ptyManager)
220227
return new Response('Method not allowed', { status: 405 })
221228
}),
222229
'/api/sessions/:id/buffer/raw': wrapWithSecurityHeaders(async (req: Request) => {
223230
if (req.method === 'GET')
224-
return getRawBuffer(req as BunRequest<'/api/sessions/:id/buffer/raw'>)
231+
return getRawBuffer(req as BunRequest<'/api/sessions/:id/buffer/raw'>, ptyManager)
225232
return new Response('Method not allowed', { status: 405 })
226233
}),
227234
'/api/sessions/:id/buffer/plain': wrapWithSecurityHeaders(async (req: Request) => {
228235
if (req.method === 'GET')
229-
return getPlainBuffer(req as BunRequest<'/api/sessions/:id/buffer/plain'>)
236+
return getPlainBuffer(req as BunRequest<'/api/sessions/:id/buffer/plain'>, ptyManager)
230237
return new Response('Method not allowed', { status: 405 })
231238
}),
232239
},

0 commit comments

Comments
 (0)