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,338 @@
1
+ // Create mock log function first
2
+ const mockLog = jest.fn()
3
+
4
+ // Mock global Candy first
5
+ const {mockCandy} = require('./__mocks__/globalCandy')
6
+
7
+ // Set up the Log mock before setting global.Candy
8
+ mockCandy.setMock('core', 'Log', {
9
+ init: jest.fn().mockReturnValue({
10
+ log: mockLog,
11
+ error: jest.fn()
12
+ })
13
+ })
14
+
15
+ global.Candy = mockCandy
16
+
17
+ // Mock axios
18
+ jest.mock('axios')
19
+ const axios = require('axios')
20
+
21
+ const Client = require('../../server/src/Client')
22
+
23
+ describe('Client', () => {
24
+ beforeEach(() => {
25
+ jest.clearAllMocks()
26
+
27
+ // Reset config
28
+ mockCandy.setMock('core', 'Config', {
29
+ config: {}
30
+ })
31
+ })
32
+
33
+ describe('authentication', () => {
34
+ it('should authenticate successfully with valid code', async () => {
35
+ // Arrange
36
+ const mockCode = 'valid-auth-code'
37
+ const mockResponse = {
38
+ data: {
39
+ result: {success: true},
40
+ data: {
41
+ token: 'test-token-123',
42
+ secret: 'test-secret-456'
43
+ }
44
+ }
45
+ }
46
+
47
+ const mockConfig = {config: {}}
48
+ mockCandy.setMock('core', 'Config', mockConfig)
49
+ axios.post.mockResolvedValue(mockResponse)
50
+
51
+ // Act
52
+ Client.auth(mockCode)
53
+
54
+ // Wait for promise to resolve
55
+ await new Promise(resolve => setTimeout(resolve, 0))
56
+
57
+ // Assert
58
+ expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
59
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/auth', {code: mockCode})
60
+ expect(mockConfig.config.auth).toEqual({
61
+ token: 'test-token-123',
62
+ secret: 'test-secret-456'
63
+ })
64
+ expect(mockLog).toHaveBeenCalledWith('CandyPack authenticated!')
65
+ })
66
+
67
+ it('should handle authentication failure with error message', async () => {
68
+ // Arrange
69
+ const mockCode = 'invalid-code'
70
+ const mockError = 'Invalid authentication code'
71
+
72
+ axios.post.mockRejectedValue({
73
+ response: {data: mockError}
74
+ })
75
+
76
+ // Act
77
+ Client.auth(mockCode)
78
+
79
+ // Wait for promise to reject
80
+ await new Promise(resolve => setTimeout(resolve, 0))
81
+
82
+ // Assert
83
+ expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
84
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/auth', {code: mockCode})
85
+ expect(mockLog).toHaveBeenCalledWith(mockError)
86
+ })
87
+
88
+ it('should handle authentication failure without error message', async () => {
89
+ // Arrange
90
+ const mockCode = 'invalid-code'
91
+
92
+ axios.post.mockRejectedValue({
93
+ response: {data: null}
94
+ })
95
+
96
+ // Act
97
+ Client.auth(mockCode)
98
+
99
+ // Wait for promise to reject
100
+ await new Promise(resolve => setTimeout(resolve, 0))
101
+
102
+ // Assert
103
+ expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
104
+ expect(mockLog).toHaveBeenCalledWith('CandyPack authentication failed!')
105
+ })
106
+
107
+ it('should handle API response with success false', async () => {
108
+ // Arrange
109
+ const mockCode = 'valid-code'
110
+ const mockResponse = {
111
+ data: {
112
+ result: {
113
+ success: false,
114
+ message: 'Authentication failed on server'
115
+ }
116
+ }
117
+ }
118
+
119
+ axios.post.mockResolvedValue(mockResponse)
120
+
121
+ // Act
122
+ Client.auth(mockCode)
123
+
124
+ // Wait for promise to reject
125
+ await new Promise(resolve => setTimeout(resolve, 0))
126
+
127
+ // Assert
128
+ expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
129
+ expect(mockLog).toHaveBeenCalledWith('Authentication failed on server')
130
+ })
131
+
132
+ it('should store authentication credentials in config', async () => {
133
+ // Arrange
134
+ const mockCode = 'test-code'
135
+ const mockToken = 'stored-token'
136
+ const mockSecret = 'stored-secret'
137
+ const mockResponse = {
138
+ data: {
139
+ result: {success: true},
140
+ data: {
141
+ token: mockToken,
142
+ secret: mockSecret
143
+ }
144
+ }
145
+ }
146
+
147
+ const mockConfig = {config: {existingProp: 'value'}}
148
+ mockCandy.setMock('core', 'Config', mockConfig)
149
+ axios.post.mockResolvedValue(mockResponse)
150
+
151
+ // Act
152
+ Client.auth(mockCode)
153
+
154
+ // Wait for promise to resolve
155
+ await new Promise(resolve => setTimeout(resolve, 0))
156
+
157
+ // Assert
158
+ expect(mockConfig.config.auth).toEqual({
159
+ token: mockToken,
160
+ secret: mockSecret
161
+ })
162
+ // Verify existing config is preserved
163
+ expect(mockConfig.config.existingProp).toBe('value')
164
+ })
165
+ })
166
+
167
+ describe('API communication', () => {
168
+ it('should make successful API call and return data', async () => {
169
+ // Arrange
170
+ const mockAction = 'test-action'
171
+ const mockData = {param1: 'value1', param2: 'value2'}
172
+ const mockResponseData = {result: 'success', info: 'test data'}
173
+ const mockResponse = {
174
+ data: {
175
+ result: {success: true},
176
+ data: mockResponseData
177
+ }
178
+ }
179
+
180
+ axios.post.mockResolvedValue(mockResponse)
181
+
182
+ // Act
183
+ const result = await Client.call(mockAction, mockData)
184
+
185
+ // Assert
186
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/test-action', mockData)
187
+ expect(result).toEqual(mockResponseData)
188
+ })
189
+
190
+ it('should handle API call with different endpoints', async () => {
191
+ // Arrange
192
+ const testCases = [
193
+ {action: 'users', data: {id: 1}},
194
+ {action: 'domains', data: {domain: 'example.com'}},
195
+ {action: 'ssl/renew', data: {cert: 'test.crt'}}
196
+ ]
197
+
198
+ const mockResponseData = {status: 'ok'}
199
+ const mockResponse = {
200
+ data: {
201
+ result: {success: true},
202
+ data: mockResponseData
203
+ }
204
+ }
205
+
206
+ axios.post.mockResolvedValue(mockResponse)
207
+
208
+ // Act & Assert
209
+ for (const testCase of testCases) {
210
+ const result = await Client.call(testCase.action, testCase.data)
211
+ expect(axios.post).toHaveBeenCalledWith(`https://api.candypack.dev/${testCase.action}`, testCase.data)
212
+ expect(result).toEqual(mockResponseData)
213
+ }
214
+ })
215
+
216
+ it('should reject when API response indicates failure', async () => {
217
+ // Arrange
218
+ const mockAction = 'failing-action'
219
+ const mockData = {test: 'data'}
220
+ const mockErrorMessage = 'API operation failed'
221
+ const mockResponse = {
222
+ data: {
223
+ result: {
224
+ success: false,
225
+ message: mockErrorMessage
226
+ }
227
+ }
228
+ }
229
+
230
+ axios.post.mockResolvedValue(mockResponse)
231
+
232
+ // Act & Assert
233
+ await expect(Client.call(mockAction, mockData)).rejects.toBe(mockErrorMessage)
234
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/failing-action', mockData)
235
+ })
236
+
237
+ it('should handle network errors and log response data', async () => {
238
+ // Arrange
239
+ const mockAction = 'network-error'
240
+ const mockData = {test: 'data'}
241
+ const mockErrorData = {error: 'Network timeout', code: 500}
242
+ const mockError = {
243
+ response: {data: mockErrorData}
244
+ }
245
+
246
+ axios.post.mockRejectedValue(mockError)
247
+
248
+ // Act & Assert
249
+ await expect(Client.call(mockAction, mockData)).rejects.toBe(mockErrorData)
250
+ expect(mockLog).toHaveBeenCalledWith(mockErrorData)
251
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/network-error', mockData)
252
+ })
253
+
254
+ it('should handle axios errors without response data', async () => {
255
+ // Arrange
256
+ const mockAction = 'connection-error'
257
+ const mockData = {test: 'data'}
258
+ const mockError = {
259
+ response: {
260
+ data: 'Connection refused'
261
+ }
262
+ }
263
+
264
+ axios.post.mockRejectedValue(mockError)
265
+
266
+ // Act & Assert
267
+ await expect(Client.call(mockAction, mockData)).rejects.toBe('Connection refused')
268
+ expect(mockLog).toHaveBeenCalledWith('Connection refused')
269
+ })
270
+
271
+ it('should format API requests correctly', async () => {
272
+ // Arrange
273
+ const mockAction = 'format-test'
274
+ const mockData = {
275
+ string: 'test',
276
+ number: 123,
277
+ boolean: true,
278
+ object: {nested: 'value'},
279
+ array: [1, 2, 3]
280
+ }
281
+ const mockResponse = {
282
+ data: {
283
+ result: {success: true},
284
+ data: {received: 'ok'}
285
+ }
286
+ }
287
+
288
+ axios.post.mockResolvedValue(mockResponse)
289
+
290
+ // Act
291
+ await Client.call(mockAction, mockData)
292
+
293
+ // Assert
294
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/format-test', mockData)
295
+ expect(axios.post).toHaveBeenCalledTimes(1)
296
+ })
297
+
298
+ it('should handle empty response data', async () => {
299
+ // Arrange
300
+ const mockAction = 'empty-response'
301
+ const mockData = {test: 'data'}
302
+ const mockResponse = {
303
+ data: {
304
+ result: {success: true},
305
+ data: null
306
+ }
307
+ }
308
+
309
+ axios.post.mockResolvedValue(mockResponse)
310
+
311
+ // Act
312
+ const result = await Client.call(mockAction, mockData)
313
+
314
+ // Assert
315
+ expect(result).toBeNull()
316
+ })
317
+
318
+ it('should handle API calls with no data parameter', async () => {
319
+ // Arrange
320
+ const mockAction = 'no-data'
321
+ const mockResponse = {
322
+ data: {
323
+ result: {success: true},
324
+ data: {message: 'success'}
325
+ }
326
+ }
327
+
328
+ axios.post.mockResolvedValue(mockResponse)
329
+
330
+ // Act
331
+ const result = await Client.call(mockAction)
332
+
333
+ // Assert
334
+ expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/no-data', undefined)
335
+ expect(result).toEqual({message: 'success'})
336
+ })
337
+ })
338
+ })