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,272 @@
1
+ /**
2
+ * Mock implementation of the https module for server tests
3
+ * Extends the http mock with SSL/TLS specific functionality
4
+ */
5
+
6
+ const http = require('./http')
7
+ const {createMockEventEmitter} = require('./testHelpers')
8
+
9
+ // Track active HTTPS servers
10
+ const activeHttpsServers = new Map()
11
+ let nextHttpsServerId = 1
12
+
13
+ const createMockHttpsServer = (options = {}) => {
14
+ const server = createMockEventEmitter()
15
+ const serverId = nextHttpsServerId++
16
+
17
+ // Inherit from HTTP server mock
18
+ const httpServer = http.createServer()
19
+ Object.setPrototypeOf(server, httpServer)
20
+ Object.assign(server, httpServer)
21
+
22
+ // Override and add HTTPS-specific properties
23
+ Object.assign(server, {
24
+ // SSL/TLS properties
25
+ cert: options.cert || null,
26
+ key: options.key || null,
27
+ ca: options.ca || null,
28
+ ciphers: options.ciphers || null,
29
+ secureProtocol: options.secureProtocol || 'TLSv1_2_method',
30
+
31
+ // SNI callback
32
+ SNICallback: options.SNICallback || null,
33
+
34
+ // Override listen to handle HTTPS port default
35
+ listen: jest.fn((port, hostname, backlog, callback) => {
36
+ // Handle different argument patterns
37
+ if (typeof port === 'function') {
38
+ callback = port
39
+ port = 443
40
+ } else if (typeof hostname === 'function') {
41
+ callback = hostname
42
+ hostname = undefined
43
+ } else if (typeof backlog === 'function') {
44
+ callback = backlog
45
+ backlog = undefined
46
+ }
47
+
48
+ server._port = port || 443
49
+ server._hostname = hostname || '0.0.0.0'
50
+ server.listening = true
51
+
52
+ if (callback) {
53
+ server.once('listening', callback)
54
+ }
55
+
56
+ setTimeout(() => {
57
+ server.emit('listening')
58
+ }, 0)
59
+
60
+ return server
61
+ }),
62
+
63
+ // SSL/TLS specific methods
64
+ addContext: jest.fn((hostname, context) => {
65
+ if (!server._contexts) {
66
+ server._contexts = new Map()
67
+ }
68
+ server._contexts.set(hostname, context)
69
+ return server
70
+ }),
71
+
72
+ // Test helpers
73
+ __simulateSecureConnection: (socket, options = {}) => {
74
+ const secureSocket = Object.assign(socket, {
75
+ encrypted: true,
76
+ authorized: options.authorized !== false,
77
+ authorizationError: options.authorizationError || null,
78
+ getPeerCertificate: jest.fn(() => ({
79
+ subject: {CN: options.commonName || 'localhost'},
80
+ issuer: {CN: 'Test CA'},
81
+ valid_from: new Date().toISOString(),
82
+ valid_to: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
83
+ fingerprint: 'AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD'
84
+ })),
85
+ getCipher: jest.fn(() => ({
86
+ name: 'ECDHE-RSA-AES128-GCM-SHA256',
87
+ version: 'TLSv1.2'
88
+ })),
89
+ getProtocol: jest.fn(() => 'TLSv1.2'),
90
+ getSession: jest.fn(() => Buffer.alloc(0))
91
+ })
92
+
93
+ setTimeout(() => {
94
+ server.emit('secureConnection', secureSocket)
95
+ }, 0)
96
+
97
+ return secureSocket
98
+ },
99
+
100
+ __simulateClientError: (error, socket) => {
101
+ server.emit('clientError', error, socket)
102
+ },
103
+
104
+ __getContexts: () => server._contexts || new Map()
105
+ })
106
+
107
+ activeHttpsServers.set(serverId, server)
108
+ return server
109
+ }
110
+
111
+ const createMockHttpsClientRequest = (options = {}) => {
112
+ const request = createMockEventEmitter()
113
+
114
+ // Inherit from HTTP client request mock
115
+ const httpRequest = http.request(options)
116
+ Object.setPrototypeOf(request, httpRequest)
117
+ Object.assign(request, httpRequest)
118
+
119
+ // Add HTTPS-specific properties
120
+ Object.assign(request, {
121
+ // SSL/TLS properties
122
+ rejectUnauthorized: options.rejectUnauthorized !== false,
123
+ servername: options.servername || options.hostname,
124
+
125
+ // Override end to simulate SSL handshake
126
+ end: jest.fn((data, encoding, callback) => {
127
+ if (data) {
128
+ request.write(data, encoding)
129
+ }
130
+
131
+ if (typeof encoding === 'function') {
132
+ callback = encoding
133
+ }
134
+ if (typeof data === 'function') {
135
+ callback = data
136
+ }
137
+
138
+ request.finished = true
139
+ request.writable = false
140
+ request.writableEnded = true
141
+
142
+ // Simulate SSL handshake and response
143
+ setTimeout(() => {
144
+ // Emit secureConnect event
145
+ request.emit('secureConnect')
146
+
147
+ const response = http.IncomingMessage()
148
+ Object.assign(response, {
149
+ statusCode: 200,
150
+ statusMessage: 'OK',
151
+ headers: {'content-type': 'text/plain'},
152
+ connection: {
153
+ encrypted: true,
154
+ authorized: true,
155
+ getPeerCertificate: jest.fn(() => ({
156
+ subject: {CN: options.servername || 'localhost'},
157
+ issuer: {CN: 'Test CA'},
158
+ valid_from: new Date().toISOString(),
159
+ valid_to: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()
160
+ }))
161
+ }
162
+ })
163
+
164
+ request.emit('response', response)
165
+
166
+ // Simulate response data
167
+ setTimeout(() => {
168
+ response.__simulateData('Mock HTTPS response data')
169
+ response.__simulateEnd()
170
+ }, 0)
171
+
172
+ if (callback) callback()
173
+ }, 0)
174
+
175
+ return request
176
+ })
177
+ })
178
+
179
+ return request
180
+ }
181
+
182
+ const https = {
183
+ // Server creation
184
+ createServer: jest.fn((options, requestListener) => {
185
+ if (typeof options === 'function') {
186
+ requestListener = options
187
+ options = {}
188
+ }
189
+
190
+ const server = createMockHttpsServer(options)
191
+
192
+ if (requestListener) {
193
+ server.on('request', requestListener)
194
+ }
195
+
196
+ return server
197
+ }),
198
+
199
+ // Client requests
200
+ request: jest.fn((options, callback) => {
201
+ if (typeof options === 'string') {
202
+ const url = new URL(options)
203
+ options = {
204
+ hostname: url.hostname,
205
+ port: url.port || 443,
206
+ path: url.pathname + url.search,
207
+ method: 'GET'
208
+ }
209
+ }
210
+
211
+ const req = createMockHttpsClientRequest(options)
212
+
213
+ if (callback) {
214
+ req.once('response', callback)
215
+ }
216
+
217
+ return req
218
+ }),
219
+
220
+ get: jest.fn((options, callback) => {
221
+ const req = https.request(options, callback)
222
+ req.end()
223
+ return req
224
+ }),
225
+
226
+ // Classes
227
+ Server: jest.fn(function (options, requestListener) {
228
+ return createMockHttpsServer(options, requestListener)
229
+ }),
230
+
231
+ Agent: jest.fn(function (options = {}) {
232
+ return {
233
+ maxSockets: options.maxSockets || Infinity,
234
+ maxFreeSockets: options.maxFreeSockets || 256,
235
+ timeout: options.timeout || 0,
236
+ keepAlive: options.keepAlive || false,
237
+ keepAliveMsecs: options.keepAliveMsecs || 1000,
238
+ maxCachedSessions: options.maxCachedSessions || 100,
239
+ servername: options.servername,
240
+
241
+ // Methods
242
+ createConnection: jest.fn(),
243
+ keepSocketAlive: jest.fn(),
244
+ reuseSocket: jest.fn(),
245
+ destroy: jest.fn()
246
+ }
247
+ }),
248
+
249
+ // Global agent
250
+ globalAgent: {
251
+ maxSockets: Infinity,
252
+ maxFreeSockets: 256,
253
+ timeout: 0,
254
+ keepAlive: false,
255
+ keepAliveMsecs: 1000,
256
+ maxCachedSessions: 100
257
+ },
258
+
259
+ // Inherit HTTP constants and methods
260
+ METHODS: http.METHODS,
261
+ STATUS_CODES: http.STATUS_CODES,
262
+
263
+ // Test helpers
264
+ __getActiveServers: () => new Map(activeHttpsServers),
265
+ __clearAll: () => {
266
+ activeHttpsServers.clear()
267
+ nextHttpsServerId = 1
268
+ http.__clearAll()
269
+ }
270
+ }
271
+
272
+ module.exports = https
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Index file for all server test mocks
3
+ * Provides easy access to all mock modules and utilities
4
+ */
5
+
6
+ // Import all mock modules
7
+ const fs = require('./fs')
8
+ const net = require('./net')
9
+ const http = require('./http')
10
+ const https = require('./https')
11
+ const child_process = require('./child_process')
12
+ const crypto = require('./crypto')
13
+ const os = require('./os')
14
+ const path = require('./path')
15
+
16
+ // Import test utilities
17
+ const {mockCandy, mockLangGet, MockCandyPack} = require('./globalCandy')
18
+ const testFactories = require('./testFactories')
19
+ const testHelpers = require('./testHelpers')
20
+
21
+ /**
22
+ * Setup all Node.js module mocks for Jest
23
+ * Call this in your test setup to mock all Node.js built-in modules
24
+ */
25
+ const setupNodeMocks = () => {
26
+ jest.mock('fs', () => require('./fs'))
27
+ jest.mock('net', () => require('./net'))
28
+ jest.mock('http', () => require('./http'))
29
+ jest.mock('https', () => require('./https'))
30
+ jest.mock('child_process', () => require('./child_process'))
31
+ jest.mock('crypto', () => require('./crypto'))
32
+ jest.mock('os', () => require('./os'))
33
+ jest.mock('path', () => require('./path'))
34
+ }
35
+
36
+ /**
37
+ * Setup global mocks (Candy and __ function)
38
+ * Call this in beforeEach to set up global mocks
39
+ */
40
+ const setupGlobalMocks = () => {
41
+ global.Candy = mockCandy
42
+ global.__ = mockLangGet
43
+
44
+ // Clear all previous mock calls
45
+ mockCandy.clearMocks()
46
+ mockLangGet.mockClear()
47
+ }
48
+
49
+ /**
50
+ * Cleanup all mocks
51
+ * Call this in afterEach to clean up mocks
52
+ */
53
+ const cleanupAllMocks = () => {
54
+ // Clear global mocks
55
+ mockCandy.resetMocks()
56
+ mockLangGet.mockClear()
57
+
58
+ // Clear Node.js module mocks
59
+ fs.__resetFileSystem()
60
+ net.__clearAll()
61
+ http.__clearAll()
62
+ https.__clearAll()
63
+ child_process.__clearAll()
64
+ crypto.__resetMocks()
65
+ os.__resetMocks()
66
+ path.__resetMocks()
67
+
68
+ // Reset global references
69
+ delete global.Candy
70
+ delete global.__
71
+ }
72
+
73
+ /**
74
+ * Create a complete test environment with all mocks
75
+ * Returns an object with all mock modules and utilities
76
+ */
77
+ const createTestEnvironment = () => {
78
+ setupNodeMocks()
79
+ setupGlobalMocks()
80
+
81
+ return {
82
+ // Mock modules
83
+ mocks: {
84
+ fs,
85
+ net,
86
+ http,
87
+ https,
88
+ child_process,
89
+ crypto,
90
+ os,
91
+ path
92
+ },
93
+
94
+ // Global mocks
95
+ global: {
96
+ Candy: mockCandy,
97
+ __: mockLangGet
98
+ },
99
+
100
+ // Test utilities
101
+ factories: testFactories,
102
+ helpers: testHelpers,
103
+
104
+ // Cleanup function
105
+ cleanup: cleanupAllMocks
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Timer mock utilities for controlling time in tests
111
+ */
112
+ const createTimerMocks = () => {
113
+ const timers = new Map()
114
+ let timerId = 1
115
+
116
+ const mockSetTimeout = jest.fn((callback, delay) => {
117
+ const id = timerId++
118
+ timers.set(id, {callback, delay, type: 'timeout'})
119
+ return id
120
+ })
121
+
122
+ const mockSetInterval = jest.fn((callback, delay) => {
123
+ const id = timerId++
124
+ const timer = {
125
+ callback,
126
+ delay,
127
+ type: 'interval',
128
+ unref: jest.fn(() => timer)
129
+ }
130
+ timers.set(id, timer)
131
+ return timer
132
+ })
133
+
134
+ const mockClearTimeout = jest.fn(id => {
135
+ timers.delete(id)
136
+ })
137
+
138
+ const mockClearInterval = jest.fn(id => {
139
+ timers.delete(id)
140
+ })
141
+
142
+ // Apply mocks to global
143
+ global.setTimeout = mockSetTimeout
144
+ global.setInterval = mockSetInterval
145
+ global.clearTimeout = mockClearTimeout
146
+ global.clearInterval = mockClearInterval
147
+
148
+ return {
149
+ setTimeout: mockSetTimeout,
150
+ setInterval: mockSetInterval,
151
+ clearTimeout: mockClearTimeout,
152
+ clearInterval: mockClearInterval,
153
+
154
+ // Helper functions
155
+ executeTimer: id => {
156
+ const timer = timers.get(id)
157
+ if (timer && timer.callback) {
158
+ timer.callback()
159
+ }
160
+ },
161
+
162
+ executeAllTimers: () => {
163
+ timers.forEach(timer => {
164
+ if (timer.callback) {
165
+ timer.callback()
166
+ }
167
+ })
168
+ },
169
+
170
+ getTimers: () => new Map(timers),
171
+
172
+ clearAllTimers: () => {
173
+ timers.clear()
174
+ timerId = 1
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Process mock utilities for controlling process behavior
181
+ */
182
+ const createProcessMocks = () => {
183
+ const originalProcess = {...process}
184
+
185
+ const mockProcess = {
186
+ ...process,
187
+ exit: jest.fn(),
188
+ kill: jest.fn(),
189
+ nextTick: jest.fn(callback => {
190
+ setTimeout(callback, 0)
191
+ }),
192
+ env: {...process.env},
193
+ argv: [...process.argv],
194
+ cwd: jest.fn(() => '/mock/cwd'),
195
+ chdir: jest.fn(directory => {
196
+ mockProcess._cwd = directory
197
+ }),
198
+
199
+ // Test helpers
200
+ __setEnv: env => {
201
+ mockProcess.env = {...env}
202
+ },
203
+
204
+ __setArgv: argv => {
205
+ mockProcess.argv = [...argv]
206
+ },
207
+
208
+ __setCwd: cwd => {
209
+ mockProcess.cwd.mockReturnValue(cwd)
210
+ },
211
+
212
+ __reset: () => {
213
+ Object.assign(mockProcess, originalProcess)
214
+ mockProcess.env = {...originalProcess.env}
215
+ mockProcess.argv = [...originalProcess.argv]
216
+ }
217
+ }
218
+
219
+ return mockProcess
220
+ }
221
+
222
+ module.exports = {
223
+ // Mock modules
224
+ fs,
225
+ net,
226
+ http,
227
+ https,
228
+ child_process,
229
+ crypto,
230
+ os,
231
+ path,
232
+
233
+ // Global mocks
234
+ mockCandy,
235
+ mockLangGet,
236
+ MockCandyPack,
237
+
238
+ // Test utilities
239
+ testFactories,
240
+ testHelpers,
241
+
242
+ // Setup functions
243
+ setupNodeMocks,
244
+ setupGlobalMocks,
245
+ cleanupAllMocks,
246
+ createTestEnvironment,
247
+ createTimerMocks,
248
+ createProcessMocks
249
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Mock implementation of mail/server for server tests
3
+ */
4
+
5
+ const {createMockEventEmitter} = require('../testHelpers')
6
+
7
+ class MockMailServer {
8
+ constructor(options = {}) {
9
+ Object.assign(this, createMockEventEmitter())
10
+
11
+ this.options = options
12
+ this.listening = false
13
+ this.port = null
14
+
15
+ // Store callbacks for testing
16
+ this.onAuth = options.onAuth
17
+ this.onAppend = options.onAppend
18
+ this.onExpunge = options.onExpunge
19
+ this.onCreate = options.onCreate
20
+ this.onDelete = options.onDelete
21
+ this.onRename = options.onRename
22
+ this.onFetch = options.onFetch
23
+ this.onList = options.onList
24
+ this.onLsub = options.onLsub
25
+ this.onSelect = options.onSelect
26
+ this.onStore = options.onStore
27
+ this.onError = options.onError
28
+ }
29
+
30
+ listen(port, hostname, callback) {
31
+ if (typeof hostname === 'function') {
32
+ callback = hostname
33
+ hostname = undefined
34
+ }
35
+
36
+ this.port = port
37
+ this.listening = true
38
+
39
+ setTimeout(() => {
40
+ this.emit('listening')
41
+ if (callback) callback()
42
+ }, 0)
43
+
44
+ return this
45
+ }
46
+
47
+ close(callback) {
48
+ this.listening = false
49
+ this.port = null
50
+
51
+ setTimeout(() => {
52
+ this.emit('close')
53
+ if (callback) callback()
54
+ }, 0)
55
+
56
+ return this
57
+ }
58
+
59
+ // Test helper methods
60
+ simulateConnection(session = {}) {
61
+ const mockSession = {
62
+ remoteAddress: '127.0.0.1',
63
+ remotePort: 12345,
64
+ user: null,
65
+ ...session
66
+ }
67
+
68
+ this.emit('connect', mockSession)
69
+ return mockSession
70
+ }
71
+
72
+ simulateAuth(auth, session, callback) {
73
+ if (this.onAuth) {
74
+ this.onAuth(auth, session, callback)
75
+ } else if (callback) {
76
+ callback(null, {user: auth.username})
77
+ }
78
+ }
79
+
80
+ simulateAppend(data, callback) {
81
+ if (this.onAppend) {
82
+ this.onAppend(data, callback)
83
+ } else if (callback) {
84
+ callback()
85
+ }
86
+ }
87
+
88
+ simulateError(error) {
89
+ if (this.onError) {
90
+ this.onError(error)
91
+ }
92
+ this.emit('error', error)
93
+ }
94
+ }
95
+
96
+ const mockMailServer = jest.fn().mockImplementation(options => {
97
+ return new MockMailServer(options)
98
+ })
99
+
100
+ module.exports = mockMailServer
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Mock implementation of mail/smtp for server tests
3
+ */
4
+
5
+ const smtp = {
6
+ send: jest.fn(mailData => {
7
+ // Mock successful send
8
+ return Promise.resolve({
9
+ messageId: mailData.messageId || '<mock@example.com>',
10
+ accepted: mailData.to ? [mailData.to.value[0].address] : ['recipient@example.com'],
11
+ rejected: [],
12
+ pending: [],
13
+ response: '250 Message accepted'
14
+ })
15
+ }),
16
+
17
+ // Test helper methods
18
+ __setMockResponse: response => {
19
+ smtp.send.mockResolvedValue(response)
20
+ },
21
+
22
+ __setMockError: error => {
23
+ smtp.send.mockRejectedValue(error)
24
+ },
25
+
26
+ __resetMock: () => {
27
+ smtp.send.mockClear()
28
+ }
29
+ }
30
+
31
+ module.exports = smtp