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,450 @@
1
+ /**
2
+ * Mock implementation of the fs module for server tests
3
+ * Provides comprehensive mocking of file system operations
4
+ */
5
+
6
+ const mockFiles = new Map()
7
+ const mockDirectories = new Set()
8
+
9
+ // Helper to simulate file system state
10
+ const resetFileSystem = () => {
11
+ mockFiles.clear()
12
+ mockDirectories.clear()
13
+
14
+ // Add some default directories
15
+ mockDirectories.add('/var/candypack')
16
+ mockDirectories.add('/etc/ssl/private')
17
+ mockDirectories.add('/etc/ssl/certs')
18
+ mockDirectories.add('/home/user/.candypack')
19
+ }
20
+
21
+ // Initialize with default state
22
+ resetFileSystem()
23
+
24
+ const fs = {
25
+ // Synchronous file operations
26
+ readFileSync: jest.fn((path, encoding) => {
27
+ if (!mockFiles.has(path)) {
28
+ const error = new Error(`ENOENT: no such file or directory, open '${path}'`)
29
+ error.code = 'ENOENT'
30
+ error.errno = -2
31
+ error.path = path
32
+ throw error
33
+ }
34
+
35
+ const content = mockFiles.get(path)
36
+ return encoding ? content : Buffer.from(content)
37
+ }),
38
+
39
+ writeFileSync: jest.fn((path, data, options) => {
40
+ const content = typeof data === 'string' ? data : data.toString()
41
+ mockFiles.set(path, content)
42
+ }),
43
+
44
+ appendFileSync: jest.fn((path, data, options) => {
45
+ const content = typeof data === 'string' ? data : data.toString()
46
+ const existing = mockFiles.get(path) || ''
47
+ mockFiles.set(path, existing + content)
48
+ }),
49
+
50
+ existsSync: jest.fn(path => {
51
+ return mockFiles.has(path) || mockDirectories.has(path)
52
+ }),
53
+
54
+ statSync: jest.fn(path => {
55
+ if (!mockFiles.has(path) && !mockDirectories.has(path)) {
56
+ const error = new Error(`ENOENT: no such file or directory, stat '${path}'`)
57
+ error.code = 'ENOENT'
58
+ error.errno = -2
59
+ error.path = path
60
+ throw error
61
+ }
62
+
63
+ const isDirectory = mockDirectories.has(path)
64
+ return {
65
+ isFile: () => !isDirectory,
66
+ isDirectory: () => isDirectory,
67
+ isSymbolicLink: () => false,
68
+ size: isDirectory ? 0 : (mockFiles.get(path) || '').length,
69
+ mtime: new Date(),
70
+ ctime: new Date(),
71
+ atime: new Date(),
72
+ mode: isDirectory ? 16877 : 33188,
73
+ uid: 1000,
74
+ gid: 1000
75
+ }
76
+ }),
77
+
78
+ lstatSync: jest.fn(path => fs.statSync(path)),
79
+
80
+ readdirSync: jest.fn(path => {
81
+ if (!mockDirectories.has(path)) {
82
+ const error = new Error(`ENOENT: no such file or directory, scandir '${path}'`)
83
+ error.code = 'ENOENT'
84
+ error.errno = -2
85
+ error.path = path
86
+ throw error
87
+ }
88
+
89
+ const entries = []
90
+
91
+ // Find files in this directory
92
+ for (const filePath of mockFiles.keys()) {
93
+ if (filePath.startsWith(path + '/')) {
94
+ const relativePath = filePath.substring(path.length + 1)
95
+ if (!relativePath.includes('/')) {
96
+ entries.push(relativePath)
97
+ }
98
+ }
99
+ }
100
+
101
+ // Find subdirectories
102
+ for (const dirPath of mockDirectories) {
103
+ if (dirPath.startsWith(path + '/')) {
104
+ const relativePath = dirPath.substring(path.length + 1)
105
+ if (!relativePath.includes('/')) {
106
+ entries.push(relativePath)
107
+ }
108
+ }
109
+ }
110
+
111
+ return entries
112
+ }),
113
+
114
+ mkdirSync: jest.fn((path, options) => {
115
+ mockDirectories.add(path)
116
+
117
+ // Create parent directories if recursive option is set
118
+ if (options && options.recursive) {
119
+ const parts = path.split('/')
120
+ let currentPath = ''
121
+ for (const part of parts) {
122
+ if (part) {
123
+ currentPath += '/' + part
124
+ mockDirectories.add(currentPath)
125
+ }
126
+ }
127
+ }
128
+ }),
129
+
130
+ rmdirSync: jest.fn((path, options) => {
131
+ mockDirectories.delete(path)
132
+
133
+ // Remove files in directory if recursive
134
+ if (options && options.recursive) {
135
+ for (const filePath of mockFiles.keys()) {
136
+ if (filePath.startsWith(path + '/')) {
137
+ mockFiles.delete(filePath)
138
+ }
139
+ }
140
+
141
+ for (const dirPath of mockDirectories) {
142
+ if (dirPath.startsWith(path + '/')) {
143
+ mockDirectories.delete(dirPath)
144
+ }
145
+ }
146
+ }
147
+ }),
148
+
149
+ unlinkSync: jest.fn(path => {
150
+ if (!mockFiles.has(path)) {
151
+ const error = new Error(`ENOENT: no such file or directory, unlink '${path}'`)
152
+ error.code = 'ENOENT'
153
+ error.errno = -2
154
+ error.path = path
155
+ throw error
156
+ }
157
+ mockFiles.delete(path)
158
+ }),
159
+
160
+ copyFileSync: jest.fn((src, dest) => {
161
+ if (!mockFiles.has(src)) {
162
+ const error = new Error(`ENOENT: no such file or directory, open '${src}'`)
163
+ error.code = 'ENOENT'
164
+ error.errno = -2
165
+ error.path = src
166
+ throw error
167
+ }
168
+ mockFiles.set(dest, mockFiles.get(src))
169
+ }),
170
+
171
+ cpSync: jest.fn((src, dest, options) => {
172
+ // Mock recursive copy operation
173
+ if (mockDirectories.has(src)) {
174
+ // Copy directory
175
+ mockDirectories.add(dest)
176
+
177
+ // Copy all files in the directory
178
+ for (const filePath of mockFiles.keys()) {
179
+ if (filePath.startsWith(src + '/')) {
180
+ const relativePath = filePath.substring(src.length)
181
+ mockFiles.set(dest + relativePath, mockFiles.get(filePath))
182
+ }
183
+ }
184
+
185
+ // Copy all subdirectories
186
+ for (const dirPath of mockDirectories) {
187
+ if (dirPath.startsWith(src + '/')) {
188
+ const relativePath = dirPath.substring(src.length)
189
+ mockDirectories.add(dest + relativePath)
190
+ }
191
+ }
192
+ } else if (mockFiles.has(src)) {
193
+ // Copy single file
194
+ mockFiles.set(dest, mockFiles.get(src))
195
+ }
196
+ }),
197
+
198
+ rmSync: jest.fn((path, options) => {
199
+ if (mockDirectories.has(path)) {
200
+ // Remove directory
201
+ mockDirectories.delete(path)
202
+
203
+ // Remove files in directory if recursive
204
+ if (options && options.recursive) {
205
+ for (const filePath of mockFiles.keys()) {
206
+ if (filePath.startsWith(path + '/')) {
207
+ mockFiles.delete(filePath)
208
+ }
209
+ }
210
+
211
+ for (const dirPath of mockDirectories) {
212
+ if (dirPath.startsWith(path + '/')) {
213
+ mockDirectories.delete(dirPath)
214
+ }
215
+ }
216
+ }
217
+ } else if (mockFiles.has(path)) {
218
+ // Remove single file
219
+ mockFiles.delete(path)
220
+ }
221
+ }),
222
+
223
+ chmodSync: jest.fn((path, mode) => {
224
+ // Mock implementation - just verify file exists
225
+ if (!mockFiles.has(path) && !mockDirectories.has(path)) {
226
+ const error = new Error(`ENOENT: no such file or directory, chmod '${path}'`)
227
+ error.code = 'ENOENT'
228
+ error.errno = -2
229
+ error.path = path
230
+ throw error
231
+ }
232
+ }),
233
+
234
+ // Callback-style asynchronous operations
235
+ readFile: jest.fn((path, encoding, callback) => {
236
+ if (typeof encoding === 'function') {
237
+ callback = encoding
238
+ encoding = 'utf8'
239
+ }
240
+
241
+ setTimeout(() => {
242
+ try {
243
+ const content = fs.readFileSync(path, encoding)
244
+ callback(null, content)
245
+ } catch (error) {
246
+ callback(error)
247
+ }
248
+ }, 0)
249
+ }),
250
+
251
+ writeFile: jest.fn((path, data, encoding, callback) => {
252
+ if (typeof encoding === 'function') {
253
+ callback = encoding
254
+ encoding = 'utf8'
255
+ }
256
+
257
+ setTimeout(() => {
258
+ try {
259
+ fs.writeFileSync(path, data, {encoding})
260
+ callback(null)
261
+ } catch (error) {
262
+ callback(error)
263
+ }
264
+ }, 0)
265
+ }),
266
+
267
+ appendFile: jest.fn((path, data, encoding, callback) => {
268
+ if (typeof encoding === 'function') {
269
+ callback = encoding
270
+ encoding = 'utf8'
271
+ }
272
+
273
+ setTimeout(() => {
274
+ try {
275
+ fs.appendFileSync(path, data, {encoding})
276
+ callback(null)
277
+ } catch (error) {
278
+ callback(error)
279
+ }
280
+ }, 0)
281
+ }),
282
+
283
+ // Asynchronous file operations
284
+ promises: {
285
+ readFile: jest.fn(async (path, encoding) => {
286
+ return fs.readFileSync(path, encoding)
287
+ }),
288
+
289
+ writeFile: jest.fn(async (path, data, options) => {
290
+ return fs.writeFileSync(path, data, options)
291
+ }),
292
+
293
+ appendFile: jest.fn(async (path, data, options) => {
294
+ return fs.appendFileSync(path, data, options)
295
+ }),
296
+
297
+ access: jest.fn(async (path, mode) => {
298
+ if (!mockFiles.has(path) && !mockDirectories.has(path)) {
299
+ const error = new Error(`ENOENT: no such file or directory, access '${path}'`)
300
+ error.code = 'ENOENT'
301
+ error.errno = -2
302
+ error.path = path
303
+ throw error
304
+ }
305
+ }),
306
+
307
+ stat: jest.fn(async path => {
308
+ return fs.statSync(path)
309
+ }),
310
+
311
+ lstat: jest.fn(async path => {
312
+ return fs.lstatSync(path)
313
+ }),
314
+
315
+ readdir: jest.fn(async path => {
316
+ return fs.readdirSync(path)
317
+ }),
318
+
319
+ mkdir: jest.fn(async (path, options) => {
320
+ return fs.mkdirSync(path, options)
321
+ }),
322
+
323
+ rmdir: jest.fn(async (path, options) => {
324
+ return fs.rmdirSync(path, options)
325
+ }),
326
+
327
+ unlink: jest.fn(async path => {
328
+ return fs.unlinkSync(path)
329
+ }),
330
+
331
+ copyFile: jest.fn(async (src, dest) => {
332
+ return fs.copyFileSync(src, dest)
333
+ }),
334
+
335
+ chmod: jest.fn(async (path, mode) => {
336
+ return fs.chmodSync(path, mode)
337
+ })
338
+ },
339
+
340
+ // Stream operations
341
+ createReadStream: jest.fn((path, options) => {
342
+ const {createMockStream} = require('./testHelpers')
343
+ const stream = createMockStream(true, false)
344
+
345
+ // Simulate reading file content
346
+ setTimeout(() => {
347
+ if (mockFiles.has(path)) {
348
+ stream.emit('data', Buffer.from(mockFiles.get(path)))
349
+ stream.emit('end')
350
+ } else {
351
+ const error = new Error(`ENOENT: no such file or directory, open '${path}'`)
352
+ error.code = 'ENOENT'
353
+ stream.emit('error', error)
354
+ }
355
+ }, 0)
356
+
357
+ return stream
358
+ }),
359
+
360
+ createWriteStream: jest.fn((path, options) => {
361
+ const {createMockStream} = require('./testHelpers')
362
+ const stream = createMockStream(false, true)
363
+
364
+ let content = ''
365
+ stream.write.mockImplementation(chunk => {
366
+ content += chunk.toString()
367
+ return true
368
+ })
369
+
370
+ stream.end.mockImplementation(chunk => {
371
+ if (chunk) content += chunk.toString()
372
+ mockFiles.set(path, content)
373
+ stream.emit('finish')
374
+ })
375
+
376
+ return stream
377
+ }),
378
+
379
+ // Watch operations
380
+ watch: jest.fn((filename, options, listener) => {
381
+ const {createMockEventEmitter} = require('./testHelpers')
382
+ const watcher = createMockEventEmitter()
383
+
384
+ watcher.close = jest.fn()
385
+
386
+ if (typeof options === 'function') {
387
+ listener = options
388
+ options = {}
389
+ }
390
+
391
+ if (listener) {
392
+ watcher.on('change', listener)
393
+ }
394
+
395
+ return watcher
396
+ }),
397
+
398
+ watchFile: jest.fn((filename, options, listener) => {
399
+ if (typeof options === 'function') {
400
+ listener = options
401
+ options = {}
402
+ }
403
+
404
+ // Mock implementation - just call listener immediately
405
+ if (listener) {
406
+ setTimeout(() => {
407
+ const stats = fs.statSync(filename)
408
+ listener(stats, stats)
409
+ }, 0)
410
+ }
411
+ }),
412
+
413
+ unwatchFile: jest.fn((filename, listener) => {
414
+ // Mock implementation - no-op
415
+ }),
416
+
417
+ // Constants
418
+ constants: {
419
+ F_OK: 0,
420
+ R_OK: 4,
421
+ W_OK: 2,
422
+ X_OK: 1,
423
+ O_RDONLY: 0,
424
+ O_WRONLY: 1,
425
+ O_RDWR: 2,
426
+ O_CREAT: 64,
427
+ O_EXCL: 128,
428
+ O_TRUNC: 512,
429
+ O_APPEND: 1024
430
+ },
431
+
432
+ // Test helpers
433
+ __setMockFiles: files => {
434
+ mockFiles.clear()
435
+ Object.entries(files).forEach(([path, content]) => {
436
+ mockFiles.set(path, content)
437
+ })
438
+ },
439
+
440
+ __setMockDirectories: directories => {
441
+ mockDirectories.clear()
442
+ directories.forEach(dir => mockDirectories.add(dir))
443
+ },
444
+
445
+ __getMockFiles: () => new Map(mockFiles),
446
+ __getMockDirectories: () => new Set(mockDirectories),
447
+ __resetFileSystem: resetFileSystem
448
+ }
449
+
450
+ module.exports = fs
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Comprehensive mock for the global Candy object used in server tests
3
+ * Provides mocked implementations of core(), server(), cli(), and watchdog() methods
4
+ */
5
+
6
+ class MockCandyPack {
7
+ constructor() {
8
+ this._registry = new Map()
9
+ this._singletons = new Map()
10
+ this._mocks = new Map()
11
+ }
12
+
13
+ // Mock the core method to return mocked core modules
14
+ core(name, singleton = true) {
15
+ const key = `core:${name}`
16
+
17
+ if (this._mocks.has(key)) {
18
+ return this._mocks.get(key)
19
+ }
20
+
21
+ // Default mocks for common core modules
22
+ const coreMocks = {
23
+ Config: {
24
+ config: {
25
+ server: {
26
+ pid: null,
27
+ started: null,
28
+ watchdog: null,
29
+ os: 'linux',
30
+ arch: 'x64'
31
+ }
32
+ },
33
+ force: jest.fn(),
34
+ reload: jest.fn(),
35
+ init: jest.fn()
36
+ },
37
+ Lang: {
38
+ get: jest.fn(key => key),
39
+ init: jest.fn()
40
+ },
41
+ Process: {
42
+ spawn: jest.fn(),
43
+ kill: jest.fn(),
44
+ init: jest.fn()
45
+ },
46
+ Commands: {
47
+ execute: jest.fn(),
48
+ init: jest.fn()
49
+ },
50
+ Log: {
51
+ init: jest.fn(function (moduleName) {
52
+ return {
53
+ log: jest.fn(),
54
+ error: jest.fn()
55
+ }
56
+ })
57
+ }
58
+ }
59
+
60
+ const mock = coreMocks[name] || {
61
+ init: jest.fn()
62
+ }
63
+
64
+ this._mocks.set(key, mock)
65
+ return mock
66
+ }
67
+
68
+ // Mock the server method to return mocked server modules
69
+ server(name, singleton = true) {
70
+ const key = `server:${name}`
71
+
72
+ if (this._mocks.has(key)) {
73
+ return this._mocks.get(key)
74
+ }
75
+
76
+ // Default mocks for server modules
77
+ const serverMocks = {
78
+ Api: {
79
+ init: jest.fn(),
80
+ start: jest.fn(),
81
+ stop: jest.fn(),
82
+ result: jest.fn((success, data) => ({success, data}))
83
+ },
84
+ Client: {
85
+ auth: jest.fn(),
86
+ call: jest.fn(),
87
+ init: jest.fn()
88
+ },
89
+ DNS: {
90
+ init: jest.fn(),
91
+ start: jest.fn(),
92
+ stop: jest.fn(),
93
+ add: jest.fn(),
94
+ delete: jest.fn(),
95
+ list: jest.fn()
96
+ },
97
+ Mail: {
98
+ init: jest.fn(),
99
+ start: jest.fn(),
100
+ stop: jest.fn(),
101
+ create: jest.fn(),
102
+ delete: jest.fn(),
103
+ list: jest.fn(),
104
+ password: jest.fn(),
105
+ send: jest.fn()
106
+ },
107
+ SSL: {
108
+ init: jest.fn(),
109
+ renew: jest.fn(),
110
+ check: jest.fn()
111
+ },
112
+ Server: {
113
+ init: jest.fn(),
114
+ start: jest.fn(),
115
+ stop: jest.fn()
116
+ },
117
+ Service: {
118
+ init: jest.fn(),
119
+ add: jest.fn(),
120
+ start: jest.fn(),
121
+ stop: jest.fn(),
122
+ list: jest.fn(),
123
+ status: jest.fn()
124
+ },
125
+ Subdomain: {
126
+ init: jest.fn(),
127
+ create: jest.fn(),
128
+ delete: jest.fn(),
129
+ list: jest.fn()
130
+ },
131
+ Web: {
132
+ init: jest.fn(),
133
+ start: jest.fn(),
134
+ stop: jest.fn(),
135
+ create: jest.fn(),
136
+ delete: jest.fn(),
137
+ list: jest.fn()
138
+ }
139
+ }
140
+
141
+ const mock = serverMocks[name] || {
142
+ init: jest.fn()
143
+ }
144
+
145
+ this._mocks.set(key, mock)
146
+ return mock
147
+ }
148
+
149
+ // Mock the cli method
150
+ cli(name, singleton = true) {
151
+ const key = `cli:${name}`
152
+
153
+ if (this._mocks.has(key)) {
154
+ return this._mocks.get(key)
155
+ }
156
+
157
+ const mock = {
158
+ init: jest.fn(),
159
+ execute: jest.fn()
160
+ }
161
+
162
+ this._mocks.set(key, mock)
163
+ return mock
164
+ }
165
+
166
+ // Mock the watchdog method
167
+ watchdog(name, singleton = true) {
168
+ const key = `watchdog:${name}`
169
+
170
+ if (this._mocks.has(key)) {
171
+ return this._mocks.get(key)
172
+ }
173
+
174
+ const mock = {
175
+ init: jest.fn(),
176
+ start: jest.fn(),
177
+ stop: jest.fn()
178
+ }
179
+
180
+ this._mocks.set(key, mock)
181
+ return mock
182
+ }
183
+
184
+ // Helper method to set custom mocks
185
+ setMock(type, name, mock) {
186
+ const key = `${type}:${name}`
187
+ this._mocks.set(key, mock)
188
+ }
189
+
190
+ // Helper method to get a mock
191
+ getMock(type, name) {
192
+ const key = `${type}:${name}`
193
+ return this._mocks.get(key)
194
+ }
195
+
196
+ // Helper method to clear all mocks
197
+ clearMocks() {
198
+ this._mocks.forEach(mock => {
199
+ if (mock && typeof mock === 'object') {
200
+ Object.values(mock).forEach(fn => {
201
+ if (jest.isMockFunction(fn)) {
202
+ fn.mockClear()
203
+ }
204
+ })
205
+ }
206
+ })
207
+ }
208
+
209
+ // Helper method to reset all mocks
210
+ resetMocks() {
211
+ this._mocks.clear()
212
+ this._registry.clear()
213
+ this._singletons.clear()
214
+ }
215
+ }
216
+
217
+ // Create the global mock instance
218
+ const mockCandy = new MockCandyPack()
219
+
220
+ // Mock the global __ function
221
+ const mockLangGet = jest.fn(key => key)
222
+
223
+ module.exports = {
224
+ mockCandy,
225
+ mockLangGet,
226
+ MockCandyPack
227
+ }