@gadmin2n/schematics 0.0.135 → 0.0.137

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +7 -6
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/gen-jwt.ts +85 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +1 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
  55. package/package.json +1 -1
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
@@ -1,11 +1,12 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import { UserModule } from '../user/user.module';
3
+ import { CanvasExportService } from './canvas-export.service';
3
4
  import { CanvasController } from './canvas.controller';
4
5
  import { CanvasService } from './canvas.service';
5
6
 
6
7
  @Module({
7
8
  imports: [UserModule],
8
9
  controllers: [CanvasController],
9
- providers: [CanvasService],
10
+ providers: [CanvasService, CanvasExportService],
10
11
  })
11
12
  export class CanvasModule {}
@@ -0,0 +1,54 @@
1
+ /// <reference types="jest" />
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { Test, TestingModule } from '@nestjs/testing';
4
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
5
+ import { TemporalTenantConfigController } from './temporalTenantConfig.controller';
6
+ import { TemporalTenantConfigService } from './temporalTenantConfig.service';
7
+
8
+ // Mock TemporalTenantConfigService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
9
+ // 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
10
+ const mockTemporalTenantConfigService = {};
11
+
12
+ // Mock ConfigService
13
+ const mockConfigService = {
14
+ get: jest.fn(),
15
+ };
16
+
17
+ // Mock Logger
18
+ const mockLogger = {
19
+ child: jest.fn().mockReturnThis(),
20
+ info: jest.fn(),
21
+ error: jest.fn(),
22
+ warn: jest.fn(),
23
+ debug: jest.fn(),
24
+ };
25
+
26
+ describe('TemporalTenantConfigController', () => {
27
+ let controller: TemporalTenantConfigController;
28
+
29
+ beforeEach(async () => {
30
+ const module: TestingModule = await Test.createTestingModule({
31
+ controllers: [TemporalTenantConfigController],
32
+ providers: [
33
+ {
34
+ provide: TemporalTenantConfigService,
35
+ useValue: mockTemporalTenantConfigService,
36
+ },
37
+ { provide: ConfigService, useValue: mockConfigService },
38
+ { provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
39
+ ],
40
+ })
41
+ // 兜底:自动 mock 任何未显式提供的依赖
42
+ // (例如 ACGuard 内部的 __roles_builder__ token)
43
+ .useMocker(() => ({}))
44
+ .compile();
45
+
46
+ controller = module.get<TemporalTenantConfigController>(
47
+ TemporalTenantConfigController,
48
+ );
49
+ });
50
+
51
+ it('should be defined', () => {
52
+ expect(controller).toBeDefined();
53
+ });
54
+ });
@@ -0,0 +1,245 @@
1
+ import {
2
+ ACAttrFilter,
3
+ ACGuard,
4
+ ApiPaginatedResponse,
5
+ diskStorage,
6
+ PageDto,
7
+ PageMetaDto,
8
+ PageOptionsDto,
9
+ pick,
10
+ UserType,
11
+ } from '@gadmin2n/nest-common';
12
+ import {
13
+ BadRequestException,
14
+ Body,
15
+ Controller,
16
+ Delete,
17
+ Inject,
18
+ NotFoundException,
19
+ Param,
20
+ ParseIntPipe,
21
+ Patch,
22
+ Post,
23
+ Req,
24
+ UploadedFile,
25
+ UseGuards,
26
+ UseInterceptors,
27
+ } from '@nestjs/common';
28
+ import {
29
+ ApiCreatedResponse,
30
+ ApiNotFoundResponse,
31
+ ApiOkResponse,
32
+ ApiTags,
33
+ } from '@nestjs/swagger';
34
+ import { Prisma } from '@prisma/client';
35
+ import { UseRoles } from 'nest-access-control';
36
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
37
+ import { Logger } from 'winston';
38
+ import { CreateTemporalTenantConfigDto } from '../../generated/temporalTenantConfig/dto/create-temporalTenantConfig.dto';
39
+ import {
40
+ EffectedCount,
41
+ TemporalTenantConfigFindManyArgs,
42
+ TemporalTenantConfigWhereSelect,
43
+ UploadResponse,
44
+ } from '../../generated/temporalTenantConfig/dto/temporalTenantConfig-types.dto';
45
+ import { UpdateTemporalTenantConfigDto } from '../../generated/temporalTenantConfig/dto/update-temporalTenantConfig.dto';
46
+ import { TemporalTenantConfigEntity } from '../../generated/temporalTenantConfig/entities/temporalTenantConfig.entity';
47
+ import { TemporalTenantConfigService } from './temporalTenantConfig.service';
48
+
49
+ import { ConfigService } from '@nestjs/config';
50
+ import { FileInterceptor } from '@nestjs/platform-express';
51
+
52
+ @UseGuards(ACGuard)
53
+ @Controller('temporalTenantConfig')
54
+ @ApiTags('temporalTenantConfig')
55
+ export class TemporalTenantConfigController {
56
+ constructor(
57
+ private readonly temporalTenantConfigService: TemporalTenantConfigService,
58
+ private readonly config: ConfigService,
59
+ @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
60
+ ) {}
61
+
62
+ @Post('createOne')
63
+ @UseRoles({
64
+ resource: 'temporalTenantConfig',
65
+ action: 'create',
66
+ possession: 'any',
67
+ })
68
+ @ApiCreatedResponse({ type: TemporalTenantConfigEntity })
69
+ async createOne(
70
+ @Body() body: CreateTemporalTenantConfigDto,
71
+ @ACAttrFilter() acAttrFilter,
72
+ @Req() request: Request & { user: UserType },
73
+ ) {
74
+ body.creator = request.user.username;
75
+
76
+ // 先调 shared temporal admin API(原子:失败则不写 DB)
77
+ const res = await fetch(`${body.adminUrl}/admin/tenants`, {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ 'X-Admin-Token': body.adminToken,
82
+ },
83
+ body: JSON.stringify({
84
+ namespace: body.namespace,
85
+ displayName: body.displayName,
86
+ dbUrl: body.dbUrl,
87
+ }),
88
+ });
89
+ if (!res.ok) {
90
+ const detail = await res.text().catch(() => '');
91
+ throw new BadRequestException(
92
+ `shared temporal API 注册失败 (${res.status}): ${detail}`,
93
+ );
94
+ }
95
+
96
+ return new TemporalTenantConfigEntity(
97
+ await this.temporalTenantConfigService.createOne({
98
+ data: acAttrFilter(body),
99
+ }),
100
+ );
101
+ }
102
+
103
+ @Post('findMany')
104
+ @UseRoles({
105
+ resource: 'temporalTenantConfig',
106
+ action: 'read',
107
+ possession: 'any',
108
+ })
109
+ @ApiPaginatedResponse(TemporalTenantConfigEntity)
110
+ async findMany(
111
+ @Body() body: TemporalTenantConfigFindManyArgs,
112
+ @ACAttrFilter() acAttrFilter,
113
+ ): Promise<PageDto<TemporalTenantConfigEntity>> {
114
+ const { itemCount, entities } =
115
+ await this.temporalTenantConfigService.findMany(
116
+ body as Prisma.TemporalTenantConfigFindManyArgs,
117
+ );
118
+ const { skip = 0, take = 10 } =
119
+ body as Prisma.TemporalTenantConfigFindManyArgs;
120
+ const pageOptionsDto: PageOptionsDto = { skip, take };
121
+ const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
122
+
123
+ return new PageDto(
124
+ entities.map(
125
+ (item) => new TemporalTenantConfigEntity(acAttrFilter(item)),
126
+ ),
127
+ pageMetaDto,
128
+ );
129
+ }
130
+
131
+ @Post('/findUnique/:id')
132
+ @UseRoles({
133
+ resource: 'temporalTenantConfig',
134
+ action: 'read',
135
+ possession: 'any',
136
+ })
137
+ @ApiOkResponse({ type: TemporalTenantConfigEntity })
138
+ @ApiNotFoundResponse({ description: 'Cannot found this record.' })
139
+ async findUnique(
140
+ @Param('id', ParseIntPipe) id: number,
141
+ @Body() body: TemporalTenantConfigWhereSelect,
142
+ @ACAttrFilter() acAttrFilter,
143
+ ) {
144
+ const item = await this.temporalTenantConfigService.findUnique(id, body);
145
+ if (!item) {
146
+ throw new NotFoundException();
147
+ }
148
+ return new TemporalTenantConfigEntity(acAttrFilter(item));
149
+ }
150
+
151
+ @Patch('updateUnique/:id')
152
+ @UseRoles({
153
+ resource: 'temporalTenantConfig',
154
+ action: 'update',
155
+ possession: 'any',
156
+ })
157
+ @ApiOkResponse({ type: TemporalTenantConfigEntity })
158
+ @ApiNotFoundResponse({ description: 'Cannot found this record.' })
159
+ async updateUnique(
160
+ @Param('id', ParseIntPipe) id: number,
161
+ @Body() body: UpdateTemporalTenantConfigDto,
162
+ @ACAttrFilter() acAttrFilter,
163
+ ) {
164
+ const existing = await this.temporalTenantConfigService.findUnique(id, {
165
+ adminUrl: true,
166
+ adminToken: true,
167
+ namespace: true,
168
+ });
169
+ if (!existing) throw new NotFoundException();
170
+
171
+ const adminUrl = (body.adminUrl ?? existing.adminUrl) as string;
172
+ const adminToken = (body.adminToken ?? existing.adminToken) as string;
173
+ const namespace = (body.namespace ?? existing.namespace) as string;
174
+
175
+ const patchBody: Record<string, string> = {};
176
+ if (body.dbUrl) patchBody.dbUrl = body.dbUrl;
177
+ if (body.displayName) patchBody.displayName = body.displayName;
178
+
179
+ if (Object.keys(patchBody).length > 0) {
180
+ const res = await fetch(
181
+ `${adminUrl}/admin/tenants/${encodeURIComponent(namespace)}`,
182
+ {
183
+ method: 'PATCH',
184
+ headers: {
185
+ 'Content-Type': 'application/json',
186
+ 'X-Admin-Token': adminToken,
187
+ },
188
+ body: JSON.stringify(patchBody),
189
+ },
190
+ );
191
+ if (!res.ok) {
192
+ const detail = await res.text().catch(() => '');
193
+ throw new BadRequestException(
194
+ `shared temporal API 更新失败 (${res.status}): ${detail}`,
195
+ );
196
+ }
197
+ }
198
+
199
+ const item = await this.temporalTenantConfigService.updateUnique(
200
+ id,
201
+ acAttrFilter(body),
202
+ );
203
+ return new TemporalTenantConfigEntity(acAttrFilter(item));
204
+ }
205
+
206
+ @Delete('deleteUnique/:id')
207
+ @UseRoles({
208
+ resource: 'temporalTenantConfig',
209
+ action: 'delete',
210
+ possession: 'any',
211
+ })
212
+ @ApiOkResponse({ type: EffectedCount })
213
+ @ApiNotFoundResponse({ description: 'Cannot found this record.' })
214
+ async deleteUnique(@Param('id', ParseIntPipe) id: number) {
215
+ const res = await this.temporalTenantConfigService.deleteUnique(id);
216
+ if (!res.count) {
217
+ throw new NotFoundException();
218
+ }
219
+ return res;
220
+ }
221
+
222
+ @Post('upload')
223
+ @UseRoles({
224
+ resource: 'temporalTenantConfig',
225
+ action: 'create',
226
+ possession: 'any',
227
+ })
228
+ @ApiCreatedResponse({ type: UploadResponse })
229
+ @UseInterceptors(FileInterceptor('file'))
230
+ async uploadFile(@UploadedFile() file: Express.Multer.File) {
231
+ const fileName = await diskStorage(
232
+ file,
233
+ this.config.get('media')['rootDir'] + '/temporalTenantConfig/',
234
+ );
235
+
236
+ return {
237
+ ...pick(file, ['mimetype', 'originalname', 'size']),
238
+ url:
239
+ this.config.get('media')['address'] +
240
+ '/temporalTenantConfig/' +
241
+ fileName,
242
+ name: fileName,
243
+ };
244
+ }
245
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { TemporalTenantConfigController } from './temporalTenantConfig.controller';
3
+ import { TemporalTenantConfigService } from './temporalTenantConfig.service';
4
+
5
+ @Module({
6
+ controllers: [TemporalTenantConfigController],
7
+ providers: [TemporalTenantConfigService],
8
+ exports: [TemporalTenantConfigService],
9
+ })
10
+ export class TemporalTenantConfigModule {}
@@ -0,0 +1,366 @@
1
+ /// <reference types="jest" />
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { Test, TestingModule } from '@nestjs/testing';
4
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
5
+
6
+ // Mock nestjs-prisma 模块
7
+ jest.mock('nestjs-prisma', () => ({
8
+ PrismaService: jest.fn(),
9
+ }));
10
+
11
+ import { PrismaService } from 'nestjs-prisma';
12
+ import { TemporalTenantConfigService } from './temporalTenantConfig.service';
13
+
14
+ // Mock PrismaService
15
+ const mockPrismaService = {
16
+ temporalTenantConfig: {
17
+ create: jest.fn(),
18
+ createMany: jest.fn(),
19
+ findMany: jest.fn(),
20
+ findUnique: jest.fn(),
21
+ update: jest.fn(),
22
+ updateMany: jest.fn(),
23
+ delete: jest.fn(),
24
+ deleteMany: jest.fn(),
25
+ count: jest.fn(),
26
+ groupBy: jest.fn(),
27
+ aggregate: jest.fn(),
28
+ },
29
+ };
30
+
31
+ // Mock Logger
32
+ const mockLogger = {
33
+ child: jest.fn().mockReturnThis(),
34
+ info: jest.fn(),
35
+ error: jest.fn(),
36
+ warn: jest.fn(),
37
+ debug: jest.fn(),
38
+ };
39
+
40
+ // Mock ConfigService
41
+ const mockConfigService = {
42
+ get: jest.fn(),
43
+ };
44
+
45
+ describe('TemporalTenantConfigService', () => {
46
+ let service: TemporalTenantConfigService;
47
+ let prisma: typeof mockPrismaService;
48
+
49
+ beforeEach(async () => {
50
+ // 每个测试前重置所有 mock
51
+ jest.clearAllMocks();
52
+
53
+ const module: TestingModule = await Test.createTestingModule({
54
+ providers: [
55
+ TemporalTenantConfigService,
56
+ { provide: PrismaService, useValue: mockPrismaService },
57
+ { provide: ConfigService, useValue: mockConfigService },
58
+ { provide: WINSTON_MODULE_PROVIDER, useValue: mockLogger },
59
+ ],
60
+ })
61
+ // 兜底:自动 mock 任何未显式提供的依赖
62
+ // (例如手工给 service 增加的额外依赖:RoleService、其他 Service 等)
63
+ .useMocker(() => ({}))
64
+ .compile();
65
+
66
+ service = module.get<TemporalTenantConfigService>(
67
+ TemporalTenantConfigService,
68
+ );
69
+ prisma = mockPrismaService;
70
+ });
71
+
72
+ it('should be defined', () => {
73
+ expect(service).toBeDefined();
74
+ });
75
+
76
+ describe('createOne', () => {
77
+ it('should create a single temporalTenantConfig record', async () => {
78
+ const createArgs = {
79
+ data: {
80
+ adminUrl: 'test_adminUrl',
81
+ adminToken: 'test_adminToken',
82
+ namespace: 'test_namespace',
83
+ dbUrl: 'test_dbUrl',
84
+ displayName: 'test_displayName',
85
+ creator: 'test_creator',
86
+ },
87
+ } as any;
88
+ const expectedResult = { id: 1, ...createArgs.data };
89
+
90
+ prisma.temporalTenantConfig.create.mockResolvedValue(expectedResult);
91
+
92
+ const result = await service.createOne(createArgs);
93
+
94
+ expect(prisma.temporalTenantConfig.create).toHaveBeenCalledWith(
95
+ createArgs,
96
+ );
97
+ expect(prisma.temporalTenantConfig.create).toHaveBeenCalledTimes(1);
98
+ expect(result).toEqual(expectedResult);
99
+ });
100
+
101
+ it('should throw error when create fails', async () => {
102
+ const createArgs = {
103
+ data: {
104
+ adminUrl: 'test_adminUrl',
105
+ adminToken: 'test_adminToken',
106
+ namespace: 'test_namespace',
107
+ dbUrl: 'test_dbUrl',
108
+ displayName: 'test_displayName',
109
+ creator: 'test_creator',
110
+ },
111
+ } as any;
112
+ const error = new Error('Database connection failed');
113
+
114
+ prisma.temporalTenantConfig.create.mockRejectedValue(error);
115
+
116
+ await expect(service.createOne(createArgs)).rejects.toThrow(
117
+ 'Database connection failed',
118
+ );
119
+ });
120
+ });
121
+
122
+ describe('createMany', () => {
123
+ it('should create multiple temporalTenantConfig records with skipDuplicates', async () => {
124
+ const data = [
125
+ {
126
+ adminUrl: 'test_adminUrl',
127
+ adminToken: 'test_adminToken',
128
+ namespace: 'test_namespace',
129
+ dbUrl: 'test_dbUrl',
130
+ displayName: 'test_displayName',
131
+ creator: 'test_creator',
132
+ },
133
+ {
134
+ adminUrl: 'test_adminUrl',
135
+ adminToken: 'test_adminToken',
136
+ namespace: 'test_namespace',
137
+ dbUrl: 'test_dbUrl',
138
+ displayName: 'test_displayName',
139
+ creator: 'test_creator',
140
+ },
141
+ ] as any;
142
+ const expectedResult = { count: 2 };
143
+
144
+ prisma.temporalTenantConfig.createMany.mockResolvedValue(expectedResult);
145
+
146
+ const result = await service.createMany(data);
147
+
148
+ expect(prisma.temporalTenantConfig.createMany).toHaveBeenCalledWith({
149
+ data,
150
+ skipDuplicates: true,
151
+ });
152
+ expect(result).toEqual(expectedResult);
153
+ });
154
+ });
155
+
156
+ describe('findMany', () => {
157
+ it('should return entities with itemCount', async () => {
158
+ const findArgs = {
159
+ where: {},
160
+ take: 10,
161
+ skip: 0,
162
+ } as any;
163
+ const mockEntities = [{ id: 1 }, { id: 2 }];
164
+
165
+ prisma.temporalTenantConfig.count.mockResolvedValue(2);
166
+ prisma.temporalTenantConfig.findMany.mockResolvedValue(mockEntities);
167
+
168
+ const result = await service.findMany(findArgs);
169
+
170
+ expect(prisma.temporalTenantConfig.count).toHaveBeenCalledWith({
171
+ where: findArgs.where,
172
+ });
173
+ expect(prisma.temporalTenantConfig.findMany).toHaveBeenCalledWith(
174
+ findArgs,
175
+ );
176
+ expect(result).toEqual({
177
+ itemCount: 2,
178
+ entities: mockEntities,
179
+ });
180
+ });
181
+
182
+ it('should return empty result when no records found', async () => {
183
+ const findArgs = { where: {} } as any;
184
+
185
+ prisma.temporalTenantConfig.count.mockResolvedValue(0);
186
+ prisma.temporalTenantConfig.findMany.mockResolvedValue([]);
187
+
188
+ const result = await service.findMany(findArgs);
189
+
190
+ expect(result).toEqual({ itemCount: 0, entities: [] });
191
+ });
192
+ });
193
+
194
+ describe('findUnique', () => {
195
+ it('should find a single temporalTenantConfig record by id', async () => {
196
+ const id = 1;
197
+ const select = { id: true };
198
+ const expectedResult = { id: 1 };
199
+
200
+ prisma.temporalTenantConfig.findUnique.mockResolvedValue(expectedResult);
201
+
202
+ const result = await service.findUnique(id, select);
203
+
204
+ expect(prisma.temporalTenantConfig.findUnique).toHaveBeenCalledWith({
205
+ where: { id },
206
+ select,
207
+ });
208
+ expect(result).toEqual(expectedResult);
209
+ });
210
+
211
+ it('should return null when record not found', async () => {
212
+ prisma.temporalTenantConfig.findUnique.mockResolvedValue(null);
213
+
214
+ const result = await service.findUnique(1, { id: true });
215
+
216
+ expect(result).toBeNull();
217
+ });
218
+ });
219
+
220
+ describe('updateUnique', () => {
221
+ it('should update a single temporalTenantConfig record', async () => {
222
+ const id = 1;
223
+ const data = {
224
+ adminUrl: 'test_adminUrl',
225
+ adminToken: 'test_adminToken',
226
+ namespace: 'test_namespace',
227
+ dbUrl: 'test_dbUrl',
228
+ displayName: 'test_displayName',
229
+ creator: 'test_creator',
230
+ } as any;
231
+ const expectedResult = { id: 1, ...data };
232
+
233
+ prisma.temporalTenantConfig.update.mockResolvedValue(expectedResult);
234
+
235
+ const result = await service.updateUnique(id, data);
236
+
237
+ expect(prisma.temporalTenantConfig.update).toHaveBeenCalledWith({
238
+ where: { id },
239
+ data,
240
+ });
241
+ expect(result).toEqual(expectedResult);
242
+ });
243
+ });
244
+
245
+ describe('updateMany', () => {
246
+ it('should update multiple temporalTenantConfig records', async () => {
247
+ const updateArgs = {
248
+ where: {},
249
+ data: {
250
+ adminUrl: 'test_adminUrl',
251
+ adminToken: 'test_adminToken',
252
+ namespace: 'test_namespace',
253
+ dbUrl: 'test_dbUrl',
254
+ displayName: 'test_displayName',
255
+ creator: 'test_creator',
256
+ },
257
+ } as any;
258
+ const expectedResult = { count: 5 };
259
+
260
+ prisma.temporalTenantConfig.updateMany.mockResolvedValue(expectedResult);
261
+
262
+ const result = await service.updateMany(updateArgs);
263
+
264
+ expect(prisma.temporalTenantConfig.updateMany).toHaveBeenCalledWith(
265
+ updateArgs,
266
+ );
267
+ expect(result).toEqual(expectedResult);
268
+ });
269
+ });
270
+
271
+ describe('deleteUnique', () => {
272
+ it('should delete a single temporalTenantConfig record and return count', async () => {
273
+ const id = 1;
274
+
275
+ prisma.temporalTenantConfig.delete.mockResolvedValue({ id: 1 });
276
+
277
+ const result = await service.deleteUnique(id);
278
+
279
+ expect(prisma.temporalTenantConfig.delete).toHaveBeenCalledWith({
280
+ where: { id },
281
+ });
282
+ expect(result).toEqual({ count: 1 });
283
+ });
284
+ });
285
+
286
+ describe('deleteMany', () => {
287
+ it('should delete multiple temporalTenantConfig records', async () => {
288
+ const deleteArgs = { where: {} } as any;
289
+ const expectedResult = { count: 3 };
290
+
291
+ prisma.temporalTenantConfig.deleteMany.mockResolvedValue(expectedResult);
292
+
293
+ const result = await service.deleteMany(deleteArgs);
294
+
295
+ expect(prisma.temporalTenantConfig.deleteMany).toHaveBeenCalledWith(
296
+ deleteArgs,
297
+ );
298
+ expect(result).toEqual(expectedResult);
299
+ });
300
+ });
301
+
302
+ describe('count', () => {
303
+ it('should count temporalTenantConfig records with filter', async () => {
304
+ const countArgs = { where: {} } as any;
305
+
306
+ prisma.temporalTenantConfig.count.mockResolvedValue(10);
307
+
308
+ const result = await service.count(countArgs);
309
+
310
+ expect(prisma.temporalTenantConfig.count).toHaveBeenCalledWith(countArgs);
311
+ expect(result).toBe(10);
312
+ });
313
+
314
+ it('should count all records when no filter provided', async () => {
315
+ prisma.temporalTenantConfig.count.mockResolvedValue(100);
316
+
317
+ const result = await service.count();
318
+
319
+ expect(prisma.temporalTenantConfig.count).toHaveBeenCalledWith(undefined);
320
+ expect(result).toBe(100);
321
+ });
322
+ });
323
+
324
+ describe('groupBy', () => {
325
+ it('should group temporalTenantConfig records by specified field', async () => {
326
+ const groupByArgs = {
327
+ by: ['id'],
328
+ _count: { id: true },
329
+ } as any;
330
+ const expectedResult = [{ id: 1, _count: { id: 50 } }];
331
+
332
+ prisma.temporalTenantConfig.groupBy.mockResolvedValue(expectedResult);
333
+
334
+ const result = await service.groupBy(groupByArgs);
335
+
336
+ expect(prisma.temporalTenantConfig.groupBy).toHaveBeenCalledWith(
337
+ groupByArgs,
338
+ );
339
+ expect(result).toEqual(expectedResult);
340
+ });
341
+ });
342
+
343
+ describe('aggregate', () => {
344
+ it('should aggregate temporalTenantConfig records', async () => {
345
+ const aggregateArgs = {
346
+ _count: true,
347
+ _max: { id: true },
348
+ _min: { id: true },
349
+ } as any;
350
+ const expectedResult = {
351
+ _count: 100,
352
+ _max: { id: 1 },
353
+ _min: { id: 1 },
354
+ };
355
+
356
+ prisma.temporalTenantConfig.aggregate.mockResolvedValue(expectedResult);
357
+
358
+ const result = await service.aggregate(aggregateArgs);
359
+
360
+ expect(prisma.temporalTenantConfig.aggregate).toHaveBeenCalledWith(
361
+ aggregateArgs,
362
+ );
363
+ expect(result).toEqual(expectedResult);
364
+ });
365
+ });
366
+ });