@yss123/cage-testkit 0.0.0-stage → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shunshunyue and Cage contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,19 @@
1
- # Temporary Holding Version
1
+ # @yss123/cage-testkit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 派生测试运行器、fake 仓储、测试数据库
4
+
5
+ Cage 是通过 TypeScript 声明业务、由框架统一执行权限和数据规则的中后台框架。此包是框架的一部分。
6
+
7
+ ## 使用
8
+
9
+ ```powershell
10
+ pnpm add -D @yss123/cage-testkit
11
+ ```
12
+
13
+ 首次使用建议通过脚手架创建完整项目,所需依赖由模板统一配置。需要 Node.js 24 或更新版本。
14
+
15
+ - [完整使用说明](https://github.com/shunshunyue/cage/blob/main/docs/使用说明.md)
16
+ - [框架源码与范例](https://github.com/shunshunyue/cage)
17
+ - [问题反馈](https://github.com/shunshunyue/cage/issues)
18
+
19
+ 许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
@@ -0,0 +1,34 @@
1
+ import { type CallResult, type TestEnv } from './env.js';
2
+ export type Skip = 'skip';
3
+ export declare const SKIP: Skip;
4
+ export interface Derived {
5
+ env(): TestEnv;
6
+ setup(): Promise<void>;
7
+ teardown(): Promise<void>;
8
+ capabilityDenied(cap: string): Promise<CallResult | Skip>;
9
+ invalidTransition(cap: string, state: string): Promise<CallResult | Skip>;
10
+ auditOnce(cap: string): Promise<{
11
+ rows: number;
12
+ } | Skip>;
13
+ idempotent(cap: string): Promise<{
14
+ same: boolean;
15
+ rows: number;
16
+ } | Skip>;
17
+ scopeHidden(agg: string, combinator: 'self' | 'dept'): Promise<{
18
+ get: string;
19
+ listed: boolean;
20
+ rawVisible: boolean;
21
+ baseDenied: boolean;
22
+ } | Skip>;
23
+ outOfScopeWrite(cap: string): Promise<CallResult | Skip>;
24
+ masked(agg: string): Promise<{
25
+ field: string;
26
+ plain: unknown;
27
+ seen: unknown;
28
+ }[] | Skip>;
29
+ invariantsHold(agg: string, runs?: number): Promise<{
30
+ steps: number;
31
+ violations: string[];
32
+ } | Skip>;
33
+ }
34
+ export declare function derived(domain: object): Derived;
@@ -0,0 +1,259 @@
1
+ // 派生测试运行时:cage test 生成的测试文件调用这里。它们不是测 handler,而是持续证明「笼子没有洞」:
2
+ // 如果有人把内核的能力判定改坏了,或者某个路由绕过了内核,这一批测试会同时失败。
3
+ import { randomUUID } from 'node:crypto';
4
+ import { rowToSnapshot } from '@yss123/cage-kernel/db';
5
+ import { systemCtx } from '@yss123/cage-kernel';
6
+ import { sql } from 'kysely';
7
+ import { z } from 'zod';
8
+ import { createTestEnv } from './env.js';
9
+ import { sampleValue } from './fakes.js';
10
+ export const SKIP = 'skip';
11
+ function sampleJson(s) {
12
+ if (!s || typeof s !== 'object')
13
+ return null;
14
+ if (s.const !== undefined)
15
+ return s.const;
16
+ if (s.enum)
17
+ return s.enum[0];
18
+ if (s.anyOf)
19
+ return sampleJson(s.anyOf.find((x) => x.type !== 'null') ?? s.anyOf[0]);
20
+ switch (Array.isArray(s.type) ? s.type.find((t) => t !== 'null') : s.type) {
21
+ case 'string':
22
+ if (s.format === 'uuid')
23
+ return randomUUID();
24
+ if (s.format === 'email')
25
+ return 'someone@example.com';
26
+ if (s.format === 'date-time')
27
+ return new Date().toISOString();
28
+ if (s.pattern)
29
+ return s.pattern.includes('\\d') ? '100.00' : 'a';
30
+ return 'x'.repeat(Math.max(1, s.minLength ?? 1));
31
+ case 'integer':
32
+ case 'number':
33
+ return Math.max(1, s.minimum ?? 1);
34
+ case 'boolean':
35
+ return false;
36
+ case 'array':
37
+ return Array.from({ length: Math.max(1, s.minItems ?? 1) }, () => sampleJson(s.items));
38
+ case 'object':
39
+ return Object.fromEntries(Object.entries(s.properties ?? {}).filter(([k]) => (s.required ?? []).includes(k)).map(([k, v]) => [k, sampleJson(v)]));
40
+ default:
41
+ return null;
42
+ }
43
+ }
44
+ export function derived(domain) {
45
+ let env;
46
+ const rc = (cap) => {
47
+ const c = env.kernel.registry.command(cap);
48
+ if (!c)
49
+ throw new Error(`没有命令 ${cap}`);
50
+ return c;
51
+ };
52
+ const agg = (name) => env.kernel.registry.aggregate(name);
53
+ async function refValue(target) {
54
+ if (target === 'Dept')
55
+ return env.rootDeptId;
56
+ if (target === 'User')
57
+ return (await env.user({ caps: [] })).id;
58
+ return (await env.insert(target)).id;
59
+ }
60
+ /** 构造一份合法输入:id 取种子数据;聚合同名字段取种子值或示例值;其他按 schema 造 */
61
+ async function sampleInput(c, seeded) {
62
+ const schema = c.cmd.input;
63
+ if (!(schema instanceof z.ZodObject))
64
+ return null;
65
+ let js = {};
66
+ try {
67
+ js = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' });
68
+ }
69
+ catch {
70
+ js = {};
71
+ }
72
+ const fields = c.cmd.agg.fields;
73
+ const out = {};
74
+ for (const key of Object.keys(schema.shape)) {
75
+ const required = (js.required ?? []).includes(key);
76
+ if (key === 'id' && seeded)
77
+ out.id = seeded.id;
78
+ else if (seeded && key in seeded && seeded[key] !== null && seeded[key] !== undefined)
79
+ out[key] = seeded[key];
80
+ else if (key in fields) {
81
+ const f = fields[key];
82
+ if (f.isOptional && !required)
83
+ continue;
84
+ out[key] = f.ref ? await refValue(f.ref) : sampleValue(f, key);
85
+ }
86
+ else if (required)
87
+ out[key] = sampleJson(js.properties?.[key]);
88
+ }
89
+ return schema.safeParse(out).success ? out : null;
90
+ }
91
+ /** 拥有全部能力、范围为全部的执行者;流程或外部专用命令用对应的系统身份 */
92
+ async function powerActor(c) {
93
+ if (c.cmd.invokedBy.includes('user'))
94
+ return (await env.user({ caps: 'all', scopes: { '*': 'all' } })).ctx();
95
+ const kind = c.cmd.invokedBy[0];
96
+ return systemCtx({ kind, name: 'derived-test', tenantId: env.tenantId, capabilities: [c.capability], onBehalfOf: (await env.user({ caps: [] })).id });
97
+ }
98
+ return {
99
+ env: () => env,
100
+ async setup() {
101
+ env = await createTestEnv({ domain });
102
+ },
103
+ async teardown() {
104
+ await env?.close();
105
+ },
106
+ async capabilityDenied(cap) {
107
+ const c = rc(cap);
108
+ if (!c.cmd.invokedBy.includes('user'))
109
+ return SKIP;
110
+ const seeded = c.cmd.from === 'new' ? null : await env.seedFor(cap);
111
+ const input = await sampleInput(c, seeded);
112
+ if (input === null)
113
+ return SKIP;
114
+ return env.callAs(await env.actorWithout(cap), cap, input);
115
+ },
116
+ async invalidTransition(cap, state) {
117
+ const c = rc(cap);
118
+ const seeded = await env.insert(c.cmd.agg, { status: state });
119
+ const input = await sampleInput(c, seeded);
120
+ if (input === null)
121
+ return SKIP;
122
+ return env.callAs(await powerActor(c), cap, input);
123
+ },
124
+ async auditOnce(cap) {
125
+ const c = rc(cap);
126
+ const seeded = c.cmd.from === 'new' ? null : await env.seedFor(cap);
127
+ const input = await sampleInput(c, seeded);
128
+ if (input === null)
129
+ return SKIP;
130
+ const r = await env.callAs(await powerActor(c), cap, input);
131
+ if (!r.ok)
132
+ return SKIP; // 业务规则拒绝了示例数据:没法走到成功路径
133
+ const rows = await env.audit({ aggregateId: r.data.id, command: cap, kind: 'write' });
134
+ return { rows: rows.length };
135
+ },
136
+ async idempotent(cap) {
137
+ const c = rc(cap);
138
+ if (!c.cmd.invokedBy.includes('user'))
139
+ return SKIP;
140
+ const seeded = c.cmd.from === 'new' ? null : await env.seedFor(cap);
141
+ const input = await sampleInput(c, seeded);
142
+ if (input === null)
143
+ return SKIP;
144
+ const ctx = { ...(await powerActor(c)), idempotencyKey: randomUUID() };
145
+ const [a, b] = await Promise.all([env.callAs(ctx, cap, input), env.callAs(ctx, cap, input)]);
146
+ if (!a.ok || !b.ok)
147
+ return SKIP;
148
+ const rows = await env.audit({ aggregateId: a.data.id, command: cap, kind: 'write' });
149
+ return { same: a.data.id === b.data.id && a.data.version === b.data.version, rows: rows.length };
150
+ },
151
+ async scopeHidden(aggName, combinator) {
152
+ const a = agg(aggName);
153
+ if (combinator === 'self' && !a.scope.ownerField)
154
+ return SKIP;
155
+ if (combinator === 'dept' && (!a.scope.deptField || a.scope.deptField === 'id'))
156
+ return SKIP;
157
+ const deptA = await env.dept(`derived-a-${randomUUID().slice(0, 6)}`);
158
+ const deptB = await env.dept(`derived-b-${randomUUID().slice(0, 6)}`);
159
+ const owner = await env.user({ caps: [], deptId: deptA });
160
+ const rec = await env.insert(a, { ownerId: owner.id, ...(a.scope.deptField && a.scope.deptField !== 'id' ? { [a.scope.deptField]: deptA } : {}) });
161
+ const other = await env.user({ caps: [`${aggName}.read`], scopes: { [aggName]: combinator }, deptId: deptB });
162
+ const ctx = await other.ctx();
163
+ let get = 'ok';
164
+ try {
165
+ await env.kernel.read.get(a, rec.id, ctx);
166
+ }
167
+ catch (e) {
168
+ get = e.code ?? 'ERROR';
169
+ }
170
+ const list = await env.kernel.read.list(a, { pageSize: 200 }, ctx);
171
+ const db = await env.readConnectionAs(ctx);
172
+ const raw = await sql `select id from ${sql.table(`safe.${a.table.name}`)}`.execute(db);
173
+ let baseDenied = false;
174
+ try {
175
+ await sql `select 1 from ${sql.table(`${a.table.schema}.${a.table.name}`)}`.execute(db);
176
+ }
177
+ catch (e) {
178
+ baseDenied = /permission denied/.test(String(e));
179
+ }
180
+ await db.release();
181
+ return { get, listed: list.items.some((x) => x.id === rec.id), rawVisible: raw.rows.some((r) => r.id === rec.id), baseDenied };
182
+ },
183
+ async outOfScopeWrite(cap) {
184
+ const c = rc(cap);
185
+ if (c.cmd.from === 'new' || !c.cmd.invokedBy.includes('user') || !c.cmd.agg.scope.ownerField)
186
+ return SKIP;
187
+ const owner = await env.user({ caps: [] });
188
+ const seeded = await env.seedFor(cap, { ownerId: owner.id });
189
+ const input = await sampleInput(c, seeded);
190
+ if (input === null)
191
+ return SKIP;
192
+ const other = await env.user({ caps: 'all', scopes: { [c.cmd.agg.name]: 'self' } });
193
+ return env.callAs(await other.ctx(), cap, input);
194
+ },
195
+ async masked(aggName) {
196
+ const a = agg(aggName);
197
+ const sens = Object.entries(a.fields).filter(([, f]) => f.sensitiveKind);
198
+ if (sens.length === 0)
199
+ return SKIP;
200
+ const values = Object.fromEntries(sens.map(([k, f]) => [k, sampleValue(f, k, true)]));
201
+ const rec = await env.insert(a, values);
202
+ const reader = await env.user({ caps: [`${aggName}.read`], scopes: { [aggName]: 'all' } });
203
+ const got = await env.kernel.read.get(a, rec.id, await reader.ctx());
204
+ return sens.map(([k]) => ({ field: k, plain: values[k], seen: got[k] }));
205
+ },
206
+ async invariantsHold(aggName, runs = 8) {
207
+ const a = agg(aggName);
208
+ const names = Object.keys(a.invariants);
209
+ if (names.length === 0)
210
+ return SKIP;
211
+ const cmds = env.kernel.registry.commands.filter((c) => c.cmd.agg === a);
212
+ const violations = [];
213
+ let steps = 0;
214
+ for (let run = 0; run < runs; run++) {
215
+ // 随机命令序列:每一步之后,库里的数据都必须满足所有不变量
216
+ let rec = null;
217
+ const creators = cmds.filter((c) => c.cmd.from === 'new');
218
+ if (creators.length) {
219
+ const c = creators[run % creators.length];
220
+ const input = await sampleInput(c, null);
221
+ if (input) {
222
+ const r = await env.callAs(await powerActor(c), c.capability, input);
223
+ if (r.ok)
224
+ rec = r.data;
225
+ }
226
+ }
227
+ rec ??= await env.insert(a);
228
+ for (let i = 0; i < 4; i++) {
229
+ const options = cmds.filter((c) => c.cmd.from !== 'new' && c.cmd.from.includes(rec.status));
230
+ if (options.length === 0)
231
+ break;
232
+ const c = options[Math.floor(Math.random() * options.length)];
233
+ const input = await sampleInput(c, rec);
234
+ if (!input)
235
+ continue;
236
+ const r = await env.callAs(await powerActor(c), c.capability, input);
237
+ steps++;
238
+ const [row] = await env.query(`select * from "${a.table.schema}"."${a.table.name}" where id = $1`, [rec.id]);
239
+ const snap = rowToSnapshot(a, row);
240
+ for (const n of names) {
241
+ let ok = false;
242
+ try {
243
+ ok = !!a.invariants[n](snap);
244
+ }
245
+ catch {
246
+ ok = false;
247
+ }
248
+ if (!ok)
249
+ violations.push(`${c.capability} 之后 ${aggName}.${n} 不成立`);
250
+ }
251
+ if (r.ok)
252
+ rec = { ...rec, ...snap };
253
+ }
254
+ }
255
+ return { steps, violations };
256
+ },
257
+ };
258
+ }
259
+ //# sourceMappingURL=derived.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derived.js","sourceRoot":"","sources":["../src/derived.ts"],"names":[],"mappings":"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"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { type AnyAggregate, type Ctx, type Kernel, type Logger } from '@yss123/cage-kernel';
2
+ import { type MigrationFile } from '@yss123/cage-kernel/db';
3
+ import { Kysely } from 'kysely';
4
+ export type CallResult = {
5
+ ok: true;
6
+ data: Record<string, any>;
7
+ code?: undefined;
8
+ } | {
9
+ ok: false;
10
+ code: string;
11
+ domainCode?: string;
12
+ detail?: unknown;
13
+ data?: undefined;
14
+ };
15
+ export interface TestUser {
16
+ id: string;
17
+ username: string;
18
+ roleId: string;
19
+ ctx(): Promise<Ctx>;
20
+ }
21
+ export interface TestEnv {
22
+ kernel: Kernel;
23
+ tenantId: string;
24
+ rootDeptId: string;
25
+ urls: {
26
+ super: string;
27
+ app: string;
28
+ read: string;
29
+ migrate: string;
30
+ };
31
+ /** 拥有全部能力的系统身份 */
32
+ system(): Ctx;
33
+ /** 以某个身份调用命令;永远不抛异常,返回 { ok, code } */
34
+ callAs(ctx: Ctx, capability: string, input: unknown): Promise<CallResult>;
35
+ /** 建一个部门 */
36
+ dept(name: string, parentId?: string): Promise<string>;
37
+ /** 建一个带角色的用户:caps 为能力点列表(或 'all'),scopes 为各聚合的组合子 */
38
+ user(opts: {
39
+ caps: string[] | 'all';
40
+ scopes?: Record<string, string | {
41
+ combinator: string;
42
+ deptIds?: string[];
43
+ }>;
44
+ deptId?: string;
45
+ params?: Record<string, Record<string, unknown>>;
46
+ username?: string;
47
+ }): Promise<TestUser>;
48
+ /** 有其他所有能力、唯独缺某个能力点的用户(范围为全部) */
49
+ actorWithout(capability: string): Promise<Ctx>;
50
+ /** 直接写入一条处在某状态的数据(测试捷径,绕过命令;只能在测试里用) */
51
+ insert(agg: AnyAggregate | string, values?: Record<string, unknown> & {
52
+ id?: string;
53
+ status?: string;
54
+ ownerId?: string;
55
+ deptId?: string;
56
+ }): Promise<Record<string, any>>;
57
+ /** 造一条处在某命令合法起始状态的数据 */
58
+ seedFor(capability: string, values?: Record<string, unknown> & {
59
+ ownerId?: string;
60
+ }): Promise<Record<string, any>>;
61
+ /** 以某人身份拿读侧连接(会话变量已设置),可以直接写裸 SQL */
62
+ readConnectionAs(ctx: Ctx): Promise<Kysely<any> & {
63
+ release(): Promise<void>;
64
+ }>;
65
+ /** 处理 outbox 直到为空 */
66
+ drain(): Promise<number>;
67
+ /** 审计记录(超级用户读取) */
68
+ audit(where?: {
69
+ aggregateId?: string;
70
+ command?: string;
71
+ kind?: string;
72
+ }): Promise<any[]>;
73
+ query(text: string, params?: unknown[]): Promise<any[]>;
74
+ close(): Promise<void>;
75
+ }
76
+ export declare function createTestEnv(opts: {
77
+ domain: object | object[];
78
+ /** 不传时直接按声明建表(不经过迁移文件) */
79
+ migrations?: MigrationFile[];
80
+ logger?: Logger;
81
+ }): Promise<TestEnv>;
package/dist/env.js ADDED
@@ -0,0 +1,185 @@
1
+ // 集成测试环境:每个测试文件一个临时库,执行初始化脚本、建表、重建安全层,返回内核和一组测试工具。
2
+ import { randomBytes, randomUUID } from 'node:crypto';
3
+ import { DEFAULT_TENANT, createKernel, isCageError, silentLogger, systemCtx, toCageError, } from '@yss123/cage-kernel';
4
+ import { bootstrapSql, createTableSql, fieldsToColumns, runMigrations, schemaModel } from '@yss123/cage-kernel/db';
5
+ import { createWorker } from '@yss123/cage-kernel/worker';
6
+ import { Kysely, PostgresDialect, sql } from 'kysely';
7
+ import pg from 'pg';
8
+ import { sampleRecord } from './fakes.js';
9
+ import { startEmbeddedPostgres, withDb } from './pg.js';
10
+ import { setSessionVarsSql } from './session.js';
11
+ let ownPg = null;
12
+ async function superUrl() {
13
+ if (process.env.CAGE_TEST_SUPER_URL)
14
+ return process.env.CAGE_TEST_SUPER_URL;
15
+ if (process.env.CAGE_TEST_DATABASE_URL)
16
+ return process.env.CAGE_TEST_DATABASE_URL;
17
+ ownPg ??= await startEmbeddedPostgres();
18
+ process.env.CAGE_TEST_SUPER_URL = ownPg.superUrl;
19
+ return ownPg.superUrl;
20
+ }
21
+ export async function createTestEnv(opts) {
22
+ const su = await superUrl();
23
+ const dbName = `cage_t_${randomBytes(5).toString('hex')}`;
24
+ const pw = { app: 'cage_app_pw', read: 'cage_read_pw', migrate: 'cage_migrate_pw' };
25
+ const superDb = withDb(su, dbName);
26
+ const admin = new pg.Client({ connectionString: su });
27
+ await admin.connect();
28
+ try {
29
+ await admin.query(`CREATE DATABASE ${dbName}`);
30
+ // 角色属于集群;多个测试文件即使使用不同临时库,也会同时修改同一批角色。
31
+ // 咨询锁必须在共同的管理库上取得,在各自临时库上加锁无法互斥。
32
+ // 只串行化初始化阶段,后续业务测试仍并行;关闭 admin 连接自动释放会话锁。
33
+ await admin.query("select pg_advisory_lock(hashtext('cage:testkit:cluster-roles'))");
34
+ const boot = new pg.Client({ connectionString: superDb });
35
+ await boot.connect();
36
+ try {
37
+ await boot.query(bootstrapSql(pw, dbName));
38
+ }
39
+ finally {
40
+ await boot.end();
41
+ }
42
+ }
43
+ finally {
44
+ await admin.end();
45
+ }
46
+ const urls = {
47
+ super: superDb,
48
+ app: withDb(su, dbName, { name: 'cage_app', password: pw.app }),
49
+ read: withDb(su, dbName, { name: 'cage_read', password: pw.read }),
50
+ migrate: withDb(su, dbName, { name: 'cage_migrate', password: pw.migrate }),
51
+ };
52
+ const kernel = createKernel({ domain: opts.domain, urls: { app: urls.app, read: urls.read }, logger: opts.logger ?? silentLogger, poolSize: 5 });
53
+ if (!kernel.registry.command('Dept.create')) {
54
+ throw new Error("[CAGE-ORG-001] domain 没有包含组织模型:在 domain 的 index.ts 里加上 export * from '@yss123/cage-admin/domain'");
55
+ }
56
+ const model = schemaModel(kernel.registry);
57
+ const migrations = opts.migrations ?? [{ name: '0001_test_schema', sql: model.tables.map(createTableSql).join('\n\n') }];
58
+ await runMigrations({ migrateUrl: urls.migrate, registry: kernel.registry, migrations });
59
+ const tenantId = DEFAULT_TENANT;
60
+ const sys = () => systemCtx({ kind: 'system', name: 'test', tenantId, capabilities: kernel.catalogCaps() });
61
+ const root = await kernel.execute('Dept.create', { name: '总部', code: 'root' }, sys());
62
+ const superPool = new pg.Pool({ connectionString: superDb, max: 2 });
63
+ const worker = createWorker(kernel, { jobs: false });
64
+ const readers = [];
65
+ const callAs = async (ctx, capability, input) => {
66
+ try {
67
+ return { ok: true, data: await kernel.execute(capability, input, ctx) };
68
+ }
69
+ catch (e) {
70
+ const ce = toCageError(e);
71
+ if (!isCageError(e) && ce.code === 'INTERNAL')
72
+ throw e;
73
+ return { ok: false, code: ce.code, domainCode: ce.domainCode, detail: ce.detail };
74
+ }
75
+ };
76
+ const env = {
77
+ kernel,
78
+ tenantId,
79
+ rootDeptId: root.id,
80
+ urls,
81
+ system: sys,
82
+ callAs,
83
+ async dept(name, parentId) {
84
+ const d = await kernel.execute('Dept.create', { name, parentId: parentId ?? root.id }, sys());
85
+ return d.id;
86
+ },
87
+ async user(o) {
88
+ const s = sys();
89
+ const role = await kernel.execute('Role.create', { code: `r_${randomBytes(4).toString('hex')}`, name: 'test role' }, s);
90
+ const caps = o.caps === 'all' ? kernel.catalogCaps() : o.caps;
91
+ for (const c of caps)
92
+ await kernel.execute('Role.grantCapability', { id: role.id, capability: c, params: o.params?.[c] }, s);
93
+ for (const [agg, sc] of Object.entries(o.scopes ?? {})) {
94
+ const g = typeof sc === 'string' ? { combinator: sc } : sc;
95
+ await kernel.execute('Role.setScope', { id: role.id, aggregate: agg, ...g }, s);
96
+ }
97
+ const username = o.username ?? `u_${randomBytes(4).toString('hex')}`;
98
+ const u = await kernel.execute('User.create', { username, name: username, deptId: o.deptId ?? root.id }, s);
99
+ await kernel.execute('User.assignRoles', { id: u.id, roleIds: [role.id] }, s);
100
+ return {
101
+ id: u.id,
102
+ username,
103
+ roleId: role.id,
104
+ ctx: async () => (await kernel.contextFor(u.id, tenantId, { requestId: randomUUID() })),
105
+ };
106
+ },
107
+ async actorWithout(capability) {
108
+ const u = await env.user({ caps: kernel.catalogCaps().filter((c) => c !== capability), scopes: { '*': 'all' } });
109
+ return u.ctx();
110
+ },
111
+ async insert(aggRef, values = {}) {
112
+ const agg = typeof aggRef === 'string' ? kernel.registry.aggregate(aggRef) : aggRef;
113
+ const { status, ownerId, id: givenId, ...fields } = values;
114
+ const rec = sampleRecord(agg, fields);
115
+ if (agg.scope.ownerField)
116
+ rec[agg.scope.ownerField] = ownerId ?? fields[agg.scope.ownerField] ?? (await env.user({ caps: [] })).id;
117
+ if (agg.scope.deptField && agg.scope.deptField !== 'id' && fields[agg.scope.deptField] === undefined)
118
+ rec[agg.scope.deptField] = root.id;
119
+ // 引用字段:指向真实存在的记录(外键)
120
+ for (const [k, f] of Object.entries(agg.fields)) {
121
+ if (!f.ref || fields[k] !== undefined || k === agg.scope.ownerField || (k === agg.scope.deptField && rec[k]))
122
+ continue;
123
+ if (f.isOptional) {
124
+ rec[k] = null;
125
+ continue;
126
+ }
127
+ rec[k] = f.ref === 'Dept' ? root.id : f.ref === 'User' ? (await env.user({ caps: [] })).id : (await env.insert(f.ref)).id;
128
+ }
129
+ const row = {
130
+ id: givenId ?? randomUUID(), tenant_id: tenantId, ...fieldsToColumns(agg, rec), status: status ?? agg.states.initial, version: 1,
131
+ };
132
+ await kernel.app.insertInto(`${agg.table.schema}.${agg.table.name}`).values(row).execute();
133
+ return { ...rec, id: row.id, status: row.status, version: 1 };
134
+ },
135
+ async seedFor(capability, values = {}) {
136
+ const rc = kernel.registry.command(capability);
137
+ if (!rc)
138
+ throw new Error(`没有命令 ${capability}`);
139
+ if (rc.cmd.from === 'new')
140
+ throw new Error(`${capability} 是新建命令,不需要种子数据`);
141
+ return env.insert(rc.cmd.agg, { status: rc.cmd.from[0], ...values });
142
+ },
143
+ async readConnectionAs(ctx) {
144
+ const pool = new pg.Pool({ connectionString: urls.read, max: 1, idleTimeoutMillis: 0 });
145
+ readers.push(pool);
146
+ const c = await pool.connect();
147
+ await c.query(setSessionVarsSql(ctx));
148
+ c.release();
149
+ const db = new Kysely({ dialect: new PostgresDialect({ pool }) });
150
+ db.release = async () => {
151
+ await db.destroy();
152
+ };
153
+ return db;
154
+ },
155
+ drain: () => worker.drain(),
156
+ async audit(where = {}) {
157
+ const cond = [];
158
+ const params = [];
159
+ if (where.aggregateId)
160
+ cond.push(`aggregate_id = $${params.push(where.aggregateId)}`);
161
+ if (where.command)
162
+ cond.push(`command = $${params.push(where.command)}`);
163
+ if (where.kind)
164
+ cond.push(`kind = $${params.push(where.kind)}`);
165
+ const r = await superPool.query(`select * from sys.audit_log ${cond.length ? 'where ' + cond.join(' and ') : ''} order by id`, params);
166
+ return r.rows;
167
+ },
168
+ async query(text, params = []) {
169
+ return (await superPool.query(text, params)).rows;
170
+ },
171
+ async close() {
172
+ await kernel.close();
173
+ for (const p of readers)
174
+ await p.end().catch(() => { });
175
+ await superPool.end();
176
+ const c = new pg.Client({ connectionString: su });
177
+ await c.connect();
178
+ await c.query(`DROP DATABASE IF EXISTS ${dbName} WITH (FORCE)`).catch(() => { });
179
+ await c.end();
180
+ },
181
+ };
182
+ void sql;
183
+ return env;
184
+ }
185
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import fc from 'fast-check';
2
+ import type { AnyAggregate, AnyCommand, AnyField, Draft, ReadRepo } from '@yss123/cage-kernel';
3
+ /** 字段的示例值:满足字段校验,便于构造草稿和种子数据 */
4
+ export declare function sampleValue(f: AnyField, name?: string, force?: boolean): unknown;
5
+ /** 字段的 fast-check 生成器:用于不变量的性质测试 */
6
+ export declare function arbitraryOf(f: AnyField): fc.Arbitrary<unknown>;
7
+ export declare function arbitrarySnapshot(agg: AnyAggregate): fc.Arbitrary<Record<string, unknown>>;
8
+ /** 一条合法的示例记录(所有字段都有值) */
9
+ export declare function sampleRecord(agg: AnyAggregate, overrides?: Record<string, unknown>): Record<string, any>;
10
+ /**
11
+ * 构造命令 handler 的草稿:状态取命令的第一个起始状态(新建命令取目标状态)
12
+ * const order = draftOf(approve, { items: [{ sku: 'A-01', qty: 10 }] })
13
+ */
14
+ export declare function draftOf<C extends AnyCommand>(cmd: C, overrides?: Partial<Draft<C['agg']>>): Draft<C['agg']>;
15
+ /** 内存只读仓储:等值条件查询,与内核的 ReadRepo 接口一致 */
16
+ export declare function fakeRepo<A extends AnyAggregate = AnyAggregate>(rows: Record<string, any>[]): ReadRepo<A>;
17
+ /** 直接调用 handler 时的第四个参数 */
18
+ export declare function handlerCtx(actorUserId?: string | null): Readonly<{
19
+ now: Date;
20
+ actorUserId: string | null;
21
+ }>;
package/dist/fakes.js ADDED
@@ -0,0 +1,129 @@
1
+ // handler 单测工具:handler 不碰数据库,把读依赖换成内存假对象,直接调用函数。毫秒级。
2
+ import { randomUUID } from 'node:crypto';
3
+ import fc from 'fast-check';
4
+ /** 字段的示例值:满足字段校验,便于构造草稿和种子数据 */
5
+ export function sampleValue(f, name = '', force = false) {
6
+ if (f.isOptional && !force)
7
+ return null;
8
+ switch (f.kind) {
9
+ case 'text':
10
+ if (f.sensitiveKind === 'phone')
11
+ return '13800001234';
12
+ if (f.sensitiveKind === 'email')
13
+ return 'someone@example.com';
14
+ if (f.sensitiveKind === 'idCard')
15
+ return '110101199001011234';
16
+ if (f.sensitiveKind === 'bankCard')
17
+ return '6222020200001234567';
18
+ if (f.sensitiveKind === 'name')
19
+ return '张三';
20
+ return `${name || 'text'}-${randomUUID().slice(0, 8)}`.slice(0, f.max ?? 500);
21
+ case 'int':
22
+ return 1;
23
+ case 'number':
24
+ return 1;
25
+ case 'money':
26
+ return '100.00';
27
+ case 'id':
28
+ case 'ref':
29
+ return randomUUID();
30
+ case 'date':
31
+ return '2026-01-01';
32
+ case 'datetime':
33
+ return new Date('2026-01-01T00:00:00Z').toISOString();
34
+ case 'bool':
35
+ return false;
36
+ case 'enum':
37
+ return f.values?.[0];
38
+ case 'list':
39
+ if (!f.shape)
40
+ return {};
41
+ if ('item' in f.shape && Object.keys(f.shape).length === 1)
42
+ return [sampleValue(f.shape.item, 'item')];
43
+ return [Object.fromEntries(Object.entries(f.shape).map(([k, sf]) => [k, sampleValue(sf, k)]))];
44
+ default:
45
+ return null;
46
+ }
47
+ }
48
+ /** 字段的 fast-check 生成器:用于不变量的性质测试 */
49
+ export function arbitraryOf(f) {
50
+ let a;
51
+ switch (f.kind) {
52
+ case 'text':
53
+ a = fc.string({ maxLength: Math.min(f.max ?? 500, 40) });
54
+ break;
55
+ case 'int':
56
+ a = fc.integer({ min: -1000, max: 100000 });
57
+ break;
58
+ case 'number':
59
+ a = fc.double({ min: -1e6, max: 1e6, noNaN: true });
60
+ break;
61
+ case 'money':
62
+ a = fc.integer({ min: -100000, max: 10_000_000 }).map((n) => (n / 100).toFixed(2));
63
+ break;
64
+ case 'id':
65
+ case 'ref':
66
+ a = fc.uuid();
67
+ break;
68
+ case 'bool':
69
+ a = fc.boolean();
70
+ break;
71
+ case 'enum':
72
+ a = fc.constantFrom(...(f.values ?? ['']));
73
+ break;
74
+ case 'list': {
75
+ const shape = f.shape ?? {};
76
+ if ('item' in shape && Object.keys(shape).length === 1)
77
+ a = fc.array(arbitraryOf(shape.item), { maxLength: 5 });
78
+ else
79
+ a = fc.array(fc.record(Object.fromEntries(Object.entries(shape).map(([k, sf]) => [k, arbitraryOf(sf)]))), { maxLength: 5 });
80
+ break;
81
+ }
82
+ default:
83
+ a = fc.constant(sampleValue(f));
84
+ }
85
+ return f.isOptional ? fc.option(a, { nil: null }) : a;
86
+ }
87
+ export function arbitrarySnapshot(agg) {
88
+ return fc.record(Object.fromEntries(Object.entries(agg.fields).map(([k, f]) => [k, arbitraryOf(f)])));
89
+ }
90
+ /** 一条合法的示例记录(所有字段都有值) */
91
+ export function sampleRecord(agg, overrides = {}) {
92
+ const out = {};
93
+ for (const [k, f] of Object.entries(agg.fields))
94
+ out[k] = sampleValue(f, k);
95
+ return { ...out, ...overrides };
96
+ }
97
+ /**
98
+ * 构造命令 handler 的草稿:状态取命令的第一个起始状态(新建命令取目标状态)
99
+ * const order = draftOf(approve, { items: [{ sku: 'A-01', qty: 10 }] })
100
+ */
101
+ export function draftOf(cmd, overrides = {}) {
102
+ const now = new Date().toISOString();
103
+ const status = cmd.from === 'new' ? cmd.to : cmd.from[0];
104
+ return {
105
+ id: randomUUID(),
106
+ status,
107
+ version: 1,
108
+ createdAt: now,
109
+ updatedAt: now,
110
+ ...sampleRecord(cmd.agg),
111
+ ...overrides,
112
+ };
113
+ }
114
+ /** 内存只读仓储:等值条件查询,与内核的 ReadRepo 接口一致 */
115
+ export function fakeRepo(rows) {
116
+ const match = (r, where) => Object.entries(where ?? {}).every(([k, v]) => v === undefined || JSON.stringify(r[k] ?? null) === JSON.stringify(v));
117
+ const clone = (r) => structuredClone(r);
118
+ return {
119
+ get: async (id) => clone(rows.find((r) => r.id === id) ?? null),
120
+ find: async (where, opts) => rows.filter((r) => match(r, where)).slice(0, opts?.limit ?? Infinity).map(clone),
121
+ findOne: async (where) => clone(rows.find((r) => match(r, where)) ?? null),
122
+ count: async (where) => rows.filter((r) => match(r, where)).length,
123
+ };
124
+ }
125
+ /** 直接调用 handler 时的第四个参数 */
126
+ export function handlerCtx(actorUserId = randomUUID()) {
127
+ return Object.freeze({ now: new Date(), actorUserId });
128
+ }
129
+ //# sourceMappingURL=fakes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakes.js","sourceRoot":"","sources":["../src/fakes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export default function setup(): Promise<() => Promise<void>>;
@@ -0,0 +1,18 @@
1
+ // Vitest globalSetup:整个测试运行只启动一个 PostgreSQL,每个测试文件各建一个临时库。
2
+ // // vitest.config.ts
3
+ // export default { test: { globalSetup: ['@yss123/cage-testkit/global-setup'] } }
4
+ import { startEmbeddedPostgres } from './pg.js';
5
+ let instance = null;
6
+ export default async function setup() {
7
+ if (process.env.CAGE_TEST_DATABASE_URL) {
8
+ process.env.CAGE_TEST_SUPER_URL = process.env.CAGE_TEST_DATABASE_URL;
9
+ return async () => { };
10
+ }
11
+ instance = await startEmbeddedPostgres();
12
+ process.env.CAGE_TEST_SUPER_URL = instance.superUrl;
13
+ return async () => {
14
+ await instance?.stop();
15
+ instance = null;
16
+ };
17
+ }
18
+ //# sourceMappingURL=global-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-setup.js","sourceRoot":"","sources":["../src/global-setup.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,wBAAwB;AACxB,oFAAoF;AACpF,OAAO,EAAE,qBAAqB,EAAmB,MAAM,SAAS,CAAC;AAEjE,IAAI,QAAQ,GAAsB,IAAI,CAAC;AAEvC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,KAAK;IACjC,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;QACrE,OAAO,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IACxB,CAAC;IACD,QAAQ,GAAG,MAAM,qBAAqB,EAAE,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,QAAQ,CAAC,QAAQ,CAAC;IACpD,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,QAAQ,EAAE,IAAI,EAAE,CAAC;QACvB,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { fakeRepo, draftOf, sampleValue, sampleRecord, arbitraryOf, arbitrarySnapshot, handlerCtx } from './fakes.js';
2
+ export { createTestEnv, type TestEnv, type TestUser, type CallResult } from './env.js';
3
+ export { startEmbeddedPostgres, withDb, freePort, type PgInstance } from './pg.js';
4
+ export { setSessionVarsSql } from './session.js';
5
+ export { derived, SKIP, type Derived, type Skip } from './derived.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @yss123/cage-testkit:handler 单测工具、集成测试环境、派生测试运行时。
2
+ export { fakeRepo, draftOf, sampleValue, sampleRecord, arbitraryOf, arbitrarySnapshot, handlerCtx } from './fakes.js';
3
+ export { createTestEnv } from './env.js';
4
+ export { startEmbeddedPostgres, withDb, freePort } from './pg.js';
5
+ export { setSessionVarsSql } from './session.js';
6
+ export { derived, SKIP } from './derived.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtH,OAAO,EAAE,aAAa,EAAgD,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAA2B,MAAM,cAAc,CAAC"}
package/dist/pg.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export interface PgInstance {
2
+ /** 超级用户连接串(指向 postgres 库) */
3
+ superUrl: string;
4
+ host: string;
5
+ port: number;
6
+ stop(): Promise<void>;
7
+ }
8
+ export declare function freePort(): Promise<number>;
9
+ export declare function startEmbeddedPostgres(opts?: {
10
+ dir?: string;
11
+ port?: number;
12
+ persistent?: boolean;
13
+ log?: boolean;
14
+ }): Promise<PgInstance>;
15
+ /** 把超级用户连接串换成另一个库、另一个账号 */
16
+ export declare function withDb(url: string, db: string, user?: {
17
+ name: string;
18
+ password: string;
19
+ }): string;
package/dist/pg.js ADDED
@@ -0,0 +1,59 @@
1
+ // 测试与本地开发用的 PostgreSQL:优先使用 CAGE_TEST_DATABASE_URL(超级用户连接串,例如 CI 里的服务容器),
2
+ // 否则启动 embedded-postgres(npm 包自带真实的 PostgreSQL 二进制,不需要 Docker)。
3
+ import { mkdtempSync, rmSync, existsSync, mkdirSync } from 'node:fs';
4
+ import { createServer } from 'node:net';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ export async function freePort() {
8
+ return new Promise((resolve, reject) => {
9
+ const s = createServer();
10
+ s.unref();
11
+ s.on('error', reject);
12
+ s.listen(0, '127.0.0.1', () => {
13
+ const port = s.address().port;
14
+ s.close(() => resolve(port));
15
+ });
16
+ });
17
+ }
18
+ export async function startEmbeddedPostgres(opts = {}) {
19
+ const { default: EmbeddedPostgres } = await import('embedded-postgres');
20
+ const port = opts.port ?? (await freePort());
21
+ const persistent = opts.persistent ?? false;
22
+ const dir = opts.dir ?? mkdtempSync(join(tmpdir(), 'cage-pg-'));
23
+ const fresh = !existsSync(join(dir, 'PG_VERSION'));
24
+ if (persistent)
25
+ mkdirSync(dir, { recursive: true });
26
+ const pg = new EmbeddedPostgres({
27
+ databaseDir: dir,
28
+ user: 'postgres',
29
+ password: 'postgres',
30
+ port,
31
+ persistent,
32
+ onLog: opts.log ? (m) => process.stdout.write(m) : () => { },
33
+ onError: opts.log ? (m) => process.stderr.write(String(m)) : () => { },
34
+ });
35
+ if (fresh)
36
+ await pg.initialise();
37
+ await pg.start();
38
+ return {
39
+ superUrl: `postgres://postgres:postgres@127.0.0.1:${port}/postgres`,
40
+ host: '127.0.0.1',
41
+ port,
42
+ async stop() {
43
+ await pg.stop();
44
+ if (!persistent)
45
+ rmSync(dir, { recursive: true, force: true });
46
+ },
47
+ };
48
+ }
49
+ /** 把超级用户连接串换成另一个库、另一个账号 */
50
+ export function withDb(url, db, user) {
51
+ const u = new URL(url);
52
+ u.pathname = `/${db}`;
53
+ if (user) {
54
+ u.username = user.name;
55
+ u.password = user.password;
56
+ }
57
+ return u.toString();
58
+ }
59
+ //# sourceMappingURL=pg.js.map
package/dist/pg.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.js","sourceRoot":"","sources":["../src/pg.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Ctx } from '@yss123/cage-kernel';
2
+ export declare function setSessionVarsSql(ctx: Ctx): string;
@@ -0,0 +1,14 @@
1
+ import { scopesJson } from '@yss123/cage-kernel/db';
2
+ const lit = (s) => `'${s.replace(/'/g, "''")}'`;
3
+ export function setSessionVarsSql(ctx) {
4
+ const vars = [
5
+ ['app.user_id', ctx.userId ?? ctx.onBehalfOf ?? ''],
6
+ ['app.tenant_id', ctx.tenantId],
7
+ ['app.dept_id', ctx.deptId ?? ''],
8
+ ['app.dept_path', ctx.deptPath ?? ''],
9
+ ['app.scopes', scopesJson(ctx)],
10
+ ['app.unmask', ctx.unmask.join(',')],
11
+ ];
12
+ return `select ${vars.map(([k, v]) => `set_config(${lit(k)}, ${lit(v)}, false)`).join(', ')}`;
13
+ }
14
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AAExD,MAAM,UAAU,iBAAiB,CAAC,GAAQ;IACxC,MAAM,IAAI,GAAuB;QAC/B,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;QACnD,CAAC,eAAe,EAAE,GAAG,CAAC,QAAQ,CAAC;QAC/B,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;QACjC,CAAC,eAAe,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;QACrC,CAAC,YAAY,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;KACrC,CAAC;IACF,OAAO,UAAU,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAChG,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "@yss123/cage-testkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "派生测试运行器、fake 仓储、测试数据库",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./global-setup": {
14
+ "types": "./dist/global-setup.d.ts",
15
+ "default": "./dist/global-setup.js"
16
+ },
17
+ "./pg": {
18
+ "types": "./dist/pg.d.ts",
19
+ "default": "./dist/pg.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "dependencies": {
26
+ "embedded-postgres": "18.3.0-beta.16",
27
+ "fast-check": "^4.3.0",
28
+ "kysely": "^0.28.5",
29
+ "pg": "^8.16.3",
30
+ "zod": "^4.1.5"
31
+ },
32
+ "peerDependencies": {
33
+ "@yss123/cage-kernel": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/pg": "^8.15.5",
37
+ "vitest": "^4.1.12",
38
+ "@yss123/cage-http": "0.1.0",
39
+ "@yss123/cage-kernel": "0.1.0",
40
+ "@yss123/cage-approval": "0.1.0",
41
+ "@yss123/cage-connectors": "0.1.0",
42
+ "@yss123/cage-admin": "0.1.0"
43
+ },
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/shunshunyue/cage.git",
48
+ "directory": "packages/testkit"
49
+ },
50
+ "homepage": "https://github.com/shunshunyue/cage#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/shunshunyue/cage/issues"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "registry": "https://registry.npmjs.org/"
57
+ },
58
+ "engines": {
59
+ "node": ">=24"
60
+ },
61
+ "scripts": {
62
+ "build": "tsc -p tsconfig.json",
63
+ "typecheck": "tsc -p tsconfig.json --noEmit",
64
+ "test": "vitest run"
65
+ }
6
66
  }