@gadmin2n/schematics 0.0.87 → 0.0.89

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 (99) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +16 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.codegen +40 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +76 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.web +53 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +219 -33
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +250 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +4 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/dev/postgres/init.sql +12 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +170 -0
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +254 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +8 -7
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +1 -1
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/prismaModels.ts +1 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +39 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/audit.seed.ts +40 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +56 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +39 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/{scripts/sync-data-mngt-pages.ts → seed/data-mngt.seed.ts} +36 -20
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/game.seed.ts +44 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +30 -6
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +130 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts +60 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +11 -25
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +108 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +1 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +31 -2
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +31 -2
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +41 -57
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +31 -2
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +309 -1
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +31 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +315 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +31 -2
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +312 -2
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +31 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +317 -1
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +31 -2
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +309 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +31 -2
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +299 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +31 -2
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +307 -1
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +31 -2
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +309 -1
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +205 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.ts +116 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +158 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +110 -1
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.spec.ts +79 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.ts +54 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +34 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +457 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +241 -4
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +34 -2
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +24 -30
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +34 -2
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +36 -36
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +34 -2
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +48 -24
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +312 -3
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +152 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +12 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +79 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +270 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +1 -1
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +23 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +70 -5
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +246 -90
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +20 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +96 -8
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/nginx.conf +74 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/ElementInspector.tsx +18 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/promptGenerator.ts +1 -1
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +1 -1
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -3
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -3
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +4 -1
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +9 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +156 -139
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +14 -2
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +62 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/PublishModal.tsx +4 -6
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +18 -27
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +32 -11
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +48 -61
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas-page/index.tsx +3 -6
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +16 -16
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +28 -35
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -3
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +1 -1
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -1
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +6 -0
  93. package/package.json +1 -1
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +0 -63
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/sync-resources.ts +0 -100
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +0 -302
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.spec.ts +0 -20
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +0 -87
  99. /package/dist/lib/application/files/gadmin2-game-angle-demo/{GRACEFUL-DEPLOYMENT.md → server/GRACEFUL-DEPLOYMENT.md} +0 -0
@@ -1,10 +1,56 @@
1
- import { Injectable, NotFoundException } from '@nestjs/common';
1
+ import {
2
+ BadRequestException,
3
+ Injectable,
4
+ Logger,
5
+ NotFoundException,
6
+ UnauthorizedException,
7
+ } from '@nestjs/common';
2
8
  import { Prisma } from '@prisma/client';
3
9
  import { PrismaService } from 'nestjs-prisma';
10
+ import { validateDslGraph } from './dsl-validate.util';
11
+ import { TemporalService } from './temporal.service';
12
+ import { verifyWebhookSignature } from './webhook-signature.util';
4
13
 
5
14
  @Injectable()
6
15
  export class WorkflowService {
7
- constructor(private prisma: PrismaService) {}
16
+ private readonly logger = new Logger(WorkflowService.name);
17
+
18
+ constructor(
19
+ private prisma: PrismaService,
20
+ private temporalService: TemporalService,
21
+ ) {}
22
+
23
+ /**
24
+ * Find the cron_trigger node config in a DSL, if any.
25
+ * Returns null when no cron_trigger node or when its `cron` field is missing.
26
+ */
27
+ private extractCronConfig(
28
+ dsl: any,
29
+ ): { cron: string; timezone?: string } | null {
30
+ const node = dsl?.nodes?.find((n: any) => n?.type === 'cron_trigger');
31
+ if (!node?.config?.cron) return null;
32
+ return { cron: node.config.cron, timezone: node.config.timezone };
33
+ }
34
+
35
+ /**
36
+ * Find all webhook_trigger configs in a DSL.
37
+ * Each entry includes path + method; used for collision detection on publish.
38
+ */
39
+ private extractWebhookConfigs(
40
+ dsl: any,
41
+ ): Array<{ path: string; method: string; secret?: string }> {
42
+ if (!Array.isArray(dsl?.nodes)) return [];
43
+ return dsl.nodes
44
+ .filter(
45
+ (n: any) =>
46
+ n?.type === 'webhook_trigger' && n?.config?.path && n?.config?.method,
47
+ )
48
+ .map((n: any) => ({
49
+ path: n.config.path,
50
+ method: n.config.method,
51
+ secret: n.config.secret,
52
+ }));
53
+ }
8
54
 
9
55
  async findMany(params: {
10
56
  status?: string;
@@ -143,15 +189,44 @@ export class WorkflowService {
143
189
  async toggleEnabled(id: bigint, enabled: boolean) {
144
190
  const workflow = await this.prisma.workflow.findUnique({ where: { id } });
145
191
  if (!workflow) throw new NotFoundException('Workflow not found');
146
- return this.prisma.workflow.update({
192
+ const updated = await this.prisma.workflow.update({
147
193
  where: { id },
148
194
  data: { isEnabled: enabled },
149
195
  });
196
+
197
+ // Cron schedule lifecycle: pause when disabled, resume when re-enabled
198
+ try {
199
+ if (enabled) {
200
+ await this.temporalService.unpauseCronSchedule(Number(id));
201
+ } else {
202
+ await this.temporalService.pauseCronSchedule(Number(id));
203
+ }
204
+ } catch (err: any) {
205
+ this.logger.warn(
206
+ `Failed to ${
207
+ enabled ? 'unpause' : 'pause'
208
+ } cron schedule for workflow ${id}: ${err?.message ?? err}`,
209
+ );
210
+ }
211
+
212
+ return updated;
150
213
  }
151
214
 
152
215
  async remove(id: bigint) {
153
216
  const workflow = await this.prisma.workflow.findUnique({ where: { id } });
154
217
  if (!workflow) throw new NotFoundException('Workflow not found');
218
+
219
+ // Best-effort: delete cron schedule before removing the workflow row
220
+ try {
221
+ await this.temporalService.deleteCronSchedule(Number(id));
222
+ } catch (err: any) {
223
+ this.logger.warn(
224
+ `Failed to delete cron schedule for workflow ${id}: ${
225
+ err?.message ?? err
226
+ }`,
227
+ );
228
+ }
229
+
155
230
  return this.prisma.workflow.delete({ where: { id } });
156
231
  }
157
232
 
@@ -202,7 +277,41 @@ export class WorkflowService {
202
277
  throw new NotFoundException('No draft DSL to publish');
203
278
  }
204
279
 
205
- return this.prisma.$transaction(async (tx) => {
280
+ // Structural + cycle validation. Reject malformed/cyclic DSL up-front so the
281
+ // worker's executeGraph never has to deal with them at runtime.
282
+ try {
283
+ validateDslGraph(dslToPublish);
284
+ } catch (err: any) {
285
+ throw new BadRequestException(`Invalid DSL: ${err?.message ?? err}`);
286
+ }
287
+
288
+ // Validate webhook path collisions across other published workflows
289
+ const webhookConfigs = this.extractWebhookConfigs(dslToPublish);
290
+ if (webhookConfigs.length > 0) {
291
+ const otherPublished = await this.prisma.workflow.findMany({
292
+ where: { status: 'PUBLISHED', id: { not: workflowId } },
293
+ select: { id: true, name: true, dsl: true },
294
+ });
295
+ for (const cfg of webhookConfigs) {
296
+ if (!cfg.secret) {
297
+ throw new Error(
298
+ `webhook_trigger path="${cfg.method} ${cfg.path}" requires a secret (HMAC strict mode)`,
299
+ );
300
+ }
301
+ const conflict = otherPublished.find((w) =>
302
+ this.extractWebhookConfigs(w.dsl).some(
303
+ (other) => other.path === cfg.path && other.method === cfg.method,
304
+ ),
305
+ );
306
+ if (conflict) {
307
+ throw new Error(
308
+ `webhook path "${cfg.method} ${cfg.path}" already used by workflow #${conflict.id} (${conflict.name})`,
309
+ );
310
+ }
311
+ }
312
+ }
313
+
314
+ const result = await this.prisma.$transaction(async (tx) => {
206
315
  const lastVersion = await tx.workflowVersion.findFirst({
207
316
  where: { workflowId },
208
317
  orderBy: { version: 'desc' },
@@ -228,6 +337,31 @@ export class WorkflowService {
228
337
 
229
338
  return version;
230
339
  });
340
+
341
+ // Cron schedule lifecycle: register/update if cron_trigger present, delete otherwise
342
+ const cronConfig = this.extractCronConfig(dslToPublish);
343
+ try {
344
+ if (cronConfig) {
345
+ await this.temporalService.upsertCronSchedule({
346
+ workflowId: Number(workflowId),
347
+ versionId: Number(result.id),
348
+ dsl: dslToPublish,
349
+ cron: cronConfig.cron,
350
+ timezone: cronConfig.timezone,
351
+ });
352
+ } else {
353
+ // No cron_trigger in this version → clean up any leftover schedule
354
+ await this.temporalService.deleteCronSchedule(Number(workflowId));
355
+ }
356
+ } catch (err: any) {
357
+ this.logger.warn(
358
+ `Cron schedule sync failed for workflow ${workflowId}: ${
359
+ err?.message ?? err
360
+ }`,
361
+ );
362
+ }
363
+
364
+ return result;
231
365
  }
232
366
 
233
367
  async getStats(workflowId: bigint) {
@@ -418,6 +552,109 @@ export class WorkflowService {
418
552
  };
419
553
  }
420
554
 
555
+ /**
556
+ * Webhook trigger entry point. Looks up published workflows whose DSL contains a
557
+ * webhook_trigger node matching (path, method), verifies HMAC signature against
558
+ * each match's secret, and starts a workflow instance per verified match.
559
+ *
560
+ * Returns the list of created instance IDs. Throws:
561
+ * - NotFoundException when no published workflow listens on this path/method
562
+ * - UnauthorizedException on bad signature or stale timestamp
563
+ */
564
+ async receiveWebhook(input: {
565
+ path: string;
566
+ method: string;
567
+ signatureHeader: string | undefined;
568
+ timestampHeader: string | undefined;
569
+ rawBody: Buffer;
570
+ body: any;
571
+ headers: Record<string, any>;
572
+ query: Record<string, any>;
573
+ }): Promise<{ instanceIds: string[] }> {
574
+ // 1) Timestamp window — reject stale requests (±5 min)
575
+ const tsMs = Number(input.timestampHeader) * 1000;
576
+ if (
577
+ !input.timestampHeader ||
578
+ !Number.isFinite(tsMs) ||
579
+ Math.abs(Date.now() - tsMs) > 5 * 60 * 1000
580
+ ) {
581
+ throw new UnauthorizedException(
582
+ 'Invalid or expired X-Workflow-Timestamp',
583
+ );
584
+ }
585
+
586
+ // 2) Lookup matching published + enabled workflows
587
+ const published = await this.prisma.workflow.findMany({
588
+ where: { status: 'PUBLISHED', isEnabled: true },
589
+ include: {
590
+ versions: { orderBy: { version: 'desc' }, take: 1 },
591
+ },
592
+ });
593
+
594
+ type Match = {
595
+ workflowId: bigint;
596
+ versionId: bigint;
597
+ dsl: any;
598
+ secret: string;
599
+ };
600
+ const matches: Match[] = [];
601
+ for (const wf of published) {
602
+ const version = wf.versions[0];
603
+ const dsl = version?.dsl ?? wf.dsl;
604
+ if (!dsl) continue;
605
+ const cfgs = this.extractWebhookConfigs(dsl);
606
+ const cfg = cfgs.find(
607
+ (c) => c.path === input.path && c.method === input.method,
608
+ );
609
+ if (!cfg || !cfg.secret) continue;
610
+ matches.push({
611
+ workflowId: wf.id,
612
+ versionId: version?.id ?? wf.id,
613
+ dsl,
614
+ secret: cfg.secret,
615
+ });
616
+ }
617
+
618
+ if (matches.length === 0) {
619
+ throw new NotFoundException(
620
+ `No published workflow listens on ${input.method} /${input.path}`,
621
+ );
622
+ }
623
+
624
+ // 3) Verify HMAC against each match's secret. Strict mode: every match must verify.
625
+ for (const m of matches) {
626
+ const ok = verifyWebhookSignature(
627
+ m.secret,
628
+ input.timestampHeader!,
629
+ input.rawBody,
630
+ input.signatureHeader,
631
+ );
632
+ if (!ok) {
633
+ throw new UnauthorizedException('Invalid X-Workflow-Signature');
634
+ }
635
+ }
636
+
637
+ // 4) Trigger executions in parallel
638
+ const context = {
639
+ headers: input.headers,
640
+ query: input.query,
641
+ body: input.body,
642
+ receivedAt: new Date().toISOString(),
643
+ };
644
+ const results = await Promise.all(
645
+ matches.map((m) =>
646
+ this.executeWorkflow(
647
+ m.workflowId,
648
+ { context },
649
+ 'webhook',
650
+ this.temporalService,
651
+ ),
652
+ ),
653
+ );
654
+
655
+ return { instanceIds: results.map((r) => r.instanceId) };
656
+ }
657
+
421
658
  async getInstanceDetail(instanceId: bigint) {
422
659
  const instance = await this.prisma.workflowInstance.findUnique({
423
660
  where: { id: instanceId },
@@ -1,15 +1,47 @@
1
+ /// <reference types="jest" />
2
+ import { ConfigService } from '@nestjs/config';
1
3
  import { Test, TestingModule } from '@nestjs/testing';
4
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
2
5
  import { WorkflowEventOutboxController } from './workflowEventOutbox.controller';
3
6
  import { WorkflowEventOutboxService } from './workflowEventOutbox.service';
4
7
 
8
+ // Mock WorkflowEventOutboxService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
9
+ // 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
10
+ const mockWorkflowEventOutboxService = {};
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
+
5
26
  describe('WorkflowEventOutboxController', () => {
6
27
  let controller: WorkflowEventOutboxController;
7
28
 
8
29
  beforeEach(async () => {
9
30
  const module: TestingModule = await Test.createTestingModule({
10
31
  controllers: [WorkflowEventOutboxController],
11
- providers: [WorkflowEventOutboxService],
12
- }).compile();
32
+ providers: [
33
+ {
34
+ provide: WorkflowEventOutboxService,
35
+ useValue: mockWorkflowEventOutboxService,
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();
13
45
 
14
46
  controller = module.get<WorkflowEventOutboxController>(
15
47
  WorkflowEventOutboxController,
@@ -77,10 +77,9 @@ describe('WorkflowEventOutboxService', () => {
77
77
  payload: {},
78
78
  processed: false,
79
79
  processedAt: new Date(),
80
- retryCount: 0,
81
80
  },
82
- };
83
- const expectedResult = { id: BigInt(1), ...createArgs.data };
81
+ } as any;
82
+ const expectedResult = { id: 1, ...createArgs.data };
84
83
 
85
84
  prisma.workflowEventOutbox.create.mockResolvedValue(expectedResult);
86
85
 
@@ -100,9 +99,8 @@ describe('WorkflowEventOutboxService', () => {
100
99
  payload: {},
101
100
  processed: false,
102
101
  processedAt: new Date(),
103
- retryCount: 0,
104
102
  },
105
- };
103
+ } as any;
106
104
  const error = new Error('Database connection failed');
107
105
 
108
106
  prisma.workflowEventOutbox.create.mockRejectedValue(error);
@@ -121,16 +119,14 @@ describe('WorkflowEventOutboxService', () => {
121
119
  payload: {},
122
120
  processed: false,
123
121
  processedAt: new Date(),
124
- retryCount: 0,
125
122
  },
126
123
  {
127
124
  eventName: 'test_eventName',
128
125
  payload: {},
129
126
  processed: false,
130
127
  processedAt: new Date(),
131
- retryCount: 0,
132
128
  },
133
- ];
129
+ ] as any;
134
130
  const expectedResult = { count: 2 };
135
131
 
136
132
  prisma.workflowEventOutbox.createMany.mockResolvedValue(expectedResult);
@@ -151,8 +147,8 @@ describe('WorkflowEventOutboxService', () => {
151
147
  where: {},
152
148
  take: 10,
153
149
  skip: 0,
154
- };
155
- const mockEntities = [{ id: BigInt(1) }, { id: BigInt(1) }];
150
+ } as any;
151
+ const mockEntities = [{ id: 1 }, { id: 2 }];
156
152
 
157
153
  prisma.workflowEventOutbox.count.mockResolvedValue(2);
158
154
  prisma.workflowEventOutbox.findMany.mockResolvedValue(mockEntities);
@@ -172,7 +168,7 @@ describe('WorkflowEventOutboxService', () => {
172
168
  });
173
169
 
174
170
  it('should return empty result when no records found', async () => {
175
- const findArgs = { where: {} };
171
+ const findArgs = { where: {} } as any;
176
172
 
177
173
  prisma.workflowEventOutbox.count.mockResolvedValue(0);
178
174
  prisma.workflowEventOutbox.findMany.mockResolvedValue([]);
@@ -185,9 +181,9 @@ describe('WorkflowEventOutboxService', () => {
185
181
 
186
182
  describe('findUnique', () => {
187
183
  it('should find a single workflowEventOutbox record by id', async () => {
188
- const id = BigInt(1);
184
+ const id = 1;
189
185
  const select = { id: true };
190
- const expectedResult = { id: BigInt(1) };
186
+ const expectedResult = { id: 1 };
191
187
 
192
188
  prisma.workflowEventOutbox.findUnique.mockResolvedValue(expectedResult);
193
189
 
@@ -203,7 +199,7 @@ describe('WorkflowEventOutboxService', () => {
203
199
  it('should return null when record not found', async () => {
204
200
  prisma.workflowEventOutbox.findUnique.mockResolvedValue(null);
205
201
 
206
- const result = await service.findUnique(BigInt(1), { id: true });
202
+ const result = await service.findUnique(1, { id: true });
207
203
 
208
204
  expect(result).toBeNull();
209
205
  });
@@ -211,15 +207,14 @@ describe('WorkflowEventOutboxService', () => {
211
207
 
212
208
  describe('updateUnique', () => {
213
209
  it('should update a single workflowEventOutbox record', async () => {
214
- const id = BigInt(1);
210
+ const id = 1;
215
211
  const data = {
216
212
  eventName: 'test_eventName',
217
213
  payload: {},
218
214
  processed: false,
219
215
  processedAt: new Date(),
220
- retryCount: 0,
221
- };
222
- const expectedResult = { id: BigInt(1), ...data };
216
+ } as any;
217
+ const expectedResult = { id: 1, ...data };
223
218
 
224
219
  prisma.workflowEventOutbox.update.mockResolvedValue(expectedResult);
225
220
 
@@ -242,9 +237,8 @@ describe('WorkflowEventOutboxService', () => {
242
237
  payload: {},
243
238
  processed: false,
244
239
  processedAt: new Date(),
245
- retryCount: 0,
246
240
  },
247
- };
241
+ } as any;
248
242
  const expectedResult = { count: 5 };
249
243
 
250
244
  prisma.workflowEventOutbox.updateMany.mockResolvedValue(expectedResult);
@@ -260,9 +254,9 @@ describe('WorkflowEventOutboxService', () => {
260
254
 
261
255
  describe('deleteUnique', () => {
262
256
  it('should delete a single workflowEventOutbox record and return count', async () => {
263
- const id = BigInt(1);
257
+ const id = 1;
264
258
 
265
- prisma.workflowEventOutbox.delete.mockResolvedValue({ id: BigInt(1) });
259
+ prisma.workflowEventOutbox.delete.mockResolvedValue({ id: 1 });
266
260
 
267
261
  const result = await service.deleteUnique(id);
268
262
 
@@ -275,7 +269,7 @@ describe('WorkflowEventOutboxService', () => {
275
269
 
276
270
  describe('deleteMany', () => {
277
271
  it('should delete multiple workflowEventOutbox records', async () => {
278
- const deleteArgs = { where: {} };
272
+ const deleteArgs = { where: {} } as any;
279
273
  const expectedResult = { count: 3 };
280
274
 
281
275
  prisma.workflowEventOutbox.deleteMany.mockResolvedValue(expectedResult);
@@ -291,7 +285,7 @@ describe('WorkflowEventOutboxService', () => {
291
285
 
292
286
  describe('count', () => {
293
287
  it('should count workflowEventOutbox records with filter', async () => {
294
- const countArgs = { where: {} };
288
+ const countArgs = { where: {} } as any;
295
289
 
296
290
  prisma.workflowEventOutbox.count.mockResolvedValue(10);
297
291
 
@@ -316,12 +310,12 @@ describe('WorkflowEventOutboxService', () => {
316
310
  const groupByArgs = {
317
311
  by: ['id'],
318
312
  _count: { id: true },
319
- };
320
- const expectedResult = [{ id: BigInt(1), _count: { id: 50 } }];
313
+ } as any;
314
+ const expectedResult = [{ id: 1, _count: { id: 50 } }];
321
315
 
322
316
  prisma.workflowEventOutbox.groupBy.mockResolvedValue(expectedResult);
323
317
 
324
- const result = await service.groupBy(groupByArgs as any);
318
+ const result = await service.groupBy(groupByArgs);
325
319
 
326
320
  expect(prisma.workflowEventOutbox.groupBy).toHaveBeenCalledWith(
327
321
  groupByArgs,
@@ -336,11 +330,11 @@ describe('WorkflowEventOutboxService', () => {
336
330
  _count: true,
337
331
  _max: { id: true },
338
332
  _min: { id: true },
339
- } as const;
333
+ } as any;
340
334
  const expectedResult = {
341
335
  _count: 100,
342
- _max: { id: BigInt(1) },
343
- _min: { id: BigInt(1) },
336
+ _max: { id: 1 },
337
+ _min: { id: 1 },
344
338
  };
345
339
 
346
340
  prisma.workflowEventOutbox.aggregate.mockResolvedValue(expectedResult);
@@ -1,15 +1,47 @@
1
+ /// <reference types="jest" />
2
+ import { ConfigService } from '@nestjs/config';
1
3
  import { Test, TestingModule } from '@nestjs/testing';
4
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
2
5
  import { WorkflowNodeInstanceController } from './workflowNodeInstance.controller';
3
6
  import { WorkflowNodeInstanceService } from './workflowNodeInstance.service';
4
7
 
8
+ // Mock WorkflowNodeInstanceService:用空对象屏蔽真实 Service 的依赖(PrismaService 等),
9
+ // 避免 NestJS 在测试模块编译时去解析 Service 的构造参数。
10
+ const mockWorkflowNodeInstanceService = {};
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
+
5
26
  describe('WorkflowNodeInstanceController', () => {
6
27
  let controller: WorkflowNodeInstanceController;
7
28
 
8
29
  beforeEach(async () => {
9
30
  const module: TestingModule = await Test.createTestingModule({
10
31
  controllers: [WorkflowNodeInstanceController],
11
- providers: [WorkflowNodeInstanceService],
12
- }).compile();
32
+ providers: [
33
+ {
34
+ provide: WorkflowNodeInstanceService,
35
+ useValue: mockWorkflowNodeInstanceService,
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();
13
45
 
14
46
  controller = module.get<WorkflowNodeInstanceController>(
15
47
  WorkflowNodeInstanceController,