odac 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- package/web/view/header/main.html +14 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
// Create a test version of the Server class
|
|
2
|
+
class TestServer {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.mockConfig = {
|
|
5
|
+
server: {
|
|
6
|
+
pid: null,
|
|
7
|
+
started: null
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
this.mockService = {
|
|
12
|
+
check: jest.fn(),
|
|
13
|
+
stopAll: jest.fn()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.mockDNS = {}
|
|
17
|
+
this.mockWeb = {
|
|
18
|
+
check: jest.fn(),
|
|
19
|
+
stopAll: jest.fn()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.mockMail = {
|
|
23
|
+
check: jest.fn()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.mockApi = {}
|
|
27
|
+
this.mockSSL = {
|
|
28
|
+
check: jest.fn()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Store original global Candy if it exists
|
|
32
|
+
this.originalCandy = global.Candy
|
|
33
|
+
|
|
34
|
+
// Setup isolated Candy mock for this instance
|
|
35
|
+
global.Candy = {
|
|
36
|
+
core: jest.fn(module => {
|
|
37
|
+
if (module === 'Config') {
|
|
38
|
+
return {config: this.mockConfig}
|
|
39
|
+
}
|
|
40
|
+
return {}
|
|
41
|
+
}),
|
|
42
|
+
server: jest.fn(module => {
|
|
43
|
+
switch (module) {
|
|
44
|
+
case 'Service':
|
|
45
|
+
return this.mockService
|
|
46
|
+
case 'DNS':
|
|
47
|
+
return this.mockDNS
|
|
48
|
+
case 'Web':
|
|
49
|
+
return this.mockWeb
|
|
50
|
+
case 'Mail':
|
|
51
|
+
return this.mockMail
|
|
52
|
+
case 'Api':
|
|
53
|
+
return this.mockApi
|
|
54
|
+
case 'SSL':
|
|
55
|
+
return this.mockSSL
|
|
56
|
+
default:
|
|
57
|
+
return {}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Initialize like the real Server
|
|
63
|
+
this.init()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
init() {
|
|
67
|
+
global.Candy.core('Config').config.server.pid = process.pid
|
|
68
|
+
global.Candy.core('Config').config.server.started = Date.now()
|
|
69
|
+
global.Candy.server('Service')
|
|
70
|
+
global.Candy.server('DNS')
|
|
71
|
+
global.Candy.server('Web')
|
|
72
|
+
global.Candy.server('Mail')
|
|
73
|
+
global.Candy.server('Api')
|
|
74
|
+
|
|
75
|
+
// Setup health checks
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
this.intervalId = setInterval(() => {
|
|
78
|
+
try {
|
|
79
|
+
global.Candy.server('Service').check()
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Ignore service check errors
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
global.Candy.server('SSL').check()
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Ignore SSL check errors
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
global.Candy.server('Web').check()
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// Ignore Web check errors
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
global.Candy.server('Mail').check()
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Ignore Mail check errors
|
|
97
|
+
}
|
|
98
|
+
}, 1000)
|
|
99
|
+
}, 1000)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
stop() {
|
|
103
|
+
try {
|
|
104
|
+
global.Candy.server('Service').stopAll()
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// Ignore service stop errors
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
global.Candy.server('Web').stopAll()
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// Ignore web stop errors
|
|
112
|
+
}
|
|
113
|
+
if (this.intervalId) {
|
|
114
|
+
clearInterval(this.intervalId)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
destroy() {
|
|
119
|
+
this.stop()
|
|
120
|
+
// Restore original Candy if it existed
|
|
121
|
+
if (this.originalCandy) {
|
|
122
|
+
global.Candy = this.originalCandy
|
|
123
|
+
} else {
|
|
124
|
+
delete global.Candy
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
describe('Server', () => {
|
|
130
|
+
let server
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
jest.clearAllMocks()
|
|
134
|
+
jest.clearAllTimers()
|
|
135
|
+
jest.useFakeTimers()
|
|
136
|
+
|
|
137
|
+
// Clear any existing global Candy
|
|
138
|
+
delete global.Candy
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
afterEach(() => {
|
|
142
|
+
if (server && server.destroy) {
|
|
143
|
+
server.destroy()
|
|
144
|
+
}
|
|
145
|
+
jest.useRealTimers()
|
|
146
|
+
delete global.Candy
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('initialization', () => {
|
|
150
|
+
test('should set server PID in config during construction', () => {
|
|
151
|
+
const originalPid = process.pid
|
|
152
|
+
|
|
153
|
+
// Set the PID before creating the server
|
|
154
|
+
Object.defineProperty(process, 'pid', {
|
|
155
|
+
value: 12345,
|
|
156
|
+
writable: true
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
server = new TestServer()
|
|
160
|
+
|
|
161
|
+
expect(server.mockConfig.server.pid).toBe(12345)
|
|
162
|
+
|
|
163
|
+
// Restore original PID
|
|
164
|
+
Object.defineProperty(process, 'pid', {
|
|
165
|
+
value: originalPid,
|
|
166
|
+
writable: true
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('should set server started timestamp during construction', () => {
|
|
171
|
+
const mockDate = 1640995200000 // 2022-01-01 00:00:00
|
|
172
|
+
jest.spyOn(Date, 'now').mockReturnValue(mockDate)
|
|
173
|
+
|
|
174
|
+
server = new TestServer()
|
|
175
|
+
|
|
176
|
+
expect(server.mockConfig.server.started).toBe(mockDate)
|
|
177
|
+
|
|
178
|
+
Date.now.mockRestore()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('should initialize all required services in correct order', () => {
|
|
182
|
+
server = new TestServer()
|
|
183
|
+
|
|
184
|
+
// Verify all services are initialized
|
|
185
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Service')
|
|
186
|
+
expect(global.Candy.server).toHaveBeenCalledWith('DNS')
|
|
187
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Web')
|
|
188
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Mail')
|
|
189
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Api')
|
|
190
|
+
|
|
191
|
+
// Verify call order
|
|
192
|
+
const serverCalls = global.Candy.server.mock.calls.map(call => call[0])
|
|
193
|
+
expect(serverCalls).toEqual(['Service', 'DNS', 'Web', 'Mail', 'Api'])
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('should setup periodic health checks after initialization delay', () => {
|
|
197
|
+
server = new TestServer()
|
|
198
|
+
|
|
199
|
+
// Initially no checks should be called
|
|
200
|
+
expect(server.mockService.check).not.toHaveBeenCalled()
|
|
201
|
+
expect(server.mockSSL.check).not.toHaveBeenCalled()
|
|
202
|
+
expect(server.mockWeb.check).not.toHaveBeenCalled()
|
|
203
|
+
expect(server.mockMail.check).not.toHaveBeenCalled()
|
|
204
|
+
|
|
205
|
+
// Fast-forward past initial delay
|
|
206
|
+
jest.advanceTimersByTime(1000)
|
|
207
|
+
|
|
208
|
+
// Still no checks after just the initial delay
|
|
209
|
+
expect(server.mockService.check).not.toHaveBeenCalled()
|
|
210
|
+
|
|
211
|
+
// Fast-forward to trigger first interval
|
|
212
|
+
jest.advanceTimersByTime(1000)
|
|
213
|
+
|
|
214
|
+
// Now checks should be called
|
|
215
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
216
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(1)
|
|
217
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
218
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(1)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('should continue periodic health checks at 1 second intervals', () => {
|
|
222
|
+
server = new TestServer()
|
|
223
|
+
|
|
224
|
+
// Fast-forward past initial delay and first interval
|
|
225
|
+
jest.advanceTimersByTime(2000)
|
|
226
|
+
|
|
227
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
228
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(1)
|
|
229
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
230
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(1)
|
|
231
|
+
|
|
232
|
+
// Fast-forward another second
|
|
233
|
+
jest.advanceTimersByTime(1000)
|
|
234
|
+
|
|
235
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(2)
|
|
236
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(2)
|
|
237
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(2)
|
|
238
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(2)
|
|
239
|
+
|
|
240
|
+
// Fast-forward multiple intervals
|
|
241
|
+
jest.advanceTimersByTime(3000)
|
|
242
|
+
|
|
243
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(5)
|
|
244
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(5)
|
|
245
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(5)
|
|
246
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(5)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('service coordination', () => {
|
|
251
|
+
test('should handle service initialization errors gracefully', () => {
|
|
252
|
+
// Create a modified TestServer that throws during DNS initialization
|
|
253
|
+
class ErrorTestServer extends TestServer {
|
|
254
|
+
init() {
|
|
255
|
+
global.Candy.core('Config').config.server.pid = process.pid
|
|
256
|
+
global.Candy.core('Config').config.server.started = Date.now()
|
|
257
|
+
global.Candy.server('Service')
|
|
258
|
+
|
|
259
|
+
// This should throw
|
|
260
|
+
throw new Error('DNS initialization failed')
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
expect(() => {
|
|
265
|
+
new ErrorTestServer()
|
|
266
|
+
}).toThrow('DNS initialization failed')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('should maintain service references for health checks', () => {
|
|
270
|
+
server = new TestServer()
|
|
271
|
+
|
|
272
|
+
// Fast-forward to trigger health checks
|
|
273
|
+
jest.advanceTimersByTime(2000)
|
|
274
|
+
|
|
275
|
+
// Verify that the same service instances are being called during health checks
|
|
276
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Service')
|
|
277
|
+
expect(global.Candy.server).toHaveBeenCalledWith('SSL')
|
|
278
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Web')
|
|
279
|
+
expect(global.Candy.server).toHaveBeenCalledWith('Mail')
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
describe('monitoring and health checks', () => {
|
|
284
|
+
beforeEach(() => {
|
|
285
|
+
server = new TestServer()
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test('should perform health checks on all monitored services', () => {
|
|
289
|
+
// Fast-forward to trigger health checks
|
|
290
|
+
jest.advanceTimersByTime(2000)
|
|
291
|
+
|
|
292
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
293
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(1)
|
|
294
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
295
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(1)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('should handle health check errors without stopping monitoring', () => {
|
|
299
|
+
// Mock a service check that throws an error
|
|
300
|
+
server.mockService.check.mockImplementation(() => {
|
|
301
|
+
throw new Error('Service check failed')
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// Should not throw when health check fails
|
|
305
|
+
expect(() => {
|
|
306
|
+
jest.advanceTimersByTime(2000)
|
|
307
|
+
}).not.toThrow()
|
|
308
|
+
|
|
309
|
+
// Other services should still be checked
|
|
310
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(1)
|
|
311
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
312
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(1)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('should continue monitoring after individual service failures', () => {
|
|
316
|
+
// Mock SSL check to fail on first call but succeed on second
|
|
317
|
+
let callCount = 0
|
|
318
|
+
server.mockSSL.check.mockImplementation(() => {
|
|
319
|
+
callCount++
|
|
320
|
+
if (callCount === 1) {
|
|
321
|
+
throw new Error('SSL check failed')
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
// First health check cycle
|
|
326
|
+
jest.advanceTimersByTime(2000)
|
|
327
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(1)
|
|
328
|
+
|
|
329
|
+
// Second health check cycle should still call SSL check
|
|
330
|
+
jest.advanceTimersByTime(1000)
|
|
331
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(2)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test('should maintain consistent monitoring intervals', () => {
|
|
335
|
+
const checkTimes = []
|
|
336
|
+
server.mockService.check.mockImplementation(() => {
|
|
337
|
+
checkTimes.push(Date.now())
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// Trigger multiple health check cycles
|
|
341
|
+
jest.advanceTimersByTime(2000) // Initial delay + first check
|
|
342
|
+
jest.advanceTimersByTime(1000) // Second check
|
|
343
|
+
jest.advanceTimersByTime(1000) // Third check
|
|
344
|
+
|
|
345
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(3)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('should implement 1-second periodic health check intervals', () => {
|
|
349
|
+
// Verify initial delay before first health check
|
|
350
|
+
jest.advanceTimersByTime(999)
|
|
351
|
+
expect(server.mockService.check).not.toHaveBeenCalled()
|
|
352
|
+
|
|
353
|
+
// First health check after 1 second delay
|
|
354
|
+
jest.advanceTimersByTime(1)
|
|
355
|
+
expect(server.mockService.check).not.toHaveBeenCalled() // Still in initial delay
|
|
356
|
+
|
|
357
|
+
// Complete initial delay and first interval
|
|
358
|
+
jest.advanceTimersByTime(1000)
|
|
359
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
360
|
+
|
|
361
|
+
// Verify subsequent 1-second intervals
|
|
362
|
+
for (let i = 2; i <= 5; i++) {
|
|
363
|
+
jest.advanceTimersByTime(1000)
|
|
364
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(i)
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
test('should monitor service status and health continuously', () => {
|
|
369
|
+
const serviceStatuses = []
|
|
370
|
+
|
|
371
|
+
// Track service check calls
|
|
372
|
+
server.mockService.check.mockImplementation(() => {
|
|
373
|
+
serviceStatuses.push('Service checked')
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
server.mockWeb.check.mockImplementation(() => {
|
|
377
|
+
serviceStatuses.push('Web checked')
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
// Run multiple monitoring cycles
|
|
381
|
+
jest.advanceTimersByTime(2000) // First cycle
|
|
382
|
+
jest.advanceTimersByTime(1000) // Second cycle
|
|
383
|
+
jest.advanceTimersByTime(1000) // Third cycle
|
|
384
|
+
|
|
385
|
+
expect(serviceStatuses).toEqual([
|
|
386
|
+
'Service checked',
|
|
387
|
+
'Web checked',
|
|
388
|
+
'Service checked',
|
|
389
|
+
'Web checked',
|
|
390
|
+
'Service checked',
|
|
391
|
+
'Web checked'
|
|
392
|
+
])
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
test('should handle all service types in monitoring cycle', () => {
|
|
396
|
+
// Fast-forward to trigger multiple health check cycles
|
|
397
|
+
jest.advanceTimersByTime(4000) // 3 complete cycles
|
|
398
|
+
|
|
399
|
+
// Verify all service types are monitored consistently
|
|
400
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(3)
|
|
401
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(3)
|
|
402
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(3)
|
|
403
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(3)
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
test('should isolate service check failures from each other', () => {
|
|
407
|
+
// Mock different services to fail at different times
|
|
408
|
+
let serviceCallCount = 0
|
|
409
|
+
let sslCallCount = 0
|
|
410
|
+
|
|
411
|
+
server.mockService.check.mockImplementation(() => {
|
|
412
|
+
serviceCallCount++
|
|
413
|
+
if (serviceCallCount === 2) {
|
|
414
|
+
throw new Error('Service check failed on second call')
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
server.mockSSL.check.mockImplementation(() => {
|
|
419
|
+
sslCallCount++
|
|
420
|
+
if (sslCallCount === 1) {
|
|
421
|
+
throw new Error('SSL check failed on first call')
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
// Run multiple cycles
|
|
426
|
+
jest.advanceTimersByTime(2000) // First cycle - SSL fails
|
|
427
|
+
jest.advanceTimersByTime(1000) // Second cycle - Service fails
|
|
428
|
+
jest.advanceTimersByTime(1000) // Third cycle - both succeed
|
|
429
|
+
|
|
430
|
+
// All services should have been called despite individual failures
|
|
431
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(3)
|
|
432
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(3)
|
|
433
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(3)
|
|
434
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(3)
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
test('should maintain monitoring state across service restarts', () => {
|
|
438
|
+
// Start monitoring
|
|
439
|
+
jest.advanceTimersByTime(2000)
|
|
440
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
441
|
+
|
|
442
|
+
// Simulate service restart by resetting mock
|
|
443
|
+
server.mockService.check.mockClear()
|
|
444
|
+
|
|
445
|
+
// Continue monitoring
|
|
446
|
+
jest.advanceTimersByTime(2000)
|
|
447
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(2)
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
describe('graceful shutdown', () => {
|
|
452
|
+
beforeEach(() => {
|
|
453
|
+
server = new TestServer()
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
test('should stop all services when stop() is called', () => {
|
|
457
|
+
server.stop()
|
|
458
|
+
|
|
459
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
460
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
test('should handle service stop errors gracefully', () => {
|
|
464
|
+
// Mock service stopAll to throw an error
|
|
465
|
+
server.mockService.stopAll.mockImplementation(() => {
|
|
466
|
+
throw new Error('Service stop failed')
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
// Should not throw when stopping services
|
|
470
|
+
expect(() => {
|
|
471
|
+
server.stop()
|
|
472
|
+
}).not.toThrow()
|
|
473
|
+
|
|
474
|
+
// Web service should still be stopped
|
|
475
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
test('should stop services in correct order', () => {
|
|
479
|
+
const stopOrder = []
|
|
480
|
+
|
|
481
|
+
server.mockService.stopAll.mockImplementation(() => {
|
|
482
|
+
stopOrder.push('Service')
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
server.mockWeb.stopAll.mockImplementation(() => {
|
|
486
|
+
stopOrder.push('Web')
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
server.stop()
|
|
490
|
+
|
|
491
|
+
expect(stopOrder).toEqual(['Service', 'Web'])
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
test('should handle partial service stop failures', () => {
|
|
495
|
+
// Mock Web stopAll to fail
|
|
496
|
+
server.mockWeb.stopAll.mockImplementation(() => {
|
|
497
|
+
throw new Error('Web stop failed')
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
expect(() => {
|
|
501
|
+
server.stop()
|
|
502
|
+
}).not.toThrow()
|
|
503
|
+
|
|
504
|
+
// Service should still be stopped
|
|
505
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
506
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
test('should clear health check intervals during shutdown', () => {
|
|
510
|
+
// Start health checks
|
|
511
|
+
jest.advanceTimersByTime(2000)
|
|
512
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
513
|
+
|
|
514
|
+
// Stop the server
|
|
515
|
+
server.stop()
|
|
516
|
+
|
|
517
|
+
// Advance time and verify no more health checks occur
|
|
518
|
+
jest.advanceTimersByTime(5000)
|
|
519
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1) // Should remain 1
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
test('should handle shutdown when health checks are not yet started', () => {
|
|
523
|
+
// Stop server immediately after creation
|
|
524
|
+
server.stop()
|
|
525
|
+
|
|
526
|
+
// Verify shutdown methods are called
|
|
527
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
528
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
529
|
+
|
|
530
|
+
// Test should not throw and shutdown should complete successfully
|
|
531
|
+
expect(() => {
|
|
532
|
+
jest.advanceTimersByTime(5000)
|
|
533
|
+
}).not.toThrow()
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
test('should perform complete cleanup of all resources', () => {
|
|
537
|
+
// Start health checks
|
|
538
|
+
jest.advanceTimersByTime(2000)
|
|
539
|
+
|
|
540
|
+
// Verify health checks are running
|
|
541
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
542
|
+
|
|
543
|
+
// Stop server
|
|
544
|
+
server.stop()
|
|
545
|
+
|
|
546
|
+
// Verify all cleanup actions
|
|
547
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
548
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
549
|
+
|
|
550
|
+
// Verify no more health checks after cleanup
|
|
551
|
+
jest.advanceTimersByTime(10000)
|
|
552
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
553
|
+
expect(server.mockSSL.check).toHaveBeenCalledTimes(1)
|
|
554
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
555
|
+
expect(server.mockMail.check).toHaveBeenCalledTimes(1)
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
test('should handle multiple shutdown calls gracefully', () => {
|
|
559
|
+
// Call stop multiple times
|
|
560
|
+
server.stop()
|
|
561
|
+
server.stop()
|
|
562
|
+
server.stop()
|
|
563
|
+
|
|
564
|
+
// Services should only be stopped once
|
|
565
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(3)
|
|
566
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(3)
|
|
567
|
+
})
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
describe('resource deallocation and cleanup', () => {
|
|
571
|
+
beforeEach(() => {
|
|
572
|
+
server = new TestServer()
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
test('should deallocate all monitoring intervals during shutdown', () => {
|
|
576
|
+
// Start monitoring
|
|
577
|
+
jest.advanceTimersByTime(2000)
|
|
578
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
579
|
+
|
|
580
|
+
// Verify interval is active
|
|
581
|
+
const activeTimers = jest.getTimerCount()
|
|
582
|
+
expect(activeTimers).toBeGreaterThan(0)
|
|
583
|
+
|
|
584
|
+
// Stop server and verify interval cleanup
|
|
585
|
+
server.stop()
|
|
586
|
+
|
|
587
|
+
// Advance time significantly and verify no more checks
|
|
588
|
+
jest.advanceTimersByTime(10000)
|
|
589
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1) // Should not increase
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
test('should handle cleanup when no intervals are active', () => {
|
|
593
|
+
// Stop server before any intervals are created
|
|
594
|
+
server.stop()
|
|
595
|
+
|
|
596
|
+
expect(() => {
|
|
597
|
+
jest.advanceTimersByTime(5000)
|
|
598
|
+
}).not.toThrow()
|
|
599
|
+
|
|
600
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
601
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
test('should perform complete resource cleanup sequence', () => {
|
|
605
|
+
const cleanupOrder = []
|
|
606
|
+
|
|
607
|
+
server.mockService.stopAll.mockImplementation(() => {
|
|
608
|
+
cleanupOrder.push('Service cleanup')
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
server.mockWeb.stopAll.mockImplementation(() => {
|
|
612
|
+
cleanupOrder.push('Web cleanup')
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
// Start monitoring to create resources
|
|
616
|
+
jest.advanceTimersByTime(2000)
|
|
617
|
+
|
|
618
|
+
// Perform cleanup
|
|
619
|
+
server.stop()
|
|
620
|
+
|
|
621
|
+
expect(cleanupOrder).toEqual(['Service cleanup', 'Web cleanup'])
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
test('should handle resource cleanup errors without failing', () => {
|
|
625
|
+
// Mock cleanup methods to throw errors
|
|
626
|
+
server.mockService.stopAll.mockImplementation(() => {
|
|
627
|
+
throw new Error('Service cleanup failed')
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
server.mockWeb.stopAll.mockImplementation(() => {
|
|
631
|
+
throw new Error('Web cleanup failed')
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
// Should not throw during cleanup
|
|
635
|
+
expect(() => {
|
|
636
|
+
server.stop()
|
|
637
|
+
}).not.toThrow()
|
|
638
|
+
|
|
639
|
+
// Verify cleanup was attempted
|
|
640
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
641
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
test('should ensure no memory leaks from monitoring intervals', () => {
|
|
645
|
+
// Test that stopped servers don't continue monitoring
|
|
646
|
+
const testServer1 = new TestServer()
|
|
647
|
+
|
|
648
|
+
// Start monitoring for first server
|
|
649
|
+
jest.advanceTimersByTime(2000)
|
|
650
|
+
const firstServerCalls = testServer1.mockService.check.mock.calls.length
|
|
651
|
+
expect(firstServerCalls).toBeGreaterThan(0)
|
|
652
|
+
|
|
653
|
+
// Stop and destroy first server
|
|
654
|
+
testServer1.destroy()
|
|
655
|
+
|
|
656
|
+
// Create second server with fresh mocks
|
|
657
|
+
const testServer2 = new TestServer()
|
|
658
|
+
|
|
659
|
+
// Advance time - only second server should have monitoring
|
|
660
|
+
jest.advanceTimersByTime(3000)
|
|
661
|
+
|
|
662
|
+
// First server should not have additional calls after stop
|
|
663
|
+
expect(testServer1.mockService.check).toHaveBeenCalledTimes(firstServerCalls)
|
|
664
|
+
|
|
665
|
+
// Second server should have its own monitoring calls
|
|
666
|
+
expect(testServer2.mockService.check.mock.calls.length).toBeGreaterThan(0)
|
|
667
|
+
|
|
668
|
+
// Clean up second server
|
|
669
|
+
testServer2.destroy()
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
test('should clean up service references during shutdown', () => {
|
|
673
|
+
// Start monitoring
|
|
674
|
+
jest.advanceTimersByTime(2000)
|
|
675
|
+
|
|
676
|
+
// Verify services are being monitored
|
|
677
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
678
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
679
|
+
|
|
680
|
+
// Stop server
|
|
681
|
+
server.stop()
|
|
682
|
+
|
|
683
|
+
// Verify service cleanup was called
|
|
684
|
+
expect(server.mockService.stopAll).toHaveBeenCalledTimes(1)
|
|
685
|
+
expect(server.mockWeb.stopAll).toHaveBeenCalledTimes(1)
|
|
686
|
+
|
|
687
|
+
// Verify no further service interactions
|
|
688
|
+
jest.advanceTimersByTime(5000)
|
|
689
|
+
expect(server.mockService.check).toHaveBeenCalledTimes(1)
|
|
690
|
+
expect(server.mockWeb.check).toHaveBeenCalledTimes(1)
|
|
691
|
+
})
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
describe('configuration management', () => {
|
|
695
|
+
test('should update config with current process PID', () => {
|
|
696
|
+
const originalPid = process.pid
|
|
697
|
+
|
|
698
|
+
// Set the PID before creating the server
|
|
699
|
+
Object.defineProperty(process, 'pid', {
|
|
700
|
+
value: 54321,
|
|
701
|
+
writable: true
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
server = new TestServer()
|
|
705
|
+
|
|
706
|
+
expect(server.mockConfig.server.pid).toBe(54321)
|
|
707
|
+
|
|
708
|
+
// Restore original PID
|
|
709
|
+
Object.defineProperty(process, 'pid', {
|
|
710
|
+
value: originalPid,
|
|
711
|
+
writable: true
|
|
712
|
+
})
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
test('should record server startup timestamp', () => {
|
|
716
|
+
const mockTimestamp = 1641081600000 // 2022-01-02 00:00:00
|
|
717
|
+
jest.spyOn(Date, 'now').mockReturnValue(mockTimestamp)
|
|
718
|
+
|
|
719
|
+
server = new TestServer()
|
|
720
|
+
|
|
721
|
+
expect(server.mockConfig.server.started).toBe(mockTimestamp)
|
|
722
|
+
|
|
723
|
+
Date.now.mockRestore()
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
test('should handle config access errors gracefully', () => {
|
|
727
|
+
// Create a function that simulates the Server initialization with config error
|
|
728
|
+
const createServerWithConfigError = () => {
|
|
729
|
+
// Setup global Candy mock that throws on Config access
|
|
730
|
+
global.Candy = {
|
|
731
|
+
core: jest.fn(module => {
|
|
732
|
+
if (module === 'Config') {
|
|
733
|
+
throw new Error('Config access failed')
|
|
734
|
+
}
|
|
735
|
+
return {}
|
|
736
|
+
}),
|
|
737
|
+
server: jest.fn(() => ({}))
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Simulate the Server initialization process
|
|
741
|
+
global.Candy.core('Config').config.server.pid = process.pid
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
expect(() => {
|
|
745
|
+
createServerWithConfigError()
|
|
746
|
+
}).toThrow('Config access failed')
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
test('should preserve existing config structure', () => {
|
|
750
|
+
server = new TestServer()
|
|
751
|
+
|
|
752
|
+
// Add existing config data
|
|
753
|
+
server.mockConfig.server.existingProperty = 'existing value'
|
|
754
|
+
server.mockConfig.otherSection = {data: 'preserved'}
|
|
755
|
+
|
|
756
|
+
// Re-initialize to test preservation
|
|
757
|
+
server.init()
|
|
758
|
+
|
|
759
|
+
expect(server.mockConfig.server.existingProperty).toBe('existing value')
|
|
760
|
+
expect(server.mockConfig.otherSection).toEqual({data: 'preserved'})
|
|
761
|
+
expect(server.mockConfig.server.pid).toBe(process.pid)
|
|
762
|
+
expect(server.mockConfig.server.started).toBeDefined()
|
|
763
|
+
})
|
|
764
|
+
})
|
|
765
|
+
})
|