Files
uptime-kuma/test/backend-test/monitors/test-tcp.js
T

241 lines
7.5 KiB
JavaScript

const { describe, test } = require("node:test");
const assert = require("node:assert");
const { TCPMonitorType } = require("../../../server/monitor-types/tcp");
const { UP, PENDING } = require("../../../src/util");
const net = require("net");
const { retryExternalService } = require("../test-util");
describe("TCP Monitor", () => {
/**
* Creates a TCP server on a specified port
* @param {number} port - The port number to listen on
* @returns {Promise<net.Server>} A promise that resolves with the created server
*/
async function createTCPServer(port) {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.listen(port, () => {
resolve(server);
});
server.on("error", (err) => {
reject(err);
});
});
}
test("check() sets status to UP when TCP server is reachable", async () => {
const port = 12345;
const server = await createTCPServer(port);
try {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "localhost",
port: port,
isEnabledExpiryNotification: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
await tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
} finally {
server.close();
}
});
test("check() rejects with connection failed when TCP server is not running", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "localhost",
port: 54321,
isEnabledExpiryNotification: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
await assert.rejects(tcpMonitor.check(monitor, heartbeat, {}), new Error("Connection failed"));
});
test("check() rejects when TLS certificate is expired or invalid", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "expired.badssl.com",
port: 443,
smtpSecurity: "secure",
isEnabledExpiryNotification: () => true,
handleTlsInfo: async (tlsInfo) => {
return tlsInfo;
},
};
const heartbeat = {
msg: "",
status: PENDING,
};
// Regex: contains with "TLS Connection failed:" or "Certificate is invalid"
const regex = /TLS Connection failed:|Certificate is invalid/;
await retryExternalService(async () => {
await assert.rejects(tcpMonitor.check(monitor, heartbeat, {}), regex);
});
});
test("check() sets status to UP when TLS certificate is valid (SSL)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "smtp.gmail.com",
port: 465,
smtpSecurity: "secure",
isEnabledExpiryNotification: () => true,
handleTlsInfo: async (tlsInfo) => {
return tlsInfo;
},
};
const heartbeat = {
msg: "",
status: PENDING,
};
await retryExternalService(async () => {
await tcpMonitor.check(monitor, heartbeat, {});
});
assert.strictEqual(heartbeat.status, UP);
});
test("check() sets status to UP when TLS certificate is valid (STARTTLS)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "smtp.gmail.com",
port: 587,
smtpSecurity: "starttls",
isEnabledExpiryNotification: () => true,
handleTlsInfo: async (tlsInfo) => {
return tlsInfo;
},
};
const heartbeat = {
msg: "",
status: PENDING,
};
await retryExternalService(async () => {
await tcpMonitor.check(monitor, heartbeat, {});
});
assert.strictEqual(heartbeat.status, UP);
});
test("check() rejects when TLS certificate hostname does not match (STARTTLS)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "wr-in-f108.1e100.net",
port: 587,
smtpSecurity: "starttls",
isEnabledExpiryNotification: () => true,
handleTlsInfo: async (tlsInfo) => {
return tlsInfo;
},
};
const heartbeat = {
msg: "",
status: PENDING,
};
const regex = /does not match certificate/;
await retryExternalService(async () => {
await assert.rejects(tcpMonitor.check(monitor, heartbeat, {}), regex);
});
});
test("check() sets status to UP for XMPP server with valid certificate (STARTTLS)", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "xmpp.earth",
port: 5222,
smtpSecurity: "starttls",
isEnabledExpiryNotification: () => true,
handleTlsInfo: async (tlsInfo) => {
return tlsInfo;
},
};
const heartbeat = {
msg: "",
status: PENDING,
};
await retryExternalService(async () => {
await tcpMonitor.check(monitor, heartbeat, {});
});
assert.strictEqual(heartbeat.status, UP);
});
// TLS Alert checking tests
test("check() rejects when expecting TLS alert but connection succeeds", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
hostname: "google.com",
port: 443,
expected_tls_alert: "certificate_required",
timeout: 10,
isEnabledExpiryNotification: () => false,
getIgnoreTls: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
// Retry with backoff for external service reliability, expecting rejection
await retryExternalService(async () => {
await assert.rejects(
tcpMonitor.check(monitor, heartbeat, {}),
/Expected TLS alert 'certificate_required' but connection succeeded/
);
});
});
test("parseTlsAlertNumber() extracts alert number from error message", async () => {
const { parseTlsAlertNumber } = require("../../../server/monitor-types/tcp");
// Test various error message formats
assert.strictEqual(parseTlsAlertNumber("alert number 116"), 116);
assert.strictEqual(parseTlsAlertNumber("SSL alert number 42"), 42);
assert.strictEqual(parseTlsAlertNumber("TLS alert number 48"), 48);
assert.strictEqual(parseTlsAlertNumber("no alert here"), null);
assert.strictEqual(parseTlsAlertNumber(""), null);
});
test("getTlsAlertName() returns correct alert name for known codes", async () => {
const { getTlsAlertName } = require("../../../server/monitor-types/tcp");
assert.strictEqual(getTlsAlertName(116), "certificate_required");
assert.strictEqual(getTlsAlertName(42), "bad_certificate");
assert.strictEqual(getTlsAlertName(48), "unknown_ca");
assert.strictEqual(getTlsAlertName(40), "handshake_failure");
assert.strictEqual(getTlsAlertName(999), "unknown_alert_999");
});
});