process-watchdog 1.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/config/default.json +16 -0
- package/dashboard/watchdog.html +856 -0
- package/dist/api/routes.d.ts +6 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +105 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/api/server.d.ts +10 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +16 -0
- package/dist/api/server.js.map +1 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +45 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/processwatchdog.err.log +973 -0
- package/dist/daemon/processwatchdog.exe +0 -0
- package/dist/daemon/processwatchdog.exe.config +6 -0
- package/dist/daemon/processwatchdog.out.log +2 -0
- package/dist/daemon/processwatchdog.wrapper.log +18 -0
- package/dist/daemon/processwatchdog.xml +30 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +203 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/service.d.ts +3 -0
- package/dist/installer/service.d.ts.map +1 -0
- package/dist/installer/service.js +41 -0
- package/dist/installer/service.js.map +1 -0
- package/dist/integrations/mah.d.ts +3 -0
- package/dist/integrations/mah.d.ts.map +1 -0
- package/dist/integrations/mah.js +22 -0
- package/dist/integrations/mah.js.map +1 -0
- package/dist/integrations/total-recall.d.ts +3 -0
- package/dist/integrations/total-recall.d.ts.map +1 -0
- package/dist/integrations/total-recall.js +22 -0
- package/dist/integrations/total-recall.js.map +1 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +10 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/platform.interface.d.ts +42 -0
- package/dist/platform/platform.interface.d.ts.map +1 -0
- package/dist/platform/platform.interface.js +2 -0
- package/dist/platform/platform.interface.js.map +1 -0
- package/dist/platform/windows.d.ts +14 -0
- package/dist/platform/windows.d.ts.map +1 -0
- package/dist/platform/windows.js +162 -0
- package/dist/platform/windows.js.map +1 -0
- package/dist/plugins/cpu-monitor.d.ts +11 -0
- package/dist/plugins/cpu-monitor.d.ts.map +1 -0
- package/dist/plugins/cpu-monitor.js +57 -0
- package/dist/plugins/cpu-monitor.js.map +1 -0
- package/dist/plugins/disk-health.d.ts +11 -0
- package/dist/plugins/disk-health.d.ts.map +1 -0
- package/dist/plugins/disk-health.js +111 -0
- package/dist/plugins/disk-health.js.map +1 -0
- package/dist/plugins/memory-monitor.d.ts +11 -0
- package/dist/plugins/memory-monitor.d.ts.map +1 -0
- package/dist/plugins/memory-monitor.js +61 -0
- package/dist/plugins/memory-monitor.js.map +1 -0
- package/dist/plugins/plugin-loader.d.ts +4 -0
- package/dist/plugins/plugin-loader.d.ts.map +1 -0
- package/dist/plugins/plugin-loader.js +25 -0
- package/dist/plugins/plugin-loader.js.map +1 -0
- package/dist/plugins/plugin.interface.d.ts +28 -0
- package/dist/plugins/plugin.interface.d.ts.map +1 -0
- package/dist/plugins/plugin.interface.js +2 -0
- package/dist/plugins/plugin.interface.js.map +1 -0
- package/dist/plugins/process-guard.d.ts +11 -0
- package/dist/plugins/process-guard.d.ts.map +1 -0
- package/dist/plugins/process-guard.js +139 -0
- package/dist/plugins/process-guard.js.map +1 -0
- package/dist/plugins/startup-optimizer.d.ts +11 -0
- package/dist/plugins/startup-optimizer.d.ts.map +1 -0
- package/dist/plugins/startup-optimizer.js +78 -0
- package/dist/plugins/startup-optimizer.js.map +1 -0
- package/dist/scheduler.d.ts +16 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +46 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/store/history.d.ts +20 -0
- package/dist/store/history.d.ts.map +1 -0
- package/dist/store/history.js +60 -0
- package/dist/store/history.js.map +1 -0
- package/package.json +35 -0
- package/src/api/routes.ts +123 -0
- package/src/api/server.ts +20 -0
- package/src/config.ts +78 -0
- package/src/index.ts +228 -0
- package/src/installer/service.ts +50 -0
- package/src/integrations/mah.ts +22 -0
- package/src/integrations/total-recall.ts +27 -0
- package/src/platform/index.ts +13 -0
- package/src/platform/platform.interface.ts +46 -0
- package/src/platform/windows.ts +242 -0
- package/src/plugins/cpu-monitor.ts +67 -0
- package/src/plugins/disk-health.ts +128 -0
- package/src/plugins/memory-monitor.ts +70 -0
- package/src/plugins/plugin-loader.ts +27 -0
- package/src/plugins/plugin.interface.ts +31 -0
- package/src/plugins/process-guard.ts +165 -0
- package/src/plugins/startup-optimizer.ts +103 -0
- package/src/scheduler.ts +53 -0
- package/src/store/history.ts +90 -0
- package/tests/api/routes.test.ts +113 -0
- package/tests/config.test.ts +24 -0
- package/tests/platform/windows.test.ts +59 -0
- package/tests/plugins/cpu-monitor.test.ts +69 -0
- package/tests/plugins/disk-health.test.ts +69 -0
- package/tests/plugins/memory-monitor.test.ts +57 -0
- package/tests/plugins/plugin-loader.test.ts +35 -0
- package/tests/plugins/process-guard.test.ts +40 -0
- package/tests/plugins/startup-optimizer.test.ts +50 -0
- package/tests/scheduler.test.ts +69 -0
- package/tests/store/history.test.ts +89 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import { DiskHealthPlugin } from '../../src/plugins/disk-health.js';
|
|
3
|
+
|
|
4
|
+
const describeWindows = process.platform === 'win32' ? describe : describe.skip;
|
|
5
|
+
|
|
6
|
+
describeWindows('DiskHealthPlugin', () => {
|
|
7
|
+
let plugin: DiskHealthPlugin;
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
plugin = new DiskHealthPlugin();
|
|
11
|
+
plugin.configure({
|
|
12
|
+
enabled: true,
|
|
13
|
+
interval: 1800000,
|
|
14
|
+
autoFix: true,
|
|
15
|
+
thresholds: { warningPct: 80, criticalPct: 90, tempFileAgeDays: 7 },
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('check() returns diskUsedPct between 0 and 100', async () => {
|
|
20
|
+
const result = await plugin.check();
|
|
21
|
+
expect(['healthy', 'warning', 'critical']).toContain(result.status);
|
|
22
|
+
expect(result.metrics).toHaveProperty('diskUsedPct');
|
|
23
|
+
expect(result.metrics.diskUsedPct).toBeGreaterThanOrEqual(0);
|
|
24
|
+
expect(result.metrics.diskUsedPct).toBeLessThanOrEqual(100);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('check() returns diskFreeGB and diskTotalGB as positive numbers', async () => {
|
|
28
|
+
const result = await plugin.check();
|
|
29
|
+
expect(result.metrics).toHaveProperty('diskFreeGB');
|
|
30
|
+
expect(result.metrics).toHaveProperty('diskTotalGB');
|
|
31
|
+
expect(result.metrics.diskFreeGB).toBeGreaterThanOrEqual(0);
|
|
32
|
+
expect(result.metrics.diskTotalGB).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('check() returns a non-empty message', async () => {
|
|
36
|
+
const result = await plugin.check();
|
|
37
|
+
expect(typeof result.message).toBe('string');
|
|
38
|
+
expect(result.message.length).toBeGreaterThan(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('check() returns warning/critical when warningPct threshold is very low', async () => {
|
|
42
|
+
plugin.configure({
|
|
43
|
+
enabled: true,
|
|
44
|
+
interval: 1800000,
|
|
45
|
+
autoFix: true,
|
|
46
|
+
thresholds: { warningPct: 1, criticalPct: 2, tempFileAgeDays: 7 },
|
|
47
|
+
});
|
|
48
|
+
const result = await plugin.check();
|
|
49
|
+
expect(['warning', 'critical']).toContain(result.status);
|
|
50
|
+
// Reset
|
|
51
|
+
plugin.configure({
|
|
52
|
+
enabled: true,
|
|
53
|
+
interval: 1800000,
|
|
54
|
+
autoFix: true,
|
|
55
|
+
thresholds: { warningPct: 80, criticalPct: 90, tempFileAgeDays: 7 },
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('fix() returns valid FixResult', async () => {
|
|
60
|
+
const result = await plugin.fix();
|
|
61
|
+
expect(result).toHaveProperty('actionsKept');
|
|
62
|
+
expect(result).toHaveProperty('resourcesFreed');
|
|
63
|
+
expect(result).toHaveProperty('beforeMetrics');
|
|
64
|
+
expect(result).toHaveProperty('afterMetrics');
|
|
65
|
+
expect(Array.isArray(result.actionsKept)).toBe(true);
|
|
66
|
+
expect(result.beforeMetrics).toHaveProperty('diskFreeGB');
|
|
67
|
+
expect(result.afterMetrics).toHaveProperty('diskFreeGB');
|
|
68
|
+
}, 30000);
|
|
69
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import { MemoryMonitorPlugin } from '../../src/plugins/memory-monitor.js';
|
|
3
|
+
|
|
4
|
+
const describeWindows = process.platform === 'win32' ? describe : describe.skip;
|
|
5
|
+
|
|
6
|
+
describeWindows('MemoryMonitorPlugin', () => {
|
|
7
|
+
let plugin: MemoryMonitorPlugin;
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
plugin = new MemoryMonitorPlugin();
|
|
11
|
+
plugin.configure({ enabled: true, interval: 120000, autoFix: false, thresholds: { warningPct: 85, criticalPct: 95 } });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('check() returns metrics with ramUsedPct, ramFreeMB, ramTotalMB, pageFileMB — all valid numbers', async () => {
|
|
15
|
+
const result = await plugin.check();
|
|
16
|
+
expect(typeof result.metrics.ramUsedPct).toBe('number');
|
|
17
|
+
expect(typeof result.metrics.ramFreeMB).toBe('number');
|
|
18
|
+
expect(typeof result.metrics.ramTotalMB).toBe('number');
|
|
19
|
+
expect(typeof result.metrics.pageFileMB).toBe('number');
|
|
20
|
+
expect(isFinite(result.metrics.ramUsedPct)).toBe(true);
|
|
21
|
+
expect(isFinite(result.metrics.ramFreeMB)).toBe(true);
|
|
22
|
+
expect(isFinite(result.metrics.ramTotalMB)).toBe(true);
|
|
23
|
+
expect(isFinite(result.metrics.pageFileMB)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('ramUsedPct is between 0 and 100', async () => {
|
|
27
|
+
const result = await plugin.check();
|
|
28
|
+
expect(result.metrics.ramUsedPct).toBeGreaterThanOrEqual(0);
|
|
29
|
+
expect(result.metrics.ramUsedPct).toBeLessThanOrEqual(100);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('status is one of healthy/warning/critical', async () => {
|
|
33
|
+
const result = await plugin.check();
|
|
34
|
+
expect(['healthy', 'warning', 'critical']).toContain(result.status);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('check() returns critical status when criticalPct threshold is very low', async () => {
|
|
38
|
+
plugin.configure({ enabled: true, interval: 120000, autoFix: false, thresholds: { warningPct: 1, criticalPct: 2 } });
|
|
39
|
+
const result = await plugin.check();
|
|
40
|
+
expect(result.status).toBe('critical');
|
|
41
|
+
// Reset
|
|
42
|
+
plugin.configure({ enabled: true, interval: 120000, autoFix: false, thresholds: { warningPct: 85, criticalPct: 95 } });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('fix() returns alert-only FixResult with correct shape', async () => {
|
|
46
|
+
const result = await plugin.fix();
|
|
47
|
+
expect(result).toHaveProperty('actionsKept');
|
|
48
|
+
expect(result).toHaveProperty('resourcesFreed');
|
|
49
|
+
expect(result).toHaveProperty('beforeMetrics');
|
|
50
|
+
expect(result).toHaveProperty('afterMetrics');
|
|
51
|
+
expect(Array.isArray(result.actionsKept)).toBe(true);
|
|
52
|
+
expect(result.actionsKept).toContain('Alert pushed (no auto-fix for memory)');
|
|
53
|
+
expect(result.resourcesFreed).toBe('0 MB (alert only)');
|
|
54
|
+
// before and after metrics should be the same (alert only, no fix)
|
|
55
|
+
expect(result.beforeMetrics).toEqual(result.afterMetrics);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { loadPlugins } from '../../src/plugins/plugin-loader.js';
|
|
3
|
+
import { loadConfig } from '../../src/config.js';
|
|
4
|
+
|
|
5
|
+
describe('loadPlugins', () => {
|
|
6
|
+
it('loads all enabled plugins from config', async () => {
|
|
7
|
+
const config = await loadConfig();
|
|
8
|
+
const plugins = loadPlugins(config);
|
|
9
|
+
const names = plugins.map(p => p.name);
|
|
10
|
+
expect(names).toContain('process-guard');
|
|
11
|
+
expect(names).toContain('memory-monitor');
|
|
12
|
+
expect(names).toContain('disk-health');
|
|
13
|
+
expect(names).toContain('startup-optimizer');
|
|
14
|
+
expect(names).toContain('cpu-monitor');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('skips disabled plugins', async () => {
|
|
18
|
+
const config = await loadConfig({
|
|
19
|
+
plugins: { 'process-guard': { enabled: false, interval: 0, autoFix: false, thresholds: {} } },
|
|
20
|
+
});
|
|
21
|
+
const plugins = loadPlugins(config);
|
|
22
|
+
const names = plugins.map(p => p.name);
|
|
23
|
+
expect(names).not.toContain('process-guard');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('configures plugins with config thresholds', async () => {
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
const plugins = loadPlugins(config);
|
|
29
|
+
for (const plugin of plugins) {
|
|
30
|
+
expect(plugin.name).toBeTruthy();
|
|
31
|
+
expect(plugin.description).toBeTruthy();
|
|
32
|
+
expect(plugin.defaultInterval).toBeGreaterThanOrEqual(0);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import { ProcessGuardPlugin } from '../../src/plugins/process-guard.js';
|
|
3
|
+
|
|
4
|
+
const describeWindows = process.platform === 'win32' ? describe : describe.skip;
|
|
5
|
+
|
|
6
|
+
describeWindows('ProcessGuardPlugin', () => {
|
|
7
|
+
let plugin: ProcessGuardPlugin;
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
plugin = new ProcessGuardPlugin();
|
|
11
|
+
plugin.configure({ enabled: true, interval: 300000, autoFix: true, thresholds: { node: 50, cmd: 20, bash: 10 } });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('check() returns valid HealthCheck with process counts', async () => {
|
|
15
|
+
const result = await plugin.check();
|
|
16
|
+
expect(['healthy', 'warning', 'critical']).toContain(result.status);
|
|
17
|
+
expect(result.metrics).toHaveProperty('nodeCount');
|
|
18
|
+
expect(result.metrics).toHaveProperty('cmdCount');
|
|
19
|
+
expect(result.metrics).toHaveProperty('bashCount');
|
|
20
|
+
expect(result.metrics).toHaveProperty('totalOrphans');
|
|
21
|
+
expect(typeof result.message).toBe('string');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('check() returns warning/critical when thresholds are very low', async () => {
|
|
25
|
+
plugin.configure({ enabled: true, interval: 300000, autoFix: true, thresholds: { node: 1, cmd: 1, bash: 1 } });
|
|
26
|
+
const result = await plugin.check();
|
|
27
|
+
expect(['warning', 'critical']).toContain(result.status);
|
|
28
|
+
// Reset
|
|
29
|
+
plugin.configure({ enabled: true, interval: 300000, autoFix: true, thresholds: { node: 50, cmd: 20, bash: 10 } });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('fix() returns valid FixResult', async () => {
|
|
33
|
+
const result = await plugin.fix();
|
|
34
|
+
expect(result).toHaveProperty('actionsKept');
|
|
35
|
+
expect(result).toHaveProperty('resourcesFreed');
|
|
36
|
+
expect(result).toHaveProperty('beforeMetrics');
|
|
37
|
+
expect(result).toHaveProperty('afterMetrics');
|
|
38
|
+
expect(Array.isArray(result.actionsKept)).toBe(true);
|
|
39
|
+
}, 30000);
|
|
40
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import { StartupOptimizerPlugin } from '../../src/plugins/startup-optimizer.js';
|
|
3
|
+
|
|
4
|
+
const describeWindows = process.platform === 'win32' ? describe : describe.skip;
|
|
5
|
+
|
|
6
|
+
describeWindows('StartupOptimizerPlugin', () => {
|
|
7
|
+
let plugin: StartupOptimizerPlugin;
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
plugin = new StartupOptimizerPlugin();
|
|
11
|
+
plugin.configure({ enabled: true, interval: 0, autoFix: false, thresholds: {} });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('check() returns startupItemCount as a number', async () => {
|
|
15
|
+
const result = await plugin.check();
|
|
16
|
+
expect(['healthy', 'warning', 'critical']).toContain(result.status);
|
|
17
|
+
expect(result.metrics).toHaveProperty('startupItemCount');
|
|
18
|
+
expect(typeof result.metrics.startupItemCount).toBe('number');
|
|
19
|
+
expect(result.metrics.startupItemCount).toBeGreaterThanOrEqual(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('check() returns bloatItemCount as a non-negative number', async () => {
|
|
23
|
+
const result = await plugin.check();
|
|
24
|
+
expect(result.metrics).toHaveProperty('bloatItemCount');
|
|
25
|
+
expect(typeof result.metrics.bloatItemCount).toBe('number');
|
|
26
|
+
expect(result.metrics.bloatItemCount).toBeGreaterThanOrEqual(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('check() bloatItemCount does not exceed startupItemCount', async () => {
|
|
30
|
+
const result = await plugin.check();
|
|
31
|
+
expect(result.metrics.bloatItemCount).toBeLessThanOrEqual(result.metrics.startupItemCount);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('check() returns a non-empty message', async () => {
|
|
35
|
+
const result = await plugin.check();
|
|
36
|
+
expect(typeof result.message).toBe('string');
|
|
37
|
+
expect(result.message.length).toBeGreaterThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('fix() returns valid FixResult with before/after metrics', async () => {
|
|
41
|
+
const result = await plugin.fix();
|
|
42
|
+
expect(result).toHaveProperty('actionsKept');
|
|
43
|
+
expect(result).toHaveProperty('resourcesFreed');
|
|
44
|
+
expect(result).toHaveProperty('beforeMetrics');
|
|
45
|
+
expect(result).toHaveProperty('afterMetrics');
|
|
46
|
+
expect(Array.isArray(result.actionsKept)).toBe(true);
|
|
47
|
+
expect(result.beforeMetrics).toHaveProperty('startupItemCount');
|
|
48
|
+
expect(result.afterMetrics).toHaveProperty('startupItemCount');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { Scheduler } from '../src/scheduler.js';
|
|
3
|
+
import { WatchdogPlugin, HealthCheck, FixResult, PluginConfig } from '../src/plugins/plugin.interface.js';
|
|
4
|
+
|
|
5
|
+
class MockPlugin implements WatchdogPlugin {
|
|
6
|
+
name = 'mock-plugin';
|
|
7
|
+
description = 'Test plugin';
|
|
8
|
+
defaultInterval = 1000;
|
|
9
|
+
checkCount = 0;
|
|
10
|
+
fixCount = 0;
|
|
11
|
+
private status: 'healthy' | 'critical' = 'healthy';
|
|
12
|
+
|
|
13
|
+
configure(_opts: PluginConfig): void {}
|
|
14
|
+
setStatus(s: 'healthy' | 'critical') { this.status = s; }
|
|
15
|
+
|
|
16
|
+
async check(): Promise<HealthCheck> {
|
|
17
|
+
this.checkCount++;
|
|
18
|
+
return { status: this.status, metrics: { count: this.checkCount }, message: 'mock', suggestedActions: [] };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async fix(): Promise<FixResult> {
|
|
22
|
+
this.fixCount++;
|
|
23
|
+
return { actionsKept: ['mock fix'], resourcesFreed: '0', beforeMetrics: {}, afterMetrics: {} };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('Scheduler', () => {
|
|
28
|
+
let plugin: MockPlugin;
|
|
29
|
+
let scheduler: Scheduler;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
plugin = new MockPlugin();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
scheduler?.stop();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('runs check() on the given interval', async () => {
|
|
40
|
+
scheduler = new Scheduler([plugin], { autoFix: {} });
|
|
41
|
+
scheduler.start(100);
|
|
42
|
+
|
|
43
|
+
await new Promise(resolve => setTimeout(resolve, 350));
|
|
44
|
+
|
|
45
|
+
expect(plugin.checkCount).toBeGreaterThanOrEqual(2);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('calls fix() when check returns critical and autoFix is true', async () => {
|
|
49
|
+
plugin.setStatus('critical');
|
|
50
|
+
scheduler = new Scheduler([plugin], { autoFix: { 'mock-plugin': true } });
|
|
51
|
+
scheduler.start(100);
|
|
52
|
+
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
54
|
+
scheduler.stop();
|
|
55
|
+
|
|
56
|
+
expect(plugin.fixCount).toBeGreaterThanOrEqual(1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('does NOT call fix() when check returns healthy', async () => {
|
|
60
|
+
plugin.setStatus('healthy');
|
|
61
|
+
scheduler = new Scheduler([plugin], { autoFix: { 'mock-plugin': true } });
|
|
62
|
+
scheduler.start(100);
|
|
63
|
+
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
65
|
+
scheduler.stop();
|
|
66
|
+
|
|
67
|
+
expect(plugin.fixCount).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { HistoryStore } from '../../src/store/history.js';
|
|
3
|
+
import { existsSync, unlinkSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const TEST_DB = './test-history.db';
|
|
6
|
+
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
if (existsSync(TEST_DB)) {
|
|
9
|
+
unlinkSync(TEST_DB);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('HistoryStore', () => {
|
|
14
|
+
it('recordCheck() then getHistory() returns the recorded check', async () => {
|
|
15
|
+
const store = new HistoryStore(TEST_DB);
|
|
16
|
+
const metrics = { cpu: 45.2, memory: 1024 };
|
|
17
|
+
|
|
18
|
+
store.recordCheck('process-guard', 'ok', metrics, 'All good');
|
|
19
|
+
|
|
20
|
+
const history = store.getHistory(null, 10);
|
|
21
|
+
expect(history).toHaveLength(1);
|
|
22
|
+
|
|
23
|
+
const row = history[0];
|
|
24
|
+
expect(row.plugin).toBe('process-guard');
|
|
25
|
+
expect(row.type).toBe('check');
|
|
26
|
+
expect(row.status).toBe('ok');
|
|
27
|
+
expect(row.metrics).toBe(JSON.stringify(metrics));
|
|
28
|
+
expect(row.message).toBe('All good');
|
|
29
|
+
expect(row.timestamp).toBeDefined();
|
|
30
|
+
|
|
31
|
+
store.close();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('recordFix() then getHistory() returns the recorded fix with type="fix" and actions array', async () => {
|
|
35
|
+
const store = new HistoryStore(TEST_DB);
|
|
36
|
+
const actions = ['killed process node (pid 1234)', 'freed 512MB'];
|
|
37
|
+
|
|
38
|
+
store.recordFix('process-guard', actions, '512MB');
|
|
39
|
+
|
|
40
|
+
const history = store.getHistory(null, 10);
|
|
41
|
+
expect(history).toHaveLength(1);
|
|
42
|
+
|
|
43
|
+
const row = history[0];
|
|
44
|
+
expect(row.plugin).toBe('process-guard');
|
|
45
|
+
expect(row.type).toBe('fix');
|
|
46
|
+
expect(row.status).toBe('fixed');
|
|
47
|
+
expect(row.actions).toBe(JSON.stringify(actions));
|
|
48
|
+
expect(row.message).toBe('512MB');
|
|
49
|
+
|
|
50
|
+
store.close();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('getLastChecks() returns one entry per plugin (the latest check for each)', async () => {
|
|
54
|
+
const store = new HistoryStore(TEST_DB);
|
|
55
|
+
|
|
56
|
+
store.recordCheck('process-guard', 'ok', { cpu: 10 }, 'first');
|
|
57
|
+
store.recordCheck('process-guard', 'warning', { cpu: 80 }, 'second');
|
|
58
|
+
store.recordCheck('disk-guard', 'ok', { disk: 50 }, 'disk check');
|
|
59
|
+
|
|
60
|
+
const lastChecks = store.getLastChecks();
|
|
61
|
+
|
|
62
|
+
expect(Object.keys(lastChecks)).toHaveLength(2);
|
|
63
|
+
expect(lastChecks['process-guard']).toBeDefined();
|
|
64
|
+
expect(lastChecks['disk-guard']).toBeDefined();
|
|
65
|
+
|
|
66
|
+
// Should be the latest check for process-guard
|
|
67
|
+
expect(lastChecks['process-guard'].status).toBe('warning');
|
|
68
|
+
expect(lastChecks['process-guard'].message).toBe('second');
|
|
69
|
+
|
|
70
|
+
// Should be the only check for disk-guard
|
|
71
|
+
expect(lastChecks['disk-guard'].status).toBe('ok');
|
|
72
|
+
expect(lastChecks['disk-guard'].message).toBe('disk check');
|
|
73
|
+
|
|
74
|
+
store.close();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('getHistory() filters by plugin when plugin is specified', async () => {
|
|
78
|
+
const store = new HistoryStore(TEST_DB);
|
|
79
|
+
|
|
80
|
+
store.recordCheck('process-guard', 'ok', {}, 'pg check');
|
|
81
|
+
store.recordCheck('disk-guard', 'ok', {}, 'dg check');
|
|
82
|
+
|
|
83
|
+
const history = store.getHistory('process-guard', 10);
|
|
84
|
+
expect(history).toHaveLength(1);
|
|
85
|
+
expect(history[0].plugin).toBe('process-guard');
|
|
86
|
+
|
|
87
|
+
store.close();
|
|
88
|
+
});
|
|
89
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|