odac 1.0.1 → 1.2.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 (143) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +33 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/auto-pr-description.yml +3 -1
  6. package/.github/workflows/release.yml +42 -1
  7. package/.github/workflows/test-coverage.yml +6 -5
  8. package/.github/workflows/test-publish.yml +36 -0
  9. package/.husky/pre-commit +10 -0
  10. package/.husky/pre-push +13 -0
  11. package/.releaserc.js +3 -3
  12. package/CHANGELOG.md +184 -0
  13. package/README.md +53 -34
  14. package/bin/odac.js +181 -49
  15. package/client/odac.js +878 -995
  16. package/docs/backend/01-overview/03-development-server.md +39 -46
  17. package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
  18. package/docs/backend/03-config/00-configuration-overview.md +15 -6
  19. package/docs/backend/03-config/01-database-connection.md +3 -3
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  21. package/docs/backend/03-config/03-request-timeout.md +1 -1
  22. package/docs/backend/03-config/04-environment-variables.md +4 -4
  23. package/docs/backend/03-config/05-early-hints.md +2 -2
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  26. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  27. package/docs/backend/04-routing/09-websocket.md +29 -0
  28. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  29. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
  30. package/docs/backend/05-controllers/03-controller-classes.md +61 -55
  31. package/docs/backend/05-forms/01-custom-forms.md +103 -95
  32. package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
  33. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  34. package/docs/backend/07-views/02-rendering-a-view.md +1 -1
  35. package/docs/backend/07-views/03-variables.md +5 -5
  36. package/docs/backend/07-views/04-request-data.md +1 -1
  37. package/docs/backend/07-views/08-backend-javascript.md +1 -1
  38. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  39. package/docs/backend/08-database/01-getting-started.md +100 -0
  40. package/docs/backend/08-database/02-basics.md +136 -0
  41. package/docs/backend/08-database/03-advanced.md +84 -0
  42. package/docs/backend/08-database/04-migrations.md +48 -0
  43. package/docs/backend/09-validation/01-the-validator-service.md +1 -0
  44. package/docs/backend/10-authentication/03-register.md +9 -2
  45. package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
  46. package/docs/backend/10-authentication/05-session-management.md +16 -2
  47. package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
  48. package/docs/backend/10-authentication/07-magic-links.md +134 -0
  49. package/docs/backend/11-mail/01-the-mail-service.md +118 -28
  50. package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
  51. package/docs/backend/13-utilities/01-odac-var.md +7 -7
  52. package/docs/backend/13-utilities/02-ipc.md +73 -0
  53. package/docs/frontend/01-overview/01-introduction.md +5 -1
  54. package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
  55. package/docs/index.json +21 -125
  56. package/eslint.config.mjs +5 -47
  57. package/jest.config.js +1 -1
  58. package/package.json +16 -7
  59. package/src/Auth.js +414 -121
  60. package/src/Config.js +12 -7
  61. package/src/Database.js +188 -0
  62. package/src/Env.js +3 -1
  63. package/src/Ipc.js +337 -0
  64. package/src/Lang.js +9 -2
  65. package/src/Mail.js +408 -37
  66. package/src/Odac.js +105 -40
  67. package/src/Request.js +71 -49
  68. package/src/Route/Cron.js +62 -18
  69. package/src/Route/Internal.js +215 -12
  70. package/src/Route/Middleware.js +7 -2
  71. package/src/Route.js +372 -109
  72. package/src/Server.js +118 -12
  73. package/src/Storage.js +169 -0
  74. package/src/Token.js +6 -4
  75. package/src/Validator.js +95 -3
  76. package/src/Var.js +22 -6
  77. package/src/View/EarlyHints.js +43 -33
  78. package/src/View/Form.js +210 -28
  79. package/src/View.js +108 -7
  80. package/src/WebSocket.js +18 -3
  81. package/template/odac.json +5 -0
  82. package/template/package.json +3 -1
  83. package/template/route/www.js +12 -10
  84. package/template/view/content/home.html +3 -3
  85. package/template/view/head/main.html +2 -2
  86. package/test/Client.test.js +168 -0
  87. package/test/Config.test.js +112 -0
  88. package/test/Lang.test.js +92 -0
  89. package/test/Odac.test.js +86 -0
  90. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  91. package/test/{framework/Route.test.js → Route.test.js} +1 -1
  92. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  93. package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
  94. package/test/scripts/check-coverage.js +4 -4
  95. package/docs/backend/08-database/01-database-connection.md +0 -99
  96. package/docs/backend/08-database/02-using-mysql.md +0 -322
  97. package/src/Mysql.js +0 -575
  98. package/template/config.json +0 -5
  99. package/test/cli/Cli.test.js +0 -36
  100. package/test/core/Candy.test.js +0 -234
  101. package/test/core/Commands.test.js +0 -538
  102. package/test/core/Config.test.js +0 -1432
  103. package/test/core/Lang.test.js +0 -250
  104. package/test/core/Process.test.js +0 -156
  105. package/test/server/Api.test.js +0 -647
  106. package/test/server/DNS.test.js +0 -2050
  107. package/test/server/DNS.test.js.bak +0 -2084
  108. package/test/server/Hub.test.js +0 -497
  109. package/test/server/Log.test.js +0 -73
  110. package/test/server/Mail.account.test_.js +0 -460
  111. package/test/server/Mail.init.test_.js +0 -411
  112. package/test/server/Mail.test_.js +0 -1340
  113. package/test/server/SSL.test_.js +0 -1491
  114. package/test/server/Server.test.js +0 -765
  115. package/test/server/Service.test_.js +0 -1127
  116. package/test/server/Subdomain.test.js +0 -440
  117. package/test/server/Web/Firewall.test.js +0 -175
  118. package/test/server/Web/Proxy.test.js +0 -397
  119. package/test/server/Web.test.js +0 -1494
  120. package/test/server/__mocks__/acme-client.js +0 -17
  121. package/test/server/__mocks__/bcrypt.js +0 -50
  122. package/test/server/__mocks__/child_process.js +0 -389
  123. package/test/server/__mocks__/crypto.js +0 -432
  124. package/test/server/__mocks__/fs.js +0 -450
  125. package/test/server/__mocks__/globalOdac.js +0 -227
  126. package/test/server/__mocks__/http.js +0 -575
  127. package/test/server/__mocks__/https.js +0 -272
  128. package/test/server/__mocks__/index.js +0 -249
  129. package/test/server/__mocks__/mail/server.js +0 -100
  130. package/test/server/__mocks__/mail/smtp.js +0 -31
  131. package/test/server/__mocks__/mailparser.js +0 -81
  132. package/test/server/__mocks__/net.js +0 -369
  133. package/test/server/__mocks__/node-forge.js +0 -328
  134. package/test/server/__mocks__/os.js +0 -320
  135. package/test/server/__mocks__/path.js +0 -291
  136. package/test/server/__mocks__/selfsigned.js +0 -8
  137. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  138. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  139. package/test/server/__mocks__/smtp-server.js +0 -106
  140. package/test/server/__mocks__/sqlite3.js +0 -394
  141. package/test/server/__mocks__/testFactories.js +0 -299
  142. package/test/server/__mocks__/testHelpers.js +0 -363
  143. package/test/server/__mocks__/tls.js +0 -229
@@ -1,497 +0,0 @@
1
- const mockLog = jest.fn()
2
- const mockError = jest.fn()
3
-
4
- const {mockOdac} = require('./__mocks__/globalOdac')
5
-
6
- mockOdac.setMock('core', 'Log', {
7
- init: jest.fn().mockReturnValue({
8
- log: mockLog,
9
- error: mockError
10
- })
11
- })
12
-
13
- global.Odac = mockOdac
14
-
15
- jest.mock('axios')
16
- const axios = require('axios')
17
-
18
- jest.mock('ws')
19
-
20
- jest.mock('os')
21
- const os = require('os')
22
-
23
- jest.mock('fs')
24
- const fs = require('fs')
25
-
26
- jest.useFakeTimers()
27
-
28
- describe('Hub', () => {
29
- let Hub
30
-
31
- beforeEach(() => {
32
- jest.clearAllMocks()
33
- jest.clearAllTimers()
34
-
35
- mockOdac.setMock('core', 'Config', {
36
- config: {
37
- hub: null,
38
- server: {started: Date.now()},
39
- websites: {},
40
- services: [],
41
- mail: {accounts: {}}
42
- }
43
- })
44
-
45
- mockOdac.setMock('server', 'Api', {
46
- result: jest.fn((success, message) => ({success, message}))
47
- })
48
-
49
- os.hostname.mockReturnValue('test-host')
50
- os.platform.mockReturnValue('linux')
51
- os.arch.mockReturnValue('x64')
52
- os.totalmem.mockReturnValue(8589934592)
53
- os.freemem.mockReturnValue(4294967296)
54
- os.cpus.mockReturnValue([
55
- {times: {user: 1000, nice: 0, sys: 500, idle: 8500, irq: 0}},
56
- {times: {user: 1000, nice: 0, sys: 500, idle: 8500, irq: 0}}
57
- ])
58
-
59
- jest.isolateModules(() => {
60
- Hub = require('../../server/src/Hub')
61
- })
62
- })
63
-
64
- afterEach(() => {
65
- if (Hub.websocket) {
66
- Hub.websocket = null
67
- }
68
- Hub.checkCounter = 0
69
- })
70
-
71
- describe('initialization', () => {
72
- it('should initialize with default values', () => {
73
- expect(Hub.websocket).toBeNull()
74
- expect(Hub.websocketReconnectAttempts).toBe(0)
75
- expect(Hub.maxReconnectAttempts).toBe(5)
76
- expect(Hub.checkCounter).toBe(0)
77
- })
78
- })
79
-
80
- describe('check counter', () => {
81
- it('should increment counter on each check', () => {
82
- expect(Hub.checkCounter).toBe(0)
83
- Hub.check()
84
- expect(Hub.checkCounter).toBe(1)
85
- Hub.check()
86
- expect(Hub.checkCounter).toBe(2)
87
- })
88
-
89
- it('should reset counter after reaching 60', () => {
90
- Hub.checkCounter = 59
91
- Hub.check()
92
- expect(Hub.checkCounter).toBe(0)
93
- })
94
-
95
- it('should skip API call when counter is not 0', async () => {
96
- mockOdac.setMock('core', 'Config', {
97
- config: {
98
- hub: {token: 'test-token', secret: 'test-secret'},
99
- server: {started: Date.now()}
100
- }
101
- })
102
-
103
- Hub.checkCounter = 1
104
- await Hub.check()
105
- expect(axios.post).not.toHaveBeenCalled()
106
- })
107
-
108
- it('should skip API call when websocket is connected', async () => {
109
- mockOdac.setMock('core', 'Config', {
110
- config: {
111
- hub: {token: 'test-token', secret: 'test-secret'},
112
- server: {started: Date.now()}
113
- }
114
- })
115
-
116
- Hub.websocket = {readyState: 1}
117
- Hub.checkCounter = 0
118
- await Hub.check()
119
- expect(axios.post).not.toHaveBeenCalled()
120
- })
121
- })
122
-
123
- describe('check', () => {
124
- beforeEach(() => {
125
- Hub.checkCounter = 0
126
- })
127
-
128
- it('should return early if no hub config', async () => {
129
- mockOdac.setMock('core', 'Config', {
130
- config: {hub: null}
131
- })
132
-
133
- await Hub.check()
134
- expect(axios.post).not.toHaveBeenCalled()
135
- })
136
-
137
- it('should return early if no token', async () => {
138
- mockOdac.setMock('core', 'Config', {
139
- config: {hub: {}}
140
- })
141
-
142
- await Hub.check()
143
- expect(axios.post).not.toHaveBeenCalled()
144
- })
145
-
146
- it('should send status to hub when counter is 0', async () => {
147
- jest.useRealTimers()
148
-
149
- mockOdac.setMock('core', 'Config', {
150
- config: {
151
- hub: {token: 'test-token', secret: 'test-secret'},
152
- server: {started: Date.now()}
153
- }
154
- })
155
-
156
- axios.post.mockResolvedValue({
157
- data: {
158
- result: {success: true},
159
- data: {authenticated: true}
160
- }
161
- })
162
-
163
- Hub.checkCounter = 59
164
- Hub.check()
165
-
166
- await new Promise(resolve => setImmediate(resolve))
167
- expect(axios.post).toHaveBeenCalled()
168
-
169
- jest.useFakeTimers()
170
- })
171
-
172
- it('should handle authentication failure', async () => {
173
- jest.useRealTimers()
174
-
175
- mockOdac.setMock('core', 'Config', {
176
- config: {
177
- hub: {token: 'invalid-token', secret: 'test-secret'},
178
- server: {started: Date.now()}
179
- }
180
- })
181
-
182
- axios.post.mockResolvedValue({
183
- data: {
184
- result: {success: true},
185
- data: {authenticated: false, reason: 'token_invalid'}
186
- }
187
- })
188
-
189
- Hub.checkCounter = 59
190
- Hub.check()
191
-
192
- await new Promise(resolve => setImmediate(resolve))
193
- expect(mockLog).toHaveBeenCalledWith('Server not authenticated: %s', 'token_invalid')
194
-
195
- jest.useFakeTimers()
196
- })
197
-
198
- it('should clear config on invalid token', async () => {
199
- jest.useRealTimers()
200
-
201
- const config = {
202
- hub: {token: 'invalid-token', secret: 'test-secret'},
203
- server: {started: Date.now()}
204
- }
205
- mockOdac.setMock('core', 'Config', {config})
206
-
207
- axios.post.mockResolvedValue({
208
- data: {
209
- result: {success: true},
210
- data: {authenticated: false, reason: 'token_invalid'}
211
- }
212
- })
213
-
214
- Hub.checkCounter = 59
215
- Hub.check()
216
-
217
- await new Promise(resolve => setImmediate(resolve))
218
- expect(config.hub).toBeUndefined()
219
-
220
- jest.useFakeTimers()
221
- })
222
-
223
- it('should handle check errors gracefully', async () => {
224
- jest.useRealTimers()
225
-
226
- mockOdac.setMock('core', 'Config', {
227
- config: {
228
- hub: {token: 'test-token', secret: 'test-secret'},
229
- server: {started: Date.now()}
230
- }
231
- })
232
-
233
- axios.post.mockRejectedValue(new Error('Network error'))
234
-
235
- Hub.checkCounter = 59
236
- Hub.check()
237
-
238
- await new Promise(resolve => setImmediate(resolve))
239
- expect(mockLog).toHaveBeenCalledWith('Failed to report status: %s', 'Network error')
240
-
241
- jest.useFakeTimers()
242
- })
243
- })
244
-
245
- describe('authentication', () => {
246
- it('should authenticate with valid code', async () => {
247
- const mockResponse = {
248
- data: {
249
- result: {success: true},
250
- data: {
251
- token: 'new-token',
252
- secret: 'new-secret'
253
- }
254
- }
255
- }
256
-
257
- const mockApiResult = {success: true, message: 'Authentication successful'}
258
- mockOdac.setMock('server', 'Api', {
259
- result: jest.fn(() => mockApiResult)
260
- })
261
-
262
- axios.post.mockResolvedValue(mockResponse)
263
-
264
- const result = await Hub.auth('valid-code')
265
-
266
- expect(axios.post).toHaveBeenCalled()
267
- expect(result).toEqual(mockApiResult)
268
- expect(mockOdac.core('Config').config.hub).toEqual({
269
- token: 'new-token',
270
- secret: 'new-secret'
271
- })
272
- })
273
-
274
- it('should handle authentication failure', async () => {
275
- const mockApiResult = {success: false, message: 'Authentication failed'}
276
- mockOdac.setMock('server', 'Api', {
277
- result: jest.fn(() => mockApiResult)
278
- })
279
-
280
- axios.post.mockRejectedValue(new Error('Invalid code'))
281
-
282
- const result = await Hub.auth('invalid-code')
283
-
284
- expect(result).toEqual(mockApiResult)
285
- expect(mockLog).toHaveBeenCalledWith('Authentication failed: %s', 'Invalid code')
286
- })
287
-
288
- it('should include distro info on Linux', async () => {
289
- os.platform.mockReturnValue('linux')
290
- fs.readFileSync.mockReturnValue('NAME="Ubuntu"\nVERSION_ID="20.04"\nID=ubuntu')
291
-
292
- axios.post.mockResolvedValue({
293
- data: {
294
- result: {success: true},
295
- data: {token: 'token', secret: 'secret'}
296
- }
297
- })
298
-
299
- await Hub.auth('code')
300
-
301
- const callArgs = axios.post.mock.calls[0][1]
302
- expect(callArgs.distro).toBeDefined()
303
- expect(callArgs.distro.name).toBe('Ubuntu')
304
- })
305
- })
306
-
307
- describe('system status', () => {
308
- it('should get system status', () => {
309
- const status = Hub.getSystemStatus()
310
-
311
- expect(status).toHaveProperty('cpu')
312
- expect(status).toHaveProperty('memory')
313
- expect(status).toHaveProperty('disk')
314
- expect(status).toHaveProperty('network')
315
- expect(status).toHaveProperty('services')
316
- expect(status).toHaveProperty('uptime')
317
- expect(status.hostname).toBe('test-host')
318
- expect(status.platform).toBe('linux')
319
- expect(status.arch).toBe('x64')
320
- })
321
-
322
- it('should get services info', () => {
323
- mockOdac.setMock('core', 'Config', {
324
- config: {
325
- websites: {
326
- 'example.com': {},
327
- 'test.com': {}
328
- },
329
- services: ['web', 'mail'],
330
- mail: {
331
- accounts: {
332
- 'user@example.com': {},
333
- 'admin@test.com': {}
334
- }
335
- }
336
- }
337
- })
338
-
339
- const services = Hub.getServicesInfo()
340
-
341
- expect(services.websites).toBe(2)
342
- expect(services.services).toBe(2)
343
- expect(services.mail).toBe(2)
344
- })
345
-
346
- it('should handle missing services config', () => {
347
- mockOdac.setMock('core', 'Config', {config: {}})
348
-
349
- const services = Hub.getServicesInfo()
350
-
351
- expect(services.websites).toBe(0)
352
- expect(services.services).toBe(0)
353
- expect(services.mail).toBe(0)
354
- })
355
- })
356
-
357
- describe('memory usage', () => {
358
- it('should get memory usage on Linux', () => {
359
- os.platform.mockReturnValue('linux')
360
- os.totalmem.mockReturnValue(8589934592)
361
- os.freemem.mockReturnValue(4294967296)
362
-
363
- const memory = Hub.getMemoryUsage()
364
-
365
- expect(memory.total).toBe(8589934592)
366
- expect(memory.used).toBe(4294967296)
367
- })
368
- })
369
-
370
- describe('CPU usage', () => {
371
- it('should return 0 on first call', () => {
372
- const usage = Hub.getCpuUsage()
373
- expect(usage).toBe(0)
374
- })
375
-
376
- it('should calculate CPU usage on subsequent calls', () => {
377
- Hub.getCpuUsage()
378
-
379
- os.cpus.mockReturnValue([
380
- {times: {user: 2000, nice: 0, sys: 1000, idle: 7000, irq: 0}},
381
- {times: {user: 2000, nice: 0, sys: 1000, idle: 7000, irq: 0}}
382
- ])
383
-
384
- const usage = Hub.getCpuUsage()
385
- expect(usage).toBeGreaterThanOrEqual(0)
386
- expect(usage).toBeLessThanOrEqual(100)
387
- })
388
- })
389
-
390
- describe('request signing', () => {
391
- it('should sign request with secret', () => {
392
- mockOdac.setMock('core', 'Config', {
393
- config: {
394
- hub: {token: 'token', secret: 'test-secret'}
395
- }
396
- })
397
-
398
- const data = {test: 'data'}
399
- const signature = Hub.signRequest(data)
400
-
401
- expect(signature).toBeTruthy()
402
- expect(typeof signature).toBe('string')
403
- })
404
-
405
- it('should return null without secret', () => {
406
- mockOdac.setMock('core', 'Config', {
407
- config: {hub: {token: 'token'}}
408
- })
409
-
410
- const signature = Hub.signRequest({test: 'data'})
411
- expect(signature).toBeNull()
412
- })
413
- })
414
-
415
- describe('API calls', () => {
416
- it('should make successful API call', async () => {
417
- axios.post.mockResolvedValue({
418
- data: {
419
- result: {success: true},
420
- data: {response: 'data'}
421
- }
422
- })
423
-
424
- const result = await Hub.call('test-action', {param: 'value'})
425
-
426
- expect(result).toEqual({response: 'data'})
427
- expect(axios.post).toHaveBeenCalledWith('https://hub.odac.run/test-action', {param: 'value'}, expect.any(Object))
428
- })
429
-
430
- it('should include authorization header when token exists', async () => {
431
- mockOdac.setMock('core', 'Config', {
432
- config: {
433
- hub: {token: 'test-token', secret: 'test-secret'}
434
- }
435
- })
436
-
437
- axios.post.mockResolvedValue({
438
- data: {
439
- result: {success: true},
440
- data: {}
441
- }
442
- })
443
-
444
- await Hub.call('test', {})
445
-
446
- const callArgs = axios.post.mock.calls[0][2]
447
- expect(callArgs.headers.Authorization).toBe('Bearer test-token')
448
- })
449
-
450
- it('should handle API errors', async () => {
451
- axios.post.mockResolvedValue({
452
- data: {
453
- result: {success: false, message: 'API error'}
454
- }
455
- })
456
-
457
- await expect(Hub.call('test', {})).rejects.toBe('API error')
458
- })
459
-
460
- it('should handle network errors', async () => {
461
- axios.post.mockRejectedValue({
462
- response: {status: 500, data: 'Server error'}
463
- })
464
-
465
- await expect(Hub.call('test', {})).rejects.toBe('Server error')
466
- })
467
- })
468
-
469
- describe('Linux distro detection', () => {
470
- it('should return null on non-Linux platforms', () => {
471
- os.platform.mockReturnValue('darwin')
472
- const distro = Hub.getLinuxDistro()
473
- expect(distro).toBeNull()
474
- })
475
-
476
- it('should parse os-release file', () => {
477
- os.platform.mockReturnValue('linux')
478
- fs.readFileSync.mockReturnValue('NAME="Ubuntu"\nVERSION_ID="20.04"\nID=ubuntu')
479
-
480
- const distro = Hub.getLinuxDistro()
481
-
482
- expect(distro.name).toBe('Ubuntu')
483
- expect(distro.version).toBe('20.04')
484
- expect(distro.id).toBe('ubuntu')
485
- })
486
-
487
- it('should handle missing os-release file', () => {
488
- os.platform.mockReturnValue('linux')
489
- fs.readFileSync.mockImplementation(() => {
490
- throw new Error('File not found')
491
- })
492
-
493
- const distro = Hub.getLinuxDistro()
494
- expect(distro).toBeNull()
495
- })
496
- })
497
- })
@@ -1,73 +0,0 @@
1
- const Log = require('../../core/Log')
2
-
3
- describe('Log', () => {
4
- let consoleLogSpy
5
- let consoleErrorSpy
6
-
7
- beforeEach(() => {
8
- consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
9
- consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
10
- })
11
-
12
- afterEach(() => {
13
- consoleLogSpy.mockRestore()
14
- consoleErrorSpy.mockRestore()
15
- })
16
-
17
- it('should initialize with a module prefix', () => {
18
- const log = new Log()
19
- const logger = log.init('TestModule')
20
- logger.log('test message')
21
- expect(consoleLogSpy).toHaveBeenCalledWith('[TestModule] ', 'test message')
22
- })
23
-
24
- it('should handle multiple module prefixes', () => {
25
- const log = new Log()
26
- const logger = log.init('Module1', 'Module2')
27
- logger.log('test message')
28
- expect(consoleLogSpy).toHaveBeenCalledWith('[Module1][Module2] ', 'test message')
29
- })
30
-
31
- it('should log messages correctly', () => {
32
- const log = new Log()
33
- const logger = log.init('Test')
34
- logger.log('message1', 'message2')
35
- expect(consoleLogSpy).toHaveBeenCalledWith('[Test] ', 'message1', 'message2')
36
- })
37
-
38
- it('should log error messages correctly', () => {
39
- const log = new Log()
40
- const logger = log.init('Test')
41
- logger.error('error message')
42
- expect(consoleErrorSpy).toHaveBeenCalledWith('[Test] ', 'error message')
43
- })
44
-
45
- it('should handle %s format specifiers', () => {
46
- const log = new Log()
47
- const logger = log.init('FormatTest')
48
- logger.log('Hello, %s!', 'World')
49
- expect(consoleLogSpy).toHaveBeenCalledWith('[FormatTest] ', 'Hello, World!')
50
- })
51
-
52
- it('should handle multiple %s format specifiers', () => {
53
- const log = new Log()
54
- const logger = log.init('FormatTest')
55
- logger.log('Hello, %s! Welcome, %s.', 'John', 'Jane')
56
- expect(consoleLogSpy).toHaveBeenCalledWith('[FormatTest] ', 'Hello, John! Welcome, Jane.')
57
- })
58
-
59
- it('should handle missing arguments for %s', () => {
60
- const log = new Log()
61
- const logger = log.init('FormatTest')
62
- logger.log('Hello, %s!')
63
- expect(consoleLogSpy).toHaveBeenCalledWith('[FormatTest] ', 'Hello, !')
64
- })
65
-
66
- it('should not log anything if no arguments are provided to log()', () => {
67
- const log = new Log()
68
- const logger = log.init('Test')
69
- const result = logger.log()
70
- expect(consoleLogSpy).not.toHaveBeenCalled()
71
- expect(result).toBeInstanceOf(Log)
72
- })
73
- })