@yunzai-ng/core 0.1.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/LICENSE +661 -0
- package/README.md +51 -0
- package/dist/adapter/accounts.d.ts +174 -0
- package/dist/adapter/accounts.d.ts.map +1 -0
- package/dist/adapter/accounts.js +653 -0
- package/dist/adapter/accounts.js.map +1 -0
- package/dist/adapter/bots.d.ts +142 -0
- package/dist/adapter/bots.d.ts.map +1 -0
- package/dist/adapter/bots.js +454 -0
- package/dist/adapter/bots.js.map +1 -0
- package/dist/adapter/host.d.ts +114 -0
- package/dist/adapter/host.d.ts.map +1 -0
- package/dist/adapter/host.js +90 -0
- package/dist/adapter/host.js.map +1 -0
- package/dist/adapter/login.d.ts +174 -0
- package/dist/adapter/login.d.ts.map +1 -0
- package/dist/adapter/login.js +345 -0
- package/dist/adapter/login.js.map +1 -0
- package/dist/adapter/registry.d.ts +102 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/registry.js +150 -0
- package/dist/adapter/registry.js.map +1 -0
- package/dist/config/core-config.d.ts +151 -0
- package/dist/config/core-config.d.ts.map +1 -0
- package/dist/config/core-config.js +338 -0
- package/dist/config/core-config.js.map +1 -0
- package/dist/config/schema.d.ts +449 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +871 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +184 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +428 -0
- package/dist/config/store.js.map +1 -0
- package/dist/config/yaml.d.ts +23 -0
- package/dist/config/yaml.d.ts.map +1 -0
- package/dist/config/yaml.js +108 -0
- package/dist/config/yaml.js.map +1 -0
- package/dist/http/client.d.ts +82 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +658 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/app.d.ts +244 -0
- package/dist/kernel/app.d.ts.map +1 -0
- package/dist/kernel/app.js +654 -0
- package/dist/kernel/app.js.map +1 -0
- package/dist/kernel/kv-sink.d.ts +34 -0
- package/dist/kernel/kv-sink.d.ts.map +1 -0
- package/dist/kernel/kv-sink.js +25 -0
- package/dist/kernel/kv-sink.js.map +1 -0
- package/dist/kernel/policy.d.ts +77 -0
- package/dist/kernel/policy.d.ts.map +1 -0
- package/dist/kernel/policy.js +99 -0
- package/dist/kernel/policy.js.map +1 -0
- package/dist/kernel/runtime.d.ts +117 -0
- package/dist/kernel/runtime.d.ts.map +1 -0
- package/dist/kernel/runtime.js +197 -0
- package/dist/kernel/runtime.js.map +1 -0
- package/dist/kernel/sql-sink.d.ts +36 -0
- package/dist/kernel/sql-sink.d.ts.map +1 -0
- package/dist/kernel/sql-sink.js +114 -0
- package/dist/kernel/sql-sink.js.map +1 -0
- package/dist/logger/format.d.ts +67 -0
- package/dist/logger/format.d.ts.map +1 -0
- package/dist/logger/format.js +218 -0
- package/dist/logger/format.js.map +1 -0
- package/dist/logger/index.d.ts +97 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +363 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/rotate.d.ts +47 -0
- package/dist/logger/rotate.d.ts.map +1 -0
- package/dist/logger/rotate.js +242 -0
- package/dist/logger/rotate.js.map +1 -0
- package/dist/message/segment.d.ts +255 -0
- package/dist/message/segment.d.ts.map +1 -0
- package/dist/message/segment.js +510 -0
- package/dist/message/segment.js.map +1 -0
- package/dist/message/split.d.ts +34 -0
- package/dist/message/split.d.ts.map +1 -0
- package/dist/message/split.js +79 -0
- package/dist/message/split.js.map +1 -0
- package/dist/message/target.d.ts +29 -0
- package/dist/message/target.d.ts.map +1 -0
- package/dist/message/target.js +34 -0
- package/dist/message/target.js.map +1 -0
- package/dist/pipeline/cooldown.d.ts +84 -0
- package/dist/pipeline/cooldown.d.ts.map +1 -0
- package/dist/pipeline/cooldown.js +94 -0
- package/dist/pipeline/cooldown.js.map +1 -0
- package/dist/pipeline/dispatch.d.ts +98 -0
- package/dist/pipeline/dispatch.d.ts.map +1 -0
- package/dist/pipeline/dispatch.js +323 -0
- package/dist/pipeline/dispatch.js.map +1 -0
- package/dist/pipeline/event.d.ts +130 -0
- package/dist/pipeline/event.d.ts.map +1 -0
- package/dist/pipeline/event.js +538 -0
- package/dist/pipeline/event.js.map +1 -0
- package/dist/pipeline/middleware.d.ts +63 -0
- package/dist/pipeline/middleware.d.ts.map +1 -0
- package/dist/pipeline/middleware.js +174 -0
- package/dist/pipeline/middleware.js.map +1 -0
- package/dist/pipeline/prompt.d.ts +80 -0
- package/dist/pipeline/prompt.d.ts.map +1 -0
- package/dist/pipeline/prompt.js +155 -0
- package/dist/pipeline/prompt.js.map +1 -0
- package/dist/pipeline/router.d.ts +98 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +489 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/platform/detect.d.ts +24 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +178 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/paths.d.ts +38 -0
- package/dist/platform/paths.d.ts.map +1 -0
- package/dist/platform/paths.js +129 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/system.d.ts +71 -0
- package/dist/platform/system.d.ts.map +1 -0
- package/dist/platform/system.js +242 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/plugin/context.d.ts +92 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +573 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +92 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +82 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/discover.d.ts +106 -0
- package/dist/plugin/discover.d.ts.map +1 -0
- package/dist/plugin/discover.js +254 -0
- package/dist/plugin/discover.js.map +1 -0
- package/dist/plugin/events.d.ts +109 -0
- package/dist/plugin/events.d.ts.map +1 -0
- package/dist/plugin/events.js +209 -0
- package/dist/plugin/events.js.map +1 -0
- package/dist/plugin/hooks.d.ts +266 -0
- package/dist/plugin/hooks.d.ts.map +1 -0
- package/dist/plugin/hooks.js +84 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.d.ts +160 -0
- package/dist/plugin/host.d.ts.map +1 -0
- package/dist/plugin/host.js +546 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/market.d.ts +264 -0
- package/dist/plugin/market.d.ts.map +1 -0
- package/dist/plugin/market.js +700 -0
- package/dist/plugin/market.js.map +1 -0
- package/dist/plugin/services.d.ts +116 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +213 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/tar.d.ts +65 -0
- package/dist/plugin/tar.d.ts.map +1 -0
- package/dist/plugin/tar.js +277 -0
- package/dist/plugin/tar.js.map +1 -0
- package/dist/render/registry.d.ts +127 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +306 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/scheduler/index.d.ts +67 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +279 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/server/api.d.ts +179 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +602 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/auth.d.ts +83 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +178 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/browse.d.ts +60 -0
- package/dist/server/browse.d.ts.map +1 -0
- package/dist/server/browse.js +187 -0
- package/dist/server/browse.js.map +1 -0
- package/dist/server/files.d.ts +27 -0
- package/dist/server/files.d.ts.map +1 -0
- package/dist/server/files.js +76 -0
- package/dist/server/files.js.map +1 -0
- package/dist/server/index.d.ts +189 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1123 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/table.d.ts +105 -0
- package/dist/server/table.d.ts.map +1 -0
- package/dist/server/table.js +289 -0
- package/dist/server/table.js.map +1 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +120 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +73 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +174 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/kv.d.ts +126 -0
- package/dist/store/kv.d.ts.map +1 -0
- package/dist/store/kv.js +232 -0
- package/dist/store/kv.js.map +1 -0
- package/dist/store/level.d.ts +103 -0
- package/dist/store/level.d.ts.map +1 -0
- package/dist/store/level.js +119 -0
- package/dist/store/level.js.map +1 -0
- package/dist/store/memory.d.ts +61 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +94 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/sql.d.ts +142 -0
- package/dist/store/sql.d.ts.map +1 -0
- package/dist/store/sql.js +246 -0
- package/dist/store/sql.js.map +1 -0
- package/dist/testing/fake.d.ts +109 -0
- package/dist/testing/fake.d.ts.map +1 -0
- package/dist/testing/fake.js +196 -0
- package/dist/testing/fake.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-adapter.d.ts +205 -0
- package/dist/testing/mock-adapter.d.ts.map +1 -0
- package/dist/testing/mock-adapter.js +309 -0
- package/dist/testing/mock-adapter.js.map +1 -0
- package/dist/util/deep.d.ts +108 -0
- package/dist/util/deep.d.ts.map +1 -0
- package/dist/util/deep.js +275 -0
- package/dist/util/deep.js.map +1 -0
- package/dist/util/defer.d.ts +107 -0
- package/dist/util/defer.d.ts.map +1 -0
- package/dist/util/defer.js +163 -0
- package/dist/util/defer.js.map +1 -0
- package/dist/util/dispose.d.ts +88 -0
- package/dist/util/dispose.d.ts.map +1 -0
- package/dist/util/dispose.js +143 -0
- package/dist/util/dispose.js.map +1 -0
- package/dist/util/duration.d.ts +28 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +77 -0
- package/dist/util/duration.js.map +1 -0
- package/dist/util/fs.d.ts +110 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +269 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/id.d.ts +49 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +87 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lru.d.ts +74 -0
- package/dist/util/lru.d.ts.map +1 -0
- package/dist/util/lru.js +156 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/util/queue.d.ts +64 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +147 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/text.d.ts +86 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +182 -0
- package/dist/util/text.js.map +1 -0
- package/package.json +44 -0
- package/src/adapter/accounts.ts +758 -0
- package/src/adapter/bots.ts +582 -0
- package/src/adapter/host.ts +224 -0
- package/src/adapter/login.ts +481 -0
- package/src/adapter/registry.ts +202 -0
- package/src/config/core-config.test.ts +60 -0
- package/src/config/core-config.ts +401 -0
- package/src/config/schema.test.ts +179 -0
- package/src/config/schema.ts +1036 -0
- package/src/config/store.test.ts +245 -0
- package/src/config/store.ts +506 -0
- package/src/config/yaml.ts +119 -0
- package/src/http/client.test.ts +568 -0
- package/src/http/client.ts +777 -0
- package/src/index.ts +106 -0
- package/src/kernel/app.test.ts +267 -0
- package/src/kernel/app.ts +843 -0
- package/src/kernel/kv-sink.ts +58 -0
- package/src/kernel/policy.ts +120 -0
- package/src/kernel/runtime.test.ts +542 -0
- package/src/kernel/runtime.ts +307 -0
- package/src/kernel/sql-sink.ts +154 -0
- package/src/logger/format.ts +259 -0
- package/src/logger/index.ts +427 -0
- package/src/logger/rotate.ts +265 -0
- package/src/message/segment.test.ts +335 -0
- package/src/message/segment.ts +569 -0
- package/src/message/split.ts +97 -0
- package/src/message/target.ts +48 -0
- package/src/pipeline/cooldown.ts +136 -0
- package/src/pipeline/dispatch.ts +419 -0
- package/src/pipeline/event.ts +735 -0
- package/src/pipeline/middleware.test.ts +426 -0
- package/src/pipeline/middleware.ts +199 -0
- package/src/pipeline/prompt.ts +212 -0
- package/src/pipeline/router.test.ts +432 -0
- package/src/pipeline/router.ts +575 -0
- package/src/platform/detect.ts +178 -0
- package/src/platform/paths.ts +147 -0
- package/src/platform/system.test.ts +102 -0
- package/src/platform/system.ts +289 -0
- package/src/plugin/context.test.ts +459 -0
- package/src/plugin/context.ts +757 -0
- package/src/plugin/define.test.ts +120 -0
- package/src/plugin/define.ts +142 -0
- package/src/plugin/discover.test.ts +253 -0
- package/src/plugin/discover.ts +353 -0
- package/src/plugin/events.test.ts +162 -0
- package/src/plugin/events.ts +266 -0
- package/src/plugin/hooks.ts +373 -0
- package/src/plugin/host.test.ts +696 -0
- package/src/plugin/host.ts +738 -0
- package/src/plugin/market.test.ts +781 -0
- package/src/plugin/market.ts +870 -0
- package/src/plugin/services.test.ts +135 -0
- package/src/plugin/services.ts +261 -0
- package/src/plugin/tar.test.ts +158 -0
- package/src/plugin/tar.ts +309 -0
- package/src/render/registry.test.ts +127 -0
- package/src/render/registry.ts +439 -0
- package/src/scheduler/index.ts +357 -0
- package/src/server/api.test.ts +802 -0
- package/src/server/api.ts +869 -0
- package/src/server/auth.ts +201 -0
- package/src/server/browse.test.ts +193 -0
- package/src/server/browse.ts +240 -0
- package/src/server/files.ts +97 -0
- package/src/server/index.test.ts +566 -0
- package/src/server/index.ts +1386 -0
- package/src/server/table.ts +351 -0
- package/src/store/index.ts +156 -0
- package/src/store/json.ts +198 -0
- package/src/store/kv.test.ts +307 -0
- package/src/store/kv.ts +256 -0
- package/src/store/level.ts +167 -0
- package/src/store/memory.ts +109 -0
- package/src/store/sql.test.ts +204 -0
- package/src/store/sql.ts +324 -0
- package/src/testing/fake.ts +297 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/mock-adapter.ts +588 -0
- package/src/util/deep.ts +272 -0
- package/src/util/defer.ts +211 -0
- package/src/util/dispose.ts +169 -0
- package/src/util/duration.ts +82 -0
- package/src/util/fs.ts +279 -0
- package/src/util/id.ts +94 -0
- package/src/util/lru.ts +185 -0
- package/src/util/queue.ts +167 -0
- package/src/util/text.ts +189 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:HTTP 客户端测试
|
|
3
|
+
* 依赖方向:测试文件,依赖 http/client + node:http
|
|
4
|
+
* 生命周期:整个文件共用一个本地 HTTP 服务器,由 `afterAll` 关闭
|
|
5
|
+
* 注意事项:此处**不对 fetch 打桩**,而是实际启动一个 `node:http` 服务器。
|
|
6
|
+
* 超时、重试、`Retry-After`、gzip 解压、重定向、连接池归还
|
|
7
|
+
* 等行为均位于 undici 与内核的接缝处,打桩将使待测行为不再被覆盖。
|
|
8
|
+
*
|
|
9
|
+
* `/slow`、`/flaky` 一类路由带有状态,用例之间以不同的 key 相互隔离,
|
|
10
|
+
* 不得在两个用例中共用同一个 key。
|
|
11
|
+
*/
|
|
12
|
+
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"
|
|
13
|
+
import { gzipSync } from "node:zlib"
|
|
14
|
+
import { readFile, rm } from "node:fs/promises"
|
|
15
|
+
import { tmpdir } from "node:os"
|
|
16
|
+
import { join } from "node:path"
|
|
17
|
+
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"
|
|
18
|
+
import { AbortError } from "../util/defer.js"
|
|
19
|
+
import { HttpError, createHttpClient, sanitizeHeaders, sanitizeUrl, type ManagedHttpClient } from "./client.js"
|
|
20
|
+
|
|
21
|
+
/** 服务器实例 */
|
|
22
|
+
let server: Server
|
|
23
|
+
|
|
24
|
+
/** 服务器根地址 */
|
|
25
|
+
let base: string
|
|
26
|
+
|
|
27
|
+
/** 每个 key 已被请求过几次(测重试) */
|
|
28
|
+
const hits = new Map<string, number>()
|
|
29
|
+
|
|
30
|
+
/** 收到的请求头(按路径记最后一次) */
|
|
31
|
+
const seenHeaders = new Map<string, IncomingMessage["headers"]>()
|
|
32
|
+
|
|
33
|
+
/** 收到的请求体(按路径记最后一次) */
|
|
34
|
+
const seenBodies = new Map<string, string>()
|
|
35
|
+
|
|
36
|
+
/** 本轮用例创建的客户端,afterEach 统一关掉 */
|
|
37
|
+
const clients: ManagedHttpClient[] = []
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 建一个客户端并登记待关闭
|
|
41
|
+
* @param opts 客户端参数
|
|
42
|
+
* @returns 客户端
|
|
43
|
+
*/
|
|
44
|
+
function makeClient(opts: Parameters<typeof createHttpClient>[0] = {}): ManagedHttpClient {
|
|
45
|
+
// useEnvProxy: false —— 开发机上通常设置了 HTTPS_PROXY,经代理连接 127.0.0.1 必然失败,
|
|
46
|
+
// 该失败与被测逻辑无关,只会导致用例无故报错
|
|
47
|
+
const client = createHttpClient({ useEnvProxy: false, ...opts })
|
|
48
|
+
clients.push(client)
|
|
49
|
+
return client
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 读完请求体
|
|
54
|
+
* @param req 请求
|
|
55
|
+
* @returns 请求体文本
|
|
56
|
+
*/
|
|
57
|
+
async function readBody(req: IncomingMessage): Promise<string> {
|
|
58
|
+
const chunks: Buffer[] = []
|
|
59
|
+
for await (const chunk of req) chunks.push(chunk as Buffer)
|
|
60
|
+
return Buffer.concat(chunks).toString("utf8")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 取得一次调用抛出的错误
|
|
65
|
+
*
|
|
66
|
+
* 相较 `.catch(e => e)` 的优势在于其同时断言该次调用确实抛出了错误 —— 使用 catch 时,
|
|
67
|
+
* 若被测代码意外成功,后续断言将以成功值进行比对,报错信息不具备提示性。
|
|
68
|
+
* @param promise 待观察的调用
|
|
69
|
+
* @returns 抛出的错误
|
|
70
|
+
* @throws 调用成功时抛错
|
|
71
|
+
*/
|
|
72
|
+
async function caught(promise: Promise<unknown>): Promise<HttpError> {
|
|
73
|
+
try {
|
|
74
|
+
await promise
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return err as HttpError
|
|
77
|
+
}
|
|
78
|
+
throw new Error("期望这次调用抛错,但它成功了")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 路由分发
|
|
83
|
+
* @param req 请求
|
|
84
|
+
* @param res 响应
|
|
85
|
+
*/
|
|
86
|
+
async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
87
|
+
const url = new URL(req.url ?? "/", base)
|
|
88
|
+
const path = url.pathname
|
|
89
|
+
seenHeaders.set(path, req.headers)
|
|
90
|
+
seenBodies.set(path, await readBody(req))
|
|
91
|
+
|
|
92
|
+
/** 计数并返回当前次数 */
|
|
93
|
+
const bump = (key: string): number => {
|
|
94
|
+
const next = (hits.get(key) ?? 0) + 1
|
|
95
|
+
hits.set(key, next)
|
|
96
|
+
return next
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
switch (path) {
|
|
100
|
+
case "/json":
|
|
101
|
+
res.writeHead(200, { "content-type": "application/json" })
|
|
102
|
+
res.end(JSON.stringify({ ok: true, query: Object.fromEntries(url.searchParams) }))
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
case "/text":
|
|
106
|
+
res.writeHead(200, { "content-type": "text/plain" })
|
|
107
|
+
res.end("纯文本")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
case "/bytes":
|
|
111
|
+
res.writeHead(200, { "content-type": "application/octet-stream" })
|
|
112
|
+
res.end(Buffer.from([1, 2, 3, 4]))
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
case "/empty":
|
|
116
|
+
res.writeHead(204)
|
|
117
|
+
res.end()
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
case "/gzip": {
|
|
121
|
+
const packed = gzipSync(Buffer.from(JSON.stringify({ zipped: true }), "utf8"))
|
|
122
|
+
res.writeHead(200, { "content-type": "application/json", "content-encoding": "gzip" })
|
|
123
|
+
res.end(packed)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case "/echo":
|
|
128
|
+
res.writeHead(200, { "content-type": "application/json" })
|
|
129
|
+
res.end(JSON.stringify({ method: req.method, body: seenBodies.get(path), type: req.headers["content-type"] }))
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
case "/not-json":
|
|
133
|
+
res.writeHead(200, { "content-type": "application/json" })
|
|
134
|
+
res.end("<html>网关错误页</html>")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
case "/404":
|
|
138
|
+
res.writeHead(404, { "content-type": "text/plain" })
|
|
139
|
+
res.end("资源不存在")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
case "/flaky": {
|
|
143
|
+
// 前两次 503,第三次成功
|
|
144
|
+
const key = `flaky:${url.searchParams.get("k") ?? ""}`
|
|
145
|
+
const n = bump(key)
|
|
146
|
+
if (n <= 2) {
|
|
147
|
+
res.writeHead(503, { "content-type": "text/plain" })
|
|
148
|
+
res.end("暂时不可用")
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
res.writeHead(200, { "content-type": "application/json" })
|
|
152
|
+
res.end(JSON.stringify({ attempts: n }))
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case "/rate-limited": {
|
|
157
|
+
const key = `rate:${url.searchParams.get("k") ?? ""}`
|
|
158
|
+
const n = bump(key)
|
|
159
|
+
if (n === 1) {
|
|
160
|
+
res.writeHead(429, { "content-type": "text/plain", "retry-after": "0" })
|
|
161
|
+
res.end("慢点")
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
res.writeHead(200, { "content-type": "application/json" })
|
|
165
|
+
res.end(JSON.stringify({ attempts: n }))
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case "/count": {
|
|
170
|
+
const n = bump(`count:${url.searchParams.get("k") ?? ""}`)
|
|
171
|
+
res.writeHead(500, { "content-type": "text/plain" })
|
|
172
|
+
res.end(String(n))
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case "/slow":
|
|
177
|
+
// 永不响应:交给客户端超时
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
case "/redirect":
|
|
181
|
+
res.writeHead(302, { location: "/json" })
|
|
182
|
+
res.end()
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
case "/huge": {
|
|
186
|
+
res.writeHead(200, { "content-type": "application/octet-stream" })
|
|
187
|
+
// 分块写 4 MiB,用来撞 maxBodySize
|
|
188
|
+
for (let i = 0; i < 64; i++) res.write(Buffer.alloc(64 * 1024, 7))
|
|
189
|
+
res.end()
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case "/file":
|
|
194
|
+
res.writeHead(200, { "content-type": "application/octet-stream" })
|
|
195
|
+
res.end(Buffer.from("下载内容", "utf8"))
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
case "/broken-stream":
|
|
199
|
+
res.writeHead(200, { "content-type": "application/octet-stream", "content-length": "1024" })
|
|
200
|
+
res.write(Buffer.alloc(16, 1))
|
|
201
|
+
// 中途掐断:download() 应该把 .part 清掉
|
|
202
|
+
req.socket.destroy()
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
default:
|
|
206
|
+
res.writeHead(404)
|
|
207
|
+
res.end()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
beforeAll(async () => {
|
|
212
|
+
server = createServer((req, res) => {
|
|
213
|
+
void handle(req, res).catch(() => {
|
|
214
|
+
if (!res.headersSent) res.writeHead(500)
|
|
215
|
+
res.end()
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve))
|
|
219
|
+
const addr = server.address()
|
|
220
|
+
if (addr === null || typeof addr === "string") throw new Error("测试服务器没拿到端口")
|
|
221
|
+
base = `http://127.0.0.1:${addr.port}`
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
afterEach(async () => {
|
|
225
|
+
await Promise.all(clients.splice(0).map(client => client.close()))
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
afterAll(async () => {
|
|
229
|
+
await new Promise<void>(resolve => server.close(() => resolve()))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe("请求与响应解析", () => {
|
|
233
|
+
it("默认按 JSON 解析", async () => {
|
|
234
|
+
const http = makeClient()
|
|
235
|
+
await expect(http.get(`${base}/json`)).resolves.toMatchObject({ ok: true })
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it("request 给出完整响应元信息", async () => {
|
|
239
|
+
const http = makeClient()
|
|
240
|
+
const res = await http.request(`${base}/json`)
|
|
241
|
+
|
|
242
|
+
expect(res.status).toBe(200)
|
|
243
|
+
expect(res.ok).toBe(true)
|
|
244
|
+
expect(res.headers["content-type"]).toContain("application/json")
|
|
245
|
+
expect(res.cost).toBeGreaterThanOrEqual(0)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it("按 responseType 给文本 / 字节 / 不读", async () => {
|
|
249
|
+
const http = makeClient()
|
|
250
|
+
|
|
251
|
+
await expect(http.request<string>(`${base}/text`, { responseType: "text" })).resolves.toMatchObject({
|
|
252
|
+
data: "纯文本"
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// 刻意断言是 Buffer 而不是裸 Uint8Array:适配器发图片时要 toString("base64")
|
|
256
|
+
const bytes = await http.buffer(`${base}/bytes`)
|
|
257
|
+
expect(Buffer.isBuffer(bytes)).toBe(true)
|
|
258
|
+
expect([...bytes]).toEqual([1, 2, 3, 4])
|
|
259
|
+
|
|
260
|
+
await expect(http.request(`${base}/json`, { responseType: "none" })).resolves.toMatchObject({ data: undefined })
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it("空响应体不会导致 JSON 解析失败", async () => {
|
|
264
|
+
const http = makeClient()
|
|
265
|
+
const res = await http.request(`${base}/empty`)
|
|
266
|
+
expect(res.status).toBe(204)
|
|
267
|
+
expect(res.data).toBeUndefined()
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it("自动解压 gzip(undici 的 request 不像 fetch 那样自带)", async () => {
|
|
271
|
+
const http = makeClient()
|
|
272
|
+
await expect(http.get(`${base}/gzip`)).resolves.toEqual({ zipped: true })
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it("响应不是 JSON 时报错带上片段", async () => {
|
|
276
|
+
const http = makeClient()
|
|
277
|
+
await expect(http.get(`${base}/not-json`)).rejects.toThrow(/不是合法 JSON.*网关错误页/s)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it("stream 模式把流交给调用方", async () => {
|
|
281
|
+
const http = makeClient()
|
|
282
|
+
const res = await http.request<import("node:stream").Readable>(`${base}/text`, { responseType: "stream" })
|
|
283
|
+
|
|
284
|
+
const chunks: Buffer[] = []
|
|
285
|
+
for await (const chunk of res.data) chunks.push(chunk as Buffer)
|
|
286
|
+
expect(Buffer.concat(chunks).toString("utf8")).toBe("纯文本")
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
describe("URL 与请求头", () => {
|
|
291
|
+
it("query 里的 undefined / null 被丢掉", async () => {
|
|
292
|
+
const http = makeClient()
|
|
293
|
+
const res = await http.get<{
|
|
294
|
+
/** 服务端回显的查询串 */
|
|
295
|
+
query: Record<string, string>
|
|
296
|
+
}>(`${base}/json`, { query: { uid: 114, keep: "yes", drop: undefined, alsoDrop: null } })
|
|
297
|
+
|
|
298
|
+
expect(res.query).toEqual({ uid: "114", keep: "yes" })
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it("baseUrl 与相对路径拼接(两边有没有斜杠都行)", async () => {
|
|
302
|
+
const a = makeClient({ baseUrl: base })
|
|
303
|
+
const b = makeClient({ baseUrl: `${base}/` })
|
|
304
|
+
|
|
305
|
+
await expect(a.get("json")).resolves.toMatchObject({ ok: true })
|
|
306
|
+
await expect(b.get("/json")).resolves.toMatchObject({ ok: true })
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it("未配置 baseUrl 时相对路径直接报错,不作推断", async () => {
|
|
310
|
+
const http = makeClient()
|
|
311
|
+
await expect(http.get("/json")).rejects.toThrow(/不是绝对 URL/)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it("请求头三层合并:内置 < 客户端默认 < 单次调用", async () => {
|
|
315
|
+
// 头值一律使用 ASCII —— HTTP 头仅允许 latin1,写入中文会被 assertHeaders 拦截
|
|
316
|
+
const http = makeClient({ headers: { "x-from": "default", "x-keep": "kept" }, userAgent: "yzng-test" })
|
|
317
|
+
await http.get(`${base}/json`, { headers: { "X-From": "once" } })
|
|
318
|
+
|
|
319
|
+
const headers = seenHeaders.get("/json")
|
|
320
|
+
expect(headers?.["x-from"]).toBe("once")
|
|
321
|
+
expect(headers?.["x-keep"]).toBe("kept")
|
|
322
|
+
expect(headers?.["user-agent"]).toBe("yzng-test")
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it("头值写入中文时立即报错,并说明正确的传递方式", async () => {
|
|
326
|
+
// undici 仅返回一句 `invalid x-foo header`,插件作者(例如将群名写入请求头)
|
|
327
|
+
// 无从判断问题所在
|
|
328
|
+
const http = makeClient()
|
|
329
|
+
await expect(http.get(`${base}/json`, { headers: { "x-name": "群名" } })).rejects.toThrow(
|
|
330
|
+
/latin1.*encodeURIComponent/s
|
|
331
|
+
)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it("json 与 form 自动设 content-type", async () => {
|
|
335
|
+
const http = makeClient()
|
|
336
|
+
|
|
337
|
+
await expect(http.post(`${base}/echo`, { a: 1 })).resolves.toMatchObject({
|
|
338
|
+
method: "POST",
|
|
339
|
+
body: '{"a":1}',
|
|
340
|
+
type: "application/json"
|
|
341
|
+
})
|
|
342
|
+
await expect(
|
|
343
|
+
http.request(`${base}/echo`, { form: { uid: 114, name: "甲" } })
|
|
344
|
+
).resolves.toMatchObject({
|
|
345
|
+
data: { method: "POST", body: "uid=114&name=%E7%94%B2", type: "application/x-www-form-urlencoded" }
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it("有 body 时方法默认 POST,无 body 默认 GET", async () => {
|
|
350
|
+
const http = makeClient()
|
|
351
|
+
await expect(http.request(`${base}/echo`, { body: "裸体" })).resolves.toMatchObject({
|
|
352
|
+
data: { method: "POST" }
|
|
353
|
+
})
|
|
354
|
+
await expect(http.request(`${base}/echo`)).resolves.toMatchObject({ data: { method: "GET" } })
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it("默认跟随重定向,关掉后拿到 302", async () => {
|
|
358
|
+
const http = makeClient()
|
|
359
|
+
|
|
360
|
+
await expect(http.get(`${base}/redirect`)).resolves.toMatchObject({ ok: true })
|
|
361
|
+
const res = await http.request(`${base}/redirect`, { followRedirect: false, throwOnError: false })
|
|
362
|
+
expect(res.status).toBe(302)
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
describe("错误处理", () => {
|
|
367
|
+
it("非 2xx 抛 HttpError,带状态码与片段", async () => {
|
|
368
|
+
const http = makeClient()
|
|
369
|
+
await expect(http.get(`${base}/404`)).rejects.toThrow(HttpError)
|
|
370
|
+
|
|
371
|
+
const err = await caught(http.get(`${base}/404`))
|
|
372
|
+
expect(err.status).toBe(404)
|
|
373
|
+
expect(err.message).toContain("资源不存在")
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it("throwOnError 关掉后由调用方自己判断", async () => {
|
|
377
|
+
const http = makeClient()
|
|
378
|
+
const res = await http.request(`${base}/404`, { responseType: "text", throwOnError: false })
|
|
379
|
+
expect(res.ok).toBe(false)
|
|
380
|
+
expect(res.status).toBe(404)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it("连不上时给 status 0 的 HttpError(而不是裸的 ECONNREFUSED)", async () => {
|
|
384
|
+
const http = makeClient()
|
|
385
|
+
// 1 端口上没人监听
|
|
386
|
+
const err = await caught(http.get("http://127.0.0.1:1/nope"))
|
|
387
|
+
expect(err).toBeInstanceOf(HttpError)
|
|
388
|
+
expect(err.status).toBe(0)
|
|
389
|
+
expect(err.message).toContain("请求失败")
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it("超时报错说清超了多久", async () => {
|
|
393
|
+
const http = makeClient()
|
|
394
|
+
const err = await caught(http.get(`${base}/slow`, { timeout: 150 }))
|
|
395
|
+
expect(err).toBeInstanceOf(HttpError)
|
|
396
|
+
expect(err.message).toMatch(/请求超时(150ms)/)
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it("调用方取消时抛 AbortError,不伪装成服务端故障", async () => {
|
|
400
|
+
const http = makeClient()
|
|
401
|
+
const abort = new AbortController()
|
|
402
|
+
const pending = http.get(`${base}/slow`, { signal: abort.signal, timeout: 5000 })
|
|
403
|
+
setTimeout(() => abort.abort(), 30)
|
|
404
|
+
|
|
405
|
+
await expect(pending).rejects.toBeInstanceOf(AbortError)
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it("响应体超过上限时中断并指路 download()", async () => {
|
|
409
|
+
const http = makeClient({ maxBodySize: 128 * 1024 })
|
|
410
|
+
await expect(http.buffer(`${base}/huge`)).rejects.toThrow(/超过上限.*download\(\)/s)
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
describe("重试", () => {
|
|
415
|
+
it("503 重试到成功", async () => {
|
|
416
|
+
const http = makeClient()
|
|
417
|
+
const res = await http.get<{
|
|
418
|
+
/** 服务端记录的尝试次数 */
|
|
419
|
+
attempts: number
|
|
420
|
+
}>(`${base}/flaky`, { query: { k: "basic" }, retry: 3 })
|
|
421
|
+
|
|
422
|
+
expect(res.attempts).toBe(3)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it("默认不重试(次数为 0)", async () => {
|
|
426
|
+
const http = makeClient()
|
|
427
|
+
await expect(http.get(`${base}/flaky`, { query: { k: "none" } })).rejects.toThrow(/503/)
|
|
428
|
+
expect(hits.get("flaky:none")).toBe(1)
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it("重试次数用尽后抛最后一次的错误", async () => {
|
|
432
|
+
const http = makeClient()
|
|
433
|
+
await expect(
|
|
434
|
+
http.get(`${base}/count`, { query: { k: "exhaust" }, retry: { times: 2, delay: "1ms" } })
|
|
435
|
+
).rejects.toThrow(/500/)
|
|
436
|
+
// 首次 + 2 次重试
|
|
437
|
+
expect(hits.get("count:exhaust")).toBe(3)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it("429 时听 Retry-After", async () => {
|
|
441
|
+
const http = makeClient()
|
|
442
|
+
await expect(
|
|
443
|
+
http.get(`${base}/rate-limited`, { query: { k: "ra" }, retry: 1 })
|
|
444
|
+
).resolves.toMatchObject({ attempts: 2 })
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it("404 这种语义错误不重试(重试一百次也是同样结果)", async () => {
|
|
448
|
+
const http = makeClient()
|
|
449
|
+
hits.delete("count:na")
|
|
450
|
+
await expect(http.get(`${base}/404`, { retry: 3 })).rejects.toThrow(/404/)
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it("retry.when 可以自定义判定", async () => {
|
|
454
|
+
const http = makeClient()
|
|
455
|
+
const res = await http.request(`${base}/count`, {
|
|
456
|
+
query: { k: "custom" },
|
|
457
|
+
responseType: "text",
|
|
458
|
+
throwOnError: false,
|
|
459
|
+
retry: {
|
|
460
|
+
times: 2,
|
|
461
|
+
delay: "1ms",
|
|
462
|
+
// 只在第一次的响应体是 "1" 时重试,第二次就收手
|
|
463
|
+
when: (_err, response) => response?.data === "1"
|
|
464
|
+
}
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
expect(res.data).toBe("2")
|
|
468
|
+
expect(hits.get("count:custom")).toBe(2)
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
it("取消信号能打断重试之间的等待", async () => {
|
|
472
|
+
const http = makeClient()
|
|
473
|
+
const abort = new AbortController()
|
|
474
|
+
const pending = http.get(`${base}/count`, {
|
|
475
|
+
query: { k: "abortwait" },
|
|
476
|
+
retry: { times: 5, delay: "5s" },
|
|
477
|
+
signal: abort.signal
|
|
478
|
+
})
|
|
479
|
+
setTimeout(() => abort.abort(), 50)
|
|
480
|
+
|
|
481
|
+
await expect(pending).rejects.toBeInstanceOf(AbortError)
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
describe("download", () => {
|
|
486
|
+
/** 落盘目录 */
|
|
487
|
+
const dir = join(tmpdir(), "yzng-http-download")
|
|
488
|
+
|
|
489
|
+
afterEach(async () => {
|
|
490
|
+
await rm(dir, { recursive: true, force: true })
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
it("流式落盘,父目录自动创建", async () => {
|
|
494
|
+
const http = makeClient()
|
|
495
|
+
const dest = join(dir, "深", "一层", "a.bin")
|
|
496
|
+
|
|
497
|
+
await expect(http.download(`${base}/file`, dest)).resolves.toBe(dest)
|
|
498
|
+
await expect(readFile(dest, "utf8")).resolves.toBe("下载内容")
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it("中途断流时不留下半个文件(否则会被当成有效缓存)", async () => {
|
|
502
|
+
const http = makeClient()
|
|
503
|
+
const dest = join(dir, "broken.bin")
|
|
504
|
+
|
|
505
|
+
await expect(http.download(`${base}/broken-stream`, dest)).rejects.toThrow()
|
|
506
|
+
await expect(readFile(dest)).rejects.toThrow(/ENOENT/)
|
|
507
|
+
await expect(readFile(`${dest}.part`)).rejects.toThrow(/ENOENT/)
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
describe("extend", () => {
|
|
512
|
+
it("派生客户端继承并覆盖默认值,且不影响原客户端", async () => {
|
|
513
|
+
const root = makeClient({ baseUrl: base, headers: { "x-tag": "root" } })
|
|
514
|
+
const child = root.extend({ headers: { "x-tag": "child" } })
|
|
515
|
+
|
|
516
|
+
await child.get("json")
|
|
517
|
+
expect(seenHeaders.get("/json")?.["x-tag"]).toBe("child")
|
|
518
|
+
|
|
519
|
+
await root.get("json")
|
|
520
|
+
expect(seenHeaders.get("/json")?.["x-tag"]).toBe("root")
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
it("派生客户端共享连接池,关根即可", async () => {
|
|
524
|
+
const root = makeClient({ baseUrl: base })
|
|
525
|
+
const child = root.extend({ timeout: "5s" })
|
|
526
|
+
|
|
527
|
+
await expect(child.get("json")).resolves.toMatchObject({ ok: true })
|
|
528
|
+
await root.close()
|
|
529
|
+
// 关掉后必须明确拒绝:悄悄重建一个池子就等于 close() 白调,
|
|
530
|
+
// 进程还会被 keep-alive 的空闲 socket 吊着不退出
|
|
531
|
+
await expect(child.get("json")).rejects.toThrow(/已关闭/)
|
|
532
|
+
})
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
describe("脱敏", () => {
|
|
536
|
+
it("URL 里的 authkey 等密钥被打码", () => {
|
|
537
|
+
const masked = sanitizeUrl("https://hk4e-api.mihoyo.com/gacha?authkey=ABCDEFGHIJKLMNOP&size=20&uid=114")
|
|
538
|
+
|
|
539
|
+
expect(masked).not.toContain("ABCDEFGHIJKLMNOP")
|
|
540
|
+
expect(masked).toContain("uid=114")
|
|
541
|
+
expect(masked).toContain("size=20")
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it("格式错误的 URL 亦不会原样输出", () => {
|
|
545
|
+
const masked = sanitizeUrl(`不是url但很长${"x".repeat(300)}`)
|
|
546
|
+
expect(masked.length).toBeLessThan(140)
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it("请求头里的 cookie / authorization 被打码", () => {
|
|
550
|
+
const masked = sanitizeHeaders({
|
|
551
|
+
cookie: "ltuid=123456789; ltoken=SECRETVALUE12345",
|
|
552
|
+
authorization: "Bearer SECRETTOKENVALUE",
|
|
553
|
+
"x-normal": "看得见"
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
expect(masked["cookie"]).not.toContain("SECRETVALUE12345")
|
|
557
|
+
expect(masked["authorization"]).not.toContain("SECRETTOKENVALUE")
|
|
558
|
+
expect(masked["x-normal"]).toBe("看得见")
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
it("报错信息里的 URL 已经打过码", async () => {
|
|
562
|
+
const http = makeClient()
|
|
563
|
+
const err = await caught(http.get(`${base}/404`, { query: { authkey: "ABCDEFGHIJKLMNOP" } }))
|
|
564
|
+
|
|
565
|
+
expect(err.message).not.toContain("ABCDEFGHIJKLMNOP")
|
|
566
|
+
expect(err.url).not.toContain("ABCDEFGHIJKLMNOP")
|
|
567
|
+
})
|
|
568
|
+
})
|