@navios/core 0.7.1 → 0.9.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 (68) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/lib/{index-DW9EPAE6.d.mts → index-D9MNh6Tx.d.mts} +534 -342
  3. package/lib/index-D9MNh6Tx.d.mts.map +1 -0
  4. package/lib/{index-pHp-dIGt.d.cts → index-Db1d3cwD.d.cts} +534 -342
  5. package/lib/index-Db1d3cwD.d.cts.map +1 -0
  6. package/lib/index.cjs +12 -3
  7. package/lib/index.d.cts +2 -2
  8. package/lib/index.d.mts +2 -2
  9. package/lib/index.mjs +3 -3
  10. package/lib/legacy-compat/index.cjs +1 -1
  11. package/lib/legacy-compat/index.cjs.map +1 -1
  12. package/lib/legacy-compat/index.d.cts +3 -3
  13. package/lib/legacy-compat/index.d.cts.map +1 -1
  14. package/lib/legacy-compat/index.d.mts +3 -3
  15. package/lib/legacy-compat/index.d.mts.map +1 -1
  16. package/lib/legacy-compat/index.mjs +1 -1
  17. package/lib/legacy-compat/index.mjs.map +1 -1
  18. package/lib/{src-QnxR5b7c.cjs → src-BRPtJ9fG.cjs} +474 -53
  19. package/lib/src-BRPtJ9fG.cjs.map +1 -0
  20. package/lib/{src-DyvCDuKO.mjs → src-Bo23RIo-.mjs} +454 -51
  21. package/lib/src-Bo23RIo-.mjs.map +1 -0
  22. package/lib/testing/index.cjs +346 -29
  23. package/lib/testing/index.cjs.map +1 -1
  24. package/lib/testing/index.d.cts +299 -63
  25. package/lib/testing/index.d.cts.map +1 -1
  26. package/lib/testing/index.d.mts +299 -63
  27. package/lib/testing/index.d.mts.map +1 -1
  28. package/lib/testing/index.mjs +347 -31
  29. package/lib/testing/index.mjs.map +1 -1
  30. package/lib/{use-guards.decorator-B6q_N0sf.cjs → use-guards.decorator-Bs8oDHOi.cjs} +29 -99
  31. package/lib/use-guards.decorator-Bs8oDHOi.cjs.map +1 -0
  32. package/lib/{use-guards.decorator-kZ3lNK8v.mjs → use-guards.decorator-CzVXuLkz.mjs} +23 -99
  33. package/lib/use-guards.decorator-CzVXuLkz.mjs.map +1 -0
  34. package/package.json +4 -4
  35. package/src/__tests__/controller-resolver.spec.mts +229 -0
  36. package/src/__tests__/controller.spec.mts +1 -1
  37. package/src/__tests__/testing-module.spec.mts +459 -0
  38. package/src/__tests__/unit-testing-module.spec.mts +424 -0
  39. package/src/decorators/controller.decorator.mts +29 -7
  40. package/src/decorators/endpoint.decorator.mts +60 -12
  41. package/src/decorators/module.decorator.mts +23 -5
  42. package/src/decorators/multipart.decorator.mts +67 -24
  43. package/src/decorators/stream.decorator.mts +65 -24
  44. package/src/interfaces/abstract-http-handler-adapter.interface.mts +31 -1
  45. package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +2 -6
  46. package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
  47. package/src/legacy-compat/decorators/multipart.decorator.mts +5 -5
  48. package/src/legacy-compat/decorators/stream.decorator.mts +5 -5
  49. package/src/logger/logger.service.mts +0 -2
  50. package/src/navios.application.mts +23 -9
  51. package/src/navios.environment.mts +3 -1
  52. package/src/navios.factory.mts +19 -18
  53. package/src/services/guard-runner.service.mts +46 -9
  54. package/src/services/index.mts +1 -0
  55. package/src/services/instance-resolver.service.mts +187 -0
  56. package/src/services/module-loader.service.mts +3 -2
  57. package/src/stores/request-id.store.mts +45 -3
  58. package/src/testing/index.mts +1 -0
  59. package/src/testing/testing-module.mts +255 -93
  60. package/src/testing/unit-testing-module.mts +298 -0
  61. package/src/tokens/index.mts +1 -0
  62. package/src/tokens/navios-options.token.mts +6 -0
  63. package/lib/index-DW9EPAE6.d.mts.map +0 -1
  64. package/lib/index-pHp-dIGt.d.cts.map +0 -1
  65. package/lib/src-DyvCDuKO.mjs.map +0 -1
  66. package/lib/src-QnxR5b7c.cjs.map +0 -1
  67. package/lib/use-guards.decorator-B6q_N0sf.cjs.map +0 -1
  68. package/lib/use-guards.decorator-kZ3lNK8v.mjs.map +0 -1
@@ -1,15 +1,21 @@
1
- import type { ClassType, ClassTypeWithInstance, InjectionToken } from '@navios/di'
2
- import type { NaviosModule } from '../interfaces/index.mjs'
3
- import type { NaviosApplicationOptions } from '../navios.application.mjs'
1
+ import type {
2
+ ClassType,
3
+ ClassTypeWithInstance,
4
+ InjectionToken,
5
+ ScopedContainer,
6
+ } from '@navios/di'
4
7
 
5
8
  import { TestContainer } from '@navios/di/testing'
6
9
 
10
+ import type { NaviosModule } from '../interfaces/index.mjs'
11
+ import type { NaviosApplicationOptions } from '../navios.application.mjs'
12
+
7
13
  import { NaviosApplication } from '../navios.application.mjs'
8
14
  import { NaviosFactory } from '../navios.factory.mjs'
9
15
 
10
16
  /**
11
17
  * Configuration for overriding a provider in the testing module.
12
- *
18
+ *
13
19
  * @typeParam T - The type of the provider being overridden
14
20
  */
15
21
  export interface TestingModuleOverride<T = any> {
@@ -29,68 +35,139 @@ export interface TestingModuleOverride<T = any> {
29
35
 
30
36
  /**
31
37
  * Options for creating a testing module.
32
- *
38
+ *
33
39
  * Extends NaviosApplicationOptions but excludes the container option,
34
40
  * as TestingModule manages its own TestContainer.
35
41
  */
36
- export interface TestingModuleOptions
37
- extends Omit<NaviosApplicationOptions, 'container'> {
42
+ export interface TestingModuleOptions extends Omit<
43
+ NaviosApplicationOptions,
44
+ 'container'
45
+ > {
38
46
  /**
39
47
  * Initial provider overrides to apply when creating the testing module.
40
- *
48
+ *
41
49
  * You can also use `overrideProvider()` method for a fluent API.
42
50
  */
43
51
  overrides?: TestingModuleOverride[]
52
+ /**
53
+ * Container to use for the testing module.
54
+ * If not provided, a new TestContainer will be created.
55
+ */
56
+ container?: TestContainer
44
57
  }
45
58
 
46
59
  /**
47
60
  * A testing-optimized wrapper around NaviosApplication.
48
61
  * Provides utilities for setting up test environments with mock dependencies.
62
+ *
63
+ * When `init()` is called, a request scope is automatically started.
64
+ * This means `get()` calls will resolve request-scoped services correctly,
65
+ * simulating a real HTTP request context.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const module = await TestingModule.create(AppModule)
70
+ * .overrideProvider(DatabaseService)
71
+ * .useValue(mockDatabase)
72
+ * .init()
73
+ *
74
+ * const userService = await module.get(UserService)
75
+ * // ... run tests ...
76
+ *
77
+ * await module.close()
78
+ * ```
49
79
  */
50
80
  export class TestingModule {
51
81
  private app: NaviosApplication | null = null
82
+ private scopedContainer: ScopedContainer | null = null
83
+ private requestId = `test-request-${Date.now()}-${Math.random().toString(36).slice(2)}`
52
84
 
53
- constructor(
85
+ private constructor(
54
86
  private readonly appModule: ClassTypeWithInstance<NaviosModule>,
55
87
  private readonly container: TestContainer,
56
88
  private readonly options: TestingModuleOptions,
57
89
  ) {}
58
90
 
59
91
  /**
60
- * Compiles the testing module and returns the NaviosApplication.
61
- * Call this after setting up all overrides.
92
+ * Creates a new TestingModule for the given app module.
93
+ * This is the main entry point for setting up integration tests.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const module = await TestingModule.create(AppModule)
98
+ * .overrideProvider(DatabaseService)
99
+ * .useValue(mockDatabase)
100
+ * .init()
101
+ * ```
62
102
  */
63
- async compile(): Promise<NaviosApplication> {
64
- this.app = await NaviosFactory.create(this.appModule, {
65
- ...this.options,
66
- container: this.container,
67
- })
68
- return this.app
103
+ static create(
104
+ appModule: ClassTypeWithInstance<NaviosModule>,
105
+ options: TestingModuleOptions = { adapter: [] },
106
+ ): TestingModule {
107
+ const container =
108
+ options.container ??
109
+ new TestContainer({
110
+ parentRegistry: options.registry,
111
+ })
112
+
113
+ // Apply initial overrides if provided
114
+ if (options.overrides) {
115
+ for (const override of options.overrides) {
116
+ if (override.useValue !== undefined) {
117
+ container.bind(override.token as any).toValue(override.useValue)
118
+ } else if (override.useClass) {
119
+ container.bind(override.token as any).toClass(override.useClass)
120
+ }
121
+ }
122
+ }
123
+
124
+ return new TestingModule(appModule, container, options)
69
125
  }
70
126
 
71
127
  /**
72
- * Initializes the application (loads modules, sets up HTTP if configured).
73
- * This is equivalent to calling app.init() on the compiled application.
128
+ * Compiles the testing module without initializing it.
129
+ * Call this if you need to access the app before initialization.
130
+ *
131
+ * @returns this for chaining
74
132
  */
75
- async init(): Promise<NaviosApplication> {
133
+ async compile(): Promise<this> {
76
134
  if (!this.app) {
77
- await this.compile()
135
+ this.app = await NaviosFactory.create(this.appModule, {
136
+ ...this.options,
137
+ container: this.container,
138
+ })
78
139
  }
79
- await this.app!.init()
80
- return this.app!
140
+ return this
81
141
  }
82
142
 
83
143
  /**
84
- * Gets the underlying TestContainer for direct manipulation.
144
+ * Initializes the application and starts a request scope.
145
+ *
146
+ * This is equivalent to calling `compile()` followed by `app.init()`,
147
+ * plus starting a request context for proper request-scoped service resolution.
148
+ *
149
+ * @returns this for chaining
85
150
  */
86
- getContainer(): TestContainer {
87
- return this.container
151
+ async init(): Promise<this> {
152
+ if (!this.app) {
153
+ await this.compile()
154
+ }
155
+ await this.app!.init()
156
+
157
+ // Begin a request scope so get() can resolve request-scoped services
158
+ this.scopedContainer = this.container.beginRequest(this.requestId, {
159
+ testingModule: true,
160
+ })
161
+
162
+ return this
88
163
  }
89
164
 
90
165
  /**
91
- * Gets the compiled application. Throws if not yet compiled.
166
+ * Gets the compiled application.
167
+ *
168
+ * @throws Error if the module has not been compiled yet
92
169
  */
93
- getApplication(): NaviosApplication {
170
+ getApp(): NaviosApplication {
94
171
  if (!this.app) {
95
172
  throw new Error(
96
173
  'TestingModule not compiled. Call compile() or init() first.',
@@ -100,7 +177,28 @@ export class TestingModule {
100
177
  }
101
178
 
102
179
  /**
103
- * Override a provider with a mock value.
180
+ * Gets the underlying TestContainer for direct manipulation.
181
+ */
182
+ getContainer(): TestContainer {
183
+ return this.container
184
+ }
185
+
186
+ /**
187
+ * Gets the scoped container for the current test request.
188
+ * Only available after calling `init()`.
189
+ *
190
+ * @throws Error if init() has not been called
191
+ */
192
+ getScopedContainer(): ScopedContainer {
193
+ if (!this.scopedContainer) {
194
+ throw new Error('No scoped container available. Call init() first.')
195
+ }
196
+ return this.scopedContainer
197
+ }
198
+
199
+ /**
200
+ * Override a provider with a mock value or class.
201
+ * Must be called before `compile()` or `init()`.
104
202
  */
105
203
  overrideProvider<T>(token: ClassType | InjectionToken<T, any>): {
106
204
  useValue: (value: T) => TestingModule
@@ -120,112 +218,176 @@ export class TestingModule {
120
218
 
121
219
  /**
122
220
  * Gets an instance from the container.
221
+ *
222
+ * If `init()` has been called, this uses the scoped container
223
+ * which properly resolves request-scoped services.
224
+ *
225
+ * If only `compile()` was called, this uses the root container
226
+ * and request-scoped services will throw.
123
227
  */
124
- async get<T>(token: ClassTypeWithInstance<T> | InjectionToken<T, any>): Promise<T> {
228
+ async get<T>(
229
+ token: ClassTypeWithInstance<T> | InjectionToken<T, any>,
230
+ ): Promise<T> {
231
+ // Use scoped container if available (after init)
232
+ if (this.scopedContainer) {
233
+ return this.scopedContainer.get(token as any)
234
+ }
235
+ // Fall back to root container (after compile only)
125
236
  return this.container.get(token as any)
126
237
  }
127
238
 
128
239
  /**
129
- * Disposes the testing module and cleans up resources.
240
+ * Disposes the testing module and cleans up all resources.
241
+ *
242
+ * This will:
243
+ * 1. End the request scope (if started)
244
+ * 2. Close the application (if initialized)
245
+ * 3. Dispose the container
130
246
  */
131
247
  async close(): Promise<void> {
248
+ // End the request scope first
249
+ if (this.scopedContainer) {
250
+ await this.scopedContainer.endRequest()
251
+ this.scopedContainer = null
252
+ }
253
+
254
+ // Close the app
132
255
  if (this.app) {
133
256
  await this.app.close()
257
+ this.app = null
134
258
  }
259
+
260
+ // Dispose the container
135
261
  await this.container.dispose()
136
262
  }
137
- }
138
263
 
139
- /**
140
- * Fluent builder interface for TestingModule.
141
- *
142
- * Provides a chainable API for configuring and using a testing module.
143
- */
144
- export interface TestingModuleBuilder {
264
+ // ===========================================================================
265
+ // ASSERTION HELPERS (delegated to TestContainer)
266
+ // ===========================================================================
267
+
145
268
  /**
146
- * Override a provider with a mock value or class.
147
- *
148
- * @param token - The injection token or class to override
149
- * @returns An object with `useValue` and `useClass` methods for chaining
150
- *
151
- * @example
152
- * ```typescript
153
- * const testingModule = await createTestingModule(AppModule)
154
- * .overrideProvider(DatabaseService)
155
- * .useValue(mockDatabaseService)
156
- * .compile()
157
- * ```
269
+ * Asserts that a service has been resolved at least once.
158
270
  */
159
- overrideProvider<T>(token: ClassType | InjectionToken<T, any>): {
160
- useValue: (value: T) => TestingModuleBuilder
161
- useClass: (target: ClassType) => TestingModuleBuilder
271
+ expectResolved(token: ClassType | InjectionToken<any, any>): void {
272
+ this.container.expectResolved(token)
162
273
  }
163
274
 
164
275
  /**
165
- * Compiles the testing module and returns the NaviosApplication.
166
- *
167
- * This creates the application instance but does not initialize it.
168
- * Call `init()` if you need the application to be fully initialized.
276
+ * Asserts that a service has NOT been resolved.
169
277
  */
170
- compile(): Promise<NaviosApplication>
278
+ expectNotResolved(token: ClassType | InjectionToken<any, any>): void {
279
+ this.container.expectNotResolved(token)
280
+ }
171
281
 
172
282
  /**
173
- * Initializes the application (loads modules, sets up HTTP if configured).
174
- *
175
- * This is equivalent to calling `compile()` followed by `app.init()`.
283
+ * Asserts that a service is registered as singleton scope.
176
284
  */
177
- init(): Promise<NaviosApplication>
285
+ expectSingleton(token: ClassType | InjectionToken<any, any>): void {
286
+ this.container.expectSingleton(token)
287
+ }
178
288
 
179
289
  /**
180
- * Gets the underlying TestContainer for direct manipulation.
290
+ * Asserts that a service is registered as transient scope.
181
291
  */
182
- getContainer(): TestContainer
292
+ expectTransient(token: ClassType | InjectionToken<any, any>): void {
293
+ this.container.expectTransient(token)
294
+ }
183
295
 
184
296
  /**
185
- * Gets an instance from the container.
186
- *
187
- * @typeParam T - The type of the instance to retrieve
188
- * @param token - The injection token or class
189
- * @returns The resolved instance
297
+ * Asserts that a service is registered as request scope.
298
+ */
299
+ expectRequestScoped(token: ClassType | InjectionToken<any, any>): void {
300
+ this.container.expectRequestScoped(token)
301
+ }
302
+
303
+ /**
304
+ * Asserts that a method was called on a service.
305
+ * Note: You must use `recordMethodCall()` in your mocks for this to work.
306
+ */
307
+ expectCalled(
308
+ token: ClassType | InjectionToken<any, any>,
309
+ method: string,
310
+ ): void {
311
+ this.container.expectCalled(token, method)
312
+ }
313
+
314
+ /**
315
+ * Asserts that a method was called with specific arguments.
316
+ * Note: You must use `recordMethodCall()` in your mocks for this to work.
317
+ */
318
+ expectCalledWith(
319
+ token: ClassType | InjectionToken<any, any>,
320
+ method: string,
321
+ expectedArgs: unknown[],
322
+ ): void {
323
+ this.container.expectCalledWith(token, method, expectedArgs)
324
+ }
325
+
326
+ /**
327
+ * Asserts that a method was called a specific number of times.
328
+ * Note: You must use `recordMethodCall()` in your mocks for this to work.
329
+ */
330
+ expectCallCount(
331
+ token: ClassType | InjectionToken<any, any>,
332
+ method: string,
333
+ count: number,
334
+ ): void {
335
+ this.container.expectCallCount(token, method, count)
336
+ }
337
+
338
+ /**
339
+ * Records a method call for tracking.
340
+ * Call this from your mock implementations to enable call assertions.
341
+ */
342
+ recordMethodCall(
343
+ token: ClassType | InjectionToken<any, any>,
344
+ method: string,
345
+ args: unknown[],
346
+ result?: unknown,
347
+ error?: Error,
348
+ ): void {
349
+ this.container.recordMethodCall(token, method, args, result, error)
350
+ }
351
+
352
+ /**
353
+ * Gets all recorded method calls for a service.
190
354
  */
191
- get<T>(token: ClassTypeWithInstance<T> | InjectionToken<T, any>): Promise<T>
355
+ getMethodCalls(token: ClassType | InjectionToken<any, any>) {
356
+ return this.container.getMethodCalls(token)
357
+ }
192
358
 
193
359
  /**
194
- * Disposes the testing module and cleans up resources.
360
+ * Gets the dependency graph for debugging or snapshot testing.
195
361
  */
196
- close(): Promise<void>
362
+ getDependencyGraph() {
363
+ return this.container.getDependencyGraph()
364
+ }
365
+
366
+ /**
367
+ * Gets a simplified dependency graph showing only token relationships.
368
+ */
369
+ getSimplifiedDependencyGraph() {
370
+ return this.container.getSimplifiedDependencyGraph()
371
+ }
197
372
  }
198
373
 
199
374
  /**
200
375
  * Creates a testing module for the given app module.
201
- * This is the main entry point for setting up tests.
376
+ *
377
+ * @deprecated Use `TestingModule.create()` instead.
202
378
  *
203
379
  * @example
204
380
  * ```typescript
205
- * const testingModule = await createTestingModule(AppModule, {
206
- * adapter: [],
207
- * })
208
- * .overrideProvider(DatabaseService)
209
- * .useValue(mockDatabaseService)
210
- * .compile()
381
+ * // Old way (deprecated)
382
+ * const module = createTestingModule(AppModule)
383
+ *
384
+ * // New way
385
+ * const module = TestingModule.create(AppModule)
211
386
  * ```
212
387
  */
213
388
  export function createTestingModule(
214
389
  appModule: ClassTypeWithInstance<NaviosModule>,
215
390
  options: TestingModuleOptions = { adapter: [] },
216
391
  ): TestingModule {
217
- const container = new TestContainer()
218
-
219
- // Apply initial overrides if provided
220
- if (options.overrides) {
221
- for (const override of options.overrides) {
222
- if (override.useValue !== undefined) {
223
- container.bind(override.token as any).toValue(override.useValue)
224
- } else if (override.useClass) {
225
- container.bind(override.token as any).toClass(override.useClass)
226
- }
227
- }
228
- }
229
-
230
- return new TestingModule(appModule, container, options)
392
+ return TestingModule.create(appModule, options)
231
393
  }