@asenajs/asena 0.7.1 → 0.8.0
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.
- package/README.md +2 -2
- package/dist/lib/adapter/AsenaAdapter.d.ts +2 -2
- package/dist/lib/adapter/AsenaAdapter.js.map +1 -1
- package/dist/lib/adapter/types/ServeOptions.d.ts +16 -0
- package/dist/lib/ioc/Container.d.ts +25 -0
- package/dist/lib/ioc/Container.js +47 -0
- package/dist/lib/ioc/Container.js.map +1 -1
- package/dist/lib/ioc/CoreContainer.d.ts +5 -3
- package/dist/lib/ioc/CoreContainer.js +9 -3
- package/dist/lib/ioc/CoreContainer.js.map +1 -1
- package/dist/lib/ioc/IocEngine.js +3 -1
- package/dist/lib/ioc/IocEngine.js.map +1 -1
- package/dist/lib/ioc/component/decorators/Inject.js +16 -0
- package/dist/lib/ioc/component/decorators/Inject.js.map +1 -1
- package/dist/lib/ioc/constants/ComponentConstants.d.ts +4 -0
- package/dist/lib/ioc/constants/ComponentConstants.js +5 -0
- package/dist/lib/ioc/constants/ComponentConstants.js.map +1 -1
- package/dist/lib/ioc/types/ICoreServiceNamesType.d.ts +1 -0
- package/dist/lib/ioc/types/ICoreServiceNamesType.js +1 -0
- package/dist/lib/ioc/types/ICoreServiceNamesType.js.map +1 -1
- package/dist/lib/ioc/types/InjectableComponent.d.ts +10 -0
- package/dist/lib/ioc/types/decorators/ComponentType.d.ts +2 -1
- package/dist/lib/ioc/types/decorators/ComponentType.js +1 -0
- package/dist/lib/ioc/types/decorators/ComponentType.js.map +1 -1
- package/dist/lib/server/AsenaServer.d.ts +46 -3
- package/dist/lib/server/AsenaServer.js +158 -28
- package/dist/lib/server/AsenaServer.js.map +1 -1
- package/dist/lib/server/AsenaServerFactory.d.ts +38 -3
- package/dist/lib/server/AsenaServerFactory.js +21 -2
- package/dist/lib/server/AsenaServerFactory.js.map +1 -1
- package/dist/lib/server/config/AsenaConfig.d.ts +82 -4
- package/dist/lib/server/config/AsenaConfig.js +39 -1
- package/dist/lib/server/config/AsenaConfig.js.map +1 -1
- package/dist/lib/server/decorators/components/MessageController.d.ts +49 -0
- package/dist/lib/server/decorators/components/MessageController.js +62 -0
- package/dist/lib/server/decorators/components/MessageController.js.map +1 -0
- package/dist/lib/server/decorators/components/index.d.ts +1 -0
- package/dist/lib/server/decorators/components/index.js +1 -0
- package/dist/lib/server/decorators/components/index.js.map +1 -1
- package/dist/lib/server/event/EventDispatchService.d.ts +3 -7
- package/dist/lib/server/event/EventDispatchService.js +9 -40
- package/dist/lib/server/event/EventDispatchService.js.map +1 -1
- package/dist/lib/server/event/PatternHandlerIndex.d.ts +53 -0
- package/dist/lib/server/event/PatternHandlerIndex.js +100 -0
- package/dist/lib/server/event/PatternHandlerIndex.js.map +1 -0
- package/dist/lib/server/event/decorators/On.d.ts +4 -0
- package/dist/lib/server/event/decorators/On.js +6 -0
- package/dist/lib/server/event/decorators/On.js.map +1 -1
- package/dist/lib/server/event/index.d.ts +1 -0
- package/dist/lib/server/event/index.js +2 -0
- package/dist/lib/server/event/index.js.map +1 -1
- package/dist/lib/server/event/types.d.ts +12 -0
- package/dist/lib/server/health/HealthServer.d.ts +26 -0
- package/dist/lib/server/health/HealthServer.js +63 -0
- package/dist/lib/server/health/HealthServer.js.map +1 -0
- package/dist/lib/server/health/index.d.ts +1 -0
- package/dist/lib/server/health/index.js +2 -0
- package/dist/lib/server/health/index.js.map +1 -0
- package/dist/lib/server/messaging/Ulak.d.ts +156 -0
- package/dist/lib/server/messaging/Ulak.js +174 -0
- package/dist/lib/server/messaging/Ulak.js.map +1 -1
- package/dist/lib/server/messaging/types.d.ts +5 -1
- package/dist/lib/server/messaging/types.js +5 -0
- package/dist/lib/server/messaging/types.js.map +1 -1
- package/dist/lib/server/microservice/InMemoryTransport.d.ts +45 -0
- package/dist/lib/server/microservice/InMemoryTransport.js +124 -0
- package/dist/lib/server/microservice/InMemoryTransport.js.map +1 -0
- package/dist/lib/server/microservice/MicroserviceTransport.d.ts +132 -0
- package/dist/lib/server/microservice/MicroserviceTransport.js +2 -0
- package/dist/lib/server/microservice/MicroserviceTransport.js.map +1 -0
- package/dist/lib/server/microservice/decorators/EventPattern.d.ts +38 -0
- package/dist/lib/server/microservice/decorators/EventPattern.js +59 -0
- package/dist/lib/server/microservice/decorators/EventPattern.js.map +1 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.d.ts +31 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.js +55 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.js.map +1 -0
- package/dist/lib/server/microservice/decorators/index.d.ts +2 -0
- package/dist/lib/server/microservice/decorators/index.js +3 -0
- package/dist/lib/server/microservice/decorators/index.js.map +1 -0
- package/dist/lib/server/microservice/index.d.ts +5 -0
- package/dist/lib/server/microservice/index.js +11 -0
- package/dist/lib/server/microservice/index.js.map +1 -0
- package/dist/lib/server/microservice/interceptorChain.d.ts +20 -0
- package/dist/lib/server/microservice/interceptorChain.js +39 -0
- package/dist/lib/server/microservice/interceptorChain.js.map +1 -0
- package/dist/lib/server/microservice/types.d.ts +142 -0
- package/dist/lib/server/microservice/types.js +5 -0
- package/dist/lib/server/microservice/types.js.map +1 -0
- package/dist/lib/server/src/services/PrepareEventService.d.ts +4 -1
- package/dist/lib/server/src/services/PrepareEventService.js +10 -3
- package/dist/lib/server/src/services/PrepareEventService.js.map +1 -1
- package/dist/lib/server/src/services/PrepareMicroserviceService.d.ts +66 -0
- package/dist/lib/server/src/services/PrepareMicroserviceService.js +193 -0
- package/dist/lib/server/src/services/PrepareMicroserviceService.js.map +1 -0
- package/dist/lib/test/factory/deepMock.d.ts +25 -0
- package/dist/lib/test/factory/deepMock.js +105 -0
- package/dist/lib/test/factory/deepMock.js.map +1 -0
- package/dist/lib/test/factory/ulakStub.d.ts +44 -0
- package/dist/lib/test/factory/ulakStub.js +40 -0
- package/dist/lib/test/factory/ulakStub.js.map +1 -0
- package/dist/lib/test/harness/createTestApp.d.ts +33 -0
- package/dist/lib/test/harness/createTestApp.js +105 -0
- package/dist/lib/test/harness/createTestApp.js.map +1 -0
- package/dist/lib/test/harness/createWebTest.d.ts +28 -0
- package/dist/lib/test/harness/createWebTest.js +127 -0
- package/dist/lib/test/harness/createWebTest.js.map +1 -0
- package/dist/lib/test/harness/silentLogger.d.ts +27 -0
- package/dist/lib/test/harness/silentLogger.js +54 -0
- package/dist/lib/test/harness/silentLogger.js.map +1 -0
- package/dist/lib/test/harness/types.d.ts +147 -0
- package/dist/lib/test/harness/types.js +2 -0
- package/dist/lib/test/harness/types.js.map +1 -0
- package/dist/lib/test/http/TestHttpCall.d.ts +78 -0
- package/dist/lib/test/http/TestHttpCall.js +149 -0
- package/dist/lib/test/http/TestHttpCall.js.map +1 -0
- package/dist/lib/test/http/TestHttpResponse.d.ts +46 -0
- package/dist/lib/test/http/TestHttpResponse.js +70 -0
- package/dist/lib/test/http/TestHttpResponse.js.map +1 -0
- package/dist/lib/test/index.d.ts +9 -0
- package/dist/lib/test/index.js +8 -0
- package/dist/lib/test/index.js.map +1 -1
- package/dist/lib/test/metadata/discovery.d.ts +21 -0
- package/dist/lib/test/metadata/discovery.js +37 -8
- package/dist/lib/test/metadata/discovery.js.map +1 -1
- package/dist/lib/test/mockComponent.d.ts +25 -0
- package/dist/lib/test/mockComponent.js +39 -6
- package/dist/lib/test/mockComponent.js.map +1 -1
- package/dist/lib/test/types.d.ts +13 -0
- package/package.json +11 -7
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response whose body has already been read.
|
|
3
|
+
*
|
|
4
|
+
* `Response` bodies can only be consumed once, which makes them awkward to assert against
|
|
5
|
+
* more than one way. This buffers the body at construction so `text()` and `json()` are
|
|
6
|
+
* synchronous and repeatable.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TestHttpResponse {
|
|
9
|
+
/**
|
|
10
|
+
* The underlying fetch Response. Its body stream is already consumed.
|
|
11
|
+
*/
|
|
12
|
+
readonly raw: Response;
|
|
13
|
+
private readonly body;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Buffers a fetch Response into a TestHttpResponse
|
|
17
|
+
*
|
|
18
|
+
* @param raw - Response to consume
|
|
19
|
+
* @returns The buffered response
|
|
20
|
+
*/
|
|
21
|
+
static from(raw: Response): Promise<TestHttpResponse>;
|
|
22
|
+
get status(): number;
|
|
23
|
+
get headers(): Headers;
|
|
24
|
+
/**
|
|
25
|
+
* The response body as text
|
|
26
|
+
*
|
|
27
|
+
* @returns Body string
|
|
28
|
+
*/
|
|
29
|
+
text(): string;
|
|
30
|
+
/**
|
|
31
|
+
* The response body parsed as JSON
|
|
32
|
+
*
|
|
33
|
+
* @returns Parsed body
|
|
34
|
+
* @throws When the body is not valid JSON - the message includes the raw body
|
|
35
|
+
*/
|
|
36
|
+
json<T = any>(): T;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Shortens a body for error messages so a large HTML page cannot bury the failure
|
|
40
|
+
*
|
|
41
|
+
* @param value - Body text
|
|
42
|
+
* @returns Truncated text
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare function truncate(value: string, limit?: number): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response whose body has already been read.
|
|
3
|
+
*
|
|
4
|
+
* `Response` bodies can only be consumed once, which makes them awkward to assert against
|
|
5
|
+
* more than one way. This buffers the body at construction so `text()` and `json()` are
|
|
6
|
+
* synchronous and repeatable.
|
|
7
|
+
*/
|
|
8
|
+
export class TestHttpResponse {
|
|
9
|
+
/**
|
|
10
|
+
* The underlying fetch Response. Its body stream is already consumed.
|
|
11
|
+
*/
|
|
12
|
+
raw;
|
|
13
|
+
body;
|
|
14
|
+
constructor(raw, body) {
|
|
15
|
+
this.raw = raw;
|
|
16
|
+
this.body = body;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Buffers a fetch Response into a TestHttpResponse
|
|
20
|
+
*
|
|
21
|
+
* @param raw - Response to consume
|
|
22
|
+
* @returns The buffered response
|
|
23
|
+
*/
|
|
24
|
+
static async from(raw) {
|
|
25
|
+
return new TestHttpResponse(raw, await raw.text());
|
|
26
|
+
}
|
|
27
|
+
get status() {
|
|
28
|
+
return this.raw.status;
|
|
29
|
+
}
|
|
30
|
+
get headers() {
|
|
31
|
+
return this.raw.headers;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The response body as text
|
|
35
|
+
*
|
|
36
|
+
* @returns Body string
|
|
37
|
+
*/
|
|
38
|
+
text() {
|
|
39
|
+
return this.body;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The response body parsed as JSON
|
|
43
|
+
*
|
|
44
|
+
* @returns Parsed body
|
|
45
|
+
* @throws When the body is not valid JSON - the message includes the raw body
|
|
46
|
+
*/
|
|
47
|
+
json() {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(this.body);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`Expected a JSON body but parsing failed. Received: ${truncate(this.body)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Shortens a body for error messages so a large HTML page cannot bury the failure
|
|
58
|
+
*
|
|
59
|
+
* @param value - Body text
|
|
60
|
+
* @returns Truncated text
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function truncate(value, limit = 500) {
|
|
65
|
+
if (value.length <= limit) {
|
|
66
|
+
return value.length ? value : '<empty body>';
|
|
67
|
+
}
|
|
68
|
+
return `${value.slice(0, limit)}... (${value.length} bytes total)`;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=TestHttpResponse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestHttpResponse.js","sourceRoot":"","sources":["../../../../lib/test/http/TestHttpResponse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAC3B;;OAEG;IACa,GAAG,CAAW;IAEb,IAAI,CAAS;IAE9B,YAAoB,GAAa,EAAE,IAAY;QAC7C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAa;QACpC,OAAO,IAAI,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IACzB,CAAC;IAED,IAAW,OAAO;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IAC1B,CAAC;IAED;;;;OAIG;IACI,IAAI;QACT,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACI,IAAI;QACT,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAM,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,sDAAsD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,KAAK,GAAG,GAAG;IACjD,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC;IAC/C,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,KAAK,CAAC,MAAM,eAAe,CAAC;AACrE,CAAC"}
|
package/dist/lib/test/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export { mockComponent, mockComponentAsync } from './mockComponent';
|
|
2
2
|
export type { MockComponentOptions, MockedComponent } from './types';
|
|
3
3
|
export { createMockFromClass } from './factory/mockFactory';
|
|
4
|
+
export { createDeepMock } from './factory/deepMock';
|
|
5
|
+
export { createTestUlakStub, type TestUlakNameSpace } from './factory/ulakStub';
|
|
6
|
+
export { discoverInjectedFields, discoverInjectedFieldsFromClass } from './metadata/discovery';
|
|
7
|
+
export { createTestApp } from './harness/createTestApp';
|
|
8
|
+
export { createWebTest } from './harness/createWebTest';
|
|
9
|
+
export { createCapturingLogger, silentLogger, type CapturedLogEntry } from './harness/silentLogger';
|
|
10
|
+
export type { DispatchMode, TestApp, TestAppOptions, WebTestOptions, WebTestResult } from './harness/types';
|
|
11
|
+
export { TestHttpCall } from './http/TestHttpCall';
|
|
12
|
+
export { TestHttpResponse } from './http/TestHttpResponse';
|
package/dist/lib/test/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export { mockComponent, mockComponentAsync } from './mockComponent';
|
|
2
2
|
export { createMockFromClass } from './factory/mockFactory';
|
|
3
|
+
export { createDeepMock } from './factory/deepMock';
|
|
4
|
+
export { createTestUlakStub } from './factory/ulakStub';
|
|
5
|
+
export { discoverInjectedFields, discoverInjectedFieldsFromClass } from './metadata/discovery';
|
|
6
|
+
export { createTestApp } from './harness/createTestApp';
|
|
7
|
+
export { createWebTest } from './harness/createWebTest';
|
|
8
|
+
export { createCapturingLogger, silentLogger } from './harness/silentLogger';
|
|
9
|
+
export { TestHttpCall } from './http/TestHttpCall';
|
|
10
|
+
export { TestHttpResponse } from './http/TestHttpResponse';
|
|
3
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAA0B,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAyB,MAAM,wBAAwB,CAAC;AAEpG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -16,6 +16,27 @@ import type { FieldMetadata } from '../types';
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export declare function discoverInjectedFields(instance: any): FieldMetadata[];
|
|
19
|
+
/**
|
|
20
|
+
* Discovers all fields with @Inject decorator in a component class
|
|
21
|
+
* Traverses the constructor chain to support inheritance - no instantiation required
|
|
22
|
+
*
|
|
23
|
+
* This is the class-based counterpart of {@link discoverInjectedFields}. It is what
|
|
24
|
+
* slice tests use, since they must inspect a component's dependencies before deciding
|
|
25
|
+
* whether to register it for real or replace it with a mock.
|
|
26
|
+
*
|
|
27
|
+
* @param ComponentClass - Component class to inspect
|
|
28
|
+
* @returns Array of field metadata containing field names, service names, class refs and expressions
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const fields = discoverInjectedFieldsFromClass(AuthService);
|
|
33
|
+
* // Returns: [
|
|
34
|
+
* // { fieldName: 'userService', serviceName: 'UserService', serviceClass: UserService },
|
|
35
|
+
* // { fieldName: 'chat', serviceName: '__Ulak__', expression: (u) => u.namespace('/chat') }
|
|
36
|
+
* // ]
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function discoverInjectedFieldsFromClass(ComponentClass: any): FieldMetadata[];
|
|
19
40
|
/**
|
|
20
41
|
* Checks if a component has any injected dependencies
|
|
21
42
|
*
|
|
@@ -17,16 +17,44 @@ import { getTypedMetadata, getOwnTypedMetadata } from '../../utils';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export function discoverInjectedFields(instance) {
|
|
20
|
+
return discoverInjectedFieldsFromClass(Object.getPrototypeOf(instance)?.constructor);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Discovers all fields with @Inject decorator in a component class
|
|
24
|
+
* Traverses the constructor chain to support inheritance - no instantiation required
|
|
25
|
+
*
|
|
26
|
+
* This is the class-based counterpart of {@link discoverInjectedFields}. It is what
|
|
27
|
+
* slice tests use, since they must inspect a component's dependencies before deciding
|
|
28
|
+
* whether to register it for real or replace it with a mock.
|
|
29
|
+
*
|
|
30
|
+
* @param ComponentClass - Component class to inspect
|
|
31
|
+
* @returns Array of field metadata containing field names, service names, class refs and expressions
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const fields = discoverInjectedFieldsFromClass(AuthService);
|
|
36
|
+
* // Returns: [
|
|
37
|
+
* // { fieldName: 'userService', serviceName: 'UserService', serviceClass: UserService },
|
|
38
|
+
* // { fieldName: 'chat', serviceName: '__Ulak__', expression: (u) => u.namespace('/chat') }
|
|
39
|
+
* // ]
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function discoverInjectedFieldsFromClass(ComponentClass) {
|
|
20
43
|
const fields = [];
|
|
21
44
|
const processedFields = new Set();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
while (
|
|
25
|
-
|
|
45
|
+
let currentClass = ComponentClass;
|
|
46
|
+
// Traverse the constructor chain (similar to how Container does it)
|
|
47
|
+
while (currentClass && currentClass !== Function.prototype) {
|
|
48
|
+
// Stop at native code - matches Container.getPrototypeChain behaviour
|
|
49
|
+
if (typeof currentClass !== 'function' || currentClass.toString().includes('[native code]')) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
26
52
|
// Get dependencies metadata for this level
|
|
27
|
-
const dependencies = getOwnTypedMetadata(ComponentConstants.DependencyKey,
|
|
53
|
+
const dependencies = getOwnTypedMetadata(ComponentConstants.DependencyKey, currentClass);
|
|
54
|
+
// Get injected class references for this level (class-based injections only)
|
|
55
|
+
const dependencyClasses = getOwnTypedMetadata(ComponentConstants.DependencyClassKey, currentClass);
|
|
28
56
|
// Get expressions metadata for this level
|
|
29
|
-
const expressions = getOwnTypedMetadata(ComponentConstants.ExpressionKey,
|
|
57
|
+
const expressions = getOwnTypedMetadata(ComponentConstants.ExpressionKey, currentClass);
|
|
30
58
|
if (dependencies) {
|
|
31
59
|
// Process each dependency field
|
|
32
60
|
for (const [fieldName, serviceName] of Object.entries(dependencies)) {
|
|
@@ -38,12 +66,13 @@ export function discoverInjectedFields(instance) {
|
|
|
38
66
|
fields.push({
|
|
39
67
|
fieldName,
|
|
40
68
|
serviceName,
|
|
69
|
+
serviceClass: dependencyClasses?.[fieldName],
|
|
41
70
|
expression: expressions?.[fieldName],
|
|
42
71
|
});
|
|
43
72
|
}
|
|
44
73
|
}
|
|
45
|
-
// Move up the
|
|
46
|
-
|
|
74
|
+
// Move up the constructor chain
|
|
75
|
+
currentClass = Object.getPrototypeOf(currentClass);
|
|
47
76
|
}
|
|
48
77
|
return fields;
|
|
49
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../lib/test/metadata/discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAa;IAClD,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../lib/test/metadata/discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAa;IAClD,OAAO,+BAA+B,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC;AACvF,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,+BAA+B,CAAC,cAAmB;IACjE,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,IAAI,YAAY,GAAG,cAAc,CAAC;IAElC,oEAAoE;IACpE,OAAO,YAAY,IAAI,YAAY,KAAK,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC3D,sEAAsE;QACtE,IAAI,OAAO,YAAY,KAAK,UAAU,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;YAC5F,MAAM;QACR,CAAC;QAED,2CAA2C;QAC3C,MAAM,YAAY,GAAG,mBAAmB,CAAe,kBAAkB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAEvG,6EAA6E;QAC7E,MAAM,iBAAiB,GAAG,mBAAmB,CAC3C,kBAAkB,CAAC,kBAAkB,EACrC,YAAY,CACb,CAAC;QAEF,0CAA0C;QAC1C,MAAM,WAAW,GAAG,mBAAmB,CAAc,kBAAkB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAErG,IAAI,YAAY,EAAE,CAAC;YACjB,gCAAgC;YAChC,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;gBACpE,oDAAoD;gBACpD,IAAI,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBACnC,SAAS;gBACX,CAAC;gBAED,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAE/B,MAAM,CAAC,IAAI,CAAC;oBACV,SAAS;oBACT,WAAW;oBACX,YAAY,EAAE,iBAAiB,EAAE,CAAC,SAAS,CAAC;oBAC5C,UAAU,EAAE,WAAW,EAAE,CAAC,SAAS,CAAC;iBACrC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,gCAAgC;QAChC,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,cAA2C;IAC3E,MAAM,YAAY,GAAG,gBAAgB,CAAe,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACtG,OAAO,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,cAA2C,EAC3C,SAAiB;IAEjB,MAAM,YAAY,GAAG,gBAAgB,CAAe,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACtG,OAAO,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,cAA2C,EAC3C,SAAiB;IAEjB,MAAM,WAAW,GAAG,gBAAgB,CAAc,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACpG,OAAO,WAAW,EAAE,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -5,6 +5,13 @@ import type { MockComponentOptions, MockedComponent } from './types';
|
|
|
5
5
|
* This is the main API for testing Asena components. It discovers all @Inject decorated
|
|
6
6
|
* fields, generates mock objects for them, and injects them into a new instance.
|
|
7
7
|
*
|
|
8
|
+
* What each field receives depends on how it was injected:
|
|
9
|
+
* - `@Inject(UserService)` - a mock shaped like the class: every method becomes a bun mock
|
|
10
|
+
* - `@Inject(ulak('/chat'))` and other expression injections - the expression is evaluated
|
|
11
|
+
* against a deep mock, so any call chain works and every call stays assertable
|
|
12
|
+
* - `@Inject('UserService')` - a plain `{}`, because a string carries no class reference.
|
|
13
|
+
* Pass `overrides` for those fields.
|
|
14
|
+
*
|
|
8
15
|
* @template T - Type of the component being mocked
|
|
9
16
|
* @param ComponentClass - Component class to instantiate and mock
|
|
10
17
|
* @param options - Optional configuration for mocking behavior
|
|
@@ -52,6 +59,24 @@ import type { MockComponentOptions, MockedComponent } from './types';
|
|
|
52
59
|
* }
|
|
53
60
|
* });
|
|
54
61
|
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Expression-based injections (e.g. the ulak() helper) work automatically:
|
|
66
|
+
* // the expression is evaluated against a deep mock, so the field behaves
|
|
67
|
+
* // like a real namespace and every method call is assertable
|
|
68
|
+
* const { instance, mocks } = mockComponent(ChatService);
|
|
69
|
+
*
|
|
70
|
+
* await instance.sendAnnouncement('hello');
|
|
71
|
+
*
|
|
72
|
+
* expect(mocks.chat.broadcast).toHaveBeenCalledWith({ type: 'announcement', text: 'hello' });
|
|
73
|
+
*
|
|
74
|
+
* // A field present in overrides is used as-is - its expression is skipped
|
|
75
|
+
* const stub = createTestUlakStub('/chat');
|
|
76
|
+
* const { instance: withStub } = mockComponent(ChatService, {
|
|
77
|
+
* overrides: { chat: stub },
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
55
80
|
*/
|
|
56
81
|
export declare function mockComponent<T extends object>(ComponentClass: new (...args: any[]) => T, options?: MockComponentOptions): MockedComponent<T>;
|
|
57
82
|
/**
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { discoverInjectedFields } from './metadata/discovery';
|
|
2
2
|
import { createMockFromClass } from './factory/mockFactory';
|
|
3
|
+
import { createDeepMock } from './factory/deepMock';
|
|
3
4
|
/**
|
|
4
5
|
* Creates a test instance of a component with all @Inject dependencies automatically mocked
|
|
5
6
|
*
|
|
6
7
|
* This is the main API for testing Asena components. It discovers all @Inject decorated
|
|
7
8
|
* fields, generates mock objects for them, and injects them into a new instance.
|
|
8
9
|
*
|
|
10
|
+
* What each field receives depends on how it was injected:
|
|
11
|
+
* - `@Inject(UserService)` - a mock shaped like the class: every method becomes a bun mock
|
|
12
|
+
* - `@Inject(ulak('/chat'))` and other expression injections - the expression is evaluated
|
|
13
|
+
* against a deep mock, so any call chain works and every call stays assertable
|
|
14
|
+
* - `@Inject('UserService')` - a plain `{}`, because a string carries no class reference.
|
|
15
|
+
* Pass `overrides` for those fields.
|
|
16
|
+
*
|
|
9
17
|
* @template T - Type of the component being mocked
|
|
10
18
|
* @param ComponentClass - Component class to instantiate and mock
|
|
11
19
|
* @param options - Optional configuration for mocking behavior
|
|
@@ -53,6 +61,24 @@ import { createMockFromClass } from './factory/mockFactory';
|
|
|
53
61
|
* }
|
|
54
62
|
* });
|
|
55
63
|
* ```
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Expression-based injections (e.g. the ulak() helper) work automatically:
|
|
68
|
+
* // the expression is evaluated against a deep mock, so the field behaves
|
|
69
|
+
* // like a real namespace and every method call is assertable
|
|
70
|
+
* const { instance, mocks } = mockComponent(ChatService);
|
|
71
|
+
*
|
|
72
|
+
* await instance.sendAnnouncement('hello');
|
|
73
|
+
*
|
|
74
|
+
* expect(mocks.chat.broadcast).toHaveBeenCalledWith({ type: 'announcement', text: 'hello' });
|
|
75
|
+
*
|
|
76
|
+
* // A field present in overrides is used as-is - its expression is skipped
|
|
77
|
+
* const stub = createTestUlakStub('/chat');
|
|
78
|
+
* const { instance: withStub } = mockComponent(ChatService, {
|
|
79
|
+
* overrides: { chat: stub },
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
56
82
|
*/
|
|
57
83
|
export function mockComponent(ComponentClass, options = {}) {
|
|
58
84
|
if (!ComponentClass || typeof ComponentClass !== 'function') {
|
|
@@ -65,16 +91,23 @@ export function mockComponent(ComponentClass, options = {}) {
|
|
|
65
91
|
: allInjectedFields;
|
|
66
92
|
const mocks = {};
|
|
67
93
|
for (const field of fieldsToMock) {
|
|
68
|
-
const { fieldName, expression } = field;
|
|
94
|
+
const { fieldName, expression, serviceClass } = field;
|
|
69
95
|
let mockValue;
|
|
70
|
-
if (options.overrides
|
|
96
|
+
if (options.overrides && Object.hasOwn(options.overrides, fieldName)) {
|
|
97
|
+
// Overrides are the FINAL field value: the expression is intentionally
|
|
98
|
+
// skipped and falsy values (0, '', null) are respected
|
|
71
99
|
mockValue = options.overrides[fieldName];
|
|
72
100
|
}
|
|
73
|
-
else {
|
|
74
|
-
|
|
101
|
+
else if (expression) {
|
|
102
|
+
// Expression fields (e.g. @Inject(ulak('/chat'))) receive a deep mock
|
|
103
|
+
// so any call chain the expression performs works without a running app
|
|
104
|
+
mockValue = expression(createDeepMock());
|
|
75
105
|
}
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
else {
|
|
107
|
+
// Class injections (@Inject(UserService)) get a mock shaped like the real class:
|
|
108
|
+
// every method becomes a bun mock. String injections (@Inject('UserService'))
|
|
109
|
+
// carry no class reference, so they degrade to {} and need an explicit override
|
|
110
|
+
mockValue = createMockFromClass(serviceClass);
|
|
78
111
|
}
|
|
79
112
|
instance[fieldName] = mockValue;
|
|
80
113
|
mocks[fieldName] = mockValue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mockComponent.js","sourceRoot":"","sources":["../../../lib/test/mockComponent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"mockComponent.js","sourceRoot":"","sources":["../../../lib/test/mockComponent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,MAAM,UAAU,aAAa,CAC3B,cAAyC,EACzC,UAAgC,EAAE;IAElC,IAAI,CAAC,cAAc,IAAI,OAAO,cAAc,KAAK,UAAU,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,4DAA4D,OAAO,cAAc,EAAE,CAAC,CAAC;IACvG,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IAE3D,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU;QACrC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnF,CAAC,CAAC,iBAAiB,CAAC;IAEtB,MAAM,KAAK,GAAwB,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;QAEtD,IAAI,SAAc,CAAC;QAEnB,IAAI,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;YACrE,uEAAuE;YACvE,uDAAuD;YACvD,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,UAAU,EAAE,CAAC;YACtB,sEAAsE;YACtE,wEAAwE;YACxE,SAAS,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,iFAAiF;YACjF,8EAA8E;YAC9E,gFAAgF;YAChF,SAAS,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;QAChD,CAAC;QAEA,QAAgB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;QACzC,KAAK,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE/C,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAQ,CAAC;QACzD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,cAAyC,EACzC,UAAgC,EAAE;IAElC,MAAM,MAAM,GAAG,aAAa,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IAEtD,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/lib/test/types.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface MockComponentOptions {
|
|
|
17
17
|
/**
|
|
18
18
|
* Provide custom mocks instead of auto-generated ones (optional)
|
|
19
19
|
*
|
|
20
|
+
* An override is the FINAL value injected into the field. For expression-based
|
|
21
|
+
* injections (e.g. @Inject(ulak('/chat'))) the expression is skipped entirely
|
|
22
|
+
* when the field is overridden. Presence is checked with Object.hasOwn, so
|
|
23
|
+
* falsy values (0, '', null, undefined) are injected as-is rather than ignored.
|
|
24
|
+
*
|
|
20
25
|
* @example
|
|
21
26
|
* ```typescript
|
|
22
27
|
* mockComponent(AuthService, {
|
|
@@ -75,6 +80,14 @@ export interface FieldMetadata {
|
|
|
75
80
|
* Service name (from @Inject decorator)
|
|
76
81
|
*/
|
|
77
82
|
serviceName: string;
|
|
83
|
+
/**
|
|
84
|
+
* Class reference the field was injected with.
|
|
85
|
+
*
|
|
86
|
+
* Only present for class-based injections (`@Inject(UserService)`). String-based
|
|
87
|
+
* injections (`@Inject('UserService')`) carry no class reference, so this is
|
|
88
|
+
* undefined and auto-mocking degrades to an empty object.
|
|
89
|
+
*/
|
|
90
|
+
serviceClass?: any;
|
|
78
91
|
/**
|
|
79
92
|
* Optional expression function for transformation
|
|
80
93
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asenajs/asena",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"author": "LibirSoft",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
"reflect-metadata": "^0.2.2"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@changesets/cli": "^2.
|
|
15
|
+
"@changesets/cli": "^2.31.1",
|
|
16
16
|
"@types/bun": "latest",
|
|
17
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
18
|
-
"@typescript-eslint/parser": "^8.
|
|
19
|
-
"eslint": "^9.39.
|
|
17
|
+
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
18
|
+
"@typescript-eslint/parser": "^8.65.0",
|
|
19
|
+
"eslint": "^9.39.5",
|
|
20
20
|
"eslint-config-prettier": "^9.1.2",
|
|
21
|
-
"prettier": "^3.
|
|
21
|
+
"prettier": "^3.9.6",
|
|
22
22
|
"typescript": "^5.9.3",
|
|
23
|
-
"typescript-eslint": "^8.
|
|
23
|
+
"typescript-eslint": "^8.65.0"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
"import": "./dist/lib/server/messaging/index.js",
|
|
84
84
|
"types": "./dist/lib/server/messaging/index.d.ts"
|
|
85
85
|
},
|
|
86
|
+
"./microservice": {
|
|
87
|
+
"import": "./dist/lib/server/microservice/index.js",
|
|
88
|
+
"types": "./dist/lib/server/microservice/index.d.ts"
|
|
89
|
+
},
|
|
86
90
|
"./event": {
|
|
87
91
|
"import": "./dist/lib/server/event/index.js",
|
|
88
92
|
"types": "./dist/lib/server/event/index.d.ts"
|