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