move monitoring tests to better folder

This commit is contained in:
Frank Elsinga
2026-01-01 16:27:37 +01:00
parent b6d5dd690d
commit b91b73a4eb
6 changed files with 0 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const { GrpcKeywordMonitorType } = require("../../server/monitor-types/grpc");
const { UP, PENDING } = require("../../src/util");
const fs = require("fs");
const path = require("path");
const os = require("os");
const testProto = `
syntax = "proto3";
package test;
service TestService {
rpc Echo (EchoRequest) returns (EchoResponse);
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
`;
/**
* Create a gRPC server for testing
* @param {number} port Port to listen on
* @param {object} methodHandlers Object with method handlers
* @returns {Promise<grpc.Server>} gRPC server instance
*/
async function createTestGrpcServer(port, methodHandlers) {
// Write proto to temp file
const tmpDir = os.tmpdir();
const protoPath = path.join(tmpDir, `test-${port}.proto`);
fs.writeFileSync(protoPath, testProto);
// Load proto file
const packageDefinition = protoLoader.loadSync(protoPath, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const testPackage = protoDescriptor.test;
const server = new grpc.Server();
// Add service implementation
server.addService(testPackage.TestService.service, {
Echo: (call, callback) => {
if (methodHandlers.Echo) {
methodHandlers.Echo(call, callback);
} else {
callback(null, { message: call.request.message });
}
},
});
return new Promise((resolve, reject) => {
server.bindAsync(
`0.0.0.0:${port}`,
grpc.ServerCredentials.createInsecure(),
(err) => {
if (err) {
reject(err);
} else {
server.start();
// Clean up temp file
fs.unlinkSync(protoPath);
resolve(server);
}
}
);
});
}
describe("GrpcKeywordMonitorType", {
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
}, () => {
test("gRPC keyword found in response", async () => {
const port = 50051;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
callback(null, { message: "Hello World with SUCCESS keyword" });
}
});
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: `localhost:${port}`,
grpcProtobuf: testProto,
grpcServiceName: "test.TestService",
grpcMethod: "echo",
grpcBody: JSON.stringify({ message: "test" }),
keyword: "SUCCESS",
invertKeyword: false,
grpcEnableTls: false,
isInvertKeyword: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await grpcMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
assert.ok(heartbeat.msg.includes("SUCCESS"));
assert.ok(heartbeat.msg.includes("is"));
} finally {
server.forceShutdown();
}
});
test("gRPC keyword not found in response", async () => {
const port = 50052;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
callback(null, { message: "Hello World without the expected keyword" });
}
});
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: `localhost:${port}`,
grpcProtobuf: testProto,
grpcServiceName: "test.TestService",
grpcMethod: "echo",
grpcBody: JSON.stringify({ message: "test" }),
keyword: "MISSING",
invertKeyword: false,
grpcEnableTls: false,
isInvertKeyword: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
grpcMonitor.check(monitor, heartbeat, {}),
(err) => {
assert.ok(err.message.includes("MISSING"));
assert.ok(err.message.includes("not"));
return true;
}
);
} finally {
server.forceShutdown();
}
});
test("gRPC inverted keyword - keyword present (should fail)", async () => {
const port = 50053;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
callback(null, { message: "Response with ERROR keyword" });
}
});
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: `localhost:${port}`,
grpcProtobuf: testProto,
grpcServiceName: "test.TestService",
grpcMethod: "echo",
grpcBody: JSON.stringify({ message: "test" }),
keyword: "ERROR",
invertKeyword: true,
grpcEnableTls: false,
isInvertKeyword: () => true,
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
grpcMonitor.check(monitor, heartbeat, {}),
(err) => {
assert.ok(err.message.includes("ERROR"));
assert.ok(err.message.includes("present"));
return true;
}
);
} finally {
server.forceShutdown();
}
});
test("gRPC inverted keyword - keyword not present (should pass)", async () => {
const port = 50054;
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
callback(null, { message: "Response without error keyword" });
}
});
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: `localhost:${port}`,
grpcProtobuf: testProto,
grpcServiceName: "test.TestService",
grpcMethod: "echo",
grpcBody: JSON.stringify({ message: "test" }),
keyword: "ERROR",
invertKeyword: true,
grpcEnableTls: false,
isInvertKeyword: () => true,
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await grpcMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
assert.ok(heartbeat.msg.includes("ERROR"));
assert.ok(heartbeat.msg.includes("not"));
} finally {
server.forceShutdown();
}
});
test("gRPC connection failure", async () => {
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: "localhost:50099",
grpcProtobuf: testProto,
grpcServiceName: "test.TestService",
grpcMethod: "echo",
grpcBody: JSON.stringify({ message: "test" }),
keyword: "SUCCESS",
invertKeyword: false,
grpcEnableTls: false,
isInvertKeyword: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
await assert.rejects(
grpcMonitor.check(monitor, heartbeat, {}),
(err) => {
// Should fail with connection error
return true;
}
);
});
test("gRPC response truncation for long messages", async () => {
const port = 50055;
const longMessage = "A".repeat(100) + " with SUCCESS keyword";
const server = await createTestGrpcServer(port, {
Echo: (call, callback) => {
callback(null, { message: longMessage });
}
});
const grpcMonitor = new GrpcKeywordMonitorType();
const monitor = {
grpcUrl: `localhost:${port}`,
grpcProtobuf: testProto,
grpcServiceName: "test.TestService",
grpcMethod: "echo",
grpcBody: JSON.stringify({ message: "test" }),
keyword: "MISSING",
invertKeyword: false,
grpcEnableTls: false,
isInvertKeyword: () => false,
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
grpcMonitor.check(monitor, heartbeat, {}),
(err) => {
// Should truncate message to 50 characters with "..."
assert.ok(err.message.includes("..."));
return true;
}
);
} finally {
server.forceShutdown();
}
});
});
+160
View File
@@ -0,0 +1,160 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { HiveMQContainer } = require("@testcontainers/hivemq");
const mqtt = require("mqtt");
const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
const { UP, PENDING } = require("../../src/util");
/**
* Runs an MQTT test with the
* @param {string} mqttSuccessMessage the message that the monitor expects
* @param {null|"keyword"|"json-query"} mqttCheckType the type of check we perform
* @param {string} receivedMessage what message is received from the mqtt channel
* @param {string} monitorTopic which MQTT topic is monitored (wildcards are allowed)
* @param {string} publishTopic to which MQTT topic the message is sent
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
*/
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, monitorTopic = "test", publishTopic = "test") {
const hiveMQContainer = await new HiveMQContainer().start();
const connectionString = hiveMQContainer.getConnectionString();
const mqttMonitorType = new MqttMonitorType();
const monitor = {
jsonPath: "firstProp", // always return firstProp for the json-query monitor
hostname: connectionString.split(":", 2).join(":"),
mqttTopic: monitorTopic,
port: connectionString.split(":")[2],
mqttUsername: null,
mqttPassword: null,
mqttWebsocketPath: null, // for WebSocket connections
interval: 20, // controls the timeout
mqttSuccessMessage: mqttSuccessMessage, // for keywords
expectedValue: mqttSuccessMessage, // for json-query
mqttCheckType: mqttCheckType,
};
const heartbeat = {
msg: "",
status: PENDING,
};
const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString());
testMqttClient.on("connect", () => {
testMqttClient.subscribe(monitorTopic, (error) => {
if (!error) {
testMqttClient.publish(publishTopic, receivedMessage);
}
});
});
try {
await mqttMonitorType.check(monitor, heartbeat, {});
} finally {
testMqttClient.end();
hiveMQContainer.stop();
}
return heartbeat;
}
describe("MqttMonitorType", {
concurrency: 4,
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
}, () => {
test("valid keywords (type=default)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
});
test("valid nested topic", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/b/c", "a/b/c");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
});
test("valid nested topic (with special chars)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/'/$/./*/%", "a/'/$/./*/%");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
});
test("valid wildcard topic (with #)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/#", "a/b/c");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
});
test("valid wildcard topic (with +)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c", "a/b/c");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
});
test("valid wildcard topic (with + and #)", async () => {
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c/#", "a/b/c/d/e");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: a/b/c/d/e; Message: -> KEYWORD <-");
});
test("invalid topic", async () => {
await assert.rejects(
testMqtt("keyword will not be checked anyway", null, "message", "x/y/z", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
test("invalid wildcard topic (with #)", async () => {
await assert.rejects(
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
test("invalid wildcard topic (with +)", async () => {
await assert.rejects(
testMqtt("", null, "message", "x/+/z", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
test("valid keywords (type=keyword)", async () => {
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
});
test("invalid keywords (type=default)", async () => {
await assert.rejects(
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
);
});
test("invalid keyword (type=keyword)", async () => {
await assert.rejects(
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
);
});
test("valid json-query", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
});
test("invalid (because query fails) json-query", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[not_relevant]", "json-query", "{}"),
new Error("Message received but value is not equal to expected value, value was: [undefined]"),
);
});
test("invalid (because successMessage fails) json-query", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
new Error("Message received but value is not equal to expected value, value was: [present]")
);
});
});
+302
View File
@@ -0,0 +1,302 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { MSSQLServerContainer } = require("@testcontainers/mssqlserver");
const { MssqlMonitorType } = require("../../server/monitor-types/mssql");
const { UP, PENDING } = require("../../src/util");
/**
* Helper function to create and start a MSSQL container
* @returns {Promise<MSSQLServerContainer>} The started MSSQL container
*/
async function createAndStartMSSQLContainer() {
return await new MSSQLServerContainer(
"mcr.microsoft.com/mssql/server:2022-latest"
)
.acceptLicense()
// The default timeout of 30 seconds might not be enough for the container to start
.withStartupTimeout(60000)
.start();
}
describe(
"MSSQL Single Node",
{
skip:
!!process.env.CI &&
(process.platform !== "linux" || process.arch !== "x64"),
},
() => {
test("MSSQL is running", async () => {
let mssqlContainer;
try {
mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
conditions: "[]",
};
const heartbeat = {
msg: "",
status: PENDING,
};
await mssqlMonitor.check(monitor, heartbeat, {});
assert.strictEqual(
heartbeat.status,
UP,
`Expected status ${UP} but got ${heartbeat.status}`
);
} catch (error) {
console.error("Test failed with error:", error.message);
console.error("Error stack:", error.stack);
if (mssqlContainer) {
console.error("Container ID:", mssqlContainer.getId());
console.error(
"Container logs:",
await mssqlContainer.logs()
);
}
throw error;
} finally {
if (mssqlContainer) {
console.log("Stopping MSSQL container...");
await mssqlContainer.stop();
}
}
});
test("MSSQL with custom query returning single value", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
databaseQuery: "SELECT 42",
conditions: "[]",
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await mssqlMonitor.check(monitor, heartbeat, {});
assert.strictEqual(
heartbeat.status,
UP,
`Expected status ${UP} but got ${heartbeat.status}`
);
} finally {
await mssqlContainer.stop();
}
});
test("MSSQL with custom query and condition that passes", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
databaseQuery: "SELECT 42 as value",
conditions: JSON.stringify([
{
type: "expression",
andOr: "and",
variable: "result",
operator: "equals",
value: "42",
},
]),
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await mssqlMonitor.check(monitor, heartbeat, {});
assert.strictEqual(
heartbeat.status,
UP,
`Expected status ${UP} but got ${heartbeat.status}`
);
} finally {
await mssqlContainer.stop();
}
});
test("MSSQL with custom query and condition that fails", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
databaseQuery: "SELECT 99 as value",
conditions: JSON.stringify([
{
type: "expression",
andOr: "and",
variable: "result",
operator: "equals",
value: "42",
},
]),
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
mssqlMonitor.check(monitor, heartbeat, {}),
new Error(
"Query result did not meet the specified conditions (99)"
)
);
assert.strictEqual(
heartbeat.status,
PENDING,
`Expected status should not be ${heartbeat.status}`
);
} finally {
await mssqlContainer.stop();
}
});
test("MSSQL query returns no results", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
databaseQuery: "SELECT 1 WHERE 1 = 0",
conditions: "[]",
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
mssqlMonitor.check(monitor, heartbeat, {}),
new Error(
"Database connection/query failed: Query returned no results"
)
);
assert.strictEqual(
heartbeat.status,
PENDING,
`Expected status should not be ${heartbeat.status}`
);
} finally {
await mssqlContainer.stop();
}
});
test("MSSQL query returns multiple rows", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
databaseQuery: "SELECT 1 UNION ALL SELECT 2",
conditions: "[]",
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
mssqlMonitor.check(monitor, heartbeat, {}),
new Error(
"Database connection/query failed: Multiple values were found, expected only one value"
)
);
assert.strictEqual(
heartbeat.status,
PENDING,
`Expected status should not be ${heartbeat.status}`
);
} finally {
await mssqlContainer.stop();
}
});
test("MSSQL query returns multiple columns", async () => {
const mssqlContainer = await createAndStartMSSQLContainer();
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
mssqlContainer.getConnectionUri(false),
databaseQuery: "SELECT 1 AS col1, 2 AS col2",
conditions: "[]",
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await assert.rejects(
mssqlMonitor.check(monitor, heartbeat, {}),
new Error(
"Database connection/query failed: Multiple columns were found, expected only one value"
)
);
assert.strictEqual(
heartbeat.status,
PENDING,
`Expected status should not be ${heartbeat.status}`
);
} finally {
await mssqlContainer.stop();
}
});
test("MSSQL is not running", async () => {
const mssqlMonitor = new MssqlMonitorType();
const monitor = {
databaseConnectionString:
"Server=localhost,15433;Database=master;User Id=Fail;Password=Fail;Encrypt=false",
conditions: "[]",
};
const heartbeat = {
msg: "",
status: PENDING,
};
await assert.rejects(
mssqlMonitor.check(monitor, heartbeat, {}),
new Error(
"Database connection/query failed: Failed to connect to localhost:15433 - Could not connect (sequence)"
)
);
assert.notStrictEqual(
heartbeat.status,
UP,
`Expected status should not be ${heartbeat.status}`
);
});
}
);
@@ -0,0 +1,60 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
const { PostgresMonitorType } = require("../../server/monitor-types/postgres");
const { UP, PENDING } = require("../../src/util");
describe(
"Postgres Single Node",
{
skip:
!!process.env.CI &&
(process.platform !== "linux" || process.arch !== "x64"),
},
() => {
test("Postgres is running", async () => {
// The default timeout of 30 seconds might not be enough for the container to start
const postgresContainer = await new PostgreSqlContainer(
"postgres:latest"
)
.withStartupTimeout(60000)
.start();
const postgresMonitor = new PostgresMonitorType();
const monitor = {
databaseConnectionString: postgresContainer.getConnectionUri(),
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await postgresMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
} finally {
postgresContainer.stop();
}
});
test("Postgres is not running", async () => {
const postgresMonitor = new PostgresMonitorType();
const monitor = {
databaseConnectionString: "http://localhost:15432",
};
const heartbeat = {
msg: "",
status: PENDING,
};
// regex match any string
const regex = /.+/;
await assert.rejects(
postgresMonitor.check(monitor, heartbeat, {}),
regex
);
});
}
);
@@ -0,0 +1,58 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { RabbitMQContainer } = require("@testcontainers/rabbitmq");
const { RabbitMqMonitorType } = require("../../server/monitor-types/rabbitmq");
const { UP, PENDING } = require("../../src/util");
describe("RabbitMQ Single Node", {
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
}, () => {
test("RabbitMQ is running", async () => {
// The default timeout of 30 seconds might not be enough for the container to start
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
const rabbitMQMonitor = new RabbitMqMonitorType();
const connectionString = `http://${rabbitMQContainer.getHost()}:${rabbitMQContainer.getMappedPort(15672)}`;
const monitor = {
rabbitmqNodes: JSON.stringify([ connectionString ]),
rabbitmqUsername: "guest",
rabbitmqPassword: "guest",
};
const heartbeat = {
msg: "",
status: PENDING,
};
try {
await rabbitMQMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "OK");
} finally {
rabbitMQContainer.stop();
}
});
test("RabbitMQ is not running", async () => {
const rabbitMQMonitor = new RabbitMqMonitorType();
const monitor = {
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),
rabbitmqUsername: "rabbitmqUser",
rabbitmqPassword: "rabbitmqPass",
};
const heartbeat = {
msg: "",
status: PENDING,
};
// regex match any string
const regex = /.+/;
await assert.rejects(
rabbitMQMonitor.check(monitor, heartbeat, {}),
regex
);
});
});
+210
View File
@@ -0,0 +1,210 @@
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");
/**
* Test suite for TCP Monitor functionality
* This test suite checks the behavior of the TCPMonitorType class
* under different network connection scenarios.
*/
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 case to verify TCP monitor works when a server is running
* Checks that the monitor correctly identifies an active TCP server
*/
test("TCP server is running", 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 case to verify TCP monitor handles non-running servers
* Checks that the monitor correctly identifies an inactive TCP server
*/
test("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 case to verify TCP monitor handles servers with expired or invalid TLS certificates
* Checks that the monitor correctly identifies TLS certificate issues
*/
test("TCP server with expired or invalid TLS certificate", async t => {
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 assert.rejects(
tcpMonitor.check(monitor, heartbeat, {}),
regex
);
});
test("TCP server with valid TLS certificate (SSL)", async t => {
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 tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
});
test("TCP server with valid TLS certificate (STARTTLS)", async t => {
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 tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
});
test("TCP server with valid but name mismatching TLS certificate (STARTTLS)", async t => {
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 assert.rejects(
tcpMonitor.check(monitor, heartbeat, {}),
regex
);
});
test("XMPP server with valid certificate (STARTTLS)", async t => {
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 tcpMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
});
});