Skip to content

Commit fe9c12e

Browse files
committed
test: add more tests to vscode-lm.spec.ts
1 parent 449af9a commit fe9c12e

2 files changed

Lines changed: 319 additions & 7 deletions

File tree

src/api/providers/__tests__/vscode-lm.spec.ts

Lines changed: 319 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ vi.mock("vscode", () => {
1212
constructor(
1313
public callId: string,
1414
public name: string,
15+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1516
public input: any,
1617
) {}
1718
}
1819

1920
return {
2021
workspace: {
2122
getConfiguration: vi.fn(() => ({
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2224
get: vi.fn((key: string, defaultValue: any) => defaultValue),
2325
})),
2426
onDidChangeConfiguration: vi.fn((_callback) => ({
@@ -106,6 +108,14 @@ describe("VsCodeLmHandler", () => {
106108
// Should reset client when config changes
107109
expect(handler["client"]).toBeNull()
108110
})
111+
112+
it("should call initializeClient during construction", () => {
113+
// Constructor calls initializeClient() without await, so it starts async initialization.
114+
// Verify the handler is created and initializeClient was triggered.
115+
expect(handler).toBeDefined()
116+
// The constructor triggers initializeClient which calls selectChatModels
117+
expect(vscode.lm.selectChatModels).toHaveBeenCalled()
118+
})
109119
})
110120

111121
describe("createClient", () => {
@@ -135,6 +145,14 @@ describe("VsCodeLmHandler", () => {
135145
expect(client.id).toBe("default-lm")
136146
expect(client.vendor).toBe("vscode")
137147
})
148+
149+
it("should throw a Zoo Code branded error when selectChatModels fails", async () => {
150+
;(vscode.lm.selectChatModels as Mock).mockRejectedValueOnce(new Error("network down"))
151+
152+
await expect(handler["createClient"]({ vendor: "test" })).rejects.toThrow(
153+
"Zoo Code <Language Model API>: Failed to select model: network down",
154+
)
155+
})
138156
})
139157

140158
describe("createMessage", () => {
@@ -471,18 +489,264 @@ describe("VsCodeLmHandler", () => {
471489
expect.stringContaining("STREAM_ERROR"),
472490
)
473491

492+
consoleErrorSpy.mockRestore()
493+
})
494+
it("should log Zoo Code branded warning for unknown chunk type in stream", async () => {
495+
const systemPrompt = "You are a helpful assistant"
496+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "Hello" }]
497+
498+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
499+
500+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
501+
stream: (async function* () {
502+
// Yield an unknown chunk type (not TextPart, not ToolCallPart)
503+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
504+
yield { type: "unknown", foo: "bar" } as any
505+
return
506+
})(),
507+
text: (async function* () {
508+
yield ""
509+
return
510+
})(),
511+
})
512+
513+
const stream = handler.createMessage(systemPrompt, messages)
514+
for await (const _chunk of stream) {
515+
// drain
516+
}
517+
518+
expect(consoleWarnSpy).toHaveBeenCalledWith(
519+
"Zoo Code <Language Model API>: Unknown chunk type received:",
520+
expect.objectContaining({ type: "unknown" }),
521+
)
522+
523+
consoleWarnSpy.mockRestore()
524+
})
525+
526+
it("should log Zoo Code branded warning for invalid text part value", async () => {
527+
const systemPrompt = "You are a helpful assistant"
528+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "Hello" }]
529+
530+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
531+
532+
// Create a TextPart with a non-string value (number)
533+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
534+
const badTextPart = new vscode.LanguageModelTextPart(42 as any)
535+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
536+
stream: (async function* () {
537+
yield badTextPart
538+
return
539+
})(),
540+
text: (async function* () {
541+
yield ""
542+
return
543+
})(),
544+
})
545+
546+
const stream = handler.createMessage(systemPrompt, messages)
547+
for await (const _chunk of stream) {
548+
// drain
549+
}
550+
551+
expect(consoleWarnSpy).toHaveBeenCalledWith(
552+
"Zoo Code <Language Model API>: Invalid text part value received:",
553+
42,
554+
)
555+
556+
consoleWarnSpy.mockRestore()
557+
})
558+
559+
it("should log Zoo Code branded warning for invalid tool callId", async () => {
560+
const systemPrompt = "You are a helpful assistant"
561+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "Hello" }]
562+
563+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
564+
565+
// Create a ToolCallPart with a non-string callId
566+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
567+
const badToolCall = new vscode.LanguageModelToolCallPart(123 as any, "valid-name", {})
568+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
569+
stream: (async function* () {
570+
yield badToolCall
571+
return
572+
})(),
573+
text: (async function* () {
574+
yield ""
575+
return
576+
})(),
577+
})
578+
579+
const stream = handler.createMessage(systemPrompt, messages)
580+
for await (const _chunk of stream) {
581+
// drain
582+
}
583+
584+
expect(consoleWarnSpy).toHaveBeenCalledWith(
585+
"Zoo Code <Language Model API>: Invalid tool callId received:",
586+
123,
587+
)
588+
589+
consoleWarnSpy.mockRestore()
590+
})
591+
592+
it("should log Zoo Code branded warning for invalid tool input", async () => {
593+
const systemPrompt = "You are a helpful assistant"
594+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "Hello" }]
595+
596+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
597+
598+
// Create a ToolCallPart with a string input (not an object)
599+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
600+
const badToolCall = new vscode.LanguageModelToolCallPart("call-1", "valid-name", "not-an-object" as any)
601+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
602+
stream: (async function* () {
603+
yield badToolCall
604+
return
605+
})(),
606+
text: (async function* () {
607+
yield ""
608+
return
609+
})(),
610+
})
611+
612+
const stream = handler.createMessage(systemPrompt, messages)
613+
for await (const _chunk of stream) {
614+
// drain
615+
}
616+
617+
expect(consoleWarnSpy).toHaveBeenCalledWith(
618+
"Zoo Code <Language Model API>: Invalid tool input received:",
619+
"not-an-object",
620+
)
621+
622+
consoleWarnSpy.mockRestore()
623+
})
624+
625+
it("should log Zoo Code branded error when tool call processing fails", async () => {
626+
const systemPrompt = "You are a helpful assistant"
627+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "Hello" }]
628+
629+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
630+
631+
// Create a ToolCallPart with circular input that will throw on JSON.stringify
632+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
633+
const circularInput: any = { name: "circular" }
634+
circularInput.self = circularInput
635+
636+
const badToolCall = new vscode.LanguageModelToolCallPart("call-1", "valid-name", circularInput)
637+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
638+
stream: (async function* () {
639+
yield badToolCall
640+
return
641+
})(),
642+
text: (async function* () {
643+
yield ""
644+
return
645+
})(),
646+
})
647+
648+
const stream = handler.createMessage(systemPrompt, messages, {
649+
taskId: "test-task",
650+
tools: [
651+
{
652+
type: "function" as const,
653+
function: { name: "test", description: "", parameters: { type: "object", properties: {} } },
654+
},
655+
],
656+
})
657+
for await (const _chunk of stream) {
658+
// drain
659+
}
660+
661+
expect(consoleErrorSpy).toHaveBeenCalledWith(
662+
"Zoo Code <Language Model API>: Failed to process tool call:",
663+
expect.any(Error),
664+
)
665+
666+
consoleErrorSpy.mockRestore()
667+
})
668+
})
669+
670+
describe("getClient", () => {
671+
it("should log Zoo Code branded debug when creating client with selector", async () => {
672+
const consoleDebugSpy = vi.spyOn(console, "debug").mockImplementation(() => {})
673+
const mockModel = { ...mockLanguageModelChat }
674+
;(vscode.lm.selectChatModels as Mock).mockResolvedValue([mockModel])
675+
handler["client"] = null
676+
677+
// @ts-ignore – access private method for coverage
678+
await handler["getClient"]()
679+
680+
expect(consoleDebugSpy).toHaveBeenCalledWith(
681+
"Zoo Code <Language Model API>: Creating client with selector:",
682+
expect.any(Object),
683+
)
684+
685+
consoleDebugSpy.mockRestore()
686+
})
687+
688+
it("should throw a Zoo Code branded error when getClient fails to create client", async () => {
689+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
690+
;(vscode.lm.selectChatModels as Mock).mockRejectedValueOnce(new Error("network error"))
691+
handler["client"] = null
692+
693+
// @ts-ignore – access private method for coverage
694+
await expect(handler["getClient"]()).rejects.toThrow(
695+
"Zoo Code <Language Model API>: Failed to create client:",
696+
)
697+
698+
expect(consoleErrorSpy).toHaveBeenCalledWith(
699+
"Zoo Code <Language Model API>: Client creation failed:",
700+
expect.stringContaining("network error"),
701+
)
702+
474703
consoleErrorSpy.mockRestore()
475704
})
476705
})
477706

478707
describe("initializeClient", () => {
708+
it("should log when client is already initialized", async () => {
709+
const consoleDebugSpy = vi.spyOn(console, "debug").mockImplementation(() => {})
710+
711+
handler["client"] = mockLanguageModelChat
712+
await handler.initializeClient()
713+
714+
expect(consoleDebugSpy).toHaveBeenCalledWith("Zoo Code <Language Model API>: Client already initialized")
715+
716+
consoleDebugSpy.mockRestore()
717+
})
718+
719+
it("should log success when client is initialized", async () => {
720+
const consoleDebugSpy = vi.spyOn(console, "debug").mockImplementation(() => {})
721+
const mockModel = { ...mockLanguageModelChat }
722+
;(vscode.lm.selectChatModels as Mock).mockResolvedValue([mockModel])
723+
handler["client"] = null
724+
725+
await handler.initializeClient()
726+
727+
expect(consoleDebugSpy).toHaveBeenCalledWith(
728+
"Zoo Code <Language Model API>: Client initialized successfully",
729+
)
730+
731+
consoleDebugSpy.mockRestore()
732+
})
733+
479734
it("should throw a Zoo Code branded error when client initialization fails", async () => {
480-
;(vscode.lm.selectChatModels as Mock).mockRejectedValueOnce(new Error("select failed"))
735+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
736+
// Use mockRejectedValue (not Once) because beforeEach already called selectChatModels once
737+
;(vscode.lm.selectChatModels as Mock).mockRejectedValue(new Error("select failed"))
481738
handler["client"] = null
482739

483740
await expect(handler.initializeClient()).rejects.toThrow(
484-
"Zoo Code <Language Model API>: Failed to initialize client: Zoo Code <Language Model API>: Failed to select model: select failed",
741+
"Zoo Code <Language Model API>: Failed to initialize client:",
485742
)
743+
744+
expect(consoleErrorSpy).toHaveBeenCalledWith(
745+
"Zoo Code <Language Model API>: Client initialization failed:",
746+
expect.stringContaining("select failed"),
747+
)
748+
749+
consoleErrorSpy.mockRestore()
486750
})
487751
})
488752

@@ -642,6 +906,7 @@ describe("VsCodeLmHandler", () => {
642906
cancel: vi.fn(),
643907
dispose: vi.fn(),
644908
}
909+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
645910
handler["currentRequestCancellation"] = mockCancellation as any
646911

647912
mockLanguageModelChat.countTokens.mockResolvedValueOnce(50)
@@ -682,6 +947,58 @@ describe("VsCodeLmHandler", () => {
682947
expect(result).toBe(5)
683948
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledWith("[IMAGE]", expect.any(Object))
684949
})
950+
951+
it("should return 0 and log when empty text is provided to internalCountTokens", async () => {
952+
handler["currentRequestCancellation"] = null
953+
const consoleDebugSpy = vi.spyOn(console, "debug").mockImplementation(() => {})
954+
955+
// @ts-ignore – access private method for coverage of line 234
956+
const result = await handler["internalCountTokens"]("")
957+
958+
expect(result).toBe(0)
959+
expect(consoleDebugSpy).toHaveBeenCalledWith(
960+
"Zoo Code <Language Model API>: Empty text provided for token counting",
961+
)
962+
963+
consoleDebugSpy.mockRestore()
964+
})
965+
966+
it("should return 0 and log when non-numeric token count is received", async () => {
967+
handler["currentRequestCancellation"] = null
968+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
969+
970+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
971+
mockLanguageModelChat.countTokens.mockResolvedValueOnce("not-a-number" as any)
972+
973+
const content: Anthropic.Messages.ContentBlockParam[] = [{ type: "text", text: "test" }]
974+
const result = await handler.countTokens(content)
975+
976+
expect(result).toBe(0)
977+
expect(consoleWarnSpy).toHaveBeenCalledWith(
978+
"Zoo Code <Language Model API>: Non-numeric token count received:",
979+
"not-a-number",
980+
)
981+
982+
consoleWarnSpy.mockRestore()
983+
})
984+
985+
it("should return 0 and log when negative token count is received", async () => {
986+
handler["currentRequestCancellation"] = null
987+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
988+
989+
mockLanguageModelChat.countTokens.mockResolvedValueOnce(-5)
990+
991+
const content: Anthropic.Messages.ContentBlockParam[] = [{ type: "text", text: "test" }]
992+
const result = await handler.countTokens(content)
993+
994+
expect(result).toBe(0)
995+
expect(consoleWarnSpy).toHaveBeenCalledWith(
996+
"Zoo Code <Language Model API>: Negative token count received:",
997+
-5,
998+
)
999+
1000+
consoleWarnSpy.mockRestore()
1001+
})
6851002
})
6861003

6871004
describe("completePrompt", () => {

src/eslint-suppressions.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,6 @@
289289
"count": 5
290290
}
291291
},
292-
"api/providers/__tests__/vscode-lm.spec.ts": {
293-
"@typescript-eslint/no-explicit-any": {
294-
"count": 3
295-
}
296-
},
297292
"api/providers/__tests__/xai.spec.ts": {
298293
"@typescript-eslint/no-explicit-any": {
299294
"count": 1

0 commit comments

Comments
 (0)