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,105 @@
1
+ /**
2
+ * Mock implementation of http-proxy module for server tests
3
+ */
4
+
5
+ const {createMockEventEmitter} = require('./testHelpers')
6
+
7
+ const createMockProxyServer = (options = {}) => {
8
+ const proxy = Object.assign(createMockEventEmitter(), {
9
+ options: options,
10
+
11
+ // Main proxy methods
12
+ web: jest.fn((req, res, options, callback) => {
13
+ // Simulate successful proxying
14
+ setTimeout(() => {
15
+ if (callback) callback()
16
+ }, 0)
17
+ }),
18
+
19
+ ws: jest.fn((req, socket, head, options, callback) => {
20
+ // Simulate WebSocket proxying
21
+ setTimeout(() => {
22
+ if (callback) callback()
23
+ }, 0)
24
+ }),
25
+
26
+ listen: jest.fn((port, hostname, callback) => {
27
+ if (typeof hostname === 'function') {
28
+ callback = hostname
29
+ hostname = undefined
30
+ }
31
+
32
+ setTimeout(() => {
33
+ if (callback) callback()
34
+ }, 0)
35
+
36
+ return proxy
37
+ }),
38
+
39
+ close: jest.fn(callback => {
40
+ setTimeout(() => {
41
+ if (callback) callback()
42
+ }, 0)
43
+ }),
44
+
45
+ // Test helper methods
46
+ simulateProxyReq: (proxyReq, req, res, options) => {
47
+ proxy.emit('proxyReq', proxyReq, req, res, options)
48
+ },
49
+
50
+ simulateProxyRes: (proxyRes, req, res) => {
51
+ proxy.emit('proxyRes', proxyRes, req, res)
52
+ },
53
+
54
+ simulateError: (error, req, res, target) => {
55
+ proxy.emit('error', error, req, res, target)
56
+ },
57
+
58
+ simulateOpen: proxySocket => {
59
+ proxy.emit('open', proxySocket)
60
+ },
61
+
62
+ simulateClose: (res, socket, head) => {
63
+ proxy.emit('close', res, socket, head)
64
+ }
65
+ })
66
+
67
+ return proxy
68
+ }
69
+
70
+ const httpProxy = {
71
+ createProxyServer: jest.fn((options = {}) => {
72
+ return createMockProxyServer(options)
73
+ }),
74
+
75
+ createServer: jest.fn((options = {}, requestListener) => {
76
+ if (typeof options === 'function') {
77
+ requestListener = options
78
+ options = {}
79
+ }
80
+
81
+ const proxy = createMockProxyServer(options)
82
+
83
+ // Add server-like methods
84
+ proxy.listen = jest.fn((port, hostname, callback) => {
85
+ if (typeof hostname === 'function') {
86
+ callback = hostname
87
+ hostname = undefined
88
+ }
89
+
90
+ setTimeout(() => {
91
+ proxy.emit('listening')
92
+ if (callback) callback()
93
+ }, 0)
94
+
95
+ return proxy
96
+ })
97
+
98
+ return proxy
99
+ }),
100
+
101
+ // Test helpers
102
+ __createMockProxyServer: createMockProxyServer
103
+ }
104
+
105
+ module.exports = httpProxy
@@ -0,0 +1,575 @@
1
+ /**
2
+ * Mock implementation of the http module for server tests
3
+ * Provides comprehensive mocking of HTTP server and client operations
4
+ */
5
+
6
+ const {createMockEventEmitter} = require('./testHelpers')
7
+ const {createMockRequest, createMockResponse} = require('./testFactories')
8
+
9
+ // Track active servers
10
+ const activeServers = new Map()
11
+ let nextServerId = 1
12
+
13
+ const createMockIncomingMessage = (options = {}) => {
14
+ const message = createMockEventEmitter()
15
+
16
+ Object.assign(message, {
17
+ // HTTP properties
18
+ httpVersion: options.httpVersion || '1.1',
19
+ httpVersionMajor: options.httpVersionMajor || 1,
20
+ httpVersionMinor: options.httpVersionMinor || 1,
21
+ complete: false,
22
+
23
+ // Request properties
24
+ url: options.url || '/',
25
+ method: options.method || 'GET',
26
+ statusCode: options.statusCode,
27
+ statusMessage: options.statusMessage,
28
+
29
+ // Headers
30
+ headers: options.headers || {},
31
+ rawHeaders: options.rawHeaders || [],
32
+ trailers: {},
33
+ rawTrailers: [],
34
+
35
+ // Connection info
36
+ connection: {
37
+ remoteAddress: options.remoteAddress || '127.0.0.1',
38
+ remotePort: options.remotePort || 12345,
39
+ localAddress: options.localAddress || '127.0.0.1',
40
+ localPort: options.localPort || 80
41
+ },
42
+ socket: options.socket || null,
43
+
44
+ // Stream properties
45
+ readable: true,
46
+ readableEnded: false,
47
+
48
+ // Methods
49
+ setTimeout: jest.fn((msecs, callback) => {
50
+ if (callback) {
51
+ message.once('timeout', callback)
52
+ }
53
+ return message
54
+ }),
55
+
56
+ // Test helpers
57
+ __simulateData: chunk => {
58
+ if (message.readable) {
59
+ message.emit('data', Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
60
+ }
61
+ },
62
+
63
+ __simulateEnd: () => {
64
+ message.complete = true
65
+ message.readableEnded = true
66
+ message.emit('end')
67
+ },
68
+
69
+ __simulateError: error => {
70
+ message.emit('error', error)
71
+ }
72
+ })
73
+
74
+ return message
75
+ }
76
+
77
+ const createMockServerResponse = (options = {}) => {
78
+ const response = createMockEventEmitter()
79
+
80
+ Object.assign(response, {
81
+ // Response properties
82
+ statusCode: 200,
83
+ statusMessage: 'OK',
84
+ headersSent: false,
85
+ finished: false,
86
+
87
+ // Headers
88
+ headers: {},
89
+
90
+ // Connection
91
+ connection: options.connection || null,
92
+ socket: options.socket || null,
93
+
94
+ // Stream properties
95
+ writable: true,
96
+ writableEnded: false,
97
+
98
+ // Methods
99
+ writeHead: jest.fn((statusCode, statusMessage, headers) => {
100
+ if (typeof statusMessage === 'object') {
101
+ headers = statusMessage
102
+ statusMessage = undefined
103
+ }
104
+
105
+ response.statusCode = statusCode
106
+ if (statusMessage) {
107
+ response.statusMessage = statusMessage
108
+ }
109
+
110
+ if (headers) {
111
+ Object.entries(headers).forEach(([name, value]) => {
112
+ response.setHeader(name, value)
113
+ })
114
+ }
115
+
116
+ response.headersSent = true
117
+ return response
118
+ }),
119
+
120
+ setHeader: jest.fn((name, value) => {
121
+ if (response.headersSent) {
122
+ throw new Error('Cannot set headers after they are sent to the client')
123
+ }
124
+ response.headers[name.toLowerCase()] = value
125
+ return response
126
+ }),
127
+
128
+ getHeader: jest.fn(name => {
129
+ return response.headers[name.toLowerCase()]
130
+ }),
131
+
132
+ getHeaders: jest.fn(() => {
133
+ return {...response.headers}
134
+ }),
135
+
136
+ getHeaderNames: jest.fn(() => {
137
+ return Object.keys(response.headers)
138
+ }),
139
+
140
+ hasHeader: jest.fn(name => {
141
+ return name.toLowerCase() in response.headers
142
+ }),
143
+
144
+ removeHeader: jest.fn(name => {
145
+ delete response.headers[name.toLowerCase()]
146
+ return response
147
+ }),
148
+
149
+ write: jest.fn((chunk, encoding, callback) => {
150
+ if (!response.writable) {
151
+ const error = new Error('Cannot write to finished response')
152
+ if (callback) callback(error)
153
+ return false
154
+ }
155
+
156
+ if (!response.headersSent) {
157
+ response.writeHead(response.statusCode)
158
+ }
159
+
160
+ if (typeof encoding === 'function') {
161
+ callback = encoding
162
+ encoding = 'utf8'
163
+ }
164
+
165
+ if (callback) {
166
+ setTimeout(callback, 0)
167
+ }
168
+
169
+ return true
170
+ }),
171
+
172
+ end: jest.fn((data, encoding, callback) => {
173
+ if (data) {
174
+ response.write(data, encoding)
175
+ }
176
+
177
+ if (typeof encoding === 'function') {
178
+ callback = encoding
179
+ }
180
+ if (typeof data === 'function') {
181
+ callback = data
182
+ }
183
+
184
+ response.finished = true
185
+ response.writable = false
186
+ response.writableEnded = true
187
+
188
+ setTimeout(() => {
189
+ response.emit('finish')
190
+ if (callback) callback()
191
+ }, 0)
192
+
193
+ return response
194
+ }),
195
+
196
+ setTimeout: jest.fn((msecs, callback) => {
197
+ if (callback) {
198
+ response.once('timeout', callback)
199
+ }
200
+ return response
201
+ }),
202
+
203
+ // Test helpers
204
+ __simulateError: error => {
205
+ response.emit('error', error)
206
+ }
207
+ })
208
+
209
+ return response
210
+ }
211
+
212
+ const createMockServer = (options = {}) => {
213
+ const server = createMockEventEmitter()
214
+ const serverId = nextServerId++
215
+
216
+ Object.assign(server, {
217
+ // Server properties
218
+ listening: false,
219
+ maxHeadersCount: null,
220
+ timeout: 120000,
221
+ keepAliveTimeout: 5000,
222
+ headersTimeout: 60000,
223
+ requestTimeout: 0,
224
+
225
+ // Methods
226
+ listen: jest.fn((port, hostname, backlog, callback) => {
227
+ // Handle different argument patterns
228
+ if (typeof port === 'function') {
229
+ callback = port
230
+ port = 0
231
+ } else if (typeof hostname === 'function') {
232
+ callback = hostname
233
+ hostname = undefined
234
+ } else if (typeof backlog === 'function') {
235
+ callback = backlog
236
+ backlog = undefined
237
+ }
238
+
239
+ server._port = port || 80
240
+ server._hostname = hostname || '0.0.0.0'
241
+ server.listening = true
242
+
243
+ if (callback) {
244
+ server.once('listening', callback)
245
+ }
246
+
247
+ setTimeout(() => {
248
+ server.emit('listening')
249
+ }, 0)
250
+
251
+ return server
252
+ }),
253
+
254
+ close: jest.fn(callback => {
255
+ server.listening = false
256
+
257
+ if (callback) {
258
+ server.once('close', callback)
259
+ }
260
+
261
+ setTimeout(() => {
262
+ server.emit('close')
263
+ }, 0)
264
+
265
+ activeServers.delete(serverId)
266
+ return server
267
+ }),
268
+
269
+ address: jest.fn(() => {
270
+ if (!server.listening) return null
271
+ return {
272
+ address: server._hostname || '0.0.0.0',
273
+ family: 'IPv4',
274
+ port: server._port || 80
275
+ }
276
+ }),
277
+
278
+ setTimeout: jest.fn((msecs, callback) => {
279
+ server.timeout = msecs
280
+ if (callback) {
281
+ server.on('timeout', callback)
282
+ }
283
+ return server
284
+ }),
285
+
286
+ // Test helpers
287
+ __simulateRequest: (requestOptions = {}, responseOptions = {}) => {
288
+ if (!server.listening) {
289
+ throw new Error('Server is not listening')
290
+ }
291
+
292
+ const req = createMockIncomingMessage({
293
+ url: '/',
294
+ method: 'GET',
295
+ headers: {host: 'localhost'},
296
+ ...requestOptions
297
+ })
298
+
299
+ const res = createMockServerResponse(responseOptions)
300
+
301
+ setTimeout(() => {
302
+ server.emit('request', req, res)
303
+ }, 0)
304
+
305
+ return {req, res}
306
+ },
307
+
308
+ __simulateError: error => {
309
+ server.emit('error', error)
310
+ }
311
+ })
312
+
313
+ activeServers.set(serverId, server)
314
+ return server
315
+ }
316
+
317
+ const createMockClientRequest = (options = {}) => {
318
+ const request = createMockEventEmitter()
319
+
320
+ Object.assign(request, {
321
+ // Request properties
322
+ method: options.method || 'GET',
323
+ path: options.path || '/',
324
+ host: options.host || 'localhost',
325
+ port: options.port || 80,
326
+
327
+ // State
328
+ aborted: false,
329
+ finished: false,
330
+
331
+ // Stream properties
332
+ writable: true,
333
+ writableEnded: false,
334
+
335
+ // Methods
336
+ write: jest.fn((chunk, encoding, callback) => {
337
+ if (!request.writable) {
338
+ const error = new Error('Cannot write to finished request')
339
+ if (callback) callback(error)
340
+ return false
341
+ }
342
+
343
+ if (typeof encoding === 'function') {
344
+ callback = encoding
345
+ }
346
+
347
+ if (callback) {
348
+ setTimeout(callback, 0)
349
+ }
350
+
351
+ return true
352
+ }),
353
+
354
+ end: jest.fn((data, encoding, callback) => {
355
+ if (data) {
356
+ request.write(data, encoding)
357
+ }
358
+
359
+ if (typeof encoding === 'function') {
360
+ callback = encoding
361
+ }
362
+ if (typeof data === 'function') {
363
+ callback = data
364
+ }
365
+
366
+ request.finished = true
367
+ request.writable = false
368
+ request.writableEnded = true
369
+
370
+ // Simulate response
371
+ setTimeout(() => {
372
+ const response = createMockIncomingMessage({
373
+ statusCode: 200,
374
+ statusMessage: 'OK',
375
+ headers: {'content-type': 'text/plain'}
376
+ })
377
+
378
+ request.emit('response', response)
379
+
380
+ // Simulate response data
381
+ setTimeout(() => {
382
+ response.__simulateData('Mock response data')
383
+ response.__simulateEnd()
384
+ }, 0)
385
+
386
+ if (callback) callback()
387
+ }, 0)
388
+
389
+ return request
390
+ }),
391
+
392
+ abort: jest.fn(() => {
393
+ request.aborted = true
394
+ request.emit('abort')
395
+ return request
396
+ }),
397
+
398
+ setTimeout: jest.fn((timeout, callback) => {
399
+ if (callback) {
400
+ request.once('timeout', callback)
401
+ }
402
+ return request
403
+ }),
404
+
405
+ setHeader: jest.fn((name, value) => {
406
+ // Mock implementation
407
+ return request
408
+ }),
409
+
410
+ getHeader: jest.fn(name => {
411
+ // Mock implementation
412
+ return undefined
413
+ }),
414
+
415
+ removeHeader: jest.fn(name => {
416
+ // Mock implementation
417
+ return request
418
+ }),
419
+
420
+ // Test helpers
421
+ __simulateError: error => {
422
+ request.emit('error', error)
423
+ },
424
+
425
+ __simulateResponse: (responseOptions = {}) => {
426
+ const response = createMockIncomingMessage({
427
+ statusCode: 200,
428
+ statusMessage: 'OK',
429
+ ...responseOptions
430
+ })
431
+
432
+ setTimeout(() => {
433
+ request.emit('response', response)
434
+ }, 0)
435
+
436
+ return response
437
+ }
438
+ })
439
+
440
+ return request
441
+ }
442
+
443
+ const http = {
444
+ // Server creation
445
+ createServer: jest.fn((options, requestListener) => {
446
+ if (typeof options === 'function') {
447
+ requestListener = options
448
+ options = {}
449
+ }
450
+
451
+ const server = createMockServer(options)
452
+
453
+ if (requestListener) {
454
+ server.on('request', requestListener)
455
+ }
456
+
457
+ return server
458
+ }),
459
+
460
+ // Client requests
461
+ request: jest.fn((options, callback) => {
462
+ if (typeof options === 'string') {
463
+ options = new URL(options)
464
+ }
465
+
466
+ const req = createMockClientRequest(options)
467
+
468
+ if (callback) {
469
+ req.once('response', callback)
470
+ }
471
+
472
+ return req
473
+ }),
474
+
475
+ get: jest.fn((options, callback) => {
476
+ const req = http.request(options, callback)
477
+ req.end()
478
+ return req
479
+ }),
480
+
481
+ // Classes
482
+ Server: jest.fn(function (options, requestListener) {
483
+ return createMockServer(options, requestListener)
484
+ }),
485
+
486
+ IncomingMessage: jest.fn(function (socket) {
487
+ return createMockIncomingMessage({socket})
488
+ }),
489
+
490
+ ServerResponse: jest.fn(function (req) {
491
+ return createMockServerResponse({connection: req?.connection})
492
+ }),
493
+
494
+ ClientRequest: jest.fn(function (options) {
495
+ return createMockClientRequest(options)
496
+ }),
497
+
498
+ // Constants
499
+ METHODS: [
500
+ 'ACL',
501
+ 'BIND',
502
+ 'CHECKOUT',
503
+ 'CONNECT',
504
+ 'COPY',
505
+ 'DELETE',
506
+ 'GET',
507
+ 'HEAD',
508
+ 'LINK',
509
+ 'LOCK',
510
+ 'M-SEARCH',
511
+ 'MERGE',
512
+ 'MKACTIVITY',
513
+ 'MKCALENDAR',
514
+ 'MKCOL',
515
+ 'MOVE',
516
+ 'NOTIFY',
517
+ 'OPTIONS',
518
+ 'PATCH',
519
+ 'POST',
520
+ 'PROPFIND',
521
+ 'PROPPATCH',
522
+ 'PURGE',
523
+ 'PUT',
524
+ 'REBIND',
525
+ 'REPORT',
526
+ 'SEARCH',
527
+ 'SOURCE',
528
+ 'SUBSCRIBE',
529
+ 'TRACE',
530
+ 'UNBIND',
531
+ 'UNLINK',
532
+ 'UNLOCK',
533
+ 'UNSUBSCRIBE'
534
+ ],
535
+
536
+ STATUS_CODES: {
537
+ 100: 'Continue',
538
+ 101: 'Switching Protocols',
539
+ 200: 'OK',
540
+ 201: 'Created',
541
+ 202: 'Accepted',
542
+ 204: 'No Content',
543
+ 300: 'Multiple Choices',
544
+ 301: 'Moved Permanently',
545
+ 302: 'Found',
546
+ 304: 'Not Modified',
547
+ 400: 'Bad Request',
548
+ 401: 'Unauthorized',
549
+ 403: 'Forbidden',
550
+ 404: 'Not Found',
551
+ 405: 'Method Not Allowed',
552
+ 500: 'Internal Server Error',
553
+ 501: 'Not Implemented',
554
+ 502: 'Bad Gateway',
555
+ 503: 'Service Unavailable'
556
+ },
557
+
558
+ // Global agent
559
+ globalAgent: {
560
+ maxSockets: Infinity,
561
+ maxFreeSockets: 256,
562
+ timeout: 0,
563
+ keepAlive: false,
564
+ keepAliveMsecs: 1000
565
+ },
566
+
567
+ // Test helpers
568
+ __getActiveServers: () => new Map(activeServers),
569
+ __clearAll: () => {
570
+ activeServers.clear()
571
+ nextServerId = 1
572
+ }
573
+ }
574
+
575
+ module.exports = http