@eggjs/mock 6.0.0-beta.3

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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +547 -0
  3. package/README.zh_CN.md +512 -0
  4. package/dist/commonjs/app/extend/agent.d.ts +33 -0
  5. package/dist/commonjs/app/extend/agent.js +49 -0
  6. package/dist/commonjs/app/extend/application.d.ts +175 -0
  7. package/dist/commonjs/app/extend/application.js +448 -0
  8. package/dist/commonjs/app/middleware/cluster_app_mock.d.ts +3 -0
  9. package/dist/commonjs/app/middleware/cluster_app_mock.js +100 -0
  10. package/dist/commonjs/app.d.ts +6 -0
  11. package/dist/commonjs/app.js +20 -0
  12. package/dist/commonjs/bootstrap.d.ts +4 -0
  13. package/dist/commonjs/bootstrap.js +58 -0
  14. package/dist/commonjs/index.d.ts +76 -0
  15. package/dist/commonjs/index.js +99 -0
  16. package/dist/commonjs/index.test-d.d.ts +1 -0
  17. package/dist/commonjs/index.test-d.js +43 -0
  18. package/dist/commonjs/lib/agent_handler.d.ts +3 -0
  19. package/dist/commonjs/lib/agent_handler.js +28 -0
  20. package/dist/commonjs/lib/app.d.ts +28 -0
  21. package/dist/commonjs/lib/app.js +303 -0
  22. package/dist/commonjs/lib/app_handler.d.ts +5 -0
  23. package/dist/commonjs/lib/app_handler.js +67 -0
  24. package/dist/commonjs/lib/cluster.d.ts +114 -0
  25. package/dist/commonjs/lib/cluster.js +337 -0
  26. package/dist/commonjs/lib/context.d.ts +1 -0
  27. package/dist/commonjs/lib/context.js +16 -0
  28. package/dist/commonjs/lib/format_options.d.ts +5 -0
  29. package/dist/commonjs/lib/format_options.js +100 -0
  30. package/dist/commonjs/lib/inject_context.d.ts +6 -0
  31. package/dist/commonjs/lib/inject_context.js +132 -0
  32. package/dist/commonjs/lib/mock_agent.d.ts +5 -0
  33. package/dist/commonjs/lib/mock_agent.js +49 -0
  34. package/dist/commonjs/lib/mock_custom_loader.d.ts +1 -0
  35. package/dist/commonjs/lib/mock_custom_loader.js +37 -0
  36. package/dist/commonjs/lib/mock_http_server.d.ts +2 -0
  37. package/dist/commonjs/lib/mock_http_server.js +24 -0
  38. package/dist/commonjs/lib/mock_httpclient.d.ts +35 -0
  39. package/dist/commonjs/lib/mock_httpclient.js +147 -0
  40. package/dist/commonjs/lib/parallel/agent.d.ts +20 -0
  41. package/dist/commonjs/lib/parallel/agent.js +125 -0
  42. package/dist/commonjs/lib/parallel/app.d.ts +20 -0
  43. package/dist/commonjs/lib/parallel/app.js +115 -0
  44. package/dist/commonjs/lib/parallel/util.d.ts +3 -0
  45. package/dist/commonjs/lib/parallel/util.js +77 -0
  46. package/dist/commonjs/lib/prerequire.d.ts +1 -0
  47. package/dist/commonjs/lib/prerequire.js +26 -0
  48. package/dist/commonjs/lib/request_call_function.d.ts +1 -0
  49. package/dist/commonjs/lib/request_call_function.js +52 -0
  50. package/dist/commonjs/lib/restore.d.ts +1 -0
  51. package/dist/commonjs/lib/restore.js +16 -0
  52. package/dist/commonjs/lib/start-cluster.d.ts +2 -0
  53. package/dist/commonjs/lib/start-cluster.js +23 -0
  54. package/dist/commonjs/lib/supertest.d.ts +11 -0
  55. package/dist/commonjs/lib/supertest.js +48 -0
  56. package/dist/commonjs/lib/tmp/empty.d.ts +1 -0
  57. package/dist/commonjs/lib/tmp/empty.js +3 -0
  58. package/dist/commonjs/lib/types.d.ts +60 -0
  59. package/dist/commonjs/lib/types.js +3 -0
  60. package/dist/commonjs/lib/utils.d.ts +9 -0
  61. package/dist/commonjs/lib/utils.js +80 -0
  62. package/dist/commonjs/package.json +3 -0
  63. package/dist/commonjs/register.d.ts +8 -0
  64. package/dist/commonjs/register.js +80 -0
  65. package/dist/esm/app/extend/agent.d.ts +33 -0
  66. package/dist/esm/app/extend/agent.js +46 -0
  67. package/dist/esm/app/extend/application.d.ts +175 -0
  68. package/dist/esm/app/extend/application.js +442 -0
  69. package/dist/esm/app/middleware/cluster_app_mock.d.ts +3 -0
  70. package/dist/esm/app/middleware/cluster_app_mock.js +98 -0
  71. package/dist/esm/app.d.ts +6 -0
  72. package/dist/esm/app.js +17 -0
  73. package/dist/esm/bootstrap.d.ts +4 -0
  74. package/dist/esm/bootstrap.js +16 -0
  75. package/dist/esm/index.d.ts +76 -0
  76. package/dist/esm/index.js +90 -0
  77. package/dist/esm/index.test-d.d.ts +1 -0
  78. package/dist/esm/index.test-d.js +42 -0
  79. package/dist/esm/lib/agent_handler.d.ts +3 -0
  80. package/dist/esm/lib/agent_handler.js +24 -0
  81. package/dist/esm/lib/app.d.ts +28 -0
  82. package/dist/esm/lib/app.js +295 -0
  83. package/dist/esm/lib/app_handler.d.ts +5 -0
  84. package/dist/esm/lib/app_handler.js +61 -0
  85. package/dist/esm/lib/cluster.d.ts +114 -0
  86. package/dist/esm/lib/cluster.js +328 -0
  87. package/dist/esm/lib/context.d.ts +1 -0
  88. package/dist/esm/lib/context.js +13 -0
  89. package/dist/esm/lib/format_options.d.ts +5 -0
  90. package/dist/esm/lib/format_options.js +94 -0
  91. package/dist/esm/lib/inject_context.d.ts +6 -0
  92. package/dist/esm/lib/inject_context.js +126 -0
  93. package/dist/esm/lib/mock_agent.d.ts +5 -0
  94. package/dist/esm/lib/mock_agent.js +45 -0
  95. package/dist/esm/lib/mock_custom_loader.d.ts +1 -0
  96. package/dist/esm/lib/mock_custom_loader.js +34 -0
  97. package/dist/esm/lib/mock_http_server.d.ts +2 -0
  98. package/dist/esm/lib/mock_http_server.js +18 -0
  99. package/dist/esm/lib/mock_httpclient.d.ts +35 -0
  100. package/dist/esm/lib/mock_httpclient.js +144 -0
  101. package/dist/esm/lib/parallel/agent.d.ts +20 -0
  102. package/dist/esm/lib/parallel/agent.js +117 -0
  103. package/dist/esm/lib/parallel/app.d.ts +20 -0
  104. package/dist/esm/lib/parallel/app.js +110 -0
  105. package/dist/esm/lib/parallel/util.d.ts +3 -0
  106. package/dist/esm/lib/parallel/util.js +73 -0
  107. package/dist/esm/lib/prerequire.d.ts +1 -0
  108. package/dist/esm/lib/prerequire.js +25 -0
  109. package/dist/esm/lib/request_call_function.d.ts +1 -0
  110. package/dist/esm/lib/request_call_function.js +47 -0
  111. package/dist/esm/lib/restore.d.ts +1 -0
  112. package/dist/esm/lib/restore.js +13 -0
  113. package/dist/esm/lib/start-cluster.d.ts +2 -0
  114. package/dist/esm/lib/start-cluster.js +18 -0
  115. package/dist/esm/lib/supertest.d.ts +11 -0
  116. package/dist/esm/lib/supertest.js +40 -0
  117. package/dist/esm/lib/tmp/empty.d.ts +1 -0
  118. package/dist/esm/lib/tmp/empty.js +2 -0
  119. package/dist/esm/lib/types.d.ts +60 -0
  120. package/dist/esm/lib/types.js +2 -0
  121. package/dist/esm/lib/utils.d.ts +9 -0
  122. package/dist/esm/lib/utils.js +69 -0
  123. package/dist/esm/package.json +3 -0
  124. package/dist/esm/register.d.ts +8 -0
  125. package/dist/esm/register.js +75 -0
  126. package/dist/package.json +4 -0
  127. package/package.json +131 -0
  128. package/src/app/extend/agent.ts +56 -0
  129. package/src/app/extend/application.ts +512 -0
  130. package/src/app/middleware/cluster_app_mock.ts +101 -0
  131. package/src/app.ts +18 -0
  132. package/src/bootstrap.ts +25 -0
  133. package/src/index.d.ts +193 -0
  134. package/src/index.test-d.ts +47 -0
  135. package/src/index.ts +110 -0
  136. package/src/lib/agent_handler.ts +28 -0
  137. package/src/lib/app.ts +313 -0
  138. package/src/lib/app_handler.ts +69 -0
  139. package/src/lib/cluster.ts +363 -0
  140. package/src/lib/context.ts +14 -0
  141. package/src/lib/format_options.ts +103 -0
  142. package/src/lib/inject_context.ts +134 -0
  143. package/src/lib/mock_agent.ts +57 -0
  144. package/src/lib/mock_custom_loader.ts +36 -0
  145. package/src/lib/mock_http_server.ts +19 -0
  146. package/src/lib/mock_httpclient.ts +181 -0
  147. package/src/lib/parallel/agent.ts +128 -0
  148. package/src/lib/parallel/app.ts +123 -0
  149. package/src/lib/parallel/util.ts +66 -0
  150. package/src/lib/prerequire.ts +25 -0
  151. package/src/lib/request_call_function.ts +49 -0
  152. package/src/lib/restore.ts +14 -0
  153. package/src/lib/start-cluster.ts +23 -0
  154. package/src/lib/supertest.ts +45 -0
  155. package/src/lib/tmp/.gitkeep +0 -0
  156. package/src/lib/tmp/empty.ts +0 -0
  157. package/src/lib/types.ts +72 -0
  158. package/src/lib/utils.ts +82 -0
  159. package/src/register.ts +80 -0
package/src/index.d.ts ADDED
@@ -0,0 +1,193 @@
1
+ // import { Application, Context, EggLogger } from 'egg';
2
+ // import { MockMate } from 'mm';
3
+ // import { Test } from 'supertest';
4
+ // import { MockAgent } from 'urllib';
5
+ // import { Suite } from 'mocha';
6
+
7
+ // export { MockAgent };
8
+
9
+ // export interface EggTest extends Test {
10
+ // unexpectHeader(name: string, b?: Function): EggTest;
11
+ // expectHeader(name: string, b?: Function): EggTest;
12
+ // }
13
+
14
+ // export type Methods = 'get' | 'post' | 'delete' | 'del' | 'put' | 'head' | 'options' | 'patch' | 'trace' | 'connect';
15
+
16
+ // export interface BaseMockApplication<T, C> extends Application {
17
+ // ready(): Promise<void>;
18
+ // close(): Promise<void>;
19
+ // callback(): any;
20
+
21
+ // /**
22
+ // * mock Context
23
+ // */
24
+ // mockContext(data?: any, options?: any): C;
25
+
26
+ // /**
27
+ // * mock Context
28
+ // */
29
+ // mockContextScope<R>(fn: (ctx: C) => Promise<R>, data?: any): Promise<R>;
30
+
31
+ // /**
32
+ // * mock cookie session
33
+ // */
34
+ // mockSession(data: any): T;
35
+
36
+ // mockCookies(cookies: any): T;
37
+
38
+ // mockHeaders(headers: any): T;
39
+
40
+ // /**
41
+ // * Mock service
42
+ // */
43
+ // mockService(service: string, methodName: string, fn: any): T;
44
+
45
+ // /**
46
+ // * mock service that return error
47
+ // */
48
+ // mockServiceError(service: string, methodName: string, err?: Error): T;
49
+
50
+ // mockHttpclient(mockUrl: string | RegExp, mockMethod: string | string[], mockResult: MockHttpClientResult): Application;
51
+
52
+ // mockHttpclient(mockUrl: string | RegExp, mockResult: MockHttpClientResult): Application;
53
+
54
+ // mockAgent(): MockAgent;
55
+ // mockAgentRestore(): Promise<void>;
56
+ // mockRestore(): Promise<void>;
57
+
58
+ // /**
59
+ // * mock csrf
60
+ // */
61
+ // mockCsrf(): T;
62
+
63
+ // /**
64
+ // * http request helper
65
+ // */
66
+ // httpRequest(): {
67
+ // [key in Methods]: (url: string) => EggTest;
68
+ // } & {
69
+ // [key: string]: (url: string) => EggTest;
70
+ // };
71
+
72
+ // /**
73
+ // * mock logger
74
+ // */
75
+ // mockLog(logger?: EggLogger | string): void;
76
+ // expectLog(expected: string | RegExp, logger?: EggLogger | string): void;
77
+ // notExpectLog(expected: string | RegExp, logger?: EggLogger | string): void;
78
+
79
+ // /**
80
+ // * background task
81
+ // */
82
+ // backgroundTasksFinished(): Promise<void>;
83
+ // }
84
+
85
+ // export interface ResultObject {
86
+ // data?: string | object | Buffer;
87
+ // status?: number;
88
+ // headers?: any;
89
+ // delay?: number;
90
+ // persist?: boolean;
91
+ // repeats?: number;
92
+ // }
93
+
94
+ // export type ResultFunction = (url?: string, opts?: any) => ResultObject | string | void;
95
+
96
+ // export type MockHttpClientResult = ResultObject | ResultFunction | string;
97
+
98
+ // export interface MockOption {
99
+ // /**
100
+ // * The directory of the application
101
+ // */
102
+ // baseDir?: string;
103
+
104
+ // /**
105
+ // * Custom you plugins
106
+ // */
107
+ // plugins?: any;
108
+
109
+ // /**
110
+ // * The directory of the egg framework
111
+ // */
112
+ // framework?: string;
113
+
114
+ // /**
115
+ // * Cache application based on baseDir
116
+ // */
117
+ // cache?: boolean;
118
+
119
+ // /**
120
+ // * Swtich on process coverage, but it'll be slower
121
+ // */
122
+ // coverage?: boolean;
123
+
124
+ // /**
125
+ // * Remove $baseDir/logs
126
+ // */
127
+ // clean?: boolean;
128
+
129
+ // /**
130
+ // * default options.mockCtxStorage value on each mockContext
131
+ // */
132
+ // mockCtxStorage?: boolean;
133
+ // }
134
+
135
+ // export interface MockClusterOption extends MockOption {
136
+ // workers?: number | string;
137
+ // cache?: boolean;
138
+ // /**
139
+ // * opt for egg-bin
140
+ // */
141
+ // opt?: object;
142
+ // }
143
+
144
+ // export type EnvType = 'default' | 'test' | 'prod' | 'local' | 'unittest' | string & {};
145
+ // export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE';
146
+
147
+ // export interface MockApplication extends BaseMockApplication<Application, Context> {
148
+ // [key: string]: any;
149
+ // }
150
+
151
+ // export interface EggMock extends MockMate {
152
+ // /**
153
+ // * Create a egg mocked application
154
+ // */
155
+ // app: (option?: MockOption) => MockApplication;
156
+
157
+ // /**
158
+ // * Create a mock cluster server, but you can't use API in application, you should test using supertest
159
+ // */
160
+ // cluster: (option?: MockClusterOption) => MockApplication;
161
+
162
+ // /**
163
+ // * mock the serverEnv of Egg
164
+ // */
165
+ // env: (env: EnvType) => void;
166
+
167
+ // /**
168
+ // * mock console level
169
+ // */
170
+ // consoleLevel: (level: LogLevel) => void;
171
+
172
+ // /**
173
+ // * set EGG_HOME path
174
+ // */
175
+ // home: (homePath: string) => void;
176
+
177
+ // /**
178
+ // * restore mock
179
+ // */
180
+ // restore: () => any;
181
+
182
+ // /**
183
+ // * If you use mm.app instead of egg-mock/bootstrap to bootstrap app.
184
+ // * Should manually call setGetAppCallback,
185
+ // * then egg-mock will inject ctx for each test case
186
+ // * @param cb
187
+ // */
188
+ // setGetAppCallback: (cb: (suite: Suite) => Promise<MockApplication>) => void;
189
+ // }
190
+
191
+ // declare const mm: EggMock;
192
+ // export { mm };
193
+ // export default mm;
@@ -0,0 +1,47 @@
1
+ // import { expectType } from 'tsd';
2
+ // import { Application, Context } from 'egg';
3
+ // import { MockApplication, MockAgent, ResultObject } from '.';
4
+ // import { app, mock, mm } from './bootstrap';
5
+
6
+ // expectType<MockApplication>(app);
7
+ // expectType<Context>(app.currentContext);
8
+ // expectType<Context | undefined>(app.ctxStorage.getStore());
9
+ // expectType<MockApplication>(mock.app());
10
+ // expectType<MockApplication>(mm.app());
11
+
12
+ // expectType<MockAgent>(mm.app().mockAgent());
13
+
14
+ // expectType<Application>(mm.app().mockHttpclient('url', 'post', { data: 'ok' }));
15
+ // expectType<Application>(mm.app().mockHttpclient('url', 'post', 'data'));
16
+ // expectType<Application>(mm.app().mockHttpclient('url', {
17
+ // data: 'mock response',
18
+ // repeats: 1,
19
+ // }));
20
+ // expectType<Application>(mm.app().mockHttpclient('url', () => {}));
21
+ // expectType<Application>(mm.app().mockHttpclient('url', 'post', () => {}));
22
+ // expectType<Application>(mm.app().mockHttpclient('url', 'get', {
23
+ // data: 'mock response',
24
+ // repeats: 1,
25
+ // }));
26
+
27
+ // expectType<void>(app.mockLog());
28
+ // expectType<void>(app.mockLog('logger'));
29
+ // expectType<void>(app.mockLog(app.logger));
30
+ // expectType<void>(app.expectLog('foo string'));
31
+ // expectType<void>(app.expectLog('foo string', 'coreLogger'));
32
+ // expectType<void>(app.expectLog('foo string', app.coreLogger));
33
+ // expectType<void>(app.expectLog(/foo string/));
34
+ // expectType<void>(app.expectLog(/foo string/, 'coreLogger'));
35
+ // expectType<void>(app.expectLog(/foo string/, app.coreLogger));
36
+ // expectType<void>(mm.env('default'));
37
+ // expectType<void>(mm.env('devserver'));
38
+
39
+ // expectType<Promise<void>>(app.mockAgentRestore());
40
+ // expectType<Promise<void>>(app.mockRestore());
41
+ // expectType<Promise<void>>(app.mockContextScope(async () => {}));
42
+ // expectType<Promise<void>>(app.mockContextScope(async ctx => {}));
43
+
44
+ // expectType<Promise<void>>(app.backgroundTasksFinished());
45
+
46
+ // const result: ResultObject = {};
47
+ // expectType<number>(result.status!);
package/src/index.ts ADDED
@@ -0,0 +1,110 @@
1
+ import mm from 'mm';
2
+ import { mock as _mock } from 'mm';
3
+ import { createCluster } from './lib/cluster.js';
4
+ import { createApp } from './lib/app.js';
5
+ // import { getMockAgent } from './lib/mock_agent.js';
6
+ import { restore } from './lib/restore.js';
7
+ import { setGetAppCallback } from './lib/app_handler.js';
8
+ import ApplicationUnittest from './app/extend/application.js';
9
+
10
+ // egg-bin will set this flag to require files for instrument
11
+ // if (process.env.EGG_BIN_PREREQUIRE) {
12
+ // require('./lib/prerequire');
13
+ // }
14
+
15
+ // inherit & extends mm
16
+ const mock = {
17
+ ...mm,
18
+ restore,
19
+
20
+ /**
21
+ * Create a egg mocked application
22
+ * @function mm#app
23
+ * @param {Object} [options]
24
+ * - {String} baseDir - The directory of the application
25
+ * - {Object} plugins - Custom you plugins
26
+ * - {String} framework - The directory of the egg framework
27
+ * - {Boolean} [true] cache - Cache application based on baseDir
28
+ * - {Boolean} [true] coverage - Switch on process coverage, but it'll be slower
29
+ * - {Boolean} [true] clean - Remove $baseDir/logs
30
+ * @return {App} return {@link Application}
31
+ * @example
32
+ * ```js
33
+ * const app = mm.app();
34
+ * ```
35
+ */
36
+ app: createApp,
37
+
38
+ /**
39
+ * Create a egg mocked cluster application
40
+ * @function mm#cluster
41
+ * @see ClusterApplication
42
+ */
43
+ cluster: createCluster,
44
+
45
+ /**
46
+ * mock the serverEnv of Egg
47
+ * @member {Function} mm#env
48
+ * @param {String} env - contain default, test, prod, local, unittest
49
+ * @see https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L78
50
+ */
51
+ env(env: string) {
52
+ _mock(process.env, 'EGG_MOCK_SERVER_ENV', env);
53
+ _mock(process.env, 'EGG_SERVER_ENV', env);
54
+ },
55
+
56
+ /**
57
+ * mock console level
58
+ * @param {String} level - logger level
59
+ */
60
+ consoleLevel(level: string) {
61
+ level = (level || '').toUpperCase();
62
+ _mock(process.env, 'EGG_LOG', level);
63
+ },
64
+
65
+ home(homePath?: string) {
66
+ if (homePath) {
67
+ _mock(process.env, 'EGG_HOME', homePath);
68
+ }
69
+ },
70
+
71
+ setGetAppCallback,
72
+ };
73
+
74
+ // import mm from '@eggjs/mock';
75
+ const proxyMock = new Proxy(_mock, {
76
+ apply(target, _, args) {
77
+ return target(args[0], args[1], args[2]);
78
+ },
79
+ get(_target, property, receiver) {
80
+ // import mm from '@eggjs/mock';
81
+ // mm.isMocked(foo, 'bar')
82
+ return Reflect.get(mock, property, receiver);
83
+ },
84
+ }) as unknown as ((target: any, property: PropertyKey, value?: any) => void) & typeof mock;
85
+
86
+ export default proxyMock;
87
+
88
+ export {
89
+ proxyMock as mock,
90
+ // alias to mm
91
+ proxyMock as mm,
92
+ ApplicationUnittest as MockApplication,
93
+ setGetAppCallback,
94
+ createApp,
95
+ createCluster,
96
+ };
97
+
98
+ process.setMaxListeners(100);
99
+
100
+ process.once('SIGQUIT', () => {
101
+ process.exit(0);
102
+ });
103
+
104
+ process.once('SIGTERM', () => {
105
+ process.exit(0);
106
+ });
107
+
108
+ process.once('SIGINT', () => {
109
+ process.exit(0);
110
+ });
@@ -0,0 +1,28 @@
1
+ import { debuglog } from 'node:util';
2
+ import { createAgent, MockAgent } from './parallel/agent.js';
3
+ import { getEggOptions } from './utils.js';
4
+
5
+ const debug = debuglog('@eggjs/mock/lib/agent_handler');
6
+
7
+ let agent: MockAgent;
8
+
9
+ export async function setupAgent() {
10
+ debug('setupAgent call, env.ENABLE_MOCHA_PARALLEL: %s, process.env.AUTO_AGENT: %s, agent: %s',
11
+ process.env.ENABLE_MOCHA_PARALLEL, process.env.AUTO_AGENT, !!agent);
12
+ if (agent) {
13
+ await agent.ready();
14
+ return agent;
15
+ }
16
+ if (process.env.ENABLE_MOCHA_PARALLEL && process.env.AUTO_AGENT) {
17
+ agent = createAgent(getEggOptions());
18
+ await agent.ready();
19
+ }
20
+ return agent;
21
+ }
22
+
23
+ export async function closeAgent() {
24
+ debug('setupAgent call, agent: %s', !!agent);
25
+ if (agent) {
26
+ await agent.close();
27
+ }
28
+ }
package/src/lib/app.ts ADDED
@@ -0,0 +1,313 @@
1
+ import { debuglog } from 'node:util';
2
+ import { strict as assert } from 'node:assert';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { Base } from 'sdk-base';
6
+ import { detectPort } from 'detect-port';
7
+ import type { Agent, Application } from 'egg';
8
+ import { importModule } from '@eggjs/utils';
9
+ import { sleep, rimraf, getProperty, getSourceDirname } from './utils.js';
10
+ import { formatOptions } from './format_options.js';
11
+ import { context } from './context.js';
12
+ import { setCustomLoader } from './mock_custom_loader.js';
13
+ import { createServer } from './mock_http_server.js';
14
+ import type { MockOptions, MockApplicationOptions } from './types.js';
15
+
16
+ const debug = debuglog('@eggjs/mock/lib/app');
17
+
18
+ const apps = new Map();
19
+ const APP_INIT = Symbol('appInit');
20
+ const MESSENGER = Symbol('messenger');
21
+ const MOCK_APP_METHOD = [
22
+ 'ready',
23
+ 'closed',
24
+ 'isClosed',
25
+ 'close',
26
+ '_agent',
27
+ '_app',
28
+ 'on',
29
+ 'once',
30
+ 'then',
31
+ ];
32
+
33
+ export class MockApplication extends Base {
34
+ _agent: Agent;
35
+ _app: Application;
36
+ declare options: MockApplicationOptions;
37
+ baseDir: string;
38
+ [APP_INIT] = false;
39
+ _initOnListeners: Set<any[]>;
40
+ _initOnceListeners: Set<any[]>;
41
+
42
+ constructor(options: MockApplicationOptions) {
43
+ super({
44
+ initMethod: '_init',
45
+ ...options,
46
+ });
47
+ this.baseDir = options.baseDir;
48
+ this._initOnListeners = new Set();
49
+ this._initOnceListeners = new Set();
50
+ }
51
+
52
+ async _init() {
53
+ if (this.options.beforeInit) {
54
+ await this.options.beforeInit(this);
55
+ // init once
56
+ this.options.beforeInit = undefined;
57
+ }
58
+ if (this.options.clean !== false) {
59
+ const logDir = path.join(this.options.baseDir, 'logs');
60
+ try {
61
+ if (os.platform() === 'win32') {
62
+ await sleep(1000);
63
+ }
64
+ await rimraf(logDir);
65
+ } catch (err: any) {
66
+ console.error(`remove log dir ${logDir} failed: ${err.stack}`);
67
+ }
68
+ const runDir = path.join(this.options.baseDir, 'run');
69
+ try {
70
+ if (os.platform() === 'win32') {
71
+ await sleep(1000);
72
+ }
73
+ await rimraf(runDir);
74
+ } catch (err: any) {
75
+ console.error(`remove run dir ${runDir} failed: ${err.stack}`);
76
+ }
77
+ }
78
+
79
+ this.options.clusterPort = await detectPort();
80
+ debug('get clusterPort %s', this.options.clusterPort);
81
+ const egg = await importModule(this.options.framework);
82
+ assert(egg.Agent, `should export Agent class from framework ${this.options.framework}`);
83
+
84
+ const Agent = egg.Agent;
85
+ const agent = this._agent = new Agent({ ...this.options }) as Agent;
86
+ debug('agent instantiate');
87
+ await agent.ready();
88
+ debug('agent ready');
89
+
90
+ const ApplicationClass = bindMessenger(egg.Application, agent);
91
+ const app = this._app = new ApplicationClass({ ...this.options }) as unknown as Application;
92
+
93
+ // https://github.com/eggjs/egg/blob/8bb7c7e7d59d6aeca4b2ed1eb580368dcb731a4d/lib/egg.js#L125
94
+ // egg single mode mount this at start(), so egg-mock should impel it.
95
+ app.agent = agent;
96
+ Reflect.set(agent, 'app', app);
97
+
98
+ // egg-mock plugin need to override egg context
99
+ Object.assign(app.context, context);
100
+
101
+ debug('app instantiate');
102
+ this[APP_INIT] = true;
103
+ debug('this[APP_INIT] = true');
104
+ this.#bindEvent();
105
+ debug('http server instantiate');
106
+ createServer(app);
107
+ await app.ready();
108
+ // work for config ready
109
+ setCustomLoader(app);
110
+
111
+ const msg = {
112
+ action: 'egg-ready',
113
+ data: this.options,
114
+ };
115
+ app.messenger.onMessage(msg);
116
+ agent.messenger.onMessage(msg);
117
+ debug('app ready');
118
+ }
119
+
120
+ #bindEvent() {
121
+ debug('bind cache events to app');
122
+ for (const args of this._initOnListeners) {
123
+ debug('on(%s), use cache and pass to app', args);
124
+ this._app.on(args[0], args[1]);
125
+ this.removeListener(args[0], args[1]);
126
+ }
127
+ for (const args of this._initOnceListeners) {
128
+ debug('once(%s), use cache and pass to app', args);
129
+ this._app.once(args[0], args[1]);
130
+ this.removeListener(args[0], args[1]);
131
+ }
132
+ }
133
+
134
+ on(...args: any[]) {
135
+ if (this[APP_INIT]) {
136
+ debug('on(%s), pass to app', args);
137
+ this._app.on(args[0], args[1]);
138
+ } else {
139
+ debug('on(%s), cache it because app has not init', args);
140
+ this._initOnListeners.add(args);
141
+ super.on(args[0], args[1]);
142
+ }
143
+ return this;
144
+ }
145
+
146
+ once(...args: any[]) {
147
+ if (this[APP_INIT]) {
148
+ debug('once(%s), pass to app', args);
149
+ this._app.once(args[0], args[1]);
150
+ } else {
151
+ debug('once(%s), cache it because app has not init', args);
152
+ this._initOnceListeners.add(args);
153
+ // maybe some edge case bug here
154
+ super.on(args[0], args[1]);
155
+ }
156
+ return this;
157
+ }
158
+
159
+ /**
160
+ * close app
161
+ */
162
+ async _close() {
163
+ const baseDir = this.baseDir;
164
+ if (this._app) {
165
+ await this._app.close();
166
+ } else {
167
+ // when app init throws an exception, must wait for app quit gracefully
168
+ await sleep(200);
169
+ }
170
+
171
+ if (this._agent) {
172
+ await this._agent.close();
173
+ }
174
+
175
+ apps.delete(baseDir);
176
+ debug('delete app cache %s, remain %s', baseDir, [ ...apps.keys() ]);
177
+
178
+ if (os.platform() === 'win32') {
179
+ await sleep(1000);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * @deprecated please use isClosed instead, keep compatible with old version
185
+ */
186
+ get closed() {
187
+ return this.isClosed;
188
+ }
189
+ }
190
+
191
+ export function createApp(createOptions?: MockOptions) {
192
+ const options = formatOptions(createOptions);
193
+ if (options.cache && apps.has(options.baseDir)) {
194
+ const app = apps.get(options.baseDir);
195
+ // return cache when it hasn't been killed
196
+ if (!app.isClosed) {
197
+ debug('use cache app %s', options.baseDir);
198
+ return app;
199
+ }
200
+ // delete the cache when it's closed
201
+ apps.delete(options.baseDir);
202
+ }
203
+
204
+ let app = new MockApplication(options);
205
+ app = new Proxy(app, {
206
+ get(target: any, prop: string) {
207
+ // don't delegate properties on MockApplication
208
+ if (MOCK_APP_METHOD.includes(prop)) {
209
+ return getProperty(target, prop);
210
+ }
211
+ if (!target[APP_INIT]) {
212
+ throw new Error(`can't get ${prop} before ready`);
213
+ }
214
+ // it's asynchronous when agent and app are loading,
215
+ // so should get the properties after loader ready
216
+ debug('proxy handler.get %s', prop);
217
+ return target._app[prop];
218
+ },
219
+ set(target, prop: string, value) {
220
+ if (MOCK_APP_METHOD.includes(prop)) return true;
221
+ if (!target[APP_INIT]) throw new Error(`can't set ${prop} before ready`);
222
+ debug('proxy handler.set %s', prop);
223
+ target._app[prop] = value;
224
+ return true;
225
+ },
226
+ defineProperty(target, prop: string, descriptor) {
227
+ // can't define properties on MockApplication
228
+ if (MOCK_APP_METHOD.includes(prop)) return true;
229
+ if (!target[APP_INIT]) throw new Error(`can't defineProperty ${prop} before ready`);
230
+ debug('proxy handler.defineProperty %s', prop);
231
+ Object.defineProperty(target._app, prop, descriptor);
232
+ return true;
233
+ },
234
+ deleteProperty(target, prop: string) {
235
+ // can't delete properties on MockApplication
236
+ if (MOCK_APP_METHOD.includes(prop)) return true;
237
+ if (!target[APP_INIT]) throw new Error(`can't delete ${prop} before ready`);
238
+ debug('proxy handler.deleteProperty %s', prop);
239
+ delete target._app[prop];
240
+ return true;
241
+ },
242
+ getOwnPropertyDescriptor(target, prop: string) {
243
+ if (MOCK_APP_METHOD.includes(prop)) {
244
+ return Object.getOwnPropertyDescriptor(target, prop);
245
+ }
246
+ if (!target[APP_INIT]) {
247
+ throw new Error(`can't getOwnPropertyDescriptor ${prop} before ready`);
248
+ }
249
+ debug('proxy handler.getOwnPropertyDescriptor %s', prop);
250
+ return Object.getOwnPropertyDescriptor(target._app, prop);
251
+ },
252
+ getPrototypeOf(target) {
253
+ if (!target[APP_INIT]) {
254
+ throw new Error('can\'t getPrototypeOf before ready');
255
+ }
256
+ debug('proxy handler.getPrototypeOf %s');
257
+ return Object.getPrototypeOf(target._app);
258
+ },
259
+ });
260
+
261
+ apps.set(options.baseDir, app);
262
+ return app;
263
+ }
264
+
265
+ function bindMessenger(Application: any, agent: Agent) {
266
+ const agentMessenger = agent.messenger;
267
+ return class MessengerApplication extends Application {
268
+ [MESSENGER]: any;
269
+
270
+ constructor(options: any) {
271
+ super(options);
272
+
273
+ // enable app to send to a random agent
274
+ this.messenger.sendRandom = (action: string, data: unknown) => {
275
+ this.messenger.sendToAgent(action, data);
276
+ };
277
+ // enable agent to send to a random app
278
+ agentMessenger.on('egg-ready', () => {
279
+ agentMessenger.sendRandom = (action: string, data: unknown) => {
280
+ agentMessenger.sendToApp(action, data);
281
+ return agentMessenger;
282
+ };
283
+ });
284
+
285
+ agentMessenger.send = new Proxy(agentMessenger.send, {
286
+ apply: this._sendMessage.bind(this),
287
+ });
288
+ }
289
+ _sendMessage(_target: any, _thisArg: unknown, [ action, data, to ]: [ string, unknown | undefined, string]) {
290
+ const appMessenger = this.messenger;
291
+ setImmediate(() => {
292
+ if (to === 'app') {
293
+ appMessenger.onMessage({ action, data });
294
+ } else {
295
+ agentMessenger.onMessage({ action, data });
296
+ }
297
+ });
298
+ }
299
+ get messenger() {
300
+ return this[MESSENGER];
301
+ }
302
+ set messenger(m) {
303
+ m.send = new Proxy(m.send, {
304
+ apply: this._sendMessage.bind(this),
305
+ });
306
+ this[MESSENGER] = m;
307
+ }
308
+
309
+ get [Symbol.for('egg#eggPath')]() {
310
+ return path.join(getSourceDirname(), 'lib/tmp');
311
+ }
312
+ };
313
+ }