pi-soly 1.9.3 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ask/index.ts +12 -11
- package/ask/picker.ts +356 -76
- package/ask/prompt.ts +6 -2
- package/ask/tests/picker.test.ts +273 -82
- package/codemap.ts +276 -0
- package/hotreload.ts +239 -0
- package/init.ts +302 -0
- package/mcp/CHANGELOG.md +384 -0
- package/mcp/LICENSE +21 -0
- package/mcp/OAUTH.md +355 -0
- package/mcp/README.md +410 -0
- package/mcp/__tests__/agent-dir-paths.upstream-test.ts +80 -0
- package/mcp/__tests__/cli.upstream-test.ts +97 -0
- package/mcp/__tests__/commands-onboarding.upstream-test.ts +157 -0
- package/mcp/__tests__/config.upstream-test.ts +303 -0
- package/mcp/__tests__/consent-manager.upstream-test.ts +151 -0
- package/mcp/__tests__/direct-tools-auto-auth.upstream-test.ts +218 -0
- package/mcp/__tests__/direct-tools.upstream-test.ts +299 -0
- package/mcp/__tests__/elicitation-handler.upstream-test.ts +346 -0
- package/mcp/__tests__/elicitation-sdk-integration.upstream-test.ts +140 -0
- package/mcp/__tests__/errors.upstream-test.ts +218 -0
- package/mcp/__tests__/fixtures/elicitation-server.mjs +98 -0
- package/mcp/__tests__/host-html-template.upstream-test.ts +278 -0
- package/mcp/__tests__/index-lifecycle.upstream-test.ts +484 -0
- package/mcp/__tests__/init-elicitation.upstream-test.ts +86 -0
- package/mcp/__tests__/interactive-visualizer-server.upstream-test.ts +28 -0
- package/mcp/__tests__/logger.upstream-test.ts +175 -0
- package/mcp/__tests__/mcp-auth-flow-client-credentials.upstream-test.ts +612 -0
- package/mcp/__tests__/mcp-auth-storage.upstream-test.ts +47 -0
- package/mcp/__tests__/mcp-callback-server-unref.upstream-test.ts +264 -0
- package/mcp/__tests__/mcp-oauth-provider.upstream-test.ts +216 -0
- package/mcp/__tests__/mcp-panel-auth.upstream-test.ts +206 -0
- package/mcp/__tests__/mcp-panel-exclude-tools.upstream-test.ts +69 -0
- package/mcp/__tests__/mcp-panel-keybindings.upstream-test.ts +186 -0
- package/mcp/__tests__/npx-resolver.upstream-test.ts +54 -0
- package/mcp/__tests__/oauth-handler.upstream-test.ts +77 -0
- package/mcp/__tests__/onboarding-state.upstream-test.ts +52 -0
- package/mcp/__tests__/package-manifest.upstream-test.ts +22 -0
- package/mcp/__tests__/proxy-modes-auto-auth.upstream-test.ts +253 -0
- package/mcp/__tests__/proxy-modes-discovery.upstream-test.ts +84 -0
- package/mcp/__tests__/proxy-modes-manual-auth.upstream-test.ts +91 -0
- package/mcp/__tests__/proxy-modes-ui-messages.upstream-test.ts +72 -0
- package/mcp/__tests__/sampling-handler.upstream-test.ts +285 -0
- package/mcp/__tests__/server-manager-http-auth.upstream-test.ts +144 -0
- package/mcp/__tests__/server-manager-sampling.upstream-test.ts +236 -0
- package/mcp/__tests__/tool-metadata.upstream-test.ts +111 -0
- package/mcp/__tests__/tool-result-renderer.upstream-test.ts +147 -0
- package/mcp/__tests__/ui-integration.upstream-test.ts +551 -0
- package/mcp/__tests__/ui-resource-handler.upstream-test.ts +303 -0
- package/mcp/__tests__/ui-server.upstream-test.ts +967 -0
- package/mcp/__tests__/ui-session-messages.upstream-test.ts +72 -0
- package/mcp/__tests__/ui-streaming.upstream-test.ts +543 -0
- package/mcp/agent-dir.ts +20 -0
- package/mcp/app-bridge.bundle.js +67 -0
- package/mcp/cli.js +184 -0
- package/mcp/commands.ts +422 -0
- package/mcp/config.ts +666 -0
- package/mcp/consent-manager.ts +64 -0
- package/mcp/direct-tools.ts +439 -0
- package/mcp/elicitation-handler.ts +347 -0
- package/mcp/errors.ts +219 -0
- package/mcp/glimpse-ui.ts +80 -0
- package/mcp/host-html-template.ts +427 -0
- package/mcp/index.ts +362 -0
- package/mcp/init.ts +362 -0
- package/mcp/lifecycle.ts +93 -0
- package/mcp/logger.ts +169 -0
- package/mcp/mcp-auth-flow.ts +559 -0
- package/mcp/mcp-auth-flow.upstream-test.ts +259 -0
- package/mcp/mcp-auth.ts +302 -0
- package/mcp/mcp-auth.upstream-test.ts +373 -0
- package/mcp/mcp-callback-server.ts +372 -0
- package/mcp/mcp-callback-server.upstream-test.ts +416 -0
- package/mcp/mcp-oauth-provider.ts +369 -0
- package/mcp/mcp-oauth-provider.upstream-test.ts +518 -0
- package/mcp/mcp-panel.ts +829 -0
- package/mcp/mcp-setup-panel.ts +580 -0
- package/mcp/metadata-cache.ts +201 -0
- package/mcp/notify.ts +111 -0
- package/mcp/npx-resolver.ts +424 -0
- package/mcp/oauth-handler.ts +57 -0
- package/mcp/onboarding-state.ts +68 -0
- package/mcp/package.json +106 -0
- package/mcp/panel-keys.ts +37 -0
- package/mcp/proxy-modes.ts +949 -0
- package/mcp/resource-tools.ts +17 -0
- package/mcp/sampling-handler.ts +268 -0
- package/mcp/server-manager.ts +545 -0
- package/mcp/state.ts +41 -0
- package/mcp/tool-metadata.ts +216 -0
- package/mcp/tool-registrar.ts +46 -0
- package/mcp/tool-result-renderer.ts +161 -0
- package/mcp/types.ts +448 -0
- package/mcp/ui-resource-handler.ts +146 -0
- package/mcp/ui-server.ts +623 -0
- package/mcp/ui-session.ts +386 -0
- package/mcp/ui-stream-types.ts +89 -0
- package/mcp/utils.ts +129 -0
- package/mcp/vitest.config.ts +14 -0
- package/migrate.ts +258 -0
- package/notification.ts +218 -0
- package/notifications-log.ts +83 -0
- package/package.json +20 -3
- package/status.ts +140 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for mcp-callback-server.ts - OAuth callback server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, beforeEach, afterEach } from "node:test"
|
|
6
|
+
import assert from "node:assert"
|
|
7
|
+
import { createServer } from "node:http"
|
|
8
|
+
import {
|
|
9
|
+
ensureCallbackServer,
|
|
10
|
+
waitForCallback,
|
|
11
|
+
cancelPendingCallback,
|
|
12
|
+
stopCallbackServer,
|
|
13
|
+
isCallbackServerRunning,
|
|
14
|
+
getPendingAuthCount,
|
|
15
|
+
releaseCallbackServer,
|
|
16
|
+
} from "./mcp-callback-server.ts"
|
|
17
|
+
import { getConfiguredOAuthCallbackPort, getOAuthCallbackPath, getOAuthCallbackPort } from "./mcp-oauth-provider.ts"
|
|
18
|
+
|
|
19
|
+
async function getFreePort(): Promise<number> {
|
|
20
|
+
const probe = createServer()
|
|
21
|
+
await new Promise<void>((resolve, reject) => {
|
|
22
|
+
probe.once("error", reject)
|
|
23
|
+
probe.listen(0, "localhost", resolve)
|
|
24
|
+
})
|
|
25
|
+
const address = probe.address()
|
|
26
|
+
await new Promise<void>((resolve) => probe.close(() => resolve()))
|
|
27
|
+
if (!address || typeof address === "string") {
|
|
28
|
+
throw new Error("Failed to reserve a free test port")
|
|
29
|
+
}
|
|
30
|
+
return address.port
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("mcp-callback-server", () => {
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
// Stop any running server before each test
|
|
36
|
+
await stopCallbackServer().catch(() => {})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(async () => {
|
|
40
|
+
// Stop server after each test
|
|
41
|
+
await stopCallbackServer().catch(() => {})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe("ensureCallbackServer", () => {
|
|
45
|
+
it("should start the callback server", async () => {
|
|
46
|
+
await ensureCallbackServer()
|
|
47
|
+
assert.strictEqual(isCallbackServerRunning(), true)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("should be idempotent", async () => {
|
|
51
|
+
await ensureCallbackServer()
|
|
52
|
+
await ensureCallbackServer()
|
|
53
|
+
await ensureCallbackServer()
|
|
54
|
+
assert.strictEqual(isCallbackServerRunning(), true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("should reserve callback state atomically with the initial bind", async () => {
|
|
58
|
+
await ensureCallbackServer({ oauthState: "reserved-initial-state", reserveState: true })
|
|
59
|
+
|
|
60
|
+
await assert.rejects(
|
|
61
|
+
async () => await ensureCallbackServer({ callbackHost: "127.0.0.1" }),
|
|
62
|
+
/cannot be switched while authorizations are pending/
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
releaseCallbackServer("reserved-initial-state")
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("should not switch callback hosts while callback state is reserved", async () => {
|
|
69
|
+
await ensureCallbackServer({ oauthState: "reserved-host-state", reserveState: true })
|
|
70
|
+
|
|
71
|
+
await assert.rejects(
|
|
72
|
+
async () => await ensureCallbackServer({ callbackHost: "127.0.0.1" }),
|
|
73
|
+
/cannot be switched while authorizations are pending/
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
releaseCallbackServer("reserved-host-state")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("should not switch callback paths while callback state is reserved", async () => {
|
|
80
|
+
await ensureCallbackServer({ callbackPath: "/first/callback", oauthState: "reserved-path-state", reserveState: true })
|
|
81
|
+
|
|
82
|
+
await assert.rejects(
|
|
83
|
+
async () => await ensureCallbackServer({ callbackPath: "/second/callback" }),
|
|
84
|
+
/cannot be switched while authorizations are pending/
|
|
85
|
+
)
|
|
86
|
+
assert.strictEqual(getOAuthCallbackPath(), "/first/callback")
|
|
87
|
+
|
|
88
|
+
releaseCallbackServer("reserved-path-state")
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("should release reserved callback state when strict binding fails", async () => {
|
|
92
|
+
const port = await getFreePort()
|
|
93
|
+
const blocker = createServer((_req, res) => {
|
|
94
|
+
res.writeHead(200)
|
|
95
|
+
res.end("blocked")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
await new Promise<void>((resolve, reject) => {
|
|
99
|
+
blocker.once("error", reject)
|
|
100
|
+
blocker.listen(port, "localhost", resolve)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await assert.rejects(
|
|
105
|
+
async () => await ensureCallbackServer({ strictPort: true, port, oauthState: "failed-bind-state", reserveState: true }),
|
|
106
|
+
/already in use/
|
|
107
|
+
)
|
|
108
|
+
} finally {
|
|
109
|
+
await new Promise<void>((resolve) => blocker.close(() => resolve()))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await ensureCallbackServer({ callbackPath: "/after-failure" })
|
|
113
|
+
await ensureCallbackServer({ callbackPath: "/after-failure-switch" })
|
|
114
|
+
assert.strictEqual(getOAuthCallbackPath(), "/after-failure-switch")
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("should bind an explicit strict host, port, and custom callback path", async () => {
|
|
118
|
+
const port = await getFreePort()
|
|
119
|
+
|
|
120
|
+
await ensureCallbackServer({ strictPort: true, port, callbackHost: "127.0.0.1", callbackPath: "/custom/callback" })
|
|
121
|
+
|
|
122
|
+
assert.strictEqual(getOAuthCallbackPort(), port)
|
|
123
|
+
assert.strictEqual(getOAuthCallbackPath(), "/custom/callback")
|
|
124
|
+
assert.strictEqual((await fetch(`http://127.0.0.1:${port}/callback?code=nope&state=custom-state`)).status, 404)
|
|
125
|
+
|
|
126
|
+
const callbackPromise = waitForCallback("custom-state")
|
|
127
|
+
const response = await fetch(`http://127.0.0.1:${port}/custom/callback?code=ok&state=custom-state`)
|
|
128
|
+
assert.strictEqual(response.status, 200)
|
|
129
|
+
assert.strictEqual(await callbackPromise, "ok")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("should reject an occupied explicit strict port", async () => {
|
|
133
|
+
const port = await getFreePort()
|
|
134
|
+
const blocker = createServer((_req, res) => {
|
|
135
|
+
res.writeHead(200)
|
|
136
|
+
res.end("blocked")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
await new Promise<void>((resolve, reject) => {
|
|
140
|
+
blocker.once("error", reject)
|
|
141
|
+
blocker.listen(port, "localhost", resolve)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await assert.rejects(
|
|
146
|
+
async () => await ensureCallbackServer({ strictPort: true, port }),
|
|
147
|
+
/already in use/
|
|
148
|
+
)
|
|
149
|
+
} finally {
|
|
150
|
+
await new Promise<void>((resolve) => blocker.close(() => resolve()))
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it("should use an OS-assigned port when the configured non-strict port is occupied", async () => {
|
|
155
|
+
const configuredPort = getConfiguredOAuthCallbackPort()
|
|
156
|
+
const blocker = createServer((_req, res) => {
|
|
157
|
+
res.writeHead(200)
|
|
158
|
+
res.end("blocked")
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await new Promise<void>((resolve, reject) => {
|
|
163
|
+
blocker.once("error", reject)
|
|
164
|
+
blocker.listen(configuredPort, "localhost", resolve)
|
|
165
|
+
})
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if ((error as NodeJS.ErrnoException).code === "EADDRINUSE") return
|
|
168
|
+
throw error
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await ensureCallbackServer()
|
|
173
|
+
const callbackPort = getOAuthCallbackPort()
|
|
174
|
+
assert.notStrictEqual(callbackPort, configuredPort)
|
|
175
|
+
|
|
176
|
+
const state = "occupied-port-state"
|
|
177
|
+
const callbackPromise = waitForCallback(state)
|
|
178
|
+
const response = await fetch(`http://localhost:${callbackPort}/callback?code=ok&state=${state}`)
|
|
179
|
+
assert.strictEqual(response.status, 200)
|
|
180
|
+
assert.strictEqual(await callbackPromise, "ok")
|
|
181
|
+
|
|
182
|
+
await assert.rejects(
|
|
183
|
+
async () => await ensureCallbackServer({ strictPort: true }),
|
|
184
|
+
/already in use/
|
|
185
|
+
)
|
|
186
|
+
} finally {
|
|
187
|
+
await new Promise<void>((resolve) => blocker.close(() => resolve()))
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe("waitForCallback / callback handling", () => {
|
|
193
|
+
it("should resolve with code on successful callback", async () => {
|
|
194
|
+
await ensureCallbackServer()
|
|
195
|
+
|
|
196
|
+
const state = "test-state-123"
|
|
197
|
+
const expectedCode = "auth-code-abc"
|
|
198
|
+
|
|
199
|
+
// Start waiting for callback
|
|
200
|
+
const callbackPromise = waitForCallback(state)
|
|
201
|
+
|
|
202
|
+
// Simulate callback by making HTTP request
|
|
203
|
+
const callbackPort = getOAuthCallbackPort()
|
|
204
|
+
const response = await fetch(
|
|
205
|
+
`http://localhost:${callbackPort}/callback?code=${expectedCode}&state=${state}`
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
// Should get HTML success response
|
|
209
|
+
assert.strictEqual(response.status, 200)
|
|
210
|
+
const html = await response.text()
|
|
211
|
+
assert.ok(html.includes("Authorization Successful"))
|
|
212
|
+
|
|
213
|
+
// Callback promise should resolve
|
|
214
|
+
const code = await callbackPromise
|
|
215
|
+
assert.strictEqual(code, expectedCode)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("should reject on error parameter", async () => {
|
|
219
|
+
await ensureCallbackServer()
|
|
220
|
+
|
|
221
|
+
const state = "test-state-error"
|
|
222
|
+
const errorMsg = "access_denied"
|
|
223
|
+
|
|
224
|
+
const callbackPromise = waitForCallback(state)
|
|
225
|
+
|
|
226
|
+
// Simulate error callback
|
|
227
|
+
const callbackPort = getOAuthCallbackPort()
|
|
228
|
+
const response = await fetch(
|
|
229
|
+
`http://localhost:${callbackPort}/callback?error=${errorMsg}&state=${state}`
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
assert.strictEqual(response.status, 200)
|
|
233
|
+
const html = await response.text()
|
|
234
|
+
assert.ok(html.includes("Authorization Failed"))
|
|
235
|
+
|
|
236
|
+
// Callback promise should reject
|
|
237
|
+
await assert.rejects(callbackPromise, /access_denied/)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it("should escape provider-controlled OAuth error details", async () => {
|
|
241
|
+
await ensureCallbackServer()
|
|
242
|
+
|
|
243
|
+
const state = "test-state-error-escaping"
|
|
244
|
+
const callbackPromise = waitForCallback(state)
|
|
245
|
+
const callbackPort = getOAuthCallbackPort()
|
|
246
|
+
const description = `<script>alert("x")</script>&reason=bad`
|
|
247
|
+
const response = await fetch(
|
|
248
|
+
`http://localhost:${callbackPort}/callback?error=access_denied&error_description=${encodeURIComponent(description)}&state=${state}`
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
assert.strictEqual(response.status, 200)
|
|
252
|
+
const html = await response.text()
|
|
253
|
+
assert.ok(!html.includes("<script>"))
|
|
254
|
+
assert.ok(html.includes("<script>alert("x")</script>&reason=bad"))
|
|
255
|
+
await assert.rejects(callbackPromise, /<script>alert\("x"\)<\/script>&reason=bad/)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it("should not reflect OAuth error details for invalid state", async () => {
|
|
259
|
+
await ensureCallbackServer()
|
|
260
|
+
|
|
261
|
+
const callbackPort = getOAuthCallbackPort()
|
|
262
|
+
const response = await fetch(
|
|
263
|
+
`http://localhost:${callbackPort}/callback?error=access_denied&error_description=${encodeURIComponent("<script>bad()</script>")}&state=invalid-state`
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert.strictEqual(response.status, 400)
|
|
267
|
+
const html = await response.text()
|
|
268
|
+
assert.ok(html.includes("Invalid or expired state parameter"))
|
|
269
|
+
assert.ok(!html.includes("<script>"))
|
|
270
|
+
assert.ok(!html.includes("bad()"))
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it("should return 400 for missing state", async () => {
|
|
274
|
+
await ensureCallbackServer()
|
|
275
|
+
|
|
276
|
+
const callbackPort = getOAuthCallbackPort()
|
|
277
|
+
const response = await fetch(
|
|
278
|
+
`http://localhost:${callbackPort}/callback?code=abc123`
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
assert.strictEqual(response.status, 400)
|
|
282
|
+
const html = await response.text()
|
|
283
|
+
assert.ok(html.includes("Missing required state parameter"))
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it("should return 400 for invalid state", async () => {
|
|
287
|
+
await ensureCallbackServer()
|
|
288
|
+
|
|
289
|
+
// Register a different state
|
|
290
|
+
const pendingCallback = waitForCallback("valid-state")
|
|
291
|
+
|
|
292
|
+
const callbackPort = getOAuthCallbackPort()
|
|
293
|
+
const response = await fetch(
|
|
294
|
+
`http://localhost:${callbackPort}/callback?code=abc123&state=invalid-state`
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
assert.strictEqual(response.status, 400)
|
|
298
|
+
const html = await response.text()
|
|
299
|
+
assert.ok(html.includes("Invalid or expired state parameter"))
|
|
300
|
+
|
|
301
|
+
cancelPendingCallback("valid-state")
|
|
302
|
+
await assert.rejects(pendingCallback, /Authorization cancelled/)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it("should return 400 for missing code", async () => {
|
|
306
|
+
await ensureCallbackServer()
|
|
307
|
+
|
|
308
|
+
const state = "test-state-no-code"
|
|
309
|
+
const pendingCallback = waitForCallback(state)
|
|
310
|
+
|
|
311
|
+
const callbackPort = getOAuthCallbackPort()
|
|
312
|
+
const response = await fetch(
|
|
313
|
+
`http://localhost:${callbackPort}/callback?state=${state}`
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
assert.strictEqual(response.status, 400)
|
|
317
|
+
const html = await response.text()
|
|
318
|
+
assert.ok(html.includes("No authorization code provided"))
|
|
319
|
+
|
|
320
|
+
cancelPendingCallback(state)
|
|
321
|
+
await assert.rejects(pendingCallback, /Authorization cancelled/)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it("should not switch callback paths while callbacks are pending", async () => {
|
|
325
|
+
await ensureCallbackServer({ callbackPath: "/first/callback" })
|
|
326
|
+
|
|
327
|
+
const state = "pending-path-state"
|
|
328
|
+
const callbackPromise = waitForCallback(state)
|
|
329
|
+
|
|
330
|
+
await assert.rejects(
|
|
331
|
+
async () => await ensureCallbackServer({ callbackPath: "/second/callback" }),
|
|
332
|
+
/cannot be switched while authorizations are pending/
|
|
333
|
+
)
|
|
334
|
+
assert.strictEqual(getOAuthCallbackPath(), "/first/callback")
|
|
335
|
+
|
|
336
|
+
cancelPendingCallback(state)
|
|
337
|
+
await assert.rejects(callbackPromise, /Authorization cancelled/)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it("should return 404 for wrong path", async () => {
|
|
341
|
+
await ensureCallbackServer()
|
|
342
|
+
|
|
343
|
+
const callbackPort = getOAuthCallbackPort()
|
|
344
|
+
const response = await fetch(
|
|
345
|
+
`http://localhost:${callbackPort}/wrong/path`
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
assert.strictEqual(response.status, 404)
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
describe("cancelPendingCallback", () => {
|
|
353
|
+
it("should reject pending callback", async () => {
|
|
354
|
+
await ensureCallbackServer()
|
|
355
|
+
|
|
356
|
+
const state = "test-state-cancel"
|
|
357
|
+
const callbackPromise = waitForCallback(state)
|
|
358
|
+
|
|
359
|
+
cancelPendingCallback(state)
|
|
360
|
+
|
|
361
|
+
await assert.rejects(callbackPromise, /Authorization cancelled/)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
describe("stopCallbackServer", () => {
|
|
366
|
+
it("should stop the server", async () => {
|
|
367
|
+
await ensureCallbackServer()
|
|
368
|
+
assert.strictEqual(isCallbackServerRunning(), true)
|
|
369
|
+
|
|
370
|
+
await stopCallbackServer()
|
|
371
|
+
assert.strictEqual(isCallbackServerRunning(), false)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it("should reject all pending callbacks", async () => {
|
|
375
|
+
await ensureCallbackServer()
|
|
376
|
+
|
|
377
|
+
const state1 = "state-1"
|
|
378
|
+
const state2 = "state-2"
|
|
379
|
+
|
|
380
|
+
const promise1 = waitForCallback(state1)
|
|
381
|
+
const promise2 = waitForCallback(state2)
|
|
382
|
+
|
|
383
|
+
await stopCallbackServer()
|
|
384
|
+
|
|
385
|
+
await assert.rejects(promise1, /OAuth callback server stopped/)
|
|
386
|
+
await assert.rejects(promise2, /OAuth callback server stopped/)
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
describe("getPendingAuthCount", () => {
|
|
391
|
+
it("should return 0 when no pending auths", async () => {
|
|
392
|
+
await ensureCallbackServer()
|
|
393
|
+
assert.strictEqual(getPendingAuthCount(), 0)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it("should return count of pending auths", async () => {
|
|
397
|
+
await ensureCallbackServer()
|
|
398
|
+
|
|
399
|
+
const promise1 = waitForCallback("state-1")
|
|
400
|
+
assert.strictEqual(getPendingAuthCount(), 1)
|
|
401
|
+
|
|
402
|
+
const promise2 = waitForCallback("state-2")
|
|
403
|
+
assert.strictEqual(getPendingAuthCount(), 2)
|
|
404
|
+
|
|
405
|
+
const promise3 = waitForCallback("state-3")
|
|
406
|
+
assert.strictEqual(getPendingAuthCount(), 3)
|
|
407
|
+
|
|
408
|
+
cancelPendingCallback("state-1")
|
|
409
|
+
cancelPendingCallback("state-2")
|
|
410
|
+
cancelPendingCallback("state-3")
|
|
411
|
+
await assert.rejects(promise1, /Authorization cancelled/)
|
|
412
|
+
await assert.rejects(promise2, /Authorization cancelled/)
|
|
413
|
+
await assert.rejects(promise3, /Authorization cancelled/)
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
})
|