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.
- package/LICENSE +21 -21
- package/README.ja.md +312 -215
- package/README.md +369 -216
- package/dist/__tests__/fixtures.d.ts +22 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +146 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/cli/commands/add-auth.d.ts +10 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +444 -0
- package/dist/cli/commands/add-auth.js.map +1 -0
- package/dist/cli/commands/add-connector.d.ts +20 -0
- package/dist/cli/commands/add-connector.d.ts.map +1 -0
- package/dist/cli/commands/add-connector.js +163 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +21 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +35 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +292 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +19 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +476 -117
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +13 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2627 -1708
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +3 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +617 -129
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +162 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +8 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +90 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +101 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +480 -0
- package/dist/core/mock/connector-mock-server.js.map +1 -0
- package/dist/core/mock/zod-mock-generator.d.ts +14 -0
- package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
- package/dist/core/mock/zod-mock-generator.js +163 -0
- package/dist/core/mock/zod-mock-generator.js.map +1 -0
- package/dist/core/operations/create-model.d.ts +15 -0
- package/dist/core/operations/create-model.d.ts.map +1 -0
- package/dist/core/operations/create-model.js +171 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +32 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +225 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts +16 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
- package/dist/core/operations/scaffold-machine.js +63 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/project/manifest.d.ts +92 -0
- package/dist/core/project/manifest.d.ts.map +1 -0
- package/dist/core/project/manifest.js +321 -0
- package/dist/core/project/manifest.js.map +1 -0
- package/dist/core/project/validation.d.ts +20 -0
- package/dist/core/project/validation.d.ts.map +1 -0
- package/dist/core/project/validation.js +204 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +38 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1244 -0
- package/dist/core/scaffold/auth-generator.js.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.js +1027 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/functions-generator.d.ts +7 -1
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +920 -213
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +329 -135
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +314 -182
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +3 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
- package/dist/core/scaffold/openapi-generator.js +190 -0
- package/dist/core/scaffold/openapi-generator.js.map +1 -0
- package/dist/core/scaffold/ui-generator.d.ts +10 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +768 -663
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/database/base-model.d.ts +3 -3
- package/dist/database/base-model.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +16 -0
- package/dist/machine/contracts.d.ts.map +1 -0
- package/dist/machine/contracts.js +3 -0
- package/dist/machine/contracts.js.map +1 -0
- package/dist/machine/errors.d.ts +11 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +34 -0
- package/dist/machine/errors.js.map +1 -0
- package/dist/machine/index.d.ts +3 -0
- package/dist/machine/index.d.ts.map +1 -0
- package/dist/machine/index.js +156 -0
- package/dist/machine/index.js.map +1 -0
- package/dist/mcp/index.d.ts +25 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +184 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +109 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +215 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/package.json +85 -73
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/auth.test.ts +654 -0
- package/src/__tests__/config.test.ts +263 -0
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +439 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/dev-seeds.test.ts +112 -0
- package/src/__tests__/dev.test.ts +148 -0
- package/src/__tests__/fixtures.ts +144 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +80 -0
- package/src/__tests__/machine.test.ts +212 -0
- package/src/__tests__/mcp.test.ts +56 -0
- package/src/__tests__/model-parser.test.ts +72 -0
- package/src/__tests__/nextjs-generator.test.ts +97 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +189 -0
- package/src/__tests__/scaffold.test.ts +39 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/ui-generator.test.ts +144 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +500 -0
- package/src/cli/commands/add-connector.ts +158 -0
- package/src/cli/commands/create-model.ts +62 -0
- package/src/cli/commands/dev-seeds.ts +358 -0
- package/src/cli/commands/dev.ts +962 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3371 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1211 -0
- package/src/cli/index.ts +191 -0
- package/src/core/config.ts +308 -0
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/operations/create-model.ts +174 -0
- package/src/core/operations/runtime.ts +235 -0
- package/src/core/operations/scaffold-machine.ts +91 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/validation.ts +221 -0
- package/src/core/scaffold/auth-generator.ts +1284 -0
- package/src/core/scaffold/connector-functions-generator.ts +1128 -0
- package/src/core/scaffold/functions-generator.ts +970 -0
- package/src/core/scaffold/model-parser.ts +841 -0
- package/src/core/scaffold/nextjs-generator.ts +370 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1061 -0
- package/src/database/base-model.ts +184 -0
- package/src/database/client.ts +140 -0
- package/src/database/repository.ts +104 -0
- package/src/database/runtime-check.ts +25 -0
- package/src/index.ts +27 -0
- package/src/machine/contracts.ts +17 -0
- package/src/machine/errors.ts +34 -0
- package/src/machine/index.ts +173 -0
- package/src/mcp/index.ts +185 -0
- package/src/types/index.ts +134 -0
- 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
|
+
});
|