@navios/core 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (93) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/lib/{index-BJjk2X1S.d.mts → index-6S7veHKD.d.mts} +845 -294
  3. package/lib/index-6S7veHKD.d.mts.map +1 -0
  4. package/lib/{index-DZ6NU03y.d.cts → index-r0i2txmg.d.cts} +845 -294
  5. package/lib/index-r0i2txmg.d.cts.map +1 -0
  6. package/lib/index.cjs +4420 -84
  7. package/lib/index.cjs.map +1 -0
  8. package/lib/index.d.cts +2 -2
  9. package/lib/index.d.mts +2 -2
  10. package/lib/index.mjs +4328 -3
  11. package/lib/index.mjs.map +1 -0
  12. package/lib/legacy-compat/index.cjs +41 -126
  13. package/lib/legacy-compat/index.cjs.map +1 -1
  14. package/lib/legacy-compat/index.d.cts +4 -60
  15. package/lib/legacy-compat/index.d.cts.map +1 -1
  16. package/lib/legacy-compat/index.d.mts +4 -60
  17. package/lib/legacy-compat/index.d.mts.map +1 -1
  18. package/lib/legacy-compat/index.mjs +14 -119
  19. package/lib/legacy-compat/index.mjs.map +1 -1
  20. package/lib/navios.factory-BanZIvtR.cjs +4134 -0
  21. package/lib/navios.factory-BanZIvtR.cjs.map +1 -0
  22. package/lib/navios.factory-C75yZCoD.mjs +3831 -0
  23. package/lib/navios.factory-C75yZCoD.mjs.map +1 -0
  24. package/lib/testing/index.cjs +3 -3
  25. package/lib/testing/index.cjs.map +1 -1
  26. package/lib/testing/index.d.cts +1 -1
  27. package/lib/testing/index.d.mts +1 -1
  28. package/lib/testing/index.mjs +2 -2
  29. package/lib/tokens-4J9sredA.mjs +100 -0
  30. package/lib/tokens-4J9sredA.mjs.map +1 -0
  31. package/lib/tokens-BuXXB01L.cjs +196 -0
  32. package/lib/tokens-BuXXB01L.cjs.map +1 -0
  33. package/lib/{use-guards.decorator-Be_QUx6b.mjs → use-guards.decorator-BecoQSmE.mjs} +3 -70
  34. package/lib/use-guards.decorator-BecoQSmE.mjs.map +1 -0
  35. package/lib/{use-guards.decorator-B6tghdxM.cjs → use-guards.decorator-DgD-kxF5.cjs} +7 -158
  36. package/lib/use-guards.decorator-DgD-kxF5.cjs.map +1 -0
  37. package/package.json +4 -4
  38. package/src/__tests__/attribute.factory.spec.mts +300 -0
  39. package/src/__tests__/console-logger.service.spec.mts +312 -0
  40. package/src/__tests__/guard-runner.service.spec.mts +399 -0
  41. package/src/__tests__/logger.service.spec.mts +147 -0
  42. package/src/__tests__/responders.spec.mts +6 -5
  43. package/src/factories/adapter.factory.mts +20 -0
  44. package/src/factories/endpoint-adapter.factory.mts +1 -1
  45. package/src/factories/http-adapter.factory.mts +1 -1
  46. package/src/factories/index.mts +1 -0
  47. package/src/factories/multipart-adapter.factory.mts +1 -1
  48. package/src/factories/reply.factory.mts +1 -1
  49. package/src/factories/request.factory.mts +1 -1
  50. package/src/factories/stream-adapter.factory.mts +1 -1
  51. package/src/factories/xml-stream-adapter.factory.mts +1 -1
  52. package/src/index.mts +1 -0
  53. package/src/interfaces/abstract-adapter.interface.mts +32 -0
  54. package/src/interfaces/abstract-http-adapter.interface.mts +27 -20
  55. package/src/interfaces/abstract-http-handler-adapter.interface.mts +86 -2
  56. package/src/interfaces/adapter-environment.interface.mts +74 -0
  57. package/src/interfaces/index.mts +2 -0
  58. package/src/interfaces/plugin.interface.mts +50 -16
  59. package/src/legacy-compat/attribute.factory.mts +2 -2
  60. package/src/legacy-compat/decorators/controller.decorator.mts +1 -1
  61. package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
  62. package/src/legacy-compat/decorators/header.decorator.mts +2 -1
  63. package/src/legacy-compat/decorators/http-code.decorator.mts +2 -1
  64. package/src/legacy-compat/decorators/index.mts +2 -2
  65. package/src/legacy-compat/decorators/module.decorator.mts +1 -1
  66. package/src/legacy-compat/decorators/multipart.decorator.mts +1 -1
  67. package/src/legacy-compat/decorators/stream.decorator.mts +1 -1
  68. package/src/legacy-compat/decorators/use-guards.decorator.mts +1 -1
  69. package/src/legacy-compat/index.mts +10 -5
  70. package/src/logger/console-logger.service.mts +97 -7
  71. package/src/metadata/module.metadata.mts +43 -0
  72. package/src/navios.application.mts +172 -60
  73. package/src/navios.environment.mts +22 -12
  74. package/src/navios.factory.mts +31 -10
  75. package/src/services/abstract-handler-adapter.service.mts +366 -0
  76. package/src/services/index.mts +1 -0
  77. package/src/services/module-loader.service.mts +1 -0
  78. package/src/tokens/adapter.token.mts +6 -0
  79. package/src/tokens/http-adapter.token.mts +1 -1
  80. package/src/tokens/index.mts +1 -0
  81. package/src/utils/adapter-supports.util.mts +47 -0
  82. package/src/utils/index.mts +1 -0
  83. package/lib/index-BJjk2X1S.d.mts.map +0 -1
  84. package/lib/index-DZ6NU03y.d.cts.map +0 -1
  85. package/lib/src-C46ePe3d.cjs +0 -8022
  86. package/lib/src-C46ePe3d.cjs.map +0 -1
  87. package/lib/src-K2k0riYJ.mjs +0 -7587
  88. package/lib/src-K2k0riYJ.mjs.map +0 -1
  89. package/lib/use-guards.decorator-B6tghdxM.cjs.map +0 -1
  90. package/lib/use-guards.decorator-Be_QUx6b.mjs.map +0 -1
  91. package/src/legacy-compat/context-compat.mts +0 -95
  92. package/src/legacy-compat/decorators/factory.decorator.mts +0 -37
  93. package/src/legacy-compat/decorators/injectable.decorator.mts +0 -41
@@ -0,0 +1,300 @@
1
+ import type { ControllerMetadata, HandlerMetadata, ModuleMetadata } from '../metadata/index.mjs'
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+ import { z } from 'zod/v4'
5
+
6
+ import { AttributeFactory } from '../attribute.factory.mjs'
7
+
8
+ // Helper to create mock metadata objects
9
+ const createMockMetadata = (
10
+ attributes: Map<symbol, any> = new Map(),
11
+ ): ModuleMetadata | ControllerMetadata | HandlerMetadata<any> => ({
12
+ customAttributes: attributes,
13
+ } as any)
14
+
15
+ describe('AttributeFactory', () => {
16
+ describe('createAttribute', () => {
17
+ it('should create a simple attribute without schema', () => {
18
+ const token = Symbol.for('TestAttribute')
19
+ const attribute = AttributeFactory.createAttribute(token)
20
+
21
+ expect(attribute.token).toBe(token)
22
+ expect(typeof attribute).toBe('function')
23
+ })
24
+
25
+ it('should create an attribute with schema', () => {
26
+ const token = Symbol.for('SchemaAttribute')
27
+ const schema = z.object({ value: z.number() })
28
+ const attribute = AttributeFactory.createAttribute(token, schema)
29
+
30
+ expect(attribute.token).toBe(token)
31
+ expect(attribute.schema).toBe(schema)
32
+ expect(typeof attribute).toBe('function')
33
+ })
34
+ })
35
+
36
+ describe('get', () => {
37
+ it('should return true for simple attribute when present', () => {
38
+ const token = Symbol.for('SimpleAttribute')
39
+ const attribute = AttributeFactory.createAttribute(token)
40
+
41
+ const metadata = createMockMetadata(new Map([[token, true]]))
42
+
43
+ const result = AttributeFactory.get(attribute, metadata)
44
+
45
+ expect(result).toBe(true)
46
+ })
47
+
48
+ it('should return null when attribute is not present', () => {
49
+ const token = Symbol.for('MissingAttribute')
50
+ const attribute = AttributeFactory.createAttribute(token)
51
+
52
+ const metadata = createMockMetadata()
53
+
54
+ const result = AttributeFactory.get(attribute, metadata)
55
+
56
+ expect(result).toBeNull()
57
+ })
58
+
59
+ it('should return validated value for schema attribute', () => {
60
+ const token = Symbol.for('RateLimitAttribute')
61
+ const schema = z.object({ requests: z.number(), window: z.number() })
62
+ const attribute = AttributeFactory.createAttribute(token, schema)
63
+
64
+ const value = { requests: 100, window: 60000 }
65
+ const metadata = createMockMetadata(new Map([[token, value]]))
66
+
67
+ const result = AttributeFactory.get(attribute, metadata)
68
+
69
+ expect(result).toEqual(value)
70
+ })
71
+
72
+ it('should work with different metadata types (module)', () => {
73
+ const token = Symbol.for('ModuleAttribute')
74
+ const attribute = AttributeFactory.createAttribute(token)
75
+
76
+ const moduleMetadata = {
77
+ customAttributes: new Map([[token, true]]),
78
+ providers: [],
79
+ controllers: new Set(),
80
+ } as unknown as ModuleMetadata
81
+
82
+ const result = AttributeFactory.get(attribute, moduleMetadata)
83
+
84
+ expect(result).toBe(true)
85
+ })
86
+
87
+ it('should work with different metadata types (controller)', () => {
88
+ const token = Symbol.for('ControllerAttribute')
89
+ const attribute = AttributeFactory.createAttribute(token)
90
+
91
+ const controllerMetadata = {
92
+ customAttributes: new Map([[token, true]]),
93
+ guards: new Set(),
94
+ } as unknown as ControllerMetadata
95
+
96
+ const result = AttributeFactory.get(attribute, controllerMetadata)
97
+
98
+ expect(result).toBe(true)
99
+ })
100
+
101
+ it('should work with different metadata types (handler)', () => {
102
+ const token = Symbol.for('HandlerAttribute')
103
+ const attribute = AttributeFactory.createAttribute(token)
104
+
105
+ const handlerMetadata = {
106
+ customAttributes: new Map([[token, true]]),
107
+ config: {},
108
+ } as unknown as HandlerMetadata<any>
109
+
110
+ const result = AttributeFactory.get(attribute, handlerMetadata)
111
+
112
+ expect(result).toBe(true)
113
+ })
114
+ })
115
+
116
+ describe('getAll', () => {
117
+ it('should return array of values when attribute is present', () => {
118
+ const token = Symbol.for('TagAttribute')
119
+ const attribute = AttributeFactory.createAttribute(token)
120
+
121
+ // Note: Map only stores unique keys, so getAll with Map will only return one value
122
+ const metadata = createMockMetadata(new Map([[token, true]]))
123
+
124
+ const result = AttributeFactory.getAll(attribute, metadata)
125
+
126
+ expect(result).toEqual([true])
127
+ })
128
+
129
+ it('should return null when attribute is not present', () => {
130
+ const token = Symbol.for('MissingAllAttribute')
131
+ const attribute = AttributeFactory.createAttribute(token)
132
+
133
+ const metadata = createMockMetadata()
134
+
135
+ const result = AttributeFactory.getAll(attribute, metadata)
136
+
137
+ expect(result).toBeNull()
138
+ })
139
+
140
+ it('should return array of schema values', () => {
141
+ const token = Symbol.for('MultiValueAttribute')
142
+ const schema = z.string()
143
+ const attribute = AttributeFactory.createAttribute(token, schema)
144
+
145
+ const metadata = createMockMetadata(new Map([[token, 'value1']]))
146
+
147
+ const result = AttributeFactory.getAll(attribute, metadata)
148
+
149
+ expect(result).toEqual(['value1'])
150
+ })
151
+ })
152
+
153
+ describe('getLast', () => {
154
+ it('should return last value from array of metadata objects', () => {
155
+ const token = Symbol.for('HierarchyAttribute')
156
+ const schema = z.number()
157
+ const attribute = AttributeFactory.createAttribute(token, schema)
158
+
159
+ const moduleMetadata = createMockMetadata(new Map([[token, 100]]))
160
+ const controllerMetadata = createMockMetadata(new Map([[token, 200]]))
161
+ const handlerMetadata = createMockMetadata(new Map([[token, 300]]))
162
+
163
+ const result = AttributeFactory.getLast(attribute, [
164
+ moduleMetadata,
165
+ controllerMetadata,
166
+ handlerMetadata,
167
+ ])
168
+
169
+ // Last one (most specific) should be returned
170
+ expect(result).toBe(300)
171
+ })
172
+
173
+ it('should find value from earlier metadata if later ones are missing', () => {
174
+ const token = Symbol.for('FallbackAttribute')
175
+ const schema = z.number()
176
+ const attribute = AttributeFactory.createAttribute(token, schema)
177
+
178
+ const moduleMetadata = createMockMetadata(new Map([[token, 100]]))
179
+ const controllerMetadata = createMockMetadata() // No attribute
180
+ const handlerMetadata = createMockMetadata() // No attribute
181
+
182
+ const result = AttributeFactory.getLast(attribute, [
183
+ moduleMetadata,
184
+ controllerMetadata,
185
+ handlerMetadata,
186
+ ])
187
+
188
+ expect(result).toBe(100)
189
+ })
190
+
191
+ it('should return null when attribute is not present in any metadata', () => {
192
+ const token = Symbol.for('NowhereAttribute')
193
+ const attribute = AttributeFactory.createAttribute(token)
194
+
195
+ const result = AttributeFactory.getLast(attribute, [
196
+ createMockMetadata(),
197
+ createMockMetadata(),
198
+ createMockMetadata(),
199
+ ])
200
+
201
+ expect(result).toBeNull()
202
+ })
203
+
204
+ it('should return null for empty array', () => {
205
+ const token = Symbol.for('EmptyArrayAttribute')
206
+ const attribute = AttributeFactory.createAttribute(token)
207
+
208
+ const result = AttributeFactory.getLast(attribute, [])
209
+
210
+ expect(result).toBeNull()
211
+ })
212
+
213
+ it('should work with simple (boolean) attributes', () => {
214
+ const token = Symbol.for('PublicAttribute')
215
+ const attribute = AttributeFactory.createAttribute(token)
216
+
217
+ const moduleMetadata = createMockMetadata()
218
+ const controllerMetadata = createMockMetadata(new Map([[token, true]]))
219
+ const handlerMetadata = createMockMetadata()
220
+
221
+ const result = AttributeFactory.getLast(attribute, [
222
+ moduleMetadata,
223
+ controllerMetadata,
224
+ handlerMetadata,
225
+ ])
226
+
227
+ expect(result).toBe(true)
228
+ })
229
+ })
230
+
231
+ describe('has', () => {
232
+ it('should return true when attribute is present', () => {
233
+ const token = Symbol.for('ExistsAttribute')
234
+ const attribute = AttributeFactory.createAttribute(token)
235
+
236
+ const metadata = createMockMetadata(new Map([[token, true]]))
237
+
238
+ const result = AttributeFactory.has(attribute, metadata)
239
+
240
+ expect(result).toBe(true)
241
+ })
242
+
243
+ it('should return false when attribute is not present', () => {
244
+ const token = Symbol.for('NotExistsAttribute')
245
+ const attribute = AttributeFactory.createAttribute(token)
246
+
247
+ const metadata = createMockMetadata()
248
+
249
+ const result = AttributeFactory.has(attribute, metadata)
250
+
251
+ expect(result).toBe(false)
252
+ })
253
+
254
+ it('should return true for schema attribute when present', () => {
255
+ const token = Symbol.for('SchemaExistsAttribute')
256
+ const schema = z.object({ key: z.string() })
257
+ const attribute = AttributeFactory.createAttribute(token, schema)
258
+
259
+ const metadata = createMockMetadata(new Map([[token, { key: 'value' }]]))
260
+
261
+ const result = AttributeFactory.has(attribute, metadata)
262
+
263
+ expect(result).toBe(true)
264
+ })
265
+
266
+ it('should return false for schema attribute when not present', () => {
267
+ const token = Symbol.for('SchemaNotExistsAttribute')
268
+ const schema = z.object({ key: z.string() })
269
+ const attribute = AttributeFactory.createAttribute(token, schema)
270
+
271
+ const metadata = createMockMetadata()
272
+
273
+ const result = AttributeFactory.has(attribute, metadata)
274
+
275
+ expect(result).toBe(false)
276
+ })
277
+ })
278
+
279
+ describe('type safety', () => {
280
+ it('should return correctly typed value for schema attributes', () => {
281
+ const token = Symbol.for('TypedAttribute')
282
+ const schema = z.object({
283
+ requests: z.number(),
284
+ window: z.number(),
285
+ })
286
+ const attribute = AttributeFactory.createAttribute(token, schema)
287
+
288
+ const value = { requests: 100, window: 60000 }
289
+ const metadata = createMockMetadata(new Map([[token, value]]))
290
+
291
+ const result = AttributeFactory.get(attribute, metadata)
292
+
293
+ // TypeScript should infer: { requests: number, window: number } | null
294
+ if (result) {
295
+ expect(typeof result.requests).toBe('number')
296
+ expect(typeof result.window).toBe('number')
297
+ }
298
+ })
299
+ })
300
+ })
@@ -0,0 +1,312 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ import { ConsoleLogger } from '../logger/console-logger.service.mjs'
4
+
5
+ describe('ConsoleLogger', () => {
6
+ let stdoutSpy: ReturnType<typeof vi.spyOn>
7
+ let stderrSpy: ReturnType<typeof vi.spyOn>
8
+
9
+ beforeEach(() => {
10
+ stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true)
11
+ stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true)
12
+ })
13
+
14
+ afterEach(() => {
15
+ stdoutSpy.mockRestore()
16
+ stderrSpy.mockRestore()
17
+ })
18
+
19
+ describe('static create()', () => {
20
+ it('should create a logger instance with default options', () => {
21
+ const logger = ConsoleLogger.create()
22
+
23
+ expect(logger).toBeInstanceOf(ConsoleLogger)
24
+ })
25
+
26
+ it('should create a logger instance with context', () => {
27
+ const logger = ConsoleLogger.create('TestContext')
28
+
29
+ logger.log('test message')
30
+
31
+ expect(stdoutSpy).toHaveBeenCalled()
32
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
33
+ expect(output).toContain('[TestContext]')
34
+ })
35
+
36
+ it('should create a logger instance with options', () => {
37
+ const logger = ConsoleLogger.create({
38
+ showPid: false,
39
+ showPrefix: false,
40
+ })
41
+
42
+ logger.log('test message')
43
+
44
+ expect(stdoutSpy).toHaveBeenCalled()
45
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
46
+ expect(output).not.toContain('[Navios]')
47
+ expect(output).not.toContain(process.pid.toString())
48
+ })
49
+
50
+ it('should create a logger instance with context and options', () => {
51
+ const logger = ConsoleLogger.create('TestContext', {
52
+ showPid: false,
53
+ })
54
+
55
+ logger.log('test message')
56
+
57
+ expect(stdoutSpy).toHaveBeenCalled()
58
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
59
+ expect(output).toContain('[TestContext]')
60
+ expect(output).not.toContain(process.pid.toString())
61
+ })
62
+ })
63
+
64
+ describe('display options', () => {
65
+ describe('showPid', () => {
66
+ it('should show PID by default', () => {
67
+ const logger = ConsoleLogger.create()
68
+
69
+ logger.log('test message')
70
+
71
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
72
+ expect(output).toContain(process.pid.toString())
73
+ })
74
+
75
+ it('should hide PID when showPid is false', () => {
76
+ const logger = ConsoleLogger.create({ showPid: false })
77
+
78
+ logger.log('test message')
79
+
80
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
81
+ expect(output).not.toContain(` ${process.pid} `)
82
+ })
83
+ })
84
+
85
+ describe('showPrefix', () => {
86
+ it('should show prefix by default', () => {
87
+ const logger = ConsoleLogger.create()
88
+
89
+ logger.log('test message')
90
+
91
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
92
+ expect(output).toContain('[Navios]')
93
+ })
94
+
95
+ it('should hide prefix when showPrefix is false', () => {
96
+ const logger = ConsoleLogger.create({ showPrefix: false })
97
+
98
+ logger.log('test message')
99
+
100
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
101
+ expect(output).not.toContain('[Navios]')
102
+ })
103
+
104
+ it('should use custom prefix when provided', () => {
105
+ const logger = ConsoleLogger.create({ prefix: 'MyApp' })
106
+
107
+ logger.log('test message')
108
+
109
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
110
+ expect(output).toContain('[MyApp]')
111
+ })
112
+ })
113
+
114
+ describe('showLogLevel', () => {
115
+ it('should show log level by default', () => {
116
+ const logger = ConsoleLogger.create()
117
+
118
+ logger.log('test message')
119
+
120
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
121
+ expect(output).toContain('LOG')
122
+ })
123
+
124
+ it('should hide log level when showLogLevel is false', () => {
125
+ const logger = ConsoleLogger.create({ showLogLevel: false })
126
+
127
+ logger.log('test message')
128
+
129
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
130
+ expect(output).not.toContain('LOG')
131
+ })
132
+ })
133
+
134
+ describe('showContext', () => {
135
+ it('should show context by default', () => {
136
+ const logger = ConsoleLogger.create('TestContext')
137
+
138
+ logger.log('test message')
139
+
140
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
141
+ expect(output).toContain('[TestContext]')
142
+ })
143
+
144
+ it('should hide context when showContext is false', () => {
145
+ const logger = ConsoleLogger.create('TestContext', { showContext: false })
146
+
147
+ logger.log('test message')
148
+
149
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
150
+ expect(output).not.toContain('[TestContext]')
151
+ })
152
+ })
153
+
154
+ describe('showTimestamp', () => {
155
+ it('should show timestamp by default', () => {
156
+ const logger = ConsoleLogger.create()
157
+
158
+ logger.log('test message')
159
+
160
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
161
+ // Timestamp format includes date separators
162
+ expect(output).toMatch(/\d{2}\/\d{2}\/\d{4}/)
163
+ })
164
+
165
+ it('should hide timestamp when showTimestamp is false', () => {
166
+ const logger = ConsoleLogger.create({ showTimestamp: false })
167
+
168
+ logger.log('test message')
169
+
170
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
171
+ expect(output).not.toMatch(/\d{2}\/\d{2}\/\d{4}/)
172
+ })
173
+ })
174
+
175
+ describe('showTimeDiff', () => {
176
+ it('should not show time diff by default', () => {
177
+ const logger = ConsoleLogger.create()
178
+
179
+ logger.log('first message')
180
+ logger.log('second message')
181
+
182
+ const output = stdoutSpy.mock.calls[1]?.[0] as string
183
+ expect(output).not.toMatch(/\+\d+ms/)
184
+ })
185
+
186
+ it('should show time diff when showTimeDiff is true', () => {
187
+ const logger = ConsoleLogger.create({ showTimeDiff: true })
188
+
189
+ logger.log('first message')
190
+ logger.log('second message')
191
+
192
+ const output = stdoutSpy.mock.calls[1]?.[0] as string
193
+ expect(output).toMatch(/\+\d+ms/)
194
+ })
195
+
196
+ it('should not show time diff on first message even when enabled', () => {
197
+ const logger = ConsoleLogger.create({ showTimeDiff: true })
198
+
199
+ logger.log('first message')
200
+
201
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
202
+ expect(output).not.toMatch(/\+\d+ms/)
203
+ })
204
+ })
205
+
206
+ describe('minimal output for CLI', () => {
207
+ it('should output only message when all display options are disabled', () => {
208
+ const logger = ConsoleLogger.create({
209
+ showPid: false,
210
+ showPrefix: false,
211
+ showLogLevel: false,
212
+ showContext: false,
213
+ showTimestamp: false,
214
+ colors: false,
215
+ })
216
+
217
+ logger.log('hello world')
218
+
219
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
220
+ expect(output).toBe('hello world\n')
221
+ })
222
+ })
223
+ })
224
+
225
+ describe('log levels', () => {
226
+ it('should write to stdout for log level', () => {
227
+ const logger = ConsoleLogger.create()
228
+ logger.log('test')
229
+ expect(stdoutSpy).toHaveBeenCalled()
230
+ })
231
+
232
+ it('should write to stderr for error level', () => {
233
+ const logger = ConsoleLogger.create()
234
+ logger.error('test')
235
+ expect(stderrSpy).toHaveBeenCalled()
236
+ })
237
+
238
+ it('should write to stdout for warn level', () => {
239
+ const logger = ConsoleLogger.create()
240
+ logger.warn('test')
241
+ expect(stdoutSpy).toHaveBeenCalled()
242
+ })
243
+
244
+ it('should write to stdout for debug level', () => {
245
+ const logger = ConsoleLogger.create()
246
+ logger.debug('test')
247
+ expect(stdoutSpy).toHaveBeenCalled()
248
+ })
249
+
250
+ it('should write to stdout for verbose level', () => {
251
+ const logger = ConsoleLogger.create()
252
+ logger.verbose('test')
253
+ expect(stdoutSpy).toHaveBeenCalled()
254
+ })
255
+
256
+ it('should write to stdout for fatal level', () => {
257
+ const logger = ConsoleLogger.create()
258
+ logger.fatal('test')
259
+ expect(stdoutSpy).toHaveBeenCalled()
260
+ })
261
+ })
262
+
263
+ describe('setContext', () => {
264
+ it('should update context', () => {
265
+ const logger = ConsoleLogger.create('InitialContext')
266
+ logger.setContext('NewContext')
267
+
268
+ logger.log('test message')
269
+
270
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
271
+ expect(output).toContain('[NewContext]')
272
+ expect(output).not.toContain('[InitialContext]')
273
+ })
274
+ })
275
+
276
+ describe('resetContext', () => {
277
+ it('should reset to original context', () => {
278
+ const logger = ConsoleLogger.create('OriginalContext')
279
+ logger.setContext('TempContext')
280
+ logger.resetContext()
281
+
282
+ logger.log('test message')
283
+
284
+ const output = stdoutSpy.mock.calls[0]?.[0] as string
285
+ expect(output).toContain('[OriginalContext]')
286
+ })
287
+ })
288
+
289
+ describe('setLogLevels', () => {
290
+ it('should filter messages based on log levels', () => {
291
+ const logger = ConsoleLogger.create()
292
+ logger.setLogLevels(['error'])
293
+
294
+ logger.log('should not appear')
295
+ logger.error('should appear')
296
+
297
+ expect(stdoutSpy).not.toHaveBeenCalled()
298
+ expect(stderrSpy).toHaveBeenCalled()
299
+ })
300
+ })
301
+
302
+ describe('isLevelEnabled', () => {
303
+ it('should return true for enabled levels', () => {
304
+ const logger = ConsoleLogger.create()
305
+ logger.setLogLevels(['log', 'error'])
306
+
307
+ expect(logger.isLevelEnabled('log')).toBe(true)
308
+ expect(logger.isLevelEnabled('error')).toBe(true)
309
+ expect(logger.isLevelEnabled('debug')).toBe(false)
310
+ })
311
+ })
312
+ })