mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
feat: remove legacy alert rules, superseded by automations
Alert rules already auto-migrate into automations on boot and the old engine stands down after. Deletes the rule/firing tables, routes, UI, and AI tools; keeps notification channels since automations still use them. Also fixes automations' notify step, which still called the deleted repository.
This commit is contained in:
1 parent
e0f14f2ecf
commit
e372b0eda7
101 files changed
+29118
-5539
No files matched your search
@@ -6,12 +6,12 @@ const BASE = { hostCount: 3, allowReadOnlyCommands: false };
|
||||
describe("buildSystemPrompt", () => {
|
||||
it("tells the assistant to stay inside what was asked", () => {
|
||||
// Without these the assistant answered "what is running on this server"
|
||||
// by also proposing a monitoring script and an alert rule nobody wanted.
|
||||
// by also proposing a monitoring script and an automation nobody wanted.
|
||||
const prompt = buildSystemPrompt(BASE);
|
||||
|
||||
expect(prompt).toContain("nothing beyond it");
|
||||
expect(prompt).toMatch(/Read, then report\. Do not propose anything\./);
|
||||
expect(prompt).toMatch(/no monitoring, no alert rules, no scripts/);
|
||||
expect(prompt).toMatch(/no monitoring automations, no scripts/);
|
||||
expect(prompt).toMatch(/One request means one proposal at most/);
|
||||
});
|
||||
|
||||
|
||||
@@ -13,14 +13,12 @@ const snippetRepository = {
|
||||
deleteSnippet: vi.fn(),
|
||||
};
|
||||
const fleetRepository = { create: vi.fn(), addMember: vi.fn() };
|
||||
const alertRepository = { createAlertRule: vi.fn() };
|
||||
const automationRepository = { create: vi.fn() };
|
||||
|
||||
vi.mock("../../../../database/repositories/factory.js", () => ({
|
||||
createCurrentHostRepository: () => hostRepository,
|
||||
createCurrentSnippetRepository: () => snippetRepository,
|
||||
createCurrentFleetRepository: () => fleetRepository,
|
||||
createCurrentAlertRepository: () => alertRepository,
|
||||
createCurrentAutomationRepository: () => automationRepository,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
/**
|
||||
* Once the alert rules have been migrated into automations, both systems hold
|
||||
* a copy of every rule. If the old engine kept evaluating, every alert would
|
||||
* be delivered twice.
|
||||
*/
|
||||
|
||||
const listEnabledRulesForHost = vi.fn();
|
||||
const listEnabledRulesForHostUser = vi.fn();
|
||||
const createFiring = vi.fn();
|
||||
|
||||
vi.mock("../../../database/repositories/factory.js", () => ({
|
||||
createCurrentAlertRepository: () => ({
|
||||
listEnabledRulesForHost,
|
||||
listEnabledRulesForHostUser,
|
||||
createFiring,
|
||||
pruneFiringsOlderThan: vi.fn(),
|
||||
listEnabledChannelsForRule: vi.fn(async () => []),
|
||||
findRuleById: vi.fn(async () => null),
|
||||
getHostDisplayName: vi.fn(async () => "host"),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/logger.js", () => ({
|
||||
statsLogger: { warn: vi.fn(), info: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/notification-sender.js", () => ({
|
||||
sendNotification: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/discord-sender.js", () => ({
|
||||
sendDiscord: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
const alertEngineModule =
|
||||
await import("../../../../../plugins/host-metrics/backend/alert-engine.js");
|
||||
|
||||
const cpuRule = {
|
||||
id: 1,
|
||||
userId: "user-1",
|
||||
hostId: null,
|
||||
name: "CPU",
|
||||
enabled: true,
|
||||
triggerType: "cpu_threshold",
|
||||
thresholdValue: 50,
|
||||
thresholdDurationSeconds: 0,
|
||||
cooldownMinutes: 0,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
listEnabledRulesForHost.mockResolvedValue([cpuRule]);
|
||||
listEnabledRulesForHostUser.mockResolvedValue([cpuRule]);
|
||||
});
|
||||
|
||||
describe("AlertEngine before migration", () => {
|
||||
it("still evaluates rules", async () => {
|
||||
expect(alertEngineModule.isAlertEngineSuperseded()).toBe(false);
|
||||
|
||||
await alertEngineModule.AlertEngine.getInstance().evaluateMetrics(7, {
|
||||
cpu: { percent: 90 },
|
||||
});
|
||||
|
||||
expect(listEnabledRulesForHost).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("AlertEngine after migration", () => {
|
||||
it("stops evaluating every trigger type", async () => {
|
||||
alertEngineModule.markAlertEngineSuperseded();
|
||||
expect(alertEngineModule.isAlertEngineSuperseded()).toBe(true);
|
||||
|
||||
const engine = alertEngineModule.AlertEngine.getInstance();
|
||||
await engine.evaluateMetrics(7, { cpu: { percent: 99 } });
|
||||
await engine.evaluateStatus(7, false);
|
||||
await engine.evaluateHealthCheck(7, "user-1", "web", false);
|
||||
await engine.evaluateUserLogin(7, "user-1", "root", "10.0.0.1");
|
||||
|
||||
// Nothing is even loaded, so nothing can be delivered a second time.
|
||||
expect(listEnabledRulesForHost).not.toHaveBeenCalled();
|
||||
expect(listEnabledRulesForHostUser).not.toHaveBeenCalled();
|
||||
expect(createFiring).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,6 @@ const route = ast.statements.find(
|
||||
);
|
||||
if (!route) throw new Error("Login event route is missing");
|
||||
const notify = vi.fn();
|
||||
const legacy = vi.fn().mockResolvedValue(undefined);
|
||||
let handler: (req: unknown, res: unknown) => Promise<void>;
|
||||
runInNewContext(
|
||||
ts.transpileModule(route.getText(ast), {
|
||||
@@ -49,7 +48,6 @@ runInNewContext(
|
||||
},
|
||||
}),
|
||||
notifyAutomationInternalEvent: notify,
|
||||
AlertEngine: { getInstance: () => ({ evaluateUserLogin: legacy }) },
|
||||
},
|
||||
);
|
||||
const body = {
|
||||
@@ -61,7 +59,7 @@ const body = {
|
||||
const response = () => ({ status: vi.fn().mockReturnThis(), json: vi.fn() });
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
it("delivers successful SSH login details to automations while retaining the legacy hook", async () => {
|
||||
it("delivers successful SSH login details to automations", async () => {
|
||||
const res = response();
|
||||
await handler(
|
||||
{
|
||||
@@ -75,12 +73,6 @@ it("delivers successful SSH login details to automations while retaining the leg
|
||||
sshUser: "alice",
|
||||
fromIp: "192.0.2.4",
|
||||
});
|
||||
expect(legacy).toHaveBeenCalledExactlyOnceWith(
|
||||
42,
|
||||
"owner",
|
||||
"alice",
|
||||
"192.0.2.4",
|
||||
);
|
||||
expect(res.json).toHaveBeenCalledWith({ ok: true });
|
||||
});
|
||||
|
||||
@@ -101,6 +93,5 @@ it.each([
|
||||
);
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(notify).not.toHaveBeenCalled();
|
||||
expect(legacy).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user