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,81 @@
1
+ /**
2
+ * Mock implementation of mailparser for server tests
3
+ */
4
+
5
+ const {createMockEmailMessage} = require('./testFactories')
6
+
7
+ const simpleParser = jest.fn((source, options, callback) => {
8
+ if (typeof options === 'function') {
9
+ callback = options
10
+ options = {}
11
+ }
12
+
13
+ // Create a mock parsed email
14
+ const mockParsedEmail = createMockEmailMessage()
15
+
16
+ // Allow customization through options
17
+ if (options.from) mockParsedEmail.from.value[0].address = options.from
18
+ if (options.to) mockParsedEmail.to[0].address = options.to
19
+ if (options.subject) mockParsedEmail.subject = options.subject
20
+ if (options.text) mockParsedEmail.text = options.text
21
+ if (options.html) mockParsedEmail.html = options.html
22
+
23
+ // Simulate async parsing
24
+ setTimeout(() => {
25
+ if (callback) {
26
+ callback(null, mockParsedEmail)
27
+ }
28
+ }, 0)
29
+
30
+ // Return a promise if no callback provided
31
+ if (!callback) {
32
+ return Promise.resolve(mockParsedEmail)
33
+ }
34
+ })
35
+
36
+ // Mock for streaming parser
37
+ const parseHeaders = jest.fn((source, callback) => {
38
+ const mockHeaders = new Map([
39
+ ['from', 'sender@example.com'],
40
+ ['to', 'recipient@example.com'],
41
+ ['subject', 'Test Email'],
42
+ ['date', new Date().toISOString()],
43
+ ['message-id', '<test@example.com>']
44
+ ])
45
+
46
+ setTimeout(() => {
47
+ if (callback) {
48
+ callback(null, mockHeaders)
49
+ }
50
+ }, 0)
51
+
52
+ if (!callback) {
53
+ return Promise.resolve(mockHeaders)
54
+ }
55
+ })
56
+
57
+ // Mock for attachment parsing
58
+ const parseAttachment = jest.fn((attachment, callback) => {
59
+ const mockAttachment = {
60
+ filename: 'test.txt',
61
+ contentType: 'text/plain',
62
+ size: 100,
63
+ content: Buffer.from('test attachment content')
64
+ }
65
+
66
+ setTimeout(() => {
67
+ if (callback) {
68
+ callback(null, mockAttachment)
69
+ }
70
+ }, 0)
71
+
72
+ if (!callback) {
73
+ return Promise.resolve(mockAttachment)
74
+ }
75
+ })
76
+
77
+ module.exports = {
78
+ simpleParser,
79
+ parseHeaders,
80
+ parseAttachment
81
+ }
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Mock implementation of the net module for server tests
3
+ * Provides comprehensive mocking of TCP server and socket operations
4
+ */
5
+
6
+ const {createMockEventEmitter} = require('./testHelpers')
7
+
8
+ // Track active servers and connections
9
+ const activeServers = new Map()
10
+ const activeConnections = new Map()
11
+ let nextConnectionId = 1
12
+
13
+ const createMockSocket = (options = {}) => {
14
+ const socket = createMockEventEmitter()
15
+ const connectionId = nextConnectionId++
16
+
17
+ Object.assign(socket, {
18
+ // Connection properties
19
+ remoteAddress: options.remoteAddress || '127.0.0.1',
20
+ remotePort: options.remotePort || Math.floor(Math.random() * 50000) + 10000,
21
+ localAddress: options.localAddress || '127.0.0.1',
22
+ localPort: options.localPort || 1453,
23
+ remoteFamily: options.remoteFamily || 'IPv4',
24
+ localFamily: options.localFamily || 'IPv4',
25
+
26
+ // State properties
27
+ readable: true,
28
+ writable: true,
29
+ destroyed: false,
30
+ connecting: false,
31
+ readyState: 'open',
32
+
33
+ // Buffer properties
34
+ bytesRead: 0,
35
+ bytesWritten: 0,
36
+
37
+ // Methods
38
+ write: jest.fn((data, encoding, callback) => {
39
+ if (socket.destroyed || !socket.writable) {
40
+ const error = new Error('Cannot write to destroyed socket')
41
+ if (callback) callback(error)
42
+ return false
43
+ }
44
+
45
+ socket.bytesWritten += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data, encoding)
46
+
47
+ if (callback) {
48
+ setTimeout(callback, 0)
49
+ }
50
+
51
+ return true
52
+ }),
53
+
54
+ end: jest.fn((data, encoding, callback) => {
55
+ if (data) {
56
+ socket.write(data, encoding)
57
+ }
58
+
59
+ if (typeof encoding === 'function') {
60
+ callback = encoding
61
+ }
62
+ if (typeof data === 'function') {
63
+ callback = data
64
+ }
65
+
66
+ socket.writable = false
67
+
68
+ setTimeout(() => {
69
+ socket.emit('end')
70
+ if (callback) callback()
71
+ }, 0)
72
+
73
+ return socket
74
+ }),
75
+
76
+ destroy: jest.fn(error => {
77
+ if (socket.destroyed) return socket
78
+
79
+ socket.destroyed = true
80
+ socket.readable = false
81
+ socket.writable = false
82
+ socket.readyState = 'closed'
83
+
84
+ activeConnections.delete(connectionId)
85
+
86
+ setTimeout(() => {
87
+ if (error) {
88
+ socket.emit('error', error)
89
+ }
90
+ socket.emit('close', !!error)
91
+ }, 0)
92
+
93
+ return socket
94
+ }),
95
+
96
+ pause: jest.fn(() => {
97
+ socket.readable = false
98
+ return socket
99
+ }),
100
+
101
+ resume: jest.fn(() => {
102
+ socket.readable = true
103
+ return socket
104
+ }),
105
+
106
+ setTimeout: jest.fn((timeout, callback) => {
107
+ if (callback) {
108
+ socket.once('timeout', callback)
109
+ }
110
+
111
+ const timeoutId = setTimeout(() => {
112
+ socket.emit('timeout')
113
+ }, timeout)
114
+
115
+ socket._timeout = timeoutId
116
+ return socket
117
+ }),
118
+
119
+ setNoDelay: jest.fn((noDelay = true) => {
120
+ socket._noDelay = noDelay
121
+ return socket
122
+ }),
123
+
124
+ setKeepAlive: jest.fn((enable = false, initialDelay = 0) => {
125
+ socket._keepAlive = enable
126
+ socket._keepAliveInitialDelay = initialDelay
127
+ return socket
128
+ }),
129
+
130
+ address: jest.fn(() => ({
131
+ address: socket.localAddress,
132
+ family: socket.localFamily,
133
+ port: socket.localPort
134
+ })),
135
+
136
+ // Test helpers
137
+ __simulateData: data => {
138
+ if (!socket.destroyed && socket.readable) {
139
+ socket.bytesRead += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)
140
+ socket.emit('data', Buffer.isBuffer(data) ? data : Buffer.from(data))
141
+ }
142
+ },
143
+
144
+ __simulateError: error => {
145
+ socket.emit('error', error)
146
+ },
147
+
148
+ __simulateClose: (hadError = false) => {
149
+ socket.destroy()
150
+ }
151
+ })
152
+
153
+ activeConnections.set(connectionId, socket)
154
+ return socket
155
+ }
156
+
157
+ const createMockServer = (options = {}) => {
158
+ const server = createMockEventEmitter()
159
+ const serverId = Math.floor(Math.random() * 1000)
160
+
161
+ Object.assign(server, {
162
+ // State properties
163
+ listening: false,
164
+ maxConnections: options.maxConnections || 0,
165
+ connections: 0,
166
+
167
+ // Server address info
168
+ address: jest.fn(() => {
169
+ if (!server.listening) return null
170
+ return {
171
+ address: server._address || '127.0.0.1',
172
+ family: server._family || 'IPv4',
173
+ port: server._port || 1453
174
+ }
175
+ }),
176
+
177
+ // Methods
178
+ listen: jest.fn((port, hostname, backlog, callback) => {
179
+ // Handle different argument patterns
180
+ if (typeof port === 'function') {
181
+ callback = port
182
+ port = 0
183
+ } else if (typeof hostname === 'function') {
184
+ callback = hostname
185
+ hostname = undefined
186
+ } else if (typeof backlog === 'function') {
187
+ callback = backlog
188
+ backlog = undefined
189
+ }
190
+
191
+ server._port = port || Math.floor(Math.random() * 50000) + 10000
192
+ server._address = hostname || '127.0.0.1'
193
+ server._family = 'IPv4'
194
+ server.listening = true
195
+
196
+ if (callback) {
197
+ server.once('listening', callback)
198
+ }
199
+
200
+ setTimeout(() => {
201
+ server.emit('listening')
202
+ }, 0)
203
+
204
+ return server
205
+ }),
206
+
207
+ close: jest.fn(callback => {
208
+ if (!server.listening) {
209
+ const error = new Error('Server is not running')
210
+ if (callback) {
211
+ setTimeout(() => callback(error), 0)
212
+ }
213
+ return server
214
+ }
215
+
216
+ server.listening = false
217
+
218
+ // Close all connections
219
+ activeConnections.forEach(socket => {
220
+ if (socket.localPort === server._port) {
221
+ socket.destroy()
222
+ }
223
+ })
224
+
225
+ if (callback) {
226
+ server.once('close', callback)
227
+ }
228
+
229
+ setTimeout(() => {
230
+ server.emit('close')
231
+ }, 0)
232
+
233
+ activeServers.delete(serverId)
234
+ return server
235
+ }),
236
+
237
+ getConnections: jest.fn(callback => {
238
+ const count = Array.from(activeConnections.values()).filter(socket => socket.localPort === server._port && !socket.destroyed).length
239
+
240
+ if (callback) {
241
+ setTimeout(() => callback(null, count), 0)
242
+ }
243
+
244
+ return count
245
+ }),
246
+
247
+ ref: jest.fn(() => server),
248
+ unref: jest.fn(() => server),
249
+
250
+ // Test helpers
251
+ __simulateConnection: (socketOptions = {}) => {
252
+ if (!server.listening) {
253
+ throw new Error('Server is not listening')
254
+ }
255
+
256
+ const socket = createMockSocket({
257
+ localPort: server._port,
258
+ localAddress: server._address,
259
+ ...socketOptions
260
+ })
261
+
262
+ server.connections++
263
+
264
+ setTimeout(() => {
265
+ server.emit('connection', socket)
266
+ }, 0)
267
+
268
+ return socket
269
+ },
270
+
271
+ __simulateError: error => {
272
+ server.emit('error', error)
273
+ }
274
+ })
275
+
276
+ activeServers.set(serverId, server)
277
+ return server
278
+ }
279
+
280
+ const net = {
281
+ // Server creation
282
+ createServer: jest.fn((options, connectionListener) => {
283
+ if (typeof options === 'function') {
284
+ connectionListener = options
285
+ options = {}
286
+ }
287
+
288
+ const server = createMockServer(options)
289
+
290
+ if (connectionListener) {
291
+ server.on('connection', connectionListener)
292
+ }
293
+
294
+ return server
295
+ }),
296
+
297
+ // Socket creation
298
+ createConnection: jest.fn((options, connectListener) => {
299
+ const socket = createMockSocket()
300
+
301
+ if (typeof options === 'number') {
302
+ options = {port: options}
303
+ }
304
+
305
+ socket.connecting = true
306
+ socket.readyState = 'opening'
307
+
308
+ if (connectListener) {
309
+ socket.once('connect', connectListener)
310
+ }
311
+
312
+ // Simulate connection
313
+ setTimeout(() => {
314
+ socket.connecting = false
315
+ socket.readyState = 'open'
316
+ socket.emit('connect')
317
+ }, 0)
318
+
319
+ return socket
320
+ }),
321
+
322
+ connect: jest.fn((...args) => net.createConnection(...args)),
323
+
324
+ // Utility functions
325
+ isIP: jest.fn(input => {
326
+ if (typeof input !== 'string') return 0
327
+
328
+ // Simple IPv4 check
329
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/
330
+ if (ipv4Regex.test(input)) {
331
+ const parts = input.split('.')
332
+ if (parts.every(part => parseInt(part) >= 0 && parseInt(part) <= 255)) {
333
+ return 4
334
+ }
335
+ }
336
+
337
+ // Simple IPv6 check
338
+ const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/
339
+ if (ipv6Regex.test(input)) {
340
+ return 6
341
+ }
342
+
343
+ return 0
344
+ }),
345
+
346
+ isIPv4: jest.fn(input => net.isIP(input) === 4),
347
+ isIPv6: jest.fn(input => net.isIP(input) === 6),
348
+
349
+ // Socket class
350
+ Socket: jest.fn(function (options = {}) {
351
+ return createMockSocket(options)
352
+ }),
353
+
354
+ // Server class
355
+ Server: jest.fn(function (options = {}, connectionListener) {
356
+ return createMockServer(options, connectionListener)
357
+ }),
358
+
359
+ // Test helpers
360
+ __getActiveServers: () => new Map(activeServers),
361
+ __getActiveConnections: () => new Map(activeConnections),
362
+ __clearAll: () => {
363
+ activeServers.clear()
364
+ activeConnections.clear()
365
+ nextConnectionId = 1
366
+ }
367
+ }
368
+
369
+ module.exports = net