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,654 @@
1
+ import {
2
+ generateAuthModels,
3
+ generateAuthFunctionsTS,
4
+ generateJwtHelperTS,
5
+ generateAuthFunctionsCSharp,
6
+ generateJwtHelperCSharp,
7
+ generateAuthFunctionsPython,
8
+ generateJwtHelperPython,
9
+ generateBFFAuthLoginRoute,
10
+ generateBFFAuthLogoutRoute,
11
+ generateBFFAuthMeRoute,
12
+ generateProxy,
13
+ generateLoginPage,
14
+ generateAuthContext,
15
+ generateBFFCallFunctionWithAuth,
16
+ generateAuthImportTS,
17
+ generateAuthGuardTS,
18
+ generateAuthGuardCSharp,
19
+ generateAuthGuardPython,
20
+ } from "../core/scaffold/auth-generator";
21
+ import { generateCompactAzureFunctionsCRUD } from "../core/scaffold/functions-generator";
22
+ import { parseAuthPolicy } from "../core/scaffold/model-parser";
23
+ import { createBasicModelInfo } from "./fixtures";
24
+ import { ModelAuthPolicy, CustomJwtConfig } from "../types";
25
+
26
+ const defaultJwtConfig: CustomJwtConfig = {
27
+ userConnector: "mysql",
28
+ userTable: "users",
29
+ loginIdColumn: "login_id",
30
+ passwordHashColumn: "password_hash",
31
+ rolesColumn: "roles",
32
+ jwtSecretEnv: "JWT_SECRET",
33
+ tokenExpiry: "24h",
34
+ };
35
+
36
+ const sharedPkg = "@myapp/shared";
37
+
38
+ // ============================================================
39
+ // auth-generator: Shared models
40
+ // ============================================================
41
+ describe("generateAuthModels", () => {
42
+ it("generates LoginRequest, AuthUser, LoginResponse Zod schemas", () => {
43
+ const code = generateAuthModels();
44
+ expect(code).toContain("LoginRequest");
45
+ expect(code).toContain("AuthUser");
46
+ expect(code).toContain("LoginResponse");
47
+ expect(code).toContain("z.object");
48
+ expect(code).toContain("loginId");
49
+ expect(code).toContain("password");
50
+ expect(code).toContain("roles");
51
+ });
52
+
53
+ it("exports type aliases for each schema", () => {
54
+ const code = generateAuthModels();
55
+ expect(code).toContain("export type LoginRequest");
56
+ expect(code).toContain("export type AuthUser");
57
+ expect(code).toContain("export type LoginResponse");
58
+ });
59
+
60
+ it("does NOT contain passwordHash or backlogApiKey", () => {
61
+ const code = generateAuthModels();
62
+ expect(code).not.toContain("passwordHash");
63
+ expect(code).not.toContain("backlogApiKey");
64
+ });
65
+ });
66
+
67
+ // ============================================================
68
+ // auth-generator: TypeScript Functions
69
+ // ============================================================
70
+ describe("generateAuthFunctionsTS", () => {
71
+ it("generates login, me, logout endpoints", () => {
72
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "mysql");
73
+ expect(code).toContain("auth-login");
74
+ expect(code).toContain("auth-me");
75
+ expect(code).toContain("auth-logout");
76
+ });
77
+
78
+ it("uses bcrypt for password verification", () => {
79
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "mysql");
80
+ expect(code).toContain("bcrypt");
81
+ expect(code).toContain("compare");
82
+ });
83
+
84
+ it("uses JWT for token generation", () => {
85
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "mysql");
86
+ expect(code).toContain("generateToken");
87
+ });
88
+
89
+ it("queries MySQL for user authentication", () => {
90
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "mysql");
91
+ expect(code).toContain("mysql2");
92
+ });
93
+ });
94
+
95
+ describe("generateJwtHelperTS", () => {
96
+ it("exports requireAuth, requireRoles, generateToken, handleAuthError", () => {
97
+ const code = generateJwtHelperTS();
98
+ expect(code).toContain("export function requireAuth");
99
+ expect(code).toContain("export function requireRoles");
100
+ expect(code).toContain("export function generateToken");
101
+ expect(code).toContain("export function handleAuthError");
102
+ });
103
+
104
+ it("exports AuthError class", () => {
105
+ const code = generateJwtHelperTS();
106
+ expect(code).toContain("export class AuthError");
107
+ });
108
+
109
+ it("exports JwtPayload interface", () => {
110
+ const code = generateJwtHelperTS();
111
+ expect(code).toContain("interface JwtPayload");
112
+ });
113
+
114
+ it("validates Authorization Bearer header", () => {
115
+ const code = generateJwtHelperTS();
116
+ expect(code).toContain("Bearer");
117
+ expect(code).toContain("authorization");
118
+ });
119
+
120
+ it("uses JWT_SECRET from environment", () => {
121
+ const code = generateJwtHelperTS();
122
+ expect(code).toContain("JWT_SECRET");
123
+ });
124
+ });
125
+
126
+ // ============================================================
127
+ // auth-generator: C# Functions
128
+ // ============================================================
129
+ describe("generateAuthFunctionsCSharp", () => {
130
+ it("generates login, me, logout endpoints", () => {
131
+ const code = generateAuthFunctionsCSharp(defaultJwtConfig, "mysql");
132
+ expect(code).toContain("auth-login");
133
+ expect(code).toContain("auth-me");
134
+ expect(code).toContain("auth-logout");
135
+ });
136
+
137
+ it("uses BCrypt for password verification", () => {
138
+ const code = generateAuthFunctionsCSharp(defaultJwtConfig, "mysql");
139
+ expect(code).toContain("BCrypt");
140
+ expect(code).toContain("Verify");
141
+ });
142
+ });
143
+
144
+ describe("generateJwtHelperCSharp", () => {
145
+ it("exports Authorize method", () => {
146
+ const code = generateJwtHelperCSharp();
147
+ expect(code).toContain("Authorize");
148
+ });
149
+
150
+ it("uses System.IdentityModel.Tokens.Jwt", () => {
151
+ const code = generateJwtHelperCSharp();
152
+ expect(code).toContain("System.IdentityModel.Tokens.Jwt");
153
+ });
154
+ });
155
+
156
+ // ============================================================
157
+ // auth-generator: Python Functions
158
+ // ============================================================
159
+ describe("generateAuthFunctionsPython", () => {
160
+ it("generates login, me, logout endpoints", () => {
161
+ const code = generateAuthFunctionsPython(defaultJwtConfig, "mysql");
162
+ expect(code).toContain("auth/login");
163
+ expect(code).toContain("auth/me");
164
+ expect(code).toContain("auth/logout");
165
+ });
166
+
167
+ it("uses bcrypt for password verification", () => {
168
+ const code = generateAuthFunctionsPython(defaultJwtConfig, "mysql");
169
+ expect(code).toContain("bcrypt");
170
+ });
171
+ });
172
+
173
+ describe("generateJwtHelperPython", () => {
174
+ it("exports require_auth, require_roles, generate_token", () => {
175
+ const code = generateJwtHelperPython();
176
+ expect(code).toContain("def require_auth");
177
+ expect(code).toContain("def require_roles");
178
+ expect(code).toContain("def generate_token");
179
+ });
180
+
181
+ it("uses PyJWT library", () => {
182
+ const code = generateJwtHelperPython();
183
+ expect(code).toContain("import jwt");
184
+ });
185
+ });
186
+
187
+ // ============================================================
188
+ // auth-generator: BFF routes
189
+ // ============================================================
190
+ describe("generateBFFAuthLoginRoute", () => {
191
+ it("generates POST handler for login", () => {
192
+ const code = generateBFFAuthLoginRoute("test-project", sharedPkg);
193
+ expect(code).toContain("POST");
194
+ expect(code).toContain("FUNCTIONS_BASE_URL");
195
+ });
196
+
197
+ it("sets httpOnly cookie with project-derived name", () => {
198
+ const code = generateBFFAuthLoginRoute("test-project", sharedPkg);
199
+ expect(code).toContain("test-project-auth-token");
200
+ expect(code).toContain("httpOnly: true");
201
+ expect(code).toContain("sameSite: 'lax'");
202
+ });
203
+
204
+ it("sanitizes scoped package names for cookie", () => {
205
+ const code = generateBFFAuthLoginRoute("@scope/my-app", sharedPkg);
206
+ expect(code).toContain("my-app-auth-token");
207
+ expect(code).not.toContain("@scope");
208
+ });
209
+ });
210
+
211
+ describe("generateBFFAuthLogoutRoute", () => {
212
+ it("generates POST handler for logout", () => {
213
+ const code = generateBFFAuthLogoutRoute("test-project");
214
+ expect(code).toContain("POST");
215
+ });
216
+
217
+ it("deletes auth cookie", () => {
218
+ const code = generateBFFAuthLogoutRoute("test-project");
219
+ expect(code).toContain("delete");
220
+ expect(code).toContain("test-project-auth-token");
221
+ });
222
+ });
223
+
224
+ describe("generateBFFAuthMeRoute", () => {
225
+ it("generates GET handler for current user", () => {
226
+ const code = generateBFFAuthMeRoute(sharedPkg);
227
+ expect(code).toContain("GET");
228
+ expect(code).toContain("FUNCTIONS_BASE_URL");
229
+ });
230
+ });
231
+
232
+ // ============================================================
233
+ // auth-generator: Proxy (formerly Middleware)
234
+ // ============================================================
235
+ describe("generateProxy", () => {
236
+ it("checks for auth cookie", () => {
237
+ const code = generateProxy("test-project");
238
+ expect(code).toContain("test-project-auth-token");
239
+ expect(code).toContain("cookies.get");
240
+ });
241
+
242
+ it("redirects to /login for unauthenticated page requests", () => {
243
+ const code = generateProxy("test-project");
244
+ expect(code).toContain("/login");
245
+ expect(code).toContain("redirect");
246
+ });
247
+
248
+ it("returns 401 for unauthenticated API requests", () => {
249
+ const code = generateProxy("test-project");
250
+ expect(code).toContain("401");
251
+ expect(code).toContain("Unauthorized");
252
+ });
253
+
254
+ it("injects Authorization header from cookie", () => {
255
+ const code = generateProxy("test-project");
256
+ expect(code).toContain("Authorization");
257
+ expect(code).toContain("Bearer");
258
+ });
259
+
260
+ it("skips public paths", () => {
261
+ const code = generateProxy("test-project");
262
+ expect(code).toContain("/login");
263
+ expect(code).toContain("/api/auth/login");
264
+ });
265
+
266
+ it("checks JWT expiry (base64 decode, no signature verification)", () => {
267
+ const code = generateProxy("test-project");
268
+ expect(code).toContain("atob");
269
+ expect(code).toContain("exp");
270
+ });
271
+
272
+ it("exports proxy function instead of middleware", () => {
273
+ const code = generateProxy("test-project");
274
+ expect(code).toContain("export function proxy");
275
+ expect(code).not.toContain("export function middleware");
276
+ });
277
+ });
278
+
279
+ // ============================================================
280
+ // auth-generator: Frontend
281
+ // ============================================================
282
+ describe("generateLoginPage", () => {
283
+ it("generates a React login form component", () => {
284
+ const code = generateLoginPage();
285
+ expect(code).toContain("use client");
286
+ expect(code).toContain("loginId");
287
+ expect(code).toContain("password");
288
+ expect(code).toContain("handleSubmit");
289
+ expect(code).toContain("/api/auth/login");
290
+ });
291
+ });
292
+
293
+ describe("generateAuthContext", () => {
294
+ it("generates React context with useAuth hook", () => {
295
+ const code = generateAuthContext();
296
+ expect(code).toContain("useAuth");
297
+ expect(code).toContain("AuthProvider");
298
+ expect(code).toContain("createContext");
299
+ });
300
+
301
+ it("provides login, logout, user state", () => {
302
+ const code = generateAuthContext();
303
+ expect(code).toContain("login");
304
+ expect(code).toContain("logout");
305
+ expect(code).toContain("user");
306
+ });
307
+ });
308
+
309
+ // ============================================================
310
+ // auth-generator: callFunction with auth
311
+ // ============================================================
312
+ describe("generateBFFCallFunctionWithAuth", () => {
313
+ it("imports headers from next/headers", () => {
314
+ const code = generateBFFCallFunctionWithAuth();
315
+ expect(code).toContain("import { headers } from 'next/headers'");
316
+ });
317
+
318
+ it("forwards Authorization header", () => {
319
+ const code = generateBFFCallFunctionWithAuth();
320
+ expect(code).toContain("Authorization");
321
+ expect(code).toContain("authorization");
322
+ });
323
+
324
+ it("handles headers() unavailable gracefully", () => {
325
+ const code = generateBFFCallFunctionWithAuth();
326
+ expect(code).toContain("catch");
327
+ });
328
+ });
329
+
330
+ // ============================================================
331
+ // auth-generator: Scaffold helpers
332
+ // ============================================================
333
+ describe("generateAuthImportTS", () => {
334
+ it("generates import from ./auth/jwt-helper", () => {
335
+ const code = generateAuthImportTS();
336
+ expect(code).toContain("requireAuth");
337
+ expect(code).toContain("handleAuthError");
338
+ expect(code).toContain("./auth/jwt-helper");
339
+ });
340
+ });
341
+
342
+ describe("generateAuthGuardTS", () => {
343
+ it("generates authentication-only guard for empty policy", () => {
344
+ const policy: ModelAuthPolicy = {};
345
+ const readGuard = generateAuthGuardTS(policy, "read");
346
+ expect(readGuard).toContain("requireAuth");
347
+ expect(readGuard).not.toContain("requireRoles");
348
+ });
349
+
350
+ it("generates role guard for policy with roles", () => {
351
+ const policy: ModelAuthPolicy = { roles: ["admin", "editor"] };
352
+ const guard = generateAuthGuardTS(policy, "read");
353
+ expect(guard).toContain("requireAuth");
354
+ expect(guard).toContain("requireRoles");
355
+ expect(guard).toContain("admin");
356
+ expect(guard).toContain("editor");
357
+ });
358
+
359
+ it("uses read roles for read operations", () => {
360
+ const policy: ModelAuthPolicy = {
361
+ read: ["viewer", "admin"],
362
+ write: ["admin"],
363
+ };
364
+ const guard = generateAuthGuardTS(policy, "read");
365
+ expect(guard).toContain("viewer");
366
+ expect(guard).toContain("admin");
367
+ expect(guard).not.toContain("'admin'"); // It should have admin as part of roles array
368
+ });
369
+
370
+ it("uses write roles for write operations", () => {
371
+ const policy: ModelAuthPolicy = {
372
+ read: ["viewer", "admin"],
373
+ write: ["admin"],
374
+ };
375
+ const guard = generateAuthGuardTS(policy, "write");
376
+ expect(guard).toContain("admin");
377
+ expect(guard).not.toContain("viewer");
378
+ });
379
+
380
+ it("falls back to roles when read/write not specified", () => {
381
+ const policy: ModelAuthPolicy = { roles: ["admin"] };
382
+ const readGuard = generateAuthGuardTS(policy, "read");
383
+ const writeGuard = generateAuthGuardTS(policy, "write");
384
+ expect(readGuard).toContain("admin");
385
+ expect(writeGuard).toContain("admin");
386
+ });
387
+ });
388
+
389
+ describe("generateAuthGuardCSharp", () => {
390
+ it("generates JwtHelper.Authorize call for C# with roles", () => {
391
+ const policy: ModelAuthPolicy = { roles: ["admin"] };
392
+ const guard = generateAuthGuardCSharp(policy, "read");
393
+ expect(guard).toContain("Authorize");
394
+ expect(guard).toContain("admin");
395
+ });
396
+
397
+ it("generates authentication-only guard for empty policy", () => {
398
+ const policy: ModelAuthPolicy = {};
399
+ const guard = generateAuthGuardCSharp(policy, "read");
400
+ expect(guard).toContain("Authorize");
401
+ });
402
+ });
403
+
404
+ describe("generateAuthGuardPython", () => {
405
+ it("generates require_auth call for Python with roles", () => {
406
+ const policy: ModelAuthPolicy = { roles: ["admin"] };
407
+ const guard = generateAuthGuardPython(policy, "read");
408
+ expect(guard).toContain("require_auth");
409
+ });
410
+
411
+ it("generates authentication-only guard for empty policy", () => {
412
+ const policy: ModelAuthPolicy = {};
413
+ const guard = generateAuthGuardPython(policy, "read");
414
+ expect(guard).toContain("require_auth");
415
+ });
416
+ });
417
+
418
+ // ============================================================
419
+ // model-parser: parseAuthPolicy
420
+ // ============================================================
421
+ describe("parseAuthPolicy", () => {
422
+ it("parses simple roles-based authPolicy", () => {
423
+ const content = `
424
+ export const Estimate = z.object({ id: z.string() });
425
+ export const displayName = '見積';
426
+ export const authPolicy = {
427
+ roles: ['admin', 'estimator'],
428
+ };
429
+ `;
430
+ const result = parseAuthPolicy(content);
431
+ expect(result).toEqual({ roles: ["admin", "estimator"] });
432
+ });
433
+
434
+ it("parses read/write authPolicy", () => {
435
+ const content = `
436
+ export const authPolicy = {
437
+ read: ['admin', 'member'],
438
+ write: ['admin'],
439
+ };
440
+ `;
441
+ const result = parseAuthPolicy(content);
442
+ expect(result).toEqual({
443
+ read: ["admin", "member"],
444
+ write: ["admin"],
445
+ });
446
+ });
447
+
448
+ it("parses authPolicy with all three fields", () => {
449
+ const content = `
450
+ export const authPolicy = {
451
+ roles: ['admin'],
452
+ read: ['admin', 'viewer'],
453
+ write: ['admin'],
454
+ };
455
+ `;
456
+ const result = parseAuthPolicy(content);
457
+ expect(result).toEqual({
458
+ roles: ["admin"],
459
+ read: ["admin", "viewer"],
460
+ write: ["admin"],
461
+ });
462
+ });
463
+
464
+ it("returns undefined when no authPolicy export exists", () => {
465
+ const content = `
466
+ export const Estimate = z.object({ id: z.string() });
467
+ export const displayName = '見積';
468
+ `;
469
+ const result = parseAuthPolicy(content);
470
+ expect(result).toBeUndefined();
471
+ });
472
+
473
+ it("handles double-quoted strings", () => {
474
+ const content = `
475
+ export const authPolicy = {
476
+ roles: ["admin", "editor"],
477
+ };
478
+ `;
479
+ const result = parseAuthPolicy(content);
480
+ expect(result).toEqual({ roles: ["admin", "editor"] });
481
+ });
482
+
483
+ it("returns undefined for empty authPolicy object", () => {
484
+ const content = `
485
+ export const authPolicy = {};
486
+ `;
487
+ const result = parseAuthPolicy(content);
488
+ expect(result).toBeUndefined();
489
+ });
490
+ });
491
+
492
+ // ============================================================
493
+ // functions-generator: Auth guard integration in TS CRUD
494
+ // ============================================================
495
+ describe("generateCompactAzureFunctionsCRUD with auth", () => {
496
+ it("generates auth imports when authPolicy is provided", () => {
497
+ const model = createBasicModelInfo();
498
+ const policy: ModelAuthPolicy = { roles: ["admin"] };
499
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared", policy);
500
+ expect(code).toContain("requireAuth");
501
+ expect(code).toContain("handleAuthError");
502
+ expect(code).toContain("./auth/jwt-helper");
503
+ });
504
+
505
+ it("does NOT generate auth imports when authPolicy is absent", () => {
506
+ const model = createBasicModelInfo();
507
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared");
508
+ expect(code).not.toContain("requireAuth");
509
+ expect(code).not.toContain("handleAuthError");
510
+ expect(code).not.toContain("./auth/jwt-helper");
511
+ });
512
+
513
+ it("injects role guards into all CRUD handlers", () => {
514
+ const model = createBasicModelInfo();
515
+ const policy: ModelAuthPolicy = { roles: ["admin", "editor"] };
516
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared", policy);
517
+ // requireAuth should appear in every handler (5 handlers)
518
+ const requireAuthCount = (code.match(/requireAuth/g) || []).length;
519
+ // At least import + handlers
520
+ expect(requireAuthCount).toBeGreaterThanOrEqual(5);
521
+ });
522
+
523
+ it("uses read roles for GET handlers and write roles for POST/PUT/DELETE", () => {
524
+ const model = createBasicModelInfo();
525
+ const policy: ModelAuthPolicy = {
526
+ read: ["admin", "viewer"],
527
+ write: ["admin"],
528
+ };
529
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared", policy);
530
+ expect(code).toContain("requireAuth");
531
+ // Should have both viewer (read) and admin (write) roles
532
+ expect(code).toContain("viewer");
533
+ expect(code).toContain("admin");
534
+ });
535
+
536
+ it("generates auth error handling in catch blocks", () => {
537
+ const model = createBasicModelInfo();
538
+ const policy: ModelAuthPolicy = { roles: ["admin"] };
539
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared", policy);
540
+ // handleAuthError should appear in each catch block
541
+ const handleAuthCount = (code.match(/handleAuthError/g) || []).length;
542
+ expect(handleAuthCount).toBeGreaterThanOrEqual(5);
543
+ });
544
+
545
+ it("generates authentication-only guard for empty policy (defaultPolicy: authenticated)", () => {
546
+ const model = createBasicModelInfo();
547
+ const policy: ModelAuthPolicy = {}; // empty = auth only, no specific roles
548
+ const code = generateCompactAzureFunctionsCRUD(model, "@myapp/shared", policy);
549
+ expect(code).toContain("requireAuth");
550
+ // requireRoles should not be called (only imported)
551
+ expect(code).not.toContain("requireRoles(");
552
+ });
553
+ });
554
+
555
+ // ============================================================
556
+ // Multi-RDB Provider: TypeScript
557
+ // ============================================================
558
+ describe("generateAuthFunctionsTS - multi-provider", () => {
559
+ it("generates PostgreSQL driver code when provider is postgres", () => {
560
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "postgres");
561
+ expect(code).toContain("import pg from 'pg'");
562
+ expect(code).toContain("new pg.Client");
563
+ expect(code).toContain("client.connect()");
564
+ expect(code).toContain("$1");
565
+ expect(code).toContain("result.rows");
566
+ expect(code).not.toContain("mysql2");
567
+ expect(code).not.toContain("mssql");
568
+ });
569
+
570
+ it("generates SQL Server driver code when provider is sqlserver", () => {
571
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "sqlserver");
572
+ expect(code).toContain("import sql from 'mssql'");
573
+ expect(code).toContain("sql.connect");
574
+ expect(code).toContain(".input(");
575
+ expect(code).toContain("@loginId");
576
+ expect(code).toContain("recordset");
577
+ expect(code).not.toContain("mysql2");
578
+ expect(code).not.toContain("import pg");
579
+ });
580
+
581
+ it("generates MySQL driver code when provider is mysql", () => {
582
+ const code = generateAuthFunctionsTS(sharedPkg, defaultJwtConfig, "mysql");
583
+ expect(code).toContain("import mysql from 'mysql2/promise'");
584
+ expect(code).toContain("mysql.createConnection");
585
+ expect(code).toContain("WHERE login_id = ?");
586
+ expect(code).not.toContain("import pg");
587
+ expect(code).not.toContain("import sql from");
588
+ });
589
+
590
+ it("uses the connection env var from userConnector config", () => {
591
+ const pgConfig: CustomJwtConfig = { ...defaultJwtConfig, userConnector: "my-pg-db" };
592
+ const code = generateAuthFunctionsTS(sharedPkg, pgConfig, "postgres");
593
+ expect(code).toContain("MY-PG-DB_CONNECTION_STRING");
594
+ });
595
+ });
596
+
597
+ // ============================================================
598
+ // Multi-RDB Provider: C#
599
+ // ============================================================
600
+ describe("generateAuthFunctionsCSharp - multi-provider", () => {
601
+ it("generates Npgsql code when provider is postgres", () => {
602
+ const code = generateAuthFunctionsCSharp(defaultJwtConfig, "postgres");
603
+ expect(code).toContain("using Npgsql");
604
+ expect(code).toContain("NpgsqlConnection");
605
+ expect(code).toContain("NpgsqlCommand");
606
+ expect(code).not.toContain("MySqlConnector");
607
+ expect(code).not.toContain("SqlClient");
608
+ });
609
+
610
+ it("generates SqlClient code when provider is sqlserver", () => {
611
+ const code = generateAuthFunctionsCSharp(defaultJwtConfig, "sqlserver");
612
+ expect(code).toContain("Microsoft.Data.SqlClient");
613
+ expect(code).toContain("SqlConnection");
614
+ expect(code).toContain("SqlCommand");
615
+ expect(code).not.toContain("MySqlConnector");
616
+ expect(code).not.toContain("Npgsql");
617
+ });
618
+
619
+ it("generates MySqlConnector code when provider is mysql", () => {
620
+ const code = generateAuthFunctionsCSharp(defaultJwtConfig, "mysql");
621
+ expect(code).toContain("MySqlConnector");
622
+ expect(code).toContain("MySqlConnection");
623
+ expect(code).toContain("MySqlCommand");
624
+ expect(code).not.toContain("Npgsql");
625
+ expect(code).not.toContain("SqlClient");
626
+ });
627
+ });
628
+
629
+ // ============================================================
630
+ // Multi-RDB Provider: Python
631
+ // ============================================================
632
+ describe("generateAuthFunctionsPython - multi-provider", () => {
633
+ it("generates psycopg2 code when provider is postgres", () => {
634
+ const code = generateAuthFunctionsPython(defaultJwtConfig, "postgres");
635
+ expect(code).toContain("psycopg2");
636
+ expect(code).toContain("RealDictCursor");
637
+ expect(code).not.toContain("mysql.connector");
638
+ expect(code).not.toContain("pymssql");
639
+ });
640
+
641
+ it("generates pymssql code when provider is sqlserver", () => {
642
+ const code = generateAuthFunctionsPython(defaultJwtConfig, "sqlserver");
643
+ expect(code).toContain("pymssql");
644
+ expect(code).not.toContain("mysql.connector");
645
+ expect(code).not.toContain("psycopg2");
646
+ });
647
+
648
+ it("generates mysql.connector code when provider is mysql", () => {
649
+ const code = generateAuthFunctionsPython(defaultJwtConfig, "mysql");
650
+ expect(code).toContain("mysql.connector");
651
+ expect(code).not.toContain("psycopg2");
652
+ expect(code).not.toContain("pymssql");
653
+ });
654
+ });