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,538 +0,0 @@
1
- // Mock the Odac global before requiring Commands
2
- global.Odac = {
3
- cli: jest.fn(),
4
- core: jest.fn()
5
- }
6
-
7
- // Mock the global __ function
8
- global.__ = jest.fn(key => key)
9
-
10
- const Commands = require('../../core/Commands')
11
-
12
- describe('Commands', () => {
13
- let mockCli, mockConnector, mockMonitor
14
-
15
- beforeEach(() => {
16
- mockCli = {
17
- parseArg: jest.fn(),
18
- question: jest.fn(),
19
- help: jest.fn(),
20
- boot: jest.fn()
21
- }
22
-
23
- mockConnector = {
24
- call: jest.fn()
25
- }
26
-
27
- mockMonitor = {
28
- debug: jest.fn(),
29
- monit: jest.fn()
30
- }
31
-
32
- global.Odac.cli.mockImplementation(name => {
33
- switch (name) {
34
- case 'Cli':
35
- return mockCli
36
- case 'Connector':
37
- return mockConnector
38
- case 'Monitor':
39
- return mockMonitor
40
- default:
41
- return {}
42
- }
43
- })
44
-
45
- global.Odac.core.mockImplementation(name => {
46
- if (name === 'Lang') {
47
- return {get: key => key}
48
- }
49
- return {}
50
- })
51
-
52
- // Clear all mocks
53
- jest.clearAllMocks()
54
- })
55
-
56
- describe('command structure', () => {
57
- it('should have all expected top-level commands', () => {
58
- expect(Commands.auth).toBeDefined()
59
- expect(Commands.debug).toBeDefined()
60
- expect(Commands.help).toBeDefined()
61
- expect(Commands.monit).toBeDefined()
62
- expect(Commands.restart).toBeDefined()
63
- expect(Commands.run).toBeDefined()
64
- expect(Commands.mail).toBeDefined()
65
- expect(Commands.ssl).toBeDefined()
66
- expect(Commands.subdomain).toBeDefined()
67
- expect(Commands.web).toBeDefined()
68
- })
69
-
70
- it('should have correct command descriptions', () => {
71
- expect(Commands.auth.description).toBe('Define your server to your Odac account')
72
- expect(Commands.debug.description).toBe('Debug Odac Server')
73
- expect(Commands.help.description).toBe('List all available commands')
74
- expect(Commands.monit.description).toBe('Monitor Website or Service')
75
- expect(Commands.restart.description).toBe('Restart Odac Server')
76
- expect(Commands.run.description).toBe('Add a new Service')
77
- })
78
- })
79
-
80
- describe('auth command', () => {
81
- it('should use provided key argument', async () => {
82
- const args = ['test-key']
83
- mockCli.parseArg.mockReturnValue('test-key')
84
-
85
- await Commands.auth.action(args)
86
-
87
- expect(mockConnector.call).toHaveBeenCalledWith({
88
- action: 'auth',
89
- data: ['test-key']
90
- })
91
- })
92
-
93
- it('should prompt for key if not provided', async () => {
94
- const args = []
95
- mockCli.parseArg.mockReturnValue(null)
96
- mockCli.question.mockResolvedValue('prompted-key')
97
-
98
- await Commands.auth.action(args)
99
-
100
- expect(mockCli.question).toHaveBeenCalledWith('Enter your authentication key: ')
101
- expect(mockConnector.call).toHaveBeenCalledWith({
102
- action: 'auth',
103
- data: ['prompted-key']
104
- })
105
- })
106
- })
107
-
108
- describe('debug command', () => {
109
- it('should call monitor debug', async () => {
110
- await Commands.debug.action()
111
- expect(mockMonitor.debug).toHaveBeenCalled()
112
- })
113
- })
114
-
115
- describe('help command', () => {
116
- it('should call cli help', async () => {
117
- await Commands.help.action()
118
- expect(mockCli.help).toHaveBeenCalled()
119
- })
120
- })
121
-
122
- describe('monit command', () => {
123
- it('should call monitor monit', async () => {
124
- await Commands.monit.action()
125
- expect(mockMonitor.monit).toHaveBeenCalled()
126
- })
127
- })
128
-
129
- describe('restart command', () => {
130
- it('should call cli boot', async () => {
131
- await Commands.restart.action()
132
- expect(mockCli.boot).toHaveBeenCalled()
133
- })
134
- })
135
-
136
- describe('run command', () => {
137
- it('should handle absolute path', async () => {
138
- const args = ['/absolute/path/service.js']
139
-
140
- await Commands.run.action(args)
141
-
142
- expect(mockConnector.call).toHaveBeenCalledWith({
143
- action: 'service.start',
144
- data: ['/absolute/path/service.js']
145
- })
146
- })
147
-
148
- it('should convert relative path to absolute', async () => {
149
- const args = ['relative/service.js']
150
-
151
- await Commands.run.action(args)
152
-
153
- expect(mockConnector.call).toHaveBeenCalledWith({
154
- action: 'service.start',
155
- data: [expect.stringContaining('relative/service.js')]
156
- })
157
- })
158
- })
159
-
160
- describe('mail commands', () => {
161
- describe('create', () => {
162
- it('should create mail account with provided credentials', async () => {
163
- const args = []
164
- mockCli.parseArg.mockImplementation((args, flags) => {
165
- if (flags.includes('--email')) return 'test@example.com'
166
- if (flags.includes('--password')) return 'password123'
167
- return null
168
- })
169
-
170
- await Commands.mail.sub.create.action(args)
171
-
172
- expect(mockConnector.call).toHaveBeenCalledWith({
173
- action: 'mail.create',
174
- data: ['test@example.com', 'password123', 'password123']
175
- })
176
- })
177
-
178
- it('should prompt for missing credentials', async () => {
179
- const args = []
180
- mockCli.parseArg.mockReturnValue(null)
181
- mockCli.question.mockImplementation(prompt => {
182
- if (prompt.includes('e-mail')) return Promise.resolve('prompted@example.com')
183
- if (prompt.includes('password')) return Promise.resolve('prompted-pass')
184
- return Promise.resolve('')
185
- })
186
-
187
- await Commands.mail.sub.create.action(args)
188
-
189
- expect(mockCli.question).toHaveBeenCalledWith('Enter the e-mail address: ')
190
- expect(mockCli.question).toHaveBeenCalledWith('Enter the password: ')
191
- expect(mockCli.question).toHaveBeenCalledWith('Re-enter the password: ')
192
- })
193
-
194
- it('should prompt for email when not provided via flag', async () => {
195
- const args = []
196
- mockCli.parseArg.mockImplementation((args, flags) => {
197
- if (flags.includes('--password')) return 'password123'
198
- return null
199
- })
200
- mockCli.question.mockResolvedValue('prompted@example.com')
201
-
202
- await Commands.mail.sub.create.action(args)
203
-
204
- expect(mockCli.question).toHaveBeenCalledWith('Enter the e-mail address: ')
205
- })
206
-
207
- it('should prompt for password when not provided via flag', async () => {
208
- const args = []
209
- mockCli.parseArg.mockImplementation((args, flags) => {
210
- if (flags.includes('--email')) return 'test@example.com'
211
- return null
212
- })
213
- mockCli.question.mockImplementation(prompt => {
214
- if (prompt.includes('Enter the password:')) return Promise.resolve('prompted-pass')
215
- if (prompt.includes('Re-enter the password:')) return Promise.resolve('prompted-pass')
216
- return Promise.resolve('')
217
- })
218
-
219
- await Commands.mail.sub.create.action(args)
220
-
221
- expect(mockCli.question).toHaveBeenCalledWith('Enter the password: ')
222
- expect(mockCli.question).toHaveBeenCalledWith('Re-enter the password: ')
223
- })
224
- })
225
-
226
- describe('delete', () => {
227
- it('should delete mail account', async () => {
228
- const args = []
229
- mockCli.parseArg.mockReturnValue('delete@example.com')
230
-
231
- await Commands.mail.sub.delete.action(args)
232
-
233
- expect(mockConnector.call).toHaveBeenCalledWith({
234
- action: 'mail.delete',
235
- data: ['delete@example.com']
236
- })
237
- })
238
-
239
- it('should prompt for email when not provided', async () => {
240
- const args = []
241
- mockCli.parseArg.mockReturnValue(null)
242
- mockCli.question.mockResolvedValue('prompted@example.com')
243
-
244
- await Commands.mail.sub.delete.action(args)
245
-
246
- expect(mockCli.question).toHaveBeenCalledWith('Enter the e-mail address: ')
247
- })
248
- })
249
-
250
- describe('list', () => {
251
- it('should list mail accounts for domain', async () => {
252
- const args = []
253
- mockCli.parseArg.mockReturnValue('example.com')
254
-
255
- await Commands.mail.sub.list.action(args)
256
-
257
- expect(mockConnector.call).toHaveBeenCalledWith({
258
- action: 'mail.list',
259
- data: ['example.com']
260
- })
261
- })
262
-
263
- it('should prompt for domain when not provided', async () => {
264
- const args = []
265
- mockCli.parseArg.mockReturnValue(null)
266
- mockCli.question.mockResolvedValue('example.com')
267
-
268
- await Commands.mail.sub.list.action(args)
269
-
270
- expect(mockCli.question).toHaveBeenCalledWith('Enter the domain name: ')
271
- })
272
- })
273
-
274
- describe('password', () => {
275
- it('should change mail account password with provided credentials', async () => {
276
- const args = []
277
- mockCli.parseArg.mockImplementation((args, flags) => {
278
- if (flags.includes('--email')) return 'test@example.com'
279
- if (flags.includes('--password')) return 'newpass123'
280
- return null
281
- })
282
-
283
- await Commands.mail.sub.password.action(args)
284
-
285
- expect(mockConnector.call).toHaveBeenCalledWith({
286
- action: 'mail.password',
287
- data: ['test@example.com', 'newpass123', 'newpass123']
288
- })
289
- })
290
-
291
- it('should prompt for missing email and password', async () => {
292
- const args = []
293
- mockCli.parseArg.mockReturnValue(null)
294
- mockCli.question.mockImplementation(prompt => {
295
- if (prompt.includes('e-mail')) return Promise.resolve('test@example.com')
296
- if (prompt.includes('new password:')) return Promise.resolve('newpass')
297
- if (prompt.includes('Re-enter')) return Promise.resolve('newpass')
298
- return Promise.resolve('')
299
- })
300
-
301
- await Commands.mail.sub.password.action(args)
302
-
303
- expect(mockCli.question).toHaveBeenCalledWith('Enter the e-mail address: ')
304
- expect(mockCli.question).toHaveBeenCalledWith('Enter the new password: ')
305
- expect(mockCli.question).toHaveBeenCalledWith('Re-enter the new password: ')
306
- })
307
- })
308
- })
309
-
310
- describe('ssl commands', () => {
311
- describe('renew', () => {
312
- it('should renew SSL certificate', async () => {
313
- const args = []
314
- mockCli.parseArg.mockReturnValue('example.com')
315
-
316
- await Commands.ssl.sub.renew.action(args)
317
-
318
- expect(mockConnector.call).toHaveBeenCalledWith({
319
- action: 'ssl.renew',
320
- data: ['example.com']
321
- })
322
- })
323
-
324
- it('should prompt for domain when not provided', async () => {
325
- const args = []
326
- mockCli.parseArg.mockReturnValue(null)
327
- mockCli.question.mockResolvedValue('example.com')
328
-
329
- await Commands.ssl.sub.renew.action(args)
330
-
331
- expect(mockCli.question).toHaveBeenCalledWith('Enter the domain name: ')
332
- })
333
- })
334
- })
335
-
336
- describe('subdomain commands', () => {
337
- describe('create', () => {
338
- it('should create subdomain', async () => {
339
- const args = []
340
- mockCli.parseArg.mockReturnValue('sub.example.com')
341
-
342
- await Commands.subdomain.sub.create.action(args)
343
-
344
- expect(mockConnector.call).toHaveBeenCalledWith({
345
- action: 'subdomain.create',
346
- data: ['sub.example.com']
347
- })
348
- })
349
-
350
- it('should prompt for subdomain when not provided', async () => {
351
- const args = []
352
- mockCli.parseArg.mockReturnValue(null)
353
- mockCli.question.mockResolvedValue('sub.example.com')
354
-
355
- await Commands.subdomain.sub.create.action(args)
356
-
357
- expect(mockCli.question).toHaveBeenCalledWith('Enter the subdomain name (subdomain.example.com): ')
358
- })
359
- })
360
-
361
- describe('delete', () => {
362
- it('should delete subdomain', async () => {
363
- mockCli.question.mockResolvedValue('sub.example.com')
364
-
365
- await Commands.subdomain.sub.delete.action()
366
-
367
- expect(mockCli.question).toHaveBeenCalledWith('Enter the subdomain name (subdomain.example.com): ')
368
- expect(mockConnector.call).toHaveBeenCalledWith({
369
- action: 'subdomain.delete',
370
- data: ['sub.example.com']
371
- })
372
- })
373
-
374
- it('should prompt for subdomain when not provided', async () => {
375
- const args = []
376
- mockCli.parseArg.mockReturnValue(null)
377
- mockCli.question.mockResolvedValue('sub.example.com')
378
-
379
- await Commands.subdomain.sub.delete.action(args)
380
-
381
- expect(mockCli.question).toHaveBeenCalledWith('Enter the subdomain name (subdomain.example.com): ')
382
- })
383
- })
384
-
385
- describe('list', () => {
386
- it('should list subdomains for domain', async () => {
387
- const args = []
388
- mockCli.parseArg.mockReturnValue('example.com')
389
-
390
- await Commands.subdomain.sub.list.action(args)
391
-
392
- expect(mockConnector.call).toHaveBeenCalledWith({
393
- action: 'subdomain.list',
394
- data: ['example.com']
395
- })
396
- })
397
-
398
- it('should prompt for domain when not provided', async () => {
399
- const args = []
400
- mockCli.parseArg.mockReturnValue(null)
401
- mockCli.question.mockResolvedValue('example.com')
402
-
403
- await Commands.subdomain.sub.list.action(args)
404
-
405
- expect(mockCli.question).toHaveBeenCalledWith('Enter the domain name: ')
406
- })
407
- })
408
- })
409
-
410
- describe('web commands', () => {
411
- describe('create', () => {
412
- it('should create website', async () => {
413
- const args = []
414
- mockCli.parseArg.mockReturnValue('newsite.com')
415
-
416
- await Commands.web.sub.create.action(args)
417
-
418
- expect(mockConnector.call).toHaveBeenCalledWith({
419
- action: 'web.create',
420
- data: ['newsite.com']
421
- })
422
- })
423
-
424
- it('should prompt for domain when not provided', async () => {
425
- const args = []
426
- mockCli.parseArg.mockReturnValue(null)
427
- mockCli.question.mockResolvedValue('newsite.com')
428
-
429
- await Commands.web.sub.create.action(args)
430
-
431
- expect(mockCli.question).toHaveBeenCalledWith('Enter the domain name: ')
432
- })
433
- })
434
-
435
- describe('delete', () => {
436
- it('should delete website', async () => {
437
- const args = []
438
- mockCli.parseArg.mockReturnValue('oldsite.com')
439
-
440
- await Commands.web.sub.delete.action(args)
441
-
442
- expect(mockConnector.call).toHaveBeenCalledWith({
443
- action: 'web.delete',
444
- data: ['oldsite.com']
445
- })
446
- })
447
-
448
- it('should prompt for domain when not provided', async () => {
449
- const args = []
450
- mockCli.parseArg.mockReturnValue(null)
451
- mockCli.question.mockResolvedValue('oldsite.com')
452
-
453
- await Commands.web.sub.delete.action(args)
454
-
455
- expect(mockCli.question).toHaveBeenCalledWith('Enter the domain name: ')
456
- })
457
- })
458
-
459
- describe('list', () => {
460
- it('should list all websites', async () => {
461
- await Commands.web.sub.list.action()
462
-
463
- expect(mockConnector.call).toHaveBeenCalledWith({
464
- action: 'web.list'
465
- })
466
- })
467
- })
468
- })
469
-
470
- describe('command structure validation', () => {
471
- it('should have correct mail command structure', () => {
472
- expect(Commands.mail.title).toBe('MAIL')
473
- expect(Commands.mail.sub).toBeDefined()
474
- expect(Commands.mail.sub.create).toBeDefined()
475
- expect(Commands.mail.sub.delete).toBeDefined()
476
- expect(Commands.mail.sub.list).toBeDefined()
477
- expect(Commands.mail.sub.password).toBeDefined()
478
- })
479
-
480
- it('should have correct ssl command structure', () => {
481
- expect(Commands.ssl.title).toBe('SSL')
482
- expect(Commands.ssl.sub).toBeDefined()
483
- expect(Commands.ssl.sub.renew).toBeDefined()
484
- })
485
-
486
- it('should have correct subdomain command structure', () => {
487
- expect(Commands.subdomain.title).toBe('SUBDOMAIN')
488
- expect(Commands.subdomain.sub).toBeDefined()
489
- expect(Commands.subdomain.sub.create).toBeDefined()
490
- expect(Commands.subdomain.sub.delete).toBeDefined()
491
- expect(Commands.subdomain.sub.list).toBeDefined()
492
- })
493
-
494
- it('should have correct web command structure', () => {
495
- expect(Commands.web.title).toBe('WEBSITE')
496
- expect(Commands.web.sub).toBeDefined()
497
- expect(Commands.web.sub.create).toBeDefined()
498
- expect(Commands.web.sub.delete).toBeDefined()
499
- expect(Commands.web.sub.list).toBeDefined()
500
- })
501
- })
502
-
503
- describe('auth command edge cases', () => {
504
- it('should use key from args[0] when parseArg returns null', async () => {
505
- const args = ['direct-key']
506
- mockCli.parseArg.mockReturnValue(null)
507
-
508
- await Commands.auth.action(args)
509
-
510
- expect(mockConnector.call).toHaveBeenCalledWith({
511
- action: 'auth',
512
- data: ['direct-key']
513
- })
514
- })
515
-
516
- it('should handle Windows path format in run command', async () => {
517
- const args = ['C:\\Windows\\service.js']
518
-
519
- await Commands.run.action(args)
520
-
521
- expect(mockConnector.call).toHaveBeenCalledWith({
522
- action: 'service.start',
523
- data: ['C:\\Windows\\service.js']
524
- })
525
- })
526
-
527
- it('should handle UNC path format in run command', async () => {
528
- const args = ['\\\\server\\share\\service.js']
529
-
530
- await Commands.run.action(args)
531
-
532
- expect(mockConnector.call).toHaveBeenCalledWith({
533
- action: 'service.start',
534
- data: ['\\\\server\\share\\service.js']
535
- })
536
- })
537
- })
538
- })