Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1afefc5

Browse files
jaggederestclaude
andcommitted
test: add comprehensive tests for inbox.ts and workspaceMonitor.ts
- Add 14 test cases for inbox.ts covering WebSocket connection, event handling, and disposal- Add 19 test cases for workspaceMonitor.ts covering SSE monitoring, notifications, and status bar updates- Test WebSocket setup with proper URL construction and authentication headers- Test EventSource setup for workspace monitoring with data/error event handling- Test notification logic for autostop, deletion, outdated workspace, and non-running states- Test status bar updates and context management- Test proper cleanup and disposal patterns- Achieve comprehensive coverage for message handling and workspace monitoring functionality🤖 Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>
1 parent01246a1 commit1afefc5

File tree

2 files changed

+773
-0
lines changed

2 files changed

+773
-0
lines changed

‎src/inbox.test.ts

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import{describe,it,expect,vi,beforeEach,afterEach}from"vitest"
2+
import*asvscodefrom"vscode"
3+
import{Inbox}from"./inbox"
4+
import{Api}from"coder/site/src/api/api"
5+
import{Workspace}from"coder/site/src/api/typesGenerated"
6+
import{ProxyAgent}from"proxy-agent"
7+
import{WebSocket}from"ws"
8+
import{Storage}from"./storage"
9+
10+
// Mock external dependencies
11+
vi.mock("vscode",()=>({
12+
window:{
13+
showInformationMessage:vi.fn(),
14+
},
15+
}))
16+
17+
vi.mock("ws",()=>({
18+
WebSocket:vi.fn(),
19+
}))
20+
21+
vi.mock("proxy-agent",()=>({
22+
ProxyAgent:vi.fn(),
23+
}))
24+
25+
vi.mock("./api",()=>({
26+
coderSessionTokenHeader:"Coder-Session-Token",
27+
}))
28+
29+
vi.mock("./api-helper",()=>({
30+
errToStr:vi.fn(),
31+
}))
32+
33+
describe("Inbox",()=>{
34+
letmockWorkspace:Workspace
35+
letmockHttpAgent:ProxyAgent
36+
letmockRestClient:Api
37+
letmockStorage:Storage
38+
letmockSocket:any
39+
letinbox:Inbox
40+
41+
beforeEach(async()=>{
42+
vi.clearAllMocks()
43+
44+
// Setup mock workspace
45+
mockWorkspace={
46+
id:"workspace-1",
47+
name:"test-workspace",
48+
owner_name:"testuser",
49+
}asWorkspace
50+
51+
// Setup mock HTTP agent
52+
mockHttpAgent={}asProxyAgent
53+
54+
// Setup mock socket
55+
mockSocket={
56+
on:vi.fn(),
57+
close:vi.fn(),
58+
}
59+
vi.mocked(WebSocket).mockReturnValue(mockSocket)
60+
61+
// Setup mock REST client
62+
mockRestClient={
63+
getAxiosInstance:vi.fn(()=>({
64+
defaults:{
65+
baseURL:"https://coder.example.com",
66+
headers:{
67+
common:{
68+
"Coder-Session-Token":"test-token",
69+
},
70+
},
71+
},
72+
})),
73+
}asany
74+
75+
// Setup mock storage
76+
mockStorage={
77+
writeToCoderOutputChannel:vi.fn(),
78+
}asany
79+
80+
// Setup errToStr mock
81+
constapiHelper=awaitimport("./api-helper")
82+
vi.mocked(apiHelper.errToStr).mockReturnValue("Mock error message")
83+
})
84+
85+
afterEach(()=>{
86+
if(inbox){
87+
inbox.dispose()
88+
}
89+
})
90+
91+
describe("constructor",()=>{
92+
it("should create WebSocket connection with correct URL and headers",()=>{
93+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
94+
95+
expect(WebSocket).toHaveBeenCalledWith(
96+
expect.any(URL),
97+
{
98+
agent:mockHttpAgent,
99+
followRedirects:true,
100+
headers:{
101+
"Coder-Session-Token":"test-token",
102+
},
103+
}
104+
)
105+
106+
// Verify the WebSocket URL is constructed correctly
107+
constwebsocketCall=vi.mocked(WebSocket).mock.calls[0]
108+
constwebsocketUrl=websocketCall[0]asURL
109+
expect(websocketUrl.protocol).toBe("wss:")
110+
expect(websocketUrl.host).toBe("coder.example.com")
111+
expect(websocketUrl.pathname).toBe("/api/v2/notifications/inbox/watch")
112+
expect(websocketUrl.searchParams.get("format")).toBe("plaintext")
113+
expect(websocketUrl.searchParams.get("templates")).toContain("a9d027b4-ac49-4fb1-9f6d-45af15f64e7a")
114+
expect(websocketUrl.searchParams.get("templates")).toContain("f047f6a3-5713-40f7-85aa-0394cce9fa3a")
115+
expect(websocketUrl.searchParams.get("targets")).toBe("workspace-1")
116+
})
117+
118+
it("should use ws protocol for http base URL",()=>{
119+
mockRestClient.getAxiosInstance=vi.fn(()=>({
120+
defaults:{
121+
baseURL:"http://coder.example.com",
122+
headers:{
123+
common:{
124+
"Coder-Session-Token":"test-token",
125+
},
126+
},
127+
},
128+
}))
129+
130+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
131+
132+
constwebsocketCall=vi.mocked(WebSocket).mock.calls[0]
133+
constwebsocketUrl=websocketCall[0]asURL
134+
expect(websocketUrl.protocol).toBe("ws:")
135+
})
136+
137+
it("should handle missing token in headers",()=>{
138+
mockRestClient.getAxiosInstance=vi.fn(()=>({
139+
defaults:{
140+
baseURL:"https://coder.example.com",
141+
headers:{
142+
common:{},
143+
},
144+
},
145+
}))
146+
147+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
148+
149+
expect(WebSocket).toHaveBeenCalledWith(
150+
expect.any(URL),
151+
{
152+
agent:mockHttpAgent,
153+
followRedirects:true,
154+
headers:undefined,
155+
}
156+
)
157+
})
158+
159+
it("should throw error when no base URL is set",()=>{
160+
mockRestClient.getAxiosInstance=vi.fn(()=>({
161+
defaults:{
162+
baseURL:undefined,
163+
headers:{
164+
common:{},
165+
},
166+
},
167+
}))
168+
169+
expect(()=>{
170+
newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
171+
}).toThrow("No base URL set on REST client")
172+
})
173+
174+
it("should register socket event handlers",()=>{
175+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
176+
177+
expect(mockSocket.on).toHaveBeenCalledWith("open",expect.any(Function))
178+
expect(mockSocket.on).toHaveBeenCalledWith("error",expect.any(Function))
179+
expect(mockSocket.on).toHaveBeenCalledWith("message",expect.any(Function))
180+
})
181+
})
182+
183+
describe("socket event handlers",()=>{
184+
beforeEach(()=>{
185+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
186+
})
187+
188+
it("should handle socket open event",()=>{
189+
constopenHandler=mockSocket.on.mock.calls.find(call=>call[0]==="open")?.[1]
190+
expect(openHandler).toBeDefined()
191+
192+
openHandler()
193+
194+
expect(mockStorage.writeToCoderOutputChannel).toHaveBeenCalledWith(
195+
"Listening to Coder Inbox"
196+
)
197+
})
198+
199+
it("should handle socket error event",()=>{
200+
consterrorHandler=mockSocket.on.mock.calls.find(call=>call[0]==="error")?.[1]
201+
expect(errorHandler).toBeDefined()
202+
203+
constmockError=newError("Socket error")
204+
constdisposeSpy=vi.spyOn(inbox,"dispose")
205+
206+
errorHandler(mockError)
207+
208+
expect(mockStorage.writeToCoderOutputChannel).toHaveBeenCalledWith("Mock error message")
209+
expect(disposeSpy).toHaveBeenCalled()
210+
})
211+
212+
it("should handle valid socket message",()=>{
213+
constmessageHandler=mockSocket.on.mock.calls.find(call=>call[0]==="message")?.[1]
214+
expect(messageHandler).toBeDefined()
215+
216+
constmockMessage={
217+
notification:{
218+
title:"Test notification",
219+
},
220+
}
221+
constmessageData=Buffer.from(JSON.stringify(mockMessage))
222+
223+
messageHandler(messageData)
224+
225+
expect(vscode.window.showInformationMessage).toHaveBeenCalledWith("Test notification")
226+
})
227+
228+
it("should handle invalid JSON in socket message",()=>{
229+
constmessageHandler=mockSocket.on.mock.calls.find(call=>call[0]==="message")?.[1]
230+
expect(messageHandler).toBeDefined()
231+
232+
constinvalidData=Buffer.from("invalid json")
233+
234+
messageHandler(invalidData)
235+
236+
expect(mockStorage.writeToCoderOutputChannel).toHaveBeenCalledWith("Mock error message")
237+
})
238+
239+
it("should handle message parsing errors",()=>{
240+
constmessageHandler=mockSocket.on.mock.calls.find(call=>call[0]==="message")?.[1]
241+
expect(messageHandler).toBeDefined()
242+
243+
constmockMessage={
244+
// Missing required notification structure
245+
}
246+
constmessageData=Buffer.from(JSON.stringify(mockMessage))
247+
248+
messageHandler(messageData)
249+
250+
// Should not throw, but may not show notification if structure is wrong
251+
// The test verifies that error handling doesn't crash the application
252+
})
253+
})
254+
255+
describe("dispose",()=>{
256+
beforeEach(()=>{
257+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
258+
})
259+
260+
it("should close socket and log when disposed",()=>{
261+
inbox.dispose()
262+
263+
expect(mockStorage.writeToCoderOutputChannel).toHaveBeenCalledWith(
264+
"No longer listening to Coder Inbox"
265+
)
266+
expect(mockSocket.close).toHaveBeenCalled()
267+
})
268+
269+
it("should handle multiple dispose calls safely",()=>{
270+
inbox.dispose()
271+
inbox.dispose()
272+
273+
// Should only log and close once
274+
expect(mockStorage.writeToCoderOutputChannel).toHaveBeenCalledTimes(1)
275+
expect(mockSocket.close).toHaveBeenCalledTimes(1)
276+
})
277+
})
278+
279+
describe("template constants",()=>{
280+
it("should include workspace out of memory template",()=>{
281+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
282+
283+
constwebsocketCall=vi.mocked(WebSocket).mock.calls[0]
284+
constwebsocketUrl=websocketCall[0]asURL
285+
consttemplates=websocketUrl.searchParams.get("templates")
286+
287+
expect(templates).toContain("a9d027b4-ac49-4fb1-9f6d-45af15f64e7a")
288+
})
289+
290+
it("should include workspace out of disk template",()=>{
291+
inbox=newInbox(mockWorkspace,mockHttpAgent,mockRestClient,mockStorage)
292+
293+
constwebsocketCall=vi.mocked(WebSocket).mock.calls[0]
294+
constwebsocketUrl=websocketCall[0]asURL
295+
consttemplates=websocketUrl.searchParams.get("templates")
296+
297+
expect(templates).toContain("f047f6a3-5713-40f7-85aa-0394cce9fa3a")
298+
})
299+
})
300+
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp