odac 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 (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. package/web/view/header/main.html +14 -0
@@ -0,0 +1,538 @@
1
+ // Mock the Candy global before requiring Commands
2
+ global.Candy = {
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.Candy.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.Candy.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 CandyPack account')
72
+ expect(Commands.debug.description).toBe('Debug CandyPack 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 CandyPack 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
+ })