claw-dashboard 2.1.0 → 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.
- package/CHANGELOG.md +29 -1
- package/docs/API.md +1 -2
- package/index.js +32 -42
- package/package.json +23 -12
- package/src/auto-save.js +11 -5
- package/src/cli/export-snapshot.js +3 -1
- package/src/cli/import-snapshot.js +3 -1
- package/src/config.js +9 -15
- package/src/errors.js +0 -9
- package/src/security.js +6 -2
- package/src/snapshot.js +73 -26
- package/src/web-server.js +7 -10
- package/src/widgets/builtin-widgets.js +0 -94
- package/src/widgets/index.js +0 -1
- package/.c8rc.json +0 -38
- package/.dockerignore +0 -68
- package/.github/dependabot.yml +0 -45
- package/.github/pull_request_template.md +0 -39
- package/.github/workflows/ci.yml +0 -147
- package/.github/workflows/release.yml +0 -40
- package/.github/workflows/security.yml +0 -84
- package/.husky/pre-commit +0 -1
- package/.planning/codebase/ARCHITECTURE.md +0 -206
- package/.planning/codebase/INTEGRATIONS.md +0 -150
- package/.planning/codebase/STACK.md +0 -122
- package/.planning/codebase/STRUCTURE.md +0 -201
- package/CONTRIBUTING.md +0 -378
- package/Dockerfile +0 -54
- package/FEATURES.md +0 -307
- package/TODO.md +0 -28
- package/ai.openclaw.dashboard.plist +0 -35
- package/build-cjs.js +0 -127
- package/cjs-shim.js +0 -13
- package/dist/clawdash +0 -1729
- package/dist/clawdash.meta.json +0 -2236
- package/dist/widgets.cjs +0 -6511
- package/docker-compose.yml +0 -67
- package/esbuild.config.js +0 -158
- package/eslint.config.js +0 -56
- package/examples/plugins/README.md +0 -122
- package/examples/plugins/api-status/index.js +0 -294
- package/examples/plugins/api-status/plugin.json +0 -19
- package/examples/plugins/hello-world/index.js +0 -104
- package/examples/plugins/hello-world/plugin.json +0 -15
- package/examples/plugins/system-metrics-chart/index.js +0 -339
- package/examples/plugins/system-metrics-chart/plugin.json +0 -18
- package/examples/plugins/weather-widget/index.js +0 -136
- package/examples/plugins/weather-widget/plugin.json +0 -19
- package/index.cjs +0 -23005
- package/jest.config.js +0 -11
- package/scripts/release.js +0 -595
- package/src/database.js +0 -734
- package/tests/alerts.test.js +0 -437
- package/tests/auto-save.test.js +0 -529
- package/tests/cache.test.js +0 -317
- package/tests/cli.test.js +0 -351
- package/tests/command-palette.test.js +0 -320
- package/tests/config-processor.test.js +0 -452
- package/tests/config-validator.test.js +0 -710
- package/tests/config-watcher.test.js +0 -594
- package/tests/config.test.js +0 -755
- package/tests/database.test.js +0 -438
- package/tests/errors.test.js +0 -189
- package/tests/example-plugins.test.js +0 -624
- package/tests/integration.test.js +0 -1321
- package/tests/loading-states.test.js +0 -300
- package/tests/manifest-validation-on-load.test.js +0 -671
- package/tests/performance-monitor.test.js +0 -190
- package/tests/plugin-api-rate-limit.test.js +0 -302
- package/tests/plugin-errors.test.js +0 -311
- package/tests/plugin-lifecycle-e2e.test.js +0 -1036
- package/tests/plugin-reload.test.js +0 -764
- package/tests/rate-limiter.test.js +0 -539
- package/tests/retry.test.js +0 -308
- package/tests/security.test.js +0 -411
- package/tests/settings-modal.test.js +0 -226
- package/tests/theme-selector.test.js +0 -57
- package/tests/utils.js +0 -242
- package/tests/utils.test.js +0 -317
- package/tests/validate-plugin-cli.test.js +0 -359
- package/tests/validation.test.js +0 -837
- package/tests/web-server.test.js +0 -646
- package/tests/widget-arrange-mode.test.js +0 -183
- package/tests/widget-config-hot-reload.test.js +0 -465
- package/tests/widget-dependency.test.js +0 -591
- package/tests/widget-error-boundary.test.js +0 -749
- package/tests/widget-error-isolation.test.js +0 -469
- package/tests/widget-integration.test.js +0 -1147
- package/tests/widget-refresh-intervals.test.js +0 -284
- package/tests/worker-pool.test.js +0 -522
|
@@ -1,1321 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration Tests for Claw Dashboard
|
|
3
|
-
* Tests end-to-end workflows covering multiple module interactions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { jest } from '@jest/globals';
|
|
7
|
-
|
|
8
|
-
// Mock systeminformation to avoid system-dependent behavior
|
|
9
|
-
const mockCurrentLoad = jest.fn();
|
|
10
|
-
const mockMem = jest.fn();
|
|
11
|
-
const mockGraphics = jest.fn();
|
|
12
|
-
const mockNetworkStats = jest.fn();
|
|
13
|
-
const mockFsSize = jest.fn();
|
|
14
|
-
const mockOsInfo = jest.fn();
|
|
15
|
-
const mockVersions = jest.fn();
|
|
16
|
-
const mockTime = jest.fn();
|
|
17
|
-
const mockProcesses = jest.fn();
|
|
18
|
-
|
|
19
|
-
jest.unstable_mockModule('systeminformation', () => ({
|
|
20
|
-
currentLoad: mockCurrentLoad,
|
|
21
|
-
mem: mockMem,
|
|
22
|
-
graphics: mockGraphics,
|
|
23
|
-
networkStats: mockNetworkStats,
|
|
24
|
-
fsSize: mockFsSize,
|
|
25
|
-
osInfo: mockOsInfo,
|
|
26
|
-
versions: mockVersions,
|
|
27
|
-
time: mockTime,
|
|
28
|
-
processes: mockProcesses,
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
// Import modules after mocking
|
|
32
|
-
import alerts from '../src/alerts.js';
|
|
33
|
-
import performanceMonitor from '../src/performance-monitor.js';
|
|
34
|
-
import cache from '../src/cache.js';
|
|
35
|
-
import retry from '../src/retry.js';
|
|
36
|
-
import config from '../src/config.js';
|
|
37
|
-
import logger from '../src/logger.js';
|
|
38
|
-
|
|
39
|
-
// Default mock responses
|
|
40
|
-
const defaultCpuData = {
|
|
41
|
-
currentLoad: 25.5,
|
|
42
|
-
currentLoadUser: 20.0,
|
|
43
|
-
currentLoadSystem: 5.5,
|
|
44
|
-
cpus: [{ load: 25.5 }],
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const defaultMemData = {
|
|
48
|
-
total: 8589934592, // 8GB
|
|
49
|
-
active: 4294967296, // 4GB
|
|
50
|
-
available: 4294967296,
|
|
51
|
-
used: 3221225472, // 3GB
|
|
52
|
-
free: 5368709120,
|
|
53
|
-
percent: 37.5,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const defaultOsData = {
|
|
57
|
-
platform: 'darwin',
|
|
58
|
-
distro: 'macOS',
|
|
59
|
-
release: '14.0',
|
|
60
|
-
codename: 'Sonoma',
|
|
61
|
-
hostname: 'test-host',
|
|
62
|
-
arch: 'arm64',
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
describe('Integration: Alert + Performance Monitor Workflow', () => {
|
|
66
|
-
beforeEach(() => {
|
|
67
|
-
// Reset all module states
|
|
68
|
-
alerts.resetThresholds();
|
|
69
|
-
alerts.clearAllAlerts();
|
|
70
|
-
alerts.resetRateLimit();
|
|
71
|
-
performanceMonitor.reset();
|
|
72
|
-
performanceMonitor.stop();
|
|
73
|
-
|
|
74
|
-
// Setup systeminformation mocks
|
|
75
|
-
mockCurrentLoad.mockReset().mockResolvedValue(defaultCpuData);
|
|
76
|
-
mockMem.mockReset().mockResolvedValue(defaultMemData);
|
|
77
|
-
mockGraphics.mockReset().mockResolvedValue({ controllers: [] });
|
|
78
|
-
mockNetworkStats.mockReset().mockResolvedValue([]);
|
|
79
|
-
mockFsSize.mockReset().mockResolvedValue([]);
|
|
80
|
-
mockOsInfo.mockReset().mockResolvedValue(defaultOsData);
|
|
81
|
-
mockVersions.mockReset().mockResolvedValue({ node: '20.0.0', npm: '10.0.0' });
|
|
82
|
-
mockTime.mockReset().mockResolvedValue({ uptime: 3600 });
|
|
83
|
-
mockProcesses.mockReset().mockResolvedValue({ list: [] });
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
afterEach(() => {
|
|
87
|
-
performanceMonitor.stop();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('performance monitoring triggers alerts when thresholds exceeded', async () => {
|
|
91
|
-
// Start performance monitoring
|
|
92
|
-
performanceMonitor.start();
|
|
93
|
-
|
|
94
|
-
// Simulate recording high CPU usage over multiple intervals
|
|
95
|
-
// We can't directly inject values, but we can verify the integration points
|
|
96
|
-
|
|
97
|
-
// Verify performance monitor is tracking
|
|
98
|
-
expect(performanceMonitor.isTracking).toBe(true);
|
|
99
|
-
|
|
100
|
-
// Record some metrics (will use actual system values)
|
|
101
|
-
const snapshot1 = await performanceMonitor.record(2000);
|
|
102
|
-
expect(snapshot1).toBeDefined();
|
|
103
|
-
expect(snapshot1.cpuPercent).toBeDefined();
|
|
104
|
-
expect(snapshot1.memoryPercent).toBeDefined();
|
|
105
|
-
|
|
106
|
-
// Check if alerts would be triggered based on recorded values
|
|
107
|
-
const alertMetrics = {
|
|
108
|
-
cpu: snapshot1.cpuPercent,
|
|
109
|
-
memory: snapshot1.memoryPercent,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const newAlerts = alerts.checkAllMetrics(alertMetrics);
|
|
113
|
-
|
|
114
|
-
// Alerts are created only if thresholds are exceeded
|
|
115
|
-
// This verifies the integration between performance monitoring and alerts
|
|
116
|
-
if (snapshot1.cpuPercent >= 70 || snapshot1.memoryPercent >= 75) {
|
|
117
|
-
expect(newAlerts.length).toBeGreaterThan(0);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('performance health check integrates with alert system', () => {
|
|
122
|
-
performanceMonitor.start();
|
|
123
|
-
|
|
124
|
-
// Record multiple snapshots to build history
|
|
125
|
-
for (let i = 0; i < 5; i++) {
|
|
126
|
-
performanceMonitor.record(2000);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Get health status
|
|
130
|
-
const health = performanceMonitor.checkHealth();
|
|
131
|
-
|
|
132
|
-
// Verify health check returns expected structure
|
|
133
|
-
expect(health).toHaveProperty('degraded');
|
|
134
|
-
expect(health).toHaveProperty('reasons');
|
|
135
|
-
expect(Array.isArray(health.reasons)).toBe(true);
|
|
136
|
-
|
|
137
|
-
// If degraded, verify alerts would be triggered
|
|
138
|
-
if (health.degraded) {
|
|
139
|
-
health.reasons.forEach(reason => {
|
|
140
|
-
// Each degradation reason should correspond to an alert type
|
|
141
|
-
expect(reason).toMatch(/(memory|CPU|event loop)/i);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('metrics history flows correctly through multiple records', async () => {
|
|
147
|
-
performanceMonitor.start();
|
|
148
|
-
performanceMonitor.maxHistory = 10;
|
|
149
|
-
|
|
150
|
-
// Record multiple snapshots
|
|
151
|
-
const snapshots = [];
|
|
152
|
-
for (let i = 0; i < 15; i++) {
|
|
153
|
-
const snapshot = await performanceMonitor.record(2000);
|
|
154
|
-
snapshots.push(snapshot);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Verify history is capped at maxHistory
|
|
158
|
-
expect(performanceMonitor.history.length).toBeLessThanOrEqual(10);
|
|
159
|
-
|
|
160
|
-
// Verify aggregates are calculated
|
|
161
|
-
const metrics = performanceMonitor.getMetrics();
|
|
162
|
-
expect(metrics.aggregates.avgMemoryUsed).toBeGreaterThan(0);
|
|
163
|
-
expect(metrics.aggregates.peakMemoryUsed).toBeGreaterThanOrEqual(metrics.aggregates.avgMemoryUsed);
|
|
164
|
-
|
|
165
|
-
// Verify sparkline data is available
|
|
166
|
-
const memorySparkline = performanceMonitor.getMemorySparkline();
|
|
167
|
-
const cpuSparkline = performanceMonitor.getCpuSparkline();
|
|
168
|
-
expect(Array.isArray(memorySparkline)).toBe(true);
|
|
169
|
-
expect(Array.isArray(cpuSparkline)).toBe(true);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('Integration: Cache + Retry Workflow', () => {
|
|
174
|
-
beforeEach(() => {
|
|
175
|
-
cache.clear();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('cached data avoids retry overhead', async () => {
|
|
179
|
-
const cacheKey = 'test-integration-key';
|
|
180
|
-
const cacheValue = { data: 'test-value', timestamp: Date.now() };
|
|
181
|
-
|
|
182
|
-
// Set cache with long TTL
|
|
183
|
-
cache.set(cacheKey, cacheValue, 60000);
|
|
184
|
-
|
|
185
|
-
// Verify cache hit
|
|
186
|
-
const cached = cache.get(cacheKey);
|
|
187
|
-
expect(cached).toEqual(cacheValue);
|
|
188
|
-
|
|
189
|
-
// Cache hit should be immediate (no retry needed)
|
|
190
|
-
const startTime = Date.now();
|
|
191
|
-
cache.get(cacheKey);
|
|
192
|
-
const elapsed = Date.now() - startTime;
|
|
193
|
-
expect(elapsed).toBeLessThan(10); // Should be nearly instant
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test('cache miss triggers fresh fetch with retry support', async () => {
|
|
197
|
-
const cacheKey = 'test-miss-key';
|
|
198
|
-
|
|
199
|
-
// Ensure cache miss by clearing
|
|
200
|
-
cache.clear();
|
|
201
|
-
const cached = cache.get(cacheKey);
|
|
202
|
-
expect(cached).toBeNull();
|
|
203
|
-
|
|
204
|
-
// Verify retry module is available for fallback
|
|
205
|
-
expect(retry.withRetry).toBeDefined();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('cache TTL expiration works correctly', async () => {
|
|
209
|
-
const cacheKey = 'test-ttl-key';
|
|
210
|
-
const shortTTL = 50; // 50ms for fast test
|
|
211
|
-
|
|
212
|
-
cache.set(cacheKey, { value: 'fresh' }, shortTTL);
|
|
213
|
-
|
|
214
|
-
// Should be cached initially
|
|
215
|
-
expect(cache.get(cacheKey)).toBeDefined();
|
|
216
|
-
|
|
217
|
-
// Wait for expiration
|
|
218
|
-
await new Promise(resolve => setTimeout(resolve, shortTTL + 10));
|
|
219
|
-
|
|
220
|
-
// Should be expired now
|
|
221
|
-
expect(cache.get(cacheKey)).toBeNull();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('cache debounce prevents duplicate fetches', async () => {
|
|
225
|
-
let fetchCount = 0;
|
|
226
|
-
let lastResult = null;
|
|
227
|
-
|
|
228
|
-
const fetchFn = async () => {
|
|
229
|
-
fetchCount++;
|
|
230
|
-
lastResult = { data: 'fetched', count: fetchCount };
|
|
231
|
-
return lastResult;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const debouncedFetch = cache.debounce(fetchFn, 50);
|
|
235
|
-
|
|
236
|
-
// Call multiple times rapidly
|
|
237
|
-
debouncedFetch();
|
|
238
|
-
debouncedFetch();
|
|
239
|
-
debouncedFetch();
|
|
240
|
-
|
|
241
|
-
// Wait for debounce to complete
|
|
242
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
243
|
-
|
|
244
|
-
// Should only fetch once due to debouncing
|
|
245
|
-
expect(fetchCount).toBe(1);
|
|
246
|
-
expect(lastResult).toBeDefined();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test('cache throttle limits fetch frequency', async () => {
|
|
250
|
-
let fetchCount = 0;
|
|
251
|
-
const results = [];
|
|
252
|
-
|
|
253
|
-
const fetchFn = async () => {
|
|
254
|
-
fetchCount++;
|
|
255
|
-
const result = { data: 'fetched', count: fetchCount };
|
|
256
|
-
results.push(result);
|
|
257
|
-
return result;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const throttledFetch = cache.throttle(fetchFn, 100);
|
|
261
|
-
|
|
262
|
-
// Call multiple times
|
|
263
|
-
await throttledFetch();
|
|
264
|
-
await throttledFetch();
|
|
265
|
-
await throttledFetch();
|
|
266
|
-
|
|
267
|
-
// Should only execute once within throttle window
|
|
268
|
-
expect(fetchCount).toBe(1);
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
describe('Integration: Retry + Logger Workflow', () => {
|
|
273
|
-
test('retry attempts are logged appropriately', async () => {
|
|
274
|
-
let attemptCount = 0;
|
|
275
|
-
const maxRetries = 2;
|
|
276
|
-
|
|
277
|
-
const failingFn = async () => {
|
|
278
|
-
attemptCount++;
|
|
279
|
-
if (attemptCount <= maxRetries) {
|
|
280
|
-
throw new Error('Network timeout'); // Matches retryable pattern
|
|
281
|
-
}
|
|
282
|
-
return 'success';
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const retryOptions = {
|
|
286
|
-
maxRetries,
|
|
287
|
-
initialDelay: 10,
|
|
288
|
-
maxDelay: 50,
|
|
289
|
-
backoffMultiplier: 1,
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
// withRetry returns a wrapped function
|
|
293
|
-
const wrappedFn = retry.withRetry(failingFn, retryOptions);
|
|
294
|
-
const result = await wrappedFn();
|
|
295
|
-
|
|
296
|
-
// Should succeed after retries
|
|
297
|
-
expect(result).toBe('success');
|
|
298
|
-
// Initial attempt + retries = maxRetries + 1
|
|
299
|
-
expect(attemptCount).toBeGreaterThanOrEqual(1);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test('retry gives up after max attempts and logs error', async () => {
|
|
303
|
-
const maxRetries = 2;
|
|
304
|
-
let attemptCount = 0;
|
|
305
|
-
|
|
306
|
-
const alwaysFailingFn = async () => {
|
|
307
|
-
attemptCount++;
|
|
308
|
-
throw new Error('Network error'); // Matches retryable pattern
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const retryOptions = {
|
|
312
|
-
maxRetries,
|
|
313
|
-
initialDelay: 10,
|
|
314
|
-
maxDelay: 50,
|
|
315
|
-
backoffMultiplier: 1,
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
const wrappedFn = retry.withRetry(alwaysFailingFn, retryOptions);
|
|
319
|
-
|
|
320
|
-
await expect(wrappedFn()).rejects.toThrow('Network error');
|
|
321
|
-
|
|
322
|
-
// Should attempt maxRetries + 1 times
|
|
323
|
-
expect(attemptCount).toBe(maxRetries + 1);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('retry respects retryable errors', async () => {
|
|
327
|
-
let attemptCount = 0;
|
|
328
|
-
|
|
329
|
-
const networkErrorFn = async () => {
|
|
330
|
-
attemptCount++;
|
|
331
|
-
const error = new Error('Connection refused');
|
|
332
|
-
error.code = 'ECONNREFUSED';
|
|
333
|
-
throw error;
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const retryOptions = {
|
|
337
|
-
maxRetries: 2,
|
|
338
|
-
initialDelay: 10,
|
|
339
|
-
maxDelay: 50,
|
|
340
|
-
backoffMultiplier: 1,
|
|
341
|
-
retryableErrors: ['ECONNREFUSED', 'ETIMEDOUT'],
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const wrappedFn = retry.withRetry(networkErrorFn, retryOptions);
|
|
345
|
-
|
|
346
|
-
await expect(wrappedFn()).rejects.toThrow('Connection refused');
|
|
347
|
-
|
|
348
|
-
// Should retry because ECONNREFUSED is retryable (initial + 2 retries = 3 attempts)
|
|
349
|
-
expect(attemptCount).toBe(3);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
test('retry does not retry non-retryable errors', async () => {
|
|
353
|
-
let attemptCount = 0;
|
|
354
|
-
|
|
355
|
-
const validationErrorFn = async () => {
|
|
356
|
-
attemptCount++;
|
|
357
|
-
throw new Error('Invalid input');
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const retryOptions = {
|
|
361
|
-
maxRetries: 3,
|
|
362
|
-
initialDelay: 10,
|
|
363
|
-
retryableErrors: ['ECONNREFUSED', 'ETIMEDOUT'],
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const wrappedFn = retry.withRetry(validationErrorFn, retryOptions);
|
|
367
|
-
|
|
368
|
-
await expect(wrappedFn()).rejects.toThrow('Invalid input');
|
|
369
|
-
|
|
370
|
-
// Should not retry - only one attempt
|
|
371
|
-
expect(attemptCount).toBe(1);
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
describe('Integration: Config + Validation + Settings Workflow', () => {
|
|
376
|
-
test('default settings match config defaults', () => {
|
|
377
|
-
const defaultSettings = config.DEFAULT_SETTINGS;
|
|
378
|
-
|
|
379
|
-
// Verify key defaults match config
|
|
380
|
-
expect(defaultSettings.refreshInterval).toBe(config.REFRESH_INTERVALS.DEFAULT);
|
|
381
|
-
expect(defaultSettings.theme).toBe('auto');
|
|
382
|
-
expect(defaultSettings.exportFormat).toBe('json');
|
|
383
|
-
|
|
384
|
-
// Verify gateway endpoint defaults
|
|
385
|
-
expect(defaultSettings.gatewayEndpoints).toBeDefined();
|
|
386
|
-
expect(defaultSettings.gatewayEndpoints.length).toBeGreaterThan(0);
|
|
387
|
-
expect(defaultSettings.gatewayEndpoints[0].port).toBe(config.GATEWAY.DEFAULT_PORT);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
test('alert thresholds match config', () => {
|
|
391
|
-
const currentThresholds = alerts.getThresholds();
|
|
392
|
-
|
|
393
|
-
expect(currentThresholds.cpu.warning).toBe(config.ALERT_THRESHOLDS.CPU.warning);
|
|
394
|
-
expect(currentThresholds.cpu.critical).toBe(config.ALERT_THRESHOLDS.CPU.critical);
|
|
395
|
-
expect(currentThresholds.memory.warning).toBe(config.ALERT_THRESHOLDS.MEMORY.warning);
|
|
396
|
-
expect(currentThresholds.disk.warning).toBe(config.ALERT_THRESHOLDS.DISK.warning);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test('rate limit config matches defaults', () => {
|
|
400
|
-
const rateLimit = alerts.getRateLimit();
|
|
401
|
-
|
|
402
|
-
expect(rateLimit.enabled).toBe(config.ALERT_RATE_LIMIT.ENABLED);
|
|
403
|
-
expect(rateLimit.windowMs).toBe(config.ALERT_RATE_LIMIT.WINDOW_MS);
|
|
404
|
-
expect(rateLimit.maxAlerts).toBe(config.ALERT_RATE_LIMIT.MAX_ALERTS);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test('cache TTL config is consistent', () => {
|
|
408
|
-
// Verify cache TTL constants are reasonable
|
|
409
|
-
expect(config.CACHE_TTL.CPU).toBeLessThan(config.CACHE_TTL.GPU); // GPU is expensive
|
|
410
|
-
expect(config.CACHE_TTL.DISK).toBeGreaterThan(config.CACHE_TTL.CPU); // Disk rarely changes
|
|
411
|
-
expect(config.CACHE_TTL.CONTAINER).toBeGreaterThan(config.CACHE_TTL.NETWORK);
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
describe('Integration: Full Dashboard Refresh Cycle Simulation', () => {
|
|
416
|
-
beforeEach(() => {
|
|
417
|
-
alerts.resetThresholds();
|
|
418
|
-
alerts.clearAllAlerts();
|
|
419
|
-
alerts.resetRateLimit();
|
|
420
|
-
performanceMonitor.reset();
|
|
421
|
-
performanceMonitor.stop();
|
|
422
|
-
cache.clear();
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
afterEach(() => {
|
|
426
|
-
performanceMonitor.stop();
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test('simulates complete dashboard refresh cycle', async () => {
|
|
430
|
-
// Phase 1: Start monitoring
|
|
431
|
-
performanceMonitor.start();
|
|
432
|
-
|
|
433
|
-
// Phase 2: Simulate data fetch with caching
|
|
434
|
-
const systemData = {
|
|
435
|
-
cpu: 45,
|
|
436
|
-
memory: 60,
|
|
437
|
-
disk: 50,
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
// Cache the data
|
|
441
|
-
cache.set('system-metrics', systemData, config.CACHE_TTL.CPU);
|
|
442
|
-
|
|
443
|
-
// Phase 3: Check alerts
|
|
444
|
-
const newAlerts = alerts.checkAllMetrics(systemData);
|
|
445
|
-
|
|
446
|
-
// No alerts should be created for normal values
|
|
447
|
-
expect(newAlerts.length).toBe(0);
|
|
448
|
-
|
|
449
|
-
// Phase 4: Record performance metrics
|
|
450
|
-
const perfSnapshot = await performanceMonitor.record(2000);
|
|
451
|
-
expect(perfSnapshot).toBeDefined();
|
|
452
|
-
|
|
453
|
-
// Phase 5: Verify health
|
|
454
|
-
const health = performanceMonitor.checkHealth();
|
|
455
|
-
expect(health.degraded).toBe(false);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
test('handles alert escalation workflow', () => {
|
|
459
|
-
performanceMonitor.start();
|
|
460
|
-
|
|
461
|
-
// Normal state
|
|
462
|
-
let newAlerts = alerts.checkAllMetrics({ cpu: 50, memory: 50, disk: 50 });
|
|
463
|
-
expect(newAlerts.length).toBe(0);
|
|
464
|
-
|
|
465
|
-
// Warning state
|
|
466
|
-
newAlerts = alerts.checkAllMetrics({ cpu: 75, memory: 50, disk: 50 });
|
|
467
|
-
expect(newAlerts.length).toBe(1);
|
|
468
|
-
expect(newAlerts[0].type).toBe('cpu');
|
|
469
|
-
expect(newAlerts[0].level).toBe(alerts.AlertLevel.WARNING);
|
|
470
|
-
|
|
471
|
-
// Critical escalation
|
|
472
|
-
newAlerts = alerts.checkAllMetrics({ cpu: 95, memory: 50, disk: 50 });
|
|
473
|
-
// Should update existing alert, not create new one
|
|
474
|
-
const activeAlerts = alerts.getActiveAlerts();
|
|
475
|
-
const cpuAlert = activeAlerts.find(a => a.type === 'cpu');
|
|
476
|
-
expect(cpuAlert.level).toBe(alerts.AlertLevel.CRITICAL);
|
|
477
|
-
|
|
478
|
-
// Recovery
|
|
479
|
-
newAlerts = alerts.checkAllMetrics({ cpu: 50, memory: 50, disk: 50 });
|
|
480
|
-
expect(newAlerts.length).toBe(1);
|
|
481
|
-
expect(newAlerts[0].level).toBe(alerts.AlertLevel.CLEARED);
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
test('handles multiple simultaneous alerts', () => {
|
|
485
|
-
// Trigger multiple alerts at once
|
|
486
|
-
const newAlerts = alerts.checkAllMetrics({
|
|
487
|
-
cpu: 95, // Critical
|
|
488
|
-
memory: 92, // Critical
|
|
489
|
-
disk: 96, // Critical
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
expect(newAlerts.length).toBe(3);
|
|
493
|
-
expect(newAlerts.map(a => a.type)).toEqual(expect.arrayContaining(['cpu', 'memory', 'disk']));
|
|
494
|
-
|
|
495
|
-
const activeAlerts = alerts.getActiveAlerts();
|
|
496
|
-
expect(activeAlerts.length).toBe(3);
|
|
497
|
-
|
|
498
|
-
// All should be critical
|
|
499
|
-
activeAlerts.forEach(alert => {
|
|
500
|
-
expect(alert.level).toBe(alerts.AlertLevel.CRITICAL);
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test('respects alert rate limiting in workflow', () => {
|
|
505
|
-
// Setup: clear and reset first, then configure rate limiting
|
|
506
|
-
alerts.clearAllAlerts();
|
|
507
|
-
alerts.resetRateLimit();
|
|
508
|
-
alerts.setRateLimit({ enabled: true, maxAlerts: 2, windowMs: 1000 });
|
|
509
|
-
|
|
510
|
-
// First alert of type 'cpu' should be created and recorded
|
|
511
|
-
let alert1 = alerts.checkThreshold('cpu', 75);
|
|
512
|
-
expect(alert1).not.toBeNull();
|
|
513
|
-
|
|
514
|
-
// Dismiss it to allow new alert
|
|
515
|
-
alerts.dismissAlert(alert1.id);
|
|
516
|
-
|
|
517
|
-
// Second alert of same type should also be created
|
|
518
|
-
let alert2 = alerts.checkThreshold('cpu', 76);
|
|
519
|
-
expect(alert2).not.toBeNull();
|
|
520
|
-
|
|
521
|
-
// Dismiss again
|
|
522
|
-
alerts.dismissAlert(alert2.id);
|
|
523
|
-
|
|
524
|
-
// Third alert of same type - should be rate limited (max 2 per window per type)
|
|
525
|
-
const alert3 = alerts.checkThreshold('cpu', 77);
|
|
526
|
-
expect(alert3).toBeNull();
|
|
527
|
-
|
|
528
|
-
// Different alert type (memory) should still work (rate limit is per-type)
|
|
529
|
-
const memAlert = alerts.checkThreshold('memory', 80);
|
|
530
|
-
expect(memAlert).not.toBeNull();
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
describe('Integration: Error Handling Across Modules', () => {
|
|
535
|
-
test('handles graceful degradation when modules fail', async () => {
|
|
536
|
-
// Simulate performance monitor being inactive
|
|
537
|
-
performanceMonitor.stop();
|
|
538
|
-
|
|
539
|
-
// Should return null gracefully
|
|
540
|
-
const snapshot = await performanceMonitor.record();
|
|
541
|
-
expect(snapshot).toBeNull();
|
|
542
|
-
|
|
543
|
-
// Status should indicate inactive
|
|
544
|
-
const status = performanceMonitor.getStatusString();
|
|
545
|
-
expect(status).toContain('inactive');
|
|
546
|
-
|
|
547
|
-
// Alerts should still work independently
|
|
548
|
-
const alert = alerts.checkThreshold('cpu', 95);
|
|
549
|
-
expect(alert).not.toBeNull();
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
test('cache fallback works when fetch fails', async () => {
|
|
553
|
-
const cacheKey = 'fallback-test';
|
|
554
|
-
const cachedValue = { data: 'stale-but-available' };
|
|
555
|
-
|
|
556
|
-
// Pre-populate cache
|
|
557
|
-
cache.set(cacheKey, cachedValue, 100);
|
|
558
|
-
|
|
559
|
-
// Simulate fetch failure with retry
|
|
560
|
-
const failingFetch = async () => {
|
|
561
|
-
throw new Error('Network error');
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
const retryOptions = {
|
|
565
|
-
maxRetries: 1,
|
|
566
|
-
initialDelay: 10,
|
|
567
|
-
retryableErrors: ['Network error'],
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
// Retry should fail, but cache has fallback data
|
|
571
|
-
await expect(retry.withRetry(failingFetch, 'test', retryOptions))
|
|
572
|
-
.rejects.toThrow('Network error');
|
|
573
|
-
|
|
574
|
-
// Cache should still have data (if not expired)
|
|
575
|
-
// In real workflow, would use cached value as fallback
|
|
576
|
-
const stillCached = cache.get(cacheKey);
|
|
577
|
-
expect(stillCached).toBeDefined();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
test('logger captures errors from all modules', () => {
|
|
581
|
-
// This test verifies logger integration
|
|
582
|
-
// Logger writes to file, so we verify it doesn't throw
|
|
583
|
-
|
|
584
|
-
expect(() => {
|
|
585
|
-
logger.error('Integration test error');
|
|
586
|
-
logger.warn('Integration test warning');
|
|
587
|
-
logger.info('Integration test info');
|
|
588
|
-
logger.debug('Integration test debug');
|
|
589
|
-
}).not.toThrow();
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
describe('Integration: Gateway Configuration Workflow', () => {
|
|
594
|
-
test('gateway config flows through settings', () => {
|
|
595
|
-
const defaultSettings = config.DEFAULT_SETTINGS;
|
|
596
|
-
|
|
597
|
-
// Verify gateway endpoint structure
|
|
598
|
-
const endpoint = defaultSettings.gatewayEndpoints[0];
|
|
599
|
-
expect(endpoint).toHaveProperty('name');
|
|
600
|
-
expect(endpoint).toHaveProperty('host');
|
|
601
|
-
expect(endpoint).toHaveProperty('port');
|
|
602
|
-
expect(endpoint).toHaveProperty('token');
|
|
603
|
-
expect(endpoint).toHaveProperty('enabled');
|
|
604
|
-
expect(endpoint).toHaveProperty('type');
|
|
605
|
-
|
|
606
|
-
// Verify port matches config
|
|
607
|
-
expect(endpoint.port).toBe(config.GATEWAY.DEFAULT_PORT);
|
|
608
|
-
|
|
609
|
-
// Verify timeout config exists
|
|
610
|
-
expect(config.GATEWAY.TIMEOUT_MS).toBeGreaterThan(0);
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
test('validation constraints are consistent with config', () => {
|
|
614
|
-
const validation = config.VALIDATION;
|
|
615
|
-
|
|
616
|
-
// Refresh interval constraints
|
|
617
|
-
expect(validation.REFRESH_INTERVAL.MIN).toBeLessThanOrEqual(config.REFRESH_INTERVALS.DEFAULT);
|
|
618
|
-
expect(validation.REFRESH_INTERVAL.MAX).toBeGreaterThan(config.REFRESH_INTERVALS.DEFAULT);
|
|
619
|
-
|
|
620
|
-
// Theme validation includes default
|
|
621
|
-
expect(validation.VALID_THEMES).toContain('auto');
|
|
622
|
-
expect(validation.VALID_THEMES).toContain('default');
|
|
623
|
-
|
|
624
|
-
// Sort modes are defined
|
|
625
|
-
expect(validation.VALID_SORT_MODES.length).toBeGreaterThan(0);
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
describe('Integration: Gateway Manager + Cache + Retry Workflow', () => {
|
|
630
|
-
let gatewayManager;
|
|
631
|
-
|
|
632
|
-
beforeEach(async () => {
|
|
633
|
-
// Import gateway manager as a singleton instance
|
|
634
|
-
const gwModule = await import('../src/gateway-manager.js');
|
|
635
|
-
gatewayManager = gwModule.gatewayManager || gwModule.default;
|
|
636
|
-
cache.clear();
|
|
637
|
-
// Reinitialize with default settings for each test
|
|
638
|
-
gatewayManager.init({ gatewayEndpoints: [] });
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
test('gateway manager initializes with default endpoints', () => {
|
|
642
|
-
const settings = { gatewayEndpoints: undefined };
|
|
643
|
-
gatewayManager.init(settings);
|
|
644
|
-
|
|
645
|
-
const enabled = gatewayManager.getEnabledEndpoints();
|
|
646
|
-
expect(enabled.length).toBeGreaterThan(0);
|
|
647
|
-
expect(enabled[0]).toHaveProperty('host');
|
|
648
|
-
expect(enabled[0]).toHaveProperty('port');
|
|
649
|
-
expect(enabled[0]).toHaveProperty('enabled', true);
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
test('gateway manager respects custom endpoint configuration', () => {
|
|
653
|
-
const customEndpoints = [
|
|
654
|
-
{ name: 'local', host: 'localhost', port: 3000, enabled: true },
|
|
655
|
-
{ name: 'remote', host: '192.168.1.100', port: 3001, enabled: false },
|
|
656
|
-
];
|
|
657
|
-
|
|
658
|
-
gatewayManager.init({ gatewayEndpoints: customEndpoints });
|
|
659
|
-
|
|
660
|
-
const enabled = gatewayManager.getEnabledEndpoints();
|
|
661
|
-
const all = gatewayManager.getAllEndpoints();
|
|
662
|
-
|
|
663
|
-
expect(all.length).toBe(2);
|
|
664
|
-
expect(enabled.length).toBe(1);
|
|
665
|
-
expect(enabled[0].name).toBe('local');
|
|
666
|
-
expect(enabled[0].port).toBe(3000);
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
test('gateway manager enforces endpoint limits', () => {
|
|
670
|
-
gatewayManager.init({ gatewayEndpoints: [] });
|
|
671
|
-
|
|
672
|
-
// Add endpoints up to limit
|
|
673
|
-
const maxEndpoints = config.GATEWAY.MAX_ENDPOINTS;
|
|
674
|
-
for (let i = 0; i < maxEndpoints; i++) {
|
|
675
|
-
const result = gatewayManager.addEndpoint({
|
|
676
|
-
name: `endpoint-${i}`,
|
|
677
|
-
host: 'localhost',
|
|
678
|
-
port: 3000 + i,
|
|
679
|
-
});
|
|
680
|
-
expect(result).not.toBeNull();
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Try to add one more - should fail
|
|
684
|
-
const overflowResult = gatewayManager.addEndpoint({
|
|
685
|
-
name: 'overflow',
|
|
686
|
-
host: 'localhost',
|
|
687
|
-
port: 9999,
|
|
688
|
-
});
|
|
689
|
-
expect(overflowResult).toBeNull();
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
test('gateway manager handles duplicate endpoint names', () => {
|
|
693
|
-
gatewayManager.init({ gatewayEndpoints: [] });
|
|
694
|
-
|
|
695
|
-
const first = gatewayManager.addEndpoint({
|
|
696
|
-
name: 'duplicate',
|
|
697
|
-
host: 'localhost',
|
|
698
|
-
port: 3000,
|
|
699
|
-
});
|
|
700
|
-
expect(first).not.toBeNull();
|
|
701
|
-
|
|
702
|
-
// Try to add duplicate - should fail
|
|
703
|
-
const duplicate = gatewayManager.addEndpoint({
|
|
704
|
-
name: 'duplicate',
|
|
705
|
-
host: 'localhost',
|
|
706
|
-
port: 3001,
|
|
707
|
-
});
|
|
708
|
-
expect(duplicate).toBeNull();
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
test('gateway manager endpoint removal works correctly', () => {
|
|
712
|
-
gatewayManager.init({ gatewayEndpoints: [] });
|
|
713
|
-
|
|
714
|
-
gatewayManager.addEndpoint({ name: 'endpoint-1', host: 'localhost', port: 3000 });
|
|
715
|
-
gatewayManager.addEndpoint({ name: 'endpoint-2', host: 'localhost', port: 3001 });
|
|
716
|
-
|
|
717
|
-
// Remove one endpoint
|
|
718
|
-
const removed = gatewayManager.removeEndpoint('endpoint-1');
|
|
719
|
-
expect(removed).toBe(true);
|
|
720
|
-
|
|
721
|
-
// Verify remaining endpoints
|
|
722
|
-
const all = gatewayManager.getAllEndpoints();
|
|
723
|
-
expect(all.length).toBe(1);
|
|
724
|
-
expect(all[0].name).toBe('endpoint-2');
|
|
725
|
-
|
|
726
|
-
// Cannot remove non-existent endpoint
|
|
727
|
-
const notRemoved = gatewayManager.removeEndpoint('nonexistent');
|
|
728
|
-
expect(notRemoved).toBe(false);
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
test('gateway manager prevents removing last endpoint', () => {
|
|
732
|
-
gatewayManager.init({ gatewayEndpoints: [] });
|
|
733
|
-
gatewayManager.addEndpoint({ name: 'only', host: 'localhost', port: 3000 });
|
|
734
|
-
|
|
735
|
-
// Should not be able to remove the last endpoint
|
|
736
|
-
const removed = gatewayManager.removeEndpoint('only');
|
|
737
|
-
expect(removed).toBe(false);
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
test('cache integration with gateway endpoint data', async () => {
|
|
741
|
-
gatewayManager.init({ gatewayEndpoints: [] });
|
|
742
|
-
gatewayManager.addEndpoint({ name: 'test-endpoint', host: 'localhost', port: 3000 });
|
|
743
|
-
|
|
744
|
-
// Simulate caching endpoint status
|
|
745
|
-
const endpointStatus = { reachable: true, latency: 45, lastSeen: Date.now() };
|
|
746
|
-
cache.set('gateway-status-test-endpoint', endpointStatus, config.CACHE_TTL.DEFAULT);
|
|
747
|
-
|
|
748
|
-
// Verify cached data
|
|
749
|
-
const cached = cache.get('gateway-status-test-endpoint');
|
|
750
|
-
expect(cached).toEqual(endpointStatus);
|
|
751
|
-
|
|
752
|
-
// Verify cache expiration
|
|
753
|
-
cache.set('gateway-expiring', { test: true }, 50);
|
|
754
|
-
await new Promise(resolve => setTimeout(resolve, 60));
|
|
755
|
-
expect(cache.get('gateway-expiring')).toBeNull();
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
describe('Integration: Database + Cache Workflow', () => {
|
|
760
|
-
let database;
|
|
761
|
-
|
|
762
|
-
beforeEach(async () => {
|
|
763
|
-
database = await import('../src/database.js');
|
|
764
|
-
cache.clear();
|
|
765
|
-
});
|
|
766
|
-
|
|
767
|
-
afterEach(async () => {
|
|
768
|
-
// Clean up any intervals
|
|
769
|
-
if (database.saveInterval) clearInterval(database.saveInterval);
|
|
770
|
-
if (database.cleanupInterval) clearInterval(database.cleanupInterval);
|
|
771
|
-
});
|
|
772
|
-
|
|
773
|
-
test('database module exports expected functions', () => {
|
|
774
|
-
expect(database.initDatabase).toBeDefined();
|
|
775
|
-
expect(typeof database.initDatabase).toBe('function');
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
test('database initialization creates required tables', async () => {
|
|
779
|
-
// Note: This test verifies the module structure
|
|
780
|
-
// Full database tests would require sql.js initialization
|
|
781
|
-
const result = await database.initDatabase();
|
|
782
|
-
|
|
783
|
-
// Should return true on successful init
|
|
784
|
-
expect(typeof result).toBe('boolean');
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
test('cache database key follows naming convention', () => {
|
|
788
|
-
// Verify cache key naming for database-related data
|
|
789
|
-
const sessionKey = 'db-session-snapshot';
|
|
790
|
-
const metricsKey = 'db-metrics-history';
|
|
791
|
-
|
|
792
|
-
cache.set(sessionKey, { test: 'session' }, config.CACHE_TTL.DEFAULT);
|
|
793
|
-
cache.set(metricsKey, { test: 'metrics' }, config.CACHE_TTL.DEFAULT);
|
|
794
|
-
|
|
795
|
-
expect(cache.get(sessionKey)).toBeDefined();
|
|
796
|
-
expect(cache.get(metricsKey)).toBeDefined();
|
|
797
|
-
});
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
describe('Integration: Full System Metrics Collection Workflow', () => {
|
|
801
|
-
beforeEach(() => {
|
|
802
|
-
cache.clear();
|
|
803
|
-
alerts.resetThresholds();
|
|
804
|
-
alerts.clearAllAlerts();
|
|
805
|
-
performanceMonitor.reset();
|
|
806
|
-
performanceMonitor.stop();
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
afterEach(() => {
|
|
810
|
-
performanceMonitor.stop();
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
test('complete metrics collection and alert cycle', async () => {
|
|
814
|
-
// Phase 1: Start performance monitoring
|
|
815
|
-
performanceMonitor.start();
|
|
816
|
-
|
|
817
|
-
// Phase 2: Simulate system metrics collection
|
|
818
|
-
const systemMetrics = {
|
|
819
|
-
cpu: 45,
|
|
820
|
-
memory: 65,
|
|
821
|
-
disk: 55,
|
|
822
|
-
};
|
|
823
|
-
|
|
824
|
-
// Phase 3: Cache the metrics
|
|
825
|
-
cache.set('system-metrics-latest', systemMetrics, config.CACHE_TTL.CPU);
|
|
826
|
-
|
|
827
|
-
// Phase 4: Record performance snapshot
|
|
828
|
-
const snapshot = await performanceMonitor.record(100);
|
|
829
|
-
expect(snapshot).toBeDefined();
|
|
830
|
-
expect(snapshot.cpuPercent).toBeDefined();
|
|
831
|
-
expect(snapshot.memoryPercent).toBeDefined();
|
|
832
|
-
|
|
833
|
-
// Phase 5: Check alerts
|
|
834
|
-
const newAlerts = alerts.checkAllMetrics(systemMetrics);
|
|
835
|
-
expect(newAlerts.length).toBe(0); // Normal values, no alerts
|
|
836
|
-
|
|
837
|
-
// Phase 6: Simulate high load scenario
|
|
838
|
-
const highLoadMetrics = {
|
|
839
|
-
cpu: 92,
|
|
840
|
-
memory: 88,
|
|
841
|
-
disk: 55,
|
|
842
|
-
};
|
|
843
|
-
|
|
844
|
-
cache.set('system-metrics-latest', highLoadMetrics, config.CACHE_TTL.CPU);
|
|
845
|
-
const highLoadAlerts = alerts.checkAllMetrics(highLoadMetrics);
|
|
846
|
-
|
|
847
|
-
// Should trigger CPU and memory alerts
|
|
848
|
-
expect(highLoadAlerts.length).toBeGreaterThanOrEqual(2);
|
|
849
|
-
const alertTypes = highLoadAlerts.map(a => a.type);
|
|
850
|
-
expect(alertTypes).toContain('cpu');
|
|
851
|
-
expect(alertTypes).toContain('memory');
|
|
852
|
-
|
|
853
|
-
// Phase 7: Verify health check
|
|
854
|
-
const health = performanceMonitor.checkHealth();
|
|
855
|
-
expect(health).toHaveProperty('degraded');
|
|
856
|
-
expect(health).toHaveProperty('reasons');
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
test('metrics history persists through multiple collection cycles', async () => {
|
|
860
|
-
performanceMonitor.start();
|
|
861
|
-
performanceMonitor.maxHistory = 20;
|
|
862
|
-
|
|
863
|
-
// Simulate multiple collection cycles
|
|
864
|
-
const cycles = 25;
|
|
865
|
-
for (let i = 0; i < cycles; i++) {
|
|
866
|
-
// Simulate varying metrics
|
|
867
|
-
const metrics = {
|
|
868
|
-
cpu: 40 + (i % 30),
|
|
869
|
-
memory: 50 + (i % 25),
|
|
870
|
-
disk: 50,
|
|
871
|
-
};
|
|
872
|
-
|
|
873
|
-
cache.set('system-metrics-cycle', metrics, config.CACHE_TTL.CPU);
|
|
874
|
-
performanceMonitor.record(50);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Verify history is capped
|
|
878
|
-
expect(performanceMonitor.history.length).toBeLessThanOrEqual(20);
|
|
879
|
-
|
|
880
|
-
// Verify aggregates are calculated
|
|
881
|
-
const metrics = performanceMonitor.getMetrics();
|
|
882
|
-
expect(metrics.aggregates).toBeDefined();
|
|
883
|
-
expect(metrics.aggregates.avgMemoryUsed).toBeDefined();
|
|
884
|
-
expect(metrics.aggregates.peakMemoryUsed).toBeDefined();
|
|
885
|
-
|
|
886
|
-
// Verify sparkline data is available
|
|
887
|
-
const memorySparkline = performanceMonitor.getMemorySparkline();
|
|
888
|
-
expect(Array.isArray(memorySparkline)).toBe(true);
|
|
889
|
-
expect(memorySparkline.length).toBeGreaterThan(0);
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
test('cache prevents redundant system calls during rapid refresh', async () => {
|
|
893
|
-
let fetchCount = 0;
|
|
894
|
-
|
|
895
|
-
const fetchSystemMetrics = async () => {
|
|
896
|
-
fetchCount++;
|
|
897
|
-
return {
|
|
898
|
-
cpu: 50 + fetchCount,
|
|
899
|
-
memory: 60 + fetchCount,
|
|
900
|
-
disk: 55,
|
|
901
|
-
};
|
|
902
|
-
};
|
|
903
|
-
|
|
904
|
-
const cacheKey = 'rapid-refresh-test';
|
|
905
|
-
const ttl = 100; // 100ms TTL
|
|
906
|
-
|
|
907
|
-
// First fetch - cache miss
|
|
908
|
-
let metrics = cache.get(cacheKey);
|
|
909
|
-
if (metrics === null) {
|
|
910
|
-
metrics = await fetchSystemMetrics();
|
|
911
|
-
cache.set(cacheKey, metrics, ttl);
|
|
912
|
-
}
|
|
913
|
-
expect(fetchCount).toBe(1);
|
|
914
|
-
|
|
915
|
-
// Second fetch within TTL - cache hit
|
|
916
|
-
metrics = cache.get(cacheKey);
|
|
917
|
-
expect(metrics).toBeDefined();
|
|
918
|
-
expect(fetchCount).toBe(1); // Should not have fetched again
|
|
919
|
-
|
|
920
|
-
// Wait for TTL to expire
|
|
921
|
-
await new Promise(resolve => setTimeout(resolve, ttl + 10));
|
|
922
|
-
|
|
923
|
-
// Third fetch after TTL - cache miss, should fetch again
|
|
924
|
-
metrics = cache.get(cacheKey);
|
|
925
|
-
if (metrics === null) {
|
|
926
|
-
metrics = await fetchSystemMetrics();
|
|
927
|
-
cache.set(cacheKey, metrics, ttl);
|
|
928
|
-
}
|
|
929
|
-
expect(fetchCount).toBe(2);
|
|
930
|
-
});
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
describe('Integration: Settings Validation + Config Workflow', () => {
|
|
934
|
-
let validateSettings, validateGatewayEndpoint, getDefaultSettings;
|
|
935
|
-
|
|
936
|
-
beforeEach(async () => {
|
|
937
|
-
const validation = await import('../src/validation.js');
|
|
938
|
-
validateSettings = validation.validateSettings;
|
|
939
|
-
validateGatewayEndpoint = validation.validateGatewayEndpoint;
|
|
940
|
-
getDefaultSettings = validation.getDefaultSettings;
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
test('default settings pass validation', () => {
|
|
944
|
-
const defaultSettings = getDefaultSettings();
|
|
945
|
-
const result = validateSettings(defaultSettings);
|
|
946
|
-
|
|
947
|
-
expect(result.valid).toBe(true);
|
|
948
|
-
expect(result.value).toBeDefined();
|
|
949
|
-
expect(result.value.refreshInterval).toBe(config.REFRESH_INTERVALS.DEFAULT);
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
test('invalid refresh interval is corrected', () => {
|
|
953
|
-
const invalidSettings = {
|
|
954
|
-
...getDefaultSettings(),
|
|
955
|
-
refreshInterval: 100, // Below minimum
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
const result = validateSettings(invalidSettings);
|
|
959
|
-
|
|
960
|
-
expect(result.valid).toBe(true); // Should auto-correct
|
|
961
|
-
expect(result.value.refreshInterval).toBeGreaterThanOrEqual(config.VALIDATION.REFRESH_INTERVAL.MIN);
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
test('invalid theme is corrected to default', () => {
|
|
965
|
-
const invalidSettings = {
|
|
966
|
-
...getDefaultSettings(),
|
|
967
|
-
theme: 'nonexistent-theme',
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
const result = validateSettings(invalidSettings);
|
|
971
|
-
|
|
972
|
-
expect(result.valid).toBe(true);
|
|
973
|
-
expect(config.VALIDATION.VALID_THEMES).toContain(result.value.theme);
|
|
974
|
-
});
|
|
975
|
-
|
|
976
|
-
test('gateway endpoint validation catches missing fields', () => {
|
|
977
|
-
const invalidEndpoints = [
|
|
978
|
-
{ name: '', host: 'localhost', port: 3000 }, // Empty name
|
|
979
|
-
{ name: 'test', host: '', port: 3000 }, // Empty host
|
|
980
|
-
];
|
|
981
|
-
|
|
982
|
-
invalidEndpoints.forEach(endpoint => {
|
|
983
|
-
const result = validateGatewayEndpoint(endpoint);
|
|
984
|
-
expect(result.valid).toBe(false);
|
|
985
|
-
});
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
test('valid gateway endpoint passes validation', () => {
|
|
989
|
-
const validEndpoint = {
|
|
990
|
-
name: 'test-endpoint',
|
|
991
|
-
host: 'localhost',
|
|
992
|
-
port: 3000,
|
|
993
|
-
token: 'test-token',
|
|
994
|
-
enabled: true,
|
|
995
|
-
type: 'local',
|
|
996
|
-
};
|
|
997
|
-
|
|
998
|
-
const result = validateGatewayEndpoint(validEndpoint);
|
|
999
|
-
expect(result.valid).toBe(true);
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
test('settings export format validation', () => {
|
|
1003
|
-
const validFormats = ['json', 'yaml'];
|
|
1004
|
-
const invalidFormat = 'xml';
|
|
1005
|
-
|
|
1006
|
-
// Valid format
|
|
1007
|
-
const validSettings = { ...getDefaultSettings(), exportFormat: 'json' };
|
|
1008
|
-
let result = validateSettings(validSettings);
|
|
1009
|
-
expect(result.valid).toBe(true);
|
|
1010
|
-
|
|
1011
|
-
// Invalid format should be corrected
|
|
1012
|
-
const invalidSettings = { ...getDefaultSettings(), exportFormat: invalidFormat };
|
|
1013
|
-
result = validateSettings(invalidSettings);
|
|
1014
|
-
expect(result.value.exportFormat).not.toBe(invalidFormat);
|
|
1015
|
-
});
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
describe('Integration: Error Recovery Workflow', () => {
|
|
1019
|
-
beforeEach(() => {
|
|
1020
|
-
cache.clear();
|
|
1021
|
-
alerts.clearAllAlerts();
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
test('graceful degradation when cache fails', async () => {
|
|
1025
|
-
// Simulate cache miss with retry fallback
|
|
1026
|
-
const cacheKey = 'degradation-test';
|
|
1027
|
-
|
|
1028
|
-
// Ensure cache miss
|
|
1029
|
-
expect(cache.get(cacheKey)).toBeNull();
|
|
1030
|
-
|
|
1031
|
-
// Simulated fetch that might fail
|
|
1032
|
-
let fetchAttempts = 0;
|
|
1033
|
-
const unreliableFetch = async () => {
|
|
1034
|
-
fetchAttempts++;
|
|
1035
|
-
if (fetchAttempts === 1) {
|
|
1036
|
-
throw new Error('Temporary failure');
|
|
1037
|
-
}
|
|
1038
|
-
return { data: 'recovered' };
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
// Use retry module for resilience
|
|
1042
|
-
const wrappedFetch = retry.withRetry(unreliableFetch, {
|
|
1043
|
-
maxRetries: 2,
|
|
1044
|
-
initialDelay: 10,
|
|
1045
|
-
retryableErrors: ['Temporary failure'],
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
const result = await wrappedFetch();
|
|
1049
|
-
expect(result.data).toBe('recovered');
|
|
1050
|
-
expect(fetchAttempts).toBe(2);
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
test('alert system continues working after module errors', () => {
|
|
1054
|
-
// Use all valid metric values that will generate alerts
|
|
1055
|
-
const metricsWithAllValid = {
|
|
1056
|
-
cpu: 95, // Above critical threshold (90)
|
|
1057
|
-
memory: 85, // Above warning threshold (75)
|
|
1058
|
-
disk: 96, // Above critical threshold (95)
|
|
1059
|
-
};
|
|
1060
|
-
|
|
1061
|
-
// Alert system should process all valid metrics
|
|
1062
|
-
const results = alerts.checkAllMetrics(metricsWithAllValid);
|
|
1063
|
-
|
|
1064
|
-
// Should generate alerts for all three metrics
|
|
1065
|
-
expect(results.length).toBeGreaterThanOrEqual(2);
|
|
1066
|
-
const alertTypes = results.map(a => a.type);
|
|
1067
|
-
expect(alertTypes).toContain('cpu');
|
|
1068
|
-
expect(alertTypes).toContain('disk');
|
|
1069
|
-
});
|
|
1070
|
-
|
|
1071
|
-
test('retry module handles transient network errors', async () => {
|
|
1072
|
-
let attemptCount = 0;
|
|
1073
|
-
const networkErrors = ['ECONNREFUSED', 'ETIMEDOUT', 'ECONNRESET'];
|
|
1074
|
-
|
|
1075
|
-
const flakyNetworkFn = async () => {
|
|
1076
|
-
attemptCount++;
|
|
1077
|
-
if (attemptCount < 3) {
|
|
1078
|
-
const error = new Error(networkErrors[attemptCount - 1]);
|
|
1079
|
-
error.code = networkErrors[attemptCount - 1];
|
|
1080
|
-
throw error;
|
|
1081
|
-
}
|
|
1082
|
-
return { success: true };
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1085
|
-
const wrappedFn = retry.withRetry(flakyNetworkFn, {
|
|
1086
|
-
maxRetries: 3,
|
|
1087
|
-
initialDelay: 10,
|
|
1088
|
-
maxDelay: 50,
|
|
1089
|
-
retryableErrors: networkErrors,
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
const result = await wrappedFn();
|
|
1093
|
-
expect(result.success).toBe(true);
|
|
1094
|
-
expect(attemptCount).toBe(3);
|
|
1095
|
-
});
|
|
1096
|
-
|
|
1097
|
-
test('retry gives up on non-retryable errors', async () => {
|
|
1098
|
-
const nonRetryableErrors = ['EINVAL', 'ENOENT', 'EACCES'];
|
|
1099
|
-
|
|
1100
|
-
for (const errorCode of nonRetryableErrors) {
|
|
1101
|
-
let attemptCount = 0;
|
|
1102
|
-
|
|
1103
|
-
const failingFn = async () => {
|
|
1104
|
-
attemptCount++;
|
|
1105
|
-
const error = new Error(`Non-retryable: ${errorCode}`);
|
|
1106
|
-
error.code = errorCode;
|
|
1107
|
-
throw error;
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
|
-
const wrappedFn = retry.withRetry(failingFn, {
|
|
1111
|
-
maxRetries: 3,
|
|
1112
|
-
initialDelay: 10,
|
|
1113
|
-
retryableErrors: ['ECONNREFUSED', 'ETIMEDOUT'],
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
await expect(wrappedFn()).rejects.toThrow();
|
|
1117
|
-
expect(attemptCount).toBe(1); // Should not retry
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
});
|
|
1121
|
-
|
|
1122
|
-
describe('Integration: Multi-Module Dashboard Refresh Simulation', () => {
|
|
1123
|
-
beforeEach(() => {
|
|
1124
|
-
cache.clear();
|
|
1125
|
-
alerts.resetThresholds();
|
|
1126
|
-
alerts.clearAllAlerts();
|
|
1127
|
-
alerts.resetRateLimit();
|
|
1128
|
-
performanceMonitor.reset();
|
|
1129
|
-
performanceMonitor.stop();
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
afterEach(() => {
|
|
1133
|
-
performanceMonitor.stop();
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
test('simulates complete dashboard refresh with all modules', async () => {
|
|
1137
|
-
// Initialize all modules
|
|
1138
|
-
performanceMonitor.start();
|
|
1139
|
-
|
|
1140
|
-
// Simulate a complete dashboard refresh cycle
|
|
1141
|
-
const refreshCycle = async () => {
|
|
1142
|
-
// 1. Fetch system metrics (simulated)
|
|
1143
|
-
const systemMetrics = {
|
|
1144
|
-
cpu: Math.random() * 100,
|
|
1145
|
-
memory: Math.random() * 100,
|
|
1146
|
-
disk: 50 + Math.random() * 10,
|
|
1147
|
-
};
|
|
1148
|
-
|
|
1149
|
-
// 2. Cache metrics
|
|
1150
|
-
cache.set('dashboard-system-metrics', systemMetrics, config.CACHE_TTL.CPU);
|
|
1151
|
-
|
|
1152
|
-
// 3. Record performance snapshot
|
|
1153
|
-
await performanceMonitor.record(100);
|
|
1154
|
-
|
|
1155
|
-
// 4. Check alerts
|
|
1156
|
-
const newAlerts = alerts.checkAllMetrics(systemMetrics);
|
|
1157
|
-
|
|
1158
|
-
// 5. Get health status
|
|
1159
|
-
const health = performanceMonitor.checkHealth();
|
|
1160
|
-
|
|
1161
|
-
return { systemMetrics, newAlerts, health };
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
// Run multiple refresh cycles
|
|
1165
|
-
const results = [];
|
|
1166
|
-
for (let i = 0; i < 5; i++) {
|
|
1167
|
-
const result = await refreshCycle();
|
|
1168
|
-
results.push(result);
|
|
1169
|
-
|
|
1170
|
-
// Small delay between cycles
|
|
1171
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Verify all cycles completed
|
|
1175
|
-
expect(results.length).toBe(5);
|
|
1176
|
-
|
|
1177
|
-
// Verify performance history was recorded
|
|
1178
|
-
expect(performanceMonitor.history.length).toBeGreaterThan(0);
|
|
1179
|
-
|
|
1180
|
-
// Verify cache was used
|
|
1181
|
-
const cachedMetrics = cache.get('dashboard-system-metrics');
|
|
1182
|
-
expect(cachedMetrics).toBeDefined();
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
test('handles rapid refresh requests with debouncing', async () => {
|
|
1186
|
-
let refreshCount = 0;
|
|
1187
|
-
|
|
1188
|
-
const doRefresh = async () => {
|
|
1189
|
-
refreshCount++;
|
|
1190
|
-
return { timestamp: Date.now() };
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
// Wrap with debounce
|
|
1194
|
-
const debouncedRefresh = cache.debounce(doRefresh, 100);
|
|
1195
|
-
|
|
1196
|
-
// Trigger multiple rapid refreshes
|
|
1197
|
-
debouncedRefresh();
|
|
1198
|
-
debouncedRefresh();
|
|
1199
|
-
debouncedRefresh();
|
|
1200
|
-
debouncedRefresh();
|
|
1201
|
-
|
|
1202
|
-
// Wait for debounce to settle
|
|
1203
|
-
await new Promise(resolve => setTimeout(resolve, 150));
|
|
1204
|
-
|
|
1205
|
-
// Should only execute once due to debouncing
|
|
1206
|
-
expect(refreshCount).toBe(1);
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
test('handles rapid refresh requests with throttling', async () => {
|
|
1210
|
-
let refreshCount = 0;
|
|
1211
|
-
const results = [];
|
|
1212
|
-
|
|
1213
|
-
const doRefresh = async () => {
|
|
1214
|
-
refreshCount++;
|
|
1215
|
-
const result = { timestamp: Date.now(), count: refreshCount };
|
|
1216
|
-
results.push(result);
|
|
1217
|
-
return result;
|
|
1218
|
-
};
|
|
1219
|
-
|
|
1220
|
-
// Wrap with throttle
|
|
1221
|
-
const throttledRefresh = cache.throttle(doRefresh, 100);
|
|
1222
|
-
|
|
1223
|
-
// Trigger multiple rapid refreshes
|
|
1224
|
-
await throttledRefresh();
|
|
1225
|
-
await throttledRefresh();
|
|
1226
|
-
await throttledRefresh();
|
|
1227
|
-
|
|
1228
|
-
// Should only execute once within throttle window
|
|
1229
|
-
expect(refreshCount).toBe(1);
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
|
-
test('alert escalation and recovery workflow', async () => {
|
|
1233
|
-
performanceMonitor.start();
|
|
1234
|
-
|
|
1235
|
-
// Phase 1: Normal state
|
|
1236
|
-
let newAlerts = alerts.checkAllMetrics({ cpu: 40, memory: 50, disk: 50 });
|
|
1237
|
-
expect(newAlerts.length).toBe(0);
|
|
1238
|
-
|
|
1239
|
-
// Phase 2: Warning state
|
|
1240
|
-
newAlerts = alerts.checkAllMetrics({ cpu: 75, memory: 50, disk: 50 });
|
|
1241
|
-
expect(newAlerts.length).toBe(1);
|
|
1242
|
-
expect(newAlerts[0].level).toBe(alerts.AlertLevel.WARNING);
|
|
1243
|
-
|
|
1244
|
-
// Phase 3: Critical escalation
|
|
1245
|
-
newAlerts = alerts.checkAllMetrics({ cpu: 95, memory: 50, disk: 50 });
|
|
1246
|
-
// Should update existing alert (returns null for updates)
|
|
1247
|
-
const activeAlerts = alerts.getActiveAlerts();
|
|
1248
|
-
const cpuAlert = activeAlerts.find(a => a.type === 'cpu');
|
|
1249
|
-
expect(cpuAlert.level).toBe(alerts.AlertLevel.CRITICAL);
|
|
1250
|
-
|
|
1251
|
-
// Phase 4: Recovery
|
|
1252
|
-
newAlerts = alerts.checkAllMetrics({ cpu: 40, memory: 50, disk: 50 });
|
|
1253
|
-
expect(newAlerts.length).toBe(1);
|
|
1254
|
-
expect(newAlerts[0].level).toBe(alerts.AlertLevel.CLEARED);
|
|
1255
|
-
expect(newAlerts[0].message).toContain('normalized');
|
|
1256
|
-
|
|
1257
|
-
// Verify alert history
|
|
1258
|
-
const history = alerts.getAlertHistory();
|
|
1259
|
-
expect(history.length).toBeGreaterThan(0);
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
test('concurrent metric updates maintain consistency', async () => {
|
|
1263
|
-
performanceMonitor.start();
|
|
1264
|
-
|
|
1265
|
-
// Simulate concurrent metric updates
|
|
1266
|
-
const concurrentUpdates = async () => {
|
|
1267
|
-
const promises = [];
|
|
1268
|
-
|
|
1269
|
-
for (let i = 0; i < 10; i++) {
|
|
1270
|
-
promises.push(
|
|
1271
|
-
Promise.resolve().then(() => {
|
|
1272
|
-
const metrics = {
|
|
1273
|
-
cpu: 50 + Math.random() * 20,
|
|
1274
|
-
memory: 50 + Math.random() * 20,
|
|
1275
|
-
disk: 50,
|
|
1276
|
-
};
|
|
1277
|
-
cache.set('concurrent-metrics', metrics, config.CACHE_TTL.CPU);
|
|
1278
|
-
performanceMonitor.record(50);
|
|
1279
|
-
return metrics;
|
|
1280
|
-
})
|
|
1281
|
-
);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
return Promise.all(promises);
|
|
1285
|
-
};
|
|
1286
|
-
|
|
1287
|
-
const results = await concurrentUpdates();
|
|
1288
|
-
expect(results.length).toBe(10);
|
|
1289
|
-
|
|
1290
|
-
// Verify final cached state is consistent
|
|
1291
|
-
const finalMetrics = cache.get('concurrent-metrics');
|
|
1292
|
-
expect(finalMetrics).toBeDefined();
|
|
1293
|
-
expect(finalMetrics.cpu).toBeGreaterThan(0);
|
|
1294
|
-
expect(finalMetrics.memory).toBeGreaterThan(0);
|
|
1295
|
-
});
|
|
1296
|
-
});
|
|
1297
|
-
|
|
1298
|
-
describe('Integration: Container Detection + System Info Workflow', () => {
|
|
1299
|
-
let containerDetector;
|
|
1300
|
-
|
|
1301
|
-
beforeEach(async () => {
|
|
1302
|
-
containerDetector = await import('../src/container-detector.js');
|
|
1303
|
-
});
|
|
1304
|
-
|
|
1305
|
-
test('container detector module exports expected functions', () => {
|
|
1306
|
-
expect(containerDetector.detectContainerEnv).toBeDefined();
|
|
1307
|
-
expect(typeof containerDetector.detectContainerEnv).toBe('function');
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
test('container detection integrates with system info', async () => {
|
|
1311
|
-
// This test verifies the module integration
|
|
1312
|
-
// Full container detection would require actual container environment
|
|
1313
|
-
const env = await containerDetector.detectContainerEnv();
|
|
1314
|
-
|
|
1315
|
-
// Should return an object with expected properties
|
|
1316
|
-
expect(env).toHaveProperty('isContainer');
|
|
1317
|
-
expect(env).toHaveProperty('isWSL');
|
|
1318
|
-
expect(typeof env.isContainer).toBe('boolean');
|
|
1319
|
-
expect(typeof env.isWSL).toBe('boolean');
|
|
1320
|
-
});
|
|
1321
|
-
});
|