mirror of
https://github.com/koichixD/uptime-kuma.git
synced 2026-09-04 17:23:21 +00:00
move monitoring tests to better folder
This commit is contained in:
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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]")
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user