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.
- package/.github/workflows/auto-pr-description.yml +0 -2
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/release.yml +13 -6
- package/.github/workflows/test-coverage.yml +10 -9
- package/.releaserc.js +9 -6
- package/CHANGELOG.md +62 -150
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +8 -8
- package/LICENSE +21 -661
- package/README.md +12 -12
- package/SECURITY.md +4 -4
- package/bin/odac.js +101 -0
- package/{framework/web/candy.js → client/odac.js} +310 -44
- package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
- package/docs/backend/01-overview/03-development-server.md +11 -11
- package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +1 -1
- package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
- package/docs/backend/03-config/04-environment-variables.md +20 -20
- package/docs/backend/03-config/05-early-hints.md +4 -4
- package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
- package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
- package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
- package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
- package/docs/backend/04-routing/05-advanced-routing.md +3 -3
- package/docs/backend/04-routing/06-error-pages.md +17 -17
- package/docs/backend/04-routing/07-cron-jobs.md +13 -13
- package/docs/backend/04-routing/08-middleware.md +214 -0
- package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
- package/docs/backend/04-routing/09-websocket-examples.md +381 -0
- package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
- package/docs/backend/04-routing/09-websocket.md +298 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
- package/docs/backend/05-controllers/03-controller-classes.md +19 -19
- package/docs/backend/05-forms/01-custom-forms.md +114 -114
- package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
- package/docs/backend/07-views/01-the-view-directory.md +1 -1
- package/docs/backend/07-views/02-rendering-a-view.md +22 -22
- package/docs/backend/07-views/03-template-syntax.md +52 -52
- package/docs/backend/07-views/03-variables.md +84 -84
- package/docs/backend/07-views/04-request-data.md +57 -57
- package/docs/backend/07-views/05-conditionals.md +78 -78
- package/docs/backend/07-views/06-loops.md +114 -114
- package/docs/backend/07-views/07-translations.md +66 -66
- package/docs/backend/07-views/08-backend-javascript.md +103 -103
- package/docs/backend/07-views/09-comments.md +71 -71
- package/docs/backend/08-database/01-database-connection.md +8 -8
- package/docs/backend/08-database/02-using-mysql.md +49 -49
- package/docs/backend/09-validation/01-the-validator-service.md +38 -38
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
- package/docs/backend/10-authentication/03-register.md +12 -12
- package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
- package/docs/backend/10-authentication/05-session-management.md +10 -10
- package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
- package/docs/backend/11-mail/01-the-mail-service.md +5 -5
- package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
- package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
- package/docs/frontend/01-overview/01-introduction.md +30 -30
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
- package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
- package/docs/frontend/03-forms/01-form-handling.md +32 -32
- package/docs/frontend/04-api-requests/01-get-post.md +33 -33
- package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
- package/docs/frontend/06-websocket/00-overview.md +76 -0
- package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
- package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
- package/docs/index.json +49 -11
- package/eslint.config.mjs +6 -6
- package/{framework/index.js → index.js} +1 -1
- package/package.json +14 -39
- package/{framework/src → src}/Auth.js +59 -59
- package/{framework/src → src}/Config.js +3 -3
- package/{framework/src → src}/Lang.js +7 -7
- package/{framework/src → src}/Mail.js +5 -5
- package/{framework/src → src}/Mysql.js +42 -42
- package/src/Odac.js +112 -0
- package/{framework/src → src}/Request.js +38 -36
- package/{framework/src → src}/Route/Internal.js +116 -116
- package/src/Route/Middleware.js +75 -0
- package/src/Route.js +621 -0
- package/src/Server.js +22 -0
- package/{framework/src → src}/Stream.js +11 -3
- package/{framework/src → src}/Validator.js +21 -21
- package/{framework/src → src}/Var.js +5 -5
- package/{framework/src → src}/View/EarlyHints.js +1 -1
- package/{framework/src → src}/View/Form.js +69 -69
- package/{framework/src → src}/View.js +78 -81
- package/src/WebSocket.js +403 -0
- package/template/config.json +5 -0
- package/{web → template}/controller/page/about.js +6 -6
- package/{web → template}/controller/page/index.js +9 -9
- package/{web → template}/package.json +4 -5
- package/{web → template}/public/assets/css/style.css +4 -4
- package/{web → template}/public/assets/js/app.js +6 -6
- package/{web → template}/route/www.js +6 -6
- package/{web → template}/skeleton/main.html +1 -1
- package/{web → template}/view/content/about.html +5 -5
- package/{web → template}/view/content/home.html +12 -12
- package/template/view/footer/main.html +11 -0
- package/{web → template}/view/head/main.html +1 -1
- package/{web → template}/view/header/main.html +2 -2
- package/test/core/Candy.test.js +58 -58
- package/test/core/Commands.test.js +7 -7
- package/test/core/Config.test.js +82 -85
- package/test/core/Lang.test.js +2 -2
- package/test/core/Process.test.js +6 -6
- package/test/framework/Route.test.js +56 -37
- package/test/framework/View/EarlyHints.test.js +2 -2
- package/test/framework/WebSocket.test.js +100 -0
- package/test/framework/middleware.test.js +85 -0
- package/test/server/Api.test.js +31 -31
- package/test/server/DNS.test.js +11 -11
- package/test/server/Hub.test.js +497 -0
- package/test/server/Mail.account.test_.js +3 -3
- package/test/server/Mail.init.test_.js +10 -10
- package/test/server/Mail.test_.js +20 -20
- package/test/server/SSL.test_.js +54 -54
- package/test/server/Server.test.js +39 -39
- package/test/server/Service.test_.js +7 -7
- package/test/server/Subdomain.test.js +7 -7
- package/test/server/Web/Firewall.test.js +87 -87
- package/test/server/Web/Proxy.test.js +397 -0
- package/test/server/{Web.test_.js → Web.test.js} +137 -205
- package/test/server/__mocks__/fs.js +2 -2
- package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
- package/test/server/__mocks__/index.js +6 -6
- package/test/server/__mocks__/testFactories.js +1 -1
- package/test/server/__mocks__/testHelpers.js +7 -7
- package/.husky/pre-commit +0 -2
- package/.kiro/steering/code-style.md +0 -56
- package/.kiro/steering/product.md +0 -20
- package/.kiro/steering/structure.md +0 -77
- package/.kiro/steering/tech.md +0 -87
- package/AGENTS.md +0 -84
- package/bin/candy +0 -10
- package/bin/candypack +0 -10
- package/cli/index.js +0 -3
- package/cli/src/Cli.js +0 -348
- package/cli/src/Connector.js +0 -93
- package/cli/src/Monitor.js +0 -416
- package/core/Candy.js +0 -87
- package/core/Commands.js +0 -239
- package/core/Config.js +0 -1094
- package/core/Lang.js +0 -52
- package/core/Log.js +0 -43
- package/core/Process.js +0 -26
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
- package/docs/server/01-installation/01-quick-install.md +0 -19
- package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
- package/docs/server/02-get-started/01-core-concepts.md +0 -7
- package/docs/server/02-get-started/02-basic-commands.md +0 -57
- package/docs/server/02-get-started/03-cli-reference.md +0 -276
- package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
- package/docs/server/03-service/01-start-a-new-service.md +0 -57
- package/docs/server/03-service/02-delete-a-service.md +0 -48
- package/docs/server/04-web/01-create-a-website.md +0 -36
- package/docs/server/04-web/02-list-websites.md +0 -9
- package/docs/server/04-web/03-delete-a-website.md +0 -29
- package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
- package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
- package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
- package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
- package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
- package/docs/server/07-mail/04-change-account-password.md +0 -23
- package/framework/src/Candy.js +0 -81
- package/framework/src/Route.js +0 -455
- package/framework/src/Server.js +0 -15
- package/locale/de-DE.json +0 -80
- package/locale/en-US.json +0 -79
- package/locale/es-ES.json +0 -80
- package/locale/fr-FR.json +0 -80
- package/locale/pt-BR.json +0 -80
- package/locale/ru-RU.json +0 -80
- package/locale/tr-TR.json +0 -85
- package/locale/zh-CN.json +0 -80
- package/server/index.js +0 -5
- package/server/src/Api.js +0 -88
- package/server/src/DNS.js +0 -940
- package/server/src/Hub.js +0 -535
- package/server/src/Mail.js +0 -571
- package/server/src/SSL.js +0 -180
- package/server/src/Server.js +0 -27
- package/server/src/Service.js +0 -248
- package/server/src/Subdomain.js +0 -64
- package/server/src/Web/Firewall.js +0 -170
- package/server/src/Web/Proxy.js +0 -134
- package/server/src/Web.js +0 -451
- package/server/src/mail/imap.js +0 -1091
- package/server/src/mail/server.js +0 -32
- package/server/src/mail/smtp.js +0 -786
- package/test/server/Client.test.js +0 -338
- package/test/server/__mocks__/http-proxy.js +0 -105
- package/watchdog/index.js +0 -3
- package/watchdog/src/Watchdog.js +0 -156
- package/web/config.json +0 -5
- package/web/view/footer/main.html +0 -11
- /package/{framework/src → src}/Env.js +0 -0
- /package/{framework/src → src}/Route/Cron.js +0 -0
- /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.
|
|
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.
|
|
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
|
|
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-
|
|
31
|
+
'X-Odac': 'token',
|
|
32
32
|
Referer: 'http://example.com/',
|
|
33
|
-
'X-
|
|
33
|
+
'X-Odac-Client': 'test-client'
|
|
34
34
|
}
|
|
35
35
|
return headers[key]
|
|
36
36
|
}),
|
|
37
37
|
cookie: jest.fn(key => {
|
|
38
|
-
if (key === '
|
|
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(
|
|
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(
|
|
53
|
-
expect(
|
|
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
|
|
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-
|
|
67
|
+
'X-Odac': 'token',
|
|
67
68
|
Referer: 'https://example.com/',
|
|
68
|
-
'X-
|
|
69
|
+
'X-Odac-Client': 'test-client'
|
|
69
70
|
}
|
|
70
71
|
return headers[key]
|
|
71
72
|
}),
|
|
72
73
|
cookie: jest.fn(key => {
|
|
73
|
-
if (key === '
|
|
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(
|
|
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
|
|
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-
|
|
103
|
+
'X-Odac': 'token',
|
|
102
104
|
Referer: 'http://example.com/',
|
|
103
|
-
'X-
|
|
105
|
+
'X-Odac-Client': 'test-client'
|
|
104
106
|
}
|
|
105
107
|
return headers[key]
|
|
106
108
|
}),
|
|
107
109
|
cookie: jest.fn(key => {
|
|
108
|
-
if (key === '
|
|
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(
|
|
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
|
|
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-
|
|
143
|
+
'X-Odac': 'token',
|
|
141
144
|
Referer: 'http://malicious.com/',
|
|
142
|
-
'X-
|
|
145
|
+
'X-Odac-Client': 'test-client'
|
|
143
146
|
}
|
|
144
147
|
return headers[key]
|
|
145
148
|
}),
|
|
146
149
|
cookie: jest.fn(key => {
|
|
147
|
-
if (key === '
|
|
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(
|
|
165
|
+
await route.check(mockOdac)
|
|
163
166
|
|
|
164
|
-
expect(
|
|
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
|
|
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-
|
|
180
|
+
'X-Odac': 'token',
|
|
178
181
|
Referer: 'http://example.com/',
|
|
179
|
-
'X-
|
|
182
|
+
'X-Odac-Client': 'test-client'
|
|
180
183
|
}
|
|
181
184
|
return headers[key]
|
|
182
185
|
}),
|
|
183
186
|
cookie: jest.fn(key => {
|
|
184
|
-
if (key === '
|
|
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(
|
|
202
|
+
await route.check(mockOdac)
|
|
200
203
|
|
|
201
|
-
expect(
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
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-
|
|
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
|
+
})
|
package/test/server/Api.test.js
CHANGED
|
@@ -16,8 +16,8 @@ describe('Api', () => {
|
|
|
16
16
|
setupGlobalMocks()
|
|
17
17
|
|
|
18
18
|
// Set up the Log mock before requiring Api
|
|
19
|
-
const {
|
|
20
|
-
|
|
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.
|
|
55
|
+
global.Odac.core('Config').config.api = undefined
|
|
56
56
|
|
|
57
57
|
Api.init()
|
|
58
58
|
|
|
59
|
-
expect(global.
|
|
60
|
-
expect(global.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
578
|
+
auth: global.Odac.core('Config').config.api.auth,
|
|
579
579
|
action: 'ssl.renew',
|
|
580
580
|
data: ['example.com']
|
|
581
581
|
})
|