@asenajs/asena 0.7.0 → 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.
Files changed (140) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/adapter/AsenaAdapter.d.ts +5 -3
  3. package/dist/lib/adapter/AsenaAdapter.js.map +1 -1
  4. package/dist/lib/adapter/AsenaContext.d.ts +6 -0
  5. package/dist/lib/adapter/types/ServeOptions.d.ts +16 -0
  6. package/dist/lib/ioc/Container.d.ts +25 -0
  7. package/dist/lib/ioc/Container.js +47 -0
  8. package/dist/lib/ioc/Container.js.map +1 -1
  9. package/dist/lib/ioc/CoreContainer.d.ts +5 -3
  10. package/dist/lib/ioc/CoreContainer.js +9 -3
  11. package/dist/lib/ioc/CoreContainer.js.map +1 -1
  12. package/dist/lib/ioc/IocEngine.js +4 -5
  13. package/dist/lib/ioc/IocEngine.js.map +1 -1
  14. package/dist/lib/ioc/component/decorators/Inject.js +16 -0
  15. package/dist/lib/ioc/component/decorators/Inject.js.map +1 -1
  16. package/dist/lib/ioc/constants/ComponentConstants.d.ts +4 -0
  17. package/dist/lib/ioc/constants/ComponentConstants.js +5 -0
  18. package/dist/lib/ioc/constants/ComponentConstants.js.map +1 -1
  19. package/dist/lib/ioc/types/ICoreServiceNamesType.d.ts +1 -0
  20. package/dist/lib/ioc/types/ICoreServiceNamesType.js +1 -0
  21. package/dist/lib/ioc/types/ICoreServiceNamesType.js.map +1 -1
  22. package/dist/lib/ioc/types/InjectableComponent.d.ts +10 -0
  23. package/dist/lib/ioc/types/decorators/ComponentType.d.ts +2 -1
  24. package/dist/lib/ioc/types/decorators/ComponentType.js +1 -0
  25. package/dist/lib/ioc/types/decorators/ComponentType.js.map +1 -1
  26. package/dist/lib/server/AsenaServer.d.ts +46 -3
  27. package/dist/lib/server/AsenaServer.js +159 -29
  28. package/dist/lib/server/AsenaServer.js.map +1 -1
  29. package/dist/lib/server/AsenaServerFactory.d.ts +38 -3
  30. package/dist/lib/server/AsenaServerFactory.js +21 -2
  31. package/dist/lib/server/AsenaServerFactory.js.map +1 -1
  32. package/dist/lib/server/config/AsenaConfig.d.ts +82 -4
  33. package/dist/lib/server/config/AsenaConfig.js +39 -1
  34. package/dist/lib/server/config/AsenaConfig.js.map +1 -1
  35. package/dist/lib/server/decorators/components/FrontendController.js.map +1 -1
  36. package/dist/lib/server/decorators/components/MessageController.d.ts +49 -0
  37. package/dist/lib/server/decorators/components/MessageController.js +62 -0
  38. package/dist/lib/server/decorators/components/MessageController.js.map +1 -0
  39. package/dist/lib/server/decorators/components/index.d.ts +1 -0
  40. package/dist/lib/server/decorators/components/index.js +1 -0
  41. package/dist/lib/server/decorators/components/index.js.map +1 -1
  42. package/dist/lib/server/event/EventDispatchService.d.ts +3 -7
  43. package/dist/lib/server/event/EventDispatchService.js +9 -40
  44. package/dist/lib/server/event/EventDispatchService.js.map +1 -1
  45. package/dist/lib/server/event/PatternHandlerIndex.d.ts +53 -0
  46. package/dist/lib/server/event/PatternHandlerIndex.js +100 -0
  47. package/dist/lib/server/event/PatternHandlerIndex.js.map +1 -0
  48. package/dist/lib/server/event/decorators/On.d.ts +4 -0
  49. package/dist/lib/server/event/decorators/On.js +6 -0
  50. package/dist/lib/server/event/decorators/On.js.map +1 -1
  51. package/dist/lib/server/event/index.d.ts +1 -0
  52. package/dist/lib/server/event/index.js +2 -0
  53. package/dist/lib/server/event/index.js.map +1 -1
  54. package/dist/lib/server/event/types.d.ts +12 -0
  55. package/dist/lib/server/health/HealthServer.d.ts +26 -0
  56. package/dist/lib/server/health/HealthServer.js +63 -0
  57. package/dist/lib/server/health/HealthServer.js.map +1 -0
  58. package/dist/lib/server/health/index.d.ts +1 -0
  59. package/dist/lib/server/health/index.js +2 -0
  60. package/dist/lib/server/health/index.js.map +1 -0
  61. package/dist/lib/server/messaging/Ulak.d.ts +156 -0
  62. package/dist/lib/server/messaging/Ulak.js +174 -0
  63. package/dist/lib/server/messaging/Ulak.js.map +1 -1
  64. package/dist/lib/server/messaging/types.d.ts +5 -1
  65. package/dist/lib/server/messaging/types.js +5 -0
  66. package/dist/lib/server/messaging/types.js.map +1 -1
  67. package/dist/lib/server/microservice/InMemoryTransport.d.ts +45 -0
  68. package/dist/lib/server/microservice/InMemoryTransport.js +124 -0
  69. package/dist/lib/server/microservice/InMemoryTransport.js.map +1 -0
  70. package/dist/lib/server/microservice/MicroserviceTransport.d.ts +132 -0
  71. package/dist/lib/server/microservice/MicroserviceTransport.js +2 -0
  72. package/dist/lib/server/microservice/MicroserviceTransport.js.map +1 -0
  73. package/dist/lib/server/microservice/decorators/EventPattern.d.ts +38 -0
  74. package/dist/lib/server/microservice/decorators/EventPattern.js +59 -0
  75. package/dist/lib/server/microservice/decorators/EventPattern.js.map +1 -0
  76. package/dist/lib/server/microservice/decorators/MessagePattern.d.ts +31 -0
  77. package/dist/lib/server/microservice/decorators/MessagePattern.js +55 -0
  78. package/dist/lib/server/microservice/decorators/MessagePattern.js.map +1 -0
  79. package/dist/lib/server/microservice/decorators/index.d.ts +2 -0
  80. package/dist/lib/server/microservice/decorators/index.js +3 -0
  81. package/dist/lib/server/microservice/decorators/index.js.map +1 -0
  82. package/dist/lib/server/microservice/index.d.ts +5 -0
  83. package/dist/lib/server/microservice/index.js +11 -0
  84. package/dist/lib/server/microservice/index.js.map +1 -0
  85. package/dist/lib/server/microservice/interceptorChain.d.ts +20 -0
  86. package/dist/lib/server/microservice/interceptorChain.js +39 -0
  87. package/dist/lib/server/microservice/interceptorChain.js.map +1 -0
  88. package/dist/lib/server/microservice/types.d.ts +142 -0
  89. package/dist/lib/server/microservice/types.js +5 -0
  90. package/dist/lib/server/microservice/types.js.map +1 -0
  91. package/dist/lib/server/src/services/PrepareEventService.d.ts +4 -1
  92. package/dist/lib/server/src/services/PrepareEventService.js +10 -3
  93. package/dist/lib/server/src/services/PrepareEventService.js.map +1 -1
  94. package/dist/lib/server/src/services/PrepareFrontendControllerService.d.ts +1 -0
  95. package/dist/lib/server/src/services/PrepareFrontendControllerService.js +1 -1
  96. package/dist/lib/server/src/services/PrepareFrontendControllerService.js.map +1 -1
  97. package/dist/lib/server/src/services/PrepareMicroserviceService.d.ts +66 -0
  98. package/dist/lib/server/src/services/PrepareMicroserviceService.js +193 -0
  99. package/dist/lib/server/src/services/PrepareMicroserviceService.js.map +1 -0
  100. package/dist/lib/server/src/services/PrepareScheduleService.js.map +1 -1
  101. package/dist/lib/server/web/decorators/methods/Connect.js +9 -1
  102. package/dist/lib/server/web/decorators/methods/Connect.js.map +1 -1
  103. package/dist/lib/server/web/decorators/methods/Options.js +9 -1
  104. package/dist/lib/server/web/decorators/methods/Options.js.map +1 -1
  105. package/dist/lib/server/web/websocket/AsenaSocket.js.map +1 -1
  106. package/dist/lib/test/factory/deepMock.d.ts +25 -0
  107. package/dist/lib/test/factory/deepMock.js +105 -0
  108. package/dist/lib/test/factory/deepMock.js.map +1 -0
  109. package/dist/lib/test/factory/ulakStub.d.ts +44 -0
  110. package/dist/lib/test/factory/ulakStub.js +40 -0
  111. package/dist/lib/test/factory/ulakStub.js.map +1 -0
  112. package/dist/lib/test/harness/createTestApp.d.ts +33 -0
  113. package/dist/lib/test/harness/createTestApp.js +105 -0
  114. package/dist/lib/test/harness/createTestApp.js.map +1 -0
  115. package/dist/lib/test/harness/createWebTest.d.ts +28 -0
  116. package/dist/lib/test/harness/createWebTest.js +127 -0
  117. package/dist/lib/test/harness/createWebTest.js.map +1 -0
  118. package/dist/lib/test/harness/silentLogger.d.ts +27 -0
  119. package/dist/lib/test/harness/silentLogger.js +54 -0
  120. package/dist/lib/test/harness/silentLogger.js.map +1 -0
  121. package/dist/lib/test/harness/types.d.ts +147 -0
  122. package/dist/lib/test/harness/types.js +2 -0
  123. package/dist/lib/test/harness/types.js.map +1 -0
  124. package/dist/lib/test/http/TestHttpCall.d.ts +78 -0
  125. package/dist/lib/test/http/TestHttpCall.js +149 -0
  126. package/dist/lib/test/http/TestHttpCall.js.map +1 -0
  127. package/dist/lib/test/http/TestHttpResponse.d.ts +46 -0
  128. package/dist/lib/test/http/TestHttpResponse.js +70 -0
  129. package/dist/lib/test/http/TestHttpResponse.js.map +1 -0
  130. package/dist/lib/test/index.d.ts +9 -0
  131. package/dist/lib/test/index.js +8 -0
  132. package/dist/lib/test/index.js.map +1 -1
  133. package/dist/lib/test/metadata/discovery.d.ts +21 -0
  134. package/dist/lib/test/metadata/discovery.js +37 -8
  135. package/dist/lib/test/metadata/discovery.js.map +1 -1
  136. package/dist/lib/test/mockComponent.d.ts +25 -0
  137. package/dist/lib/test/mockComponent.js +39 -6
  138. package/dist/lib/test/mockComponent.js.map +1 -1
  139. package/dist/lib/test/types.d.ts +13 -0
  140. package/package.json +11 -7
@@ -0,0 +1,105 @@
1
+ import { mock } from 'bun:test';
2
+ /**
3
+ * Keys that must NOT resolve to a mock function on a deep mock node.
4
+ *
5
+ * `then`/`catch`/`finally`: if property access returned a function, the node
6
+ * would be treated as a thenable and `await deepMock` would never resolve.
7
+ * `toJSON`: keeps JSON.stringify from invoking an auto-generated mock.
8
+ */
9
+ const IGNORED_KEYS = new Set(['then', 'catch', 'finally', 'toJSON']);
10
+ /**
11
+ * Creates a leaf mock: a REAL Bun mock function (no Proxy wrapper)
12
+ *
13
+ * Leaves must stay raw because Bun's expect() matchers and mock configuration
14
+ * methods (mockReturnValue, mockResolvedValue...) brand-check their target -
15
+ * a Proxy-wrapped mock fails "Expected value must be a mock function". The
16
+ * default implementation returns a memoized deep node so call chains continue.
17
+ *
18
+ * @param path - Access path of this mock, used as the function name for debugging
19
+ * @returns Bun mock function
20
+ *
21
+ * @internal
22
+ */
23
+ function createDeepMockLeaf(path) {
24
+ let callResult;
25
+ const leaf = mock(() => {
26
+ callResult ??= createDeepMockNode(`${path}()`);
27
+ return callResult;
28
+ });
29
+ Object.defineProperty(leaf, 'name', { value: path, configurable: true });
30
+ return leaf;
31
+ }
32
+ /**
33
+ * Creates a deep mock node
34
+ *
35
+ * A node is a Proxy whose target is a Bun mock function: the node is callable
36
+ * (returning a memoized child node) and any unknown property resolves to a
37
+ * memoized raw leaf mock.
38
+ *
39
+ * @param path - Access path of this node, used as the function name for debugging
40
+ * @returns Proxy-wrapped mock function
41
+ *
42
+ * @internal
43
+ */
44
+ function createDeepMockNode(path) {
45
+ let callResult;
46
+ const fn = mock(() => {
47
+ callResult ??= createDeepMockNode(`${path}()`);
48
+ return callResult;
49
+ });
50
+ Object.defineProperty(fn, 'name', { value: path, configurable: true });
51
+ const children = new Map();
52
+ return new Proxy(fn, {
53
+ get(target, key) {
54
+ // Pass through everything that exists on the mock function itself
55
+ // (mock state, Function.prototype members). Methods are bound to the
56
+ // raw target so native brand checks keep working through the Proxy.
57
+ if (Reflect.has(target, key)) {
58
+ const value = Reflect.get(target, key, target);
59
+ return typeof value === 'function' ? value.bind(target) : value;
60
+ }
61
+ if (IGNORED_KEYS.has(key)) {
62
+ return undefined;
63
+ }
64
+ // Well-known symbols (Symbol.iterator, Symbol.toPrimitive, inspect
65
+ // symbols...) fall back to engine defaults instead of becoming mocks
66
+ if (typeof key === 'symbol') {
67
+ return undefined;
68
+ }
69
+ // Memoized per key so repeated access returns the SAME mock function
70
+ // and call counts accumulate across accesses
71
+ if (!children.has(key)) {
72
+ children.set(key, createDeepMockLeaf(`${path}.${String(key)}`));
73
+ }
74
+ return children.get(key);
75
+ },
76
+ });
77
+ }
78
+ /**
79
+ * Creates a deep mock object backed by native Proxy
80
+ *
81
+ * Any property access returns a memoized Bun mock function, and calling one
82
+ * returns another deep mock node - so arbitrary call chains work without any
83
+ * upfront shape definition. Used by mockComponent to evaluate @Inject expression
84
+ * transformations (e.g. the ulak() helper) without a running application.
85
+ *
86
+ * @returns Deep mock root node
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const deepMock = createDeepMock();
91
+ *
92
+ * // Any chain works out of the box
93
+ * const channel = deepMock.namespace('/chat');
94
+ * await channel.broadcast({ text: 'hello' });
95
+ *
96
+ * expect(channel.broadcast).toHaveBeenCalledWith({ text: 'hello' });
97
+ *
98
+ * // Mocks are configurable like any Bun mock
99
+ * channel.getSocketCount.mockReturnValue(5);
100
+ * ```
101
+ */
102
+ export function createDeepMock() {
103
+ return createDeepMockNode('deepMock');
104
+ }
105
+ //# sourceMappingURL=deepMock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deepMock.js","sourceRoot":"","sources":["../../../../lib/test/factory/deepMock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,CAAc,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAElF;;;;;;;;;;;;GAYG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,UAAe,CAAC;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE;QACrB,UAAU,KAAK,kBAAkB,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAE/C,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,UAAe,CAAC;IAEpB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;QACnB,UAAU,KAAK,kBAAkB,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAE/C,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE7C,OAAO,IAAI,KAAK,CAAC,EAAE,EAAE;QACnB,GAAG,CAAC,MAAM,EAAE,GAAG;YACb,kEAAkE;YAClE,qEAAqE;YACrE,oEAAoE;YACpE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;gBAE/C,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAClE,CAAC;YAED,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1B,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,mEAAmE;YACnE,qEAAqE;YACrE,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,qEAAqE;YACrE,6CAA6C;YAC7C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { Mock } from 'bun:test';
2
+ import type { Ulak } from '../../server/messaging';
3
+ /**
4
+ * A Ulak.NameSpace stub whose methods are all Bun mock functions
5
+ *
6
+ * Extends Ulak.NameSpace so any method added to the interface in the future
7
+ * breaks this file's compilation instead of silently drifting out of sync.
8
+ */
9
+ export interface TestUlakNameSpace<T extends string = string> extends Ulak.NameSpace<T> {
10
+ broadcast: Mock<(data: any) => Promise<void>>;
11
+ to: Mock<(room: string, data: any) => Promise<void>>;
12
+ toSocket: Mock<(socketId: string, data: any) => Promise<void>>;
13
+ toMany: Mock<(rooms: string[], data: any) => Promise<void>>;
14
+ getSocketCount: Mock<() => number>;
15
+ }
16
+ /**
17
+ * Creates a typed Ulak namespace stub for tests
18
+ *
19
+ * Use it as an explicit override for @Inject(ulak(...)) fields when you want
20
+ * a fully typed mock instead of the automatic deep mock generated by
21
+ * mockComponent.
22
+ *
23
+ * @param path - The namespace path the stub reports (defaults to '/')
24
+ * @returns Ulak.NameSpace implementation with all methods mocked
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { mockComponent, createTestUlakStub } from '@asenajs/asena/test';
29
+ *
30
+ * const statsChannel = createTestUlakStub('/ws/public/stats');
31
+ *
32
+ * const { instance } = mockComponent(UserService, {
33
+ * overrides: { statsChannel },
34
+ * });
35
+ *
36
+ * await instance.createAnonUser('John');
37
+ *
38
+ * expect(statsChannel.broadcast).toHaveBeenCalledWith({
39
+ * action: 'update',
40
+ * data: { newUser: 1 },
41
+ * });
42
+ * ```
43
+ */
44
+ export declare function createTestUlakStub<T extends string = string>(path?: T): TestUlakNameSpace<T>;
@@ -0,0 +1,40 @@
1
+ import { mock } from 'bun:test';
2
+ /**
3
+ * Creates a typed Ulak namespace stub for tests
4
+ *
5
+ * Use it as an explicit override for @Inject(ulak(...)) fields when you want
6
+ * a fully typed mock instead of the automatic deep mock generated by
7
+ * mockComponent.
8
+ *
9
+ * @param path - The namespace path the stub reports (defaults to '/')
10
+ * @returns Ulak.NameSpace implementation with all methods mocked
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { mockComponent, createTestUlakStub } from '@asenajs/asena/test';
15
+ *
16
+ * const statsChannel = createTestUlakStub('/ws/public/stats');
17
+ *
18
+ * const { instance } = mockComponent(UserService, {
19
+ * overrides: { statsChannel },
20
+ * });
21
+ *
22
+ * await instance.createAnonUser('John');
23
+ *
24
+ * expect(statsChannel.broadcast).toHaveBeenCalledWith({
25
+ * action: 'update',
26
+ * data: { newUser: 1 },
27
+ * });
28
+ * ```
29
+ */
30
+ export function createTestUlakStub(path = '/') {
31
+ return {
32
+ path,
33
+ broadcast: mock(async () => undefined),
34
+ to: mock(async () => undefined),
35
+ toSocket: mock(async () => undefined),
36
+ toMany: mock(async () => undefined),
37
+ getSocketCount: mock(() => 0),
38
+ };
39
+ }
40
+ //# sourceMappingURL=ulakStub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ulakStub.js","sourceRoot":"","sources":["../../../../lib/test/factory/ulakStub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAkBhC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,kBAAkB,CAA4B,OAAU,GAAQ;IAC9E,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACtC,EAAE,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QAC/B,QAAQ,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACrC,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;QACnC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { AsenaAdapter } from '../../adapter';
2
+ import type { TestApp, TestAppOptions } from './types';
3
+ /**
4
+ * Boots a full Asena application for testing - the equivalent of Spring's `@SpringBootTest`.
5
+ *
6
+ * Everything runs for real: the IoC container, all bootstrap phases, the adapter and its
7
+ * routing pipeline. Only the components you name are registered, and any of them can be
8
+ * swapped for a double through `overrides`.
9
+ *
10
+ * @param options - Adapter, components and optional overrides
11
+ * @returns A started app with a fluent HTTP client attached
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const [adapter] = createHonoAdapter({ logger: silentLogger });
16
+ * const app = await createTestApp({
17
+ * adapter,
18
+ * components: [UserController, UserService],
19
+ * overrides: { UserService: { findById: mock(async () => ({ id: '1', name: 'Ada' })) } },
20
+ * });
21
+ *
22
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
23
+ *
24
+ * await app.stop();
25
+ * ```
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Automatic cleanup, and no TCP port taken at all
30
+ * await using app = await createTestApp({ adapter, components, dispatch: 'socket' });
31
+ * ```
32
+ */
33
+ export declare function createTestApp<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>>(options: TestAppOptions<A>): Promise<TestApp>;
@@ -0,0 +1,105 @@
1
+ import { unlink } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { AsenaServerFactory } from '../../server/AsenaServerFactory';
5
+ import { TestHttpCall } from '../http/TestHttpCall';
6
+ import { silentLogger } from './silentLogger';
7
+ // Monotonic, not random: workflow scripts and test runners may run many apps per process
8
+ // and a collision would surface as a confusing EADDRINUSE on a socket file
9
+ let socketCounter = 0;
10
+ function nextSocketPath() {
11
+ socketCounter += 1;
12
+ return join(tmpdir(), `asena-test-${process.pid}-${socketCounter}.sock`);
13
+ }
14
+ /**
15
+ * Boots a full Asena application for testing - the equivalent of Spring's `@SpringBootTest`.
16
+ *
17
+ * Everything runs for real: the IoC container, all bootstrap phases, the adapter and its
18
+ * routing pipeline. Only the components you name are registered, and any of them can be
19
+ * swapped for a double through `overrides`.
20
+ *
21
+ * @param options - Adapter, components and optional overrides
22
+ * @returns A started app with a fluent HTTP client attached
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const [adapter] = createHonoAdapter({ logger: silentLogger });
27
+ * const app = await createTestApp({
28
+ * adapter,
29
+ * components: [UserController, UserService],
30
+ * overrides: { UserService: { findById: mock(async () => ({ id: '1', name: 'Ada' })) } },
31
+ * });
32
+ *
33
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
34
+ *
35
+ * await app.stop();
36
+ * ```
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * // Automatic cleanup, and no TCP port taken at all
41
+ * await using app = await createTestApp({ adapter, components, dispatch: 'socket' });
42
+ * ```
43
+ */
44
+ export async function createTestApp(options) {
45
+ const { adapter, components, overrides, logger = silentLogger, dispatch = 'server' } = options;
46
+ const socketPath = dispatch === 'socket' ? nextSocketPath() : undefined;
47
+ const server = await AsenaServerFactory.create({
48
+ adapter,
49
+ logger,
50
+ components,
51
+ overrides,
52
+ // Port 0 lets Bun assign a free ephemeral port, which removes the random-port race
53
+ port: options.port ?? 0,
54
+ });
55
+ await server.start(socketPath ? { unix: socketPath } : undefined);
56
+ const port = server.httpServer?.port ?? 0;
57
+ const baseUrl = socketPath ? 'http://localhost' : `http://localhost:${port}`;
58
+ const call = (path, init, method) => {
59
+ const requestInit = { ...init };
60
+ if (method) {
61
+ requestInit.method = method;
62
+ }
63
+ if (socketPath) {
64
+ // Bun routes the request over the socket while still parsing the URL for path/query
65
+ requestInit.unix = socketPath;
66
+ }
67
+ return new TestHttpCall(`${baseUrl}${path}`, requestInit);
68
+ };
69
+ let stopped = false;
70
+ const app = {
71
+ server,
72
+ container: server.coreContainer.container,
73
+ port,
74
+ baseUrl,
75
+ socketPath,
76
+ resolve: async (name) => (await server.coreContainer.container.resolve(name)),
77
+ wsUrl: (path) => (socketPath ? `ws+unix://${socketPath}:${path}` : `ws://localhost:${port}${path}`),
78
+ request: (path, init) => call(path, init),
79
+ get: (path, init) => call(path, init, 'GET'),
80
+ post: (path, init) => call(path, init, 'POST'),
81
+ put: (path, init) => call(path, init, 'PUT'),
82
+ patch: (path, init) => call(path, init, 'PATCH'),
83
+ delete: (path, init) => call(path, init, 'DELETE'),
84
+ head: (path, init) => call(path, init, 'HEAD'),
85
+ options: (path, init) => call(path, init, 'OPTIONS'),
86
+ stop: async () => {
87
+ if (stopped) {
88
+ return;
89
+ }
90
+ stopped = true;
91
+ await server.stop();
92
+ if (socketPath) {
93
+ // Bun removes the socket file on a clean stop; this only matters after a crash
94
+ await unlink(socketPath).catch(() => {
95
+ // Already gone - nothing to clean up
96
+ });
97
+ }
98
+ },
99
+ [Symbol.asyncDispose]: async () => {
100
+ await app.stop();
101
+ },
102
+ };
103
+ return app;
104
+ }
105
+ //# sourceMappingURL=createTestApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTestApp.js","sourceRoot":"","sources":["../../../../lib/test/harness/createTestApp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { AsenaAdapter } from '../../adapter';
2
+ import type { WebTestOptions, WebTestResult } from './types';
3
+ /**
4
+ * Boots only the web layer of an application - the equivalent of Spring's `@WebMvcTest`.
5
+ *
6
+ * The controllers, their middlewares and their validators run for real. Every other
7
+ * dependency they inject is replaced by an auto-generated mock shaped like the real class,
8
+ * so a controller test never drags in a database or an HTTP client.
9
+ *
10
+ * Core services (the container, the logger, `ulak(...)`, the event emitter, ...) are always
11
+ * real - they are wired during bootstrap, before user components exist.
12
+ *
13
+ * @param options - Adapter, controllers and optional explicit overrides
14
+ * @returns The started app plus every double in play, keyed by service name
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { app, mocks } = await createWebTest({ adapter, controllers: [UserController] });
19
+ *
20
+ * mocks.UserService.findById.mockResolvedValue({ id: '1', name: 'Ada' });
21
+ *
22
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
23
+ * expect(mocks.UserService.findById).toHaveBeenCalledWith('1');
24
+ *
25
+ * await app.stop();
26
+ * ```
27
+ */
28
+ export declare function createWebTest<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>>(options: WebTestOptions<A>): Promise<WebTestResult>;
@@ -0,0 +1,127 @@
1
+ import { ComponentConstants, ComponentType, ICoreServiceNames } from '../../ioc';
2
+ import { getOwnTypedMetadata, getTypedMetadata } from '../../utils';
3
+ import { createMockFromClass } from '../factory/mockFactory';
4
+ import { discoverInjectedFieldsFromClass } from '../metadata/discovery';
5
+ import { createTestApp } from './createTestApp';
6
+ import { silentLogger } from './silentLogger';
7
+ /**
8
+ * The registered container name for a component
9
+ *
10
+ * @internal
11
+ */
12
+ function componentName(Class) {
13
+ return getTypedMetadata(ComponentConstants.NameKey, Class) || Class.name;
14
+ }
15
+ /**
16
+ * Collects the classes the web layer cannot function without.
17
+ *
18
+ * Middlewares, validators and static-serve configs are resolved **by name from the
19
+ * container** at start-up (PrepareMiddlewareService / PrepareValidatorService) and throw
20
+ * when missing, so a slice test has to register them for real rather than mock them.
21
+ *
22
+ * @internal
23
+ */
24
+ function collectWebLayer(controllers) {
25
+ const webLayer = new Set(controllers);
26
+ for (const controller of controllers) {
27
+ for (const middleware of getTypedMetadata(ComponentConstants.MiddlewaresKey, controller) || []) {
28
+ webLayer.add(middleware);
29
+ }
30
+ const routes = getOwnTypedMetadata(ComponentConstants.RouteKey, controller) || {};
31
+ for (const params of Object.values(routes)) {
32
+ for (const middleware of params.middlewares || []) {
33
+ webLayer.add(middleware);
34
+ }
35
+ if (params.validator) {
36
+ webLayer.add(params.validator);
37
+ }
38
+ if (params.staticServe) {
39
+ webLayer.add(params.staticServe);
40
+ }
41
+ }
42
+ }
43
+ return webLayer;
44
+ }
45
+ /**
46
+ * Boots only the web layer of an application - the equivalent of Spring's `@WebMvcTest`.
47
+ *
48
+ * The controllers, their middlewares and their validators run for real. Every other
49
+ * dependency they inject is replaced by an auto-generated mock shaped like the real class,
50
+ * so a controller test never drags in a database or an HTTP client.
51
+ *
52
+ * Core services (the container, the logger, `ulak(...)`, the event emitter, ...) are always
53
+ * real - they are wired during bootstrap, before user components exist.
54
+ *
55
+ * @param options - Adapter, controllers and optional explicit overrides
56
+ * @returns The started app plus every double in play, keyed by service name
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const { app, mocks } = await createWebTest({ adapter, controllers: [UserController] });
61
+ *
62
+ * mocks.UserService.findById.mockResolvedValue({ id: '1', name: 'Ada' });
63
+ *
64
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
65
+ * expect(mocks.UserService.findById).toHaveBeenCalledWith('1');
66
+ *
67
+ * await app.stop();
68
+ * ```
69
+ */
70
+ export async function createWebTest(options) {
71
+ const { adapter, controllers, components = [], overrides = {}, logger = silentLogger, port, dispatch } = options;
72
+ const controllerList = Array.isArray(controllers) ? controllers : [controllers];
73
+ if (!controllerList.length) {
74
+ throw new Error('createWebTest requires at least one @Controller class.');
75
+ }
76
+ for (const controller of controllerList) {
77
+ if (typeof controller !== 'function' || !getTypedMetadata(ComponentType.CONTROLLER, controller)) {
78
+ throw new Error(`createWebTest expects @Controller classes, but received '${controller?.name ?? typeof controller}'. ` +
79
+ 'Pass services and middlewares through `components` instead.');
80
+ }
81
+ }
82
+ const realComponents = collectWebLayer(controllerList);
83
+ for (const component of components) {
84
+ realComponents.add(component);
85
+ }
86
+ const realNames = new Set();
87
+ for (const component of realComponents) {
88
+ realNames.add(componentName(component));
89
+ }
90
+ const coreServiceNames = new Set(Object.values(ICoreServiceNames));
91
+ const autoMocks = {};
92
+ for (const component of realComponents) {
93
+ for (const field of discoverInjectedFieldsFromClass(component)) {
94
+ const { serviceName, serviceClass, fieldName } = field;
95
+ // Core services are wired during bootstrap and are always real - mocking them would
96
+ // also trip the factory's core-key guard. ulak(...) lands here.
97
+ if (coreServiceNames.has(serviceName)) {
98
+ continue;
99
+ }
100
+ // Already satisfied - either registered for real or replaced by the caller
101
+ if (realNames.has(serviceName) || Object.hasOwn(overrides, serviceName)) {
102
+ continue;
103
+ }
104
+ // One shared mock per service, so two controllers injecting it see the same double
105
+ if (Object.hasOwn(autoMocks, serviceName)) {
106
+ continue;
107
+ }
108
+ if (!serviceClass) {
109
+ logger.warn(`createWebTest: '${serviceName}' (field '${fieldName}' on ${component.name}) was injected by name, ` +
110
+ 'so its method shape is unknown and the mock will be an empty object. ' +
111
+ 'Pass an explicit override if the test needs its methods.');
112
+ }
113
+ autoMocks[serviceName] = createMockFromClass(serviceClass);
114
+ }
115
+ }
116
+ const app = await createTestApp({
117
+ adapter,
118
+ logger,
119
+ port,
120
+ dispatch,
121
+ components: [...realComponents],
122
+ // Explicit overrides win over the generated ones
123
+ overrides: { ...autoMocks, ...overrides },
124
+ });
125
+ return { app, mocks: { ...autoMocks, ...overrides } };
126
+ }
127
+ //# sourceMappingURL=createWebTest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWebTest.js","sourceRoot":"","sources":["../../../../lib/test/harness/createWebTest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { ServerLogger } from '../../logger';
2
+ export declare const silentLogger: ServerLogger;
3
+ /**
4
+ * A single captured log line.
5
+ */
6
+ export interface CapturedLogEntry {
7
+ level: 'info' | 'warn' | 'error' | 'profile';
8
+ message: string;
9
+ meta?: any;
10
+ }
11
+ /**
12
+ * Creates a silent logger that also records every call, so tests can assert on warnings.
13
+ *
14
+ * @returns The logger plus the array it appends to (live, not a copy)
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { logger, entries } = createCapturingLogger();
19
+ * const { app } = await createWebTest({ adapter, controllers: [UserController], logger });
20
+ *
21
+ * expect(entries.some((e) => e.level === 'warn' && e.message.includes('cannot be auto-mocked'))).toBe(true);
22
+ * ```
23
+ */
24
+ export declare function createCapturingLogger(): {
25
+ logger: ServerLogger;
26
+ entries: CapturedLogEntry[];
27
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A ServerLogger that discards everything.
3
+ *
4
+ * The default logger for test apps: booting a server prints the banner, the adapter name,
5
+ * a route table and a "server running" line, none of which belong in test output.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const [adapter] = createHonoAdapter({ logger: silentLogger });
10
+ * ```
11
+ */
12
+ const noop = () => {
13
+ // Intentionally empty - the whole point is to keep test output clean
14
+ };
15
+ export const silentLogger = {
16
+ info: noop,
17
+ warn: noop,
18
+ error: noop,
19
+ profile: noop,
20
+ };
21
+ /**
22
+ * Creates a silent logger that also records every call, so tests can assert on warnings.
23
+ *
24
+ * @returns The logger plus the array it appends to (live, not a copy)
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const { logger, entries } = createCapturingLogger();
29
+ * const { app } = await createWebTest({ adapter, controllers: [UserController], logger });
30
+ *
31
+ * expect(entries.some((e) => e.level === 'warn' && e.message.includes('cannot be auto-mocked'))).toBe(true);
32
+ * ```
33
+ */
34
+ export function createCapturingLogger() {
35
+ const entries = [];
36
+ return {
37
+ entries,
38
+ logger: {
39
+ info: (message, meta) => {
40
+ entries.push({ level: 'info', message, meta });
41
+ },
42
+ warn: (message, meta) => {
43
+ entries.push({ level: 'warn', message, meta });
44
+ },
45
+ error: (message, meta) => {
46
+ entries.push({ level: 'error', message, meta });
47
+ },
48
+ profile: (message) => {
49
+ entries.push({ level: 'profile', message });
50
+ },
51
+ },
52
+ };
53
+ }
54
+ //# sourceMappingURL=silentLogger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"silentLogger.js","sourceRoot":"","sources":["../../../../lib/test/harness/silentLogger.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,IAAI,GAAG,GAAS,EAAE;IACtB,qEAAqE;AACvE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,IAAI;CACd,CAAC;AAWF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,OAAO,GAAuB,EAAE,CAAC;IAEvC,OAAO;QACL,OAAO;QACP,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,OAAe,EAAE,IAAU,EAAE,EAAE;gBACpC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,EAAE,CAAC,OAAe,EAAE,IAAU,EAAE,EAAE;gBACpC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;YACD,KAAK,EAAE,CAAC,OAAe,EAAE,IAAU,EAAE,EAAE;gBACrC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,CAAC;YACD,OAAO,EAAE,CAAC,OAAe,EAAE,EAAE;gBAC3B,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YAC9C,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}