claw-dashboard 1.9.0 → 2.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/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5236 -566
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -5
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1941 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1057 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- package/tests/worker-pool.test.js +522 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
import config, {
|
|
2
|
+
DASHBOARD_VERSION,
|
|
3
|
+
REFRESH_INTERVALS,
|
|
4
|
+
IDLE_THRESHOLD_MS,
|
|
5
|
+
HISTORY,
|
|
6
|
+
GATEWAY,
|
|
7
|
+
DEFAULT_GATEWAY_ENDPOINT,
|
|
8
|
+
CHECKSUM,
|
|
9
|
+
UI,
|
|
10
|
+
CACHE_TTL,
|
|
11
|
+
CACHE_CONFIG,
|
|
12
|
+
DATABASE,
|
|
13
|
+
RETRY,
|
|
14
|
+
DEFAULT_RETRY_OPTIONS,
|
|
15
|
+
ALERT_THRESHOLDS,
|
|
16
|
+
ALERT_RATE_LIMIT,
|
|
17
|
+
MAX_ALERT_HISTORY,
|
|
18
|
+
VALIDATION,
|
|
19
|
+
COMMAND_TIMEOUTS,
|
|
20
|
+
PATHS,
|
|
21
|
+
DEFAULT_SETTINGS,
|
|
22
|
+
WORKERS,
|
|
23
|
+
WEB,
|
|
24
|
+
WIDGETS
|
|
25
|
+
} from '../src/config.js';
|
|
26
|
+
import os from 'os';
|
|
27
|
+
|
|
28
|
+
describe('Config Module', () => {
|
|
29
|
+
describe('DASHBOARD_VERSION', () => {
|
|
30
|
+
test('exports version string', () => {
|
|
31
|
+
expect(typeof DASHBOARD_VERSION).toBe('string');
|
|
32
|
+
expect(DASHBOARD_VERSION.length).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('follows semantic versioning format', () => {
|
|
36
|
+
// Should match X.Y.Z pattern
|
|
37
|
+
expect(DASHBOARD_VERSION).toMatch(/^\d+\.\d+\.\d+/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('is accessible from default export', () => {
|
|
41
|
+
expect(config.DASHBOARD_VERSION).toBe(DASHBOARD_VERSION);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('REFRESH_INTERVALS', () => {
|
|
46
|
+
test('has required properties', () => {
|
|
47
|
+
expect(REFRESH_INTERVALS).toHaveProperty('DEFAULT');
|
|
48
|
+
expect(REFRESH_INTERVALS).toHaveProperty('ACTIVE');
|
|
49
|
+
expect(REFRESH_INTERVALS).toHaveProperty('IDLE');
|
|
50
|
+
expect(REFRESH_INTERVALS).toHaveProperty('OPTIONS');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('all values are positive numbers', () => {
|
|
54
|
+
expect(REFRESH_INTERVALS.DEFAULT).toBeGreaterThan(0);
|
|
55
|
+
expect(REFRESH_INTERVALS.ACTIVE).toBeGreaterThan(0);
|
|
56
|
+
expect(REFRESH_INTERVALS.IDLE).toBeGreaterThan(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('DEFAULT equals ACTIVE value', () => {
|
|
60
|
+
expect(REFRESH_INTERVALS.DEFAULT).toBe(REFRESH_INTERVALS.ACTIVE);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('IDLE is greater than ACTIVE', () => {
|
|
64
|
+
expect(REFRESH_INTERVALS.IDLE).toBeGreaterThan(REFRESH_INTERVALS.ACTIVE);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('OPTIONS is an array of valid intervals', () => {
|
|
68
|
+
expect(Array.isArray(REFRESH_INTERVALS.OPTIONS)).toBe(true);
|
|
69
|
+
expect(REFRESH_INTERVALS.OPTIONS.length).toBeGreaterThan(0);
|
|
70
|
+
expect(REFRESH_INTERVALS.OPTIONS).toContain(REFRESH_INTERVALS.DEFAULT);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('all OPTIONS are positive integers', () => {
|
|
74
|
+
for (const option of REFRESH_INTERVALS.OPTIONS) {
|
|
75
|
+
expect(typeof option).toBe('number');
|
|
76
|
+
expect(Number.isInteger(option)).toBe(true);
|
|
77
|
+
expect(option).toBeGreaterThan(0);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('is accessible from default export', () => {
|
|
82
|
+
expect(config.REFRESH_INTERVALS).toEqual(REFRESH_INTERVALS);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('IDLE_THRESHOLD_MS', () => {
|
|
87
|
+
test('is a positive number', () => {
|
|
88
|
+
expect(typeof IDLE_THRESHOLD_MS).toBe('number');
|
|
89
|
+
expect(IDLE_THRESHOLD_MS).toBeGreaterThan(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('is 5 minutes in milliseconds', () => {
|
|
93
|
+
expect(IDLE_THRESHOLD_MS).toBe(5 * 60 * 1000);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('HISTORY', () => {
|
|
98
|
+
test('has required properties', () => {
|
|
99
|
+
expect(HISTORY).toHaveProperty('LENGTH');
|
|
100
|
+
expect(HISTORY).toHaveProperty('NETWORK_LENGTH');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('all values are positive integers', () => {
|
|
104
|
+
expect(Number.isInteger(HISTORY.LENGTH)).toBe(true);
|
|
105
|
+
expect(HISTORY.LENGTH).toBeGreaterThan(0);
|
|
106
|
+
expect(Number.isInteger(HISTORY.NETWORK_LENGTH)).toBe(true);
|
|
107
|
+
expect(HISTORY.NETWORK_LENGTH).toBeGreaterThan(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('GATEWAY', () => {
|
|
112
|
+
test('has required properties', () => {
|
|
113
|
+
expect(GATEWAY).toHaveProperty('DEFAULT_PORT');
|
|
114
|
+
expect(GATEWAY).toHaveProperty('TIMEOUT_MS');
|
|
115
|
+
expect(GATEWAY).toHaveProperty('MAX_ENDPOINTS');
|
|
116
|
+
expect(GATEWAY).toHaveProperty('DEFAULT_ENDPOINT_NAME');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('DEFAULT_PORT is valid port number', () => {
|
|
120
|
+
expect(GATEWAY.DEFAULT_PORT).toBeGreaterThan(0);
|
|
121
|
+
expect(GATEWAY.DEFAULT_PORT).toBeLessThanOrEqual(65535);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('TIMEOUT_MS is positive', () => {
|
|
125
|
+
expect(GATEWAY.TIMEOUT_MS).toBeGreaterThan(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('MAX_ENDPOINTS is positive integer', () => {
|
|
129
|
+
expect(Number.isInteger(GATEWAY.MAX_ENDPOINTS)).toBe(true);
|
|
130
|
+
expect(GATEWAY.MAX_ENDPOINTS).toBeGreaterThan(0);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('DEFAULT_GATEWAY_ENDPOINT', () => {
|
|
135
|
+
test('has required properties', () => {
|
|
136
|
+
expect(DEFAULT_GATEWAY_ENDPOINT).toHaveProperty('name');
|
|
137
|
+
expect(DEFAULT_GATEWAY_ENDPOINT).toHaveProperty('host');
|
|
138
|
+
expect(DEFAULT_GATEWAY_ENDPOINT).toHaveProperty('port');
|
|
139
|
+
expect(DEFAULT_GATEWAY_ENDPOINT).toHaveProperty('token');
|
|
140
|
+
expect(DEFAULT_GATEWAY_ENDPOINT).toHaveProperty('enabled');
|
|
141
|
+
expect(DEFAULT_GATEWAY_ENDPOINT).toHaveProperty('type');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('name matches GATEWAY.DEFAULT_ENDPOINT_NAME', () => {
|
|
145
|
+
expect(DEFAULT_GATEWAY_ENDPOINT.name).toBe(GATEWAY.DEFAULT_ENDPOINT_NAME);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('port matches GATEWAY.DEFAULT_PORT', () => {
|
|
149
|
+
expect(DEFAULT_GATEWAY_ENDPOINT.port).toBe(GATEWAY.DEFAULT_PORT);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('enabled is boolean true', () => {
|
|
153
|
+
expect(DEFAULT_GATEWAY_ENDPOINT.enabled).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('token is null by default', () => {
|
|
157
|
+
expect(DEFAULT_GATEWAY_ENDPOINT.token).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('type is valid endpoint type', () => {
|
|
161
|
+
expect(['local', 'remote', 'cloud']).toContain(DEFAULT_GATEWAY_ENDPOINT.type);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('CHECKSUM', () => {
|
|
166
|
+
test('has required properties', () => {
|
|
167
|
+
expect(CHECKSUM).toHaveProperty('ENABLED');
|
|
168
|
+
expect(CHECKSUM).toHaveProperty('ALGORITHM');
|
|
169
|
+
expect(CHECKSUM).toHaveProperty('HEADER_NAME');
|
|
170
|
+
expect(CHECKSUM).toHaveProperty('STRICT_MODE');
|
|
171
|
+
expect(CHECKSUM).toHaveProperty('MAX_AGE_MS');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('ENABLED is boolean', () => {
|
|
175
|
+
expect(typeof CHECKSUM.ENABLED).toBe('boolean');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('ALGORITHM is valid hash algorithm', () => {
|
|
179
|
+
expect(['sha256', 'sha512', 'md5']).toContain(CHECKSUM.ALGORITHM);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('HEADER_NAME starts with x-', () => {
|
|
183
|
+
expect(CHECKSUM.HEADER_NAME).toMatch(/^x-/i);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('STRICT_MODE is boolean', () => {
|
|
187
|
+
expect(typeof CHECKSUM.STRICT_MODE).toBe('boolean');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('MAX_AGE_MS is positive', () => {
|
|
191
|
+
expect(CHECKSUM.MAX_AGE_MS).toBeGreaterThan(0);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('UI', () => {
|
|
196
|
+
test('has required properties', () => {
|
|
197
|
+
expect(UI).toHaveProperty('GAUGE_WIDTH');
|
|
198
|
+
expect(UI).toHaveProperty('SPARKLINE_WIDTH');
|
|
199
|
+
expect(UI).toHaveProperty('LOG_BOX_MIN_HEIGHT');
|
|
200
|
+
expect(UI).toHaveProperty('DEFAULT_WIDTH');
|
|
201
|
+
expect(UI).toHaveProperty('DEFAULT_HEIGHT');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('all values are positive integers', () => {
|
|
205
|
+
for (const [key, value] of Object.entries(UI)) {
|
|
206
|
+
expect(typeof value).toBe('number');
|
|
207
|
+
expect(value).toBeGreaterThan(0);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('DEFAULT_WIDTH is reasonable terminal width', () => {
|
|
212
|
+
expect(UI.DEFAULT_WIDTH).toBeGreaterThanOrEqual(40);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('DEFAULT_HEIGHT is reasonable terminal height', () => {
|
|
216
|
+
expect(UI.DEFAULT_HEIGHT).toBeGreaterThanOrEqual(10);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('CACHE_TTL', () => {
|
|
221
|
+
test('has all required cache types', () => {
|
|
222
|
+
expect(CACHE_TTL).toHaveProperty('CPU');
|
|
223
|
+
expect(CACHE_TTL).toHaveProperty('MEMORY');
|
|
224
|
+
expect(CACHE_TTL).toHaveProperty('GPU');
|
|
225
|
+
expect(CACHE_TTL).toHaveProperty('NETWORK');
|
|
226
|
+
expect(CACHE_TTL).toHaveProperty('DISK');
|
|
227
|
+
expect(CACHE_TTL).toHaveProperty('SYSTEM');
|
|
228
|
+
expect(CACHE_TTL).toHaveProperty('CONTAINER');
|
|
229
|
+
expect(CACHE_TTL).toHaveProperty('DEFAULT');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('all values are positive numbers', () => {
|
|
233
|
+
for (const [key, value] of Object.entries(CACHE_TTL)) {
|
|
234
|
+
expect(typeof value).toBe('number');
|
|
235
|
+
expect(value).toBeGreaterThan(0);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('DISK and CONTAINER have longer TTL (rarely change)', () => {
|
|
240
|
+
expect(CACHE_TTL.DISK).toBeGreaterThanOrEqual(CACHE_TTL.CPU);
|
|
241
|
+
expect(CACHE_TTL.CONTAINER).toBeGreaterThanOrEqual(CACHE_TTL.CPU);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('GPU has longer TTL (expensive)', () => {
|
|
245
|
+
expect(CACHE_TTL.GPU).toBeGreaterThanOrEqual(CACHE_TTL.CPU);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('CACHE_CONFIG', () => {
|
|
250
|
+
test('has config for all cache types', () => {
|
|
251
|
+
expect(CACHE_CONFIG).toHaveProperty('cpu');
|
|
252
|
+
expect(CACHE_CONFIG).toHaveProperty('memory');
|
|
253
|
+
expect(CACHE_CONFIG).toHaveProperty('gpu');
|
|
254
|
+
expect(CACHE_CONFIG).toHaveProperty('network');
|
|
255
|
+
expect(CACHE_CONFIG).toHaveProperty('disk');
|
|
256
|
+
expect(CACHE_CONFIG).toHaveProperty('system');
|
|
257
|
+
expect(CACHE_CONFIG).toHaveProperty('container');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('each config has ttl property', () => {
|
|
261
|
+
for (const [key, value] of Object.entries(CACHE_CONFIG)) {
|
|
262
|
+
expect(value).toHaveProperty('ttl');
|
|
263
|
+
expect(typeof value.ttl).toBe('number');
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('ttl values match CACHE_TTL', () => {
|
|
268
|
+
expect(CACHE_CONFIG.cpu.ttl).toBe(CACHE_TTL.CPU);
|
|
269
|
+
expect(CACHE_CONFIG.memory.ttl).toBe(CACHE_TTL.MEMORY);
|
|
270
|
+
expect(CACHE_CONFIG.gpu.ttl).toBe(CACHE_TTL.GPU);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('DATABASE', () => {
|
|
275
|
+
test('has required properties', () => {
|
|
276
|
+
expect(DATABASE).toHaveProperty('PATH');
|
|
277
|
+
expect(DATABASE).toHaveProperty('SAVE_INTERVAL_MS');
|
|
278
|
+
expect(DATABASE).toHaveProperty('CLEANUP_INTERVAL_MS');
|
|
279
|
+
expect(DATABASE).toHaveProperty('DEFAULT_RETENTION_DAYS');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('PATH includes home directory', () => {
|
|
283
|
+
expect(DATABASE.PATH).toContain(os.homedir());
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('PATH ends with .db extension', () => {
|
|
287
|
+
expect(DATABASE.PATH).toMatch(/\.db$/);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('intervals are positive', () => {
|
|
291
|
+
expect(DATABASE.SAVE_INTERVAL_MS).toBeGreaterThan(0);
|
|
292
|
+
expect(DATABASE.CLEANUP_INTERVAL_MS).toBeGreaterThan(0);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('DEFAULT_RETENTION_DAYS is positive integer', () => {
|
|
296
|
+
expect(Number.isInteger(DATABASE.DEFAULT_RETENTION_DAYS)).toBe(true);
|
|
297
|
+
expect(DATABASE.DEFAULT_RETENTION_DAYS).toBeGreaterThan(0);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('RETRY', () => {
|
|
302
|
+
test('has required properties', () => {
|
|
303
|
+
expect(RETRY).toHaveProperty('DEFAULT_MAX_RETRIES');
|
|
304
|
+
expect(RETRY).toHaveProperty('DEFAULT_INITIAL_DELAY');
|
|
305
|
+
expect(RETRY).toHaveProperty('DEFAULT_MAX_DELAY');
|
|
306
|
+
expect(RETRY).toHaveProperty('DEFAULT_BACKOFF_MULTIPLIER');
|
|
307
|
+
expect(RETRY).toHaveProperty('TIMEOUT');
|
|
308
|
+
expect(RETRY).toHaveProperty('INTERVAL');
|
|
309
|
+
expect(RETRY).toHaveProperty('JITTER_FACTOR');
|
|
310
|
+
expect(RETRY).toHaveProperty('RETRYABLE_STATUSES');
|
|
311
|
+
expect(RETRY).toHaveProperty('RETRYABLE_ERRORS');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('retry settings are valid', () => {
|
|
315
|
+
expect(RETRY.DEFAULT_MAX_RETRIES).toBeGreaterThanOrEqual(0);
|
|
316
|
+
expect(RETRY.DEFAULT_INITIAL_DELAY).toBeGreaterThan(0);
|
|
317
|
+
expect(RETRY.DEFAULT_MAX_DELAY).toBeGreaterThan(RETRY.DEFAULT_INITIAL_DELAY);
|
|
318
|
+
expect(RETRY.DEFAULT_BACKOFF_MULTIPLIER).toBeGreaterThan(1);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('JITTER_FACTOR is between 0 and 1', () => {
|
|
322
|
+
expect(RETRY.JITTER_FACTOR).toBeGreaterThanOrEqual(0);
|
|
323
|
+
expect(RETRY.JITTER_FACTOR).toBeLessThanOrEqual(1);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('RETRYABLE_STATUSES is array of HTTP status codes', () => {
|
|
327
|
+
expect(Array.isArray(RETRY.RETRYABLE_STATUSES)).toBe(true);
|
|
328
|
+
for (const status of RETRY.RETRYABLE_STATUSES) {
|
|
329
|
+
expect(typeof status).toBe('number');
|
|
330
|
+
expect(status).toBeGreaterThanOrEqual(400);
|
|
331
|
+
expect(status).toBeLessThan(600);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('RETRYABLE_ERRORS is array of error codes', () => {
|
|
336
|
+
expect(Array.isArray(RETRY.RETRYABLE_ERRORS)).toBe(true);
|
|
337
|
+
for (const error of RETRY.RETRYABLE_ERRORS) {
|
|
338
|
+
expect(typeof error).toBe('string');
|
|
339
|
+
expect(error.length).toBeGreaterThan(0);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('DEFAULT_RETRY_OPTIONS', () => {
|
|
345
|
+
test('contains all retry options', () => {
|
|
346
|
+
expect(DEFAULT_RETRY_OPTIONS).toHaveProperty('maxRetries');
|
|
347
|
+
expect(DEFAULT_RETRY_OPTIONS).toHaveProperty('initialDelay');
|
|
348
|
+
expect(DEFAULT_RETRY_OPTIONS).toHaveProperty('maxDelay');
|
|
349
|
+
expect(DEFAULT_RETRY_OPTIONS).toHaveProperty('backoffMultiplier');
|
|
350
|
+
expect(DEFAULT_RETRY_OPTIONS).toHaveProperty('retryableStatuses');
|
|
351
|
+
expect(DEFAULT_RETRY_OPTIONS).toHaveProperty('retryableErrors');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('values match RETRY constants', () => {
|
|
355
|
+
expect(DEFAULT_RETRY_OPTIONS.maxRetries).toBe(RETRY.DEFAULT_MAX_RETRIES);
|
|
356
|
+
expect(DEFAULT_RETRY_OPTIONS.initialDelay).toBe(RETRY.DEFAULT_INITIAL_DELAY);
|
|
357
|
+
expect(DEFAULT_RETRY_OPTIONS.maxDelay).toBe(RETRY.DEFAULT_MAX_DELAY);
|
|
358
|
+
expect(DEFAULT_RETRY_OPTIONS.backoffMultiplier).toBe(RETRY.DEFAULT_BACKOFF_MULTIPLIER);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('ALERT_THRESHOLDS', () => {
|
|
363
|
+
test('has thresholds for CPU, MEMORY, DISK', () => {
|
|
364
|
+
expect(ALERT_THRESHOLDS).toHaveProperty('CPU');
|
|
365
|
+
expect(ALERT_THRESHOLDS).toHaveProperty('MEMORY');
|
|
366
|
+
expect(ALERT_THRESHOLDS).toHaveProperty('DISK');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('each threshold has warning and critical', () => {
|
|
370
|
+
for (const type of ['CPU', 'MEMORY', 'DISK']) {
|
|
371
|
+
expect(ALERT_THRESHOLDS[type]).toHaveProperty('warning');
|
|
372
|
+
expect(ALERT_THRESHOLDS[type]).toHaveProperty('critical');
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('all threshold values are 0-100', () => {
|
|
377
|
+
for (const [type, thresholds] of Object.entries(ALERT_THRESHOLDS)) {
|
|
378
|
+
expect(thresholds.warning).toBeGreaterThanOrEqual(0);
|
|
379
|
+
expect(thresholds.warning).toBeLessThanOrEqual(100);
|
|
380
|
+
expect(thresholds.critical).toBeGreaterThanOrEqual(0);
|
|
381
|
+
expect(thresholds.critical).toBeLessThanOrEqual(100);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('critical is greater than warning', () => {
|
|
386
|
+
for (const [type, thresholds] of Object.entries(ALERT_THRESHOLDS)) {
|
|
387
|
+
expect(thresholds.critical).toBeGreaterThanOrEqual(thresholds.warning);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('ALERT_RATE_LIMIT', () => {
|
|
393
|
+
test('has required properties', () => {
|
|
394
|
+
expect(ALERT_RATE_LIMIT).toHaveProperty('ENABLED');
|
|
395
|
+
expect(ALERT_RATE_LIMIT).toHaveProperty('WINDOW_MS');
|
|
396
|
+
expect(ALERT_RATE_LIMIT).toHaveProperty('MAX_ALERTS');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('ENABLED is boolean', () => {
|
|
400
|
+
expect(typeof ALERT_RATE_LIMIT.ENABLED).toBe('boolean');
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('WINDOW_MS is positive', () => {
|
|
404
|
+
expect(ALERT_RATE_LIMIT.WINDOW_MS).toBeGreaterThan(0);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('MAX_ALERTS is positive integer', () => {
|
|
408
|
+
expect(Number.isInteger(ALERT_RATE_LIMIT.MAX_ALERTS)).toBe(true);
|
|
409
|
+
expect(ALERT_RATE_LIMIT.MAX_ALERTS).toBeGreaterThan(0);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('MAX_ALERT_HISTORY', () => {
|
|
414
|
+
test('is positive integer', () => {
|
|
415
|
+
expect(Number.isInteger(MAX_ALERT_HISTORY)).toBe(true);
|
|
416
|
+
expect(MAX_ALERT_HISTORY).toBeGreaterThan(0);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe('VALIDATION', () => {
|
|
421
|
+
test('has REFRESH_INTERVAL constraints', () => {
|
|
422
|
+
expect(VALIDATION).toHaveProperty('REFRESH_INTERVAL');
|
|
423
|
+
expect(VALIDATION.REFRESH_INTERVAL).toHaveProperty('MIN');
|
|
424
|
+
expect(VALIDATION.REFRESH_INTERVAL).toHaveProperty('MAX');
|
|
425
|
+
expect(VALIDATION.REFRESH_INTERVAL.MIN).toBeLessThan(VALIDATION.REFRESH_INTERVAL.MAX);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test('has VALID_THEMES array', () => {
|
|
429
|
+
expect(Array.isArray(VALIDATION.VALID_THEMES)).toBe(true);
|
|
430
|
+
expect(VALIDATION.VALID_THEMES.length).toBeGreaterThan(0);
|
|
431
|
+
expect(VALIDATION.VALID_THEMES).toContain('default');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('has VALID_SORT_MODES array', () => {
|
|
435
|
+
expect(Array.isArray(VALIDATION.VALID_SORT_MODES)).toBe(true);
|
|
436
|
+
expect(VALIDATION.VALID_SORT_MODES.length).toBeGreaterThan(0);
|
|
437
|
+
expect(VALIDATION.VALID_SORT_MODES).toContain('time');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('has VALID_LOG_LEVELS array', () => {
|
|
441
|
+
expect(Array.isArray(VALIDATION.VALID_LOG_LEVELS)).toBe(true);
|
|
442
|
+
expect(VALIDATION.VALID_LOG_LEVELS.length).toBeGreaterThan(0);
|
|
443
|
+
expect(VALIDATION.VALID_LOG_LEVELS).toContain('all');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('has VALID_EXPORT_FORMATS array', () => {
|
|
447
|
+
expect(Array.isArray(VALIDATION.VALID_EXPORT_FORMATS)).toBe(true);
|
|
448
|
+
expect(VALIDATION.VALID_EXPORT_FORMATS.length).toBeGreaterThan(0);
|
|
449
|
+
expect(VALIDATION.VALID_EXPORT_FORMATS).toContain('json');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('has VALID_ENDPOINT_TYPES array', () => {
|
|
453
|
+
expect(Array.isArray(VALIDATION.VALID_ENDPOINT_TYPES)).toBe(true);
|
|
454
|
+
expect(VALIDATION.VALID_ENDPOINT_TYPES).toContain('local');
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('has ENDPOINT_NAME constraints', () => {
|
|
458
|
+
expect(VALIDATION).toHaveProperty('ENDPOINT_NAME');
|
|
459
|
+
expect(VALIDATION.ENDPOINT_NAME).toHaveProperty('MIN_LENGTH');
|
|
460
|
+
expect(VALIDATION.ENDPOINT_NAME).toHaveProperty('MAX_LENGTH');
|
|
461
|
+
expect(VALIDATION.ENDPOINT_NAME).toHaveProperty('PATTERN');
|
|
462
|
+
expect(VALIDATION.ENDPOINT_NAME.PATTERN instanceof RegExp).toBe(true);
|
|
463
|
+
expect(VALIDATION.ENDPOINT_NAME.MIN_LENGTH).toBeLessThan(VALIDATION.ENDPOINT_NAME.MAX_LENGTH);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe('COMMAND_TIMEOUTS', () => {
|
|
468
|
+
test('has timeout for all commands', () => {
|
|
469
|
+
const expectedCommands = [
|
|
470
|
+
'LAUNCHCTL', 'PS', 'SYSTEM_PROFILER', 'IOREG',
|
|
471
|
+
'POWERMETRICS', 'OPENCLAW_VERSION', 'OPENCLAW_LOGS',
|
|
472
|
+
'NVIDIA_SMI', 'LSPCI', 'RADEONTOP', 'POWERSHELL', 'WSL_SMI'
|
|
473
|
+
];
|
|
474
|
+
for (const cmd of expectedCommands) {
|
|
475
|
+
expect(COMMAND_TIMEOUTS).toHaveProperty(cmd);
|
|
476
|
+
expect(typeof COMMAND_TIMEOUTS[cmd]).toBe('number');
|
|
477
|
+
expect(COMMAND_TIMEOUTS[cmd]).toBeGreaterThan(0);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe('PATHS', () => {
|
|
483
|
+
test('has all required paths', () => {
|
|
484
|
+
expect(PATHS).toHaveProperty('SETTINGS');
|
|
485
|
+
expect(PATHS).toHaveProperty('EXPORTS');
|
|
486
|
+
expect(PATHS).toHaveProperty('OPENCLAW_CONFIG');
|
|
487
|
+
expect(PATHS).toHaveProperty('LOG');
|
|
488
|
+
expect(PATHS).toHaveProperty('HOME_DIR');
|
|
489
|
+
expect(PATHS).toHaveProperty('OPENCLAW_DIR');
|
|
490
|
+
expect(PATHS).toHaveProperty('AGENTS_DIR');
|
|
491
|
+
expect(PATHS).toHaveProperty('WIDGETS_DIR');
|
|
492
|
+
expect(PATHS).toHaveProperty('PLUGINS_DIR');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test('all paths are strings', () => {
|
|
496
|
+
for (const [key, value] of Object.entries(PATHS)) {
|
|
497
|
+
expect(typeof value).toBe('string');
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('HOME_DIR matches os.homedir()', () => {
|
|
502
|
+
expect(PATHS.HOME_DIR).toBe(os.homedir());
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('paths include home directory', () => {
|
|
506
|
+
expect(PATHS.SETTINGS).toContain(os.homedir());
|
|
507
|
+
expect(PATHS.EXPORTS).toContain(os.homedir());
|
|
508
|
+
expect(PATHS.LOG).toContain(os.homedir());
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test('paths are in .openclaw directory', () => {
|
|
512
|
+
expect(PATHS.SETTINGS).toContain('.openclaw');
|
|
513
|
+
expect(PATHS.OPENCLAW_CONFIG).toContain('.openclaw');
|
|
514
|
+
expect(PATHS.LOG).toContain('.openclaw');
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
describe('DEFAULT_SETTINGS', () => {
|
|
519
|
+
test('has required settings', () => {
|
|
520
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('refreshInterval');
|
|
521
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('logLevelFilter');
|
|
522
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('sessionSortMode');
|
|
523
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('theme');
|
|
524
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('exportFormat');
|
|
525
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('exportDirectory');
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test('refreshInterval matches default', () => {
|
|
529
|
+
expect(DEFAULT_SETTINGS.refreshInterval).toBe(REFRESH_INTERVALS.DEFAULT);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('logLevelFilter is valid', () => {
|
|
533
|
+
expect(VALIDATION.VALID_LOG_LEVELS).toContain(DEFAULT_SETTINGS.logLevelFilter);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test('sessionSortMode is valid', () => {
|
|
537
|
+
expect(VALIDATION.VALID_SORT_MODES).toContain(DEFAULT_SETTINGS.sessionSortMode);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('theme is valid', () => {
|
|
541
|
+
expect(VALIDATION.VALID_THEMES).toContain(DEFAULT_SETTINGS.theme);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('exportFormat is valid', () => {
|
|
545
|
+
expect(VALIDATION.VALID_EXPORT_FORMATS).toContain(DEFAULT_SETTINGS.exportFormat);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('exportDirectory matches PATHS.EXPORTS', () => {
|
|
549
|
+
expect(DEFAULT_SETTINGS.exportDirectory).toBe(PATHS.EXPORTS);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('has all widget visibility settings', () => {
|
|
553
|
+
for (let i = 1; i <= 8; i++) {
|
|
554
|
+
expect(DEFAULT_SETTINGS).toHaveProperty(`showWidget${i}`);
|
|
555
|
+
expect(typeof DEFAULT_SETTINGS[`showWidget${i}`]).toBe('boolean');
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test('favorites is empty object', () => {
|
|
560
|
+
expect(DEFAULT_SETTINGS.favorites).toEqual({});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test('showFavoritesOnly is boolean', () => {
|
|
564
|
+
expect(typeof DEFAULT_SETTINGS.showFavoritesOnly).toBe('boolean');
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('firstRun is boolean', () => {
|
|
568
|
+
expect(typeof DEFAULT_SETTINGS.firstRun).toBe('boolean');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test('showPerformanceMetrics is boolean', () => {
|
|
572
|
+
expect(typeof DEFAULT_SETTINGS.showPerformanceMetrics).toBe('boolean');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('has gatewayEndpoints array', () => {
|
|
576
|
+
expect(Array.isArray(DEFAULT_SETTINGS.gatewayEndpoints)).toBe(true);
|
|
577
|
+
expect(DEFAULT_SETTINGS.gatewayEndpoints.length).toBeGreaterThan(0);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test('first gateway endpoint is valid', () => {
|
|
581
|
+
const endpoint = DEFAULT_SETTINGS.gatewayEndpoints[0];
|
|
582
|
+
expect(endpoint).toHaveProperty('name');
|
|
583
|
+
expect(endpoint).toHaveProperty('host');
|
|
584
|
+
expect(endpoint).toHaveProperty('port');
|
|
585
|
+
expect(endpoint).toHaveProperty('enabled');
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test('has webInterface config', () => {
|
|
589
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('webInterface');
|
|
590
|
+
expect(DEFAULT_SETTINGS.webInterface).toHaveProperty('enabled');
|
|
591
|
+
expect(DEFAULT_SETTINGS.webInterface).toHaveProperty('port');
|
|
592
|
+
expect(DEFAULT_SETTINGS.webInterface).toHaveProperty('host');
|
|
593
|
+
expect(DEFAULT_SETTINGS.webInterface).toHaveProperty('cors');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test('webInterface port matches WEB.DEFAULT_PORT', () => {
|
|
597
|
+
expect(DEFAULT_SETTINGS.webInterface.port).toBe(WEB.DEFAULT_PORT);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test('has widgetLoading config', () => {
|
|
601
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('widgetLoading');
|
|
602
|
+
expect(DEFAULT_SETTINGS.widgetLoading).toHaveProperty('enabled');
|
|
603
|
+
expect(DEFAULT_SETTINGS.widgetLoading).toHaveProperty('preloadPriority');
|
|
604
|
+
expect(DEFAULT_SETTINGS.widgetLoading).toHaveProperty('lazyLoadDelay');
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test('has plugins config object', () => {
|
|
608
|
+
expect(DEFAULT_SETTINGS).toHaveProperty('plugins');
|
|
609
|
+
expect(typeof DEFAULT_SETTINGS.plugins).toBe('object');
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
describe('WORKERS', () => {
|
|
614
|
+
test('has required properties', () => {
|
|
615
|
+
expect(WORKERS).toHaveProperty('ENABLED');
|
|
616
|
+
expect(WORKERS).toHaveProperty('MAX_WORKERS');
|
|
617
|
+
expect(WORKERS).toHaveProperty('TASK_TIMEOUT');
|
|
618
|
+
expect(WORKERS).toHaveProperty('FALLBACK_ON_ERROR');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test('ENABLED is boolean', () => {
|
|
622
|
+
expect(typeof WORKERS.ENABLED).toBe('boolean');
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test('MAX_WORKERS is positive integer', () => {
|
|
626
|
+
expect(Number.isInteger(WORKERS.MAX_WORKERS)).toBe(true);
|
|
627
|
+
expect(WORKERS.MAX_WORKERS).toBeGreaterThan(0);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test('TASK_TIMEOUT is positive', () => {
|
|
631
|
+
expect(WORKERS.TASK_TIMEOUT).toBeGreaterThan(0);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('FALLBACK_ON_ERROR is boolean', () => {
|
|
635
|
+
expect(typeof WORKERS.FALLBACK_ON_ERROR).toBe('boolean');
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
describe('WEB', () => {
|
|
640
|
+
test('has required properties', () => {
|
|
641
|
+
expect(WEB).toHaveProperty('DEFAULT_PORT');
|
|
642
|
+
expect(WEB).toHaveProperty('HOST');
|
|
643
|
+
expect(WEB).toHaveProperty('CORS_ORIGIN');
|
|
644
|
+
expect(WEB).toHaveProperty('REQUEST_TIMEOUT');
|
|
645
|
+
expect(WEB).toHaveProperty('REFRESH_CACHE_MS');
|
|
646
|
+
expect(WEB).toHaveProperty('ENDPOINTS');
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test('DEFAULT_PORT is valid port', () => {
|
|
650
|
+
expect(WEB.DEFAULT_PORT).toBeGreaterThan(0);
|
|
651
|
+
expect(WEB.DEFAULT_PORT).toBeLessThanOrEqual(65535);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
test('HOST is valid', () => {
|
|
655
|
+
expect(typeof WEB.HOST).toBe('string');
|
|
656
|
+
expect(WEB.HOST.length).toBeGreaterThan(0);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test('ENDPOINTS has required routes', () => {
|
|
660
|
+
expect(WEB.ENDPOINTS).toHaveProperty('HEALTH');
|
|
661
|
+
expect(WEB.ENDPOINTS).toHaveProperty('METRICS');
|
|
662
|
+
expect(WEB.ENDPOINTS).toHaveProperty('SESSIONS');
|
|
663
|
+
expect(WEB.ENDPOINTS).toHaveProperty('AGENTS');
|
|
664
|
+
expect(WEB.ENDPOINTS).toHaveProperty('LOGS');
|
|
665
|
+
expect(WEB.ENDPOINTS).toHaveProperty('STATUS');
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test('all endpoints start with /', () => {
|
|
669
|
+
for (const [key, value] of Object.entries(WEB.ENDPOINTS)) {
|
|
670
|
+
expect(value).toMatch(/^\//);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
describe('WIDGETS', () => {
|
|
676
|
+
test('has required properties', () => {
|
|
677
|
+
expect(WIDGETS).toHaveProperty('ENABLED');
|
|
678
|
+
expect(WIDGETS).toHaveProperty('AUTO_DISCOVER');
|
|
679
|
+
expect(WIDGETS).toHaveProperty('PRELOAD_PRIORITY');
|
|
680
|
+
expect(WIDGETS).toHaveProperty('LAZY_LOAD_DELAY');
|
|
681
|
+
expect(WIDGETS).toHaveProperty('MAX_CONCURRENT_LOADS');
|
|
682
|
+
expect(WIDGETS).toHaveProperty('FALLBACK_ON_ERROR');
|
|
683
|
+
expect(WIDGETS).toHaveProperty('CACHE_TTL');
|
|
684
|
+
expect(WIDGETS).toHaveProperty('BUILTIN');
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test('ENABLED and AUTO_DISCOVER are boolean', () => {
|
|
688
|
+
expect(typeof WIDGETS.ENABLED).toBe('boolean');
|
|
689
|
+
expect(typeof WIDGETS.AUTO_DISCOVER).toBe('boolean');
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test('PRELOAD_PRIORITY is array with known widgets', () => {
|
|
693
|
+
expect(Array.isArray(WIDGETS.PRELOAD_PRIORITY)).toBe(true);
|
|
694
|
+
expect(WIDGETS.PRELOAD_PRIORITY).toContain('cpu');
|
|
695
|
+
expect(WIDGETS.PRELOAD_PRIORITY).toContain('memory');
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
test('LAZY_LOAD_DELAY is positive', () => {
|
|
699
|
+
expect(WIDGETS.LAZY_LOAD_DELAY).toBeGreaterThan(0);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test('MAX_CONCURRENT_LOADS is positive integer', () => {
|
|
703
|
+
expect(Number.isInteger(WIDGETS.MAX_CONCURRENT_LOADS)).toBe(true);
|
|
704
|
+
expect(WIDGETS.MAX_CONCURRENT_LOADS).toBeGreaterThan(0);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test('CACHE_TTL is positive', () => {
|
|
708
|
+
expect(WIDGETS.CACHE_TTL).toBeGreaterThan(0);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
test('BUILTIN has known widgets', () => {
|
|
712
|
+
expect(WIDGETS.BUILTIN).toHaveProperty('cpu');
|
|
713
|
+
expect(WIDGETS.BUILTIN).toHaveProperty('memory');
|
|
714
|
+
expect(WIDGETS.BUILTIN).toHaveProperty('gpu');
|
|
715
|
+
expect(WIDGETS.BUILTIN).toHaveProperty('network');
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test('each builtin widget has priority and lazyLoad', () => {
|
|
719
|
+
for (const [name, config] of Object.entries(WIDGETS.BUILTIN)) {
|
|
720
|
+
expect(config).toHaveProperty('priority');
|
|
721
|
+
expect(config).toHaveProperty('lazyLoad');
|
|
722
|
+
expect(typeof config.priority).toBe('number');
|
|
723
|
+
expect(typeof config.lazyLoad).toBe('boolean');
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
describe('Default export', () => {
|
|
729
|
+
test('exports all named exports', () => {
|
|
730
|
+
expect(config).toHaveProperty('REFRESH_INTERVALS');
|
|
731
|
+
expect(config).toHaveProperty('IDLE_THRESHOLD_MS');
|
|
732
|
+
expect(config).toHaveProperty('HISTORY');
|
|
733
|
+
expect(config).toHaveProperty('GATEWAY');
|
|
734
|
+
expect(config).toHaveProperty('DEFAULT_GATEWAY_ENDPOINT');
|
|
735
|
+
expect(config).toHaveProperty('CHECKSUM');
|
|
736
|
+
expect(config).toHaveProperty('UI');
|
|
737
|
+
expect(config).toHaveProperty('CACHE_TTL');
|
|
738
|
+
expect(config).toHaveProperty('CACHE_CONFIG');
|
|
739
|
+
expect(config).toHaveProperty('DATABASE');
|
|
740
|
+
expect(config).toHaveProperty('RETRY');
|
|
741
|
+
expect(config).toHaveProperty('DEFAULT_RETRY_OPTIONS');
|
|
742
|
+
expect(config).toHaveProperty('ALERT_THRESHOLDS');
|
|
743
|
+
expect(config).toHaveProperty('ALERT_RATE_LIMIT');
|
|
744
|
+
expect(config).toHaveProperty('MAX_ALERT_HISTORY');
|
|
745
|
+
expect(config).toHaveProperty('VALIDATION');
|
|
746
|
+
expect(config).toHaveProperty('COMMAND_TIMEOUTS');
|
|
747
|
+
expect(config).toHaveProperty('PATHS');
|
|
748
|
+
expect(config).toHaveProperty('DEFAULT_SETTINGS');
|
|
749
|
+
expect(config).toHaveProperty('WORKERS');
|
|
750
|
+
expect(config).toHaveProperty('WEB');
|
|
751
|
+
expect(config).toHaveProperty('WIDGETS');
|
|
752
|
+
expect(config).toHaveProperty('DASHBOARD_VERSION');
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
});
|