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,394 @@
1
+ /**
2
+ * Mock implementation of sqlite3 for server tests
3
+ */
4
+
5
+ const {createMockEventEmitter} = require('./testHelpers')
6
+
7
+ class MockDatabase {
8
+ constructor(filename, mode, callback) {
9
+ Object.assign(this, createMockEventEmitter())
10
+
11
+ if (typeof mode === 'function') {
12
+ callback = mode
13
+ mode = undefined
14
+ }
15
+
16
+ this.filename = filename
17
+ this.mode = mode
18
+ this.open = true
19
+ this.statements = new Map()
20
+ this.data = new Map() // Mock data storage
21
+
22
+ // Simulate async database opening
23
+ setTimeout(() => {
24
+ if (callback) {
25
+ callback(null) // No error
26
+ }
27
+ this.emit('open')
28
+ }, 0)
29
+ }
30
+
31
+ serialize(callback) {
32
+ if (callback) {
33
+ callback()
34
+ }
35
+ return this
36
+ }
37
+
38
+ parallelize(callback) {
39
+ if (callback) {
40
+ callback()
41
+ }
42
+ return this
43
+ }
44
+
45
+ run(sql, params, callback) {
46
+ if (typeof params === 'function') {
47
+ callback = params
48
+ params = []
49
+ }
50
+
51
+ // Mock SQL execution
52
+ const result = {
53
+ lastID: Math.floor(Math.random() * 1000) + 1,
54
+ changes: 1
55
+ }
56
+
57
+ // Store mock data for SELECT queries
58
+ if (sql.includes('INSERT INTO')) {
59
+ const tableName = this.extractTableName(sql)
60
+ if (!this.data.has(tableName)) {
61
+ this.data.set(tableName, [])
62
+ }
63
+ this.data.get(tableName).push({id: result.lastID, ...params})
64
+ }
65
+
66
+ setTimeout(() => {
67
+ if (callback) {
68
+ callback.call(result, null)
69
+ }
70
+ this.emit('run', sql, params)
71
+ }, 0)
72
+
73
+ return this
74
+ }
75
+
76
+ get(sql, params, callback) {
77
+ if (typeof params === 'function') {
78
+ callback = params
79
+ params = []
80
+ }
81
+
82
+ let row = null
83
+
84
+ // Mock data retrieval
85
+ if (sql.includes('SELECT')) {
86
+ const tableName = this.extractTableName(sql)
87
+ const tableData = this.data.get(tableName) || []
88
+
89
+ if (tableData.length > 0) {
90
+ row = tableData[0] // Return first row
91
+ }
92
+ }
93
+
94
+ setTimeout(() => {
95
+ if (callback) {
96
+ callback(null, row)
97
+ }
98
+ this.emit('get', sql, params, row)
99
+ }, 0)
100
+
101
+ return this
102
+ }
103
+
104
+ all(sql, params, callback) {
105
+ if (typeof params === 'function') {
106
+ callback = params
107
+ params = []
108
+ }
109
+
110
+ let rows = []
111
+
112
+ // Mock data retrieval
113
+ if (sql.includes('SELECT')) {
114
+ const tableName = this.extractTableName(sql)
115
+ rows = this.data.get(tableName) || []
116
+ }
117
+
118
+ setTimeout(() => {
119
+ if (callback) {
120
+ callback(null, rows)
121
+ }
122
+ this.emit('all', sql, params, rows)
123
+ }, 0)
124
+
125
+ return this
126
+ }
127
+
128
+ each(sql, params, rowCallback, completeCallback) {
129
+ if (typeof params === 'function') {
130
+ completeCallback = rowCallback
131
+ rowCallback = params
132
+ params = []
133
+ }
134
+
135
+ let rows = []
136
+ let count = 0
137
+
138
+ // Mock data retrieval
139
+ if (sql.includes('SELECT')) {
140
+ const tableName = this.extractTableName(sql)
141
+ rows = this.data.get(tableName) || []
142
+ count = rows.length
143
+ }
144
+
145
+ setTimeout(() => {
146
+ // Call row callback for each row
147
+ rows.forEach(row => {
148
+ if (rowCallback) {
149
+ rowCallback(null, row)
150
+ }
151
+ })
152
+
153
+ // Call complete callback
154
+ if (completeCallback) {
155
+ completeCallback(null, count)
156
+ }
157
+
158
+ this.emit('each', sql, params, count)
159
+ }, 0)
160
+
161
+ return this
162
+ }
163
+
164
+ prepare(sql, params, callback) {
165
+ if (typeof params === 'function') {
166
+ callback = params
167
+ params = []
168
+ }
169
+
170
+ const statement = new MockStatement(sql, params, this)
171
+ const statementId = Math.random().toString(36)
172
+ this.statements.set(statementId, statement)
173
+
174
+ setTimeout(() => {
175
+ if (callback) {
176
+ callback(null, statement)
177
+ }
178
+ this.emit('prepare', sql, params)
179
+ }, 0)
180
+
181
+ return statement
182
+ }
183
+
184
+ exec(sql, callback) {
185
+ setTimeout(() => {
186
+ if (callback) {
187
+ callback(null)
188
+ }
189
+ this.emit('exec', sql)
190
+ }, 0)
191
+
192
+ return this
193
+ }
194
+
195
+ close(callback) {
196
+ this.open = false
197
+
198
+ setTimeout(() => {
199
+ if (callback) {
200
+ callback(null)
201
+ }
202
+ this.emit('close')
203
+ }, 0)
204
+
205
+ return this
206
+ }
207
+
208
+ // Helper method to extract table name from SQL
209
+ extractTableName(sql) {
210
+ const match = sql.match(/(?:FROM|INTO|UPDATE)\s+(\w+)/i)
211
+ return match ? match[1] : 'unknown'
212
+ }
213
+
214
+ // Test helper methods
215
+ setMockData(tableName, data) {
216
+ this.data.set(tableName, data)
217
+ }
218
+
219
+ getMockData(tableName) {
220
+ return this.data.get(tableName) || []
221
+ }
222
+
223
+ clearMockData() {
224
+ this.data.clear()
225
+ }
226
+ }
227
+
228
+ class MockStatement {
229
+ constructor(sql, params, database) {
230
+ Object.assign(this, createMockEventEmitter())
231
+
232
+ this.sql = sql
233
+ this.params = params
234
+ this.database = database
235
+ this.finalized = false
236
+ }
237
+
238
+ bind(params, callback) {
239
+ this.params = Array.isArray(params) ? params : [params]
240
+
241
+ setTimeout(() => {
242
+ if (callback) {
243
+ callback(null)
244
+ }
245
+ this.emit('bind', params)
246
+ }, 0)
247
+
248
+ return this
249
+ }
250
+
251
+ reset(callback) {
252
+ this.params = []
253
+
254
+ setTimeout(() => {
255
+ if (callback) {
256
+ callback(null)
257
+ }
258
+ this.emit('reset')
259
+ }, 0)
260
+
261
+ return this
262
+ }
263
+
264
+ finalize(callback) {
265
+ this.finalized = true
266
+
267
+ setTimeout(() => {
268
+ if (callback) {
269
+ callback(null)
270
+ }
271
+ this.emit('finalize')
272
+ }, 0)
273
+
274
+ return this
275
+ }
276
+
277
+ run(params, callback) {
278
+ if (typeof params === 'function') {
279
+ callback = params
280
+ params = this.params
281
+ }
282
+
283
+ const result = {
284
+ lastID: Math.floor(Math.random() * 1000) + 1,
285
+ changes: 1
286
+ }
287
+
288
+ setTimeout(() => {
289
+ if (callback) {
290
+ callback.call(result, null)
291
+ }
292
+ this.emit('run', params)
293
+ }, 0)
294
+
295
+ return this
296
+ }
297
+
298
+ get(params, callback) {
299
+ if (typeof params === 'function') {
300
+ callback = params
301
+ params = this.params
302
+ }
303
+
304
+ const row = {id: 1, data: 'mock'}
305
+
306
+ setTimeout(() => {
307
+ if (callback) {
308
+ callback(null, row)
309
+ }
310
+ this.emit('get', params, row)
311
+ }, 0)
312
+
313
+ return this
314
+ }
315
+
316
+ all(params, callback) {
317
+ if (typeof params === 'function') {
318
+ callback = params
319
+ params = this.params
320
+ }
321
+
322
+ const rows = [{id: 1, data: 'mock'}]
323
+
324
+ setTimeout(() => {
325
+ if (callback) {
326
+ callback(null, rows)
327
+ }
328
+ this.emit('all', params, rows)
329
+ }, 0)
330
+
331
+ return this
332
+ }
333
+
334
+ each(params, rowCallback, completeCallback) {
335
+ if (typeof params === 'function') {
336
+ completeCallback = rowCallback
337
+ rowCallback = params
338
+ params = this.params
339
+ }
340
+
341
+ const rows = [{id: 1, data: 'mock'}]
342
+
343
+ setTimeout(() => {
344
+ rows.forEach(row => {
345
+ if (rowCallback) {
346
+ rowCallback(null, row)
347
+ }
348
+ })
349
+
350
+ if (completeCallback) {
351
+ completeCallback(null, rows.length)
352
+ }
353
+
354
+ this.emit('each', params, rows.length)
355
+ }, 0)
356
+
357
+ return this
358
+ }
359
+ }
360
+
361
+ // Mock constants
362
+ const OPEN_READONLY = 1
363
+ const OPEN_READWRITE = 2
364
+ const OPEN_CREATE = 4
365
+ const OPEN_FULLMUTEX = 16
366
+ const OPEN_SHAREDCACHE = 32
367
+ const OPEN_PRIVATECACHE = 64
368
+
369
+ const sqlite3 = {
370
+ Database: MockDatabase,
371
+ Statement: MockStatement,
372
+
373
+ // Constants
374
+ OPEN_READONLY,
375
+ OPEN_READWRITE,
376
+ OPEN_CREATE,
377
+ OPEN_FULLMUTEX,
378
+ OPEN_SHAREDCACHE,
379
+ OPEN_PRIVATECACHE,
380
+
381
+ // Verbose mode
382
+ verbose: jest.fn(() => ({
383
+ Database: MockDatabase,
384
+ Statement: MockStatement,
385
+ OPEN_READONLY,
386
+ OPEN_READWRITE,
387
+ OPEN_CREATE,
388
+ OPEN_FULLMUTEX,
389
+ OPEN_SHAREDCACHE,
390
+ OPEN_PRIVATECACHE
391
+ }))
392
+ }
393
+
394
+ module.exports = sqlite3
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Test data factory functions for consistent mock data generation
3
+ * Provides builders for common data structures used across server tests
4
+ */
5
+
6
+ /**
7
+ * Factory for creating mock website configurations
8
+ */
9
+ const createMockWebsiteConfig = (domain = 'example.com', overrides = {}) => {
10
+ return {
11
+ domain,
12
+ path: `/var/candypack/${domain}`,
13
+ subdomain: ['www'],
14
+ cert: {
15
+ ssl: {
16
+ key: `/etc/ssl/private/${domain}.key`,
17
+ cert: `/etc/ssl/certs/${domain}.crt`,
18
+ expiry: Date.now() + 86400000 * 30 // 30 days from now
19
+ }
20
+ },
21
+ DNS: {
22
+ A: [{name: domain, value: '127.0.0.1'}],
23
+ AAAA: [{name: domain, value: '::1'}],
24
+ CNAME: [{name: `www.${domain}`, value: domain}],
25
+ MX: [{name: domain, value: `mail.${domain}`, priority: 10}],
26
+ TXT: [{name: domain, value: 'v=spf1 mx ~all'}],
27
+ NS: [
28
+ {name: domain, value: `ns1.${domain}`},
29
+ {name: domain, value: `ns2.${domain}`}
30
+ ]
31
+ },
32
+ ...overrides
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Factory for creating mock service configurations
38
+ */
39
+ const createMockServiceConfig = (name = 'test-service.js', overrides = {}) => {
40
+ return {
41
+ id: Math.floor(Math.random() * 1000),
42
+ name,
43
+ file: `/path/to/${name}`,
44
+ active: true,
45
+ pid: Math.floor(Math.random() * 10000) + 1000,
46
+ status: 'running',
47
+ started: Date.now(),
48
+ restarts: 0,
49
+ errors: 0,
50
+ ...overrides
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Factory for creating mock mail account data
56
+ */
57
+ const createMockMailAccount = (email = 'test@example.com', overrides = {}) => {
58
+ const [localPart, domain] = email.split('@')
59
+ return {
60
+ email,
61
+ localPart,
62
+ domain,
63
+ password: '$2b$10$hashedpasswordexample',
64
+ created: Date.now(),
65
+ lastLogin: null,
66
+ ...overrides
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Factory for creating mock DNS records
72
+ */
73
+ const createMockDNSRecord = (type = 'A', name = 'example.com', value = '127.0.0.1', overrides = {}) => {
74
+ const baseRecord = {
75
+ name,
76
+ type,
77
+ value,
78
+ ttl: 300
79
+ }
80
+
81
+ // Add type-specific properties
82
+ switch (type) {
83
+ case 'MX':
84
+ baseRecord.priority = 10
85
+ break
86
+ case 'SOA':
87
+ baseRecord.serial = Math.floor(Date.now() / 1000)
88
+ baseRecord.refresh = 3600
89
+ baseRecord.retry = 1800
90
+ baseRecord.expire = 604800
91
+ baseRecord.minimum = 86400
92
+ break
93
+ case 'SRV':
94
+ baseRecord.priority = 10
95
+ baseRecord.weight = 5
96
+ baseRecord.port = 443
97
+ break
98
+ }
99
+
100
+ return {
101
+ ...baseRecord,
102
+ ...overrides
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Factory for creating mock SSL certificate data
108
+ */
109
+ const createMockSSLCertificate = (domain = 'example.com', overrides = {}) => {
110
+ return {
111
+ domain,
112
+ keyPath: `/etc/ssl/private/${domain}.key`,
113
+ certPath: `/etc/ssl/certs/${domain}.crt`,
114
+ chainPath: `/etc/ssl/certs/${domain}-chain.crt`,
115
+ issued: Date.now() - 86400000 * 10, // 10 days ago
116
+ expiry: Date.now() + 86400000 * 80, // 80 days from now
117
+ issuer: "Let's Encrypt Authority X3",
118
+ algorithm: 'RSA',
119
+ keySize: 2048,
120
+ ...overrides
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Factory for creating mock HTTP request objects
126
+ */
127
+ const createMockRequest = (url = '/', method = 'GET', overrides = {}) => {
128
+ return {
129
+ url,
130
+ method,
131
+ headers: {
132
+ host: 'example.com',
133
+ 'user-agent': 'Mozilla/5.0 Test Browser',
134
+ accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
135
+ ...overrides.headers
136
+ },
137
+ connection: {
138
+ remoteAddress: '127.0.0.1',
139
+ remotePort: 12345
140
+ },
141
+ body: '',
142
+ query: {},
143
+ params: {},
144
+ ...overrides
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Factory for creating mock HTTP response objects
150
+ */
151
+ const createMockResponse = (overrides = {}) => {
152
+ const response = {
153
+ statusCode: 200,
154
+ headers: {},
155
+ headersSent: false,
156
+ finished: false,
157
+ setHeader: jest.fn((name, value) => {
158
+ response.headers[name.toLowerCase()] = value
159
+ }),
160
+ getHeader: jest.fn(name => response.headers[name.toLowerCase()]),
161
+ removeHeader: jest.fn(name => {
162
+ delete response.headers[name.toLowerCase()]
163
+ }),
164
+ writeHead: jest.fn((statusCode, headers) => {
165
+ response.statusCode = statusCode
166
+ if (headers) {
167
+ Object.entries(headers).forEach(([name, value]) => {
168
+ response.setHeader(name, value)
169
+ })
170
+ }
171
+ response.headersSent = true
172
+ }),
173
+ write: jest.fn(),
174
+ end: jest.fn(data => {
175
+ if (data) response.write(data)
176
+ response.finished = true
177
+ }),
178
+ ...overrides
179
+ }
180
+
181
+ return response
182
+ }
183
+
184
+ /**
185
+ * Factory for creating mock TCP socket objects
186
+ */
187
+ const createMockSocket = (overrides = {}) => {
188
+ return {
189
+ remoteAddress: '127.0.0.1',
190
+ remotePort: 12345,
191
+ localAddress: '127.0.0.1',
192
+ localPort: 1453,
193
+ readable: true,
194
+ writable: true,
195
+ destroyed: false,
196
+ write: jest.fn(),
197
+ end: jest.fn(),
198
+ destroy: jest.fn(() => {
199
+ socket.destroyed = true
200
+ }),
201
+ on: jest.fn(),
202
+ once: jest.fn(),
203
+ emit: jest.fn(),
204
+ removeListener: jest.fn(),
205
+ removeAllListeners: jest.fn(),
206
+ ...overrides
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Factory for creating mock child process objects
212
+ */
213
+ const createMockChildProcess = (pid = 12345, overrides = {}) => {
214
+ return {
215
+ pid,
216
+ killed: false,
217
+ exitCode: null,
218
+ signalCode: null,
219
+ connected: true,
220
+ stdout: {
221
+ on: jest.fn(),
222
+ pipe: jest.fn(),
223
+ readable: true
224
+ },
225
+ stderr: {
226
+ on: jest.fn(),
227
+ pipe: jest.fn(),
228
+ readable: true
229
+ },
230
+ stdin: {
231
+ write: jest.fn(),
232
+ end: jest.fn(),
233
+ writable: true
234
+ },
235
+ kill: jest.fn((signal = 'SIGTERM') => {
236
+ process.killed = true
237
+ process.signalCode = signal
238
+ return true
239
+ }),
240
+ send: jest.fn(),
241
+ disconnect: jest.fn(),
242
+ on: jest.fn(),
243
+ once: jest.fn(),
244
+ emit: jest.fn(),
245
+ removeListener: jest.fn(),
246
+ removeAllListeners: jest.fn(),
247
+ ...overrides
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Factory for creating mock database result objects
253
+ */
254
+ const createMockDatabaseResult = (rows = [], overrides = {}) => {
255
+ return {
256
+ rows,
257
+ rowCount: rows.length,
258
+ command: 'SELECT',
259
+ fields: [],
260
+ ...overrides
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Factory for creating mock email message objects
266
+ */
267
+ const createMockEmailMessage = (from = 'sender@example.com', to = 'recipient@example.com', overrides = {}) => {
268
+ return {
269
+ messageId: `<${Date.now()}.${Math.random()}@example.com>`,
270
+ from: {address: from, name: ''},
271
+ to: [{address: to, name: ''}],
272
+ subject: 'Test Email',
273
+ date: new Date(),
274
+ headers: new Map([
275
+ ['from', from],
276
+ ['to', to],
277
+ ['subject', 'Test Email'],
278
+ ['date', new Date().toISOString()]
279
+ ]),
280
+ text: 'This is a test email message.',
281
+ html: '<p>This is a test email message.</p>',
282
+ attachments: [],
283
+ ...overrides
284
+ }
285
+ }
286
+
287
+ module.exports = {
288
+ createMockWebsiteConfig,
289
+ createMockServiceConfig,
290
+ createMockMailAccount,
291
+ createMockDNSRecord,
292
+ createMockSSLCertificate,
293
+ createMockRequest,
294
+ createMockResponse,
295
+ createMockSocket,
296
+ createMockChildProcess,
297
+ createMockDatabaseResult,
298
+ createMockEmailMessage
299
+ }