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,647 @@
1
+ /**
2
+ * Comprehensive unit tests for the Api.js module
3
+ * Tests TCP server functionality, authentication, and command routing
4
+ */
5
+
6
+ const {setupGlobalMocks, cleanupGlobalMocks} = require('./__mocks__/testHelpers')
7
+
8
+ // Create mock log function first
9
+ const mockLog = jest.fn()
10
+ const mockError = jest.fn()
11
+
12
+ describe('Api', () => {
13
+ let Api
14
+
15
+ beforeEach(() => {
16
+ setupGlobalMocks()
17
+
18
+ // Set up the Log mock before requiring Api
19
+ const {mockCandy} = require('./__mocks__/globalCandy')
20
+ mockCandy.setMock('core', 'Log', {
21
+ init: jest.fn().mockReturnValue({
22
+ log: mockLog,
23
+ error: mockError
24
+ })
25
+ })
26
+
27
+ // Mock the net module at the module level
28
+ jest.doMock('net', () => ({
29
+ createServer: jest.fn(() => ({
30
+ on: jest.fn(),
31
+ listen: jest.fn()
32
+ }))
33
+ }))
34
+
35
+ // Mock the crypto module at the module level
36
+ jest.doMock('crypto', () => ({
37
+ randomBytes: jest.fn(() => Buffer.from('mock-auth-token-32-bytes-long-test'))
38
+ }))
39
+
40
+ // Clear module cache and require Api
41
+ jest.resetModules()
42
+ Api = require('../../server/src/Api')
43
+ })
44
+
45
+ afterEach(() => {
46
+ cleanupGlobalMocks()
47
+ jest.resetModules()
48
+ jest.dontMock('net')
49
+ jest.dontMock('crypto')
50
+ })
51
+
52
+ describe('initialization', () => {
53
+ it('should initialize api config if not exists', () => {
54
+ // Clear the api config
55
+ global.Candy.core('Config').config.api = undefined
56
+
57
+ Api.init()
58
+
59
+ expect(global.Candy.core('Config').config.api).toBeDefined()
60
+ expect(global.Candy.core('Config').config.api.auth).toBeDefined()
61
+ })
62
+
63
+ it('should create TCP server and set up handlers', () => {
64
+ const net = require('net')
65
+ const mockServer = {
66
+ on: jest.fn(),
67
+ listen: jest.fn()
68
+ }
69
+ net.createServer.mockReturnValue(mockServer)
70
+
71
+ Api.init()
72
+
73
+ expect(net.createServer).toHaveBeenCalled()
74
+ expect(mockServer.listen).toHaveBeenCalledWith(1453)
75
+ expect(mockServer.on).toHaveBeenCalledWith('connection', expect.any(Function))
76
+ })
77
+
78
+ it('should generate auth token', () => {
79
+ const crypto = require('crypto')
80
+
81
+ Api.init()
82
+
83
+ expect(crypto.randomBytes).toHaveBeenCalledWith(32)
84
+ })
85
+ })
86
+
87
+ describe('connection handling', () => {
88
+ let mockServer
89
+ let connectionHandler
90
+
91
+ beforeEach(() => {
92
+ const net = require('net')
93
+ mockServer = {
94
+ on: jest.fn(),
95
+ listen: jest.fn()
96
+ }
97
+ net.createServer.mockReturnValue(mockServer)
98
+
99
+ Api.init()
100
+
101
+ // Get the connection handler
102
+ const connectionCall = mockServer.on.mock.calls.find(call => call[0] === 'connection')
103
+ connectionHandler = connectionCall ? connectionCall[1] : null
104
+ })
105
+
106
+ it('should accept connections from localhost IPv4', () => {
107
+ if (!connectionHandler) {
108
+ fail('Connection handler not found')
109
+ return
110
+ }
111
+
112
+ const mockSocket = {
113
+ remoteAddress: '127.0.0.1',
114
+ on: jest.fn(),
115
+ destroy: jest.fn()
116
+ }
117
+
118
+ connectionHandler(mockSocket)
119
+
120
+ expect(mockSocket.destroy).not.toHaveBeenCalled()
121
+ expect(mockSocket.on).toHaveBeenCalledWith('data', expect.any(Function))
122
+ expect(mockSocket.on).toHaveBeenCalledWith('close', expect.any(Function))
123
+ })
124
+
125
+ it('should accept connections from localhost IPv6', () => {
126
+ if (!connectionHandler) {
127
+ fail('Connection handler not found')
128
+ return
129
+ }
130
+
131
+ const mockSocket = {
132
+ remoteAddress: '::ffff:127.0.0.1',
133
+ on: jest.fn(),
134
+ destroy: jest.fn()
135
+ }
136
+
137
+ connectionHandler(mockSocket)
138
+
139
+ expect(mockSocket.destroy).not.toHaveBeenCalled()
140
+ expect(mockSocket.on).toHaveBeenCalledWith('data', expect.any(Function))
141
+ expect(mockSocket.on).toHaveBeenCalledWith('close', expect.any(Function))
142
+ })
143
+
144
+ it('should reject connections from non-localhost addresses', () => {
145
+ if (!connectionHandler) {
146
+ fail('Connection handler not found')
147
+ return
148
+ }
149
+
150
+ const mockSocket = {
151
+ remoteAddress: '192.168.1.100',
152
+ on: jest.fn(),
153
+ destroy: jest.fn()
154
+ }
155
+
156
+ connectionHandler(mockSocket)
157
+
158
+ expect(mockSocket.destroy).toHaveBeenCalled()
159
+ expect(mockSocket.on).not.toHaveBeenCalled()
160
+ })
161
+
162
+ it('should reject connections from external IPv6 addresses', () => {
163
+ if (!connectionHandler) {
164
+ fail('Connection handler not found')
165
+ return
166
+ }
167
+
168
+ const mockSocket = {
169
+ remoteAddress: '::ffff:192.168.1.100',
170
+ on: jest.fn(),
171
+ destroy: jest.fn()
172
+ }
173
+
174
+ connectionHandler(mockSocket)
175
+
176
+ expect(mockSocket.destroy).toHaveBeenCalled()
177
+ expect(mockSocket.on).not.toHaveBeenCalled()
178
+ })
179
+
180
+ it('should clean up connection on close', () => {
181
+ if (!connectionHandler) {
182
+ fail('Connection handler not found')
183
+ return
184
+ }
185
+
186
+ const mockSocket = {
187
+ remoteAddress: '127.0.0.1',
188
+ on: jest.fn(),
189
+ destroy: jest.fn()
190
+ }
191
+
192
+ connectionHandler(mockSocket)
193
+
194
+ // Get the close handler
195
+ const closeCall = mockSocket.on.mock.calls.find(call => call[0] === 'close')
196
+ expect(closeCall).toBeDefined()
197
+
198
+ const closeHandler = closeCall[1]
199
+
200
+ // Simulate connection close
201
+ closeHandler()
202
+
203
+ // Connection should be cleaned up (tested indirectly through no errors)
204
+ expect(closeHandler).toBeDefined()
205
+ })
206
+ })
207
+
208
+ describe('data processing and authentication', () => {
209
+ let mockServer
210
+ let connectionHandler
211
+ let dataHandler
212
+ let mockSocket
213
+
214
+ beforeEach(() => {
215
+ const net = require('net')
216
+ mockServer = {
217
+ on: jest.fn(),
218
+ listen: jest.fn()
219
+ }
220
+ net.createServer.mockReturnValue(mockServer)
221
+
222
+ Api.init()
223
+
224
+ // Get the connection handler
225
+ const connectionCall = mockServer.on.mock.calls.find(call => call[0] === 'connection')
226
+ connectionHandler = connectionCall ? connectionCall[1] : null
227
+
228
+ if (!connectionHandler) {
229
+ fail('Connection handler not found')
230
+ return
231
+ }
232
+
233
+ // Set up a localhost connection
234
+ mockSocket = {
235
+ remoteAddress: '127.0.0.1',
236
+ on: jest.fn(),
237
+ write: jest.fn(),
238
+ destroy: jest.fn()
239
+ }
240
+
241
+ connectionHandler(mockSocket)
242
+
243
+ // Get the data handler
244
+ const dataCall = mockSocket.on.mock.calls.find(call => call[0] === 'data')
245
+ dataHandler = dataCall ? dataCall[1] : null
246
+ })
247
+
248
+ it('should return invalid_json error for malformed JSON', async () => {
249
+ if (!dataHandler) {
250
+ fail('Data handler not found')
251
+ return
252
+ }
253
+
254
+ const invalidJson = Buffer.from('invalid json data')
255
+
256
+ await dataHandler(invalidJson)
257
+
258
+ expect(mockSocket.write).toHaveBeenCalledWith(JSON.stringify(Api.result(false, 'invalid_json')))
259
+ })
260
+
261
+ it('should return unauthorized error for missing auth token', async () => {
262
+ if (!dataHandler) {
263
+ fail('Data handler not found')
264
+ return
265
+ }
266
+
267
+ const payload = JSON.stringify({
268
+ action: 'mail.list',
269
+ data: []
270
+ })
271
+
272
+ await dataHandler(Buffer.from(payload))
273
+
274
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":false'))
275
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"message":"unauthorized"'))
276
+ })
277
+
278
+ it('should return unauthorized error for invalid auth token', async () => {
279
+ if (!dataHandler) {
280
+ fail('Data handler not found')
281
+ return
282
+ }
283
+
284
+ const payload = JSON.stringify({
285
+ auth: 'invalid-token',
286
+ action: 'mail.list',
287
+ data: []
288
+ })
289
+
290
+ await dataHandler(Buffer.from(payload))
291
+
292
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":false'))
293
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"message":"unauthorized"'))
294
+ })
295
+
296
+ it('should return unknown_action error for invalid action', async () => {
297
+ if (!dataHandler) {
298
+ fail('Data handler not found')
299
+ return
300
+ }
301
+
302
+ const payload = JSON.stringify({
303
+ auth: global.Candy.core('Config').config.api.auth,
304
+ action: 'invalid.action',
305
+ data: []
306
+ })
307
+
308
+ await dataHandler(Buffer.from(payload))
309
+
310
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":false'))
311
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"message":"unknown_action"'))
312
+ })
313
+
314
+ it('should return unknown_action error for missing action', async () => {
315
+ if (!dataHandler) {
316
+ fail('Data handler not found')
317
+ return
318
+ }
319
+
320
+ const payload = JSON.stringify({
321
+ auth: global.Candy.core('Config').config.api.auth,
322
+ data: []
323
+ })
324
+
325
+ await dataHandler(Buffer.from(payload))
326
+
327
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":false'))
328
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"message":"unknown_action"'))
329
+ })
330
+
331
+ it('should execute valid mail.create command', async () => {
332
+ if (!dataHandler) {
333
+ fail('Data handler not found')
334
+ return
335
+ }
336
+
337
+ // Mock the Mail service
338
+ const mockMailService = global.Candy.server('Mail')
339
+ mockMailService.create.mockResolvedValue(Api.result(true, 'Account created'))
340
+
341
+ const payload = JSON.stringify({
342
+ auth: global.Candy.core('Config').config.api.auth,
343
+ action: 'mail.create',
344
+ data: ['test@example.com', 'password123']
345
+ })
346
+
347
+ await dataHandler(Buffer.from(payload))
348
+
349
+ expect(mockMailService.create).toHaveBeenCalledWith('test@example.com', 'password123', expect.any(Function))
350
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":true'))
351
+ expect(mockSocket.destroy).toHaveBeenCalled()
352
+ })
353
+
354
+ it('should execute valid service.start command', async () => {
355
+ if (!dataHandler) {
356
+ fail('Data handler not found')
357
+ return
358
+ }
359
+
360
+ const mockServiceService = global.Candy.server('Service')
361
+ mockServiceService.start.mockResolvedValue(Api.result(true, 'Service started'))
362
+
363
+ const payload = JSON.stringify({
364
+ auth: global.Candy.core('Config').config.api.auth,
365
+ action: 'service.start',
366
+ data: ['my-service.js']
367
+ })
368
+
369
+ await dataHandler(Buffer.from(payload))
370
+
371
+ expect(mockServiceService.start).toHaveBeenCalledWith('my-service.js', expect.any(Function))
372
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":true'))
373
+ expect(mockSocket.destroy).toHaveBeenCalled()
374
+ })
375
+
376
+ it('should execute valid server.stop command', async () => {
377
+ if (!dataHandler) {
378
+ fail('Data handler not found')
379
+ return
380
+ }
381
+
382
+ const mockServerService = global.Candy.server('Server')
383
+ mockServerService.stop.mockResolvedValue(Api.result(true, 'Server stopped'))
384
+
385
+ const payload = JSON.stringify({
386
+ auth: global.Candy.core('Config').config.api.auth,
387
+ action: 'server.stop',
388
+ data: []
389
+ })
390
+
391
+ await dataHandler(Buffer.from(payload))
392
+
393
+ expect(mockServerService.stop).toHaveBeenCalledWith()
394
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":true'))
395
+ expect(mockSocket.destroy).toHaveBeenCalled()
396
+ })
397
+
398
+ it('should handle command execution errors', async () => {
399
+ if (!dataHandler) {
400
+ fail('Data handler not found')
401
+ return
402
+ }
403
+
404
+ const mockMailService = global.Candy.server('Mail')
405
+ mockMailService.create.mockRejectedValue(new Error('Database connection failed'))
406
+
407
+ const payload = JSON.stringify({
408
+ auth: global.Candy.core('Config').config.api.auth,
409
+ action: 'mail.create',
410
+ data: ['test@example.com', 'password123']
411
+ })
412
+
413
+ await dataHandler(Buffer.from(payload))
414
+
415
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":false'))
416
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"message":"Database connection failed"'))
417
+ expect(mockSocket.destroy).toHaveBeenCalled()
418
+ })
419
+
420
+ it('should handle commands with no data parameter', async () => {
421
+ if (!dataHandler) {
422
+ fail('Data handler not found')
423
+ return
424
+ }
425
+
426
+ const mockMailService = global.Candy.server('Mail')
427
+ mockMailService.list.mockResolvedValue(Api.result(true, []))
428
+
429
+ const payload = JSON.stringify({
430
+ auth: global.Candy.core('Config').config.api.auth,
431
+ action: 'mail.list'
432
+ // No data parameter
433
+ })
434
+
435
+ await dataHandler(Buffer.from(payload))
436
+
437
+ expect(mockMailService.list).toHaveBeenCalledWith(expect.any(Function))
438
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":true'))
439
+ expect(mockSocket.destroy).toHaveBeenCalled()
440
+ })
441
+
442
+ it('should execute all mail commands', async () => {
443
+ if (!dataHandler) {
444
+ fail('Data handler not found')
445
+ return
446
+ }
447
+
448
+ const mockMailService = global.Candy.server('Mail')
449
+ mockMailService.delete.mockResolvedValue(Api.result(true, 'Deleted'))
450
+ mockMailService.password.mockResolvedValue(Api.result(true, 'Password changed'))
451
+ mockMailService.send.mockResolvedValue(Api.result(true, 'Email sent'))
452
+
453
+ // Test mail.delete
454
+ let payload = JSON.stringify({
455
+ auth: global.Candy.core('Config').config.api.auth,
456
+ action: 'mail.delete',
457
+ data: ['test@example.com']
458
+ })
459
+
460
+ await dataHandler(Buffer.from(payload))
461
+ expect(mockMailService.delete).toHaveBeenCalledWith('test@example.com', expect.any(Function))
462
+
463
+ // Test mail.password
464
+ payload = JSON.stringify({
465
+ auth: global.Candy.core('Config').config.api.auth,
466
+ action: 'mail.password',
467
+ data: ['test@example.com', 'newpassword']
468
+ })
469
+
470
+ await dataHandler(Buffer.from(payload))
471
+ expect(mockMailService.password).toHaveBeenCalledWith('test@example.com', 'newpassword', expect.any(Function))
472
+
473
+ // Test mail.send
474
+ payload = JSON.stringify({
475
+ auth: global.Candy.core('Config').config.api.auth,
476
+ action: 'mail.send',
477
+ data: ['test@example.com', 'Subject', 'Body']
478
+ })
479
+
480
+ await dataHandler(Buffer.from(payload))
481
+ expect(mockMailService.send).toHaveBeenCalledWith('test@example.com', 'Subject', 'Body', expect.any(Function))
482
+ })
483
+
484
+ it('should execute all subdomain commands', async () => {
485
+ if (!dataHandler) {
486
+ fail('Data handler not found')
487
+ return
488
+ }
489
+
490
+ const mockSubdomainService = global.Candy.server('Subdomain')
491
+ mockSubdomainService.create.mockResolvedValue(Api.result(true, 'Created'))
492
+ mockSubdomainService.delete.mockResolvedValue(Api.result(true, 'Deleted'))
493
+ mockSubdomainService.list.mockResolvedValue(Api.result(true, ['www', 'api']))
494
+
495
+ // Test subdomain.create
496
+ let payload = JSON.stringify({
497
+ auth: global.Candy.core('Config').config.api.auth,
498
+ action: 'subdomain.create',
499
+ data: ['api.example.com']
500
+ })
501
+
502
+ await dataHandler(Buffer.from(payload))
503
+ expect(mockSubdomainService.create).toHaveBeenCalledWith('api.example.com', expect.any(Function))
504
+
505
+ // Test subdomain.delete
506
+ payload = JSON.stringify({
507
+ auth: global.Candy.core('Config').config.api.auth,
508
+ action: 'subdomain.delete',
509
+ data: ['api.example.com']
510
+ })
511
+
512
+ await dataHandler(Buffer.from(payload))
513
+ expect(mockSubdomainService.delete).toHaveBeenCalledWith('api.example.com', expect.any(Function))
514
+
515
+ // Test subdomain.list
516
+ payload = JSON.stringify({
517
+ auth: global.Candy.core('Config').config.api.auth,
518
+ action: 'subdomain.list',
519
+ data: ['example.com']
520
+ })
521
+
522
+ await dataHandler(Buffer.from(payload))
523
+ expect(mockSubdomainService.list).toHaveBeenCalledWith('example.com', expect.any(Function))
524
+ })
525
+
526
+ it('should execute all web commands', async () => {
527
+ if (!dataHandler) {
528
+ fail('Data handler not found')
529
+ return
530
+ }
531
+
532
+ const mockWebService = global.Candy.server('Web')
533
+ mockWebService.create.mockResolvedValue(Api.result(true, 'Created'))
534
+ mockWebService.delete.mockResolvedValue(Api.result(true, 'Deleted'))
535
+ mockWebService.list.mockResolvedValue(Api.result(true, ['example.com']))
536
+
537
+ // Test web.create
538
+ let payload = JSON.stringify({
539
+ auth: global.Candy.core('Config').config.api.auth,
540
+ action: 'web.create',
541
+ data: ['example.com']
542
+ })
543
+
544
+ await dataHandler(Buffer.from(payload))
545
+ expect(mockWebService.create).toHaveBeenCalledWith('example.com', expect.any(Function))
546
+
547
+ // Test web.delete
548
+ payload = JSON.stringify({
549
+ auth: global.Candy.core('Config').config.api.auth,
550
+ action: 'web.delete',
551
+ data: ['example.com']
552
+ })
553
+
554
+ await dataHandler(Buffer.from(payload))
555
+ expect(mockWebService.delete).toHaveBeenCalledWith('example.com', expect.any(Function))
556
+
557
+ // Test web.list
558
+ payload = JSON.stringify({
559
+ auth: global.Candy.core('Config').config.api.auth,
560
+ action: 'web.list',
561
+ data: []
562
+ })
563
+
564
+ await dataHandler(Buffer.from(payload))
565
+ expect(mockWebService.list).toHaveBeenCalledWith(expect.any(Function))
566
+ })
567
+
568
+ it('should execute ssl.renew command', async () => {
569
+ if (!dataHandler) {
570
+ fail('Data handler not found')
571
+ return
572
+ }
573
+
574
+ const mockSSLService = global.Candy.server('SSL')
575
+ mockSSLService.renew.mockResolvedValue(Api.result(true, 'SSL renewed'))
576
+
577
+ const payload = JSON.stringify({
578
+ auth: global.Candy.core('Config').config.api.auth,
579
+ action: 'ssl.renew',
580
+ data: ['example.com']
581
+ })
582
+
583
+ await dataHandler(Buffer.from(payload))
584
+
585
+ expect(mockSSLService.renew).toHaveBeenCalledWith('example.com', expect.any(Function))
586
+ expect(mockSocket.write).toHaveBeenCalledWith(expect.stringContaining('"result":true'))
587
+ expect(mockSocket.destroy).toHaveBeenCalled()
588
+ })
589
+ })
590
+
591
+ describe('utility methods', () => {
592
+ it('should format result correctly', () => {
593
+ const successResult = Api.result(true, 'Operation successful')
594
+ expect(successResult).toEqual({
595
+ result: true,
596
+ message: 'Operation successful'
597
+ })
598
+
599
+ const errorResult = Api.result(false, 'Operation failed')
600
+ expect(errorResult).toEqual({
601
+ result: false,
602
+ message: 'Operation failed'
603
+ })
604
+ })
605
+
606
+ it('should handle send to non-existent connection gracefully', () => {
607
+ Api.init()
608
+
609
+ // Try to send to non-existent connection
610
+ const result = Api.send('non-existent-id', 'test-process', 'running', 'Test message')
611
+
612
+ // Should not throw and should return undefined
613
+ expect(result).toBeUndefined()
614
+ })
615
+
616
+ it('should send messages to active connections', () => {
617
+ const net = require('net')
618
+ const mockServer = {
619
+ on: jest.fn(),
620
+ listen: jest.fn()
621
+ }
622
+ net.createServer.mockReturnValue(mockServer)
623
+
624
+ Api.init()
625
+
626
+ // Set up a connection
627
+ const connectionCall = mockServer.on.mock.calls.find(call => call[0] === 'connection')
628
+ const connectionHandler = connectionCall[1]
629
+
630
+ const mockSocket = {
631
+ remoteAddress: '127.0.0.1',
632
+ on: jest.fn(),
633
+ write: jest.fn(),
634
+ destroy: jest.fn()
635
+ }
636
+
637
+ connectionHandler(mockSocket)
638
+
639
+ // The send method exists and can be called
640
+ expect(Api.send).toBeDefined()
641
+ expect(typeof Api.send).toBe('function')
642
+
643
+ // Test that it doesn't throw when called
644
+ expect(() => Api.send('test-id', 'test-process', 'running', 'Test message')).not.toThrow()
645
+ })
646
+ })
647
+ })