@simplysm/sd-cli 14.0.89 → 14.0.90
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/dist/commands/init/generators/client-common.d.ts.map +1 -1
- package/dist/commands/init/generators/client-common.js +6 -2
- package/dist/commands/init/generators/client-common.js.map +1 -1
- package/dist/commands/init/generators/client.d.ts.map +1 -1
- package/dist/commands/init/generators/client.js +13 -2
- package/dist/commands/init/generators/client.js.map +1 -1
- package/dist/commands/init/generators/common.d.ts.map +1 -1
- package/dist/commands/init/generators/common.js +16 -1
- package/dist/commands/init/generators/common.js.map +1 -1
- package/dist/commands/init/generators/server.d.ts.map +1 -1
- package/dist/commands/init/generators/server.js +9 -0
- package/dist/commands/init/generators/server.js.map +1 -1
- package/dist/commands/init/init.d.ts.map +1 -1
- package/dist/commands/init/init.js +11 -2
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/init/normalize.d.ts.map +1 -1
- package/dist/commands/init/normalize.js +40 -4
- package/dist/commands/init/normalize.js.map +1 -1
- package/dist/commands/init/prompts.d.ts +1 -1
- package/dist/commands/init/prompts.d.ts.map +1 -1
- package/dist/commands/init/prompts.js +34 -3
- package/dist/commands/init/prompts.js.map +1 -1
- package/dist/commands/init/types.d.ts +16 -1
- package/dist/commands/init/types.d.ts.map +1 -1
- package/dist/commands/init/validate.d.ts.map +1 -1
- package/dist/commands/init/validate.js +3 -0
- package/dist/commands/init/validate.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/init/generators/client-common.ts +18 -5
- package/src/commands/init/generators/client.ts +41 -2
- package/src/commands/init/generators/common.ts +56 -2
- package/src/commands/init/generators/server.ts +30 -0
- package/src/commands/init/init.ts +12 -2
- package/src/commands/init/normalize.ts +49 -4
- package/src/commands/init/prompts.ts +34 -3
- package/src/commands/init/templates/client/login-public/assets/logo-landscape.png +0 -0
- package/src/commands/init/templates/client/login-public/assets/logo.png +0 -0
- package/src/commands/init/templates/client/package.json.hbs +3 -2
- package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +137 -0
- package/src/commands/init/templates/client/src/app/home/main/main.view.ts.hbs +16 -0
- package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +265 -0
- package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +144 -0
- package/src/commands/init/templates/client/src/app.root.ts.hbs +64 -0
- package/src/commands/init/templates/client/src/index.html.hbs +75 -1
- package/src/commands/init/templates/client/src/main.ts.hbs +147 -7
- package/src/commands/init/templates/client/src/modals/dev.modal.ts.hbs +63 -0
- package/src/commands/init/templates/client/src/routes.ts.hbs +29 -0
- package/src/commands/init/templates/client-common/package.json.hbs +1 -0
- package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -2
- package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +90 -0
- package/src/commands/init/templates/client-common/src/providers/{AppOrmProvider.ts.hbs → app-orm.provider.ts.hbs} +5 -11
- package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +68 -0
- package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +100 -0
- package/src/commands/init/templates/common/package.json.hbs +2 -1
- package/src/commands/init/templates/common/src/app-structure.ts.hbs +26 -0
- package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -0
- package/src/commands/init/templates/common/src/db/db-context.ts.hbs +20 -0
- package/src/commands/init/templates/common/src/db/system-data-log.ext.ts.hbs +138 -0
- package/src/commands/init/templates/common/src/db/tables/master/user-config.ts.hbs +15 -0
- package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +20 -0
- package/src/commands/init/templates/common/src/db/tables/system/role-permission.ts.hbs +16 -0
- package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +16 -0
- package/src/commands/init/templates/common/src/db/tables/system/system-data-log.ts.hbs +23 -0
- package/src/commands/init/templates/common/src/db/tables/system/system-log.ts.hbs +21 -0
- package/src/commands/init/templates/common/src/index.ts.hbs +14 -1
- package/src/commands/init/templates/server/package.json.hbs +7 -3
- package/src/commands/init/templates/server/public-dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
- package/src/commands/init/templates/server/src/index.ts.hbs +4 -0
- package/src/commands/init/templates/server/src/main.ts.hbs +11 -1
- package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +284 -0
- package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +112 -0
- package/src/commands/init/templates/server/src/utils/orm.utils.ts.hbs +8 -0
- package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +2 -2
- package/src/commands/init/types.ts +16 -1
- package/src/commands/init/validate.ts +6 -0
- package/tests/init/__snapshots__/render.spec.ts.snap +36 -9
- package/tests/init/normalize.spec.ts +95 -1
- package/tests/init/render.spec.ts +951 -10
- package/src/commands/init/templates/client/src/AppPage.ts.hbs +0 -18
- package/src/commands/init/templates/client/src/routes.ts +0 -3
- package/src/commands/init/templates/client-common/src/providers/AppServiceProvider.ts +0 -27
- package/src/commands/init/templates/common/src/DbContext.ts.hbs +0 -4
|
@@ -128,6 +128,140 @@ describe("server/src/main.ts.hbs", () => {
|
|
|
128
128
|
expect(out).toMatchSnapshot();
|
|
129
129
|
expect(out).toContain("OrmService");
|
|
130
130
|
});
|
|
131
|
+
|
|
132
|
+
it("DB=Y 인증 OFF — AuthService 없음", async () => {
|
|
133
|
+
const data = buildData({
|
|
134
|
+
workspaceName: "demo",
|
|
135
|
+
description: "Demo",
|
|
136
|
+
hasServer: true,
|
|
137
|
+
hasDb: true,
|
|
138
|
+
dbDialect: "mysql",
|
|
139
|
+
hasAuth: false,
|
|
140
|
+
clients: [],
|
|
141
|
+
serverPort: 40080,
|
|
142
|
+
});
|
|
143
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/main.ts.hbs"), data);
|
|
144
|
+
expect(out).toContain(
|
|
145
|
+
'services: [OrmService, ...(parseBoolEnv(env("DEV")) ? [DevService] : [])],',
|
|
146
|
+
);
|
|
147
|
+
expect(out).not.toContain("AuthService");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("인증 ON — AuthService import + services 등록", async () => {
|
|
151
|
+
const data = buildData({
|
|
152
|
+
workspaceName: "demo",
|
|
153
|
+
description: "Demo",
|
|
154
|
+
hasServer: true,
|
|
155
|
+
hasDb: true,
|
|
156
|
+
dbDialect: "mysql",
|
|
157
|
+
hasAuth: true,
|
|
158
|
+
clients: [],
|
|
159
|
+
serverPort: 40080,
|
|
160
|
+
});
|
|
161
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/main.ts.hbs"), data);
|
|
162
|
+
expect(out).toContain('import { AuthService } from "./services/auth.service";');
|
|
163
|
+
expect(out).toContain('import { DevService } from "./services/dev.service";');
|
|
164
|
+
expect(out).toContain(
|
|
165
|
+
'services: [OrmService, AuthService, ...(parseBoolEnv(env("DEV")) ? [DevService] : [])],',
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("server 인증 템플릿", () => {
|
|
171
|
+
const authData = buildData({
|
|
172
|
+
workspaceName: "demo",
|
|
173
|
+
description: "Demo",
|
|
174
|
+
hasServer: true,
|
|
175
|
+
hasDb: true,
|
|
176
|
+
dbDialect: "mysql",
|
|
177
|
+
dbContextName: "main",
|
|
178
|
+
hasAuth: true,
|
|
179
|
+
userEntityName: "employee",
|
|
180
|
+
userEntityLabel: "직원",
|
|
181
|
+
clients: [],
|
|
182
|
+
serverPort: 40080,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("orm.utils: dbContextClassName + 설정키(Upper) + workspace common import 반영", async () => {
|
|
186
|
+
const out = await renderTemplate(
|
|
187
|
+
path.join(TPL_ROOT, "server/src/utils/orm.utils.ts.hbs"),
|
|
188
|
+
authData,
|
|
189
|
+
);
|
|
190
|
+
expect(out).toContain('import { MainDbContext } from "@demo/common";');
|
|
191
|
+
expect(out).toContain("await ctx.getConfig<{ MAIN: DbConnConfig }>(\"orm\")");
|
|
192
|
+
expect(out).toContain("return createOrm(MainDbContext, ormConfig.MAIN);");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("auth.service: 사용자 엔티티 네이밍이 IAuthData/쿼리/메시지에 반영", async () => {
|
|
196
|
+
const out = await renderTemplate(
|
|
197
|
+
path.join(TPL_ROOT, "server/src/services/auth.service.ts.hbs"),
|
|
198
|
+
authData,
|
|
199
|
+
);
|
|
200
|
+
expect(out).toContain('import { AuthInfoChangedEvent, EmployeeConfig, RolePermission } from "@demo/common";');
|
|
201
|
+
expect(out).toContain("export interface IEmployeeConfigMap {");
|
|
202
|
+
expect(out).toContain("employeeId: number;");
|
|
203
|
+
expect(out).toContain("name: string;");
|
|
204
|
+
expect(out).toContain("roleName: string;");
|
|
205
|
+
expect(out).toContain("configs: IEmployeeConfigMap;");
|
|
206
|
+
expect(out).toContain(".employee()");
|
|
207
|
+
expect(out).toContain("currentAuth.employeeId");
|
|
208
|
+
expect(out).toContain("roleName: employee.role!.name,");
|
|
209
|
+
expect(out).toContain(".include((item) => item.role)");
|
|
210
|
+
expect(out).toContain(".from(EmployeeConfig)");
|
|
211
|
+
expect(out).toContain("expr.eq(cfg.employeeId, e.id)");
|
|
212
|
+
expect(out).toContain("(configs as Record<string, unknown>)[cfg.code] = JSON.parse(cfg.valueJson);");
|
|
213
|
+
expect(out).toContain("유효하지 않은 직원입니다.");
|
|
214
|
+
|
|
215
|
+
// update(내정보수정) 메서드 + 인증정보 변경 이벤트 emit
|
|
216
|
+
expect(out).toContain("update: auth(");
|
|
217
|
+
expect(out).toContain("configs: IEmployeeConfigMap;");
|
|
218
|
+
expect(out).toContain(".employeeConfig()");
|
|
219
|
+
expect(out).toContain("expr.eq(c.employeeId, employeeId)");
|
|
220
|
+
expect(out).toContain('action: "수정(내정보수정)",');
|
|
221
|
+
expect(out).toContain("ctx.server.emitEvent(");
|
|
222
|
+
expect(out).toContain("(info) => info.employeeId === employeeId,");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("auth.service: 기본 네이밍(user/사용자) 반영", async () => {
|
|
226
|
+
const data = buildData({
|
|
227
|
+
workspaceName: "demo",
|
|
228
|
+
description: "Demo",
|
|
229
|
+
hasServer: true,
|
|
230
|
+
hasDb: true,
|
|
231
|
+
dbDialect: "mysql",
|
|
232
|
+
hasAuth: true,
|
|
233
|
+
clients: [],
|
|
234
|
+
serverPort: 40080,
|
|
235
|
+
});
|
|
236
|
+
const out = await renderTemplate(
|
|
237
|
+
path.join(TPL_ROOT, "server/src/services/auth.service.ts.hbs"),
|
|
238
|
+
data,
|
|
239
|
+
);
|
|
240
|
+
expect(out).toContain("userId: number;");
|
|
241
|
+
expect(out).toContain(".user()");
|
|
242
|
+
expect(out).toContain("유효하지 않은 사용자입니다.");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("server/package.json: 인증 ON 시 bcrypt + @types/bcrypt 포함", async () => {
|
|
246
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/package.json.hbs"), authData);
|
|
247
|
+
expect(out).toContain('"bcrypt": "^6.0.0"');
|
|
248
|
+
expect(out).toContain('"@types/bcrypt": "^6.0.0"');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("server/package.json: 인증 OFF 시 bcrypt 없음", async () => {
|
|
252
|
+
const data = buildData({
|
|
253
|
+
workspaceName: "demo",
|
|
254
|
+
description: "Demo",
|
|
255
|
+
hasServer: true,
|
|
256
|
+
hasDb: true,
|
|
257
|
+
dbDialect: "mysql",
|
|
258
|
+
hasAuth: false,
|
|
259
|
+
clients: [],
|
|
260
|
+
serverPort: 40080,
|
|
261
|
+
});
|
|
262
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/package.json.hbs"), data);
|
|
263
|
+
expect(out).not.toContain("bcrypt");
|
|
264
|
+
});
|
|
131
265
|
});
|
|
132
266
|
|
|
133
267
|
describe("client/src/main.ts.hbs", () => {
|
|
@@ -163,7 +297,7 @@ describe("client/src/main.ts.hbs", () => {
|
|
|
163
297
|
});
|
|
164
298
|
});
|
|
165
299
|
|
|
166
|
-
describe("client/src/
|
|
300
|
+
describe("client/src/app.root.ts.hbs", () => {
|
|
167
301
|
it("라우팅 Y → router-outlet", async () => {
|
|
168
302
|
const data = buildData({
|
|
169
303
|
workspaceName: "demo",
|
|
@@ -173,7 +307,7 @@ describe("client/src/AppPage.ts.hbs", () => {
|
|
|
173
307
|
clients: [{ name: "admin", type: "web", hasRouter: true }],
|
|
174
308
|
});
|
|
175
309
|
const ctx = { ...data, client: data.clients[0] };
|
|
176
|
-
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/
|
|
310
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app.root.ts.hbs"), ctx);
|
|
177
311
|
expect(out).toContain("router-outlet");
|
|
178
312
|
});
|
|
179
313
|
|
|
@@ -186,13 +320,146 @@ describe("client/src/AppPage.ts.hbs", () => {
|
|
|
186
320
|
clients: [{ name: "x", type: "web", hasRouter: false }],
|
|
187
321
|
});
|
|
188
322
|
const ctx = { ...data, client: data.clients[0] };
|
|
189
|
-
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/
|
|
323
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app.root.ts.hbs"), ctx);
|
|
190
324
|
expect(out).not.toContain("router-outlet");
|
|
191
325
|
expect(out).toContain("<div></div>");
|
|
192
326
|
});
|
|
193
327
|
});
|
|
194
328
|
|
|
195
|
-
describe("client
|
|
329
|
+
describe("client 인증 로그인 (routes / login.view / ng-icons)", () => {
|
|
330
|
+
function authClientCtx(hasAuth: boolean, hasRouter = true) {
|
|
331
|
+
const data = buildData({
|
|
332
|
+
workspaceName: "demo",
|
|
333
|
+
description: "Demo",
|
|
334
|
+
hasServer: true,
|
|
335
|
+
hasDb: true,
|
|
336
|
+
dbDialect: "mysql",
|
|
337
|
+
hasAuth,
|
|
338
|
+
clients: [{ name: "admin", type: "web", hasRouter }],
|
|
339
|
+
serverPort: 40080,
|
|
340
|
+
});
|
|
341
|
+
return { ...data, client: data.clients[0] };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
it("routes.ts: 인증 ON → /login 리다이렉트 + lazy LoginView", async () => {
|
|
345
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/routes.ts.hbs"), authClientCtx(true));
|
|
346
|
+
expect(out).toContain('{ path: "", redirectTo: "/login", pathMatch: "full" }');
|
|
347
|
+
expect(out).toContain('import("./app/login/login.view").then((m) => m.LoginView)');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("routes.ts: 인증 OFF → 빈 routes", async () => {
|
|
351
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/routes.ts.hbs"), authClientCtx(false));
|
|
352
|
+
expect(out).toContain("export const routes: Routes = [];");
|
|
353
|
+
expect(out).not.toContain("login");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("login.view: client-common import + Angular 보간 복원 + configs 시작경로", async () => {
|
|
357
|
+
const out = await renderTemplate(
|
|
358
|
+
path.join(TPL_ROOT, "client/src/app/login/login.view.ts.hbs"),
|
|
359
|
+
authClientCtx(true),
|
|
360
|
+
);
|
|
361
|
+
expect(out).toContain('import { AppAuthProvider } from "@demo/client-common";');
|
|
362
|
+
expect(out).toContain('alt="demo"');
|
|
363
|
+
expect(out).toContain("v{{ VER }}.{{ DEV ? \"d\" : \"p\" }}");
|
|
364
|
+
expect(out).toContain('this._appAuth.authInfo()?.configs["first-router-link"] ?? "/home/main"');
|
|
365
|
+
expect(out).not.toContain("{{workspaceName}}");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("client/package.json: 인증 ON + router → @ng-icons 포함", async () => {
|
|
369
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/package.json.hbs"), authClientCtx(true));
|
|
370
|
+
expect(out).toContain('"@ng-icons/core": "^33.2.3"');
|
|
371
|
+
expect(out).toContain('"@ng-icons/tabler-icons": "^33.2.3"');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("client/package.json: 인증 OFF → @ng-icons 없음", async () => {
|
|
375
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/package.json.hbs"), authClientCtx(false));
|
|
376
|
+
expect(out).not.toContain("@ng-icons");
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe("관리자 셸 (home/main/app-structure/main.ts 와이어링)", () => {
|
|
381
|
+
function authClientCtx(hasAuth: boolean, hasRouter = true, userEntityName = "employee", userEntityLabel = "직원") {
|
|
382
|
+
const data = buildData({
|
|
383
|
+
workspaceName: "demo",
|
|
384
|
+
description: "Demo",
|
|
385
|
+
hasServer: true,
|
|
386
|
+
hasDb: true,
|
|
387
|
+
dbDialect: "mysql",
|
|
388
|
+
hasAuth,
|
|
389
|
+
userEntityName: hasAuth ? userEntityName : undefined,
|
|
390
|
+
userEntityLabel: hasAuth ? userEntityLabel : undefined,
|
|
391
|
+
clients: [{ name: "admin", type: "web", hasRouter }],
|
|
392
|
+
serverPort: 40080,
|
|
393
|
+
});
|
|
394
|
+
return { ...data, client: data.clients[0] };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
it("common/app-structure: 사용자 엔티티 메뉴 항목에 선택 네이밍 반영", async () => {
|
|
398
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/app-structure.ts.hbs"), authClientCtx(true));
|
|
399
|
+
expect(out).toContain('import type { AppStructureItem } from "@simplysm/service-common";');
|
|
400
|
+
expect(out).toContain("export const appStructureItems: AppStructureItem[] = [");
|
|
401
|
+
expect(out).toContain('{ code: "employee", title: "직원", perms: ["use", "edit"] }');
|
|
402
|
+
expect(out).toContain('{ code: "role-permission", title: "역할/권한", perms: ["use", "edit"] }');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("common/index: 인증 ON → app-structure re-export", async () => {
|
|
406
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/index.ts.hbs"), authClientCtx(true));
|
|
407
|
+
expect(out).toContain('export * from "./app-structure";');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("common/index: 인증 OFF → app-structure 없음", async () => {
|
|
411
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/index.ts.hbs"), authClientCtx(false));
|
|
412
|
+
expect(out).not.toContain("app-structure");
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("common/package.json: 인증 ON → service-common 의존", async () => {
|
|
416
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/package.json.hbs"), authClientCtx(true));
|
|
417
|
+
expect(out).toContain('"@simplysm/service-common": "^14.0.0"');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("common/package.json: 인증 OFF → service-common 없음", async () => {
|
|
421
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/package.json.hbs"), authClientCtx(false));
|
|
422
|
+
expect(out).not.toContain("service-common");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("routes.ts: 인증 ON → home(+main children) 라우트 포함", async () => {
|
|
426
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/routes.ts.hbs"), authClientCtx(true));
|
|
427
|
+
expect(out).toContain('import("./app/home/home.view").then((m) => m.HomeView)');
|
|
428
|
+
expect(out).toContain('import("./app/home/main/main.view").then((m) => m.MainView)');
|
|
429
|
+
expect(out).toContain('{ path: "", redirectTo: "main", pathMatch: "full" }');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("home.view: client-common import + Angular 보간 복원 + 사이드바", async () => {
|
|
433
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app/home/home.view.ts.hbs"), authClientCtx(true));
|
|
434
|
+
expect(out).toContain('import { AppAuthProvider } from "@demo/client-common";');
|
|
435
|
+
expect(out).toContain("export class HomeView {");
|
|
436
|
+
expect(out).toContain("{{ authInfo()?.name }}");
|
|
437
|
+
expect(out).toContain("{{ authInfo()?.roleName }}");
|
|
438
|
+
expect(out).toContain("this._sdAppStructure.usableMenus()");
|
|
439
|
+
expect(out).not.toContain("\\{{");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("main.view: SdBaseContainer + viewType", async () => {
|
|
443
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app/home/main/main.view.ts.hbs"), authClientCtx(true));
|
|
444
|
+
expect(out).toContain("export class MainView {");
|
|
445
|
+
expect(out).toContain("viewType = injectViewTypeSignal();");
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("client/main.ts: 인증 ON + router → app-structure 초기화 와이어링", async () => {
|
|
449
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/main.ts.hbs"), authClientCtx(true));
|
|
450
|
+
expect(out).toContain("SdAppStructureProvider");
|
|
451
|
+
expect(out).toContain('import { appStructureItems } from "@demo/common";');
|
|
452
|
+
expect(out).toContain("inject(SdAppStructureProvider).initialize(appStructureItems);");
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("client/main.ts: 인증 OFF → app-structure 초기화 없음", async () => {
|
|
456
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/main.ts.hbs"), authClientCtx(false));
|
|
457
|
+
expect(out).not.toContain("SdAppStructureProvider");
|
|
458
|
+
expect(out).not.toContain("appStructureItems");
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe("client-common/src/providers/app-orm.provider.ts.hbs", () => {
|
|
196
463
|
it("default dbContextName=main → MainDbContext + workspaceNameUpper database", async () => {
|
|
197
464
|
const data = buildData({
|
|
198
465
|
workspaceName: "demo2",
|
|
@@ -204,7 +471,7 @@ describe("client-common/src/providers/AppOrmProvider.ts.hbs", () => {
|
|
|
204
471
|
serverPort: 40080,
|
|
205
472
|
});
|
|
206
473
|
const out = await renderTemplate(
|
|
207
|
-
path.join(TPL_ROOT, "client-common/src/providers/
|
|
474
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-orm.provider.ts.hbs"),
|
|
208
475
|
data,
|
|
209
476
|
);
|
|
210
477
|
expect(out).toContain('database: "DEMO2"');
|
|
@@ -226,7 +493,7 @@ describe("client-common/src/providers/AppOrmProvider.ts.hbs", () => {
|
|
|
226
493
|
serverPort: 40080,
|
|
227
494
|
});
|
|
228
495
|
const out = await renderTemplate(
|
|
229
|
-
path.join(TPL_ROOT, "client-common/src/providers/
|
|
496
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-orm.provider.ts.hbs"),
|
|
230
497
|
data,
|
|
231
498
|
);
|
|
232
499
|
expect(out).toContain("import { OrderDbContext }");
|
|
@@ -235,6 +502,171 @@ describe("client-common/src/providers/AppOrmProvider.ts.hbs", () => {
|
|
|
235
502
|
});
|
|
236
503
|
});
|
|
237
504
|
|
|
505
|
+
describe("client-common/src/providers/app-service.provider.ts.hbs", () => {
|
|
506
|
+
const svcTpl = "client-common/src/providers/app-service.provider.ts.hbs";
|
|
507
|
+
|
|
508
|
+
it("인증 OFF (DB만) → auth getter / AuthServiceMethods 없음", async () => {
|
|
509
|
+
const data = buildData({
|
|
510
|
+
workspaceName: "demo",
|
|
511
|
+
description: "Demo",
|
|
512
|
+
hasServer: true,
|
|
513
|
+
hasDb: true,
|
|
514
|
+
dbDialect: "mysql",
|
|
515
|
+
hasAuth: false,
|
|
516
|
+
clients: [],
|
|
517
|
+
serverPort: 40080,
|
|
518
|
+
});
|
|
519
|
+
const out = await renderTemplate(path.join(TPL_ROOT, svcTpl), data);
|
|
520
|
+
expect(out).toContain("get orm()");
|
|
521
|
+
expect(out).not.toContain("AuthServiceMethods");
|
|
522
|
+
expect(out).not.toContain("get auth()");
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("인증 ON → AuthService 프록시 getter + server 타입 import", async () => {
|
|
526
|
+
const data = buildData({
|
|
527
|
+
workspaceName: "demo",
|
|
528
|
+
description: "Demo",
|
|
529
|
+
hasServer: true,
|
|
530
|
+
hasDb: true,
|
|
531
|
+
dbDialect: "mysql",
|
|
532
|
+
hasAuth: true,
|
|
533
|
+
clients: [],
|
|
534
|
+
serverPort: 40080,
|
|
535
|
+
});
|
|
536
|
+
const out = await renderTemplate(path.join(TPL_ROOT, svcTpl), data);
|
|
537
|
+
expect(out).toContain('import type { AuthServiceMethods } from "@demo/server";');
|
|
538
|
+
expect(out).toContain("type ServiceProxy,");
|
|
539
|
+
expect(out).toContain("private _auth?: ServiceProxy<AuthServiceMethods>;");
|
|
540
|
+
expect(out).toContain("get auth(): ServiceProxy<AuthServiceMethods> {");
|
|
541
|
+
expect(out).toContain(
|
|
542
|
+
'this._auth ??= this.client.getService<AuthServiceMethods>("AuthService")',
|
|
543
|
+
);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe("client-common/src/providers/app-auth.provider.ts.hbs", () => {
|
|
548
|
+
it("인증 ON → IAuthData(server) import + auth 흐름 메서드 포함", async () => {
|
|
549
|
+
const data = buildData({
|
|
550
|
+
workspaceName: "demo",
|
|
551
|
+
description: "Demo",
|
|
552
|
+
hasServer: true,
|
|
553
|
+
hasDb: true,
|
|
554
|
+
dbDialect: "mysql",
|
|
555
|
+
hasAuth: true,
|
|
556
|
+
clients: [],
|
|
557
|
+
serverPort: 40080,
|
|
558
|
+
});
|
|
559
|
+
const out = await renderTemplate(
|
|
560
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-auth.provider.ts.hbs"),
|
|
561
|
+
data,
|
|
562
|
+
);
|
|
563
|
+
expect(out).toContain('import type { IAuthData } from "@demo/server";');
|
|
564
|
+
expect(out).toContain("export class AppAuthProvider {");
|
|
565
|
+
expect(out).toContain("authInfo = signal<IAuthData | undefined>(undefined);");
|
|
566
|
+
expect(out).toContain("inject(SdAppStructureProvider)");
|
|
567
|
+
expect(out).toContain("this._sdAppStructure.permRecord.set(this.authInfo()?.permissions);");
|
|
568
|
+
expect(out).toContain("async login(email: string, password: string): Promise<void>");
|
|
569
|
+
expect(out).toContain("async tryReloadAuth(): Promise<boolean>");
|
|
570
|
+
expect(out).toContain("await this._appService.client.auth(token);");
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
describe("client-common/src/index.ts.hbs auth export", () => {
|
|
575
|
+
it("인증 ON → AppAuthProvider export", async () => {
|
|
576
|
+
const data = buildData({
|
|
577
|
+
workspaceName: "demo",
|
|
578
|
+
description: "Demo",
|
|
579
|
+
hasServer: true,
|
|
580
|
+
hasDb: true,
|
|
581
|
+
dbDialect: "mysql",
|
|
582
|
+
hasAuth: true,
|
|
583
|
+
clients: [],
|
|
584
|
+
serverPort: 40080,
|
|
585
|
+
});
|
|
586
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client-common/src/index.ts.hbs"), data);
|
|
587
|
+
expect(out).toContain('export { AppAuthProvider } from "./providers/app-auth.provider";');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it("인증 OFF → AppAuthProvider export 없음", async () => {
|
|
591
|
+
const data = buildData({
|
|
592
|
+
workspaceName: "demo",
|
|
593
|
+
description: "Demo",
|
|
594
|
+
hasServer: true,
|
|
595
|
+
hasDb: true,
|
|
596
|
+
dbDialect: "mysql",
|
|
597
|
+
hasAuth: false,
|
|
598
|
+
clients: [],
|
|
599
|
+
serverPort: 40080,
|
|
600
|
+
});
|
|
601
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client-common/src/index.ts.hbs"), data);
|
|
602
|
+
expect(out).not.toContain("AppAuthProvider");
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
describe("server/src/index.ts.hbs", () => {
|
|
607
|
+
it("인증 ON → auth service 타입 re-export", async () => {
|
|
608
|
+
const data = buildData({
|
|
609
|
+
workspaceName: "demo",
|
|
610
|
+
description: "Demo",
|
|
611
|
+
hasServer: true,
|
|
612
|
+
hasDb: true,
|
|
613
|
+
dbDialect: "mysql",
|
|
614
|
+
hasAuth: true,
|
|
615
|
+
clients: [],
|
|
616
|
+
serverPort: 40080,
|
|
617
|
+
});
|
|
618
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/index.ts.hbs"), data);
|
|
619
|
+
expect(out).toContain(
|
|
620
|
+
'export type { AuthServiceMethods, IAuthData, IAuthResult } from "./services/auth.service";',
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
describe("client-common/package.json.hbs server 의존", () => {
|
|
626
|
+
it("인증 ON → @workspace/server workspace 의존 추가", async () => {
|
|
627
|
+
const data = buildData({
|
|
628
|
+
workspaceName: "demo",
|
|
629
|
+
description: "Demo",
|
|
630
|
+
hasServer: true,
|
|
631
|
+
hasDb: true,
|
|
632
|
+
dbDialect: "mysql",
|
|
633
|
+
hasAuth: true,
|
|
634
|
+
clients: [],
|
|
635
|
+
serverPort: 40080,
|
|
636
|
+
});
|
|
637
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client-common/package.json.hbs"), data);
|
|
638
|
+
expect(out).toContain('"@demo/server": "workspace:*"');
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("DB OFF → @workspace/server 의존 없음", async () => {
|
|
642
|
+
const data = buildData({
|
|
643
|
+
workspaceName: "demo",
|
|
644
|
+
description: "Demo",
|
|
645
|
+
hasServer: true,
|
|
646
|
+
hasDb: false,
|
|
647
|
+
clients: [],
|
|
648
|
+
serverPort: 40080,
|
|
649
|
+
});
|
|
650
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client-common/package.json.hbs"), data);
|
|
651
|
+
expect(out).not.toContain("/server");
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it("DB ON 인증 OFF → dev 서비스용 server 의존 유지", async () => {
|
|
655
|
+
const data = buildData({
|
|
656
|
+
workspaceName: "demo",
|
|
657
|
+
description: "Demo",
|
|
658
|
+
hasServer: true,
|
|
659
|
+
hasDb: true,
|
|
660
|
+
dbDialect: "mysql",
|
|
661
|
+
hasAuth: false,
|
|
662
|
+
clients: [],
|
|
663
|
+
serverPort: 40080,
|
|
664
|
+
});
|
|
665
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client-common/package.json.hbs"), data);
|
|
666
|
+
expect(out).toContain('"@demo/server": "workspace:*"');
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
238
670
|
describe("sd.config.ts.hbs orm config 키", () => {
|
|
239
671
|
it("DB=Y default → MAIN", async () => {
|
|
240
672
|
const data = buildData({
|
|
@@ -273,7 +705,9 @@ describe("sd.config.ts.hbs orm config 키", () => {
|
|
|
273
705
|
});
|
|
274
706
|
});
|
|
275
707
|
|
|
276
|
-
describe("common/src/
|
|
708
|
+
describe("common/src/db/db-context.ts.hbs", () => {
|
|
709
|
+
const dbContextTpl = "common/src/db/db-context.ts.hbs";
|
|
710
|
+
|
|
277
711
|
it("dbContextClassName 자리에 클래스명 들어감", async () => {
|
|
278
712
|
const data = buildData({
|
|
279
713
|
workspaceName: "demo",
|
|
@@ -285,24 +719,198 @@ describe("common/src/DbContext.ts.hbs", () => {
|
|
|
285
719
|
clients: [],
|
|
286
720
|
serverPort: 40080,
|
|
287
721
|
});
|
|
288
|
-
const out = await renderTemplate(path.join(TPL_ROOT,
|
|
722
|
+
const out = await renderTemplate(path.join(TPL_ROOT, dbContextTpl), data);
|
|
289
723
|
expect(out).toContain("export class MainDbContext extends DbContext");
|
|
290
724
|
});
|
|
725
|
+
|
|
726
|
+
it("인증 ON → 사용자/역할/권한 queryable 등록 (선택 네이밍 반영)", async () => {
|
|
727
|
+
const data = buildData({
|
|
728
|
+
workspaceName: "demo",
|
|
729
|
+
description: "Demo",
|
|
730
|
+
hasServer: true,
|
|
731
|
+
hasDb: true,
|
|
732
|
+
dbDialect: "mysql",
|
|
733
|
+
hasAuth: true,
|
|
734
|
+
userEntityName: "employee",
|
|
735
|
+
userEntityLabel: "직원",
|
|
736
|
+
clients: [],
|
|
737
|
+
serverPort: 40080,
|
|
738
|
+
});
|
|
739
|
+
const out = await renderTemplate(path.join(TPL_ROOT, dbContextTpl), data);
|
|
740
|
+
expect(out).toContain("employee = this.queryable(Employee);");
|
|
741
|
+
expect(out).toContain("employeeConfig = this.queryable(EmployeeConfig);");
|
|
742
|
+
expect(out).toContain("role = this.queryable(Role);");
|
|
743
|
+
expect(out).toContain("rolePermission = this.queryable(RolePermission);");
|
|
744
|
+
expect(out).toContain("dataLog = this.queryable(SystemDataLog);");
|
|
745
|
+
expect(out).toContain("systemLog = this.queryable(SystemLog);");
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it("인증 OFF → 로그 queryable 만, 사용자/역할 없음", async () => {
|
|
749
|
+
const data = buildData({
|
|
750
|
+
workspaceName: "demo",
|
|
751
|
+
description: "Demo",
|
|
752
|
+
hasServer: true,
|
|
753
|
+
hasDb: true,
|
|
754
|
+
dbDialect: "mysql",
|
|
755
|
+
hasAuth: false,
|
|
756
|
+
clients: [],
|
|
757
|
+
serverPort: 40080,
|
|
758
|
+
});
|
|
759
|
+
const out = await renderTemplate(path.join(TPL_ROOT, dbContextTpl), data);
|
|
760
|
+
expect(out).toContain("dataLog = this.queryable(SystemDataLog);");
|
|
761
|
+
expect(out).toContain("systemLog = this.queryable(SystemLog);");
|
|
762
|
+
expect(out).not.toContain("queryable(Role)");
|
|
763
|
+
expect(out).not.toContain("Config = this.queryable");
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
describe("common/src/db/tables", () => {
|
|
768
|
+
const authData = buildData({
|
|
769
|
+
workspaceName: "demo",
|
|
770
|
+
description: "Demo",
|
|
771
|
+
hasServer: true,
|
|
772
|
+
hasDb: true,
|
|
773
|
+
dbDialect: "mysql",
|
|
774
|
+
hasAuth: true,
|
|
775
|
+
userEntityName: "employee",
|
|
776
|
+
userEntityLabel: "직원",
|
|
777
|
+
clients: [],
|
|
778
|
+
serverPort: 40080,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it("master 사용자 테이블: Pascal 테이블명 + 한글 라벨 + config 역참조 (camel 관계명)", async () => {
|
|
782
|
+
const out = await renderTemplate(
|
|
783
|
+
path.join(TPL_ROOT, "common/src/db/tables/master/user.ts.hbs"),
|
|
784
|
+
authData,
|
|
785
|
+
);
|
|
786
|
+
expect(out).toContain('export const Employee = Table("Employee")');
|
|
787
|
+
expect(out).toContain('.description("직원")');
|
|
788
|
+
expect(out).toContain("configs: r.foreignKeyTarget(() => EmployeeConfig, \"employee\")");
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it("master 사용자-config 테이블: {camel}Id FK", async () => {
|
|
792
|
+
const out = await renderTemplate(
|
|
793
|
+
path.join(TPL_ROOT, "common/src/db/tables/master/user-config.ts.hbs"),
|
|
794
|
+
authData,
|
|
795
|
+
);
|
|
796
|
+
expect(out).toContain('export const EmployeeConfig = Table("EmployeeConfig")');
|
|
797
|
+
expect(out).toContain("employeeId: c.bigint(),");
|
|
798
|
+
expect(out).toContain('employee: r.foreignKey(["employeeId"], () => Employee)');
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it("system-data-log 인증 ON → {camel}Id FK 포함", async () => {
|
|
802
|
+
const out = await renderTemplate(
|
|
803
|
+
path.join(TPL_ROOT, "common/src/db/tables/system/system-data-log.ts.hbs"),
|
|
804
|
+
authData,
|
|
805
|
+
);
|
|
806
|
+
expect(out).toContain("employeeId: c.bigint().nullable(),");
|
|
807
|
+
expect(out).toContain('employee: r.foreignKey(["employeeId"], () => Employee)');
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it("system-data-log 인증 OFF → employee 컬럼/관계 없음, 문장 종료 정상", async () => {
|
|
811
|
+
const data = buildData({
|
|
812
|
+
workspaceName: "demo",
|
|
813
|
+
description: "Demo",
|
|
814
|
+
hasServer: true,
|
|
815
|
+
hasDb: true,
|
|
816
|
+
dbDialect: "mysql",
|
|
817
|
+
hasAuth: false,
|
|
818
|
+
clients: [],
|
|
819
|
+
serverPort: 40080,
|
|
820
|
+
});
|
|
821
|
+
const out = await renderTemplate(
|
|
822
|
+
path.join(TPL_ROOT, "common/src/db/tables/system/system-data-log.ts.hbs"),
|
|
823
|
+
data,
|
|
824
|
+
);
|
|
825
|
+
expect(out).not.toContain("employeeId");
|
|
826
|
+
expect(out).not.toContain(".relations(");
|
|
827
|
+
expect(out).not.toContain("master/");
|
|
828
|
+
expect(out.trimEnd().endsWith("]);")).toBe(true);
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
describe("common/src/db/system-data-log.ext.ts.hbs", () => {
|
|
833
|
+
it("인증 ON → {camel}Id/{camel}Name 조인 select 포함", async () => {
|
|
834
|
+
const data = buildData({
|
|
835
|
+
workspaceName: "demo",
|
|
836
|
+
description: "Demo",
|
|
837
|
+
hasServer: true,
|
|
838
|
+
hasDb: true,
|
|
839
|
+
dbDialect: "mysql",
|
|
840
|
+
hasAuth: true,
|
|
841
|
+
userEntityName: "employee",
|
|
842
|
+
userEntityLabel: "직원",
|
|
843
|
+
clients: [],
|
|
844
|
+
serverPort: 40080,
|
|
845
|
+
});
|
|
846
|
+
const out = await renderTemplate(
|
|
847
|
+
path.join(TPL_ROOT, "common/src/db/system-data-log.ext.ts.hbs"),
|
|
848
|
+
data,
|
|
849
|
+
);
|
|
850
|
+
expect(out).toContain("employeeId?: number;");
|
|
851
|
+
expect(out).toContain("employeeName?: string;");
|
|
852
|
+
expect(out).toContain(".include((dl) => dl.employee)");
|
|
853
|
+
expect(out).toContain("employeeName: dl.employee!.name,");
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it("인증 OFF → employee 조인 없음 (action/dateTime 만)", async () => {
|
|
857
|
+
const data = buildData({
|
|
858
|
+
workspaceName: "demo",
|
|
859
|
+
description: "Demo",
|
|
860
|
+
hasServer: true,
|
|
861
|
+
hasDb: true,
|
|
862
|
+
dbDialect: "mysql",
|
|
863
|
+
hasAuth: false,
|
|
864
|
+
clients: [],
|
|
865
|
+
serverPort: 40080,
|
|
866
|
+
});
|
|
867
|
+
const out = await renderTemplate(
|
|
868
|
+
path.join(TPL_ROOT, "common/src/db/system-data-log.ext.ts.hbs"),
|
|
869
|
+
data,
|
|
870
|
+
);
|
|
871
|
+
expect(out).not.toContain("employee");
|
|
872
|
+
expect(out).not.toContain(".include(");
|
|
873
|
+
});
|
|
291
874
|
});
|
|
292
875
|
|
|
293
876
|
describe("common/src/index.ts.hbs", () => {
|
|
294
|
-
it("DB=Y →
|
|
877
|
+
it("DB=Y 인증 ON → db폴더 경로 + 사용자/역할 re-export", async () => {
|
|
878
|
+
const data = buildData({
|
|
879
|
+
workspaceName: "demo",
|
|
880
|
+
description: "Demo",
|
|
881
|
+
hasServer: true,
|
|
882
|
+
hasDb: true,
|
|
883
|
+
dbDialect: "mysql",
|
|
884
|
+
hasAuth: true,
|
|
885
|
+
userEntityName: "employee",
|
|
886
|
+
userEntityLabel: "직원",
|
|
887
|
+
clients: [],
|
|
888
|
+
serverPort: 40080,
|
|
889
|
+
});
|
|
890
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/index.ts.hbs"), data);
|
|
891
|
+
expect(out).toContain('export * from "./db-main/main.db-context"');
|
|
892
|
+
expect(out).toContain('export * from "./db-main/tables/master/employee"');
|
|
893
|
+
expect(out).toContain('export * from "./db-main/tables/master/employee-config"');
|
|
894
|
+
expect(out).toContain('export * from "./db-main/tables/system/role"');
|
|
895
|
+
expect(out).toContain('export * from "./db-main/system-data-log.ext"');
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("DB=Y 인증 OFF → 로그 테이블만 re-export, 사용자/역할 없음", async () => {
|
|
295
899
|
const data = buildData({
|
|
296
900
|
workspaceName: "demo",
|
|
297
901
|
description: "Demo",
|
|
298
902
|
hasServer: true,
|
|
299
903
|
hasDb: true,
|
|
300
904
|
dbDialect: "mysql",
|
|
905
|
+
hasAuth: false,
|
|
301
906
|
clients: [],
|
|
302
907
|
serverPort: 40080,
|
|
303
908
|
});
|
|
304
909
|
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/index.ts.hbs"), data);
|
|
305
|
-
expect(out).toContain('export * from "./
|
|
910
|
+
expect(out).toContain('export * from "./db-main/main.db-context"');
|
|
911
|
+
expect(out).toContain('export * from "./db-main/tables/system/system-data-log"');
|
|
912
|
+
expect(out).not.toContain("master/");
|
|
913
|
+
expect(out).not.toContain("tables/system/role");
|
|
306
914
|
});
|
|
307
915
|
|
|
308
916
|
it("DB=N → 빈 export", async () => {
|
|
@@ -355,3 +963,336 @@ describe("root/package.json.hbs", () => {
|
|
|
355
963
|
expect(out).not.toContain("run-device");
|
|
356
964
|
});
|
|
357
965
|
});
|
|
966
|
+
|
|
967
|
+
describe("common/src/auth-info-changed.event.ts.hbs", () => {
|
|
968
|
+
it("인증 ON → AuthInfoChangedEvent 정의 (선택 네이밍 반영)", async () => {
|
|
969
|
+
const data = buildData({
|
|
970
|
+
workspaceName: "demo",
|
|
971
|
+
description: "Demo",
|
|
972
|
+
hasServer: true,
|
|
973
|
+
hasDb: true,
|
|
974
|
+
dbDialect: "mysql",
|
|
975
|
+
hasAuth: true,
|
|
976
|
+
userEntityName: "employee",
|
|
977
|
+
userEntityLabel: "직원",
|
|
978
|
+
clients: [],
|
|
979
|
+
serverPort: 40080,
|
|
980
|
+
});
|
|
981
|
+
const out = await renderTemplate(
|
|
982
|
+
path.join(TPL_ROOT, "common/src/auth-info-changed.event.ts.hbs"),
|
|
983
|
+
data,
|
|
984
|
+
);
|
|
985
|
+
expect(out).toContain('import { defineEvent } from "@simplysm/service-common";');
|
|
986
|
+
expect(out).toContain(
|
|
987
|
+
'export const AuthInfoChangedEvent = defineEvent<{ employeeId: number }, void>("AuthInfoChanged");',
|
|
988
|
+
);
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
describe("common/src/index.ts.hbs auth-info-changed export", () => {
|
|
993
|
+
it("인증 ON → auth-info-changed.event re-export", async () => {
|
|
994
|
+
const data = buildData({
|
|
995
|
+
workspaceName: "demo",
|
|
996
|
+
description: "Demo",
|
|
997
|
+
hasServer: true,
|
|
998
|
+
hasDb: true,
|
|
999
|
+
dbDialect: "mysql",
|
|
1000
|
+
hasAuth: true,
|
|
1001
|
+
clients: [],
|
|
1002
|
+
serverPort: 40080,
|
|
1003
|
+
});
|
|
1004
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/index.ts.hbs"), data);
|
|
1005
|
+
expect(out).toContain('export * from "./auth-info-changed.event";');
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
it("인증 OFF → auth-info-changed.event 없음", async () => {
|
|
1009
|
+
const data = buildData({
|
|
1010
|
+
workspaceName: "demo",
|
|
1011
|
+
description: "Demo",
|
|
1012
|
+
hasServer: true,
|
|
1013
|
+
hasDb: true,
|
|
1014
|
+
dbDialect: "mysql",
|
|
1015
|
+
hasAuth: false,
|
|
1016
|
+
clients: [],
|
|
1017
|
+
serverPort: 40080,
|
|
1018
|
+
});
|
|
1019
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/index.ts.hbs"), data);
|
|
1020
|
+
expect(out).not.toContain("auth-info-changed");
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
describe("client-common 인증정보 변경 구독 배선", () => {
|
|
1025
|
+
const data = buildData({
|
|
1026
|
+
workspaceName: "demo",
|
|
1027
|
+
description: "Demo",
|
|
1028
|
+
hasServer: true,
|
|
1029
|
+
hasDb: true,
|
|
1030
|
+
dbDialect: "mysql",
|
|
1031
|
+
hasAuth: true,
|
|
1032
|
+
userEntityName: "employee",
|
|
1033
|
+
userEntityLabel: "직원",
|
|
1034
|
+
clients: [],
|
|
1035
|
+
serverPort: 40080,
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
it("app-service: authInfoChangedEvent getter + ClientEventProxy/이벤트 import", async () => {
|
|
1039
|
+
const out = await renderTemplate(
|
|
1040
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-service.provider.ts.hbs"),
|
|
1041
|
+
data,
|
|
1042
|
+
);
|
|
1043
|
+
expect(out).toContain("type ClientEventProxy,");
|
|
1044
|
+
expect(out).toContain('import { AuthInfoChangedEvent } from "@demo/common";');
|
|
1045
|
+
expect(out).toContain(
|
|
1046
|
+
"get authInfoChangedEvent(): ClientEventProxy<typeof AuthInfoChangedEvent> {",
|
|
1047
|
+
);
|
|
1048
|
+
expect(out).toContain(
|
|
1049
|
+
"this._authInfoChangedEvent ??= this.client.getEvent(AuthInfoChangedEvent)",
|
|
1050
|
+
);
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
it("app-auth: 이벤트 구독 등록/해제 + 비동기 logout", async () => {
|
|
1054
|
+
const out = await renderTemplate(
|
|
1055
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-auth.provider.ts.hbs"),
|
|
1056
|
+
data,
|
|
1057
|
+
);
|
|
1058
|
+
expect(out).toContain("async logout(): Promise<void> {");
|
|
1059
|
+
expect(out).toContain("await this._unregisterAuthEvent();");
|
|
1060
|
+
expect(out).toContain("await this._registerAuthEvent(authData.employeeId);");
|
|
1061
|
+
expect(out).toContain("private async _registerAuthEvent(employeeId: number): Promise<void> {");
|
|
1062
|
+
expect(out).toContain("this._appService.authInfoChangedEvent.addListener(");
|
|
1063
|
+
});
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
describe("client/src/app/home/my-info/my-info.detail.ts.hbs", () => {
|
|
1067
|
+
const data = buildData({
|
|
1068
|
+
workspaceName: "demo",
|
|
1069
|
+
description: "Demo",
|
|
1070
|
+
hasServer: true,
|
|
1071
|
+
hasDb: true,
|
|
1072
|
+
dbDialect: "mysql",
|
|
1073
|
+
hasAuth: true,
|
|
1074
|
+
userEntityName: "employee",
|
|
1075
|
+
userEntityLabel: "직원",
|
|
1076
|
+
clients: [{ name: "admin", type: "web", hasRouter: true }],
|
|
1077
|
+
serverPort: 40080,
|
|
1078
|
+
});
|
|
1079
|
+
const ctx = { ...data, client: data.clients[0] };
|
|
1080
|
+
|
|
1081
|
+
it("내정보수정 화면: 네이밍 반영 + Angular 보간 복원 + update 호출", async () => {
|
|
1082
|
+
const out = await renderTemplate(
|
|
1083
|
+
path.join(TPL_ROOT, "client/src/app/home/my-info/my-info.detail.ts.hbs"),
|
|
1084
|
+
ctx,
|
|
1085
|
+
);
|
|
1086
|
+
expect(out).toContain("export class MyInfoDetail {");
|
|
1087
|
+
expect(out).toContain(
|
|
1088
|
+
'import { AppAuthProvider, AppOrmProvider, AppServiceProvider } from "@demo/client-common";',
|
|
1089
|
+
);
|
|
1090
|
+
expect(out).toContain('import { EmployeeConfig } from "@demo/common";');
|
|
1091
|
+
expect(out).toContain('import type { IEmployeeConfigMap } from "@demo/server";');
|
|
1092
|
+
expect(out).toContain(".employee()");
|
|
1093
|
+
expect(out).toContain(".from(EmployeeConfig)");
|
|
1094
|
+
expect(out).toContain("this._appAuth.authInfo()?.employeeId");
|
|
1095
|
+
expect(out).toContain("lastModifiedBy: item.lastDataLog?.employeeName,");
|
|
1096
|
+
expect(out).toContain("await this._appService.auth.update({");
|
|
1097
|
+
expect(out).toContain('{{ flatMenu.titleChain.join(" / ") }}');
|
|
1098
|
+
expect(out).not.toContain("\\{{");
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
describe("client/src/routes.ts.hbs my-info 라우트", () => {
|
|
1103
|
+
it("인증 ON → my-info lazy 라우트 포함", async () => {
|
|
1104
|
+
const data = buildData({
|
|
1105
|
+
workspaceName: "demo",
|
|
1106
|
+
description: "Demo",
|
|
1107
|
+
hasServer: true,
|
|
1108
|
+
hasDb: true,
|
|
1109
|
+
dbDialect: "mysql",
|
|
1110
|
+
hasAuth: true,
|
|
1111
|
+
clients: [{ name: "admin", type: "web", hasRouter: true }],
|
|
1112
|
+
serverPort: 40080,
|
|
1113
|
+
});
|
|
1114
|
+
const ctx = { ...data, client: data.clients[0] };
|
|
1115
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/routes.ts.hbs"), ctx);
|
|
1116
|
+
expect(out).toContain(
|
|
1117
|
+
'import("./app/home/my-info/my-info.detail").then((m) => m.MyInfoDetail)',
|
|
1118
|
+
);
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
describe("server/src/services/dev.service.ts.hbs", () => {
|
|
1123
|
+
it("인증 ON → 엑셀 시드(역할/직원/역할권한) + 사용자 네이밍 반영", async () => {
|
|
1124
|
+
const data = buildData({
|
|
1125
|
+
workspaceName: "demo",
|
|
1126
|
+
description: "Demo",
|
|
1127
|
+
hasServer: true,
|
|
1128
|
+
hasDb: true,
|
|
1129
|
+
dbDialect: "mysql",
|
|
1130
|
+
hasAuth: true,
|
|
1131
|
+
userEntityName: "employee",
|
|
1132
|
+
userEntityLabel: "직원",
|
|
1133
|
+
clients: [],
|
|
1134
|
+
serverPort: 40080,
|
|
1135
|
+
});
|
|
1136
|
+
const out = await renderTemplate(
|
|
1137
|
+
path.join(TPL_ROOT, "server/src/services/dev.service.ts.hbs"),
|
|
1138
|
+
data,
|
|
1139
|
+
);
|
|
1140
|
+
expect(out).toContain('export const DevService = defineService("DevService"');
|
|
1141
|
+
expect(out).toContain("await db.initialize({ force: true });");
|
|
1142
|
+
expect(out).toContain('import { ExcelWorkbook } from "@simplysm/excel";');
|
|
1143
|
+
expect(out).toContain('import { appStructureItems } from "@demo/common";');
|
|
1144
|
+
expect(out).toContain(".employee().insert(");
|
|
1145
|
+
expect(out).toContain("getFlatPermissions(appStructureItems, undefined)");
|
|
1146
|
+
expect(out).toContain("// TODO: 업무 테이블 초기 데이터 시드 추가");
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
it("인증 OFF (DB만) → 시드 없이 db.initialize + TODO", async () => {
|
|
1150
|
+
const data = buildData({
|
|
1151
|
+
workspaceName: "demo",
|
|
1152
|
+
description: "Demo",
|
|
1153
|
+
hasServer: true,
|
|
1154
|
+
hasDb: true,
|
|
1155
|
+
dbDialect: "mysql",
|
|
1156
|
+
hasAuth: false,
|
|
1157
|
+
clients: [],
|
|
1158
|
+
serverPort: 40080,
|
|
1159
|
+
});
|
|
1160
|
+
const out = await renderTemplate(
|
|
1161
|
+
path.join(TPL_ROOT, "server/src/services/dev.service.ts.hbs"),
|
|
1162
|
+
data,
|
|
1163
|
+
);
|
|
1164
|
+
expect(out).toContain("await db.initialize({ force: true });");
|
|
1165
|
+
expect(out).toContain("// TODO: 초기 데이터 시드 추가");
|
|
1166
|
+
expect(out).not.toContain("ExcelWorkbook");
|
|
1167
|
+
expect(out).not.toContain("bcrypt");
|
|
1168
|
+
expect(out).not.toContain(".insert(");
|
|
1169
|
+
});
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
describe("server/src/index.ts.hbs dev export", () => {
|
|
1173
|
+
it("DB ON 인증 OFF → DevServiceMethods 만 export", async () => {
|
|
1174
|
+
const data = buildData({
|
|
1175
|
+
workspaceName: "demo",
|
|
1176
|
+
description: "Demo",
|
|
1177
|
+
hasServer: true,
|
|
1178
|
+
hasDb: true,
|
|
1179
|
+
dbDialect: "mysql",
|
|
1180
|
+
hasAuth: false,
|
|
1181
|
+
clients: [],
|
|
1182
|
+
serverPort: 40080,
|
|
1183
|
+
});
|
|
1184
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/index.ts.hbs"), data);
|
|
1185
|
+
expect(out).toContain('export type { DevServiceMethods } from "./services/dev.service";');
|
|
1186
|
+
expect(out).not.toContain("auth.service");
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it("인증 ON → auth + dev export", async () => {
|
|
1190
|
+
const data = buildData({
|
|
1191
|
+
workspaceName: "demo",
|
|
1192
|
+
description: "Demo",
|
|
1193
|
+
hasServer: true,
|
|
1194
|
+
hasDb: true,
|
|
1195
|
+
dbDialect: "mysql",
|
|
1196
|
+
hasAuth: true,
|
|
1197
|
+
clients: [],
|
|
1198
|
+
serverPort: 40080,
|
|
1199
|
+
});
|
|
1200
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/index.ts.hbs"), data);
|
|
1201
|
+
expect(out).toContain('from "./services/auth.service";');
|
|
1202
|
+
expect(out).toContain('export type { DevServiceMethods } from "./services/dev.service";');
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
describe("client-common app-service dev getter", () => {
|
|
1207
|
+
it("DB ON → dev getter + DevServiceMethods import", async () => {
|
|
1208
|
+
const data = buildData({
|
|
1209
|
+
workspaceName: "demo",
|
|
1210
|
+
description: "Demo",
|
|
1211
|
+
hasServer: true,
|
|
1212
|
+
hasDb: true,
|
|
1213
|
+
dbDialect: "mysql",
|
|
1214
|
+
hasAuth: false,
|
|
1215
|
+
clients: [],
|
|
1216
|
+
serverPort: 40080,
|
|
1217
|
+
});
|
|
1218
|
+
const out = await renderTemplate(
|
|
1219
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-service.provider.ts.hbs"),
|
|
1220
|
+
data,
|
|
1221
|
+
);
|
|
1222
|
+
expect(out).toContain('import type { DevServiceMethods } from "@demo/server";');
|
|
1223
|
+
expect(out).toContain("type ServiceProxy,");
|
|
1224
|
+
expect(out).toContain("get dev(): ServiceProxy<DevServiceMethods> {");
|
|
1225
|
+
expect(out).toContain('this._dev ??= this.client.getService<DevServiceMethods>("DevService")');
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
it("DB OFF → dev getter 없음", async () => {
|
|
1229
|
+
const data = buildData({
|
|
1230
|
+
workspaceName: "demo",
|
|
1231
|
+
description: "Demo",
|
|
1232
|
+
hasServer: true,
|
|
1233
|
+
hasDb: false,
|
|
1234
|
+
clients: [],
|
|
1235
|
+
serverPort: 40080,
|
|
1236
|
+
});
|
|
1237
|
+
const out = await renderTemplate(
|
|
1238
|
+
path.join(TPL_ROOT, "client-common/src/providers/app-service.provider.ts.hbs"),
|
|
1239
|
+
data,
|
|
1240
|
+
);
|
|
1241
|
+
expect(out).not.toContain("DevServiceMethods");
|
|
1242
|
+
expect(out).not.toContain("get dev()");
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
describe("client/src/app.root.ts.hbs dev 도구 배선", () => {
|
|
1247
|
+
function devCtx(hasDb: boolean, hasRouter = true) {
|
|
1248
|
+
const data = buildData({
|
|
1249
|
+
workspaceName: "demo",
|
|
1250
|
+
description: "Demo",
|
|
1251
|
+
hasServer: hasDb,
|
|
1252
|
+
hasDb,
|
|
1253
|
+
dbDialect: "mysql",
|
|
1254
|
+
clients: [{ name: "admin", type: "web", hasRouter }],
|
|
1255
|
+
serverPort: 40080,
|
|
1256
|
+
});
|
|
1257
|
+
return { ...data, client: data.clients[0] };
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
it("DB ON → HostListener keydown + DevModal 호출", async () => {
|
|
1261
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app.root.ts.hbs"), devCtx(true));
|
|
1262
|
+
expect(out).toContain('@HostListener("document:keydown", ["$event"])');
|
|
1263
|
+
expect(out).toContain('import { DevModal } from "./modals/dev.modal";');
|
|
1264
|
+
expect(out).toContain("this._sdModal.showAsync({ type: DevModal");
|
|
1265
|
+
expect(out).toContain("export class AppRoot {");
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it("DB OFF → 빈 AppRoot (dev 배선 없음)", async () => {
|
|
1269
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app.root.ts.hbs"), devCtx(false));
|
|
1270
|
+
expect(out).toContain("export class AppRoot {}");
|
|
1271
|
+
expect(out).not.toContain("HostListener");
|
|
1272
|
+
expect(out).not.toContain("DevModal");
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
describe("client/src/modals/dev.modal.ts.hbs", () => {
|
|
1277
|
+
it("dev 모달: client-common import + initDb 호출 (아이콘 의존 없음)", async () => {
|
|
1278
|
+
const data = buildData({
|
|
1279
|
+
workspaceName: "demo",
|
|
1280
|
+
description: "Demo",
|
|
1281
|
+
hasServer: true,
|
|
1282
|
+
hasDb: true,
|
|
1283
|
+
dbDialect: "mysql",
|
|
1284
|
+
hasAuth: true,
|
|
1285
|
+
clients: [{ name: "admin", type: "web", hasRouter: true }],
|
|
1286
|
+
serverPort: 40080,
|
|
1287
|
+
});
|
|
1288
|
+
const ctx = { ...data, client: data.clients[0] };
|
|
1289
|
+
const out = await renderTemplate(
|
|
1290
|
+
path.join(TPL_ROOT, "client/src/modals/dev.modal.ts.hbs"),
|
|
1291
|
+
ctx,
|
|
1292
|
+
);
|
|
1293
|
+
expect(out).toContain("export class DevModal implements SdModalContentDef<void> {");
|
|
1294
|
+
expect(out).toContain('import { AppServiceProvider } from "@demo/client-common";');
|
|
1295
|
+
expect(out).toContain("await this._appService.dev.initDb();");
|
|
1296
|
+
expect(out).not.toContain("@ng-icons");
|
|
1297
|
+
});
|
|
1298
|
+
});
|