Repository: artusjs/core
Pull request #15 (2022-03-22 15:40:55)

test: 🚑 add event / timer trigger test case
补充了 Trigger 在 Event / Timer 模型下的测试用例

hyj1991: 将 trigger 测试拆分成目录，是因为 jest 一个文件的测试运行在一个进程中，artus container 初始化前的 trigger 注入机制只有一次，所以需要分开独立每个进程进行测试

Patch:
diff --git a/package.json b/package.json
index 484757a..d213c54 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
   },
   "dependencies": {
     "@artus/injection": "^0.0.2",
-    "@artus/pipeline": "^0.1.0"
+    "@artus/pipeline": "^0.1.2"
   },
   "ci": {
     "type": "github",
diff --git a/src/trigger.ts b/src/trigger.ts
index 1042c90..72c0737 100644
--- a/src/trigger.ts
+++ b/src/trigger.ts
@@ -8,15 +8,9 @@ import { ARTUS_TRIGGER_ID } from "./constraints";
 })
 export class Trigger {
   private pipeline: Pipeline;
-  protected input: Input;
 
-  constructor(){
+  constructor() {
     this.pipeline = new Pipeline();
-    this.input = new Input();
-  }
-
-  async init(input: Input) {
-    this.input = input ?? this.input;
   }
 
   async use(middleware: MiddlewareInput): Promise<void> {
@@ -24,12 +18,12 @@ export class Trigger {
     this.pipeline.use(middleware);
   }
 
-  async initContext(): Promise<Context> {
-    return new Context(this.input);
+  async initContext(input: Input): Promise<Context> {
+    return new Context(input);
   }
 
-  async startPipeline(): Promise<Context> {
-    const ctx = await this.initContext();
+  async startPipeline(input: Input = new Input()): Promise<Context> {
+    const ctx = await this.initContext(input);
     await this.pipeline.run(ctx);
     return ctx;
   }


Test patch:
diff --git a/test/fixtures/trigger-event/app.ts b/test/fixtures/trigger-event/app.ts
new file mode 100644
index 0000000..8ecd0fc
--- /dev/null
+++ b/test/fixtures/trigger-event/app.ts
@@ -0,0 +1,79 @@
+import { Inject } from '@artus/injection';
+import { artusContainer, ArtusApplication, getArtusApplication } from '../../../src';
+import { ApplicationHook, ApplicationHookClass } from '../../../src/decorator';
+import { Context, Input, Next } from '@artus/pipeline';
+import { EventTrigger } from './eventTrigger';
+import { EventEmitter } from 'events';
+
+artusContainer.set({ type: EventTrigger });
+let event = new EventEmitter();
+
+@ApplicationHookClass()
+export class ApplicationHookExtension {
+  @Inject(ArtusApplication)
+  // @ts-ignore
+  app: ArtusApplication;
+
+  @ApplicationHook()
+  async didLoad() {
+    this.app.trigger.use(async (ctx: Context, next: Next) => {
+      const { input: { params: { type, payload } } } = ctx;
+      if (type !== 'e1') {
+        return await next();
+      }
+      const { output: { data } } = ctx;
+      data.type = Array.from(type).reverse().join('');
+      data.payload = payload;
+    });
+
+    this.app.trigger.use(async (ctx: Context, next: Next) => {
+      const { input: { params: { type, payload } } } = ctx;
+      if (type !== 'e2') {
+        return await next();
+      }
+      const { output: { data } } = ctx;
+      data.type = Array.from(type).reverse().join('');
+      data.payload = payload;
+    });
+  }
+
+  @ApplicationHook()
+  willReady() {
+    event.on('e1', async payload => {
+      const input = new Input();
+      input.params.type = 'e1';
+      input.params.payload = payload;
+      await this.app.trigger.startPipeline(input);
+    });
+
+    event.on('e2', async payload => {
+      const input = new Input();
+      input.params.type = 'e2';
+      input.params.payload = payload;
+      await this.app.trigger.startPipeline(input);
+    });
+  }
+
+  @ApplicationHook()
+  beforeClose() {
+    event.removeAllListeners();
+  }
+}
+
+async function main() {
+  const app: ArtusApplication = getArtusApplication();
+  await app.load({
+    rootDir: __dirname,
+    items: []
+  });
+  await app.run();
+};
+
+function pub(e: 'e1' | 'e2', p: any) {
+  event.emit(e, p);
+}
+
+export {
+  main,
+  pub
+};
diff --git a/test/fixtures/trigger-event/eventTrigger.ts b/test/fixtures/trigger-event/eventTrigger.ts
new file mode 100644
index 0000000..fcae043
--- /dev/null
+++ b/test/fixtures/trigger-event/eventTrigger.ts
@@ -0,0 +1,24 @@
+import { Injectable, ScopeEnum } from "@artus/injection";
+import { Context, Next } from "@artus/pipeline";
+import { Trigger } from "../../../src";
+import { ARTUS_TRIGGER_ID } from "../../../src/constraints";
+
+@Injectable({
+  id: ARTUS_TRIGGER_ID,
+  scope: ScopeEnum.SINGLETON
+})
+export class EventTrigger extends Trigger {
+  constructor() {
+    super();
+    // first of all
+    this.use(async (ctx: Context, next: Next) => {
+      await next();
+      await this.respond(ctx)
+    });
+  }
+
+  async respond(ctx: Context) {
+    const { output: { data: { type, payload } } } = ctx;
+    payload.cb(type);
+  }
+}
diff --git a/test/fixtures/trigger-http/app.ts b/test/fixtures/trigger-http/app.ts
index 525e21f..7ae4424 100644
--- a/test/fixtures/trigger-http/app.ts
+++ b/test/fixtures/trigger-http/app.ts
@@ -29,8 +29,7 @@ export class ApplicationHookExtension {
       .createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => {
         const input = new Input();
         input.params = { req, res };
-        await this.app.trigger.init(input);
-        await this.app.trigger.startPipeline();
+        await this.app.trigger.startPipeline(input);
       })
       .listen(3001)
   }
diff --git a/test/fixtures/trigger-timer/app.ts b/test/fixtures/trigger-timer/app.ts
new file mode 100644
index 0000000..7a42c46
--- /dev/null
+++ b/test/fixtures/trigger-timer/app.ts
@@ -0,0 +1,82 @@
+import { Inject } from '@artus/injection';
+import { artusContainer, ArtusApplication, getArtusApplication } from '../../../src';
+import { ApplicationHook, ApplicationHookClass } from '../../../src/decorator';
+import { Context, Input } from '@artus/pipeline';
+import { TimerTrigger } from './timerTrigger';
+
+artusContainer.set({ type: TimerTrigger });
+let timers: any[] = [];
+let execution = {
+  task1: {
+    count: 0,
+    cost: 0,
+  },
+  task2: {
+    count: 0,
+    cost: 0
+  }
+};
+
+@ApplicationHookClass()
+export class ApplicationHookExtension {
+  @Inject(ArtusApplication)
+  // @ts-ignore
+  app: ArtusApplication;
+
+  @ApplicationHook()
+  async didLoad() {
+    this.app.trigger.use(async (ctx: Context) => {
+      const { input: { params } } = ctx;
+
+      // task 1
+      if (params.task === '1') {
+        await new Promise(resolve => setTimeout(resolve, 200));
+        params.execution.task1.count++;
+      }
+
+      // task 2
+      if (params.task === '2') {
+        await new Promise(resolve => setTimeout(resolve, 100));
+        params.execution.task2.count++;
+      }
+    });
+  }
+
+  @ApplicationHook()
+  willReady() {
+    timers.push(setInterval(async () => {
+      const input = new Input();
+      input.params = { task: '1', execution };
+      await this.app.trigger.startPipeline(input);
+    }, 100));
+
+    timers.push(setInterval(async () => {
+      const input = new Input();
+      input.params = { task: '2', execution };
+      await this.app.trigger.startPipeline(input);
+    }, 200));
+  }
+
+  @ApplicationHook()
+  beforeClose() {
+    timers.forEach(clearInterval);
+  }
+}
+
+async function main() {
+  const app: ArtusApplication = getArtusApplication();
+  await app.load({
+    rootDir: __dirname,
+    items: []
+  });
+  await app.run();
+};
+
+function getTaskExecution() {
+  return execution;
+}
+
+export {
+  main,
+  getTaskExecution
+};
diff --git a/test/fixtures/trigger-timer/timerTrigger.ts b/test/fixtures/trigger-timer/timerTrigger.ts
new file mode 100644
index 0000000..8487966
--- /dev/null
+++ b/test/fixtures/trigger-timer/timerTrigger.ts
@@ -0,0 +1,22 @@
+import { Injectable, ScopeEnum } from "@artus/injection";
+import { Context, Next } from "@artus/pipeline";
+import { Trigger } from "../../../src";
+import { ARTUS_TRIGGER_ID } from "../../../src/constraints";
+
+@Injectable({
+  id: ARTUS_TRIGGER_ID,
+  scope: ScopeEnum.SINGLETON
+})
+export class TimerTrigger extends Trigger {
+  constructor() {
+    super();
+    // first of all
+    this.use(async (ctx: Context, next: Next) => {
+      const start = Date.now();
+      await next();
+      const cost = Date.now() - start;
+      const { input: { params: { task: id, execution } } } = ctx;
+      execution[`task${id}`].cost += cost;
+    });
+  }
+}
diff --git a/test/trigger.test.ts b/test/trigger.test.ts
deleted file mode 100644
index fed9834..0000000
--- a/test/trigger.test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import 'reflect-metadata';
-import axios from 'axios';
-import assert from 'assert';
-import { getArtusApplication } from '../src';
-
-describe('test/trigger.test.ts', () => {
-  describe('trigger with http', () => {
-    it('should run succeed', async () => {
-      let error;
-      try {
-        const {
-          main,
-          isListening
-        } = await import('./fixtures/trigger-http/app');
-        const app = getArtusApplication();
-        await main();
-        const testResponse = await axios.get('http://127.0.0.1:3001');
-        assert(testResponse.status === 200);
-        assert(testResponse.data.title === 'Hello Artus');
-        await app.close();
-        assert(!isListening());
-      } catch (err) {
-        error = err;
-      }
-      error && console.log(error);
-      assert(!error);
-    });
-  });
-});
diff --git a/test/trigger/event.test.ts b/test/trigger/event.test.ts
new file mode 100644
index 0000000..04885cf
--- /dev/null
+++ b/test/trigger/event.test.ts
@@ -0,0 +1,30 @@
+import 'reflect-metadata';
+import assert from 'assert';
+import { getArtusApplication } from '../../src';
+
+describe('test/trigger/timer.test.ts', () => {
+  it('[trigger with timer] should run succeed', async () => {
+    const {
+      main,
+      pub
+    } = await import('../fixtures/trigger-event/app');
+    const app = getArtusApplication();
+    await main();
+    let e1Result, e2Result;
+    pub('e1', {
+      cb(res) {
+        e1Result = res;
+      }
+    });
+    pub('e2', {
+      cb(res) {
+        e2Result = res;
+      }
+    });
+    // wait for event handle
+    await new Promise(resolve => setTimeout(resolve, 1000));
+    assert(e1Result === '1e');
+    assert(e2Result === '2e');
+    await app.close();
+  });
+});
\ No newline at end of file
diff --git a/test/trigger/http.test.ts b/test/trigger/http.test.ts
new file mode 100644
index 0000000..e907d34
--- /dev/null
+++ b/test/trigger/http.test.ts
@@ -0,0 +1,23 @@
+import 'reflect-metadata';
+import axios from 'axios';
+import assert from 'assert';
+import { getArtusApplication } from '../../src';
+
+describe('test/trigger/http.test.ts', () => {
+  it('should run succeed', async () => {
+    const {
+      main,
+      isListening
+    } = await import('../fixtures/trigger-http/app');
+    const app = getArtusApplication();
+    await main();
+    const testResponse = await axios.get('http://127.0.0.1:3001');
+    await axios.get('http://127.0.0.1:3001');
+    await axios.get('http://127.0.0.1:3001');
+    await axios.get('http://127.0.0.1:3001');
+    assert(testResponse.status === 200);
+    assert(testResponse.data.title === 'Hello Artus');
+    await app.close();
+    assert(!isListening());
+  });
+});
diff --git a/test/trigger/timer.test.ts b/test/trigger/timer.test.ts
new file mode 100644
index 0000000..7643e8d
--- /dev/null
+++ b/test/trigger/timer.test.ts
@@ -0,0 +1,31 @@
+import 'reflect-metadata';
+import assert from 'assert';
+import { getArtusApplication } from '../../src';
+
+describe('test/trigger/timer.test.ts', () => {
+  it('[trigger with timer] should run succeed', async () => {
+    const {
+      main,
+      getTaskExecution,
+    } = await import('../fixtures/trigger-timer/app');
+    const app = getArtusApplication();
+    await main();
+    await new Promise(resolve => setTimeout(resolve, 3000));
+    const execution = getTaskExecution();
+    assert(execution);
+    console.log(execution);
+    // task1
+    const task1ExecutionCount = execution.task1.count;
+    assert(task1ExecutionCount > 10);
+
+    // task2
+    const task2ExecutionCount = execution.task2.count;
+    assert(task2ExecutionCount > 5);
+
+    // execution average cost
+    const task1ExecutionAverage = execution.task1.cost / task1ExecutionCount;
+    const task2ExecutionAverage = execution.task2.cost / task2ExecutionCount;
+    assert(task2ExecutionAverage < task1ExecutionAverage);
+    await app.close();
+  });
+});
\ No newline at end of file