@asenajs/asena 0.7.1 → 0.8.1

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 (148) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/adapter/AsenaAdapter.d.ts +2 -2
  3. package/dist/lib/adapter/AsenaAdapter.js.map +1 -1
  4. package/dist/lib/adapter/types/ServeOptions.d.ts +16 -0
  5. package/dist/lib/adapter/types/ValidationError.d.ts +53 -0
  6. package/dist/lib/adapter/types/ValidationError.js +24 -0
  7. package/dist/lib/adapter/types/ValidationError.js.map +1 -0
  8. package/dist/lib/adapter/types/index.d.ts +1 -0
  9. package/dist/lib/adapter/types/index.js +1 -0
  10. package/dist/lib/adapter/types/index.js.map +1 -1
  11. package/dist/lib/ioc/Container.d.ts +25 -0
  12. package/dist/lib/ioc/Container.js +47 -0
  13. package/dist/lib/ioc/Container.js.map +1 -1
  14. package/dist/lib/ioc/CoreContainer.d.ts +5 -3
  15. package/dist/lib/ioc/CoreContainer.js +9 -3
  16. package/dist/lib/ioc/CoreContainer.js.map +1 -1
  17. package/dist/lib/ioc/IocEngine.d.ts +50 -4
  18. package/dist/lib/ioc/IocEngine.js +138 -12
  19. package/dist/lib/ioc/IocEngine.js.map +1 -1
  20. package/dist/lib/ioc/component/componentRegistry.d.ts +22 -0
  21. package/dist/lib/ioc/component/componentRegistry.js +119 -0
  22. package/dist/lib/ioc/component/componentRegistry.js.map +1 -0
  23. package/dist/lib/ioc/component/componentUtils.js +4 -0
  24. package/dist/lib/ioc/component/componentUtils.js.map +1 -1
  25. package/dist/lib/ioc/component/decorators/Inject.js +16 -0
  26. package/dist/lib/ioc/component/decorators/Inject.js.map +1 -1
  27. package/dist/lib/ioc/component/index.d.ts +1 -0
  28. package/dist/lib/ioc/component/index.js +1 -0
  29. package/dist/lib/ioc/component/index.js.map +1 -1
  30. package/dist/lib/ioc/constants/ComponentConstants.d.ts +4 -0
  31. package/dist/lib/ioc/constants/ComponentConstants.js +5 -0
  32. package/dist/lib/ioc/constants/ComponentConstants.js.map +1 -1
  33. package/dist/lib/ioc/helper/fileHelper.d.ts +9 -0
  34. package/dist/lib/ioc/helper/fileHelper.js +41 -11
  35. package/dist/lib/ioc/helper/fileHelper.js.map +1 -1
  36. package/dist/lib/ioc/types/ICoreServiceNamesType.d.ts +1 -0
  37. package/dist/lib/ioc/types/ICoreServiceNamesType.js +1 -0
  38. package/dist/lib/ioc/types/ICoreServiceNamesType.js.map +1 -1
  39. package/dist/lib/ioc/types/InjectableComponent.d.ts +10 -0
  40. package/dist/lib/ioc/types/IocConfig.d.ts +7 -0
  41. package/dist/lib/ioc/types/decorators/ComponentType.d.ts +2 -1
  42. package/dist/lib/ioc/types/decorators/ComponentType.js +1 -0
  43. package/dist/lib/ioc/types/decorators/ComponentType.js.map +1 -1
  44. package/dist/lib/server/AsenaServer.d.ts +47 -3
  45. package/dist/lib/server/AsenaServer.js +167 -28
  46. package/dist/lib/server/AsenaServer.js.map +1 -1
  47. package/dist/lib/server/AsenaServerFactory.d.ts +38 -3
  48. package/dist/lib/server/AsenaServerFactory.js +21 -2
  49. package/dist/lib/server/AsenaServerFactory.js.map +1 -1
  50. package/dist/lib/server/config/AsenaConfig.d.ts +82 -4
  51. package/dist/lib/server/config/AsenaConfig.js +39 -1
  52. package/dist/lib/server/config/AsenaConfig.js.map +1 -1
  53. package/dist/lib/server/decorators/components/MessageController.d.ts +49 -0
  54. package/dist/lib/server/decorators/components/MessageController.js +62 -0
  55. package/dist/lib/server/decorators/components/MessageController.js.map +1 -0
  56. package/dist/lib/server/decorators/components/index.d.ts +1 -0
  57. package/dist/lib/server/decorators/components/index.js +1 -0
  58. package/dist/lib/server/decorators/components/index.js.map +1 -1
  59. package/dist/lib/server/event/EventDispatchService.d.ts +3 -7
  60. package/dist/lib/server/event/EventDispatchService.js +9 -40
  61. package/dist/lib/server/event/EventDispatchService.js.map +1 -1
  62. package/dist/lib/server/event/PatternHandlerIndex.d.ts +53 -0
  63. package/dist/lib/server/event/PatternHandlerIndex.js +100 -0
  64. package/dist/lib/server/event/PatternHandlerIndex.js.map +1 -0
  65. package/dist/lib/server/event/decorators/On.d.ts +4 -0
  66. package/dist/lib/server/event/decorators/On.js +6 -0
  67. package/dist/lib/server/event/decorators/On.js.map +1 -1
  68. package/dist/lib/server/event/index.d.ts +1 -0
  69. package/dist/lib/server/event/index.js +2 -0
  70. package/dist/lib/server/event/index.js.map +1 -1
  71. package/dist/lib/server/event/types.d.ts +12 -0
  72. package/dist/lib/server/health/HealthServer.d.ts +26 -0
  73. package/dist/lib/server/health/HealthServer.js +63 -0
  74. package/dist/lib/server/health/HealthServer.js.map +1 -0
  75. package/dist/lib/server/health/index.d.ts +1 -0
  76. package/dist/lib/server/health/index.js +2 -0
  77. package/dist/lib/server/health/index.js.map +1 -0
  78. package/dist/lib/server/messaging/Ulak.d.ts +156 -0
  79. package/dist/lib/server/messaging/Ulak.js +174 -0
  80. package/dist/lib/server/messaging/Ulak.js.map +1 -1
  81. package/dist/lib/server/messaging/types.d.ts +5 -1
  82. package/dist/lib/server/messaging/types.js +5 -0
  83. package/dist/lib/server/messaging/types.js.map +1 -1
  84. package/dist/lib/server/microservice/InMemoryTransport.d.ts +45 -0
  85. package/dist/lib/server/microservice/InMemoryTransport.js +124 -0
  86. package/dist/lib/server/microservice/InMemoryTransport.js.map +1 -0
  87. package/dist/lib/server/microservice/MicroserviceTransport.d.ts +132 -0
  88. package/dist/lib/server/microservice/MicroserviceTransport.js +2 -0
  89. package/dist/lib/server/microservice/MicroserviceTransport.js.map +1 -0
  90. package/dist/lib/server/microservice/decorators/EventPattern.d.ts +38 -0
  91. package/dist/lib/server/microservice/decorators/EventPattern.js +59 -0
  92. package/dist/lib/server/microservice/decorators/EventPattern.js.map +1 -0
  93. package/dist/lib/server/microservice/decorators/MessagePattern.d.ts +31 -0
  94. package/dist/lib/server/microservice/decorators/MessagePattern.js +55 -0
  95. package/dist/lib/server/microservice/decorators/MessagePattern.js.map +1 -0
  96. package/dist/lib/server/microservice/decorators/index.d.ts +2 -0
  97. package/dist/lib/server/microservice/decorators/index.js +3 -0
  98. package/dist/lib/server/microservice/decorators/index.js.map +1 -0
  99. package/dist/lib/server/microservice/index.d.ts +5 -0
  100. package/dist/lib/server/microservice/index.js +11 -0
  101. package/dist/lib/server/microservice/index.js.map +1 -0
  102. package/dist/lib/server/microservice/interceptorChain.d.ts +20 -0
  103. package/dist/lib/server/microservice/interceptorChain.js +39 -0
  104. package/dist/lib/server/microservice/interceptorChain.js.map +1 -0
  105. package/dist/lib/server/microservice/types.d.ts +142 -0
  106. package/dist/lib/server/microservice/types.js +5 -0
  107. package/dist/lib/server/microservice/types.js.map +1 -0
  108. package/dist/lib/server/src/services/PrepareEventService.d.ts +4 -1
  109. package/dist/lib/server/src/services/PrepareEventService.js +10 -3
  110. package/dist/lib/server/src/services/PrepareEventService.js.map +1 -1
  111. package/dist/lib/server/src/services/PrepareMicroserviceService.d.ts +66 -0
  112. package/dist/lib/server/src/services/PrepareMicroserviceService.js +193 -0
  113. package/dist/lib/server/src/services/PrepareMicroserviceService.js.map +1 -0
  114. package/dist/lib/test/factory/deepMock.d.ts +25 -0
  115. package/dist/lib/test/factory/deepMock.js +105 -0
  116. package/dist/lib/test/factory/deepMock.js.map +1 -0
  117. package/dist/lib/test/factory/ulakStub.d.ts +44 -0
  118. package/dist/lib/test/factory/ulakStub.js +40 -0
  119. package/dist/lib/test/factory/ulakStub.js.map +1 -0
  120. package/dist/lib/test/harness/createTestApp.d.ts +33 -0
  121. package/dist/lib/test/harness/createTestApp.js +105 -0
  122. package/dist/lib/test/harness/createTestApp.js.map +1 -0
  123. package/dist/lib/test/harness/createWebTest.d.ts +28 -0
  124. package/dist/lib/test/harness/createWebTest.js +127 -0
  125. package/dist/lib/test/harness/createWebTest.js.map +1 -0
  126. package/dist/lib/test/harness/silentLogger.d.ts +27 -0
  127. package/dist/lib/test/harness/silentLogger.js +54 -0
  128. package/dist/lib/test/harness/silentLogger.js.map +1 -0
  129. package/dist/lib/test/harness/types.d.ts +147 -0
  130. package/dist/lib/test/harness/types.js +2 -0
  131. package/dist/lib/test/harness/types.js.map +1 -0
  132. package/dist/lib/test/http/TestHttpCall.d.ts +78 -0
  133. package/dist/lib/test/http/TestHttpCall.js +149 -0
  134. package/dist/lib/test/http/TestHttpCall.js.map +1 -0
  135. package/dist/lib/test/http/TestHttpResponse.d.ts +46 -0
  136. package/dist/lib/test/http/TestHttpResponse.js +70 -0
  137. package/dist/lib/test/http/TestHttpResponse.js.map +1 -0
  138. package/dist/lib/test/index.d.ts +9 -0
  139. package/dist/lib/test/index.js +8 -0
  140. package/dist/lib/test/index.js.map +1 -1
  141. package/dist/lib/test/metadata/discovery.d.ts +21 -0
  142. package/dist/lib/test/metadata/discovery.js +37 -8
  143. package/dist/lib/test/metadata/discovery.js.map +1 -1
  144. package/dist/lib/test/mockComponent.d.ts +25 -0
  145. package/dist/lib/test/mockComponent.js +39 -6
  146. package/dist/lib/test/mockComponent.js.map +1 -1
  147. package/dist/lib/test/types.d.ts +13 -0
  148. package/package.json +11 -7
@@ -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"}
@@ -0,0 +1,147 @@
1
+ import type { AsenaAdapter } from '../../adapter';
2
+ import type { Container } from '../../ioc';
3
+ import type { ServerLogger } from '../../logger';
4
+ import type { AsenaServer } from '../../server/AsenaServer';
5
+ import type { Class } from '../../server/types';
6
+ import type { TestHttpCall } from '../http/TestHttpCall';
7
+ /**
8
+ * How the test app accepts requests.
9
+ *
10
+ * - `server` - listens on a TCP port (default). Matches production exactly.
11
+ * - `socket` - listens on a unix domain socket instead. Still the adapter's real routing
12
+ * pipeline, but occupies no port, so parallel suites cannot collide. Requires an adapter
13
+ * that honours `AsenaStartOptions.unix`.
14
+ */
15
+ export type DispatchMode = 'server' | 'socket';
16
+ /**
17
+ * Options for {@link createTestApp}
18
+ */
19
+ export interface TestAppOptions<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>> {
20
+ /**
21
+ * The adapter instance to boot, e.g. `createHonoAdapter({ logger: silentLogger })[0]`
22
+ */
23
+ adapter: A;
24
+ /**
25
+ * Components to register. Passing this list skips filesystem scanning entirely.
26
+ */
27
+ components: Class[];
28
+ /**
29
+ * Replace registered components with test doubles, keyed by service name (`@MockBean`).
30
+ *
31
+ * Override services, not controllers: a plain object mock carries no `@Controller`
32
+ * metadata, so an overridden controller's routes would never be registered.
33
+ */
34
+ overrides?: Record<string, object>;
35
+ /**
36
+ * Logger for the booted server
37
+ *
38
+ * @default silentLogger
39
+ */
40
+ logger?: ServerLogger;
41
+ /**
42
+ * Port to bind. Ignored in `socket` dispatch.
43
+ *
44
+ * @default 0 - Bun picks a free ephemeral port, which removes the port race entirely
45
+ */
46
+ port?: number;
47
+ /**
48
+ * @default 'server'
49
+ */
50
+ dispatch?: DispatchMode;
51
+ }
52
+ /**
53
+ * A booted test application
54
+ */
55
+ export interface TestApp {
56
+ /**
57
+ * The running server
58
+ */
59
+ readonly server: AsenaServer<any>;
60
+ /**
61
+ * The user component container
62
+ */
63
+ readonly container: Container;
64
+ /**
65
+ * The bound TCP port. `0` in `socket` dispatch, where no port is used.
66
+ */
67
+ readonly port: number;
68
+ /**
69
+ * Base URL for HTTP calls. In `socket` dispatch the host is a placeholder - routing
70
+ * happens over {@link socketPath}.
71
+ */
72
+ readonly baseUrl: string;
73
+ /**
74
+ * Path of the unix socket, when running in `socket` dispatch
75
+ */
76
+ readonly socketPath?: string;
77
+ /**
78
+ * Resolves a component from the container
79
+ *
80
+ * @param name - Registered service name
81
+ */
82
+ resolve<T>(name: string): Promise<T>;
83
+ /**
84
+ * Builds a WebSocket URL for the given path, correct for the active dispatch mode
85
+ *
86
+ * @param path - Route path, e.g. `/ws/chat`
87
+ */
88
+ wsUrl(path: string): string;
89
+ /**
90
+ * Starts a request with an explicit method in `init`
91
+ *
92
+ * @param path - Path relative to the app root
93
+ * @param init - Standard fetch init
94
+ */
95
+ request(path: string, init?: RequestInit): TestHttpCall;
96
+ get(path: string, init?: RequestInit): TestHttpCall;
97
+ post(path: string, init?: RequestInit): TestHttpCall;
98
+ put(path: string, init?: RequestInit): TestHttpCall;
99
+ patch(path: string, init?: RequestInit): TestHttpCall;
100
+ delete(path: string, init?: RequestInit): TestHttpCall;
101
+ head(path: string, init?: RequestInit): TestHttpCall;
102
+ options(path: string, init?: RequestInit): TestHttpCall;
103
+ /**
104
+ * Stops the server and removes the unix socket if one was created. Idempotent.
105
+ */
106
+ stop(): Promise<void>;
107
+ /**
108
+ * Enables `await using app = await createTestApp(...)`
109
+ */
110
+ [Symbol.asyncDispose](): Promise<void>;
111
+ }
112
+ /**
113
+ * Options for {@link createWebTest}
114
+ */
115
+ export interface WebTestOptions<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>> {
116
+ adapter: A;
117
+ /**
118
+ * Controller(s) under test
119
+ */
120
+ controllers: Class | Class[];
121
+ /**
122
+ * Extra components to register for real, alongside the controllers' middlewares and
123
+ * validators (which are always real, since the framework resolves them by name)
124
+ */
125
+ components?: Class[];
126
+ /**
127
+ * Explicit doubles, keyed by service name. These win over the generated auto-mocks.
128
+ */
129
+ overrides?: Record<string, object>;
130
+ logger?: ServerLogger;
131
+ port?: number;
132
+ dispatch?: DispatchMode;
133
+ }
134
+ /**
135
+ * Result of {@link createWebTest}
136
+ */
137
+ export interface WebTestResult {
138
+ app: TestApp;
139
+ /**
140
+ * Every double in play - both auto-generated mocks and the explicit `overrides` - keyed
141
+ * by **service name** (not field name, unlike `mockComponent`).
142
+ *
143
+ * One shared mock per service, so two controllers injecting the same service see the
144
+ * same double.
145
+ */
146
+ mocks: Record<string, any>;
147
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../lib/test/harness/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { TestHttpResponse } from './TestHttpResponse';
2
+ /**
3
+ * A pending HTTP call with a fluent assertion chain.
4
+ *
5
+ * Nothing is sent until the call is awaited. Assertions queue up in the order they were
6
+ * chained and run against the buffered response; the send is memoized, so awaiting the
7
+ * same call twice does not issue a second request.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
12
+ *
13
+ * const res = await app.post('/users', { body: JSON.stringify({ name: 'Ada' }) }).expectStatus(201);
14
+ * expect(res.json().id).toBeString();
15
+ * ```
16
+ */
17
+ export declare class TestHttpCall implements PromiseLike<TestHttpResponse> {
18
+ private readonly assertions;
19
+ private pending?;
20
+ private readonly url;
21
+ private readonly init?;
22
+ constructor(url: string, init?: RequestInit);
23
+ /**
24
+ * Asserts the response status code
25
+ *
26
+ * @param expected - Expected status
27
+ * @returns this, for chaining
28
+ */
29
+ expectStatus(expected: number): this;
30
+ /**
31
+ * Asserts a response header
32
+ *
33
+ * @param name - Header name (case-insensitive)
34
+ * @param expected - Exact value, or a pattern to match
35
+ * @returns this, for chaining
36
+ */
37
+ expectHeader(name: string, expected: string | RegExp): this;
38
+ /**
39
+ * Asserts the whole JSON body deep-equals the expected value
40
+ *
41
+ * @param expected - Expected body
42
+ * @returns this, for chaining
43
+ */
44
+ expectJson<T>(expected: T): this;
45
+ /**
46
+ * Asserts the JSON body contains at least the given properties
47
+ *
48
+ * @param partial - Subset the body must match
49
+ * @returns this, for chaining
50
+ */
51
+ expectJsonContains(partial: object): this;
52
+ /**
53
+ * Asserts the raw text body
54
+ *
55
+ * @param expected - Exact body, or a pattern to match
56
+ * @returns this, for chaining
57
+ */
58
+ expectBody(expected: string | RegExp): this;
59
+ /**
60
+ * Escape hatch for assertions the chain does not cover
61
+ *
62
+ * @param assertion - Runs against the buffered response, may be async
63
+ * @returns this, for chaining
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * await app.get('/users').expect((res) => {
68
+ * expect(res.json<User[]>()).toHaveLength(3);
69
+ * });
70
+ * ```
71
+ */
72
+ expect(assertion: (response: TestHttpResponse) => void | Promise<void>): this;
73
+ then<TResult1 = TestHttpResponse, TResult2 = never>(onfulfilled?: ((value: TestHttpResponse) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
74
+ private enqueue;
75
+ private send;
76
+ private run;
77
+ private describeRequest;
78
+ }
@@ -0,0 +1,149 @@
1
+ import { expect } from 'bun:test';
2
+ import { TestHttpResponse, truncate } from './TestHttpResponse';
3
+ /**
4
+ * A pending HTTP call with a fluent assertion chain.
5
+ *
6
+ * Nothing is sent until the call is awaited. Assertions queue up in the order they were
7
+ * chained and run against the buffered response; the send is memoized, so awaiting the
8
+ * same call twice does not issue a second request.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
13
+ *
14
+ * const res = await app.post('/users', { body: JSON.stringify({ name: 'Ada' }) }).expectStatus(201);
15
+ * expect(res.json().id).toBeString();
16
+ * ```
17
+ */
18
+ export class TestHttpCall {
19
+ assertions = [];
20
+ pending;
21
+ url;
22
+ init;
23
+ constructor(url, init) {
24
+ this.url = url;
25
+ this.init = init;
26
+ }
27
+ /**
28
+ * Asserts the response status code
29
+ *
30
+ * @param expected - Expected status
31
+ * @returns this, for chaining
32
+ */
33
+ expectStatus(expected) {
34
+ // Deliberately not `expect(status).toBe(n)`: a bare number mismatch is the least
35
+ // useful failure in an HTTP test. The body almost always explains why.
36
+ return this.enqueue((response) => {
37
+ if (response.status !== expected) {
38
+ throw new Error(`Expected status ${expected} but received ${response.status} for ${this.describeRequest()}\n` +
39
+ `Body: ${truncate(response.text())}`);
40
+ }
41
+ });
42
+ }
43
+ /**
44
+ * Asserts a response header
45
+ *
46
+ * @param name - Header name (case-insensitive)
47
+ * @param expected - Exact value, or a pattern to match
48
+ * @returns this, for chaining
49
+ */
50
+ expectHeader(name, expected) {
51
+ return this.enqueue((response) => {
52
+ const actual = response.headers.get(name);
53
+ if (actual === null) {
54
+ const received = [];
55
+ response.headers.forEach((_value, key) => received.push(key));
56
+ throw new Error(`Expected header '${name}' to be present for ${this.describeRequest()}, but it was not sent.\n` +
57
+ `Received headers: ${received.join(', ') || '<none>'}`);
58
+ }
59
+ if (expected instanceof RegExp) {
60
+ expect(actual).toMatch(expected);
61
+ }
62
+ else {
63
+ expect(actual).toBe(expected);
64
+ }
65
+ });
66
+ }
67
+ /**
68
+ * Asserts the whole JSON body deep-equals the expected value
69
+ *
70
+ * @param expected - Expected body
71
+ * @returns this, for chaining
72
+ */
73
+ expectJson(expected) {
74
+ return this.enqueue((response) => {
75
+ expect(response.json()).toEqual(expected);
76
+ });
77
+ }
78
+ /**
79
+ * Asserts the JSON body contains at least the given properties
80
+ *
81
+ * @param partial - Subset the body must match
82
+ * @returns this, for chaining
83
+ */
84
+ expectJsonContains(partial) {
85
+ return this.enqueue((response) => {
86
+ expect(response.json()).toMatchObject(partial);
87
+ });
88
+ }
89
+ /**
90
+ * Asserts the raw text body
91
+ *
92
+ * @param expected - Exact body, or a pattern to match
93
+ * @returns this, for chaining
94
+ */
95
+ expectBody(expected) {
96
+ return this.enqueue((response) => {
97
+ if (expected instanceof RegExp) {
98
+ expect(response.text()).toMatch(expected);
99
+ }
100
+ else {
101
+ expect(response.text()).toBe(expected);
102
+ }
103
+ });
104
+ }
105
+ /**
106
+ * Escape hatch for assertions the chain does not cover
107
+ *
108
+ * @param assertion - Runs against the buffered response, may be async
109
+ * @returns this, for chaining
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * await app.get('/users').expect((res) => {
114
+ * expect(res.json<User[]>()).toHaveLength(3);
115
+ * });
116
+ * ```
117
+ */
118
+ expect(assertion) {
119
+ return this.enqueue(assertion);
120
+ }
121
+ then(onfulfilled, onrejected) {
122
+ return this.send().then(onfulfilled, onrejected);
123
+ }
124
+ enqueue(assertion) {
125
+ if (this.pending !== undefined) {
126
+ throw new Error('Cannot add assertions to a TestHttpCall that has already been sent. ' +
127
+ 'Chain every expect* call before awaiting the request.');
128
+ }
129
+ this.assertions.push(assertion);
130
+ return this;
131
+ }
132
+ send() {
133
+ if (this.pending === undefined) {
134
+ this.pending = this.run();
135
+ }
136
+ return this.pending;
137
+ }
138
+ async run() {
139
+ const response = await TestHttpResponse.from(await fetch(this.url, this.init));
140
+ for (const assertion of this.assertions) {
141
+ await assertion(response);
142
+ }
143
+ return response;
144
+ }
145
+ describeRequest() {
146
+ return `${(this.init?.method ?? 'GET').toUpperCase()} ${this.url}`;
147
+ }
148
+ }
149
+ //# sourceMappingURL=TestHttpCall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestHttpCall.js","sourceRoot":"","sources":["../../../../lib/test/http/TestHttpCall.ts"],"names":[],"mappings":"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"}
@@ -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;