Added local mock executable to support all CI environments
This commit is contained in:
parent
23ece162dd
commit
dc6e1ad96e
@ -1,12 +1,18 @@
|
||||
const { describe, it, beforeEach } = require("node:test");
|
||||
const { describe, it, beforeEach, afterEach } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { SystemServiceMonitorType } = require("../../server/monitor-types/system-service");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
const process = require("process");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
|
||||
describe("SystemServiceMonitorType", () => {
|
||||
let monitorType;
|
||||
let heartbeat;
|
||||
let tempDir;
|
||||
let originalPath;
|
||||
let originalPlatform;
|
||||
|
||||
beforeEach(() => {
|
||||
monitorType = new SystemServiceMonitorType();
|
||||
@ -14,85 +20,107 @@ describe("SystemServiceMonitorType", () => {
|
||||
status: DOWN,
|
||||
msg: "",
|
||||
};
|
||||
originalPath = process.env.PATH;
|
||||
originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
|
||||
});
|
||||
|
||||
it("should detect a running service", async (t) => {
|
||||
if (process.platform !== "linux" && process.platform !== "win32") {
|
||||
t.skip("Skipping integration test on unsupported platform");
|
||||
return;
|
||||
afterEach(() => {
|
||||
process.env.PATH = originalPath;
|
||||
if (originalPlatform) {
|
||||
Object.defineProperty(process, "platform", originalPlatform);
|
||||
}
|
||||
|
||||
// Use core services that are guaranteed to be running
|
||||
let serviceName = "dummy";
|
||||
if (process.platform === "linux") {
|
||||
serviceName = "systemd-journald";
|
||||
} else if (process.platform === "win32") {
|
||||
serviceName = "Dnscache";
|
||||
}
|
||||
|
||||
const monitor = { system_service_name: serviceName };
|
||||
|
||||
try {
|
||||
await monitorType.check(monitor, heartbeat);
|
||||
} catch (e) {
|
||||
// GitHub Actions runners often run inside containers without systemd (PID 1).
|
||||
// In this specific case, we skip the test instead of failing.
|
||||
const msg = e.message || "";
|
||||
if (msg.includes("System has not been booted with systemd") ||
|
||||
msg.includes("Host is down") ||
|
||||
msg.includes("Connect to D-Bus")) {
|
||||
t.skip("Skipping: CI environment does not support systemd/D-Bus");
|
||||
return;
|
||||
if (tempDir && fs.existsSync(tempDir)) {
|
||||
try {
|
||||
fs.rmSync(tempDir, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
// If it failed for any other reason, rethrow (fail the test)
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to create a fake executable that prints specific output.
|
||||
* @param {string} baseName The name of the executable (e.g. systemctl)
|
||||
* @param {string} outputText The text to echo to stdout
|
||||
* @param {number} exitCode The exit code
|
||||
* @returns {void}
|
||||
*/
|
||||
function createMockCommand(baseName, outputText, exitCode = 0) {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "uptime-kuma-test-"));
|
||||
const fileName = process.platform === "win32" ? baseName + ".cmd" : baseName;
|
||||
|
||||
let content;
|
||||
if (process.platform === "win32") {
|
||||
content = `@echo off\necho ${outputText}\nexit /b ${exitCode}`;
|
||||
} else {
|
||||
content = `#!/bin/sh\necho "${outputText}"\nexit ${exitCode}`;
|
||||
}
|
||||
|
||||
const scriptPath = path.join(tempDir, fileName);
|
||||
fs.writeFileSync(scriptPath, content);
|
||||
fs.chmodSync(scriptPath, 0o755);
|
||||
process.env.PATH = tempDir + path.delimiter + process.env.PATH;
|
||||
}
|
||||
|
||||
it("should detect a running service", async () => {
|
||||
const isWin = process.platform === "win32";
|
||||
// We mock the command to output "active", but the Monitor implementation
|
||||
// abstracts this away into a friendly message like "Service ... is running."
|
||||
createMockCommand(isWin ? "powershell" : "systemctl", isWin ? "Running" : "active", 0);
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: "linux",
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
const monitor = {
|
||||
system_service_name: "myservice",
|
||||
};
|
||||
|
||||
await monitorType.check(monitor, heartbeat);
|
||||
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.ok(heartbeat.msg && heartbeat.msg.length > 0);
|
||||
assert.ok(heartbeat.msg.includes("is running"));
|
||||
});
|
||||
|
||||
it("should handle non-existent service gracefully", async (t) => {
|
||||
// Skip this test on macOS/Docker runners where systemctl doesn't exist
|
||||
if (process.platform !== "linux" && process.platform !== "win32") {
|
||||
t.skip("Skipping integration test on unsupported platform");
|
||||
return;
|
||||
it("should detect a stopped service", async () => {
|
||||
const isWin = process.platform === "win32";
|
||||
createMockCommand(isWin ? "powershell" : "systemctl", isWin ? "Stopped" : "inactive", 1);
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: "linux",
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
const monitor = { system_service_name: "non-existent-service-12345" };
|
||||
const monitor = {
|
||||
system_service_name: "myservice",
|
||||
};
|
||||
|
||||
try {
|
||||
await monitorType.check(monitor, heartbeat);
|
||||
} catch (e) {
|
||||
// GitHub Actions workaround: If systemd is missing, we can't test "non-existent"
|
||||
// because the tool itself fails before checking the service.
|
||||
const msg = e.message || "";
|
||||
if (msg.includes("System has not been booted with systemd") || msg.includes("Host is down")) {
|
||||
t.skip("Skipping: CI environment does not support systemd");
|
||||
return;
|
||||
}
|
||||
// Otherwise, we expect a failure for non-existent service, so we catch it.
|
||||
}
|
||||
|
||||
// If we skipped above, this assertion won't be reached.
|
||||
// If we didn't skip, heartbeat should be DOWN.
|
||||
if (heartbeat.msg && heartbeat.msg.includes("System has not been booted with systemd")) {
|
||||
// Redundant check, but ensures safety
|
||||
return;
|
||||
// Expected error
|
||||
}
|
||||
|
||||
assert.strictEqual(heartbeat.status, DOWN);
|
||||
// Ensure some output was captured from the command
|
||||
assert.ok(heartbeat.msg && heartbeat.msg.length > 0);
|
||||
});
|
||||
|
||||
it("should fail gracefully with invalid characters", async () => {
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: "linux",
|
||||
configurable: true
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const monitor = { system_service_name: "invalid&service;name" };
|
||||
const monitor = {
|
||||
system_service_name: "invalid&service;name",
|
||||
};
|
||||
|
||||
try {
|
||||
await monitorType.check(monitor, heartbeat);
|
||||
@ -104,20 +132,15 @@ describe("SystemServiceMonitorType", () => {
|
||||
});
|
||||
|
||||
it("should throw error on unsupported platforms", async () => {
|
||||
// Mock the platform to be 'darwin' (macOS)
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: "darwin",
|
||||
configurable: true
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const monitor = { system_service_name: "test-service" };
|
||||
const monitor = {
|
||||
system_service_name: "test-service",
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
async () => await monitorType.check(monitor, heartbeat),
|
||||
(err) => {
|
||||
assert.match(err.message, /not supported on/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
await assert.rejects(async () => await monitorType.check(monitor, heartbeat), /not supported/);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user