claw-dashboard 2.1.1 → 2.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +31 -41
  4. package/package.json +22 -12
  5. package/src/auto-save.js +11 -5
  6. package/src/cli/export-snapshot.js +3 -1
  7. package/src/cli/import-snapshot.js +3 -1
  8. package/src/config.js +9 -15
  9. package/src/errors.js +0 -9
  10. package/src/security.js +6 -2
  11. package/src/snapshot.js +73 -26
  12. package/src/web-server.js +7 -10
  13. package/.c8rc.json +0 -38
  14. package/.dockerignore +0 -68
  15. package/.github/dependabot.yml +0 -45
  16. package/.github/pull_request_template.md +0 -39
  17. package/.github/workflows/ci.yml +0 -147
  18. package/.github/workflows/release.yml +0 -40
  19. package/.github/workflows/security.yml +0 -84
  20. package/.husky/pre-commit +0 -1
  21. package/.planning/codebase/ARCHITECTURE.md +0 -206
  22. package/.planning/codebase/INTEGRATIONS.md +0 -150
  23. package/.planning/codebase/STACK.md +0 -122
  24. package/.planning/codebase/STRUCTURE.md +0 -201
  25. package/CONTRIBUTING.md +0 -378
  26. package/Dockerfile +0 -54
  27. package/FEATURES.md +0 -307
  28. package/TODO.md +0 -28
  29. package/ai.openclaw.dashboard.plist +0 -35
  30. package/build-cjs.js +0 -127
  31. package/cjs-shim.js +0 -13
  32. package/dist/clawdash +0 -1729
  33. package/dist/clawdash.meta.json +0 -2236
  34. package/dist/widgets.cjs +0 -6511
  35. package/docker-compose.yml +0 -67
  36. package/esbuild.config.js +0 -158
  37. package/eslint.config.js +0 -56
  38. package/examples/plugins/README.md +0 -122
  39. package/examples/plugins/api-status/index.js +0 -294
  40. package/examples/plugins/api-status/plugin.json +0 -19
  41. package/examples/plugins/hello-world/index.js +0 -104
  42. package/examples/plugins/hello-world/plugin.json +0 -15
  43. package/examples/plugins/system-metrics-chart/index.js +0 -339
  44. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  45. package/examples/plugins/weather-widget/index.js +0 -136
  46. package/examples/plugins/weather-widget/plugin.json +0 -19
  47. package/index.cjs +0 -23005
  48. package/jest.config.js +0 -11
  49. package/scripts/release.js +0 -595
  50. package/src/database.js +0 -734
  51. package/tests/alerts.test.js +0 -437
  52. package/tests/auto-save.test.js +0 -529
  53. package/tests/cache.test.js +0 -317
  54. package/tests/cli.test.js +0 -351
  55. package/tests/command-palette.test.js +0 -320
  56. package/tests/config-processor.test.js +0 -452
  57. package/tests/config-validator.test.js +0 -710
  58. package/tests/config-watcher.test.js +0 -594
  59. package/tests/config.test.js +0 -755
  60. package/tests/database.test.js +0 -438
  61. package/tests/errors.test.js +0 -189
  62. package/tests/example-plugins.test.js +0 -624
  63. package/tests/integration.test.js +0 -1321
  64. package/tests/loading-states.test.js +0 -300
  65. package/tests/manifest-validation-on-load.test.js +0 -671
  66. package/tests/performance-monitor.test.js +0 -190
  67. package/tests/plugin-api-rate-limit.test.js +0 -302
  68. package/tests/plugin-errors.test.js +0 -311
  69. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  70. package/tests/plugin-reload.test.js +0 -764
  71. package/tests/rate-limiter.test.js +0 -539
  72. package/tests/removed-dependencies.test.js +0 -74
  73. package/tests/retry.test.js +0 -308
  74. package/tests/security.test.js +0 -411
  75. package/tests/settings-modal.test.js +0 -226
  76. package/tests/theme-selector.test.js +0 -57
  77. package/tests/utils.js +0 -242
  78. package/tests/utils.test.js +0 -317
  79. package/tests/validate-plugin-cli.test.js +0 -359
  80. package/tests/validation.test.js +0 -837
  81. package/tests/web-server.test.js +0 -646
  82. package/tests/widget-arrange-mode.test.js +0 -183
  83. package/tests/widget-config-hot-reload.test.js +0 -465
  84. package/tests/widget-dependency.test.js +0 -591
  85. package/tests/widget-error-boundary.test.js +0 -749
  86. package/tests/widget-error-isolation.test.js +0 -469
  87. package/tests/widget-integration.test.js +0 -1147
  88. package/tests/widget-refresh-intervals.test.js +0 -284
  89. package/tests/worker-pool.test.js +0 -522
@@ -1,710 +0,0 @@
1
- /**
2
- * Tests for config-validator module
3
- */
4
-
5
- import { validateConfig, validateConfigFile, formatConfigValidationResult, getDefaultConfigPath } from '../src/config-validator.js';
6
- import { mkdtempSync, writeFileSync, rmSync } from 'fs';
7
- import { join } from 'path';
8
- import os from 'os';
9
- import { execSync } from 'child_process';
10
-
11
- const CLI_PATH = join(process.cwd(), 'index.js');
12
-
13
- describe('config-validator', () => {
14
- describe('validateConfig', () => {
15
- describe('valid configurations', () => {
16
- test('validates minimal valid config', () => {
17
- const config = {
18
- refreshInterval: 2000,
19
- theme: 'auto',
20
- };
21
- const result = validateConfig(config);
22
- expect(result.valid).toBe(true);
23
- expect(result.errors).toHaveLength(0);
24
- });
25
-
26
- test('validates full default config', () => {
27
- const config = {
28
- refreshInterval: 2000,
29
- logLevelFilter: 'all',
30
- sessionSortMode: 'time',
31
- theme: 'dark',
32
- exportFormat: 'json',
33
- showWidget1: true,
34
- showWidget2: true,
35
- showPerformanceMetrics: false,
36
- firstRun: false,
37
- showFavoritesOnly: false,
38
- favorites: {},
39
- gatewayEndpoints: [
40
- { name: 'local', host: 'localhost', port: 18789, enabled: true, type: 'local' }
41
- ],
42
- activeGatewayEndpoint: 'local',
43
- webInterface: {
44
- enabled: false,
45
- port: 18790,
46
- host: '0.0.0.0',
47
- cors: true,
48
- },
49
- widgetLoading: {
50
- enabled: true,
51
- preloadPriority: ['cpu', 'memory'],
52
- lazyLoadDelay: 500,
53
- maxConcurrent: 3,
54
- autoDiscover: true,
55
- },
56
- plugins: {},
57
- };
58
- const result = validateConfig(config);
59
- expect(result.valid).toBe(true);
60
- expect(result.errors).toHaveLength(0);
61
- });
62
-
63
- test('validates all valid themes', () => {
64
- const themes = ['default', 'dark', 'high-contrast', 'ocean', 'auto'];
65
- for (const theme of themes) {
66
- const result = validateConfig({ theme });
67
- expect(result.valid).toBe(true);
68
- expect(result.errors).toHaveLength(0);
69
- }
70
- });
71
-
72
- test('validates all valid log levels', () => {
73
- const levels = ['all', 'error', 'warn', 'info', 'debug'];
74
- for (const level of levels) {
75
- const result = validateConfig({ logLevelFilter: level });
76
- expect(result.valid).toBe(true);
77
- expect(result.errors).toHaveLength(0);
78
- }
79
- });
80
-
81
- test('validates all valid sort modes', () => {
82
- const modes = ['time', 'tokens', 'idle', 'name'];
83
- for (const mode of modes) {
84
- const result = validateConfig({ sessionSortMode: mode });
85
- expect(result.valid).toBe(true);
86
- expect(result.errors).toHaveLength(0);
87
- }
88
- });
89
-
90
- test('validates all valid export formats', () => {
91
- const formats = ['json', 'csv'];
92
- for (const format of formats) {
93
- const result = validateConfig({ exportFormat: format });
94
- expect(result.valid).toBe(true);
95
- expect(result.errors).toHaveLength(0);
96
- }
97
- });
98
- });
99
-
100
- describe('invalid configurations', () => {
101
- test('rejects non-object config', () => {
102
- const result = validateConfig('not an object');
103
- expect(result.valid).toBe(false);
104
- expect(result.errors[0]).toContain('JSON object');
105
- });
106
-
107
- test('rejects null config', () => {
108
- const result = validateConfig(null);
109
- expect(result.valid).toBe(false);
110
- });
111
-
112
- test('rejects invalid theme', () => {
113
- const result = validateConfig({ theme: 'invalid-theme' });
114
- expect(result.valid).toBe(false);
115
- expect(result.errors[0]).toContain('theme');
116
- });
117
-
118
- test('rejects invalid log level', () => {
119
- const result = validateConfig({ logLevelFilter: 'verbose' });
120
- expect(result.valid).toBe(false);
121
- expect(result.errors[0]).toContain('logLevelFilter');
122
- });
123
-
124
- test('rejects invalid sort mode', () => {
125
- const result = validateConfig({ sessionSortMode: 'alphabetical' });
126
- expect(result.valid).toBe(false);
127
- expect(result.errors[0]).toContain('sessionSortMode');
128
- });
129
-
130
- test('rejects invalid export format', () => {
131
- const result = validateConfig({ exportFormat: 'xml' });
132
- expect(result.valid).toBe(false);
133
- expect(result.errors[0]).toContain('exportFormat');
134
- });
135
-
136
- test('rejects refresh interval too low', () => {
137
- const result = validateConfig({ refreshInterval: 100 });
138
- expect(result.valid).toBe(false);
139
- expect(result.errors[0]).toContain('refreshInterval');
140
- expect(result.errors[0]).toContain('at least');
141
- });
142
-
143
- test('rejects refresh interval too high', () => {
144
- const result = validateConfig({ refreshInterval: 120000 });
145
- expect(result.valid).toBe(false);
146
- expect(result.errors[0]).toContain('refreshInterval');
147
- expect(result.errors[0]).toContain('at most');
148
- });
149
- });
150
-
151
- describe('gateway endpoints validation', () => {
152
- test('validates valid endpoint', () => {
153
- const config = {
154
- gatewayEndpoints: [
155
- { name: 'local', host: 'localhost', port: 18789, enabled: true, type: 'local' }
156
- ]
157
- };
158
- const result = validateConfig(config);
159
- expect(result.valid).toBe(true);
160
- });
161
-
162
- test('rejects missing required endpoint fields', () => {
163
- const config = {
164
- gatewayEndpoints: [
165
- { host: 'localhost', port: 18789 } // missing name
166
- ]
167
- };
168
- const result = validateConfig(config);
169
- expect(result.valid).toBe(false);
170
- expect(result.errors.some(e => e.includes('name'))).toBe(true);
171
- });
172
-
173
- test('rejects invalid endpoint name', () => {
174
- const config = {
175
- gatewayEndpoints: [
176
- { name: 'invalid name!', host: 'localhost', port: 18789 }
177
- ]
178
- };
179
- const result = validateConfig(config);
180
- expect(result.valid).toBe(false);
181
- expect(result.errors.some(e => e.includes('name'))).toBe(true);
182
- });
183
-
184
- test('rejects too long endpoint name', () => {
185
- const config = {
186
- gatewayEndpoints: [
187
- { name: 'a'.repeat(50), host: 'localhost', port: 18789 }
188
- ]
189
- };
190
- const result = validateConfig(config);
191
- expect(result.valid).toBe(false);
192
- });
193
-
194
- test('rejects invalid port number', () => {
195
- const config = {
196
- gatewayEndpoints: [
197
- { name: 'local', host: 'localhost', port: 99999 }
198
- ]
199
- };
200
- const result = validateConfig(config);
201
- expect(result.valid).toBe(false);
202
- expect(result.errors.some(e => e.includes('port'))).toBe(true);
203
- });
204
-
205
- test('rejects invalid endpoint type', () => {
206
- const config = {
207
- gatewayEndpoints: [
208
- { name: 'local', host: 'localhost', port: 18789, type: 'invalid' }
209
- ]
210
- };
211
- const result = validateConfig(config);
212
- expect(result.valid).toBe(false);
213
- expect(result.errors.some(e => e.includes('type'))).toBe(true);
214
- });
215
-
216
- test('warns on empty gatewayEndpoints', () => {
217
- const config = { gatewayEndpoints: [] };
218
- const result = validateConfig(config);
219
- expect(result.valid).toBe(true);
220
- expect(result.warnings.some(w => w.includes('empty'))).toBe(true);
221
- });
222
-
223
- test('rejects too many endpoints', () => {
224
- const endpoints = Array(15).fill(null).map((_, i) => ({
225
- name: `endpoint${i}`,
226
- host: 'localhost',
227
- port: 18789
228
- }));
229
- const config = { gatewayEndpoints: endpoints };
230
- const result = validateConfig(config);
231
- expect(result.valid).toBe(false);
232
- expect(result.errors.some(e => e.includes('maximum'))).toBe(true);
233
- });
234
- });
235
-
236
- describe('web interface validation', () => {
237
- test('validates valid web interface config', () => {
238
- const config = {
239
- webInterface: {
240
- enabled: true,
241
- port: 18790,
242
- host: '0.0.0.0',
243
- cors: true,
244
- }
245
- };
246
- const result = validateConfig(config);
247
- expect(result.valid).toBe(true);
248
- });
249
-
250
- test('rejects invalid port in web interface', () => {
251
- const config = {
252
- webInterface: {
253
- enabled: true,
254
- port: 70000,
255
- }
256
- };
257
- const result = validateConfig(config);
258
- expect(result.valid).toBe(false);
259
- expect(result.errors.some(e => e.includes('port'))).toBe(true);
260
- });
261
-
262
- test('validates rate limit config', () => {
263
- const config = {
264
- webInterface: {
265
- rateLimit: {
266
- enabled: true,
267
- windowMs: 60000,
268
- maxRequests: 100,
269
- }
270
- }
271
- };
272
- const result = validateConfig(config);
273
- expect(result.valid).toBe(true);
274
- });
275
-
276
- test('rejects invalid rate limit windowMs', () => {
277
- const config = {
278
- webInterface: {
279
- rateLimit: {
280
- enabled: true,
281
- windowMs: 500,
282
- maxRequests: 100,
283
- }
284
- }
285
- };
286
- const result = validateConfig(config);
287
- expect(result.valid).toBe(true); // Still valid, but warns
288
- expect(result.warnings.some(w => w.includes('windowMs'))).toBe(true);
289
- });
290
-
291
- test('validates CORS origins as array', () => {
292
- const config = {
293
- webInterface: {
294
- corsOrigins: ['https://example.com', 'https://app.example.com']
295
- }
296
- };
297
- const result = validateConfig(config);
298
- expect(result.valid).toBe(true);
299
- });
300
-
301
- test('validates CORS origins as string', () => {
302
- const config = {
303
- webInterface: {
304
- corsOrigins: '*'
305
- }
306
- };
307
- const result = validateConfig(config);
308
- expect(result.valid).toBe(true);
309
- });
310
-
311
- test('rejects invalid CORS origins type', () => {
312
- const config = {
313
- webInterface: {
314
- corsOrigins: 123
315
- }
316
- };
317
- const result = validateConfig(config);
318
- expect(result.valid).toBe(false);
319
- });
320
- });
321
-
322
- describe('widget loading validation', () => {
323
- test('validates valid widget loading config', () => {
324
- const config = {
325
- widgetLoading: {
326
- enabled: true,
327
- preloadPriority: ['cpu', 'memory'],
328
- lazyLoadDelay: 500,
329
- maxConcurrent: 3,
330
- autoDiscover: true,
331
- }
332
- };
333
- const result = validateConfig(config);
334
- expect(result.valid).toBe(true);
335
- });
336
-
337
- test('rejects negative lazyLoadDelay', () => {
338
- const config = {
339
- widgetLoading: {
340
- lazyLoadDelay: -100
341
- }
342
- };
343
- const result = validateConfig(config);
344
- expect(result.valid).toBe(false);
345
- });
346
-
347
- test('rejects zero maxConcurrent', () => {
348
- const config = {
349
- widgetLoading: {
350
- maxConcurrent: 0
351
- }
352
- };
353
- const result = validateConfig(config);
354
- expect(result.valid).toBe(false);
355
- });
356
- });
357
-
358
- describe('strict mode', () => {
359
- test('allows unknown properties when not strict', () => {
360
- const config = { unknownField: 'value' };
361
- const result = validateConfig(config, { strict: false });
362
- expect(result.valid).toBe(true);
363
- });
364
-
365
- test('rejects unknown properties in strict mode', () => {
366
- const config = { unknownField: 'value' };
367
- const result = validateConfig(config, { strict: true });
368
- expect(result.valid).toBe(false);
369
- expect(result.errors.some(e => e.includes('unknownField'))).toBe(true);
370
- });
371
- });
372
-
373
- describe('stats', () => {
374
- test('returns field count in stats', () => {
375
- const config = { refreshInterval: 2000, theme: 'auto' };
376
- const result = validateConfig(config);
377
- expect(result.stats.fieldCount).toBe(2);
378
- });
379
- });
380
- });
381
-
382
- describe('validateConfigFile', () => {
383
- let tempDir;
384
-
385
- beforeEach(() => {
386
- tempDir = mkdtempSync(join(os.tmpdir(), 'config-validator-test-'));
387
- });
388
-
389
- afterEach(() => {
390
- try {
391
- rmSync(tempDir, { recursive: true, force: true });
392
- } catch {}
393
- });
394
-
395
- test('validates existing config file', () => {
396
- const config = { refreshInterval: 2000, theme: 'dark' };
397
- const configPath = join(tempDir, 'config.json');
398
- writeFileSync(configPath, JSON.stringify(config, null, 2));
399
-
400
- const result = validateConfigFile(configPath);
401
- expect(result.valid).toBe(true);
402
- });
403
-
404
- test('returns error for non-existent file', () => {
405
- const result = validateConfigFile(join(tempDir, 'nonexistent.json'));
406
- expect(result.valid).toBe(false);
407
- expect(result.errors[0]).toContain('not found');
408
- });
409
-
410
- test('returns error for invalid JSON', () => {
411
- const configPath = join(tempDir, 'invalid.json');
412
- writeFileSync(configPath, 'not valid json {');
413
-
414
- const result = validateConfigFile(configPath);
415
- expect(result.valid).toBe(false);
416
- expect(result.errors[0]).toContain('parse');
417
- });
418
- });
419
-
420
- describe('formatConfigValidationResult', () => {
421
- test('formats valid result', () => {
422
- const result = {
423
- valid: true,
424
- errors: [],
425
- warnings: [],
426
- info: ['Test info'],
427
- stats: { fieldCount: 5 }
428
- };
429
- const formatted = formatConfigValidationResult(result);
430
- expect(formatted).toContain('is valid');
431
- expect(formatted).toContain('5 field(s)');
432
- });
433
-
434
- test('formats invalid result', () => {
435
- const result = {
436
- valid: false,
437
- errors: ['Error 1', 'Error 2'],
438
- warnings: ['Warning 1'],
439
- info: [],
440
- stats: { fieldCount: 3 }
441
- };
442
- const formatted = formatConfigValidationResult(result, '/test/config.json');
443
- expect(formatted).toContain('validation failed');
444
- expect(formatted).toContain('/test/config.json');
445
- expect(formatted).toContain('Error 1');
446
- expect(formatted).toContain('Warning 1');
447
- });
448
-
449
- test('includes path in output', () => {
450
- const result = {
451
- valid: true,
452
- errors: [],
453
- warnings: [],
454
- info: [],
455
- stats: { fieldCount: 1 }
456
- };
457
- const formatted = formatConfigValidationResult(result, '/path/to/config.json');
458
- expect(formatted).toContain('/path/to/config.json');
459
- });
460
- });
461
-
462
- describe('getDefaultConfigPath', () => {
463
- test('returns path in home directory', () => {
464
- const path = getDefaultConfigPath();
465
- expect(path).toContain('.openclaw');
466
- expect(path).toContain('dashboard-settings.json');
467
- expect(path.startsWith(os.homedir())).toBe(true);
468
- });
469
- });
470
- });
471
-
472
- describe('validate-config CLI', () => {
473
- let tempDir;
474
-
475
- beforeEach(() => {
476
- tempDir = mkdtempSync(join(os.tmpdir(), 'validate-config-test-'));
477
- });
478
-
479
- afterEach(() => {
480
- try {
481
- rmSync(tempDir, { recursive: true, force: true });
482
- } catch {}
483
- });
484
-
485
- const runCli = (args) => {
486
- try {
487
- const output = execSync(`node ${CLI_PATH} ${args}`, {
488
- encoding: 'utf8',
489
- cwd: process.cwd(),
490
- });
491
- return { exitCode: 0, output, stderr: '' };
492
- } catch (error) {
493
- return {
494
- exitCode: error.status || 1,
495
- output: error.stdout || '',
496
- stderr: error.stderr || error.message,
497
- };
498
- }
499
- };
500
-
501
- describe('help', () => {
502
- test('shows help with --help', () => {
503
- const result = runCli('validate-config --help');
504
- expect(result.exitCode).toBe(0);
505
- expect(result.output).toContain('Validate Dashboard Configuration');
506
- expect(result.output).toContain('Usage:');
507
- expect(result.output).toContain('Options:');
508
- expect(result.output).toContain('--json');
509
- expect(result.output).toContain('--strict');
510
- });
511
-
512
- test('shows help with -h', () => {
513
- const result = runCli('validate-config -h');
514
- expect(result.exitCode).toBe(0);
515
- expect(result.output).toContain('Validate Dashboard Configuration');
516
- });
517
- });
518
-
519
- describe('validation', () => {
520
- test('validates a valid config file', () => {
521
- const validConfig = {
522
- refreshInterval: 2000,
523
- theme: 'dark',
524
- logLevelFilter: 'info',
525
- sessionSortMode: 'time',
526
- };
527
- const configPath = join(tempDir, 'valid-config.json');
528
- writeFileSync(configPath, JSON.stringify(validConfig, null, 2));
529
-
530
- const result = runCli(`validate-config ${configPath}`);
531
- expect(result.exitCode).toBe(0);
532
- expect(result.output).toContain('is valid');
533
- });
534
-
535
- test('returns error for non-existent file', () => {
536
- const result = runCli('validate-config /nonexistent/path/config.json');
537
- expect(result.exitCode).toBe(1);
538
- expect(result.output).toContain('not found');
539
- });
540
-
541
- test('returns error for invalid JSON', () => {
542
- const configPath = join(tempDir, 'invalid.json');
543
- writeFileSync(configPath, 'not valid json {');
544
-
545
- const result = runCli(`validate-config ${configPath}`);
546
- expect(result.exitCode).toBe(1);
547
- expect(result.output).toContain('parse');
548
- });
549
-
550
- test('detects invalid theme', () => {
551
- const config = { theme: 'invalid-theme' };
552
- const configPath = join(tempDir, 'bad-theme.json');
553
- writeFileSync(configPath, JSON.stringify(config, null, 2));
554
-
555
- const result = runCli(`validate-config ${configPath}`);
556
- expect(result.exitCode).toBe(1);
557
- expect(result.output).toContain('theme');
558
- });
559
-
560
- test('detects multiple validation errors', () => {
561
- const config = {
562
- theme: 'invalid',
563
- logLevelFilter: 'verbose',
564
- refreshInterval: 100,
565
- };
566
- const configPath = join(tempDir, 'multiple-errors.json');
567
- writeFileSync(configPath, JSON.stringify(config, null, 2));
568
-
569
- const result = runCli(`validate-config ${configPath}`);
570
- expect(result.exitCode).toBe(1);
571
- expect(result.errors?.length > 1 || result.output.split('✗').length > 2).toBeTruthy();
572
- });
573
- });
574
-
575
- describe('json output', () => {
576
- test('outputs valid result as JSON with --json', () => {
577
- const config = { refreshInterval: 2000, theme: 'auto' };
578
- const configPath = join(tempDir, 'config.json');
579
- writeFileSync(configPath, JSON.stringify(config, null, 2));
580
-
581
- const result = runCli(`validate-config ${configPath} --json`);
582
- expect(result.exitCode).toBe(0);
583
- const json = JSON.parse(result.output);
584
- expect(json.valid).toBe(true);
585
- expect(json.path).toBeDefined();
586
- expect(json.stats).toBeDefined();
587
- });
588
-
589
- test('outputs invalid result as JSON with -j', () => {
590
- const config = { theme: 'invalid' };
591
- const configPath = join(tempDir, 'config.json');
592
- writeFileSync(configPath, JSON.stringify(config, null, 2));
593
-
594
- const result = runCli(`validate-config ${configPath} -j`);
595
- expect(result.exitCode).toBe(1);
596
- const json = JSON.parse(result.output);
597
- expect(json.valid).toBe(false);
598
- expect(json.errors).toBeDefined();
599
- expect(json.errors.length).toBeGreaterThan(0);
600
- });
601
- });
602
-
603
- describe('strict mode', () => {
604
- test('accepts unknown properties without --strict', () => {
605
- const config = { customField: 'value', refreshInterval: 2000 };
606
- const configPath = join(tempDir, 'config.json');
607
- writeFileSync(configPath, JSON.stringify(config, null, 2));
608
-
609
- const result = runCli(`validate-config ${configPath}`);
610
- expect(result.exitCode).toBe(0);
611
- });
612
-
613
- test('rejects unknown properties with --strict', () => {
614
- const config = { customField: 'value', refreshInterval: 2000 };
615
- const configPath = join(tempDir, 'config.json');
616
- writeFileSync(configPath, JSON.stringify(config, null, 2));
617
-
618
- const result = runCli(`validate-config ${configPath} --strict`);
619
- expect(result.exitCode).toBe(1);
620
- expect(result.output).toContain('Unknown');
621
- });
622
-
623
- test('rejects unknown properties with -s', () => {
624
- const config = { customField: 'value' };
625
- const configPath = join(tempDir, 'config.json');
626
- writeFileSync(configPath, JSON.stringify(config, null, 2));
627
-
628
- const result = runCli(`validate-config ${configPath} -s`);
629
- expect(result.exitCode).toBe(1);
630
- });
631
- });
632
-
633
- describe('default config path', () => {
634
- test('uses default path when no path provided', () => {
635
- // When no path is provided, uses the default config path
636
- // Result depends on whether default config exists in environment
637
- const result = runCli('validate-config');
638
- // Should either succeed (if file exists) or fail with file not found
639
- expect(result.exitCode).toBeGreaterThanOrEqual(0);
640
- expect(result.output).toContain('Configuration');
641
- });
642
-
643
- test('shows file not found for missing default config', () => {
644
- // Test with a path that definitely doesn't exist
645
- const result = runCli('validate-config /nonexistent/path/dashboard-settings.json');
646
- expect(result.exitCode).toBe(1);
647
- expect(result.output).toContain('not found');
648
- });
649
- });
650
-
651
- describe('complex configurations', () => {
652
- test('validates config with gateway endpoints', () => {
653
- const config = {
654
- refreshInterval: 2000,
655
- gatewayEndpoints: [
656
- { name: 'local', host: 'localhost', port: 18789, enabled: true, type: 'local' },
657
- { name: 'remote', host: '192.168.1.100', port: 18789, token: 'secret', type: 'remote' }
658
- ]
659
- };
660
- const configPath = join(tempDir, 'config.json');
661
- writeFileSync(configPath, JSON.stringify(config, null, 2));
662
-
663
- const result = runCli(`validate-config ${configPath}`);
664
- expect(result.exitCode).toBe(0);
665
- });
666
-
667
- test('validates config with web interface', () => {
668
- const config = {
669
- webInterface: {
670
- enabled: true,
671
- port: 18790,
672
- host: '0.0.0.0',
673
- cors: true,
674
- corsOrigins: ['https://example.com'],
675
- rateLimit: {
676
- enabled: true,
677
- windowMs: 60000,
678
- maxRequests: 100,
679
- },
680
- auth: {
681
- enabled: false,
682
- keys: []
683
- }
684
- }
685
- };
686
- const configPath = join(tempDir, 'config.json');
687
- writeFileSync(configPath, JSON.stringify(config, null, 2));
688
-
689
- const result = runCli(`validate-config ${configPath}`);
690
- expect(result.exitCode).toBe(0);
691
- });
692
-
693
- test('validates config with widget loading settings', () => {
694
- const config = {
695
- widgetLoading: {
696
- enabled: true,
697
- preloadPriority: ['cpu', 'memory', 'gpu'],
698
- lazyLoadDelay: 1000,
699
- maxConcurrent: 5,
700
- autoDiscover: true,
701
- }
702
- };
703
- const configPath = join(tempDir, 'config.json');
704
- writeFileSync(configPath, JSON.stringify(config, null, 2));
705
-
706
- const result = runCli(`validate-config ${configPath}`);
707
- expect(result.exitCode).toBe(0);
708
- });
709
- });
710
- });