-
Notifications
You must be signed in to change notification settings - Fork 212
Expand file tree
/
Copy pathkimi-code.spec.ts
More file actions
183 lines (168 loc) · 6.23 KB
/
Copy pathkimi-code.spec.ts
File metadata and controls
183 lines (168 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import { buildApiHandler } from "../../index"
import { KimiCodeHandler } from "../kimi-code"
import type { Mock } from "vitest"
vi.mock("../../../integrations/kimi-code/oauth", () => {
const mockGetAccessToken = vi.fn().mockResolvedValue("oauth-token")
const mockForceRefreshAccessToken = vi.fn().mockResolvedValue("refreshed-token")
return {
kimiCodeOAuthManager: {
getAccessToken: mockGetAccessToken,
forceRefreshAccessToken: mockForceRefreshAccessToken,
},
mockGetAccessToken,
mockForceRefreshAccessToken,
}
})
vi.mock("../fetchers/modelCache", () => {
const mockGetModels = vi.fn().mockRejectedValue(new Error("offline"))
return {
getModels: mockGetModels,
mockGetModels,
}
})
const { mockGetAccessToken, mockForceRefreshAccessToken } = await import("../../../integrations/kimi-code/oauth")
const { mockGetModels } = await import("../fetchers/modelCache")
describe("KimiCodeHandler", () => {
beforeEach(() => {
vi.clearAllMocks()
;(mockGetAccessToken as any).mockResolvedValue("oauth-token")
;(mockForceRefreshAccessToken as any).mockResolvedValue("refreshed-token")
;(mockGetModels as any).mockRejectedValue(new Error("offline"))
})
it("is dispatched separately from Moonshot and preserves an unknown selected model", () => {
const handler = buildApiHandler({
apiProvider: "kimi-code",
kimiCodeAuthMethod: "api-key",
kimiCodeApiKey: "kimi-key",
apiModelId: "future-kimi-model",
})
expect(handler).toBeInstanceOf(KimiCodeHandler)
expect(handler.getModel().id).toBe("future-kimi-model")
})
it("uses kimi-for-coding only when no model is selected", () => {
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "kimi-key" })
expect(handler.getModel().id).toBe("kimi-for-coding")
})
it("uses API key when auth method is api-key", async () => {
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "my-api-key" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
new Response(JSON.stringify({ choices: [{ message: { content: "response" }, finish_reason: "stop" }] }), {
status: 200,
}),
)
try {
for await (const chunk of gen) {
// consume
}
} catch {
// expected - mock is incomplete
}
expect(mockGetAccessToken as any).not.toHaveBeenCalled()
})
it("uses OAuth token when auth method is oauth or not specified", async () => {
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "oauth" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
try {
for await (const chunk of gen) {
// consume
}
} catch {
// expected - mock will fail
}
expect(mockGetAccessToken as any).toHaveBeenCalled()
})
it("throws error when OAuth is required but no token available", async () => {
;(mockGetAccessToken as any).mockResolvedValueOnce(null)
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "oauth" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
await expect(async () => {
for await (const chunk of gen) {
// consume
}
}).rejects.toThrow("Not authenticated with Kimi Code")
})
it("throws error when API key auth is missing the key", async () => {
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
await expect(async () => {
for await (const chunk of gen) {
// consume
}
}).rejects.toThrow("Kimi Code API key is required")
})
it("retries with forced refresh on 401 when using OAuth", async () => {
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "oauth" })
const fetchSpy = vi.spyOn(globalThis, "fetch")
fetchSpy.mockResolvedValueOnce(
new Response(null, { status: 401 }),
)
fetchSpy.mockResolvedValueOnce(
new Response(JSON.stringify({ choices: [{ message: { content: "ok" }, finish_reason: "stop" }] }), {
status: 200,
}),
)
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
try {
for await (const chunk of gen) {
// consume
}
} catch {
// expected - mock is incomplete
}
expect(mockForceRefreshAccessToken as any).toHaveBeenCalled()
})
it("does not retry on 401 when using API key auth", async () => {
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "key" })
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
new Response(null, { status: 401 }),
)
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
await expect(async () => {
for await (const chunk of gen) {
// consume
}
}).rejects.toThrow()
expect(mockForceRefreshAccessToken as any).not.toHaveBeenCalled()
})
it("fetches models during prepareRequest", async () => {
;(mockGetModels as any).mockResolvedValueOnce({ "test-model": { maxTokens: 1000 } })
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "key" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
try {
for await (const chunk of gen) {
// consume
}
} catch {
// expected
}
expect(mockGetModels as any).toHaveBeenCalled()
})
it("continues when model discovery fails", async () => {
;(mockGetModels as any).mockRejectedValueOnce(new Error("discovery failed"))
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "key" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
try {
for await (const chunk of gen) {
// consume
}
} catch {
// expected - different error
}
expect(mockGetModels as any).toHaveBeenCalled()
})
it("uses discovered model info when available", async () => {
;(mockGetModels as any).mockResolvedValueOnce({ "kimi-for-coding": { maxTokens: 8000, contextWindow: 128000 } })
const handler = new KimiCodeHandler({ kimiCodeAuthMethod: "api-key", kimiCodeApiKey: "key" })
const gen = handler.createMessage("system", [{ role: "user", content: "test" }])
try {
for await (const chunk of gen) {
// consume
}
} catch {
// expected
}
const model = handler.getModel()
expect(model.info.maxTokens).toBe(8000)
})
})