@taicode/common-server 4.0.0-beta.2 → 4.0.0-beta.4
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/output/app/index.d.ts +13 -15
- package/output/app/index.d.ts.map +1 -1
- package/output/app/index.js +256 -18
- package/output/auth/index.d.ts +72 -0
- package/output/auth/index.d.ts.map +1 -0
- package/output/auth/index.js +176 -0
- package/output/core/assemble.d.ts +64 -0
- package/output/core/assemble.d.ts.map +1 -0
- package/output/core/assemble.js +88 -0
- package/output/core/boot/context.d.ts +89 -0
- package/output/core/boot/context.d.ts.map +1 -0
- package/output/core/boot/context.js +106 -0
- package/output/core/boot/lifecycle.d.ts +94 -0
- package/output/core/boot/lifecycle.d.ts.map +1 -0
- package/output/{app → core/boot}/lifecycle.js +45 -14
- package/output/core/boot/shutdown.d.ts +30 -0
- package/output/core/boot/shutdown.d.ts.map +1 -0
- package/output/core/boot/shutdown.js +45 -0
- package/output/core/boot/signal.d.ts +13 -0
- package/output/core/boot/signal.d.ts.map +1 -0
- package/output/{app → core/boot}/signal.js +3 -2
- package/output/core/boot/steps.d.ts +107 -0
- package/output/core/boot/steps.d.ts.map +1 -0
- package/output/core/boot/steps.js +200 -0
- package/output/core/entries.d.ts +123 -0
- package/output/core/entries.d.ts.map +1 -0
- package/output/core/entries.js +193 -0
- package/output/core/entries.test.d.ts +2 -0
- package/output/core/entries.test.d.ts.map +1 -0
- package/output/core/entries.test.js +217 -0
- package/output/core/env.d.ts +62 -0
- package/output/core/env.d.ts.map +1 -0
- package/output/core/env.js +96 -0
- package/output/{app → core}/env.test.d.ts.map +1 -1
- package/output/core/env.test.js +105 -0
- package/output/core/modules/auth-provider/index.d.ts +10 -0
- package/output/core/modules/auth-provider/index.d.ts.map +1 -0
- package/output/core/modules/auth-provider/index.js +9 -0
- package/output/core/modules/auth-provider/types.d.ts +71 -0
- package/output/core/modules/auth-provider/types.d.ts.map +1 -0
- package/output/core/modules/context/index.d.ts +18 -0
- package/output/core/modules/context/index.d.ts.map +1 -0
- package/output/core/modules/context/index.js +23 -0
- package/output/core/modules/context/index.test.d.ts.map +1 -0
- package/output/core/modules/cookie/index.d.ts +21 -0
- package/output/core/modules/cookie/index.d.ts.map +1 -0
- package/output/{app/core/cookie.js → core/modules/cookie/index.js} +1 -6
- package/output/core/modules/identity/index.d.ts +4 -0
- package/output/core/modules/identity/index.d.ts.map +1 -0
- package/output/{app/core/identity.js → core/modules/identity/index.js} +1 -1
- package/output/core/modules/logging/index.d.ts +5 -0
- package/output/core/modules/logging/index.d.ts.map +1 -0
- package/output/{app/core/logging.js → core/modules/logging/index.js} +2 -2
- package/output/core/modules/openapi/index.d.ts +7 -0
- package/output/core/modules/openapi/index.d.ts.map +1 -0
- package/output/{app/core/openapi.js → core/modules/openapi/index.js} +11 -12
- package/output/core/modules/permission-operations/index.d.ts +26 -0
- package/output/core/modules/permission-operations/index.d.ts.map +1 -0
- package/output/core/modules/permission-operations/index.js +36 -0
- package/output/core/modules/permission-operations/index.test.d.ts +2 -0
- package/output/core/modules/permission-operations/index.test.d.ts.map +1 -0
- package/output/core/modules/permission-operations/index.test.js +45 -0
- package/output/{app/core/request-id.d.ts → core/modules/request-id/index.d.ts} +1 -4
- package/output/core/modules/request-id/index.d.ts.map +1 -0
- package/output/{app/core/request-id.js → core/modules/request-id/index.js} +2 -2
- package/output/core/modules/response/index.d.ts +22 -0
- package/output/core/modules/response/index.d.ts.map +1 -0
- package/output/{app/core/response.js → core/modules/response/index.js} +22 -11
- package/output/{schema/index.d.ts → core/modules/schema/builders.d.ts} +8 -1
- package/output/core/modules/schema/builders.d.ts.map +1 -0
- package/output/{schema/index.js → core/modules/schema/builders.js} +7 -4
- package/output/core/modules/schema/index.d.ts +34 -0
- package/output/core/modules/schema/index.d.ts.map +1 -0
- package/output/core/modules/schema/index.js +62 -0
- package/output/core/modules/schema/index.test.d.ts +2 -0
- package/output/core/modules/schema/index.test.d.ts.map +1 -0
- package/output/core/modules/schema/index.test.js +55 -0
- package/output/core/modules/system-routes/index.d.ts +15 -0
- package/output/core/modules/system-routes/index.d.ts.map +1 -0
- package/output/core/modules/system-routes/index.js +171 -0
- package/output/core/test-support/database.d.ts +96 -0
- package/output/core/test-support/database.d.ts.map +1 -0
- package/output/core/test-support/database.js +144 -0
- package/output/core/test-support/database.test.d.ts +2 -0
- package/output/core/test-support/database.test.d.ts.map +1 -0
- package/output/core/test-support/database.test.js +110 -0
- package/output/core/test-support/environment.d.ts +33 -0
- package/output/core/test-support/environment.d.ts.map +1 -0
- package/output/core/test-support/environment.js +42 -0
- package/output/core/test-support/test-server.d.ts +94 -0
- package/output/core/test-support/test-server.d.ts.map +1 -0
- package/output/core/test-support/test-server.js +60 -0
- package/output/core/test-support/test-server.test.d.ts +2 -0
- package/output/core/test-support/test-server.test.d.ts.map +1 -0
- package/output/core/test-support/test-server.test.js +162 -0
- package/output/{app → core}/type-augment.d.ts +2 -2
- package/output/core/type-augment.d.ts.map +1 -0
- package/output/core/types.d.ts +338 -0
- package/output/core/types.d.ts.map +1 -0
- package/output/cors/index.d.ts +3 -0
- package/output/cors/index.d.ts.map +1 -0
- package/output/cors/index.js +27 -0
- package/output/index.d.ts +8 -4
- package/output/index.d.ts.map +1 -1
- package/output/index.js +24 -5
- package/output/rate-limit/index.d.ts +23 -0
- package/output/rate-limit/index.d.ts.map +1 -0
- package/output/rate-limit/index.js +42 -0
- package/output/{rate-limiter/rate-limiter.d.ts → rate-limit/limiter.d.ts} +1 -1
- package/output/rate-limit/limiter.d.ts.map +1 -0
- package/output/rate-limit/memory-store.d.ts.map +1 -0
- package/output/rate-limit/redis-store.d.ts.map +1 -0
- package/output/rate-limit/redis-store.test.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/redis-store.test.js +2 -2
- package/output/{rate-limiter → rate-limit}/types.d.ts +0 -1
- package/output/rate-limit/types.d.ts.map +1 -0
- package/output/rate-limit/types.js +1 -0
- package/output/{redis-cache/redis-cache.d.ts → redis/cache.d.ts} +1 -1
- package/output/redis/cache.d.ts.map +1 -0
- package/output/redis/cache.test.d.ts +2 -0
- package/output/redis/cache.test.d.ts.map +1 -0
- package/output/{redis-cache/redis-cache.test.js → redis/cache.test.js} +1 -1
- package/output/{app/redis.d.ts → redis/index.d.ts} +12 -7
- package/output/redis/index.d.ts.map +1 -0
- package/output/{app/redis.js → redis/index.js} +15 -5
- package/output/redis/index.test.d.ts +2 -0
- package/output/{context → redis}/index.test.d.ts.map +1 -1
- package/output/redis/index.test.js +55 -0
- package/output/redis-queue/index.js +1 -1
- package/output/redis-queue/test-helpers.d.ts +1 -1
- package/output/redis-queue/test-helpers.js +1 -1
- package/output/redis-queue/types.d.ts +3 -12
- package/output/redis-queue/types.d.ts.map +1 -1
- package/package.json +16 -15
- package/LICENSE +0 -21
- package/output/app/auth.test.d.ts +0 -2
- package/output/app/auth.test.d.ts.map +0 -1
- package/output/app/auth.test.js +0 -148
- package/output/app/boot.d.ts +0 -39
- package/output/app/boot.d.ts.map +0 -1
- package/output/app/boot.js +0 -140
- package/output/app/config.test.d.ts +0 -2
- package/output/app/config.test.d.ts.map +0 -1
- package/output/app/config.test.js +0 -132
- package/output/app/context.d.ts +0 -70
- package/output/app/context.d.ts.map +0 -1
- package/output/app/context.js +0 -73
- package/output/app/core/cookie.d.ts +0 -25
- package/output/app/core/cookie.d.ts.map +0 -1
- package/output/app/core/identity.d.ts +0 -12
- package/output/app/core/identity.d.ts.map +0 -1
- package/output/app/core/index.d.ts +0 -44
- package/output/app/core/index.d.ts.map +0 -1
- package/output/app/core/index.js +0 -57
- package/output/app/core/logging.d.ts +0 -13
- package/output/app/core/logging.d.ts.map +0 -1
- package/output/app/core/openapi.d.ts +0 -17
- package/output/app/core/openapi.d.ts.map +0 -1
- package/output/app/core/request-id.d.ts.map +0 -1
- package/output/app/core/response.d.ts +0 -41
- package/output/app/core/response.d.ts.map +0 -1
- package/output/app/define-server.negative-cases.d.ts +0 -40
- package/output/app/define-server.negative-cases.d.ts.map +0 -1
- package/output/app/define-server.negative-cases.js +0 -268
- package/output/app/define-server.test.d.ts +0 -2
- package/output/app/define-server.test.d.ts.map +0 -1
- package/output/app/define-server.test.js +0 -97
- package/output/app/entries.d.ts +0 -29
- package/output/app/entries.d.ts.map +0 -1
- package/output/app/entries.js +0 -64
- package/output/app/env.d.ts +0 -59
- package/output/app/env.d.ts.map +0 -1
- package/output/app/env.js +0 -66
- package/output/app/env.test.js +0 -124
- package/output/app/error-contract.test.d.ts +0 -2
- package/output/app/error-contract.test.d.ts.map +0 -1
- package/output/app/error-contract.test.js +0 -227
- package/output/app/errors.d.ts +0 -20
- package/output/app/errors.d.ts.map +0 -1
- package/output/app/errors.js +0 -51
- package/output/app/generate-openapi.test.d.ts +0 -2
- package/output/app/generate-openapi.test.d.ts.map +0 -1
- package/output/app/generate-openapi.test.js +0 -123
- package/output/app/lifecycle-logging.test.d.ts +0 -2
- package/output/app/lifecycle-logging.test.d.ts.map +0 -1
- package/output/app/lifecycle-logging.test.js +0 -155
- package/output/app/lifecycle.d.ts +0 -70
- package/output/app/lifecycle.d.ts.map +0 -1
- package/output/app/lifecycle.test.d.ts +0 -2
- package/output/app/lifecycle.test.d.ts.map +0 -1
- package/output/app/lifecycle.test.js +0 -152
- package/output/app/middleware/auth.d.ts +0 -71
- package/output/app/middleware/auth.d.ts.map +0 -1
- package/output/app/middleware/auth.js +0 -190
- package/output/app/middleware/context.d.ts +0 -13
- package/output/app/middleware/context.d.ts.map +0 -1
- package/output/app/middleware/context.js +0 -15
- package/output/app/middleware/cors.d.ts +0 -14
- package/output/app/middleware/cors.d.ts.map +0 -1
- package/output/app/middleware/cors.js +0 -30
- package/output/app/middleware/index.d.ts +0 -17
- package/output/app/middleware/index.d.ts.map +0 -1
- package/output/app/middleware/index.js +0 -16
- package/output/app/middleware/rate-limit.d.ts +0 -24
- package/output/app/middleware/rate-limit.d.ts.map +0 -1
- package/output/app/middleware/rate-limit.js +0 -46
- package/output/app/middleware-order.test.d.ts +0 -2
- package/output/app/middleware-order.test.d.ts.map +0 -1
- package/output/app/middleware-order.test.js +0 -148
- package/output/app/permission-operations.d.ts +0 -48
- package/output/app/permission-operations.d.ts.map +0 -1
- package/output/app/permission-operations.js +0 -77
- package/output/app/permission-operations.test.d.ts +0 -2
- package/output/app/permission-operations.test.d.ts.map +0 -1
- package/output/app/permission-operations.test.js +0 -72
- package/output/app/permission-registration.test.d.ts +0 -2
- package/output/app/permission-registration.test.d.ts.map +0 -1
- package/output/app/permission-registration.test.js +0 -138
- package/output/app/redis.d.ts.map +0 -1
- package/output/app/registered-operations.test.d.ts +0 -2
- package/output/app/registered-operations.test.d.ts.map +0 -1
- package/output/app/registered-operations.test.js +0 -57
- package/output/app/request-id.test.d.ts +0 -2
- package/output/app/request-id.test.d.ts.map +0 -1
- package/output/app/request-id.test.js +0 -130
- package/output/app/resources.test.d.ts +0 -2
- package/output/app/resources.test.d.ts.map +0 -1
- package/output/app/resources.test.js +0 -199
- package/output/app/shutdown.d.ts +0 -22
- package/output/app/shutdown.d.ts.map +0 -1
- package/output/app/shutdown.js +0 -30
- package/output/app/shutdown.test.d.ts +0 -2
- package/output/app/shutdown.test.d.ts.map +0 -1
- package/output/app/shutdown.test.js +0 -103
- package/output/app/signal.d.ts +0 -12
- package/output/app/signal.d.ts.map +0 -1
- package/output/app/smoke.test.d.ts +0 -2
- package/output/app/smoke.test.d.ts.map +0 -1
- package/output/app/smoke.test.js +0 -210
- package/output/app/test-support.d.ts +0 -21
- package/output/app/test-support.d.ts.map +0 -1
- package/output/app/test-support.js +0 -24
- package/output/app/type-augment.d.ts.map +0 -1
- package/output/app/types.d.ts +0 -228
- package/output/app/types.d.ts.map +0 -1
- package/output/context/index.d.ts +0 -6
- package/output/context/index.d.ts.map +0 -1
- package/output/context/index.js +0 -14
- package/output/rate-limiter/index.d.ts +0 -5
- package/output/rate-limiter/index.d.ts.map +0 -1
- package/output/rate-limiter/index.js +0 -4
- package/output/rate-limiter/memory-store.d.ts.map +0 -1
- package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
- package/output/rate-limiter/redis-store.d.ts.map +0 -1
- package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
- package/output/rate-limiter/types.d.ts.map +0 -1
- package/output/redis-cache/index.d.ts +0 -4
- package/output/redis-cache/index.d.ts.map +0 -1
- package/output/redis-cache/index.js +0 -1
- package/output/redis-cache/redis-cache.d.ts.map +0 -1
- package/output/redis-cache/redis-cache.test.d.ts +0 -2
- package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
- package/output/schema/index.d.ts.map +0 -1
- /package/output/{app → core}/env.test.d.ts +0 -0
- /package/output/{rate-limiter → core/modules/auth-provider}/types.js +0 -0
- /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
- /package/output/{context → core/modules/context}/index.test.js +0 -0
- /package/output/{app → core}/type-augment.js +0 -0
- /package/output/{app → core}/types.js +0 -0
- /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
- /package/output/{rate-limiter → rate-limit}/memory-store.d.ts +0 -0
- /package/output/{rate-limiter → rate-limit}/memory-store.js +0 -0
- /package/output/{rate-limiter → rate-limit}/redis-store.d.ts +0 -0
- /package/output/{rate-limiter → rate-limit}/redis-store.js +0 -0
- /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
- /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ConfigEnv } from '../env.js';
|
|
2
|
+
import type { ClientsView, LoggerLike, ResourcesView, Server } from '../types.js';
|
|
3
|
+
import type { FastifyInstance } from 'fastify';
|
|
4
|
+
/**
|
|
5
|
+
* `createTestServer()` ——**测试对象的唯一构造方式**。
|
|
6
|
+
*
|
|
7
|
+
* ## 它是唯一的一个,不是「又一个」入口
|
|
8
|
+
*
|
|
9
|
+
* 这里有一个硬性约束:**测试必须跑在生产的同一套装配上**。请求链、响应信封、错误码映射、
|
|
10
|
+
* `request.ctx`、关闭流程,只要测试里另有一套实现,它就会在生产之外漂移,而漂移只在线暴露。
|
|
11
|
+
*
|
|
12
|
+
* 因此测试对象的构造只有一个入口,它**不接受任何槽位覆盖**:没有 `resources` 参数、没有
|
|
13
|
+
* `services` 参数、没有 `overrideXxx`。覆盖面一开,测试就能绕开生产接线,这个入口随即失去
|
|
14
|
+
* 全部证明力——那还不如直接写个假 app。
|
|
15
|
+
*
|
|
16
|
+
* 需要「只测一个模块」时,**组合定义**,而不是覆盖槽位:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const moduleServer = defineServer({
|
|
20
|
+
* ...server,
|
|
21
|
+
* routers: ({ app }) => app.register(createUserRouter(stub)),
|
|
22
|
+
* })
|
|
23
|
+
* const testServer = await createTestServer(moduleServer)
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* 组合用的是同一个 `defineServer` 产物、同一套装配,只是把另一个模块的接线换掉。
|
|
27
|
+
*
|
|
28
|
+
* ## 与 `startServer` 的唯一差别
|
|
29
|
+
*
|
|
30
|
+
* `startServer` 会 listen 并装信号处理器;测试用 `app.inject()`,且 vitest worker 收不到
|
|
31
|
+
* SIGTERM。除此之外(env 分层 → resources → config → clients → services → `init()` →
|
|
32
|
+
* middlewares → 系统路由 → routers、七步关闭)**逐行相同**。
|
|
33
|
+
*
|
|
34
|
+
* ## 参数就是那个 `Server`,没有第二个参数
|
|
35
|
+
*
|
|
36
|
+
* 连接串结算、闸门、改写 `DATABASE_URL` 全部固定(见 `prepareTestDatabase()`)。
|
|
37
|
+
* 没有「用哪个连接串」这类开关:测试对象的身份就是测试,让它可以被配置成「有点像测试」
|
|
38
|
+
* 是把安全前提变成调用方的责任。
|
|
39
|
+
*
|
|
40
|
+
* ## 返回值:`TestServer` 就是测试要的一切
|
|
41
|
+
*
|
|
42
|
+
* 测试要断言的对象往往不是 HTTP 响应,而是**装配出来的东西本身**:`resources.database` 连的
|
|
43
|
+
* 是不是 `*_test`、某个 service 有没有被 `dispose`、`config` 里那个开关有没有生效。
|
|
44
|
+
* 因此返回装配期**已经构造完成**的各槽位产物,而不是让调用方重新跑一遍工厂——重新构造就等于
|
|
45
|
+
* 测试里又拼了一份,与生产接线脱钩。
|
|
46
|
+
*
|
|
47
|
+
* **不额外包方法**:没有 `close()`、没有 `getDatabase()`、没有 `reset()`。`app` 就是原生
|
|
48
|
+
* `FastifyInstance`,关闭就是 `await app.close()`,走同一套七步流程;连接串就是
|
|
49
|
+
* `env.DATABASE_URL`。多包一层 `close()` 只是在已有接口上再加一个同义名,反而让「用哪个」变成
|
|
50
|
+
* 需要记的事。
|
|
51
|
+
*/
|
|
52
|
+
export interface TestServer<R, C, CL, S> {
|
|
53
|
+
/** 原生 Fastify 实例(已 `ready()`,用 `inject` 发请求;关闭用 `app.close()`) */
|
|
54
|
+
readonly app: FastifyInstance;
|
|
55
|
+
/**
|
|
56
|
+
* 装配后的固定约定 env。
|
|
57
|
+
*
|
|
58
|
+
* **测试断言「连的是哪个库」就看这里**:`env.DATABASE_URL` 在装配前已被改写成
|
|
59
|
+
* `*_test` 目标,它就是本次实际连的连接串。不额外给一个 `databaseUrl` 字段——
|
|
60
|
+
* 同一份事实出现两次,迟早会有人读到另一个。
|
|
61
|
+
*/
|
|
62
|
+
readonly env: ConfigEnv;
|
|
63
|
+
/** `resources` 槽位产物(`false` 键已按 `Live<T>` 剔除) */
|
|
64
|
+
readonly resources: ResourcesView<R>;
|
|
65
|
+
/** `config` 槽位产物(`Awaited<>` 归一) */
|
|
66
|
+
readonly config: Awaited<C>;
|
|
67
|
+
/** `clients` 槽位产物 */
|
|
68
|
+
readonly clients: ClientsView<CL>;
|
|
69
|
+
/** `services` 槽位产物(已 `init()` 过;测试直接拿它调方法,不 mock) */
|
|
70
|
+
readonly services: Awaited<S>;
|
|
71
|
+
/** 日志器(与 `app.log` 同一个;关闭阶段仍可用) */
|
|
72
|
+
readonly log: LoggerLike;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 构造测试对象。
|
|
76
|
+
*
|
|
77
|
+
* 固定顺序(安全闸门全部在装配之前,`resources` 一行代码都还没跑):
|
|
78
|
+
* ```
|
|
79
|
+
* prepareTestDatabase():
|
|
80
|
+
* 加载 env(强制叠加 .env.test / .env.test.local,ENV 未给时兜底 test)
|
|
81
|
+
* → 环境闸门(ENV 必须可清理)
|
|
82
|
+
* → 结算测试连接串(显式 TEST_DATABASE_URL 优先,否则派生 +_test,幂等)
|
|
83
|
+
* → schema 闸门(必须以 _test 结尾)
|
|
84
|
+
* → 回写 process.env.DATABASE_URL
|
|
85
|
+
* → resources → core → config → clients → services → init() → middlewares
|
|
86
|
+
* → 系统路由 → routers → 关闭钩子 → ready()
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* 不会再拿应用目标跟测试目标比对:本进程读到的 `DATABASE_URL` 此时已经是测试目标,比对恒等于
|
|
90
|
+
* 「自比自」。真正拦住「测试清空应用库」的是派生 + schema 闸门——派生只会给 schema 追加
|
|
91
|
+
* `_test`,schema 闸门只放行 `*_test`,所以测试目标结构上不可能是应用目标。
|
|
92
|
+
*/
|
|
93
|
+
export declare function createTestServer<R, C, CL, S>(server: Server<R, C, CL, S>): Promise<TestServer<R, C, CL, S>>;
|
|
94
|
+
//# sourceMappingURL=test-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-server.d.ts","sourceRoot":"","sources":["../../../source/core/test-support/test-server.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACrC,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAA;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;IACvB,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IACpC,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3B,qBAAqB;IACrB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC,CAAA;IACjC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CACzB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CA8CjH"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { initServiceUnits } from '../boot/lifecycle.js';
|
|
2
|
+
import { buildClients, buildConfig, buildResources, buildServices, createBootContext, installShutdownHook, registerCoreStep, registerMiddlewares, registerRouters, registerSystemRoutesStep, } from '../boot/steps.js';
|
|
3
|
+
import { maskDatabaseUrl } from './database.js';
|
|
4
|
+
import { prepareTestDatabase } from './environment.js';
|
|
5
|
+
/**
|
|
6
|
+
* 构造测试对象。
|
|
7
|
+
*
|
|
8
|
+
* 固定顺序(安全闸门全部在装配之前,`resources` 一行代码都还没跑):
|
|
9
|
+
* ```
|
|
10
|
+
* prepareTestDatabase():
|
|
11
|
+
* 加载 env(强制叠加 .env.test / .env.test.local,ENV 未给时兜底 test)
|
|
12
|
+
* → 环境闸门(ENV 必须可清理)
|
|
13
|
+
* → 结算测试连接串(显式 TEST_DATABASE_URL 优先,否则派生 +_test,幂等)
|
|
14
|
+
* → schema 闸门(必须以 _test 结尾)
|
|
15
|
+
* → 回写 process.env.DATABASE_URL
|
|
16
|
+
* → resources → core → config → clients → services → init() → middlewares
|
|
17
|
+
* → 系统路由 → routers → 关闭钩子 → ready()
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* 不会再拿应用目标跟测试目标比对:本进程读到的 `DATABASE_URL` 此时已经是测试目标,比对恒等于
|
|
21
|
+
* 「自比自」。真正拦住「测试清空应用库」的是派生 + schema 闸门——派生只会给 schema 追加
|
|
22
|
+
* `_test`,schema 闸门只放行 `*_test`,所以测试目标结构上不可能是应用目标。
|
|
23
|
+
*/
|
|
24
|
+
export async function createTestServer(server) {
|
|
25
|
+
// 闸门 + 结算 + 改写全在框架内部,顺序不由调用方决定
|
|
26
|
+
const env = prepareTestDatabase();
|
|
27
|
+
// 以下步序与 `startServer` 逐行对应,只去掉 listen 与信号处理器
|
|
28
|
+
const ctx = createBootContext(server, env);
|
|
29
|
+
const { resources, classifyResourceError } = await buildResources(ctx);
|
|
30
|
+
await registerCoreStep(ctx, classifyResourceError);
|
|
31
|
+
await buildConfig(ctx, resources);
|
|
32
|
+
const clients = await buildClients(ctx, resources);
|
|
33
|
+
const services = await buildServices(ctx, resources, clients);
|
|
34
|
+
await initServiceUnits({
|
|
35
|
+
service: ctx.service,
|
|
36
|
+
log: ctx.log,
|
|
37
|
+
services: services,
|
|
38
|
+
});
|
|
39
|
+
await registerMiddlewares(ctx, services, clients);
|
|
40
|
+
registerSystemRoutesStep(ctx, services);
|
|
41
|
+
await registerRouters(ctx, services, clients);
|
|
42
|
+
installShutdownHook(ctx, services);
|
|
43
|
+
await ctx.app.ready();
|
|
44
|
+
ctx.log.info({ service: ctx.service, database: maskDatabaseUrl(env.DATABASE_URL), env: env.ENV }, '[test-server] ready(测试实例不会监听端口)');
|
|
45
|
+
// `buildConfig` 一定跑过(本路径不跳过 config),缺了就按「没有 config」的语义
|
|
46
|
+
// 如实报错(`read()` 抛 `CONFIG_UNAVAILABLE`),而不是拿 `undefined` 冒充产物。
|
|
47
|
+
if (!ctx.configAccess.available) {
|
|
48
|
+
throw new Error('[test-server] config 槽位没有产出:装配顺序被改坏了(应当紧跟 resources 之后)');
|
|
49
|
+
}
|
|
50
|
+
const app = ctx.app;
|
|
51
|
+
return {
|
|
52
|
+
app,
|
|
53
|
+
env,
|
|
54
|
+
resources,
|
|
55
|
+
config: ctx.configAccess.read(),
|
|
56
|
+
clients,
|
|
57
|
+
services,
|
|
58
|
+
log: ctx.log
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-server.test.d.ts","sourceRoot":"","sources":["../../../source/core/test-support/test-server.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { defineServer } from '../entries.js';
|
|
3
|
+
import { routerSchema } from '../modules/schema/builders.js';
|
|
4
|
+
import { createTestServer } from './test-server.js';
|
|
5
|
+
const IDENTITY = { service: 'probe', title: 'Probe', version: '0.0.0' };
|
|
6
|
+
const ROUTE_SCHEMA = routerSchema({
|
|
7
|
+
operationId: 'probeGet',
|
|
8
|
+
summary: '探测',
|
|
9
|
+
description: '探测路由',
|
|
10
|
+
tags: ['探测'],
|
|
11
|
+
});
|
|
12
|
+
const APP_URL = 'postgresql://postgres:secret@db.internal:5432/taicode-labs?schema=probe';
|
|
13
|
+
const TEST_URL = 'postgresql://postgres:secret@db.internal:5432/taicode-labs?schema=probe_test';
|
|
14
|
+
const ORIGINAL_ENV = { ...process.env };
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
process.env = { ...ORIGINAL_ENV };
|
|
17
|
+
});
|
|
18
|
+
function buildServer(seen = {}) {
|
|
19
|
+
return defineServer({
|
|
20
|
+
app: IDENTITY,
|
|
21
|
+
resources: (ctx) => {
|
|
22
|
+
seen.databaseUrl = ctx.env.DATABASE_URL;
|
|
23
|
+
seen.env = ctx.env.ENV;
|
|
24
|
+
return {};
|
|
25
|
+
},
|
|
26
|
+
config: () => ({ server: { cookieNames: ['token'] } }),
|
|
27
|
+
clients: () => ({}),
|
|
28
|
+
services: () => ({}),
|
|
29
|
+
middlewares: () => ({}),
|
|
30
|
+
routers: ({ app }) => {
|
|
31
|
+
app.get('/probe', { schema: ROUTE_SCHEMA }, async () => ({ ok: true }));
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** 把 vitest 自己设的环境变量清干净,让每个用例只看到自己注入的值 */
|
|
36
|
+
function useEnv(overrides) {
|
|
37
|
+
process.env.ENV = 'development';
|
|
38
|
+
// 注意:给 process.env 赋 undefined 会变成字符串 "undefined",所以必须先 delete
|
|
39
|
+
delete process.env.DATABASE_URL;
|
|
40
|
+
delete process.env.TEST_DATABASE_URL;
|
|
41
|
+
process.env.LOG_LEVEL = 'silent';
|
|
42
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
delete process.env[key];
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
process.env[key] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
describe('createTestServer', () => {
|
|
52
|
+
it('与 startServer 装配同一套处理链:系统路由 + 业务路由 + zod 序列化 + 成功信封', async () => {
|
|
53
|
+
useEnv({ DATABASE_URL: APP_URL });
|
|
54
|
+
const testServer = await createTestServer(buildServer());
|
|
55
|
+
const response = await testServer.app.inject({ method: 'GET', url: '/probe' });
|
|
56
|
+
expect(response.statusCode).toBe(200);
|
|
57
|
+
// 信封形状来自框架 core/response,而不是测试自己拼的一份
|
|
58
|
+
expect(response.json()).toEqual({ status: 'SUCCESS', message: 'ok', data: { ok: true } });
|
|
59
|
+
// 框架系统路由同样就位
|
|
60
|
+
expect((await testServer.app.inject({ method: 'GET', url: '/health' })).statusCode).toBe(200);
|
|
61
|
+
await testServer.app.close();
|
|
62
|
+
});
|
|
63
|
+
it('数据层被强制指向 *_test(resources 拿到的 DATABASE_URL 已改写)', async () => {
|
|
64
|
+
useEnv({ DATABASE_URL: APP_URL });
|
|
65
|
+
const seen = {};
|
|
66
|
+
const testServer = await createTestServer(buildServer(seen));
|
|
67
|
+
expect(seen.databaseUrl).toContain('schema=probe_test');
|
|
68
|
+
expect(process.env.DATABASE_URL).toContain('schema=probe_test');
|
|
69
|
+
// 交出来的连接串就是「测试实际连的那个」,不是从 process.env 再推一遍
|
|
70
|
+
expect(testServer.env.DATABASE_URL).toBe(seen.databaseUrl);
|
|
71
|
+
await testServer.app.close();
|
|
72
|
+
});
|
|
73
|
+
it('显式 TEST_DATABASE_URL 优先于派生', async () => {
|
|
74
|
+
useEnv({
|
|
75
|
+
DATABASE_URL: APP_URL,
|
|
76
|
+
TEST_DATABASE_URL: TEST_URL.replace('probe_test', 'explicit_test'),
|
|
77
|
+
});
|
|
78
|
+
const seen = {};
|
|
79
|
+
const testServer = await createTestServer(buildServer(seen));
|
|
80
|
+
expect(seen.databaseUrl).toContain('schema=explicit_test');
|
|
81
|
+
await testServer.app.close();
|
|
82
|
+
});
|
|
83
|
+
it('不监听端口(测试用 inject,占端口会让重跑互相踩)', async () => {
|
|
84
|
+
useEnv({ DATABASE_URL: APP_URL });
|
|
85
|
+
const testServer = await createTestServer(buildServer());
|
|
86
|
+
expect(testServer.app.server.listening).toBe(false);
|
|
87
|
+
await testServer.app.close();
|
|
88
|
+
});
|
|
89
|
+
it('env 强制加载测试层:ENV 没人给时兜底成 test(不用调用方额外声明)', async () => {
|
|
90
|
+
useEnv({ DATABASE_URL: APP_URL, ENV: undefined });
|
|
91
|
+
const seen = {};
|
|
92
|
+
const testServer = await createTestServer(buildServer(seen));
|
|
93
|
+
expect(seen.env).toBe('test');
|
|
94
|
+
await testServer.app.close();
|
|
95
|
+
});
|
|
96
|
+
it('环境闸门:非可清理环境在装配之前就失败(resources 一次都没跑)', async () => {
|
|
97
|
+
useEnv({ DATABASE_URL: APP_URL, ENV: 'production' });
|
|
98
|
+
const onResources = vi.fn(buildServer().resources);
|
|
99
|
+
const server = defineServer({ ...buildServer(), resources: onResources });
|
|
100
|
+
await expect(createTestServer(server)).rejects.toThrow(/ENV="production"/);
|
|
101
|
+
expect(onResources).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
it('schema 闸门:目标不以 *_test 结尾时拒绝装配', async () => {
|
|
104
|
+
useEnv({ DATABASE_URL: APP_URL, TEST_DATABASE_URL: APP_URL });
|
|
105
|
+
const onResources = vi.fn(buildServer().resources);
|
|
106
|
+
const server = defineServer({ ...buildServer(), resources: onResources });
|
|
107
|
+
await expect(createTestServer(server)).rejects.toThrow(/必须以 _test 结尾/);
|
|
108
|
+
expect(onResources).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
it('关闭时走同一套七步流程(services 的 dispose 被调用)', async () => {
|
|
111
|
+
useEnv({ DATABASE_URL: APP_URL });
|
|
112
|
+
const onDispose = vi.fn();
|
|
113
|
+
const server = defineServer({
|
|
114
|
+
...buildServer(),
|
|
115
|
+
services: () => ({ unit: { dispose: onDispose } }),
|
|
116
|
+
});
|
|
117
|
+
const testServer = await createTestServer(server);
|
|
118
|
+
await testServer.app.close();
|
|
119
|
+
expect(onDispose).toHaveBeenCalledTimes(1);
|
|
120
|
+
});
|
|
121
|
+
it('业务路由缺少 schema 时注册即失败(守卫在测试入口同样生效)', async () => {
|
|
122
|
+
useEnv({ DATABASE_URL: APP_URL });
|
|
123
|
+
const server = defineServer({
|
|
124
|
+
...buildServer(),
|
|
125
|
+
routers: ({ app }) => {
|
|
126
|
+
app.get('/bad', async () => ({ ok: true }));
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
await expect(createTestServer(server)).rejects.toThrow(/GET \/bad 缺少 schema/);
|
|
130
|
+
});
|
|
131
|
+
it('返回装配产物本身:resources / config / clients / services 与槽位一一对应', async () => {
|
|
132
|
+
useEnv({ DATABASE_URL: APP_URL });
|
|
133
|
+
const resources = { cache: { get: () => undefined } };
|
|
134
|
+
const config = { server: { cookieNames: ['token'] } };
|
|
135
|
+
const clients = { account: { verify: async () => true } };
|
|
136
|
+
const services = { user: { name: () => 'user' } };
|
|
137
|
+
const testServer = await createTestServer(defineServer({
|
|
138
|
+
app: IDENTITY,
|
|
139
|
+
resources: () => resources,
|
|
140
|
+
config: () => config,
|
|
141
|
+
clients: () => clients,
|
|
142
|
+
services: () => services,
|
|
143
|
+
middlewares: () => ({}),
|
|
144
|
+
routers: () => { },
|
|
145
|
+
}));
|
|
146
|
+
// 装配产物直接交出来:测试能断言/操作它们本身,而不是再构造一份副本。
|
|
147
|
+
// `resources` / `clients` 经过 `Live<T>` 视图(剔 `false` 键),所以按值比较;
|
|
148
|
+
// `config` / `services` 就是槽位原始返回值,引用相同。
|
|
149
|
+
expect(testServer.resources).toEqual(resources);
|
|
150
|
+
expect(testServer.clients).toEqual(clients);
|
|
151
|
+
expect(testServer.config).toBe(config);
|
|
152
|
+
expect(testServer.services).toBe(services);
|
|
153
|
+
// env 是**改写后**的快照(resources 看到的就是它)
|
|
154
|
+
expect(testServer.env.DATABASE_URL).toContain('schema=probe_test');
|
|
155
|
+
await testServer.app.close();
|
|
156
|
+
});
|
|
157
|
+
it('只有定义一个参数:没有「半个生产」的配置入口', () => {
|
|
158
|
+
// 签名只有一个必填参数。这条断言盯着「别再加 options」——一旦加了,测试就能绕开
|
|
159
|
+
// 生产接线,本入口随即失去证明力。
|
|
160
|
+
expect(createTestServer.length).toBe(1);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `request.user` / `request.token` / `request.id` 与 `routeOptions.config.accessControls`(以及
|
|
5
5
|
* 限流规则 `rateLimit`)的 `declare module 'fastify'` 由 common 统一提供,各仓删除自建声明。
|
|
6
6
|
*/
|
|
7
|
-
import type { RequestContext } from '
|
|
7
|
+
import type { RequestContext } from './modules/context/index.js';
|
|
8
8
|
/** 路由级访问控制声明(各服务在 `routeOptions.config.accessControls` 里写) */
|
|
9
9
|
export type AccessControl = 'NoLoginRequired' | 'IgnorePermission' | 'RequiredSecurityCode';
|
|
10
10
|
/** 路由级限流规则(`createRateLimitMiddleware` 消费,另有路由级 `config.rateLimit`) */
|
|
@@ -21,7 +21,7 @@ declare module 'fastify' {
|
|
|
21
21
|
user?: unknown;
|
|
22
22
|
/** 认证后由 `auth` 中间件填充 */
|
|
23
23
|
token?: unknown;
|
|
24
|
-
/**
|
|
24
|
+
/** 由 core 固定注入的请求级上下文(`registerRequestContext`) */
|
|
25
25
|
ctx?: RequestContext;
|
|
26
26
|
}
|
|
27
27
|
interface FastifyContextConfig {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-augment.d.ts","sourceRoot":"","sources":["../../source/core/type-augment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAEhE,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,sBAAsB,CAAA;AAE3F,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,cAAc;IACd,MAAM,EAAE,MAAM,CAAA;IACd,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,wBAAwB;QACxB,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,wBAAwB;QACxB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,mDAAmD;QACnD,GAAG,CAAC,EAAE,cAAc,CAAA;KACrB;IAED,UAAU,oBAAoB;QAC5B,+EAA+E;QAC/E,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;QAChC,cAAc;QACd,SAAS,CAAC,EAAE,aAAa,CAAA;KAC1B;CACF"}
|