odac 0.9.0 → 1.0.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 (208) hide show
  1. package/.github/workflows/auto-pr-description.yml +0 -2
  2. package/.github/workflows/codeql.yml +46 -0
  3. package/.github/workflows/release.yml +13 -6
  4. package/.github/workflows/test-coverage.yml +10 -9
  5. package/.releaserc.js +9 -6
  6. package/CHANGELOG.md +62 -150
  7. package/CODE_OF_CONDUCT.md +1 -1
  8. package/CONTRIBUTING.md +8 -8
  9. package/LICENSE +21 -661
  10. package/README.md +12 -12
  11. package/SECURITY.md +4 -4
  12. package/bin/odac.js +101 -0
  13. package/{framework/web/candy.js → client/odac.js} +310 -44
  14. package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
  15. package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
  16. package/docs/backend/01-overview/03-development-server.md +11 -11
  17. package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
  18. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  19. package/docs/backend/03-config/01-database-connection.md +1 -1
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
  21. package/docs/backend/03-config/04-environment-variables.md +20 -20
  22. package/docs/backend/03-config/05-early-hints.md +4 -4
  23. package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
  26. package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
  27. package/docs/backend/04-routing/05-advanced-routing.md +3 -3
  28. package/docs/backend/04-routing/06-error-pages.md +17 -17
  29. package/docs/backend/04-routing/07-cron-jobs.md +13 -13
  30. package/docs/backend/04-routing/08-middleware.md +214 -0
  31. package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
  32. package/docs/backend/04-routing/09-websocket-examples.md +381 -0
  33. package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
  34. package/docs/backend/04-routing/09-websocket.md +298 -0
  35. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
  36. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
  37. package/docs/backend/05-controllers/03-controller-classes.md +19 -19
  38. package/docs/backend/05-forms/01-custom-forms.md +114 -114
  39. package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
  40. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
  41. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
  42. package/docs/backend/07-views/01-the-view-directory.md +1 -1
  43. package/docs/backend/07-views/02-rendering-a-view.md +22 -22
  44. package/docs/backend/07-views/03-template-syntax.md +52 -52
  45. package/docs/backend/07-views/03-variables.md +84 -84
  46. package/docs/backend/07-views/04-request-data.md +57 -57
  47. package/docs/backend/07-views/05-conditionals.md +78 -78
  48. package/docs/backend/07-views/06-loops.md +114 -114
  49. package/docs/backend/07-views/07-translations.md +66 -66
  50. package/docs/backend/07-views/08-backend-javascript.md +103 -103
  51. package/docs/backend/07-views/09-comments.md +71 -71
  52. package/docs/backend/08-database/01-database-connection.md +8 -8
  53. package/docs/backend/08-database/02-using-mysql.md +49 -49
  54. package/docs/backend/09-validation/01-the-validator-service.md +38 -38
  55. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
  56. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
  57. package/docs/backend/10-authentication/03-register.md +12 -12
  58. package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
  59. package/docs/backend/10-authentication/05-session-management.md +10 -10
  60. package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
  61. package/docs/backend/11-mail/01-the-mail-service.md +5 -5
  62. package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
  63. package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
  64. package/docs/frontend/01-overview/01-introduction.md +30 -30
  65. package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
  66. package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
  67. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
  68. package/docs/frontend/03-forms/01-form-handling.md +32 -32
  69. package/docs/frontend/04-api-requests/01-get-post.md +33 -33
  70. package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
  71. package/docs/frontend/06-websocket/00-overview.md +76 -0
  72. package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
  73. package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
  74. package/docs/index.json +49 -11
  75. package/eslint.config.mjs +6 -6
  76. package/{framework/index.js → index.js} +1 -1
  77. package/package.json +14 -39
  78. package/{framework/src → src}/Auth.js +59 -59
  79. package/{framework/src → src}/Config.js +3 -3
  80. package/{framework/src → src}/Lang.js +7 -7
  81. package/{framework/src → src}/Mail.js +5 -5
  82. package/{framework/src → src}/Mysql.js +42 -42
  83. package/src/Odac.js +112 -0
  84. package/{framework/src → src}/Request.js +38 -36
  85. package/{framework/src → src}/Route/Internal.js +116 -116
  86. package/src/Route/Middleware.js +75 -0
  87. package/src/Route.js +621 -0
  88. package/src/Server.js +22 -0
  89. package/{framework/src → src}/Stream.js +11 -3
  90. package/{framework/src → src}/Validator.js +21 -21
  91. package/{framework/src → src}/Var.js +5 -5
  92. package/{framework/src → src}/View/EarlyHints.js +1 -1
  93. package/{framework/src → src}/View/Form.js +69 -69
  94. package/{framework/src → src}/View.js +78 -81
  95. package/src/WebSocket.js +403 -0
  96. package/template/config.json +5 -0
  97. package/{web → template}/controller/page/about.js +6 -6
  98. package/{web → template}/controller/page/index.js +9 -9
  99. package/{web → template}/package.json +4 -5
  100. package/{web → template}/public/assets/css/style.css +4 -4
  101. package/{web → template}/public/assets/js/app.js +6 -6
  102. package/{web → template}/route/www.js +6 -6
  103. package/{web → template}/skeleton/main.html +1 -1
  104. package/{web → template}/view/content/about.html +5 -5
  105. package/{web → template}/view/content/home.html +12 -12
  106. package/template/view/footer/main.html +11 -0
  107. package/{web → template}/view/head/main.html +1 -1
  108. package/{web → template}/view/header/main.html +2 -2
  109. package/test/core/Candy.test.js +58 -58
  110. package/test/core/Commands.test.js +7 -7
  111. package/test/core/Config.test.js +82 -85
  112. package/test/core/Lang.test.js +2 -2
  113. package/test/core/Process.test.js +6 -6
  114. package/test/framework/Route.test.js +56 -37
  115. package/test/framework/View/EarlyHints.test.js +2 -2
  116. package/test/framework/WebSocket.test.js +100 -0
  117. package/test/framework/middleware.test.js +85 -0
  118. package/test/server/Api.test.js +31 -31
  119. package/test/server/DNS.test.js +11 -11
  120. package/test/server/Hub.test.js +497 -0
  121. package/test/server/Mail.account.test_.js +3 -3
  122. package/test/server/Mail.init.test_.js +10 -10
  123. package/test/server/Mail.test_.js +20 -20
  124. package/test/server/SSL.test_.js +54 -54
  125. package/test/server/Server.test.js +39 -39
  126. package/test/server/Service.test_.js +7 -7
  127. package/test/server/Subdomain.test.js +7 -7
  128. package/test/server/Web/Firewall.test.js +87 -87
  129. package/test/server/Web/Proxy.test.js +397 -0
  130. package/test/server/{Web.test_.js → Web.test.js} +137 -205
  131. package/test/server/__mocks__/fs.js +2 -2
  132. package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
  133. package/test/server/__mocks__/index.js +6 -6
  134. package/test/server/__mocks__/testFactories.js +1 -1
  135. package/test/server/__mocks__/testHelpers.js +7 -7
  136. package/.husky/pre-commit +0 -2
  137. package/.kiro/steering/code-style.md +0 -56
  138. package/.kiro/steering/product.md +0 -20
  139. package/.kiro/steering/structure.md +0 -77
  140. package/.kiro/steering/tech.md +0 -87
  141. package/AGENTS.md +0 -84
  142. package/bin/candy +0 -10
  143. package/bin/candypack +0 -10
  144. package/cli/index.js +0 -3
  145. package/cli/src/Cli.js +0 -348
  146. package/cli/src/Connector.js +0 -93
  147. package/cli/src/Monitor.js +0 -416
  148. package/core/Candy.js +0 -87
  149. package/core/Commands.js +0 -239
  150. package/core/Config.js +0 -1094
  151. package/core/Lang.js +0 -52
  152. package/core/Log.js +0 -43
  153. package/core/Process.js +0 -26
  154. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
  155. package/docs/server/01-installation/01-quick-install.md +0 -19
  156. package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
  157. package/docs/server/02-get-started/01-core-concepts.md +0 -7
  158. package/docs/server/02-get-started/02-basic-commands.md +0 -57
  159. package/docs/server/02-get-started/03-cli-reference.md +0 -276
  160. package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
  161. package/docs/server/03-service/01-start-a-new-service.md +0 -57
  162. package/docs/server/03-service/02-delete-a-service.md +0 -48
  163. package/docs/server/04-web/01-create-a-website.md +0 -36
  164. package/docs/server/04-web/02-list-websites.md +0 -9
  165. package/docs/server/04-web/03-delete-a-website.md +0 -29
  166. package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
  167. package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
  168. package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
  169. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
  170. package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
  171. package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
  172. package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
  173. package/docs/server/07-mail/04-change-account-password.md +0 -23
  174. package/framework/src/Candy.js +0 -81
  175. package/framework/src/Route.js +0 -455
  176. package/framework/src/Server.js +0 -15
  177. package/locale/de-DE.json +0 -80
  178. package/locale/en-US.json +0 -79
  179. package/locale/es-ES.json +0 -80
  180. package/locale/fr-FR.json +0 -80
  181. package/locale/pt-BR.json +0 -80
  182. package/locale/ru-RU.json +0 -80
  183. package/locale/tr-TR.json +0 -85
  184. package/locale/zh-CN.json +0 -80
  185. package/server/index.js +0 -5
  186. package/server/src/Api.js +0 -88
  187. package/server/src/DNS.js +0 -940
  188. package/server/src/Hub.js +0 -535
  189. package/server/src/Mail.js +0 -571
  190. package/server/src/SSL.js +0 -180
  191. package/server/src/Server.js +0 -27
  192. package/server/src/Service.js +0 -248
  193. package/server/src/Subdomain.js +0 -64
  194. package/server/src/Web/Firewall.js +0 -170
  195. package/server/src/Web/Proxy.js +0 -134
  196. package/server/src/Web.js +0 -451
  197. package/server/src/mail/imap.js +0 -1091
  198. package/server/src/mail/server.js +0 -32
  199. package/server/src/mail/smtp.js +0 -786
  200. package/test/server/Client.test.js +0 -338
  201. package/test/server/__mocks__/http-proxy.js +0 -105
  202. package/watchdog/index.js +0 -3
  203. package/watchdog/src/Watchdog.js +0 -156
  204. package/web/config.json +0 -5
  205. package/web/view/footer/main.html +0 -11
  206. /package/{framework/src → src}/Env.js +0 -0
  207. /package/{framework/src → src}/Route/Cron.js +0 -0
  208. /package/{framework/src → src}/Token.js +0 -0
@@ -5,7 +5,7 @@ describe('Route', () => {
5
5
 
6
6
  beforeEach(() => {
7
7
  route = new Route()
8
- global.Candy = {
8
+ global.Odac = {
9
9
  Route: {},
10
10
  Config: {}
11
11
  }
@@ -13,13 +13,13 @@ describe('Route', () => {
13
13
  })
14
14
 
15
15
  afterEach(() => {
16
- delete global.Candy
16
+ delete global.Odac
17
17
  delete global.__dir
18
18
  })
19
19
 
20
20
  describe('check - token request', () => {
21
21
  it('should handle token request with undefined route gracefully', async () => {
22
- const mockCandy = {
22
+ const mockOdac = {
23
23
  Request: {
24
24
  url: '/',
25
25
  method: 'get',
@@ -28,33 +28,34 @@ describe('Route', () => {
28
28
  host: 'example.com',
29
29
  header: jest.fn(key => {
30
30
  const headers = {
31
- 'X-Candy': 'token',
31
+ 'X-Odac': 'token',
32
32
  Referer: 'http://example.com/',
33
- 'X-Candy-Client': 'test-client'
33
+ 'X-Odac-Client': 'test-client'
34
34
  }
35
35
  return headers[key]
36
36
  }),
37
37
  cookie: jest.fn(key => {
38
- if (key === 'candy_client') return 'test-client'
38
+ if (key === 'odac_client') return 'test-client'
39
39
  return null
40
- })
40
+ }),
41
+ abort: jest.fn()
41
42
  },
42
43
  token: jest.fn(() => 'test-token')
43
44
  }
44
45
 
45
46
  route.routes = {}
46
47
 
47
- const result = await route.check(mockCandy)
48
+ const result = await route.check(mockOdac)
48
49
 
49
50
  expect(result).toBeDefined()
50
51
  expect(result.token).toBe('test-token')
51
52
  expect(result.page).toBeUndefined()
52
- expect(mockCandy.Request.header).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://example.com')
53
- expect(mockCandy.Request.header).toHaveBeenCalledWith('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
53
+ expect(mockOdac.Request.header).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://example.com')
54
+ expect(mockOdac.Request.header).toHaveBeenCalledWith('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
54
55
  })
55
56
 
56
57
  it('should handle token request with route but no page defined', async () => {
57
- const mockCandy = {
58
+ const mockOdac = {
58
59
  Request: {
59
60
  url: '/',
60
61
  method: 'get',
@@ -63,16 +64,17 @@ describe('Route', () => {
63
64
  host: 'example.com',
64
65
  header: jest.fn(key => {
65
66
  const headers = {
66
- 'X-Candy': 'token',
67
+ 'X-Odac': 'token',
67
68
  Referer: 'https://example.com/',
68
- 'X-Candy-Client': 'test-client'
69
+ 'X-Odac-Client': 'test-client'
69
70
  }
70
71
  return headers[key]
71
72
  }),
72
73
  cookie: jest.fn(key => {
73
- if (key === 'candy_client') return 'test-client'
74
+ if (key === 'odac_client') return 'test-client'
74
75
  return null
75
- })
76
+ }),
77
+ abort: jest.fn()
76
78
  },
77
79
  token: jest.fn(() => 'test-token-2')
78
80
  }
@@ -81,7 +83,7 @@ describe('Route', () => {
81
83
  test_route: {}
82
84
  }
83
85
 
84
- const result = await route.check(mockCandy)
86
+ const result = await route.check(mockOdac)
85
87
 
86
88
  expect(result).toBeDefined()
87
89
  expect(result.token).toBe('test-token-2')
@@ -89,7 +91,7 @@ describe('Route', () => {
89
91
  })
90
92
 
91
93
  it('should handle token request with route and page but no url match', async () => {
92
- const mockCandy = {
94
+ const mockOdac = {
93
95
  Request: {
94
96
  url: '/',
95
97
  method: 'get',
@@ -98,16 +100,17 @@ describe('Route', () => {
98
100
  host: 'example.com',
99
101
  header: jest.fn(key => {
100
102
  const headers = {
101
- 'X-Candy': 'token',
103
+ 'X-Odac': 'token',
102
104
  Referer: 'http://example.com/',
103
- 'X-Candy-Client': 'test-client'
105
+ 'X-Odac-Client': 'test-client'
104
106
  }
105
107
  return headers[key]
106
108
  }),
107
109
  cookie: jest.fn(key => {
108
- if (key === 'candy_client') return 'test-client'
110
+ if (key === 'odac_client') return 'test-client'
109
111
  return null
110
- })
112
+ }),
113
+ abort: jest.fn()
111
114
  },
112
115
  token: jest.fn(() => 'test-token-3')
113
116
  }
@@ -120,7 +123,7 @@ describe('Route', () => {
120
123
  }
121
124
  }
122
125
 
123
- const result = await route.check(mockCandy)
126
+ const result = await route.check(mockOdac)
124
127
 
125
128
  expect(result).toBeDefined()
126
129
  expect(result.token).toBe('test-token-3')
@@ -128,7 +131,7 @@ describe('Route', () => {
128
131
  })
129
132
 
130
133
  it('should not return token when referer does not match', async () => {
131
- const mockCandy = {
134
+ const mockOdac = {
132
135
  Request: {
133
136
  url: '/',
134
137
  method: 'get',
@@ -137,14 +140,14 @@ describe('Route', () => {
137
140
  host: 'example.com',
138
141
  header: jest.fn(key => {
139
142
  const headers = {
140
- 'X-Candy': 'token',
143
+ 'X-Odac': 'token',
141
144
  Referer: 'http://malicious.com/',
142
- 'X-Candy-Client': 'test-client'
145
+ 'X-Odac-Client': 'test-client'
143
146
  }
144
147
  return headers[key]
145
148
  }),
146
149
  cookie: jest.fn(key => {
147
- if (key === 'candy_client') return 'test-client'
150
+ if (key === 'odac_client') return 'test-client'
148
151
  return null
149
152
  }),
150
153
  abort: jest.fn()
@@ -159,13 +162,13 @@ describe('Route', () => {
159
162
  }
160
163
  }
161
164
 
162
- await route.check(mockCandy)
165
+ await route.check(mockOdac)
163
166
 
164
- expect(mockCandy.Request.header).not.toHaveBeenCalledWith('Access-Control-Allow-Origin', expect.any(String))
167
+ expect(mockOdac.Request.header).not.toHaveBeenCalledWith('Access-Control-Allow-Origin', expect.any(String))
165
168
  })
166
169
 
167
170
  it('should not return token when client cookie does not match', async () => {
168
- const mockCandy = {
171
+ const mockOdac = {
169
172
  Request: {
170
173
  url: '/',
171
174
  method: 'get',
@@ -174,14 +177,14 @@ describe('Route', () => {
174
177
  host: 'example.com',
175
178
  header: jest.fn(key => {
176
179
  const headers = {
177
- 'X-Candy': 'token',
180
+ 'X-Odac': 'token',
178
181
  Referer: 'http://example.com/',
179
- 'X-Candy-Client': 'test-client'
182
+ 'X-Odac-Client': 'test-client'
180
183
  }
181
184
  return headers[key]
182
185
  }),
183
186
  cookie: jest.fn(key => {
184
- if (key === 'candy_client') return 'different-client'
187
+ if (key === 'odac_client') return 'different-client'
185
188
  return null
186
189
  }),
187
190
  abort: jest.fn()
@@ -196,15 +199,15 @@ describe('Route', () => {
196
199
  }
197
200
  }
198
201
 
199
- await route.check(mockCandy)
202
+ await route.check(mockOdac)
200
203
 
201
- expect(mockCandy.Request.header).not.toHaveBeenCalledWith('Access-Control-Allow-Origin', expect.any(String))
204
+ expect(mockOdac.Request.header).not.toHaveBeenCalledWith('Access-Control-Allow-Origin', expect.any(String))
202
205
  })
203
206
  })
204
207
 
205
208
  describe('set', () => {
206
209
  it('should register a route with function handler', () => {
207
- global.Candy.Route.buff = 'test_route'
210
+ global.Odac.Route.buff = 'test_route'
208
211
  const handler = jest.fn()
209
212
 
210
213
  route.set('get', '/test', handler)
@@ -217,7 +220,7 @@ describe('Route', () => {
217
220
  })
218
221
 
219
222
  it('should handle array of methods', () => {
220
- global.Candy.Route.buff = 'test_route'
223
+ global.Odac.Route.buff = 'test_route'
221
224
  const handler = jest.fn()
222
225
 
223
226
  route.set(['get', 'post'], '/test', handler)
@@ -227,7 +230,7 @@ describe('Route', () => {
227
230
  })
228
231
 
229
232
  it('should strip trailing slash from url', () => {
230
- global.Candy.Route.buff = 'test_route'
233
+ global.Odac.Route.buff = 'test_route'
231
234
  const handler = jest.fn()
232
235
 
233
236
  route.set('get', '/test/', handler)
@@ -236,4 +239,20 @@ describe('Route', () => {
236
239
  expect(route.routes.test_route.get['/test/']).toBeUndefined()
237
240
  })
238
241
  })
242
+
243
+ describe('WebSocket cleanup', () => {
244
+ it('should call ws() method successfully', () => {
245
+ const handler = jest.fn()
246
+ expect(() => {
247
+ route.ws('/test', handler, {token: false})
248
+ }).not.toThrow()
249
+ })
250
+
251
+ it('should call auth.ws() method successfully', () => {
252
+ const handler = jest.fn()
253
+ expect(() => {
254
+ route.auth.ws('/test', handler)
255
+ }).not.toThrow()
256
+ })
257
+ })
239
258
  })
@@ -245,7 +245,7 @@ describe('EarlyHints', () => {
245
245
 
246
246
  const result = earlyHints.send(mockRes, resources)
247
247
  expect(result).toBe(true)
248
- expect(mockRes.setHeader).toHaveBeenCalledWith('X-Candy-Early-Hints', JSON.stringify(['</css/main.css>; rel=preload; as=style']))
248
+ expect(mockRes.setHeader).toHaveBeenCalledWith('X-Odac-Early-Hints', JSON.stringify(['</css/main.css>; rel=preload; as=style']))
249
249
  })
250
250
 
251
251
  it('should send early hints successfully', () => {
@@ -262,7 +262,7 @@ describe('EarlyHints', () => {
262
262
  expect(mockRes.writeEarlyHints).toHaveBeenCalledWith({
263
263
  link: ['</css/main.css>; rel=preload; as=style']
264
264
  })
265
- expect(mockRes.setHeader).toHaveBeenCalledWith('X-Candy-Early-Hints', JSON.stringify(['</css/main.css>; rel=preload; as=style']))
265
+ expect(mockRes.setHeader).toHaveBeenCalledWith('X-Odac-Early-Hints', JSON.stringify(['</css/main.css>; rel=preload; as=style']))
266
266
  })
267
267
 
268
268
  it('should handle writeEarlyHints errors gracefully', () => {
@@ -0,0 +1,100 @@
1
+ const {WebSocketServer} = require('../../framework/src/WebSocket.js')
2
+
3
+ describe('WebSocketServer', () => {
4
+ let server
5
+
6
+ beforeEach(() => {
7
+ server = new WebSocketServer()
8
+ })
9
+
10
+ describe('route', () => {
11
+ it('should register a route', () => {
12
+ const handler = jest.fn()
13
+ server.route('/chat', handler)
14
+ expect(server.getRoute('/chat')).toBe(handler)
15
+ })
16
+
17
+ it('should return null for unregistered route', () => {
18
+ expect(server.getRoute('/unknown')).toBeNull()
19
+ })
20
+
21
+ it('should match parameterized routes', () => {
22
+ const handler = jest.fn()
23
+ server.route('/room/{id}', handler)
24
+
25
+ const result = server.getRoute('/room/123')
26
+ expect(result).toBeDefined()
27
+ expect(result.handler).toBe(handler)
28
+ expect(result.params).toEqual({id: '123'})
29
+ })
30
+
31
+ it('should match multiple parameters', () => {
32
+ const handler = jest.fn()
33
+ server.route('/chat/{room}/user/{userId}', handler)
34
+
35
+ const result = server.getRoute('/chat/general/user/42')
36
+ expect(result.params).toEqual({room: 'general', userId: '42'})
37
+ })
38
+ })
39
+
40
+ describe('rooms', () => {
41
+ it('should join and leave rooms', () => {
42
+ server.joinRoom('client1', 'room1')
43
+ server.joinRoom('client2', 'room1')
44
+
45
+ server.leaveRoom('client1', 'room1')
46
+ server.leaveRoom('client2', 'room1')
47
+ })
48
+ })
49
+
50
+ describe('broadcast', () => {
51
+ it('should have broadcast method', () => {
52
+ expect(typeof server.broadcast).toBe('function')
53
+ })
54
+ })
55
+
56
+ describe('clients', () => {
57
+ it('should track client count', () => {
58
+ expect(server.clientCount).toBe(0)
59
+ })
60
+ })
61
+
62
+ describe('cleanup on disconnect', () => {
63
+ it('should be handled by Route.setWs wrapper', () => {
64
+ expect(true).toBe(true)
65
+ })
66
+ })
67
+ })
68
+
69
+ describe('Route WebSocket Integration', () => {
70
+ const Route = require('../../framework/src/Route.js')
71
+
72
+ beforeEach(() => {
73
+ global.Odac = {
74
+ Route: new Route()
75
+ }
76
+ })
77
+
78
+ it('should support ws() method', () => {
79
+ expect(typeof Odac.Route.ws).toBe('function')
80
+ })
81
+
82
+ it('should support auth.ws() method', () => {
83
+ expect(typeof Odac.Route.auth.ws).toBe('function')
84
+ })
85
+
86
+ it('should support middleware with ws()', () => {
87
+ const chain = Odac.Route.use('test-middleware')
88
+ expect(typeof chain.ws).toBe('function')
89
+ })
90
+
91
+ it('should support middleware with auth.ws()', () => {
92
+ const chain = Odac.Route.use('test-middleware')
93
+ expect(typeof chain.auth.ws).toBe('function')
94
+ })
95
+
96
+ it('should support auth.use() with ws()', () => {
97
+ const chain = Odac.Route.auth.use('test-middleware')
98
+ expect(typeof chain.ws).toBe('function')
99
+ })
100
+ })
@@ -0,0 +1,85 @@
1
+ const Route = require('../../framework/src/Route.js')
2
+
3
+ describe('Middleware System', () => {
4
+ let route
5
+
6
+ beforeEach(() => {
7
+ route = new Route()
8
+ global.Odac = {Route: {buff: 'test'}}
9
+ global.__dir = __dirname
10
+ })
11
+
12
+ test('use() should return MiddlewareChain', () => {
13
+ const chain = route.use('auth', 'logger')
14
+ expect(chain).not.toBe(route)
15
+ expect(chain._middlewares).toEqual(['auth', 'logger'])
16
+ })
17
+
18
+ test('use() should support chaining with more middlewares', () => {
19
+ const chain = route.use('auth').use('logger')
20
+ expect(chain._middlewares).toEqual(['auth', 'logger'])
21
+ })
22
+
23
+ test('page() should return this for chaining', () => {
24
+ const result = route.page('/', 'index')
25
+ expect(result).toBe(route)
26
+ })
27
+
28
+ test('post() should return this for chaining', () => {
29
+ const result = route.post('/api', 'api')
30
+ expect(result).toBe(route)
31
+ })
32
+
33
+ test('get() should return this for chaining', () => {
34
+ const result = route.get('/api', 'api')
35
+ expect(result).toBe(route)
36
+ })
37
+
38
+ test('auth.use() should return MiddlewareChain', () => {
39
+ const chain = route.auth.use('admin')
40
+ expect(chain).not.toBe(route)
41
+ expect(chain._middlewares).toEqual(['admin'])
42
+ })
43
+
44
+ test('chaining should work: use().page().page()', () => {
45
+ route
46
+ .use('auth')
47
+ .page('/profile', () => {})
48
+ .page('/settings', () => {})
49
+ expect(route.routes.test.page['/profile'].middlewares).toEqual(['auth'])
50
+ expect(route.routes.test.page['/settings'].middlewares).toEqual(['auth'])
51
+ })
52
+
53
+ test('chaining should work: auth.use().page()', () => {
54
+ route.auth.use('admin').page('/admin', () => {})
55
+ expect(route.routes.test.page['/admin'].middlewares).toEqual(['admin'])
56
+ })
57
+
58
+ test('middlewares should be attached to routes', () => {
59
+ route
60
+ .use('auth')
61
+ .page('/profile', () => {})
62
+ .page('/settings', () => {})
63
+ expect(route.routes.test.page['/profile'].middlewares).toEqual(['auth'])
64
+ expect(route.routes.test.page['/settings'].middlewares).toEqual(['auth'])
65
+ })
66
+
67
+ test('routes without use() should have no middlewares', () => {
68
+ route.use('auth').page('/profile', () => {})
69
+ route.page('/public', () => {})
70
+ expect(route.routes.test.page['/profile'].middlewares).toEqual(['auth'])
71
+ expect(route.routes.test.page['/public'].middlewares).toBeUndefined()
72
+ })
73
+
74
+ test('multiple middlewares should be attached', () => {
75
+ route.use('cors', 'rateLimit').post('/api/upload', () => {})
76
+ expect(route.routes.test.post['/api/upload'].middlewares).toEqual(['cors', 'rateLimit'])
77
+ })
78
+
79
+ test('separate use() chains should be independent', () => {
80
+ route.use('auth').page('/profile', () => {})
81
+ route.use('cors').page('/api', () => {})
82
+ expect(route.routes.test.page['/profile'].middlewares).toEqual(['auth'])
83
+ expect(route.routes.test.page['/api'].middlewares).toEqual(['cors'])
84
+ })
85
+ })
@@ -16,8 +16,8 @@ describe('Api', () => {
16
16
  setupGlobalMocks()
17
17
 
18
18
  // Set up the Log mock before requiring Api
19
- const {mockCandy} = require('./__mocks__/globalCandy')
20
- mockCandy.setMock('core', 'Log', {
19
+ const {mockOdac} = require('./__mocks__/globalOdac')
20
+ mockOdac.setMock('core', 'Log', {
21
21
  init: jest.fn().mockReturnValue({
22
22
  log: mockLog,
23
23
  error: mockError
@@ -52,12 +52,12 @@ describe('Api', () => {
52
52
  describe('initialization', () => {
53
53
  it('should initialize api config if not exists', () => {
54
54
  // Clear the api config
55
- global.Candy.core('Config').config.api = undefined
55
+ global.Odac.core('Config').config.api = undefined
56
56
 
57
57
  Api.init()
58
58
 
59
- expect(global.Candy.core('Config').config.api).toBeDefined()
60
- expect(global.Candy.core('Config').config.api.auth).toBeDefined()
59
+ expect(global.Odac.core('Config').config.api).toBeDefined()
60
+ expect(global.Odac.core('Config').config.api.auth).toBeDefined()
61
61
  })
62
62
 
63
63
  it('should create TCP server and set up handlers', () => {
@@ -300,7 +300,7 @@ describe('Api', () => {
300
300
  }
301
301
 
302
302
  const payload = JSON.stringify({
303
- auth: global.Candy.core('Config').config.api.auth,
303
+ auth: global.Odac.core('Config').config.api.auth,
304
304
  action: 'invalid.action',
305
305
  data: []
306
306
  })
@@ -318,7 +318,7 @@ describe('Api', () => {
318
318
  }
319
319
 
320
320
  const payload = JSON.stringify({
321
- auth: global.Candy.core('Config').config.api.auth,
321
+ auth: global.Odac.core('Config').config.api.auth,
322
322
  data: []
323
323
  })
324
324
 
@@ -335,11 +335,11 @@ describe('Api', () => {
335
335
  }
336
336
 
337
337
  // Mock the Mail service
338
- const mockMailService = global.Candy.server('Mail')
338
+ const mockMailService = global.Odac.server('Mail')
339
339
  mockMailService.create.mockResolvedValue(Api.result(true, 'Account created'))
340
340
 
341
341
  const payload = JSON.stringify({
342
- auth: global.Candy.core('Config').config.api.auth,
342
+ auth: global.Odac.core('Config').config.api.auth,
343
343
  action: 'mail.create',
344
344
  data: ['test@example.com', 'password123']
345
345
  })
@@ -357,11 +357,11 @@ describe('Api', () => {
357
357
  return
358
358
  }
359
359
 
360
- const mockServiceService = global.Candy.server('Service')
360
+ const mockServiceService = global.Odac.server('Service')
361
361
  mockServiceService.start.mockResolvedValue(Api.result(true, 'Service started'))
362
362
 
363
363
  const payload = JSON.stringify({
364
- auth: global.Candy.core('Config').config.api.auth,
364
+ auth: global.Odac.core('Config').config.api.auth,
365
365
  action: 'service.start',
366
366
  data: ['my-service.js']
367
367
  })
@@ -379,11 +379,11 @@ describe('Api', () => {
379
379
  return
380
380
  }
381
381
 
382
- const mockServerService = global.Candy.server('Server')
382
+ const mockServerService = global.Odac.server('Server')
383
383
  mockServerService.stop.mockResolvedValue(Api.result(true, 'Server stopped'))
384
384
 
385
385
  const payload = JSON.stringify({
386
- auth: global.Candy.core('Config').config.api.auth,
386
+ auth: global.Odac.core('Config').config.api.auth,
387
387
  action: 'server.stop',
388
388
  data: []
389
389
  })
@@ -401,11 +401,11 @@ describe('Api', () => {
401
401
  return
402
402
  }
403
403
 
404
- const mockMailService = global.Candy.server('Mail')
404
+ const mockMailService = global.Odac.server('Mail')
405
405
  mockMailService.create.mockRejectedValue(new Error('Database connection failed'))
406
406
 
407
407
  const payload = JSON.stringify({
408
- auth: global.Candy.core('Config').config.api.auth,
408
+ auth: global.Odac.core('Config').config.api.auth,
409
409
  action: 'mail.create',
410
410
  data: ['test@example.com', 'password123']
411
411
  })
@@ -423,11 +423,11 @@ describe('Api', () => {
423
423
  return
424
424
  }
425
425
 
426
- const mockMailService = global.Candy.server('Mail')
426
+ const mockMailService = global.Odac.server('Mail')
427
427
  mockMailService.list.mockResolvedValue(Api.result(true, []))
428
428
 
429
429
  const payload = JSON.stringify({
430
- auth: global.Candy.core('Config').config.api.auth,
430
+ auth: global.Odac.core('Config').config.api.auth,
431
431
  action: 'mail.list'
432
432
  // No data parameter
433
433
  })
@@ -445,14 +445,14 @@ describe('Api', () => {
445
445
  return
446
446
  }
447
447
 
448
- const mockMailService = global.Candy.server('Mail')
448
+ const mockMailService = global.Odac.server('Mail')
449
449
  mockMailService.delete.mockResolvedValue(Api.result(true, 'Deleted'))
450
450
  mockMailService.password.mockResolvedValue(Api.result(true, 'Password changed'))
451
451
  mockMailService.send.mockResolvedValue(Api.result(true, 'Email sent'))
452
452
 
453
453
  // Test mail.delete
454
454
  let payload = JSON.stringify({
455
- auth: global.Candy.core('Config').config.api.auth,
455
+ auth: global.Odac.core('Config').config.api.auth,
456
456
  action: 'mail.delete',
457
457
  data: ['test@example.com']
458
458
  })
@@ -462,7 +462,7 @@ describe('Api', () => {
462
462
 
463
463
  // Test mail.password
464
464
  payload = JSON.stringify({
465
- auth: global.Candy.core('Config').config.api.auth,
465
+ auth: global.Odac.core('Config').config.api.auth,
466
466
  action: 'mail.password',
467
467
  data: ['test@example.com', 'newpassword']
468
468
  })
@@ -472,7 +472,7 @@ describe('Api', () => {
472
472
 
473
473
  // Test mail.send
474
474
  payload = JSON.stringify({
475
- auth: global.Candy.core('Config').config.api.auth,
475
+ auth: global.Odac.core('Config').config.api.auth,
476
476
  action: 'mail.send',
477
477
  data: ['test@example.com', 'Subject', 'Body']
478
478
  })
@@ -487,14 +487,14 @@ describe('Api', () => {
487
487
  return
488
488
  }
489
489
 
490
- const mockSubdomainService = global.Candy.server('Subdomain')
490
+ const mockSubdomainService = global.Odac.server('Subdomain')
491
491
  mockSubdomainService.create.mockResolvedValue(Api.result(true, 'Created'))
492
492
  mockSubdomainService.delete.mockResolvedValue(Api.result(true, 'Deleted'))
493
493
  mockSubdomainService.list.mockResolvedValue(Api.result(true, ['www', 'api']))
494
494
 
495
495
  // Test subdomain.create
496
496
  let payload = JSON.stringify({
497
- auth: global.Candy.core('Config').config.api.auth,
497
+ auth: global.Odac.core('Config').config.api.auth,
498
498
  action: 'subdomain.create',
499
499
  data: ['api.example.com']
500
500
  })
@@ -504,7 +504,7 @@ describe('Api', () => {
504
504
 
505
505
  // Test subdomain.delete
506
506
  payload = JSON.stringify({
507
- auth: global.Candy.core('Config').config.api.auth,
507
+ auth: global.Odac.core('Config').config.api.auth,
508
508
  action: 'subdomain.delete',
509
509
  data: ['api.example.com']
510
510
  })
@@ -514,7 +514,7 @@ describe('Api', () => {
514
514
 
515
515
  // Test subdomain.list
516
516
  payload = JSON.stringify({
517
- auth: global.Candy.core('Config').config.api.auth,
517
+ auth: global.Odac.core('Config').config.api.auth,
518
518
  action: 'subdomain.list',
519
519
  data: ['example.com']
520
520
  })
@@ -529,14 +529,14 @@ describe('Api', () => {
529
529
  return
530
530
  }
531
531
 
532
- const mockWebService = global.Candy.server('Web')
532
+ const mockWebService = global.Odac.server('Web')
533
533
  mockWebService.create.mockResolvedValue(Api.result(true, 'Created'))
534
534
  mockWebService.delete.mockResolvedValue(Api.result(true, 'Deleted'))
535
535
  mockWebService.list.mockResolvedValue(Api.result(true, ['example.com']))
536
536
 
537
537
  // Test web.create
538
538
  let payload = JSON.stringify({
539
- auth: global.Candy.core('Config').config.api.auth,
539
+ auth: global.Odac.core('Config').config.api.auth,
540
540
  action: 'web.create',
541
541
  data: ['example.com']
542
542
  })
@@ -546,7 +546,7 @@ describe('Api', () => {
546
546
 
547
547
  // Test web.delete
548
548
  payload = JSON.stringify({
549
- auth: global.Candy.core('Config').config.api.auth,
549
+ auth: global.Odac.core('Config').config.api.auth,
550
550
  action: 'web.delete',
551
551
  data: ['example.com']
552
552
  })
@@ -556,7 +556,7 @@ describe('Api', () => {
556
556
 
557
557
  // Test web.list
558
558
  payload = JSON.stringify({
559
- auth: global.Candy.core('Config').config.api.auth,
559
+ auth: global.Odac.core('Config').config.api.auth,
560
560
  action: 'web.list',
561
561
  data: []
562
562
  })
@@ -571,11 +571,11 @@ describe('Api', () => {
571
571
  return
572
572
  }
573
573
 
574
- const mockSSLService = global.Candy.server('SSL')
574
+ const mockSSLService = global.Odac.server('SSL')
575
575
  mockSSLService.renew.mockResolvedValue(Api.result(true, 'SSL renewed'))
576
576
 
577
577
  const payload = JSON.stringify({
578
- auth: global.Candy.core('Config').config.api.auth,
578
+ auth: global.Odac.core('Config').config.api.auth,
579
579
  action: 'ssl.renew',
580
580
  data: ['example.com']
581
581
  })