swallowkit 1.0.0-beta.2 → 1.0.0-beta.20

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 (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +312 -215
  3. package/README.md +369 -216
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +10 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +444 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +21 -82
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +35 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +292 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +19 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +476 -117
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +13 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2627 -1708
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +3 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +617 -129
  39. package/dist/cli/commands/scaffold.js.map +1 -1
  40. package/dist/cli/index.d.ts +4 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +162 -42
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +8 -2
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +90 -4
  47. package/dist/core/config.js.map +1 -1
  48. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  49. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  50. package/dist/core/mock/connector-mock-server.js +480 -0
  51. package/dist/core/mock/connector-mock-server.js.map +1 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  53. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  54. package/dist/core/mock/zod-mock-generator.js +163 -0
  55. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  56. package/dist/core/operations/create-model.d.ts +15 -0
  57. package/dist/core/operations/create-model.d.ts.map +1 -0
  58. package/dist/core/operations/create-model.js +171 -0
  59. package/dist/core/operations/create-model.js.map +1 -0
  60. package/dist/core/operations/runtime.d.ts +32 -0
  61. package/dist/core/operations/runtime.d.ts.map +1 -0
  62. package/dist/core/operations/runtime.js +225 -0
  63. package/dist/core/operations/runtime.js.map +1 -0
  64. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  65. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  66. package/dist/core/operations/scaffold-machine.js +63 -0
  67. package/dist/core/operations/scaffold-machine.js.map +1 -0
  68. package/dist/core/project/manifest.d.ts +92 -0
  69. package/dist/core/project/manifest.d.ts.map +1 -0
  70. package/dist/core/project/manifest.js +321 -0
  71. package/dist/core/project/manifest.js.map +1 -0
  72. package/dist/core/project/validation.d.ts +20 -0
  73. package/dist/core/project/validation.d.ts.map +1 -0
  74. package/dist/core/project/validation.js +204 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1244 -0
  79. package/dist/core/scaffold/auth-generator.js.map +1 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  81. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  82. package/dist/core/scaffold/connector-functions-generator.js +1027 -0
  83. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  84. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  85. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  86. package/dist/core/scaffold/functions-generator.js +920 -213
  87. package/dist/core/scaffold/functions-generator.js.map +1 -1
  88. package/dist/core/scaffold/model-parser.d.ts +20 -1
  89. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  90. package/dist/core/scaffold/model-parser.js +329 -135
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  93. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  94. package/dist/core/scaffold/nextjs-generator.js +314 -182
  95. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  96. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  97. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  98. package/dist/core/scaffold/openapi-generator.js +190 -0
  99. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  100. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  101. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  102. package/dist/core/scaffold/ui-generator.js +768 -663
  103. package/dist/core/scaffold/ui-generator.js.map +1 -1
  104. package/dist/database/base-model.d.ts +3 -3
  105. package/dist/database/base-model.js +3 -3
  106. package/dist/index.d.ts +2 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +2 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/machine/contracts.d.ts +16 -0
  111. package/dist/machine/contracts.d.ts.map +1 -0
  112. package/dist/machine/contracts.js +3 -0
  113. package/dist/machine/contracts.js.map +1 -0
  114. package/dist/machine/errors.d.ts +11 -0
  115. package/dist/machine/errors.d.ts.map +1 -0
  116. package/dist/machine/errors.js +34 -0
  117. package/dist/machine/errors.js.map +1 -0
  118. package/dist/machine/index.d.ts +3 -0
  119. package/dist/machine/index.d.ts.map +1 -0
  120. package/dist/machine/index.js +156 -0
  121. package/dist/machine/index.js.map +1 -0
  122. package/dist/mcp/index.d.ts +25 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +184 -0
  125. package/dist/mcp/index.js.map +1 -0
  126. package/dist/types/index.d.ts +65 -0
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/utils/package-manager.d.ts +109 -0
  129. package/dist/utils/package-manager.d.ts.map +1 -0
  130. package/dist/utils/package-manager.js +215 -0
  131. package/dist/utils/package-manager.js.map +1 -0
  132. package/package.json +85 -73
  133. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  134. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  135. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  136. package/src/__tests__/auth.test.ts +654 -0
  137. package/src/__tests__/config.test.ts +263 -0
  138. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  139. package/src/__tests__/connector-mock-server.test.ts +439 -0
  140. package/src/__tests__/connector-model-bff.test.ts +162 -0
  141. package/src/__tests__/dev-seeds.test.ts +112 -0
  142. package/src/__tests__/dev.test.ts +148 -0
  143. package/src/__tests__/fixtures.ts +144 -0
  144. package/src/__tests__/functions-generator.test.ts +237 -0
  145. package/src/__tests__/init.test.ts +80 -0
  146. package/src/__tests__/machine.test.ts +212 -0
  147. package/src/__tests__/mcp.test.ts +56 -0
  148. package/src/__tests__/model-parser.test.ts +72 -0
  149. package/src/__tests__/nextjs-generator.test.ts +97 -0
  150. package/src/__tests__/openapi-generator.test.ts +43 -0
  151. package/src/__tests__/package-manager.test.ts +189 -0
  152. package/src/__tests__/scaffold.test.ts +39 -0
  153. package/src/__tests__/string-utils.test.ts +75 -0
  154. package/src/__tests__/ui-generator.test.ts +144 -0
  155. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  156. package/src/cli/commands/add-auth.ts +500 -0
  157. package/src/cli/commands/add-connector.ts +158 -0
  158. package/src/cli/commands/create-model.ts +62 -0
  159. package/src/cli/commands/dev-seeds.ts +358 -0
  160. package/src/cli/commands/dev.ts +962 -0
  161. package/src/cli/commands/index.ts +9 -0
  162. package/src/cli/commands/init.ts +3371 -0
  163. package/src/cli/commands/provision.ts +193 -0
  164. package/src/cli/commands/scaffold.ts +1211 -0
  165. package/src/cli/index.ts +191 -0
  166. package/src/core/config.ts +308 -0
  167. package/src/core/mock/connector-mock-server.ts +555 -0
  168. package/src/core/mock/zod-mock-generator.ts +205 -0
  169. package/src/core/operations/create-model.ts +174 -0
  170. package/src/core/operations/runtime.ts +235 -0
  171. package/src/core/operations/scaffold-machine.ts +91 -0
  172. package/src/core/project/manifest.ts +402 -0
  173. package/src/core/project/validation.ts +221 -0
  174. package/src/core/scaffold/auth-generator.ts +1284 -0
  175. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  176. package/src/core/scaffold/functions-generator.ts +970 -0
  177. package/src/core/scaffold/model-parser.ts +841 -0
  178. package/src/core/scaffold/nextjs-generator.ts +370 -0
  179. package/src/core/scaffold/openapi-generator.ts +212 -0
  180. package/src/core/scaffold/ui-generator.ts +1061 -0
  181. package/src/database/base-model.ts +184 -0
  182. package/src/database/client.ts +140 -0
  183. package/src/database/repository.ts +104 -0
  184. package/src/database/runtime-check.ts +25 -0
  185. package/src/index.ts +27 -0
  186. package/src/machine/contracts.ts +17 -0
  187. package/src/machine/errors.ts +34 -0
  188. package/src/machine/index.ts +173 -0
  189. package/src/mcp/index.ts +185 -0
  190. package/src/types/index.ts +134 -0
  191. package/src/utils/package-manager.ts +229 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * コネクタモックサーバーのテスト
3
+ */
4
+
5
+ import * as http from "http";
6
+ import { ConnectorMockServer } from "../core/mock/connector-mock-server";
7
+ import {
8
+ createRdbConnectorModelInfo,
9
+ createApiConnectorModelInfo,
10
+ } from "./fixtures";
11
+
12
+ // ─── Helpers ────────────────────────────────────────────────
13
+
14
+ function httpRequest(
15
+ port: number,
16
+ method: string,
17
+ path: string,
18
+ body?: unknown,
19
+ headers?: Record<string, string>
20
+ ): Promise<{ status: number; body: unknown }> {
21
+ return new Promise((resolve, reject) => {
22
+ const opts: http.RequestOptions = {
23
+ hostname: "localhost",
24
+ port,
25
+ path,
26
+ method,
27
+ headers: { "Content-Type": "application/json", ...headers },
28
+ };
29
+
30
+ const req = http.request(opts, (res) => {
31
+ let data = "";
32
+ res.on("data", (chunk) => (data += chunk));
33
+ res.on("end", () => {
34
+ try {
35
+ resolve({
36
+ status: res.statusCode || 0,
37
+ body: data ? JSON.parse(data) : null,
38
+ });
39
+ } catch {
40
+ resolve({ status: res.statusCode || 0, body: data });
41
+ }
42
+ });
43
+ });
44
+
45
+ req.on("error", reject);
46
+
47
+ if (body) {
48
+ req.write(JSON.stringify(body));
49
+ }
50
+ req.end();
51
+ });
52
+ }
53
+
54
+ // ─── Tests ──────────────────────────────────────────────────
55
+
56
+ describe("ConnectorMockServer", () => {
57
+ let server: ConnectorMockServer;
58
+ const TEST_PORT = 19876; // Unlikely to conflict
59
+
60
+ afterEach(async () => {
61
+ if (server) {
62
+ await server.stop();
63
+ }
64
+ });
65
+
66
+ it("starts and stops without errors", async () => {
67
+ server = new ConnectorMockServer({
68
+ port: TEST_PORT,
69
+ functionsTarget: "localhost:7071",
70
+ connectorModels: [createRdbConnectorModelInfo()],
71
+ mockCount: 2,
72
+ });
73
+
74
+ await server.start();
75
+ await server.stop();
76
+ });
77
+
78
+ it("serves GET /api/<model> with mock data", async () => {
79
+ server = new ConnectorMockServer({
80
+ port: TEST_PORT,
81
+ functionsTarget: "localhost:7071",
82
+ connectorModels: [createRdbConnectorModelInfo()],
83
+ mockCount: 3,
84
+ });
85
+
86
+ await server.start();
87
+
88
+ const { status, body } = await httpRequest(TEST_PORT, "GET", "/api/user");
89
+ expect(status).toBe(200);
90
+ expect(Array.isArray(body)).toBe(true);
91
+ expect((body as any[]).length).toBe(3);
92
+ expect((body as any[])[0]).toHaveProperty("id");
93
+ expect((body as any[])[0]).toHaveProperty("email");
94
+ });
95
+
96
+ it("serves GET /api/<model>/<id> for single item", async () => {
97
+ server = new ConnectorMockServer({
98
+ port: TEST_PORT,
99
+ functionsTarget: "localhost:7071",
100
+ connectorModels: [createRdbConnectorModelInfo()],
101
+ mockCount: 3,
102
+ });
103
+
104
+ await server.start();
105
+
106
+ const { status, body } = await httpRequest(TEST_PORT, "GET", "/api/user/user-001");
107
+ expect(status).toBe(200);
108
+ expect((body as any).id).toBe("user-001");
109
+ });
110
+
111
+ it("returns 404 for non-existent item", async () => {
112
+ server = new ConnectorMockServer({
113
+ port: TEST_PORT,
114
+ functionsTarget: "localhost:7071",
115
+ connectorModels: [createRdbConnectorModelInfo()],
116
+ mockCount: 2,
117
+ });
118
+
119
+ await server.start();
120
+
121
+ const { status } = await httpRequest(TEST_PORT, "GET", "/api/user/nonexistent");
122
+ expect(status).toBe(404);
123
+ });
124
+
125
+ it("returns 405 for write operations on read-only connector", async () => {
126
+ // RDB connector model has operations: ["getAll", "getById"] (read-only)
127
+ server = new ConnectorMockServer({
128
+ port: TEST_PORT,
129
+ functionsTarget: "localhost:7071",
130
+ connectorModels: [createRdbConnectorModelInfo()],
131
+ mockCount: 2,
132
+ });
133
+
134
+ await server.start();
135
+
136
+ const { status } = await httpRequest(TEST_PORT, "POST", "/api/user", { name: "New" });
137
+ expect(status).toBe(405);
138
+ });
139
+
140
+ it("supports POST for read-write connector models", async () => {
141
+ // API connector model has operations: ["getAll", "getById", "create", "update"]
142
+ server = new ConnectorMockServer({
143
+ port: TEST_PORT,
144
+ functionsTarget: "localhost:7071",
145
+ connectorModels: [createApiConnectorModelInfo()],
146
+ mockCount: 0,
147
+ });
148
+
149
+ await server.start();
150
+
151
+ const { status, body } = await httpRequest(TEST_PORT, "POST", "/api/backlogIssue", {
152
+ summary: "Test issue",
153
+ projectId: "proj-1",
154
+ issueKey: "TEST-001",
155
+ });
156
+ expect(status).toBe(201);
157
+ expect((body as any).summary).toBe("Test issue");
158
+ expect((body as any).id).toBeDefined();
159
+
160
+ // Verify it was stored
161
+ const { body: allBody } = await httpRequest(TEST_PORT, "GET", "/api/backlogIssue");
162
+ expect((allBody as any[]).length).toBe(1);
163
+ });
164
+
165
+ it("supports PUT for update operations", async () => {
166
+ server = new ConnectorMockServer({
167
+ port: TEST_PORT,
168
+ functionsTarget: "localhost:7071",
169
+ connectorModels: [createApiConnectorModelInfo()],
170
+ mockCount: 2,
171
+ });
172
+
173
+ await server.start();
174
+
175
+ const store = server.getStore("BacklogIssue");
176
+ const firstId = store[0].id as string;
177
+
178
+ const { status, body } = await httpRequest(TEST_PORT, "PUT", `/api/backlogIssue/${firstId}`, {
179
+ summary: "Updated summary",
180
+ });
181
+ expect(status).toBe(200);
182
+ expect((body as any).summary).toBe("Updated summary");
183
+ expect((body as any).id).toBe(firstId);
184
+ });
185
+
186
+ it("returns 405 for DELETE when not in operations", async () => {
187
+ // API connector model doesn't include "delete" in operations
188
+ server = new ConnectorMockServer({
189
+ port: TEST_PORT,
190
+ functionsTarget: "localhost:7071",
191
+ connectorModels: [createApiConnectorModelInfo()],
192
+ mockCount: 2,
193
+ });
194
+
195
+ await server.start();
196
+
197
+ const store = server.getStore("BacklogIssue");
198
+ const firstId = store[0].id as string;
199
+
200
+ const { status } = await httpRequest(TEST_PORT, "DELETE", `/api/backlogIssue/${firstId}`);
201
+ expect(status).toBe(405);
202
+ });
203
+
204
+ it("proxies non-connector routes to Functions target (returns 502 when Functions not running)", async () => {
205
+ server = new ConnectorMockServer({
206
+ port: TEST_PORT,
207
+ functionsTarget: "localhost:19877", // No server on this port
208
+ connectorModels: [createRdbConnectorModelInfo()],
209
+ mockCount: 1,
210
+ });
211
+
212
+ await server.start();
213
+
214
+ // /api/todo is NOT a connector model route, so it should be proxied
215
+ const { status, body } = await httpRequest(TEST_PORT, "GET", "/api/todo");
216
+ expect(status).toBe(502);
217
+ expect((body as any).error).toContain("not available");
218
+ });
219
+
220
+ it("handles multiple connector models simultaneously", async () => {
221
+ server = new ConnectorMockServer({
222
+ port: TEST_PORT,
223
+ functionsTarget: "localhost:7071",
224
+ connectorModels: [createRdbConnectorModelInfo(), createApiConnectorModelInfo()],
225
+ mockCount: 2,
226
+ });
227
+
228
+ await server.start();
229
+
230
+ const usersRes = await httpRequest(TEST_PORT, "GET", "/api/user");
231
+ expect(usersRes.status).toBe(200);
232
+ expect((usersRes.body as any[]).length).toBe(2);
233
+
234
+ const issuesRes = await httpRequest(TEST_PORT, "GET", "/api/backlogIssue");
235
+ expect(issuesRes.status).toBe(200);
236
+ expect((issuesRes.body as any[]).length).toBe(2);
237
+ });
238
+
239
+ it("getStore returns current data for a model", async () => {
240
+ server = new ConnectorMockServer({
241
+ port: TEST_PORT,
242
+ functionsTarget: "localhost:7071",
243
+ connectorModels: [createRdbConnectorModelInfo()],
244
+ mockCount: 3,
245
+ });
246
+
247
+ await server.start();
248
+
249
+ const store = server.getStore("User");
250
+ expect(store.length).toBe(3);
251
+ expect(store[0].id).toBe("user-001");
252
+ });
253
+ });
254
+
255
+ // ============================================================
256
+ // Mock Auth Endpoints
257
+ // ============================================================
258
+ describe("ConnectorMockServer - Auth Endpoints", () => {
259
+ let server: ConnectorMockServer;
260
+ const AUTH_PORT = 19877;
261
+ const JWT_SECRET = "test-jwt-secret-for-mock-auth-tests";
262
+
263
+ // Auth-compatible User model (has loginId, password, roles fields)
264
+ const authUserModel = createRdbConnectorModelInfo({
265
+ name: "User",
266
+ displayName: "User",
267
+ schemaName: "userSchema",
268
+ filePath: "/models/user.ts",
269
+ fields: [
270
+ { name: "id", type: "string", isOptional: false, isArray: false },
271
+ { name: "loginId", type: "string", isOptional: false, isArray: false },
272
+ { name: "password", type: "string", isOptional: false, isArray: false },
273
+ { name: "name", type: "string", isOptional: false, isArray: false },
274
+ { name: "email", type: "string", isOptional: false, isArray: false },
275
+ { name: "roles", type: "string", isOptional: false, isArray: true },
276
+ ],
277
+ connectorConfig: {
278
+ connector: "mysql",
279
+ operations: ["getAll", "getById"],
280
+ table: "users",
281
+ idColumn: "id",
282
+ },
283
+ });
284
+
285
+ const testUsers = [
286
+ { id: "1", loginId: "admin", password: "password123", name: "Admin User", email: "admin@example.com", roles: ["admin"] },
287
+ { id: "2", loginId: "user", password: "password123", name: "Test User", email: "user@example.com", roles: ["user"] },
288
+ ];
289
+
290
+ afterEach(async () => {
291
+ if (server) {
292
+ await server.stop();
293
+ }
294
+ });
295
+
296
+ /** Start mock server with auth-compatible User model and seed data */
297
+ async function startAuthServer() {
298
+ server = new ConnectorMockServer({
299
+ port: AUTH_PORT,
300
+ functionsTarget: "localhost:7071",
301
+ connectorModels: [authUserModel],
302
+ mockCount: 0,
303
+ authConfig: {
304
+ jwtSecret: JWT_SECRET,
305
+ tokenExpiry: "1h",
306
+ customJwt: {
307
+ userTable: "users",
308
+ loginIdColumn: "loginId",
309
+ passwordHashColumn: "password",
310
+ rolesColumn: "roles",
311
+ },
312
+ },
313
+ });
314
+ await server.start();
315
+ // Populate user store with known test data
316
+ const store = server.getStore("User");
317
+ store.push(...testUsers);
318
+ }
319
+
320
+ it("handles POST /api/auth/login with users from RDB mock store", async () => {
321
+ await startAuthServer();
322
+
323
+ const res = await httpRequest(AUTH_PORT, "POST", "/api/auth/login", {
324
+ loginId: "admin",
325
+ password: "password123",
326
+ });
327
+
328
+ expect(res.status).toBe(200);
329
+ const body = res.body as any;
330
+ expect(body.user).toBeDefined();
331
+ expect(body.user.loginId).toBe("admin");
332
+ expect(body.user.roles).toContain("admin");
333
+ expect(body.token).toBeDefined();
334
+ expect(typeof body.token).toBe("string");
335
+ expect(body.expiresAt).toBeDefined();
336
+ });
337
+
338
+ it("returns 401 for invalid credentials", async () => {
339
+ await startAuthServer();
340
+
341
+ const res = await httpRequest(AUTH_PORT, "POST", "/api/auth/login", {
342
+ loginId: "admin",
343
+ password: "wrong-password",
344
+ });
345
+
346
+ expect(res.status).toBe(401);
347
+ });
348
+
349
+ it("returns 401 for non-existent user", async () => {
350
+ await startAuthServer();
351
+
352
+ const res = await httpRequest(AUTH_PORT, "POST", "/api/auth/login", {
353
+ loginId: "nobody",
354
+ password: "password123",
355
+ });
356
+
357
+ expect(res.status).toBe(401);
358
+ });
359
+
360
+ it("handles GET /api/auth/me with valid JWT", async () => {
361
+ await startAuthServer();
362
+
363
+ // Login first
364
+ const loginRes = await httpRequest(AUTH_PORT, "POST", "/api/auth/login", {
365
+ loginId: "admin",
366
+ password: "password123",
367
+ });
368
+ const token = (loginRes.body as any).token;
369
+
370
+ // Then call /me
371
+ const meRes = await httpRequest(AUTH_PORT, "GET", "/api/auth/me", undefined, {
372
+ Authorization: `Bearer ${token}`,
373
+ });
374
+
375
+ expect(meRes.status).toBe(200);
376
+ const meBody = meRes.body as any;
377
+ expect(meBody.loginId).toBe("admin");
378
+ expect(meBody.roles).toContain("admin");
379
+ });
380
+
381
+ it("returns 401 for /api/auth/me without token", async () => {
382
+ await startAuthServer();
383
+
384
+ const res = await httpRequest(AUTH_PORT, "GET", "/api/auth/me");
385
+ expect(res.status).toBe(401);
386
+ });
387
+
388
+ it("handles POST /api/auth/logout", async () => {
389
+ await startAuthServer();
390
+
391
+ const res = await httpRequest(AUTH_PORT, "POST", "/api/auth/logout");
392
+ expect(res.status).toBe(200);
393
+ expect((res.body as any).message).toBe("Logged out");
394
+ });
395
+
396
+ it("does not intercept auth routes when authConfig is not set", async () => {
397
+ // Without authConfig, auth routes should be proxied (which will fail since no real Functions)
398
+ server = new ConnectorMockServer({
399
+ port: AUTH_PORT,
400
+ functionsTarget: "localhost:19999", // non-existent to trigger proxy error
401
+ connectorModels: [],
402
+ });
403
+ await server.start();
404
+
405
+ const res = await httpRequest(AUTH_PORT, "POST", "/api/auth/login", {
406
+ loginId: "admin",
407
+ password: "password123",
408
+ });
409
+
410
+ // Should get 502 (proxy error) since auth is not handled by mock
411
+ expect(res.status).toBe(502);
412
+ });
413
+
414
+ it("returns 500 when no user model matches the configured userTable", async () => {
415
+ server = new ConnectorMockServer({
416
+ port: AUTH_PORT,
417
+ functionsTarget: "localhost:7071",
418
+ connectorModels: [], // no models at all
419
+ authConfig: {
420
+ jwtSecret: JWT_SECRET,
421
+ customJwt: {
422
+ userTable: "users",
423
+ loginIdColumn: "loginId",
424
+ passwordHashColumn: "password",
425
+ rolesColumn: "roles",
426
+ },
427
+ },
428
+ });
429
+ await server.start();
430
+
431
+ const res = await httpRequest(AUTH_PORT, "POST", "/api/auth/login", {
432
+ loginId: "admin",
433
+ password: "password123",
434
+ });
435
+
436
+ expect(res.status).toBe(500);
437
+ expect((res.body as any).error).toContain("No user model found");
438
+ });
439
+ });
@@ -0,0 +1,162 @@
1
+ /**
2
+ * コネクタ関連のモデルパーサーおよびBFFジェネレーターのテスト
3
+ */
4
+
5
+ import { parseConnectorConfig } from "../core/scaffold/model-parser";
6
+ import { generateConnectorBFFRoutes } from "../core/scaffold/nextjs-generator";
7
+ import {
8
+ createRdbConnectorModelInfo,
9
+ createApiConnectorModelInfo,
10
+ } from "./fixtures";
11
+
12
+ // ─── parseConnectorConfig ───────────────────────────────────
13
+
14
+ describe("parseConnectorConfig", () => {
15
+ it("parses RDB connector config from model content", () => {
16
+ const content = `
17
+ import { z } from 'zod/v4';
18
+ export const User = z.object({ id: z.string(), name: z.string() });
19
+ export type User = z.infer<typeof User>;
20
+ export const displayName = 'User';
21
+
22
+ export const connectorConfig = {
23
+ connector: 'mysql',
24
+ operations: ['getAll', 'getById'] as const,
25
+ table: 'users',
26
+ idColumn: 'id',
27
+ };
28
+ `;
29
+ const result = parseConnectorConfig(content);
30
+ expect(result).toBeDefined();
31
+ expect(result!.connector).toBe("mysql");
32
+ expect(result!.operations).toEqual(["getAll", "getById"]);
33
+ expect((result as any).table).toBe("users");
34
+ expect((result as any).idColumn).toBe("id");
35
+ });
36
+
37
+ it("parses API connector config from model content", () => {
38
+ const content = `
39
+ export const connectorConfig = {
40
+ connector: 'backlog',
41
+ operations: ['getAll', 'getById', 'create', 'update'] as const,
42
+ endpoints: {
43
+ getAll: 'GET /issues',
44
+ getById: 'GET /issues/{id}',
45
+ create: 'POST /issues',
46
+ update: 'PATCH /issues/{id}',
47
+ },
48
+ };
49
+ `;
50
+ const result = parseConnectorConfig(content);
51
+ expect(result).toBeDefined();
52
+ expect(result!.connector).toBe("backlog");
53
+ expect(result!.operations).toEqual(["getAll", "getById", "create", "update"]);
54
+ expect((result as any).endpoints).toEqual({
55
+ getAll: "GET /issues",
56
+ getById: "GET /issues/{id}",
57
+ create: "POST /issues",
58
+ update: "PATCH /issues/{id}",
59
+ });
60
+ });
61
+
62
+ it("returns undefined when no connectorConfig export exists", () => {
63
+ const content = `
64
+ export const Todo = z.object({ id: z.string() });
65
+ export type Todo = z.infer<typeof Todo>;
66
+ export const displayName = 'Todo';
67
+ `;
68
+ const result = parseConnectorConfig(content);
69
+ expect(result).toBeUndefined();
70
+ });
71
+
72
+ it("returns undefined when connector name is missing", () => {
73
+ const content = `
74
+ export const connectorConfig = {
75
+ operations: ['getAll'],
76
+ };
77
+ `;
78
+ const result = parseConnectorConfig(content);
79
+ expect(result).toBeUndefined();
80
+ });
81
+
82
+ it("handles single quotes and double quotes", () => {
83
+ const content = `
84
+ export const connectorConfig = {
85
+ connector: "postgres",
86
+ operations: ["getAll", "getById"] as const,
87
+ table: "items",
88
+ };
89
+ `;
90
+ const result = parseConnectorConfig(content);
91
+ expect(result).toBeDefined();
92
+ expect(result!.connector).toBe("postgres");
93
+ expect((result as any).table).toBe("items");
94
+ });
95
+
96
+ it("handles API connector without endpoints", () => {
97
+ const content = `
98
+ export const connectorConfig = {
99
+ connector: 'external-api',
100
+ operations: ['getAll'] as const,
101
+ };
102
+ `;
103
+ const result = parseConnectorConfig(content);
104
+ expect(result).toBeDefined();
105
+ expect(result!.connector).toBe("external-api");
106
+ expect(result!.operations).toEqual(["getAll"]);
107
+ expect((result as any).table).toBeUndefined();
108
+ expect((result as any).endpoints).toBeUndefined();
109
+ });
110
+ });
111
+
112
+ // ─── generateConnectorBFFRoutes ─────────────────────────────
113
+
114
+ describe("generateConnectorBFFRoutes", () => {
115
+ it("generates read-only BFF routes for RDB connector", () => {
116
+ const model = createRdbConnectorModelInfo();
117
+ const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById"]);
118
+
119
+ expect(routes.listRoute).toContain("export async function GET");
120
+ expect(routes.listRoute).toContain("/api/user");
121
+ expect(routes.listRoute).not.toContain("export async function POST");
122
+
123
+ expect(routes.detailRoute).toContain("export async function GET");
124
+ expect(routes.detailRoute).not.toContain("export async function PUT");
125
+ expect(routes.detailRoute).not.toContain("export async function DELETE");
126
+ });
127
+
128
+ it("generates read-write BFF routes for API connector", () => {
129
+ const model = createApiConnectorModelInfo();
130
+ const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById", "create", "update"]);
131
+
132
+ expect(routes.listRoute).toContain("export async function GET");
133
+ expect(routes.listRoute).toContain("export async function POST");
134
+
135
+ expect(routes.detailRoute).toContain("export async function GET");
136
+ expect(routes.detailRoute).toContain("export async function PUT");
137
+ });
138
+
139
+ it("does not generate DELETE route when delete is not in operations", () => {
140
+ const model = createApiConnectorModelInfo();
141
+ const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById", "create", "update"]);
142
+
143
+ expect(routes.detailRoute).not.toContain("export async function DELETE");
144
+ });
145
+
146
+ it("uses correct route paths based on model name", () => {
147
+ const model = createApiConnectorModelInfo();
148
+ const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById"]);
149
+
150
+ // BacklogIssue → backlogIssue route
151
+ expect(routes.listRoute).toContain("backlogIssue");
152
+ expect(routes.detailRoute).toContain("backlogIssue");
153
+ });
154
+
155
+ it("imports from shared package", () => {
156
+ const model = createRdbConnectorModelInfo();
157
+ const routes = generateConnectorBFFRoutes(model, "@myapp/shared", ["getAll", "getById"]);
158
+
159
+ expect(routes.listRoute).toContain("@myapp/shared");
160
+ expect(routes.detailRoute).toContain("@myapp/shared");
161
+ });
162
+ });