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
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// Create mock log function first
|
|
2
|
-
const mockLog = jest.fn()
|
|
3
|
-
|
|
4
|
-
// Mock global Candy first
|
|
5
|
-
const {mockCandy} = require('./__mocks__/globalCandy')
|
|
6
|
-
|
|
7
|
-
// Set up the Log mock before setting global.Candy
|
|
8
|
-
mockCandy.setMock('core', 'Log', {
|
|
9
|
-
init: jest.fn().mockReturnValue({
|
|
10
|
-
log: mockLog,
|
|
11
|
-
error: jest.fn()
|
|
12
|
-
})
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
global.Candy = mockCandy
|
|
16
|
-
|
|
17
|
-
// Mock axios
|
|
18
|
-
jest.mock('axios')
|
|
19
|
-
const axios = require('axios')
|
|
20
|
-
|
|
21
|
-
const Client = require('../../server/src/Client')
|
|
22
|
-
|
|
23
|
-
describe('Client', () => {
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
jest.clearAllMocks()
|
|
26
|
-
|
|
27
|
-
// Reset config
|
|
28
|
-
mockCandy.setMock('core', 'Config', {
|
|
29
|
-
config: {}
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('authentication', () => {
|
|
34
|
-
it('should authenticate successfully with valid code', async () => {
|
|
35
|
-
// Arrange
|
|
36
|
-
const mockCode = 'valid-auth-code'
|
|
37
|
-
const mockResponse = {
|
|
38
|
-
data: {
|
|
39
|
-
result: {success: true},
|
|
40
|
-
data: {
|
|
41
|
-
token: 'test-token-123',
|
|
42
|
-
secret: 'test-secret-456'
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const mockConfig = {config: {}}
|
|
48
|
-
mockCandy.setMock('core', 'Config', mockConfig)
|
|
49
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
50
|
-
|
|
51
|
-
// Act
|
|
52
|
-
Client.auth(mockCode)
|
|
53
|
-
|
|
54
|
-
// Wait for promise to resolve
|
|
55
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
56
|
-
|
|
57
|
-
// Assert
|
|
58
|
-
expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
|
|
59
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/auth', {code: mockCode})
|
|
60
|
-
expect(mockConfig.config.auth).toEqual({
|
|
61
|
-
token: 'test-token-123',
|
|
62
|
-
secret: 'test-secret-456'
|
|
63
|
-
})
|
|
64
|
-
expect(mockLog).toHaveBeenCalledWith('CandyPack authenticated!')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('should handle authentication failure with error message', async () => {
|
|
68
|
-
// Arrange
|
|
69
|
-
const mockCode = 'invalid-code'
|
|
70
|
-
const mockError = 'Invalid authentication code'
|
|
71
|
-
|
|
72
|
-
axios.post.mockRejectedValue({
|
|
73
|
-
response: {data: mockError}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// Act
|
|
77
|
-
Client.auth(mockCode)
|
|
78
|
-
|
|
79
|
-
// Wait for promise to reject
|
|
80
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
81
|
-
|
|
82
|
-
// Assert
|
|
83
|
-
expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
|
|
84
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/auth', {code: mockCode})
|
|
85
|
-
expect(mockLog).toHaveBeenCalledWith(mockError)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('should handle authentication failure without error message', async () => {
|
|
89
|
-
// Arrange
|
|
90
|
-
const mockCode = 'invalid-code'
|
|
91
|
-
|
|
92
|
-
axios.post.mockRejectedValue({
|
|
93
|
-
response: {data: null}
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
// Act
|
|
97
|
-
Client.auth(mockCode)
|
|
98
|
-
|
|
99
|
-
// Wait for promise to reject
|
|
100
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
101
|
-
|
|
102
|
-
// Assert
|
|
103
|
-
expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
|
|
104
|
-
expect(mockLog).toHaveBeenCalledWith('CandyPack authentication failed!')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('should handle API response with success false', async () => {
|
|
108
|
-
// Arrange
|
|
109
|
-
const mockCode = 'valid-code'
|
|
110
|
-
const mockResponse = {
|
|
111
|
-
data: {
|
|
112
|
-
result: {
|
|
113
|
-
success: false,
|
|
114
|
-
message: 'Authentication failed on server'
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
120
|
-
|
|
121
|
-
// Act
|
|
122
|
-
Client.auth(mockCode)
|
|
123
|
-
|
|
124
|
-
// Wait for promise to reject
|
|
125
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
126
|
-
|
|
127
|
-
// Assert
|
|
128
|
-
expect(mockLog).toHaveBeenCalledWith('CandyPack authenticating...')
|
|
129
|
-
expect(mockLog).toHaveBeenCalledWith('Authentication failed on server')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('should store authentication credentials in config', async () => {
|
|
133
|
-
// Arrange
|
|
134
|
-
const mockCode = 'test-code'
|
|
135
|
-
const mockToken = 'stored-token'
|
|
136
|
-
const mockSecret = 'stored-secret'
|
|
137
|
-
const mockResponse = {
|
|
138
|
-
data: {
|
|
139
|
-
result: {success: true},
|
|
140
|
-
data: {
|
|
141
|
-
token: mockToken,
|
|
142
|
-
secret: mockSecret
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const mockConfig = {config: {existingProp: 'value'}}
|
|
148
|
-
mockCandy.setMock('core', 'Config', mockConfig)
|
|
149
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
150
|
-
|
|
151
|
-
// Act
|
|
152
|
-
Client.auth(mockCode)
|
|
153
|
-
|
|
154
|
-
// Wait for promise to resolve
|
|
155
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
|
156
|
-
|
|
157
|
-
// Assert
|
|
158
|
-
expect(mockConfig.config.auth).toEqual({
|
|
159
|
-
token: mockToken,
|
|
160
|
-
secret: mockSecret
|
|
161
|
-
})
|
|
162
|
-
// Verify existing config is preserved
|
|
163
|
-
expect(mockConfig.config.existingProp).toBe('value')
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
describe('API communication', () => {
|
|
168
|
-
it('should make successful API call and return data', async () => {
|
|
169
|
-
// Arrange
|
|
170
|
-
const mockAction = 'test-action'
|
|
171
|
-
const mockData = {param1: 'value1', param2: 'value2'}
|
|
172
|
-
const mockResponseData = {result: 'success', info: 'test data'}
|
|
173
|
-
const mockResponse = {
|
|
174
|
-
data: {
|
|
175
|
-
result: {success: true},
|
|
176
|
-
data: mockResponseData
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
181
|
-
|
|
182
|
-
// Act
|
|
183
|
-
const result = await Client.call(mockAction, mockData)
|
|
184
|
-
|
|
185
|
-
// Assert
|
|
186
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/test-action', mockData)
|
|
187
|
-
expect(result).toEqual(mockResponseData)
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('should handle API call with different endpoints', async () => {
|
|
191
|
-
// Arrange
|
|
192
|
-
const testCases = [
|
|
193
|
-
{action: 'users', data: {id: 1}},
|
|
194
|
-
{action: 'domains', data: {domain: 'example.com'}},
|
|
195
|
-
{action: 'ssl/renew', data: {cert: 'test.crt'}}
|
|
196
|
-
]
|
|
197
|
-
|
|
198
|
-
const mockResponseData = {status: 'ok'}
|
|
199
|
-
const mockResponse = {
|
|
200
|
-
data: {
|
|
201
|
-
result: {success: true},
|
|
202
|
-
data: mockResponseData
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
207
|
-
|
|
208
|
-
// Act & Assert
|
|
209
|
-
for (const testCase of testCases) {
|
|
210
|
-
const result = await Client.call(testCase.action, testCase.data)
|
|
211
|
-
expect(axios.post).toHaveBeenCalledWith(`https://api.candypack.dev/${testCase.action}`, testCase.data)
|
|
212
|
-
expect(result).toEqual(mockResponseData)
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('should reject when API response indicates failure', async () => {
|
|
217
|
-
// Arrange
|
|
218
|
-
const mockAction = 'failing-action'
|
|
219
|
-
const mockData = {test: 'data'}
|
|
220
|
-
const mockErrorMessage = 'API operation failed'
|
|
221
|
-
const mockResponse = {
|
|
222
|
-
data: {
|
|
223
|
-
result: {
|
|
224
|
-
success: false,
|
|
225
|
-
message: mockErrorMessage
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
231
|
-
|
|
232
|
-
// Act & Assert
|
|
233
|
-
await expect(Client.call(mockAction, mockData)).rejects.toBe(mockErrorMessage)
|
|
234
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/failing-action', mockData)
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('should handle network errors and log response data', async () => {
|
|
238
|
-
// Arrange
|
|
239
|
-
const mockAction = 'network-error'
|
|
240
|
-
const mockData = {test: 'data'}
|
|
241
|
-
const mockErrorData = {error: 'Network timeout', code: 500}
|
|
242
|
-
const mockError = {
|
|
243
|
-
response: {data: mockErrorData}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
axios.post.mockRejectedValue(mockError)
|
|
247
|
-
|
|
248
|
-
// Act & Assert
|
|
249
|
-
await expect(Client.call(mockAction, mockData)).rejects.toBe(mockErrorData)
|
|
250
|
-
expect(mockLog).toHaveBeenCalledWith(mockErrorData)
|
|
251
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/network-error', mockData)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('should handle axios errors without response data', async () => {
|
|
255
|
-
// Arrange
|
|
256
|
-
const mockAction = 'connection-error'
|
|
257
|
-
const mockData = {test: 'data'}
|
|
258
|
-
const mockError = {
|
|
259
|
-
response: {
|
|
260
|
-
data: 'Connection refused'
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
axios.post.mockRejectedValue(mockError)
|
|
265
|
-
|
|
266
|
-
// Act & Assert
|
|
267
|
-
await expect(Client.call(mockAction, mockData)).rejects.toBe('Connection refused')
|
|
268
|
-
expect(mockLog).toHaveBeenCalledWith('Connection refused')
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('should format API requests correctly', async () => {
|
|
272
|
-
// Arrange
|
|
273
|
-
const mockAction = 'format-test'
|
|
274
|
-
const mockData = {
|
|
275
|
-
string: 'test',
|
|
276
|
-
number: 123,
|
|
277
|
-
boolean: true,
|
|
278
|
-
object: {nested: 'value'},
|
|
279
|
-
array: [1, 2, 3]
|
|
280
|
-
}
|
|
281
|
-
const mockResponse = {
|
|
282
|
-
data: {
|
|
283
|
-
result: {success: true},
|
|
284
|
-
data: {received: 'ok'}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
289
|
-
|
|
290
|
-
// Act
|
|
291
|
-
await Client.call(mockAction, mockData)
|
|
292
|
-
|
|
293
|
-
// Assert
|
|
294
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/format-test', mockData)
|
|
295
|
-
expect(axios.post).toHaveBeenCalledTimes(1)
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
it('should handle empty response data', async () => {
|
|
299
|
-
// Arrange
|
|
300
|
-
const mockAction = 'empty-response'
|
|
301
|
-
const mockData = {test: 'data'}
|
|
302
|
-
const mockResponse = {
|
|
303
|
-
data: {
|
|
304
|
-
result: {success: true},
|
|
305
|
-
data: null
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
310
|
-
|
|
311
|
-
// Act
|
|
312
|
-
const result = await Client.call(mockAction, mockData)
|
|
313
|
-
|
|
314
|
-
// Assert
|
|
315
|
-
expect(result).toBeNull()
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
it('should handle API calls with no data parameter', async () => {
|
|
319
|
-
// Arrange
|
|
320
|
-
const mockAction = 'no-data'
|
|
321
|
-
const mockResponse = {
|
|
322
|
-
data: {
|
|
323
|
-
result: {success: true},
|
|
324
|
-
data: {message: 'success'}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
329
|
-
|
|
330
|
-
// Act
|
|
331
|
-
const result = await Client.call(mockAction)
|
|
332
|
-
|
|
333
|
-
// Assert
|
|
334
|
-
expect(axios.post).toHaveBeenCalledWith('https://api.candypack.dev/no-data', undefined)
|
|
335
|
-
expect(result).toEqual({message: 'success'})
|
|
336
|
-
})
|
|
337
|
-
})
|
|
338
|
-
})
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock implementation of http-proxy module for server tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const {createMockEventEmitter} = require('./testHelpers')
|
|
6
|
-
|
|
7
|
-
const createMockProxyServer = (options = {}) => {
|
|
8
|
-
const proxy = Object.assign(createMockEventEmitter(), {
|
|
9
|
-
options: options,
|
|
10
|
-
|
|
11
|
-
// Main proxy methods
|
|
12
|
-
web: jest.fn((req, res, options, callback) => {
|
|
13
|
-
// Simulate successful proxying
|
|
14
|
-
setTimeout(() => {
|
|
15
|
-
if (callback) callback()
|
|
16
|
-
}, 0)
|
|
17
|
-
}),
|
|
18
|
-
|
|
19
|
-
ws: jest.fn((req, socket, head, options, callback) => {
|
|
20
|
-
// Simulate WebSocket proxying
|
|
21
|
-
setTimeout(() => {
|
|
22
|
-
if (callback) callback()
|
|
23
|
-
}, 0)
|
|
24
|
-
}),
|
|
25
|
-
|
|
26
|
-
listen: jest.fn((port, hostname, callback) => {
|
|
27
|
-
if (typeof hostname === 'function') {
|
|
28
|
-
callback = hostname
|
|
29
|
-
hostname = undefined
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
setTimeout(() => {
|
|
33
|
-
if (callback) callback()
|
|
34
|
-
}, 0)
|
|
35
|
-
|
|
36
|
-
return proxy
|
|
37
|
-
}),
|
|
38
|
-
|
|
39
|
-
close: jest.fn(callback => {
|
|
40
|
-
setTimeout(() => {
|
|
41
|
-
if (callback) callback()
|
|
42
|
-
}, 0)
|
|
43
|
-
}),
|
|
44
|
-
|
|
45
|
-
// Test helper methods
|
|
46
|
-
simulateProxyReq: (proxyReq, req, res, options) => {
|
|
47
|
-
proxy.emit('proxyReq', proxyReq, req, res, options)
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
simulateProxyRes: (proxyRes, req, res) => {
|
|
51
|
-
proxy.emit('proxyRes', proxyRes, req, res)
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
simulateError: (error, req, res, target) => {
|
|
55
|
-
proxy.emit('error', error, req, res, target)
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
simulateOpen: proxySocket => {
|
|
59
|
-
proxy.emit('open', proxySocket)
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
simulateClose: (res, socket, head) => {
|
|
63
|
-
proxy.emit('close', res, socket, head)
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
return proxy
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const httpProxy = {
|
|
71
|
-
createProxyServer: jest.fn((options = {}) => {
|
|
72
|
-
return createMockProxyServer(options)
|
|
73
|
-
}),
|
|
74
|
-
|
|
75
|
-
createServer: jest.fn((options = {}, requestListener) => {
|
|
76
|
-
if (typeof options === 'function') {
|
|
77
|
-
requestListener = options
|
|
78
|
-
options = {}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const proxy = createMockProxyServer(options)
|
|
82
|
-
|
|
83
|
-
// Add server-like methods
|
|
84
|
-
proxy.listen = jest.fn((port, hostname, callback) => {
|
|
85
|
-
if (typeof hostname === 'function') {
|
|
86
|
-
callback = hostname
|
|
87
|
-
hostname = undefined
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
proxy.emit('listening')
|
|
92
|
-
if (callback) callback()
|
|
93
|
-
}, 0)
|
|
94
|
-
|
|
95
|
-
return proxy
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
return proxy
|
|
99
|
-
}),
|
|
100
|
-
|
|
101
|
-
// Test helpers
|
|
102
|
-
__createMockProxyServer: createMockProxyServer
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
module.exports = httpProxy
|
package/watchdog/index.js
DELETED
package/watchdog/src/Watchdog.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
const {spawn} = require('child_process')
|
|
2
|
-
const fs = require('fs').promises
|
|
3
|
-
const os = require('os')
|
|
4
|
-
const path = require('path')
|
|
5
|
-
|
|
6
|
-
// --- Constants ---
|
|
7
|
-
const CANDYPACK_HOME = path.join(os.homedir(), '.candypack')
|
|
8
|
-
const LOG_DIR = path.join(CANDYPACK_HOME, 'logs')
|
|
9
|
-
const SERVER_SCRIPT_PATH = path.join(__dirname, '..', '..', 'server', 'index.js')
|
|
10
|
-
|
|
11
|
-
const MAX_RESTARTS_IN_WINDOW = 100
|
|
12
|
-
const RESTART_WINDOW_MS = 1000 * 60 * 5 // 5 minutes
|
|
13
|
-
const SAVE_INTERVAL_MS = 1000 // 1 second
|
|
14
|
-
|
|
15
|
-
class Watchdog {
|
|
16
|
-
#logBuffer = ''
|
|
17
|
-
#errorBuffer = ''
|
|
18
|
-
#restartCount = 0
|
|
19
|
-
#lastRestartTimestamp = 0
|
|
20
|
-
#isSaving = false
|
|
21
|
-
|
|
22
|
-
init() {
|
|
23
|
-
// Set up periodic log saving. This is done only once.
|
|
24
|
-
setInterval(() => this.#saveLogs(), SAVE_INTERVAL_MS)
|
|
25
|
-
|
|
26
|
-
// Start the server for the first time.
|
|
27
|
-
this.#startServer()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Saves the buffered logs to their respective files.
|
|
32
|
-
* This function is designed to be called periodically.
|
|
33
|
-
* Keeps only the last 1000 lines in each log file.
|
|
34
|
-
*/
|
|
35
|
-
async #saveLogs() {
|
|
36
|
-
if (this.#isSaving) return
|
|
37
|
-
this.#isSaving = true
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
// Ensure log directory exists before attempting to write files
|
|
41
|
-
await fs.mkdir(LOG_DIR, {recursive: true})
|
|
42
|
-
const logFile = path.join(LOG_DIR, '.candypack.log')
|
|
43
|
-
const errFile = path.join(LOG_DIR, '.candypack_err.log')
|
|
44
|
-
|
|
45
|
-
// Limit log buffer to last 1000 lines
|
|
46
|
-
const logLines = this.#logBuffer.split('\n')
|
|
47
|
-
if (logLines.length > 1000) {
|
|
48
|
-
this.#logBuffer = logLines.slice(-1000).join('\n')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Limit error buffer to last 1000 lines
|
|
52
|
-
const errLines = this.#errorBuffer.split('\n')
|
|
53
|
-
if (errLines.length > 1000) {
|
|
54
|
-
this.#errorBuffer = errLines.slice(-1000).join('\n')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
await fs.writeFile(logFile, this.#logBuffer, 'utf8')
|
|
58
|
-
await fs.writeFile(errFile, this.#errorBuffer, 'utf8')
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error('Failed to save logs:', error)
|
|
61
|
-
} finally {
|
|
62
|
-
this.#isSaving = false
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Performs startup checks to ensure a clean environment.
|
|
68
|
-
* It kills any old watchdog or server processes that might still be running.
|
|
69
|
-
* It also creates the necessary configuration files and directories if they don't exist.
|
|
70
|
-
* @returns {Promise<boolean>} A promise that resolves to true if the checks pass.
|
|
71
|
-
*/
|
|
72
|
-
async #performStartupChecks() {
|
|
73
|
-
try {
|
|
74
|
-
// Kill previous watchdog process if it exists and is different from the current one
|
|
75
|
-
if (Candy.core('Config').config.server.watchdog && Candy.core('Config').config.server.watchdog !== process.pid)
|
|
76
|
-
await Candy.core('Process').stop(Candy.core('Config').config.server.watchdog)
|
|
77
|
-
|
|
78
|
-
// Kill previous server process if it exists
|
|
79
|
-
if (Candy.core('Config').config.server.pid) await Candy.core('Process').stop(Candy.core('Config').config.server.pid)
|
|
80
|
-
|
|
81
|
-
for (const domain of Object.keys(Candy.core('Config').config?.websites ?? []))
|
|
82
|
-
if (Candy.core('Config').config.websites[domain].pid)
|
|
83
|
-
await Candy.core('Process').stop(Candy.core('Config').config.websites[domain].pid)
|
|
84
|
-
|
|
85
|
-
for (const service of Candy.core('Config').config.services ?? []) if (service.pid) await Candy.core('Process').stop(service.pid)
|
|
86
|
-
|
|
87
|
-
// Update config with current watchdog's info
|
|
88
|
-
Candy.core('Config').config.server.watchdog = process.pid
|
|
89
|
-
Candy.core('Config').config.server.started = Date.now()
|
|
90
|
-
Candy.core('Config').force()
|
|
91
|
-
|
|
92
|
-
return new Promise(resolve => setTimeout(() => resolve(true), 1000))
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error('Error during startup checks:', error)
|
|
95
|
-
return false
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Starts the server process and sets up monitoring.
|
|
101
|
-
*/
|
|
102
|
-
async #startServer() {
|
|
103
|
-
const checksPassed = await this.#performStartupChecks()
|
|
104
|
-
if (!checksPassed) {
|
|
105
|
-
console.error('Startup checks failed. Aborting.')
|
|
106
|
-
process.exit(1)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Ensure log directory exists before starting
|
|
110
|
-
await fs.mkdir(LOG_DIR, {recursive: true})
|
|
111
|
-
|
|
112
|
-
const child = spawn('node', [SERVER_SCRIPT_PATH])
|
|
113
|
-
|
|
114
|
-
process.on('exit', () => child.kill())
|
|
115
|
-
|
|
116
|
-
Candy.core('Config').config.server.pid = child.pid
|
|
117
|
-
|
|
118
|
-
console.log(`Watchdog process started with PID: ${process.pid}`)
|
|
119
|
-
console.log(`Server process started with PID: ${child.pid}`)
|
|
120
|
-
|
|
121
|
-
child.stdout.on('data', data => {
|
|
122
|
-
this.#logBuffer += `[LOG][${new Date().toISOString()}] ${data.toString()}`
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
child.stderr.on('data', data => {
|
|
126
|
-
this.#logBuffer += `[ERR][${new Date().toISOString()}] ${data.toString()}`
|
|
127
|
-
this.#errorBuffer += `[ERR][${new Date().toISOString()}] ${data.toString()}`
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
child.on('close', code => {
|
|
131
|
-
Candy.core('Config').reload()
|
|
132
|
-
this.#errorBuffer += `[ERR][${new Date().toISOString()}] Process closed with code ${code}\n`
|
|
133
|
-
|
|
134
|
-
// Reset restart count if the last restart was a while ago
|
|
135
|
-
if (Date.now() - this.#lastRestartTimestamp > RESTART_WINDOW_MS) {
|
|
136
|
-
this.#restartCount = 0
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
this.#restartCount++
|
|
140
|
-
this.#lastRestartTimestamp = Date.now()
|
|
141
|
-
|
|
142
|
-
// If restart limit is not exceeded, restart the server
|
|
143
|
-
if (this.#restartCount < MAX_RESTARTS_IN_WINDOW) {
|
|
144
|
-
console.log('Server process closed. Restarting...')
|
|
145
|
-
// Relaunch the server process without setting up new intervals
|
|
146
|
-
this.#startServer()
|
|
147
|
-
} else {
|
|
148
|
-
console.error('Server has crashed too many times. Not restarting.')
|
|
149
|
-
// Final attempt to save logs before exiting
|
|
150
|
-
this.#saveLogs().then(() => process.exit(1))
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
module.exports = new Watchdog()
|
package/web/config.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<!-- Footer -->
|
|
2
|
-
<div class="footer">
|
|
3
|
-
<div class="container">
|
|
4
|
-
<p>Powered by <strong>CandyPack</strong> - A next-generation server and framework toolkit</p>
|
|
5
|
-
<p class="footer-links">
|
|
6
|
-
<a href="https://github.com/candypack" target="_blank" data-navigate="false">GitHub</a> •
|
|
7
|
-
<a href="https://docs.candypack.dev" target="_blank" data-navigate="false">Documentation</a> •
|
|
8
|
-
<a href="https://candypack.dev" target="_blank" data-navigate="false">Official Site</a>
|
|
9
|
-
</p>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|