odac 1.0.1 → 1.2.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/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +33 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/auto-pr-description.yml +3 -1
- package/.github/workflows/release.yml +42 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.github/workflows/test-publish.yml +36 -0
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +3 -3
- package/CHANGELOG.md +184 -0
- package/README.md +53 -34
- package/bin/odac.js +181 -49
- package/client/odac.js +878 -995
- package/docs/backend/01-overview/03-development-server.md +39 -46
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
- package/docs/backend/03-config/00-configuration-overview.md +15 -6
- package/docs/backend/03-config/01-database-connection.md +3 -3
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +29 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
- package/docs/backend/05-controllers/03-controller-classes.md +61 -55
- package/docs/backend/05-forms/01-custom-forms.md +103 -95
- package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/02-rendering-a-view.md +1 -1
- package/docs/backend/07-views/03-variables.md +5 -5
- package/docs/backend/07-views/04-request-data.md +1 -1
- package/docs/backend/07-views/08-backend-javascript.md +1 -1
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +100 -0
- package/docs/backend/08-database/02-basics.md +136 -0
- package/docs/backend/08-database/03-advanced.md +84 -0
- package/docs/backend/08-database/04-migrations.md +48 -0
- package/docs/backend/09-validation/01-the-validator-service.md +1 -0
- package/docs/backend/10-authentication/03-register.md +9 -2
- package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
- package/docs/backend/10-authentication/05-session-management.md +16 -2
- package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
- package/docs/backend/10-authentication/07-magic-links.md +134 -0
- package/docs/backend/11-mail/01-the-mail-service.md +118 -28
- package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
- package/docs/backend/13-utilities/01-odac-var.md +7 -7
- package/docs/backend/13-utilities/02-ipc.md +73 -0
- package/docs/frontend/01-overview/01-introduction.md +5 -1
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
- package/docs/index.json +21 -125
- package/eslint.config.mjs +5 -47
- package/jest.config.js +1 -1
- package/package.json +16 -7
- package/src/Auth.js +414 -121
- package/src/Config.js +12 -7
- package/src/Database.js +188 -0
- package/src/Env.js +3 -1
- package/src/Ipc.js +337 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +408 -37
- package/src/Odac.js +105 -40
- package/src/Request.js +71 -49
- package/src/Route/Cron.js +62 -18
- package/src/Route/Internal.js +215 -12
- package/src/Route/Middleware.js +7 -2
- package/src/Route.js +372 -109
- package/src/Server.js +118 -12
- package/src/Storage.js +169 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +95 -3
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +210 -28
- package/src/View.js +108 -7
- package/src/WebSocket.js +18 -3
- package/template/odac.json +5 -0
- package/template/package.json +3 -1
- package/template/route/www.js +12 -10
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +86 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +1 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
- package/test/scripts/check-coverage.js +4 -4
- package/docs/backend/08-database/01-database-connection.md +0 -99
- package/docs/backend/08-database/02-using-mysql.md +0 -322
- package/src/Mysql.js +0 -575
- package/template/config.json +0 -5
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Candy.test.js +0 -234
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Process.test.js +0 -156
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
package/test/server/Hub.test.js
DELETED
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
const mockLog = jest.fn()
|
|
2
|
-
const mockError = jest.fn()
|
|
3
|
-
|
|
4
|
-
const {mockOdac} = require('./__mocks__/globalOdac')
|
|
5
|
-
|
|
6
|
-
mockOdac.setMock('core', 'Log', {
|
|
7
|
-
init: jest.fn().mockReturnValue({
|
|
8
|
-
log: mockLog,
|
|
9
|
-
error: mockError
|
|
10
|
-
})
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
global.Odac = mockOdac
|
|
14
|
-
|
|
15
|
-
jest.mock('axios')
|
|
16
|
-
const axios = require('axios')
|
|
17
|
-
|
|
18
|
-
jest.mock('ws')
|
|
19
|
-
|
|
20
|
-
jest.mock('os')
|
|
21
|
-
const os = require('os')
|
|
22
|
-
|
|
23
|
-
jest.mock('fs')
|
|
24
|
-
const fs = require('fs')
|
|
25
|
-
|
|
26
|
-
jest.useFakeTimers()
|
|
27
|
-
|
|
28
|
-
describe('Hub', () => {
|
|
29
|
-
let Hub
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
jest.clearAllMocks()
|
|
33
|
-
jest.clearAllTimers()
|
|
34
|
-
|
|
35
|
-
mockOdac.setMock('core', 'Config', {
|
|
36
|
-
config: {
|
|
37
|
-
hub: null,
|
|
38
|
-
server: {started: Date.now()},
|
|
39
|
-
websites: {},
|
|
40
|
-
services: [],
|
|
41
|
-
mail: {accounts: {}}
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
mockOdac.setMock('server', 'Api', {
|
|
46
|
-
result: jest.fn((success, message) => ({success, message}))
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
os.hostname.mockReturnValue('test-host')
|
|
50
|
-
os.platform.mockReturnValue('linux')
|
|
51
|
-
os.arch.mockReturnValue('x64')
|
|
52
|
-
os.totalmem.mockReturnValue(8589934592)
|
|
53
|
-
os.freemem.mockReturnValue(4294967296)
|
|
54
|
-
os.cpus.mockReturnValue([
|
|
55
|
-
{times: {user: 1000, nice: 0, sys: 500, idle: 8500, irq: 0}},
|
|
56
|
-
{times: {user: 1000, nice: 0, sys: 500, idle: 8500, irq: 0}}
|
|
57
|
-
])
|
|
58
|
-
|
|
59
|
-
jest.isolateModules(() => {
|
|
60
|
-
Hub = require('../../server/src/Hub')
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
afterEach(() => {
|
|
65
|
-
if (Hub.websocket) {
|
|
66
|
-
Hub.websocket = null
|
|
67
|
-
}
|
|
68
|
-
Hub.checkCounter = 0
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
describe('initialization', () => {
|
|
72
|
-
it('should initialize with default values', () => {
|
|
73
|
-
expect(Hub.websocket).toBeNull()
|
|
74
|
-
expect(Hub.websocketReconnectAttempts).toBe(0)
|
|
75
|
-
expect(Hub.maxReconnectAttempts).toBe(5)
|
|
76
|
-
expect(Hub.checkCounter).toBe(0)
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('check counter', () => {
|
|
81
|
-
it('should increment counter on each check', () => {
|
|
82
|
-
expect(Hub.checkCounter).toBe(0)
|
|
83
|
-
Hub.check()
|
|
84
|
-
expect(Hub.checkCounter).toBe(1)
|
|
85
|
-
Hub.check()
|
|
86
|
-
expect(Hub.checkCounter).toBe(2)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('should reset counter after reaching 60', () => {
|
|
90
|
-
Hub.checkCounter = 59
|
|
91
|
-
Hub.check()
|
|
92
|
-
expect(Hub.checkCounter).toBe(0)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('should skip API call when counter is not 0', async () => {
|
|
96
|
-
mockOdac.setMock('core', 'Config', {
|
|
97
|
-
config: {
|
|
98
|
-
hub: {token: 'test-token', secret: 'test-secret'},
|
|
99
|
-
server: {started: Date.now()}
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
Hub.checkCounter = 1
|
|
104
|
-
await Hub.check()
|
|
105
|
-
expect(axios.post).not.toHaveBeenCalled()
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should skip API call when websocket is connected', async () => {
|
|
109
|
-
mockOdac.setMock('core', 'Config', {
|
|
110
|
-
config: {
|
|
111
|
-
hub: {token: 'test-token', secret: 'test-secret'},
|
|
112
|
-
server: {started: Date.now()}
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
Hub.websocket = {readyState: 1}
|
|
117
|
-
Hub.checkCounter = 0
|
|
118
|
-
await Hub.check()
|
|
119
|
-
expect(axios.post).not.toHaveBeenCalled()
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
describe('check', () => {
|
|
124
|
-
beforeEach(() => {
|
|
125
|
-
Hub.checkCounter = 0
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('should return early if no hub config', async () => {
|
|
129
|
-
mockOdac.setMock('core', 'Config', {
|
|
130
|
-
config: {hub: null}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
await Hub.check()
|
|
134
|
-
expect(axios.post).not.toHaveBeenCalled()
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('should return early if no token', async () => {
|
|
138
|
-
mockOdac.setMock('core', 'Config', {
|
|
139
|
-
config: {hub: {}}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
await Hub.check()
|
|
143
|
-
expect(axios.post).not.toHaveBeenCalled()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('should send status to hub when counter is 0', async () => {
|
|
147
|
-
jest.useRealTimers()
|
|
148
|
-
|
|
149
|
-
mockOdac.setMock('core', 'Config', {
|
|
150
|
-
config: {
|
|
151
|
-
hub: {token: 'test-token', secret: 'test-secret'},
|
|
152
|
-
server: {started: Date.now()}
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
axios.post.mockResolvedValue({
|
|
157
|
-
data: {
|
|
158
|
-
result: {success: true},
|
|
159
|
-
data: {authenticated: true}
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
Hub.checkCounter = 59
|
|
164
|
-
Hub.check()
|
|
165
|
-
|
|
166
|
-
await new Promise(resolve => setImmediate(resolve))
|
|
167
|
-
expect(axios.post).toHaveBeenCalled()
|
|
168
|
-
|
|
169
|
-
jest.useFakeTimers()
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('should handle authentication failure', async () => {
|
|
173
|
-
jest.useRealTimers()
|
|
174
|
-
|
|
175
|
-
mockOdac.setMock('core', 'Config', {
|
|
176
|
-
config: {
|
|
177
|
-
hub: {token: 'invalid-token', secret: 'test-secret'},
|
|
178
|
-
server: {started: Date.now()}
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
axios.post.mockResolvedValue({
|
|
183
|
-
data: {
|
|
184
|
-
result: {success: true},
|
|
185
|
-
data: {authenticated: false, reason: 'token_invalid'}
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
Hub.checkCounter = 59
|
|
190
|
-
Hub.check()
|
|
191
|
-
|
|
192
|
-
await new Promise(resolve => setImmediate(resolve))
|
|
193
|
-
expect(mockLog).toHaveBeenCalledWith('Server not authenticated: %s', 'token_invalid')
|
|
194
|
-
|
|
195
|
-
jest.useFakeTimers()
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('should clear config on invalid token', async () => {
|
|
199
|
-
jest.useRealTimers()
|
|
200
|
-
|
|
201
|
-
const config = {
|
|
202
|
-
hub: {token: 'invalid-token', secret: 'test-secret'},
|
|
203
|
-
server: {started: Date.now()}
|
|
204
|
-
}
|
|
205
|
-
mockOdac.setMock('core', 'Config', {config})
|
|
206
|
-
|
|
207
|
-
axios.post.mockResolvedValue({
|
|
208
|
-
data: {
|
|
209
|
-
result: {success: true},
|
|
210
|
-
data: {authenticated: false, reason: 'token_invalid'}
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
Hub.checkCounter = 59
|
|
215
|
-
Hub.check()
|
|
216
|
-
|
|
217
|
-
await new Promise(resolve => setImmediate(resolve))
|
|
218
|
-
expect(config.hub).toBeUndefined()
|
|
219
|
-
|
|
220
|
-
jest.useFakeTimers()
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('should handle check errors gracefully', async () => {
|
|
224
|
-
jest.useRealTimers()
|
|
225
|
-
|
|
226
|
-
mockOdac.setMock('core', 'Config', {
|
|
227
|
-
config: {
|
|
228
|
-
hub: {token: 'test-token', secret: 'test-secret'},
|
|
229
|
-
server: {started: Date.now()}
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
axios.post.mockRejectedValue(new Error('Network error'))
|
|
234
|
-
|
|
235
|
-
Hub.checkCounter = 59
|
|
236
|
-
Hub.check()
|
|
237
|
-
|
|
238
|
-
await new Promise(resolve => setImmediate(resolve))
|
|
239
|
-
expect(mockLog).toHaveBeenCalledWith('Failed to report status: %s', 'Network error')
|
|
240
|
-
|
|
241
|
-
jest.useFakeTimers()
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
describe('authentication', () => {
|
|
246
|
-
it('should authenticate with valid code', async () => {
|
|
247
|
-
const mockResponse = {
|
|
248
|
-
data: {
|
|
249
|
-
result: {success: true},
|
|
250
|
-
data: {
|
|
251
|
-
token: 'new-token',
|
|
252
|
-
secret: 'new-secret'
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const mockApiResult = {success: true, message: 'Authentication successful'}
|
|
258
|
-
mockOdac.setMock('server', 'Api', {
|
|
259
|
-
result: jest.fn(() => mockApiResult)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
axios.post.mockResolvedValue(mockResponse)
|
|
263
|
-
|
|
264
|
-
const result = await Hub.auth('valid-code')
|
|
265
|
-
|
|
266
|
-
expect(axios.post).toHaveBeenCalled()
|
|
267
|
-
expect(result).toEqual(mockApiResult)
|
|
268
|
-
expect(mockOdac.core('Config').config.hub).toEqual({
|
|
269
|
-
token: 'new-token',
|
|
270
|
-
secret: 'new-secret'
|
|
271
|
-
})
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
it('should handle authentication failure', async () => {
|
|
275
|
-
const mockApiResult = {success: false, message: 'Authentication failed'}
|
|
276
|
-
mockOdac.setMock('server', 'Api', {
|
|
277
|
-
result: jest.fn(() => mockApiResult)
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
axios.post.mockRejectedValue(new Error('Invalid code'))
|
|
281
|
-
|
|
282
|
-
const result = await Hub.auth('invalid-code')
|
|
283
|
-
|
|
284
|
-
expect(result).toEqual(mockApiResult)
|
|
285
|
-
expect(mockLog).toHaveBeenCalledWith('Authentication failed: %s', 'Invalid code')
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
it('should include distro info on Linux', async () => {
|
|
289
|
-
os.platform.mockReturnValue('linux')
|
|
290
|
-
fs.readFileSync.mockReturnValue('NAME="Ubuntu"\nVERSION_ID="20.04"\nID=ubuntu')
|
|
291
|
-
|
|
292
|
-
axios.post.mockResolvedValue({
|
|
293
|
-
data: {
|
|
294
|
-
result: {success: true},
|
|
295
|
-
data: {token: 'token', secret: 'secret'}
|
|
296
|
-
}
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
await Hub.auth('code')
|
|
300
|
-
|
|
301
|
-
const callArgs = axios.post.mock.calls[0][1]
|
|
302
|
-
expect(callArgs.distro).toBeDefined()
|
|
303
|
-
expect(callArgs.distro.name).toBe('Ubuntu')
|
|
304
|
-
})
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
describe('system status', () => {
|
|
308
|
-
it('should get system status', () => {
|
|
309
|
-
const status = Hub.getSystemStatus()
|
|
310
|
-
|
|
311
|
-
expect(status).toHaveProperty('cpu')
|
|
312
|
-
expect(status).toHaveProperty('memory')
|
|
313
|
-
expect(status).toHaveProperty('disk')
|
|
314
|
-
expect(status).toHaveProperty('network')
|
|
315
|
-
expect(status).toHaveProperty('services')
|
|
316
|
-
expect(status).toHaveProperty('uptime')
|
|
317
|
-
expect(status.hostname).toBe('test-host')
|
|
318
|
-
expect(status.platform).toBe('linux')
|
|
319
|
-
expect(status.arch).toBe('x64')
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
it('should get services info', () => {
|
|
323
|
-
mockOdac.setMock('core', 'Config', {
|
|
324
|
-
config: {
|
|
325
|
-
websites: {
|
|
326
|
-
'example.com': {},
|
|
327
|
-
'test.com': {}
|
|
328
|
-
},
|
|
329
|
-
services: ['web', 'mail'],
|
|
330
|
-
mail: {
|
|
331
|
-
accounts: {
|
|
332
|
-
'user@example.com': {},
|
|
333
|
-
'admin@test.com': {}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
const services = Hub.getServicesInfo()
|
|
340
|
-
|
|
341
|
-
expect(services.websites).toBe(2)
|
|
342
|
-
expect(services.services).toBe(2)
|
|
343
|
-
expect(services.mail).toBe(2)
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
it('should handle missing services config', () => {
|
|
347
|
-
mockOdac.setMock('core', 'Config', {config: {}})
|
|
348
|
-
|
|
349
|
-
const services = Hub.getServicesInfo()
|
|
350
|
-
|
|
351
|
-
expect(services.websites).toBe(0)
|
|
352
|
-
expect(services.services).toBe(0)
|
|
353
|
-
expect(services.mail).toBe(0)
|
|
354
|
-
})
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
describe('memory usage', () => {
|
|
358
|
-
it('should get memory usage on Linux', () => {
|
|
359
|
-
os.platform.mockReturnValue('linux')
|
|
360
|
-
os.totalmem.mockReturnValue(8589934592)
|
|
361
|
-
os.freemem.mockReturnValue(4294967296)
|
|
362
|
-
|
|
363
|
-
const memory = Hub.getMemoryUsage()
|
|
364
|
-
|
|
365
|
-
expect(memory.total).toBe(8589934592)
|
|
366
|
-
expect(memory.used).toBe(4294967296)
|
|
367
|
-
})
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
describe('CPU usage', () => {
|
|
371
|
-
it('should return 0 on first call', () => {
|
|
372
|
-
const usage = Hub.getCpuUsage()
|
|
373
|
-
expect(usage).toBe(0)
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
it('should calculate CPU usage on subsequent calls', () => {
|
|
377
|
-
Hub.getCpuUsage()
|
|
378
|
-
|
|
379
|
-
os.cpus.mockReturnValue([
|
|
380
|
-
{times: {user: 2000, nice: 0, sys: 1000, idle: 7000, irq: 0}},
|
|
381
|
-
{times: {user: 2000, nice: 0, sys: 1000, idle: 7000, irq: 0}}
|
|
382
|
-
])
|
|
383
|
-
|
|
384
|
-
const usage = Hub.getCpuUsage()
|
|
385
|
-
expect(usage).toBeGreaterThanOrEqual(0)
|
|
386
|
-
expect(usage).toBeLessThanOrEqual(100)
|
|
387
|
-
})
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
describe('request signing', () => {
|
|
391
|
-
it('should sign request with secret', () => {
|
|
392
|
-
mockOdac.setMock('core', 'Config', {
|
|
393
|
-
config: {
|
|
394
|
-
hub: {token: 'token', secret: 'test-secret'}
|
|
395
|
-
}
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
const data = {test: 'data'}
|
|
399
|
-
const signature = Hub.signRequest(data)
|
|
400
|
-
|
|
401
|
-
expect(signature).toBeTruthy()
|
|
402
|
-
expect(typeof signature).toBe('string')
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
it('should return null without secret', () => {
|
|
406
|
-
mockOdac.setMock('core', 'Config', {
|
|
407
|
-
config: {hub: {token: 'token'}}
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
const signature = Hub.signRequest({test: 'data'})
|
|
411
|
-
expect(signature).toBeNull()
|
|
412
|
-
})
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
describe('API calls', () => {
|
|
416
|
-
it('should make successful API call', async () => {
|
|
417
|
-
axios.post.mockResolvedValue({
|
|
418
|
-
data: {
|
|
419
|
-
result: {success: true},
|
|
420
|
-
data: {response: 'data'}
|
|
421
|
-
}
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
const result = await Hub.call('test-action', {param: 'value'})
|
|
425
|
-
|
|
426
|
-
expect(result).toEqual({response: 'data'})
|
|
427
|
-
expect(axios.post).toHaveBeenCalledWith('https://hub.odac.run/test-action', {param: 'value'}, expect.any(Object))
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
it('should include authorization header when token exists', async () => {
|
|
431
|
-
mockOdac.setMock('core', 'Config', {
|
|
432
|
-
config: {
|
|
433
|
-
hub: {token: 'test-token', secret: 'test-secret'}
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
axios.post.mockResolvedValue({
|
|
438
|
-
data: {
|
|
439
|
-
result: {success: true},
|
|
440
|
-
data: {}
|
|
441
|
-
}
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
await Hub.call('test', {})
|
|
445
|
-
|
|
446
|
-
const callArgs = axios.post.mock.calls[0][2]
|
|
447
|
-
expect(callArgs.headers.Authorization).toBe('Bearer test-token')
|
|
448
|
-
})
|
|
449
|
-
|
|
450
|
-
it('should handle API errors', async () => {
|
|
451
|
-
axios.post.mockResolvedValue({
|
|
452
|
-
data: {
|
|
453
|
-
result: {success: false, message: 'API error'}
|
|
454
|
-
}
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
await expect(Hub.call('test', {})).rejects.toBe('API error')
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
it('should handle network errors', async () => {
|
|
461
|
-
axios.post.mockRejectedValue({
|
|
462
|
-
response: {status: 500, data: 'Server error'}
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
await expect(Hub.call('test', {})).rejects.toBe('Server error')
|
|
466
|
-
})
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
describe('Linux distro detection', () => {
|
|
470
|
-
it('should return null on non-Linux platforms', () => {
|
|
471
|
-
os.platform.mockReturnValue('darwin')
|
|
472
|
-
const distro = Hub.getLinuxDistro()
|
|
473
|
-
expect(distro).toBeNull()
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
it('should parse os-release file', () => {
|
|
477
|
-
os.platform.mockReturnValue('linux')
|
|
478
|
-
fs.readFileSync.mockReturnValue('NAME="Ubuntu"\nVERSION_ID="20.04"\nID=ubuntu')
|
|
479
|
-
|
|
480
|
-
const distro = Hub.getLinuxDistro()
|
|
481
|
-
|
|
482
|
-
expect(distro.name).toBe('Ubuntu')
|
|
483
|
-
expect(distro.version).toBe('20.04')
|
|
484
|
-
expect(distro.id).toBe('ubuntu')
|
|
485
|
-
})
|
|
486
|
-
|
|
487
|
-
it('should handle missing os-release file', () => {
|
|
488
|
-
os.platform.mockReturnValue('linux')
|
|
489
|
-
fs.readFileSync.mockImplementation(() => {
|
|
490
|
-
throw new Error('File not found')
|
|
491
|
-
})
|
|
492
|
-
|
|
493
|
-
const distro = Hub.getLinuxDistro()
|
|
494
|
-
expect(distro).toBeNull()
|
|
495
|
-
})
|
|
496
|
-
})
|
|
497
|
-
})
|
package/test/server/Log.test.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
const Log = require('../../core/Log')
|
|
2
|
-
|
|
3
|
-
describe('Log', () => {
|
|
4
|
-
let consoleLogSpy
|
|
5
|
-
let consoleErrorSpy
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
|
|
9
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
consoleLogSpy.mockRestore()
|
|
14
|
-
consoleErrorSpy.mockRestore()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('should initialize with a module prefix', () => {
|
|
18
|
-
const log = new Log()
|
|
19
|
-
const logger = log.init('TestModule')
|
|
20
|
-
logger.log('test message')
|
|
21
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[TestModule] ', 'test message')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should handle multiple module prefixes', () => {
|
|
25
|
-
const log = new Log()
|
|
26
|
-
const logger = log.init('Module1', 'Module2')
|
|
27
|
-
logger.log('test message')
|
|
28
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[Module1][Module2] ', 'test message')
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should log messages correctly', () => {
|
|
32
|
-
const log = new Log()
|
|
33
|
-
const logger = log.init('Test')
|
|
34
|
-
logger.log('message1', 'message2')
|
|
35
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[Test] ', 'message1', 'message2')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('should log error messages correctly', () => {
|
|
39
|
-
const log = new Log()
|
|
40
|
-
const logger = log.init('Test')
|
|
41
|
-
logger.error('error message')
|
|
42
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith('[Test] ', 'error message')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('should handle %s format specifiers', () => {
|
|
46
|
-
const log = new Log()
|
|
47
|
-
const logger = log.init('FormatTest')
|
|
48
|
-
logger.log('Hello, %s!', 'World')
|
|
49
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[FormatTest] ', 'Hello, World!')
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('should handle multiple %s format specifiers', () => {
|
|
53
|
-
const log = new Log()
|
|
54
|
-
const logger = log.init('FormatTest')
|
|
55
|
-
logger.log('Hello, %s! Welcome, %s.', 'John', 'Jane')
|
|
56
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[FormatTest] ', 'Hello, John! Welcome, Jane.')
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('should handle missing arguments for %s', () => {
|
|
60
|
-
const log = new Log()
|
|
61
|
-
const logger = log.init('FormatTest')
|
|
62
|
-
logger.log('Hello, %s!')
|
|
63
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[FormatTest] ', 'Hello, !')
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('should not log anything if no arguments are provided to log()', () => {
|
|
67
|
-
const log = new Log()
|
|
68
|
-
const logger = log.init('Test')
|
|
69
|
-
const result = logger.log()
|
|
70
|
-
expect(consoleLogSpy).not.toHaveBeenCalled()
|
|
71
|
-
expect(result).toBeInstanceOf(Log)
|
|
72
|
-
})
|
|
73
|
-
})
|