befly 3.10.1 → 3.10.2

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.
Files changed (66) hide show
  1. package/.gitignore +0 -0
  2. package/configs/presetFields.ts +10 -0
  3. package/configs/presetRegexp.ts +225 -0
  4. package/package.json +15 -16
  5. package/tests/_mocks/mockSqliteDb.ts +0 -204
  6. package/tests/addonHelper-cache.test.ts +0 -32
  7. package/tests/api-integration-array-number.test.ts +0 -282
  8. package/tests/apiHandler-routePath-only.test.ts +0 -32
  9. package/tests/befly-config-env.test.ts +0 -78
  10. package/tests/cacheHelper.test.ts +0 -323
  11. package/tests/cacheKeys.test.ts +0 -41
  12. package/tests/checkApi-routePath-strict.test.ts +0 -166
  13. package/tests/checkMenu.test.ts +0 -346
  14. package/tests/checkTable-smoke.test.ts +0 -157
  15. package/tests/cipher.test.ts +0 -249
  16. package/tests/dbDialect-cache.test.ts +0 -23
  17. package/tests/dbDialect.test.ts +0 -46
  18. package/tests/dbHelper-advanced.test.ts +0 -723
  19. package/tests/dbHelper-all-array-types.test.ts +0 -316
  20. package/tests/dbHelper-array-serialization.test.ts +0 -258
  21. package/tests/dbHelper-batch-write.test.ts +0 -90
  22. package/tests/dbHelper-columns.test.ts +0 -234
  23. package/tests/dbHelper-execute.test.ts +0 -187
  24. package/tests/dbHelper-joins.test.ts +0 -221
  25. package/tests/fields-redis-cache.test.ts +0 -127
  26. package/tests/fields-validate.test.ts +0 -99
  27. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  28. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  29. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  30. package/tests/getClientIp.test.ts +0 -54
  31. package/tests/integration.test.ts +0 -189
  32. package/tests/jwt.test.ts +0 -65
  33. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  34. package/tests/logger.test.ts +0 -325
  35. package/tests/redisHelper.test.ts +0 -495
  36. package/tests/redisKeys.test.ts +0 -9
  37. package/tests/scanConfig.test.ts +0 -144
  38. package/tests/scanFiles-routePath.test.ts +0 -46
  39. package/tests/smoke-sql.test.ts +0 -24
  40. package/tests/sqlBuilder-advanced.test.ts +0 -608
  41. package/tests/sqlBuilder.test.ts +0 -209
  42. package/tests/sync-connection.test.ts +0 -183
  43. package/tests/sync-init-guard.test.ts +0 -105
  44. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  45. package/tests/syncApi-obsolete-records.test.ts +0 -69
  46. package/tests/syncApi-type-compat.test.ts +0 -72
  47. package/tests/syncDev-permissions.test.ts +0 -81
  48. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  49. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  50. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  51. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  52. package/tests/syncMenu-paths.test.ts +0 -59
  53. package/tests/syncTable-apply.test.ts +0 -279
  54. package/tests/syncTable-array-number.test.ts +0 -160
  55. package/tests/syncTable-constants.test.ts +0 -101
  56. package/tests/syncTable-db-integration.test.ts +0 -237
  57. package/tests/syncTable-ddl.test.ts +0 -245
  58. package/tests/syncTable-helpers.test.ts +0 -99
  59. package/tests/syncTable-schema.test.ts +0 -99
  60. package/tests/syncTable-testkit.test.ts +0 -25
  61. package/tests/syncTable-types.test.ts +0 -122
  62. package/tests/tableRef-and-deserialize.test.ts +0 -67
  63. package/tests/util.test.ts +0 -100
  64. package/tests/validator-array-number.test.ts +0 -310
  65. package/tests/validator-default.test.ts +0 -373
  66. package/tests/validator.test.ts +0 -679
@@ -1,237 +0,0 @@
1
- /**
2
- * syncTable 端到端行为测试(纯 mock,不连接真实数据库)
3
- */
4
-
5
- import type { FieldDefinition } from "../types/validate.js";
6
- import type { ScanFileResult } from "../utils/scanFiles.js";
7
- import type { MockSqliteState } from "./_mocks/mockSqliteDb.js";
8
-
9
- import { describe, expect, test } from "bun:test";
10
-
11
- import { CacheKeys } from "../lib/cacheKeys.js";
12
- import { syncTable } from "../sync/syncTable.js";
13
- import { createMockSqliteDb } from "./_mocks/mockSqliteDb.js";
14
-
15
- function buildTableItem(options: { tableFileName: string; content: any }): ScanFileResult {
16
- return {
17
- source: "app",
18
- type: "table",
19
- sourceName: "项目",
20
- filePath: "",
21
- relativePath: options.tableFileName,
22
- fileName: options.tableFileName,
23
- moduleName: `app_${options.tableFileName}`,
24
- addonName: "",
25
- fileBaseName: "",
26
- fileDir: "",
27
- content: options.content
28
- } as any;
29
- }
30
-
31
- function fdString(options: { name: string; min: number; max: number; defaultValue: any; nullable: boolean }): FieldDefinition {
32
- return {
33
- name: options.name,
34
- type: "string",
35
- min: options.min,
36
- max: options.max,
37
- default: options.defaultValue,
38
- nullable: options.nullable
39
- } as any;
40
- }
41
-
42
- function fdNumber(options: { name: string; min: number; max: number; defaultValue: any; nullable: boolean }): FieldDefinition {
43
- return {
44
- name: options.name,
45
- type: "number",
46
- min: options.min,
47
- max: options.max,
48
- default: options.defaultValue,
49
- nullable: options.nullable
50
- } as any;
51
- }
52
-
53
- function fdText(options: { name: string; min: number; max: number; defaultValue: any; nullable: boolean }): FieldDefinition {
54
- return {
55
- name: options.name,
56
- type: "text",
57
- min: options.min,
58
- max: options.max,
59
- default: options.defaultValue,
60
- nullable: options.nullable
61
- } as any;
62
- }
63
-
64
- describe("syncTable(ctx, items) - mock sqlite", () => {
65
- test("首次同步:应创建表并包含系统字段 + 业务字段,同时清理 columns 缓存", async () => {
66
- const state: MockSqliteState = {
67
- executedSql: [],
68
- tables: {}
69
- };
70
-
71
- const db = createMockSqliteDb(state);
72
-
73
- const redisCalls: Array<{ keys: string[] }> = [];
74
- const ctx = {
75
- db: db,
76
- redis: {
77
- delBatch: async (keys: string[]) => {
78
- redisCalls.push({ keys: keys });
79
- return keys.length;
80
- }
81
- },
82
- config: {
83
- db: { type: "sqlite", database: "" }
84
- }
85
- } as any;
86
-
87
- const tableFileName = "test_sync_table_integration_user";
88
- const item = buildTableItem({
89
- tableFileName: tableFileName,
90
- content: {
91
- email: fdString({ name: "邮箱", min: 0, max: 100, defaultValue: null, nullable: false }),
92
- nickname: fdString({ name: "昵称", min: 0, max: 50, defaultValue: "用户", nullable: true }),
93
- age: fdNumber({ name: "年龄", min: 0, max: 999, defaultValue: 0, nullable: true })
94
- }
95
- });
96
-
97
- await syncTable(ctx, [item]);
98
-
99
- expect(state.executedSql.some((s) => s.includes("CREATE TABLE") && s.includes(tableFileName))).toBe(true);
100
-
101
- const runtime = syncTable.TestKit.createRuntime("sqlite", db as any, "");
102
- const exists = await syncTable.TestKit.tableExistsRuntime(runtime, tableFileName);
103
- expect(exists).toBe(true);
104
-
105
- const columns = await syncTable.TestKit.getTableColumnsRuntime(runtime, tableFileName);
106
-
107
- expect(columns.id).toBeDefined();
108
- expect(columns.created_at).toBeDefined();
109
- expect(columns.updated_at).toBeDefined();
110
- expect(columns.state).toBeDefined();
111
-
112
- expect(columns.email).toBeDefined();
113
- expect(columns.nickname).toBeDefined();
114
- expect(columns.age).toBeDefined();
115
-
116
- expect(redisCalls.length).toBe(1);
117
- expect(redisCalls[0].keys).toEqual([CacheKeys.tableColumns(tableFileName)]);
118
- });
119
-
120
- test("二次同步:新增字段应落库(ADD COLUMN),同时清理 columns 缓存", async () => {
121
- const state: MockSqliteState = {
122
- executedSql: [],
123
- tables: {}
124
- };
125
-
126
- const db = createMockSqliteDb(state);
127
-
128
- const redisCalls: Array<{ keys: string[] }> = [];
129
- const ctx = {
130
- db: db,
131
- redis: {
132
- delBatch: async (keys: string[]) => {
133
- redisCalls.push({ keys: keys });
134
- return keys.length;
135
- }
136
- },
137
- config: {
138
- db: { type: "sqlite", database: "" }
139
- }
140
- } as any;
141
-
142
- const tableFileName = "test_sync_table_integration_profile";
143
-
144
- const itemV1 = buildTableItem({
145
- tableFileName: tableFileName,
146
- content: {
147
- nickname: fdString({ name: "昵称", min: 0, max: 50, defaultValue: "用户", nullable: true })
148
- }
149
- });
150
-
151
- await syncTable(ctx, [itemV1]);
152
-
153
- const itemV2 = buildTableItem({
154
- tableFileName: tableFileName,
155
- content: {
156
- nickname: fdString({ name: "昵称", min: 0, max: 50, defaultValue: "用户", nullable: true }),
157
- bio: fdText({ name: "简介", min: 0, max: 200, defaultValue: null, nullable: true })
158
- }
159
- });
160
-
161
- await syncTable(ctx, [itemV2]);
162
-
163
- expect(state.executedSql.some((s) => s.includes("ALTER TABLE") && s.includes(tableFileName) && s.includes("ADD COLUMN") && s.includes("bio"))).toBe(true);
164
-
165
- const runtime = syncTable.TestKit.createRuntime("sqlite", db as any, "");
166
- const columns = await syncTable.TestKit.getTableColumnsRuntime(runtime, tableFileName);
167
- expect(columns.nickname).toBeDefined();
168
- expect(columns.bio).toBeDefined();
169
-
170
- // 两次同步,每次都会清一次缓存
171
- expect(redisCalls.length).toBe(2);
172
- expect(redisCalls[0].keys).toEqual([CacheKeys.tableColumns(tableFileName)]);
173
- expect(redisCalls[1].keys).toEqual([CacheKeys.tableColumns(tableFileName)]);
174
- });
175
-
176
- test("索引变更:仅删除单列索引;复合索引不会被误删", async () => {
177
- const tableFileName = "test_sync_table_integration_indexes";
178
-
179
- const state: MockSqliteState = {
180
- executedSql: [],
181
- tables: {
182
- [tableFileName]: {
183
- columns: {
184
- id: { name: "id", type: "INTEGER", notnull: 1, dflt_value: null },
185
- created_at: { name: "created_at", type: "INTEGER", notnull: 1, dflt_value: "0" },
186
- updated_at: { name: "updated_at", type: "INTEGER", notnull: 1, dflt_value: "0" },
187
- deleted_at: { name: "deleted_at", type: "INTEGER", notnull: 1, dflt_value: "0" },
188
- state: { name: "state", type: "INTEGER", notnull: 1, dflt_value: "1" },
189
- user_id: { name: "user_id", type: "INTEGER", notnull: 1, dflt_value: "0" },
190
- user_name: { name: "user_name", type: "TEXT", notnull: 1, dflt_value: "''" }
191
- },
192
- indexes: {
193
- // 单列索引:应该能被识别并在 index=false 时被 drop
194
- idx_user_name: ["user_name"],
195
- // 复合索引:即使名字像单列索引,也因为多列而被 runtime 忽略,因此不会被 drop
196
- idx_user_id: ["user_id", "created_at"]
197
- }
198
- }
199
- }
200
- };
201
-
202
- const db = createMockSqliteDb(state);
203
-
204
- const redisCalls: Array<{ keys: string[] }> = [];
205
- const ctx = {
206
- db: db,
207
- redis: {
208
- delBatch: async (keys: string[]) => {
209
- redisCalls.push({ keys: keys });
210
- return keys.length;
211
- }
212
- },
213
- config: {
214
- db: { type: "sqlite", database: "" }
215
- }
216
- } as any;
217
-
218
- const item = buildTableItem({
219
- tableFileName: tableFileName,
220
- content: {
221
- userId: fdNumber({ name: "用户ID", min: 0, max: 999999999, defaultValue: 0, nullable: false }),
222
- userName: fdString({ name: "用户名", min: 0, max: 50, defaultValue: "", nullable: false })
223
- }
224
- });
225
-
226
- await syncTable(ctx, [item]);
227
-
228
- const dropUserName = state.executedSql.some((s) => s.includes("DROP INDEX") && s.includes("idx_user_name"));
229
- expect(dropUserName).toBe(true);
230
-
231
- const dropUserIdComposite = state.executedSql.some((s) => s.includes("DROP INDEX") && s.includes("idx_user_id"));
232
- expect(dropUserIdComposite).toBe(false);
233
-
234
- expect(redisCalls.length).toBe(1);
235
- expect(redisCalls[0].keys).toEqual([CacheKeys.tableColumns(tableFileName)]);
236
- });
237
- });
@@ -1,245 +0,0 @@
1
- /**
2
- * syncTable DDL 构建模块测试
3
- *
4
- * 测试 ddl.ts 中的函数:
5
- * - buildIndexSQL
6
- * - buildSystemColumnDefs
7
- * - buildBusinessColumnDefs
8
- * - generateDDLClause
9
- * - isCompatibleTypeChange
10
- */
11
-
12
- import { describe, test, expect } from "bun:test";
13
-
14
- import { syncTable } from "../sync/syncTable.js";
15
-
16
- describe("buildIndexSQL (MySQL)", () => {
17
- test("创建索引 SQL", () => {
18
- const sql = syncTable.TestKit.buildIndexSQL("mysql", "user", "idx_created_at", "created_at", "create");
19
- expect(sql).toContain("ALTER TABLE `user`");
20
- expect(sql).toContain("ADD INDEX `idx_created_at`");
21
- expect(sql).toContain("(`created_at`)");
22
- expect(sql).toContain("ALGORITHM=INPLACE");
23
- expect(sql).toContain("LOCK=NONE");
24
- });
25
-
26
- test("删除索引 SQL", () => {
27
- const sql = syncTable.TestKit.buildIndexSQL("mysql", "user", "idx_created_at", "created_at", "drop");
28
- expect(sql).toContain("ALTER TABLE `user`");
29
- expect(sql).toContain("DROP INDEX `idx_created_at`");
30
- });
31
- });
32
-
33
- describe("buildSystemColumnDefs (MySQL)", () => {
34
- test("返回 5 个系统字段定义", () => {
35
- const defs = syncTable.TestKit.buildSystemColumnDefs("mysql");
36
- expect(defs.length).toBe(5);
37
- });
38
-
39
- test("包含 id 主键", () => {
40
- const defs = syncTable.TestKit.buildSystemColumnDefs("mysql");
41
- const idDef = defs.find((d: string) => d.includes("`id`"));
42
- expect(idDef).toContain("PRIMARY KEY");
43
- expect(idDef).toContain("AUTO_INCREMENT");
44
- expect(idDef).toContain("BIGINT UNSIGNED");
45
- });
46
-
47
- test("包含 created_at 字段", () => {
48
- const defs = syncTable.TestKit.buildSystemColumnDefs("mysql");
49
- const def = defs.find((d: string) => d.includes("`created_at`"));
50
- expect(def).toContain("BIGINT UNSIGNED");
51
- expect(def).toContain("NOT NULL");
52
- expect(def).toContain("DEFAULT 0");
53
- });
54
-
55
- test("包含 state 字段", () => {
56
- const defs = syncTable.TestKit.buildSystemColumnDefs("mysql");
57
- const def = defs.find((d: string) => d.includes("`state`"));
58
- expect(def).toContain("BIGINT UNSIGNED");
59
- expect(def).toContain("NOT NULL");
60
- expect(def).toContain("DEFAULT 1");
61
- });
62
- });
63
-
64
- describe("buildSystemColumnDefs (PostgreSQL)", () => {
65
- test("返回 5 个系统字段定义", () => {
66
- const defs = syncTable.TestKit.buildSystemColumnDefs("postgresql");
67
- expect(defs.length).toBe(5);
68
- });
69
-
70
- test("包含 id 主键 identity", () => {
71
- const defs = syncTable.TestKit.buildSystemColumnDefs("postgresql");
72
- const idDef = defs.find((d: string) => d.includes('"id"'));
73
- expect(idDef).toContain("BIGINT");
74
- expect(idDef).toContain("PRIMARY KEY");
75
- expect(idDef).toContain("GENERATED");
76
- expect(idDef).toContain("IDENTITY");
77
- });
78
-
79
- test("包含 created_at BIGINT 默认值", () => {
80
- const defs = syncTable.TestKit.buildSystemColumnDefs("postgresql");
81
- const def = defs.find((d: string) => d.includes('"created_at"'));
82
- expect(def).toContain("BIGINT");
83
- expect(def).toContain("NOT NULL");
84
- expect(def).toContain("DEFAULT 0");
85
- });
86
-
87
- test("包含 state BIGINT 默认值", () => {
88
- const defs = syncTable.TestKit.buildSystemColumnDefs("postgresql");
89
- const def = defs.find((d: string) => d.includes('"state"'));
90
- expect(def).toContain("BIGINT");
91
- expect(def).toContain("NOT NULL");
92
- expect(def).toContain("DEFAULT 1");
93
- });
94
- });
95
-
96
- describe("buildBusinessColumnDefs (MySQL)", () => {
97
- test("生成 string 类型字段", () => {
98
- const fields = {
99
- userName: {
100
- name: "用户名",
101
- type: "string",
102
- max: 50,
103
- default: null,
104
- unique: false,
105
- nullable: false,
106
- unsigned: true
107
- }
108
- };
109
- const defs = syncTable.TestKit.buildBusinessColumnDefs("mysql", fields as any);
110
- expect(defs.length).toBe(1);
111
- expect(defs[0]).toContain("`user_name`");
112
- expect(defs[0]).toContain("VARCHAR(50)");
113
- expect(defs[0]).toContain("NOT NULL");
114
- expect(defs[0]).toContain("DEFAULT ''");
115
- expect(defs[0]).toContain('COMMENT "用户名"');
116
- });
117
-
118
- test("生成 number 类型字段", () => {
119
- const fields = {
120
- age: {
121
- name: "年龄",
122
- type: "number",
123
- max: null,
124
- default: 0,
125
- unique: false,
126
- nullable: false,
127
- unsigned: true
128
- }
129
- };
130
- const defs = syncTable.TestKit.buildBusinessColumnDefs("mysql", fields as any);
131
- expect(defs[0]).toContain("`age`");
132
- expect(defs[0]).toContain("BIGINT UNSIGNED");
133
- expect(defs[0]).toContain("DEFAULT 0");
134
- });
135
-
136
- test("生成 unique 字段", () => {
137
- const fields = {
138
- email: {
139
- name: "邮箱",
140
- type: "string",
141
- max: 100,
142
- default: null,
143
- unique: true,
144
- nullable: false,
145
- unsigned: true
146
- }
147
- };
148
- const defs = syncTable.TestKit.buildBusinessColumnDefs("mysql", fields as any);
149
- expect(defs[0]).toContain("UNIQUE");
150
- });
151
-
152
- test("生成 nullable 字段", () => {
153
- const fields = {
154
- remark: {
155
- name: "备注",
156
- type: "string",
157
- max: 200,
158
- default: null,
159
- unique: false,
160
- nullable: true,
161
- unsigned: true
162
- }
163
- };
164
- const defs = syncTable.TestKit.buildBusinessColumnDefs("mysql", fields as any);
165
- expect(defs[0]).toContain("NULL");
166
- expect(defs[0]).not.toContain("NOT NULL");
167
- });
168
- });
169
-
170
- describe("generateDDLClause (MySQL)", () => {
171
- test("生成 ADD COLUMN 子句", () => {
172
- const fieldDef = {
173
- name: "用户名",
174
- type: "string",
175
- max: 50,
176
- default: null,
177
- unique: false,
178
- nullable: false,
179
- unsigned: true
180
- };
181
- const clause = syncTable.TestKit.generateDDLClause("mysql", "userName", fieldDef as any, true);
182
- expect(clause).toContain("ADD COLUMN");
183
- expect(clause).toContain("`user_name`");
184
- expect(clause).toContain("VARCHAR(50)");
185
- });
186
-
187
- test("生成 MODIFY COLUMN 子句", () => {
188
- const fieldDef = {
189
- name: "用户名",
190
- type: "string",
191
- max: 100,
192
- default: null,
193
- unique: false,
194
- nullable: false,
195
- unsigned: true
196
- };
197
- const clause = syncTable.TestKit.generateDDLClause("mysql", "userName", fieldDef as any, false);
198
- expect(clause).toContain("MODIFY COLUMN");
199
- expect(clause).toContain("`user_name`");
200
- expect(clause).toContain("VARCHAR(100)");
201
- });
202
- });
203
-
204
- describe("isCompatibleTypeChange", () => {
205
- test("varchar -> text 是兼容变更", () => {
206
- expect(syncTable.TestKit.isCompatibleTypeChange("character varying", "text")).toBe(true);
207
- expect(syncTable.TestKit.isCompatibleTypeChange("varchar(100)", "text")).toBe(true);
208
- expect(syncTable.TestKit.isCompatibleTypeChange("varchar(100)", "mediumtext")).toBe(true);
209
- });
210
-
211
- test("text -> varchar 不是兼容变更", () => {
212
- expect(syncTable.TestKit.isCompatibleTypeChange("text", "character varying")).toBe(false);
213
- expect(syncTable.TestKit.isCompatibleTypeChange("text", "varchar(100)")).toBe(false);
214
- });
215
-
216
- test("int -> bigint 是兼容变更", () => {
217
- expect(syncTable.TestKit.isCompatibleTypeChange("int", "bigint")).toBe(true);
218
- expect(syncTable.TestKit.isCompatibleTypeChange("int unsigned", "bigint unsigned")).toBe(true);
219
- expect(syncTable.TestKit.isCompatibleTypeChange("tinyint", "int")).toBe(true);
220
- expect(syncTable.TestKit.isCompatibleTypeChange("tinyint", "bigint")).toBe(true);
221
- expect(syncTable.TestKit.isCompatibleTypeChange("smallint", "int")).toBe(true);
222
- expect(syncTable.TestKit.isCompatibleTypeChange("mediumint", "bigint")).toBe(true);
223
- });
224
-
225
- test("bigint -> int 不是兼容变更(收缩)", () => {
226
- expect(syncTable.TestKit.isCompatibleTypeChange("bigint", "int")).toBe(false);
227
- expect(syncTable.TestKit.isCompatibleTypeChange("int", "tinyint")).toBe(false);
228
- });
229
-
230
- test("PG integer -> bigint 是兼容变更", () => {
231
- expect(syncTable.TestKit.isCompatibleTypeChange("integer", "bigint")).toBe(true);
232
- expect(syncTable.TestKit.isCompatibleTypeChange("smallint", "integer")).toBe(true);
233
- expect(syncTable.TestKit.isCompatibleTypeChange("smallint", "bigint")).toBe(true);
234
- });
235
-
236
- test("相同类型不是变更", () => {
237
- expect(syncTable.TestKit.isCompatibleTypeChange("text", "text")).toBe(false);
238
- expect(syncTable.TestKit.isCompatibleTypeChange("bigint", "bigint")).toBe(false);
239
- });
240
-
241
- test("空值处理", () => {
242
- expect(syncTable.TestKit.isCompatibleTypeChange(null as any, "text")).toBe(false);
243
- expect(syncTable.TestKit.isCompatibleTypeChange("text", null as any)).toBe(false);
244
- });
245
- });
@@ -1,99 +0,0 @@
1
- /**
2
- * syncTable 辅助工具模块测试
3
- *
4
- * 测试 helpers.ts 中的函数:
5
- * - quoteIdentifier
6
- * - escapeComment
7
- * - applyFieldDefaults
8
- */
9
-
10
- import { describe, test, expect } from "bun:test";
11
-
12
- import { syncTable } from "../sync/syncTable.js";
13
-
14
- describe("quoteIdentifier (MySQL)", () => {
15
- test("使用反引号包裹标识符", () => {
16
- expect(syncTable.TestKit.quoteIdentifier("mysql", "user_table")).toBe("`user_table`");
17
- });
18
-
19
- test("处理普通表名", () => {
20
- expect(syncTable.TestKit.quoteIdentifier("mysql", "admin")).toBe("`admin`");
21
- });
22
-
23
- test("处理带下划线的表名", () => {
24
- expect(syncTable.TestKit.quoteIdentifier("mysql", "addon_admin_menu")).toBe("`addon_admin_menu`");
25
- });
26
- });
27
-
28
- describe("escapeComment", () => {
29
- test("普通注释不变", () => {
30
- expect(syncTable.TestKit.escapeComment("用户名称")).toBe("用户名称");
31
- });
32
-
33
- test("双引号被转义", () => {
34
- expect(syncTable.TestKit.escapeComment('用户"昵称"')).toBe('用户\\"昵称\\"');
35
- });
36
-
37
- test("空字符串", () => {
38
- expect(syncTable.TestKit.escapeComment("")).toBe("");
39
- });
40
- });
41
-
42
- describe("applyFieldDefaults", () => {
43
- test("为空字段定义应用默认值", () => {
44
- const fieldDef: any = {
45
- name: "用户名",
46
- type: "string"
47
- };
48
-
49
- syncTable.TestKit.applyFieldDefaults(fieldDef);
50
-
51
- expect(fieldDef.detail).toBe("");
52
- expect(fieldDef.min).toBe(0);
53
- expect(fieldDef.max).toBe(100);
54
- expect(fieldDef.default).toBe(null);
55
- expect(fieldDef.index).toBe(false);
56
- expect(fieldDef.unique).toBe(false);
57
- expect(fieldDef.nullable).toBe(false);
58
- expect(fieldDef.unsigned).toBe(true);
59
- expect(fieldDef.regexp).toBe(null);
60
- });
61
-
62
- test("保留已有值", () => {
63
- const fieldDef: any = {
64
- name: "用户名",
65
- type: "string",
66
- max: 200,
67
- index: true,
68
- unique: true,
69
- nullable: true
70
- };
71
-
72
- syncTable.TestKit.applyFieldDefaults(fieldDef);
73
-
74
- expect(fieldDef.max).toBe(200);
75
- expect(fieldDef.index).toBe(true);
76
- expect(fieldDef.unique).toBe(true);
77
- expect(fieldDef.nullable).toBe(true);
78
- });
79
-
80
- test("处理 0 和 false 值", () => {
81
- const fieldDef: any = {
82
- name: "排序",
83
- type: "number",
84
- min: 0,
85
- max: 0,
86
- default: 0,
87
- index: false,
88
- unsigned: false
89
- };
90
-
91
- syncTable.TestKit.applyFieldDefaults(fieldDef);
92
-
93
- expect(fieldDef.min).toBe(0);
94
- expect(fieldDef.max).toBe(0);
95
- expect(fieldDef.default).toBe(0);
96
- expect(fieldDef.index).toBe(false);
97
- expect(fieldDef.unsigned).toBe(false);
98
- });
99
- });
@@ -1,99 +0,0 @@
1
- /**
2
- * syncTable 表结构查询模块测试(纯 mock,不连接真实数据库)
3
- */
4
-
5
- import { describe, expect, test } from "bun:test";
6
-
7
- import { syncTable } from "../sync/syncTable.js";
8
- import { createMockSqliteDb } from "./_mocks/mockSqliteDb.js";
9
-
10
- describe("tableExistsRuntime", () => {
11
- test("sql 执行器未初始化时抛出错误", async () => {
12
- try {
13
- await syncTable.TestKit.tableExistsRuntime(syncTable.TestKit.createRuntime("sqlite", null as any, ""), "user");
14
- expect(true).toBe(false);
15
- } catch (error: any) {
16
- expect(error.message).toBe("SQL 执行器未初始化");
17
- }
18
- });
19
-
20
- test("mock sqlite:表存在返回 true;表不存在返回 false", async () => {
21
- const db = createMockSqliteDb({
22
- executedSql: [],
23
- tables: {
24
- test_sync_table_exists: {
25
- columns: {},
26
- indexes: {}
27
- }
28
- }
29
- });
30
-
31
- const runtime = syncTable.TestKit.createRuntime("sqlite", db as any, "");
32
- const exist = await syncTable.TestKit.tableExistsRuntime(runtime, "test_sync_table_exists");
33
- expect(exist).toBe(true);
34
-
35
- const notExist = await syncTable.TestKit.tableExistsRuntime(runtime, "test_sync_table_not_exist_12345");
36
- expect(notExist).toBe(false);
37
- });
38
- });
39
-
40
- describe("getTableColumnsRuntime", () => {
41
- test("mock sqlite:返回列信息结构(至少包含我们定义的列)", async () => {
42
- const db = createMockSqliteDb({
43
- executedSql: [],
44
- tables: {
45
- test_sync_table_columns: {
46
- columns: {
47
- id: { name: "id", type: "INTEGER", notnull: 1, dflt_value: null },
48
- user_name: { name: "user_name", type: "TEXT", notnull: 1, dflt_value: "''" },
49
- user_id: { name: "user_id", type: "INTEGER", notnull: 1, dflt_value: "0" },
50
- age: { name: "age", type: "INTEGER", notnull: 0, dflt_value: "0" },
51
- created_at: { name: "created_at", type: "INTEGER", notnull: 1, dflt_value: "0" }
52
- },
53
- indexes: {}
54
- }
55
- }
56
- });
57
-
58
- const runtime = syncTable.TestKit.createRuntime("sqlite", db as any, "");
59
- const columns = await syncTable.TestKit.getTableColumnsRuntime(runtime, "test_sync_table_columns");
60
-
61
- expect(columns.id).toBeDefined();
62
- expect(columns.user_name).toBeDefined();
63
- expect(columns.user_id).toBeDefined();
64
- expect(columns.age).toBeDefined();
65
- expect(columns.created_at).toBeDefined();
66
-
67
- expect(columns.user_name.nullable).toBe(false);
68
- });
69
- });
70
-
71
- describe("getTableIndexesRuntime", () => {
72
- test("mock sqlite:返回索引信息结构(仅单列索引;复合索引会被忽略)", async () => {
73
- const db = createMockSqliteDb({
74
- executedSql: [],
75
- tables: {
76
- test_sync_table_indexes: {
77
- columns: {},
78
- indexes: {
79
- idx_created_at: ["created_at"],
80
- idx_user_name: ["user_name"],
81
- idx_composite: ["user_id", "created_at"]
82
- }
83
- }
84
- }
85
- });
86
-
87
- const runtime = syncTable.TestKit.createRuntime("sqlite", db as any, "");
88
- const indexes = await syncTable.TestKit.getTableIndexesRuntime(runtime, "test_sync_table_indexes");
89
-
90
- expect(indexes.idx_created_at).toBeDefined();
91
- expect(indexes.idx_created_at).toContain("created_at");
92
-
93
- expect(indexes.idx_user_name).toBeDefined();
94
- expect(indexes.idx_user_name).toContain("user_name");
95
-
96
- // sqlite 路径下为了避免多列索引误判,仅收集单列索引
97
- expect(indexes.idx_composite).toBeUndefined();
98
- });
99
- });