@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
@@ -0,0 +1,512 @@
1
+ import { debuglog } from 'node:util';
2
+ import http, { IncomingMessage } from 'node:http';
3
+ import fs from 'node:fs';
4
+ import assert from 'node:assert';
5
+ import mergeDescriptors from 'merge-descriptors';
6
+ import { isAsyncFunction, isObject } from 'is-type-of';
7
+ import { mock, restore } from 'mm';
8
+ import type { HttpClient } from 'urllib';
9
+ import { Transport, Logger, LoggerLevel, LoggerMeta } from 'egg-logger';
10
+ import { EggCore, EggCoreOptions, ContextDelegation } from '@eggjs/core';
11
+ import { getMockAgent, restoreMockAgent } from '../../lib/mock_agent.js';
12
+ import {
13
+ createMockHttpClient, MockResultFunction,
14
+ MockResultOptions,
15
+ MockHttpClientMethod,
16
+ } from '../../lib/mock_httpclient.js';
17
+ import { request as supertestRequest, EggTestRequest } from '../../lib/supertest.js';
18
+ import { MockOptions } from '../../lib/types.js';
19
+
20
+ const debug = debuglog('@eggjs/mock/app/extend/application');
21
+
22
+ const ORIGIN_TYPES = Symbol('@eggjs/mock originTypes');
23
+ const BACKGROUND_TASKS = Symbol('Application#backgroundTasks');
24
+ const REUSED_CTX = Symbol('Context#reusedInSuite');
25
+
26
+ export interface MockContextOptions {
27
+ /**
28
+ * mock ctxStorage or not, default is `true`
29
+ */
30
+ mockCtxStorage?: boolean;
31
+ /**
32
+ * reuse ctxStorage or not, default is `true`
33
+ */
34
+ reuseCtxStorage?: boolean;
35
+ }
36
+
37
+ export interface MockContextData {
38
+ headers?: Record<string, string | string[]>;
39
+ [key: string]: any;
40
+ }
41
+
42
+ export interface MockContextDelegation extends ContextDelegation {
43
+ service: any;
44
+ }
45
+
46
+ export default abstract class ApplicationUnittest extends EggCore {
47
+ [key: string]: any;
48
+ declare options: MockOptions & EggCoreOptions;
49
+ _mockHttpClient: MockHttpClientMethod;
50
+ declare logger: Logger;
51
+ declare coreLogger: Logger;
52
+ abstract getLogger(name: string): Logger;
53
+ declare httpClient: HttpClient;
54
+ declare httpclient: HttpClient;
55
+
56
+ /**
57
+ * mock Context
58
+ * @function App#mockContext
59
+ * @param {Object} data - ctx data
60
+ * @param {Object} [options] - mock ctx options
61
+ * @example
62
+ * ```js
63
+ * const ctx = app.mockContext({
64
+ * user: {
65
+ * name: 'Jason'
66
+ * }
67
+ * });
68
+ * console.log(ctx.user.name); // Jason
69
+ *
70
+ * // controller
71
+ * module.exports = function*() {
72
+ * this.body = this.user.name;
73
+ * };
74
+ * ```
75
+ */
76
+ mockContext(data?: MockContextData, options?: MockContextOptions): MockContextDelegation {
77
+ data = data ?? {};
78
+ function mockRequest(req: IncomingMessage) {
79
+ for (const key in data?.headers) {
80
+ mock(req.headers, key, data.headers[key]);
81
+ mock(req.headers, key.toLowerCase(), data.headers[key]);
82
+ }
83
+ }
84
+
85
+ // try to use app.options.mockCtxStorage first
86
+ const mockCtxStorage = this.options.mockCtxStorage ?? true;
87
+ options = Object.assign({ mockCtxStorage }, options);
88
+
89
+ if ('_customMockContext' in this && typeof this._customMockContext === 'function') {
90
+ this._customMockContext(data);
91
+ }
92
+
93
+ // 使用者自定义mock,可以覆盖上面的 mock
94
+ for (const key in data) {
95
+ mock(this.context, key, data[key]);
96
+ }
97
+
98
+ const req = this.mockRequest(data);
99
+ const res = new http.ServerResponse(req);
100
+
101
+ if (options.reuseCtxStorage !== false) {
102
+ if (this.currentContext && !this.currentContext[REUSED_CTX]) {
103
+ mockRequest(this.currentContext.request.req);
104
+ this.currentContext[REUSED_CTX] = true;
105
+ return this.currentContext as MockContextDelegation;
106
+ }
107
+ }
108
+ const ctx = this.createContext(req, res);
109
+ if (options.mockCtxStorage) {
110
+ mock(this.ctxStorage, 'getStore', () => ctx);
111
+ }
112
+ return ctx as MockContextDelegation;
113
+ }
114
+
115
+ async mockContextScope(fn: (ctx?: MockContextDelegation) => Promise<any>, data?: MockContextData) {
116
+ const ctx = this.mockContext(data, {
117
+ mockCtxStorage: false,
118
+ reuseCtxStorage: false,
119
+ });
120
+ return await this.ctxStorage.run(ctx, async () => {
121
+ return await fn(ctx);
122
+ });
123
+ }
124
+
125
+ /**
126
+ * mock cookie session
127
+ * @function App#mockSession
128
+ * @param {Object} data - session object
129
+ */
130
+ mockSession(data: any) {
131
+ if (!data) {
132
+ return this;
133
+ }
134
+
135
+ if (isObject(data) && !('save' in data)) {
136
+ // keep session.save() work
137
+ Object.defineProperty(data, 'save', {
138
+ value: () => {},
139
+ enumerable: false,
140
+ });
141
+ }
142
+ mock(this.context, 'session', data);
143
+ return this;
144
+ }
145
+
146
+ /**
147
+ * Mock service
148
+ * @function App#mockService
149
+ * @param {String} service - name
150
+ * @param {String} methodName - method
151
+ * @param {Object|Function|Error} fn - mock you data
152
+ */
153
+ mockService(service: string | any, methodName: string, fn: any) {
154
+ if (typeof service === 'string') {
155
+ const splits = service.split('.');
156
+ service = this.serviceClasses;
157
+ for (const key of splits) {
158
+ service = service[key];
159
+ }
160
+ service = service.prototype || service;
161
+ }
162
+ this._mockFn(service, methodName, fn);
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ * mock service that return error
168
+ * @function App#mockServiceError
169
+ * @param {String} service - name
170
+ * @param {String} methodName - method
171
+ * @param {Error} [err] - error information
172
+ */
173
+ mockServiceError(service: string | any, methodName: string, err?: string | Error) {
174
+ if (typeof err === 'string') {
175
+ err = new Error(err);
176
+ }
177
+ if (!err) {
178
+ // mockServiceError(service, methodName)
179
+ err = new Error(`mock ${methodName} error`);
180
+ }
181
+ this.mockService(service, methodName, err);
182
+ return this;
183
+ }
184
+
185
+ _mockFn(obj: any, name: string, data: any) {
186
+ const origin = obj[name];
187
+ assert(typeof origin === 'function', `property ${name} in original object must be function`);
188
+
189
+ // keep origin properties' type to support mock multi times
190
+ if (!obj[ORIGIN_TYPES]) obj[ORIGIN_TYPES] = {};
191
+ let type = obj[ORIGIN_TYPES][name];
192
+ if (!type) {
193
+ type = obj[ORIGIN_TYPES][name] = isAsyncFunction(origin) ? 'async' : 'sync';
194
+ }
195
+
196
+ if (typeof data === 'function') {
197
+ const fn = data;
198
+ // if original is async function
199
+ // but the mock function is normal function, need to change it return a promise
200
+ if (type === 'async' && !isAsyncFunction(fn)) {
201
+ mock(obj, name, function(this: any, ...args: any[]) {
202
+ return new Promise(resolve => {
203
+ resolve(fn.apply(this, args));
204
+ });
205
+ });
206
+ return;
207
+ }
208
+
209
+ mock(obj, name, fn);
210
+ return;
211
+ }
212
+
213
+ if (type === 'async') {
214
+ mock(obj, name, () => {
215
+ return new Promise((resolve, reject) => {
216
+ if (data instanceof Error) return reject(data);
217
+ resolve(data);
218
+ });
219
+ });
220
+ return;
221
+ }
222
+
223
+ mock(obj, name, () => {
224
+ if (data instanceof Error) {
225
+ throw data;
226
+ }
227
+ return data;
228
+ });
229
+ }
230
+
231
+ /**
232
+ * mock request
233
+ * @function App#mockRequest
234
+ * @param {Request} req - mock request
235
+ */
236
+ mockRequest(req: MockContextData) {
237
+ req = { ...req };
238
+ const headers = req.headers ?? {};
239
+ for (const key in req.headers) {
240
+ headers[key.toLowerCase()] = req.headers[key];
241
+ }
242
+ if (!headers['x-forwarded-for']) {
243
+ headers['x-forwarded-for'] = '127.0.0.1';
244
+ }
245
+ headers['x-mock-request-from'] = '@eggjs/mock';
246
+ req.headers = headers;
247
+ mergeDescriptors(req, {
248
+ query: {},
249
+ querystring: '',
250
+ host: '127.0.0.1',
251
+ hostname: '127.0.0.1',
252
+ protocol: 'http',
253
+ secure: 'false',
254
+ method: 'GET',
255
+ url: '/',
256
+ path: '/',
257
+ socket: {
258
+ remoteAddress: '127.0.0.1',
259
+ remotePort: 7001,
260
+ },
261
+ });
262
+ return req as IncomingMessage;
263
+ }
264
+
265
+ /**
266
+ * mock cookies
267
+ * @function App#mockCookies
268
+ */
269
+ mockCookies(cookies: Record<string, string | string[]>) {
270
+ if (!cookies) {
271
+ return this;
272
+ }
273
+ const createContext = this.createContext;
274
+ mock(this, 'createContext', function(this: any, req: any, res: any) {
275
+ const ctx = createContext.call(this, req, res);
276
+ const getCookie = ctx.cookies.get;
277
+ mock(ctx.cookies, 'get', function(this: any, key: string, opts: any) {
278
+ if (cookies[key]) {
279
+ return cookies[key];
280
+ }
281
+ return getCookie.call(this, key, opts);
282
+ });
283
+ return ctx;
284
+ });
285
+ return this;
286
+ }
287
+
288
+ /**
289
+ * mock header
290
+ * @function App#mockHeaders
291
+ */
292
+ mockHeaders(headers: Record<string, string | string[]>) {
293
+ if (!headers) {
294
+ return this;
295
+ }
296
+ const getHeader = this.request.get;
297
+ mock(this.request, 'get', function(this: unknown, field: string) {
298
+ const value = findHeaders(headers, field);
299
+ if (value) return value;
300
+ return getHeader.call(this, field);
301
+ });
302
+ return this;
303
+ }
304
+
305
+ /**
306
+ * mock csrf
307
+ * @function App#mockCsrf
308
+ * @since 1.11
309
+ */
310
+ mockCsrf() {
311
+ mock(this.context, 'assertCSRF', () => {});
312
+ mock(this.context, 'assertCsrf', () => {});
313
+ return this;
314
+ }
315
+
316
+ /**
317
+ * mock httpclient
318
+ * @alias mockHttpClient
319
+ * @function App#mockHttpclient
320
+ */
321
+ mockHttpclient(mockUrl: string | RegExp, mockMethod: string | string[] | MockResultOptions | MockResultFunction, mockResult?: MockResultOptions | MockResultFunction | string) {
322
+ return this.mockHttpClient(mockUrl, mockMethod, mockResult);
323
+ }
324
+
325
+ /**
326
+ * mock httpclient
327
+ * @function App#mockHttpClient
328
+ */
329
+ mockHttpClient(mockUrl: string | RegExp, mockMethod: string | string[] | MockResultOptions | MockResultFunction, mockResult?: MockResultOptions | MockResultFunction | string) {
330
+ if (!this._mockHttpClient) {
331
+ this._mockHttpClient = createMockHttpClient(this);
332
+ }
333
+ return this._mockHttpClient(mockUrl, mockMethod, mockResult);
334
+ }
335
+
336
+ /**
337
+ * @deprecated Please use app.mockHttpClient instead of app.mockUrllib
338
+ */
339
+ mockUrllib(mockUrl: string | RegExp, mockMethod: string | string[] | MockResultOptions | MockResultFunction, mockResult?: MockResultOptions | MockResultFunction | string) {
340
+ this.deprecate('[@eggjs/mock] Please use app.mockHttpClient instead of app.mockUrllib');
341
+ return this.mockHttpClient(mockUrl, mockMethod, mockResult);
342
+ }
343
+
344
+ /**
345
+ * get mock httpclient agent
346
+ * @function App#mockHttpclientAgent
347
+ */
348
+ mockAgent() {
349
+ return getMockAgent(this as any);
350
+ }
351
+
352
+ async mockAgentRestore() {
353
+ await restoreMockAgent();
354
+ }
355
+
356
+ /**
357
+ * @see mm#restore
358
+ * @function App#mockRestore
359
+ */
360
+ get mockRestore() {
361
+ return restore;
362
+ }
363
+
364
+ /**
365
+ * @see mm
366
+ * @function App#mm
367
+ */
368
+ get mm() {
369
+ return mock;
370
+ }
371
+
372
+ /**
373
+ * override loadAgent
374
+ * @function App#loadAgent
375
+ */
376
+ loadAgent() {}
377
+
378
+ /**
379
+ * mock serverEnv
380
+ * @function App#mockEnv
381
+ * @param {String} env - serverEnv
382
+ */
383
+ mockEnv(env: string) {
384
+ mock(this.config, 'env', env);
385
+ mock(this.config, 'serverEnv', env);
386
+ debug('mock env: %o', env);
387
+ return this;
388
+ }
389
+
390
+ /**
391
+ * http request helper
392
+ * @function App#httpRequest
393
+ * @return {SupertestRequest} req - supertest request
394
+ * @see https://github.com/visionmedia/supertest
395
+ */
396
+ httpRequest(): EggTestRequest {
397
+ return supertestRequest(this);
398
+ }
399
+
400
+ /**
401
+ * collection logger message, then can be use on `expectLog()`
402
+ * @param {String|Logger} [logger] - logger instance, default is `app.logger`
403
+ * @function App#mockLog
404
+ */
405
+ mockLog(logger?: string | Logger) {
406
+ logger = logger ?? this.logger;
407
+ if (typeof logger === 'string') {
408
+ logger = this.getLogger(logger);
409
+ }
410
+ // make sure mock once
411
+ if ('_mockLogs' in logger && logger._mockLogs) return;
412
+
413
+ const transport = new Transport(logger.options);
414
+ // https://github.com/eggjs/egg-logger/blob/master/lib/logger.js#L64
415
+ const log = logger.log;
416
+ const mockLogs: string[] = [];
417
+ mock(logger, '_mockLogs', mockLogs);
418
+ mock(logger, 'log', (level: LoggerLevel, args: any[], meta: LoggerMeta) => {
419
+ const message = transport.log(level, args, meta);
420
+ mockLogs.push(message);
421
+ log.apply(logger, [ level, args, meta ]);
422
+ });
423
+ }
424
+
425
+ __checkExpectLog(expectOrNot: boolean, str: string | RegExp, logger?: string | Logger) {
426
+ logger = logger || this.logger;
427
+ if (typeof logger === 'string') {
428
+ logger = this.getLogger(logger);
429
+ }
430
+ const filepath = logger.options.file;
431
+ let content;
432
+ if ('_mockLogs' in logger && logger._mockLogs) {
433
+ content = (logger._mockLogs as string[]).join('\n');
434
+ } else {
435
+ content = fs.readFileSync(filepath, 'utf8');
436
+ }
437
+ let match;
438
+ let type;
439
+ if (str instanceof RegExp) {
440
+ match = str.test(content);
441
+ type = 'RegExp';
442
+ } else {
443
+ match = content.includes(String(str));
444
+ type = 'String';
445
+ }
446
+ if (expectOrNot) {
447
+ assert(match,
448
+ `Can't find ${type}:"${str}" in ${filepath}, log content: ...${content.substring(content.length - 500)}`);
449
+ } else {
450
+ assert(!match,
451
+ `Find ${type}:"${str}" in ${filepath}, log content: ...${content.substring(content.length - 500)}`);
452
+ }
453
+ }
454
+
455
+ /**
456
+ * expect str/regexp in the logger, if your server disk is slow, please call `mockLog()` first.
457
+ * @param {String|RegExp} str - test str or regexp
458
+ * @param {String|Logger} [logger] - logger instance, default is `ctx.logger`
459
+ * @function App#expectLog
460
+ */
461
+ expectLog(str: string | RegExp, logger?: string | Logger) {
462
+ this.__checkExpectLog(true, str, logger);
463
+ }
464
+
465
+ /**
466
+ * not expect str/regexp in the logger, if your server disk is slow, please call `mockLog()` first.
467
+ * @param {String|RegExp} str - test str or regexp
468
+ * @param {String|Logger} [logger] - logger instance, default is `ctx.logger`
469
+ * @function App#notExpectLog
470
+ */
471
+ notExpectLog(str: string | RegExp, logger?: string | Logger) {
472
+ this.__checkExpectLog(false, str, logger);
473
+ }
474
+
475
+ async backgroundTasksFinished() {
476
+ const tasks = this._backgroundTasks;
477
+ debug('waiting %d background tasks', tasks.length);
478
+ if (tasks.length === 0) return;
479
+
480
+ this._backgroundTasks = [];
481
+ await Promise.all(tasks);
482
+ debug('finished %d background tasks', tasks.length);
483
+ if (this._backgroundTasks.length) {
484
+ debug('new background tasks created: %s', this._backgroundTasks.length);
485
+ await this.backgroundTasksFinished();
486
+ }
487
+ }
488
+
489
+ get _backgroundTasks() {
490
+ if (!this[BACKGROUND_TASKS]) {
491
+ this[BACKGROUND_TASKS] = [];
492
+ }
493
+ return this[BACKGROUND_TASKS] as Promise<any>[];
494
+ }
495
+
496
+ set _backgroundTasks(tasks) {
497
+ this[BACKGROUND_TASKS] = tasks;
498
+ }
499
+ }
500
+
501
+ function findHeaders(headers: Record<string, any>, key: string) {
502
+ if (!headers || !key) {
503
+ return null;
504
+ }
505
+ key = key.toLowerCase();
506
+ for (const headerKey in headers) {
507
+ if (key === headerKey.toLowerCase()) {
508
+ return headers[headerKey];
509
+ }
510
+ }
511
+ return null;
512
+ }
@@ -0,0 +1,101 @@
1
+ import { debuglog } from 'node:util';
2
+ import { ContextDelegation, Next } from '@eggjs/core';
3
+
4
+ const debug = debuglog('@eggjs/mock/app/middleware/cluster_app_mock');
5
+
6
+ export default () => {
7
+ return async function clusterAppMock(ctx: ContextDelegation, next: Next) {
8
+ // use originalUrl to make sure other middlewares can't change request url
9
+ if (ctx.originalUrl !== '/__egg_mock_call_function') {
10
+ return next();
11
+ }
12
+ debug('%s %s, body: %j', ctx.method, ctx.url, ctx.request.body);
13
+ const { method, property, args, needResult } = ctx.request.body;
14
+ if (!method) {
15
+ ctx.status = 422;
16
+ ctx.body = {
17
+ success: false,
18
+ error: 'Missing method',
19
+ };
20
+ return;
21
+ }
22
+ if (args && !Array.isArray(args)) {
23
+ ctx.status = 422;
24
+ ctx.body = {
25
+ success: false,
26
+ error: 'args should be an Array instance',
27
+ };
28
+ return;
29
+ }
30
+ if (property) {
31
+ // method: '__getter__' and property: 'config'
32
+ if (method === '__getter__') {
33
+ if (!ctx.app[property]) {
34
+ debug('property %s not exists on app', property);
35
+ ctx.status = 422;
36
+ ctx.body = {
37
+ success: false,
38
+ error: `property "${property}" not exists on app`,
39
+ };
40
+ return;
41
+ }
42
+ ctx.body = { success: true, result: ctx.app[property] };
43
+ return;
44
+ }
45
+
46
+ if (!ctx.app[property] || typeof (ctx.app as any)[property][method] !== 'function') {
47
+ debug('property %s.%s not exists on app', property, method);
48
+ ctx.status = 422;
49
+ ctx.body = {
50
+ success: false,
51
+ error: `method "${method}" not exists on app.${property}`,
52
+ };
53
+ return;
54
+ }
55
+ } else {
56
+ if (typeof ctx.app[method] !== 'function') {
57
+ debug('method %s not exists on app', method);
58
+ ctx.status = 422;
59
+ ctx.body = {
60
+ success: false,
61
+ error: `method "${method}" not exists on app`,
62
+ };
63
+ return;
64
+ }
65
+ }
66
+
67
+ debug('call %s with %j', method, args);
68
+
69
+ for (let i = 0; i < args.length; i++) {
70
+ const arg = args[i];
71
+ if (arg && typeof arg === 'object') {
72
+ // convert __egg_mock_type back to function
73
+ if (arg.__egg_mock_type === 'function') {
74
+ // eslint-disable-next-line
75
+ args[i] = eval(`(function() { return ${arg.value} })()`);
76
+ } else if (arg.__egg_mock_type === 'error') {
77
+ const err: any = new Error(arg.message);
78
+ err.name = arg.name;
79
+ err.stack = arg.stack;
80
+ for (const key in arg) {
81
+ if (key !== 'name' && key !== 'message' && key !== 'stack' && key !== '__egg_mock_type') {
82
+ err[key] = arg[key];
83
+ }
84
+ }
85
+ args[i] = err;
86
+ }
87
+ }
88
+ }
89
+
90
+ const target: any = property ? ctx.app[property] : ctx.app;
91
+ const fn = target[method];
92
+ try {
93
+ Promise.resolve(fn.call(target, ...args)).then(result => {
94
+ ctx.body = needResult ? { success: true, result } : { success: true };
95
+ });
96
+ } catch (err: any) {
97
+ ctx.status = 500;
98
+ ctx.body = { success: false, error: err.message };
99
+ }
100
+ };
101
+ };
package/src/app.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { ILifecycleBoot, EggCore } from '@eggjs/core';
2
+
3
+ export default class Boot implements ILifecycleBoot {
4
+ #app: EggCore;
5
+ constructor(app: EggCore) {
6
+ this.#app = app;
7
+ }
8
+
9
+ configWillLoad() {
10
+ // make sure clusterAppMock position before securities
11
+ const index = this.#app.config.coreMiddleware.indexOf('securities');
12
+ if (index >= 0) {
13
+ this.#app.config.coreMiddleware.splice(index, 0, 'clusterAppMock');
14
+ } else {
15
+ this.#app.config.coreMiddleware.push('clusterAppMock');
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,25 @@
1
+ import { strict as assert } from 'node:assert';
2
+ import path from 'node:path';
3
+ import { readJSONSync } from 'utility';
4
+ import mm, { mock } from './index.js';
5
+ import { getBootstrapApp, setupApp } from './lib/app_handler.js';
6
+ import { getEggOptions } from './lib/utils.js';
7
+
8
+ const options = getEggOptions();
9
+
10
+ // throw error when an egg plugin test is using bootstrap
11
+ const pkgInfo = readJSONSync(path.join(options.baseDir || process.cwd(), 'package.json'));
12
+ if (pkgInfo.eggPlugin) {
13
+ throw new Error('DO NOT USE bootstrap to test plugin');
14
+ }
15
+
16
+ setupApp();
17
+
18
+ const app = getBootstrapApp();
19
+
20
+ export {
21
+ assert,
22
+ app,
23
+ mm,
24
+ mock,
25
+ };