mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-05-17 08:26:56 +03:00
chore: enable formatting over the entire codebase in CI (#6655)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -6,8 +6,9 @@ Create a test file in this directory with the name `*.js`.
|
||||
|
||||
> [!TIP]
|
||||
> Writing great tests is hard.
|
||||
>
|
||||
>
|
||||
> You can make our live much simpler by following this guidance:
|
||||
>
|
||||
> - Use `describe()` to group related tests
|
||||
> - Use `test()` for individual test cases
|
||||
> - One test per scenario
|
||||
@@ -21,9 +22,9 @@ const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
|
||||
describe("Feature Name", () => {
|
||||
test("function() returns expected value when condition is met", () => {
|
||||
assert.strictEqual(1, 1);
|
||||
});
|
||||
test("function() returns expected value when condition is met", () => {
|
||||
assert.strictEqual(1, 1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function getStartEnd(line, key) {
|
||||
if (start === -1) {
|
||||
start = 0;
|
||||
}
|
||||
return [ start, start + key.length ];
|
||||
return [start, start + key.length];
|
||||
}
|
||||
|
||||
describe("Check Translations", () => {
|
||||
@@ -40,14 +40,14 @@ describe("Check Translations", () => {
|
||||
|
||||
// this is a resonably crude check, you can get around this trivially
|
||||
/// this check is just to save on maintainer energy to explain this on every review ^^
|
||||
const translationRegex = /\$t\(['"](?<key1>.*?)['"]\s*[,)]|i18n-t[^>]*\s+keypath="(?<key2>[^"]+)"/gd;
|
||||
const translationRegex = /\$t\(['"](?<key1>.*?)['"]\s*[,)]|i18n-t[^>]*\s+keypath="(?<key2>[^"]+)"/dg;
|
||||
|
||||
// detect server-side TranslatableError usage: new TranslatableError("key")
|
||||
const translatableErrorRegex = /new\s+TranslatableError\(\s*['"](?<key3>[^'"]+)['"]\s*\)/g;
|
||||
|
||||
const missingKeys = [];
|
||||
|
||||
const roots = [ "src", "server" ];
|
||||
const roots = ["src", "server"];
|
||||
|
||||
for (const root of roots) {
|
||||
for (const filePath of walk(root)) {
|
||||
@@ -59,7 +59,7 @@ describe("Check Translations", () => {
|
||||
while ((match = translationRegex.exec(line)) !== null) {
|
||||
const key = match.groups.key1 || match.groups.key2;
|
||||
if (key && !enTranslations[key]) {
|
||||
const [ start, end ] = getStartEnd(line, key);
|
||||
const [start, end] = getStartEnd(line, key);
|
||||
missingKeys.push({
|
||||
filePath,
|
||||
lineNum: lineNum + 1,
|
||||
@@ -76,7 +76,7 @@ describe("Check Translations", () => {
|
||||
while ((m = translatableErrorRegex.exec(line)) !== null) {
|
||||
const key3 = m.groups.key3;
|
||||
if (key3 && !enTranslations[key3]) {
|
||||
const [ start, end ] = getStartEnd(line, key3);
|
||||
const [start, end] = getStartEnd(line, key3);
|
||||
missingKeys.push({
|
||||
filePath,
|
||||
lineNum: lineNum + 1,
|
||||
@@ -103,10 +103,11 @@ describe("Check Translations", () => {
|
||||
report += `\n | ${arrow} unrecognized translation key`;
|
||||
report += "\n |";
|
||||
report += `\n = note: please register the translation key '${key}' in en.json so that our awesome team of translators can translate them`;
|
||||
report += "\n = tip: if you want to contribute translations, please visit https://weblate.kuma.pet\n";
|
||||
report +=
|
||||
"\n = tip: if you want to contribute translations, please visit https://weblate.kuma.pet\n";
|
||||
});
|
||||
report += "\n===============================";
|
||||
const fileCount = new Set(missingKeys.map(item => item.filePath)).size;
|
||||
const fileCount = new Set(missingKeys.map((item) => item.filePath)).size;
|
||||
report += `\nFound a total of ${missingKeys.length} missing keys in ${fileCount} files.`;
|
||||
assert.fail(report);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { ConditionExpressionGroup, ConditionExpression, LOGICAL } = require("../../../server/monitor-conditions/expression.js");
|
||||
const {
|
||||
ConditionExpressionGroup,
|
||||
ConditionExpression,
|
||||
LOGICAL,
|
||||
} = require("../../../server/monitor-conditions/expression.js");
|
||||
const { evaluateExpressionGroup, evaluateExpression } = require("../../../server/monitor-conditions/evaluator.js");
|
||||
|
||||
describe("Expression Evaluator", () => {
|
||||
|
||||
@@ -6,37 +6,37 @@ test("Test ConditionExpressionGroup.fromMonitor", async (t) => {
|
||||
const monitor = {
|
||||
conditions: JSON.stringify([
|
||||
{
|
||||
"type": "expression",
|
||||
"andOr": "and",
|
||||
"operator": "contains",
|
||||
"value": "foo",
|
||||
"variable": "record"
|
||||
type: "expression",
|
||||
andOr: "and",
|
||||
operator: "contains",
|
||||
value: "foo",
|
||||
variable: "record",
|
||||
},
|
||||
{
|
||||
"type": "group",
|
||||
"andOr": "and",
|
||||
"children": [
|
||||
type: "group",
|
||||
andOr: "and",
|
||||
children: [
|
||||
{
|
||||
"type": "expression",
|
||||
"andOr": "and",
|
||||
"operator": "contains",
|
||||
"value": "bar",
|
||||
"variable": "record"
|
||||
type: "expression",
|
||||
andOr: "and",
|
||||
operator: "contains",
|
||||
value: "bar",
|
||||
variable: "record",
|
||||
},
|
||||
{
|
||||
"type": "group",
|
||||
"andOr": "and",
|
||||
"children": [
|
||||
type: "group",
|
||||
andOr: "and",
|
||||
children: [
|
||||
{
|
||||
"type": "expression",
|
||||
"andOr": "and",
|
||||
"operator": "contains",
|
||||
"value": "car",
|
||||
"variable": "record"
|
||||
}
|
||||
]
|
||||
type: "expression",
|
||||
andOr: "and",
|
||||
operator: "contains",
|
||||
value: "car",
|
||||
variable: "record",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { operatorMap, OP_CONTAINS, OP_NOT_CONTAINS, OP_LT, OP_GT, OP_LTE, OP_GTE, OP_STR_EQUALS, OP_STR_NOT_EQUALS, OP_NUM_EQUALS, OP_NUM_NOT_EQUALS, OP_STARTS_WITH, OP_ENDS_WITH, OP_NOT_STARTS_WITH, OP_NOT_ENDS_WITH } = require("../../../server/monitor-conditions/operators.js");
|
||||
const {
|
||||
operatorMap,
|
||||
OP_CONTAINS,
|
||||
OP_NOT_CONTAINS,
|
||||
OP_LT,
|
||||
OP_GT,
|
||||
OP_LTE,
|
||||
OP_GTE,
|
||||
OP_STR_EQUALS,
|
||||
OP_STR_NOT_EQUALS,
|
||||
OP_NUM_EQUALS,
|
||||
OP_NUM_NOT_EQUALS,
|
||||
OP_STARTS_WITH,
|
||||
OP_ENDS_WITH,
|
||||
OP_NOT_STARTS_WITH,
|
||||
OP_NOT_ENDS_WITH,
|
||||
} = require("../../../server/monitor-conditions/operators.js");
|
||||
|
||||
describe("Expression Operators", () => {
|
||||
test("StringEqualsOperator returns true for identical strings and false otherwise", () => {
|
||||
@@ -25,8 +41,8 @@ describe("Expression Operators", () => {
|
||||
|
||||
test("ContainsOperator returns true when array contains element", () => {
|
||||
const op = operatorMap.get(OP_CONTAINS);
|
||||
assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
|
||||
assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
|
||||
assert.strictEqual(true, op.test(["example.org"], "example.org"));
|
||||
assert.strictEqual(false, op.test(["example.org"], "example.com"));
|
||||
});
|
||||
|
||||
test("NotContainsOperator returns true when scalar does not contain substring", () => {
|
||||
@@ -37,8 +53,8 @@ describe("Expression Operators", () => {
|
||||
|
||||
test("NotContainsOperator returns true when array does not contain element", () => {
|
||||
const op = operatorMap.get(OP_NOT_CONTAINS);
|
||||
assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
|
||||
assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
|
||||
assert.strictEqual(true, op.test(["example.org"], "example.com"));
|
||||
assert.strictEqual(false, op.test(["example.org"], "example.org"));
|
||||
});
|
||||
|
||||
test("StartsWithOperator returns true when string starts with prefix", () => {
|
||||
|
||||
@@ -44,10 +44,7 @@ describe("GameDig Monitor", () => {
|
||||
const gamedigMonitor = new GameDigMonitorType();
|
||||
|
||||
mock.method(GameDig, "query", async (options) => {
|
||||
assert.ok(
|
||||
net.isIP(options.host) !== 0,
|
||||
`Expected IP address, got ${options.host}`
|
||||
);
|
||||
assert.ok(net.isIP(options.host) !== 0, `Expected IP address, got ${options.host}`);
|
||||
return {
|
||||
name: "Test Server",
|
||||
ping: 50,
|
||||
@@ -234,10 +231,7 @@ describe("GameDig Monitor", () => {
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
gamedigMonitor.check(monitor, heartbeat, {}),
|
||||
/Error/
|
||||
);
|
||||
await assert.rejects(gamedigMonitor.check(monitor, heartbeat, {}), /Error/);
|
||||
});
|
||||
|
||||
test("resolveHostname() returns IP address when given valid hostname", async () => {
|
||||
@@ -245,10 +239,7 @@ describe("GameDig Monitor", () => {
|
||||
|
||||
const resolvedIP = await gamedigMonitor.resolveHostname("localhost");
|
||||
|
||||
assert.ok(
|
||||
net.isIP(resolvedIP) !== 0,
|
||||
`Expected valid IP address, got ${resolvedIP}`
|
||||
);
|
||||
assert.ok(net.isIP(resolvedIP) !== 0, `Expected valid IP address, got ${resolvedIP}`);
|
||||
});
|
||||
|
||||
test("resolveHostname() rejects when DNS resolution fails for invalid hostname", async () => {
|
||||
|
||||
@@ -43,7 +43,7 @@ async function createTestGrpcServer(port, methodHandlers) {
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
oneofs: true,
|
||||
});
|
||||
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
||||
const testPackage = protoDescriptor.test;
|
||||
@@ -62,245 +62,233 @@ async function createTestGrpcServer(port, methodHandlers) {
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
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("check() sets status to UP when keyword is found in response", async () => {
|
||||
const port = 50051;
|
||||
const server = await createTestGrpcServer(port, {
|
||||
Echo: (call, callback) => {
|
||||
callback(null, { message: "Hello World with SUCCESS keyword" });
|
||||
describe(
|
||||
"GrpcKeywordMonitorType",
|
||||
{
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("check() sets status to UP when keyword is 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();
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
test("check() rejects when keyword is 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 heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
test("check() rejects when keyword is 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) => {
|
||||
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("check() rejects when inverted keyword is present in response", async () => {
|
||||
const port = 50053;
|
||||
const server = await createTestGrpcServer(port, {
|
||||
Echo: (call, callback) => {
|
||||
callback(null, { message: "Response with ERROR keyword" });
|
||||
});
|
||||
} finally {
|
||||
server.forceShutdown();
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
test("check() rejects when inverted keyword is present in response", async () => {
|
||||
const port = 50053;
|
||||
const server = await createTestGrpcServer(port, {
|
||||
Echo: (call, callback) => {
|
||||
callback(null, { message: "Response with ERROR keyword" });
|
||||
},
|
||||
});
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
grpcMonitor.check(monitor, heartbeat, {}),
|
||||
(err) => {
|
||||
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("check() sets status to UP when inverted keyword is not present in response", async () => {
|
||||
const port = 50054;
|
||||
const server = await createTestGrpcServer(port, {
|
||||
Echo: (call, callback) => {
|
||||
callback(null, { message: "Response without error keyword" });
|
||||
});
|
||||
} finally {
|
||||
server.forceShutdown();
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
test("check() sets status to UP when inverted keyword is not present in response", async () => {
|
||||
const port = 50054;
|
||||
const server = await createTestGrpcServer(port, {
|
||||
Echo: (call, callback) => {
|
||||
callback(null, { message: "Response without error keyword" });
|
||||
},
|
||||
});
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
test("check() rejects when gRPC server is unreachable", 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,
|
||||
};
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
test("check() rejects when gRPC server is unreachable", 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,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
grpcMonitor.check(monitor, heartbeat, {}),
|
||||
(err) => {
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(grpcMonitor.check(monitor, heartbeat, {}), (err) => {
|
||||
// Should fail with connection error
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("check() truncates long response messages in error output", 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,
|
||||
};
|
||||
test("check() truncates long response messages in error output", async () => {
|
||||
const port = 50055;
|
||||
const longMessage = "A".repeat(100) + " with SUCCESS keyword";
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
const server = await createTestGrpcServer(port, {
|
||||
Echo: (call, callback) => {
|
||||
callback(null, { message: longMessage });
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
grpcMonitor.check(monitor, heartbeat, {}),
|
||||
(err) => {
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
server.forceShutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -15,7 +15,14 @@ const { UP, PENDING } = require("../../../src/util");
|
||||
* @param {string|null} conditions JSON string of conditions or null
|
||||
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
|
||||
*/
|
||||
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, monitorTopic = "test", publishTopic = "test", conditions = null) {
|
||||
async function testMqtt(
|
||||
mqttSuccessMessage,
|
||||
mqttCheckType,
|
||||
receivedMessage,
|
||||
monitorTopic = "test",
|
||||
publishTopic = "test",
|
||||
conditions = null
|
||||
) {
|
||||
const hiveMQContainer = await new HiveMQContainer().start();
|
||||
const connectionString = hiveMQContainer.getConnectionString();
|
||||
const mqttMonitorType = new MqttMonitorType();
|
||||
@@ -56,170 +63,174 @@ async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, moni
|
||||
return heartbeat;
|
||||
}
|
||||
|
||||
describe("MqttMonitorType", {
|
||||
concurrency: 4,
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
|
||||
}, () => {
|
||||
test("check() sets status to UP when keyword is found in message (type=default)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
describe(
|
||||
"MqttMonitorType",
|
||||
{
|
||||
concurrency: 4,
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("check() sets status to UP when keyword is found in message (type=default)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("check() sets status to UP when keyword is found in 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("check() sets status to UP when keyword is found in 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("check() sets status to UP when keyword is found in nested topic with special characters", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/'/$/./*/%", "a/'/$/./*/%");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
|
||||
});
|
||||
test("check() sets status to UP when keyword is found in nested topic with special characters", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/'/$/./*/%", "a/'/$/./*/%");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("check() sets status to UP when keyword is found using # wildcard", 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("check() sets status to UP when keyword is found using # wildcard", 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("check() sets status to UP when keyword is found using + wildcard", 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("check() sets status to UP when keyword is found using + wildcard", 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("check() sets status to UP when keyword is found using + and # wildcards", 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("check() sets status to UP when keyword is found using + and # wildcards", 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("check() rejects with timeout when topic does not match", 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("check() rejects with timeout when topic does not match", 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("check() rejects with timeout when # wildcard is not last character", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
|
||||
new Error("Timeout, Message not received"),
|
||||
);
|
||||
});
|
||||
test("check() rejects with timeout when # wildcard is not last character", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
|
||||
new Error("Timeout, Message not received")
|
||||
);
|
||||
});
|
||||
|
||||
test("check() rejects with timeout when + wildcard topic does not match", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "message", "x/+/z", "a/b/c"),
|
||||
new Error("Timeout, Message not received"),
|
||||
);
|
||||
});
|
||||
test("check() rejects with timeout when + wildcard topic does not match", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "message", "x/+/z", "a/b/c"),
|
||||
new Error("Timeout, Message not received")
|
||||
);
|
||||
});
|
||||
|
||||
test("check() sets status to UP when keyword is found in message (type=keyword)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
test("check() sets status to UP when keyword is found in message (type=keyword)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("check() rejects when keyword is not found in message (type=default)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
|
||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
||||
);
|
||||
});
|
||||
test("check() rejects when keyword is not found in message (type=default)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
|
||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-")
|
||||
);
|
||||
});
|
||||
|
||||
test("check() rejects when keyword is not found in message (type=keyword)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
|
||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
||||
);
|
||||
});
|
||||
test("check() rejects when keyword is not found in message (type=keyword)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
|
||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-")
|
||||
);
|
||||
});
|
||||
|
||||
test("check() sets status to UP when json-query finds expected value", 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("check() sets status to UP when json-query finds expected value", 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("check() rejects when json-query path returns undefined", 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("check() rejects when json-query path returns undefined", 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("check() rejects when json-query value does not match expected value", 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]")
|
||||
);
|
||||
});
|
||||
test("check() rejects when json-query value does not match expected value", 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]")
|
||||
);
|
||||
});
|
||||
|
||||
// Conditions system tests
|
||||
test("check() sets status to UP when message condition matches (contains)", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "message",
|
||||
operator: "contains",
|
||||
value: "KEYWORD"
|
||||
}
|
||||
]);
|
||||
const heartbeat = await testMqtt("", null, "-> KEYWORD <-", "test", "test", conditions);
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
// Conditions system tests
|
||||
test("check() sets status to UP when message condition matches (contains)", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "message",
|
||||
operator: "contains",
|
||||
value: "KEYWORD",
|
||||
},
|
||||
]);
|
||||
const heartbeat = await testMqtt("", null, "-> KEYWORD <-", "test", "test", conditions);
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("check() sets status to UP when topic condition matches (equals)", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "topic",
|
||||
operator: "equals",
|
||||
value: "sensors/temp"
|
||||
}
|
||||
]);
|
||||
const heartbeat = await testMqtt("", null, "any message", "sensors/temp", "sensors/temp", conditions);
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
});
|
||||
test("check() sets status to UP when topic condition matches (equals)", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "topic",
|
||||
operator: "equals",
|
||||
value: "sensors/temp",
|
||||
},
|
||||
]);
|
||||
const heartbeat = await testMqtt("", null, "any message", "sensors/temp", "sensors/temp", conditions);
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
});
|
||||
|
||||
test("check() rejects when message condition does not match", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "message",
|
||||
operator: "contains",
|
||||
value: "EXPECTED"
|
||||
}
|
||||
]);
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "actual message without keyword", "test", "test", conditions),
|
||||
new Error("Conditions not met - Topic: test; Message: actual message without keyword")
|
||||
);
|
||||
});
|
||||
test("check() rejects when message condition does not match", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "message",
|
||||
operator: "contains",
|
||||
value: "EXPECTED",
|
||||
},
|
||||
]);
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "actual message without keyword", "test", "test", conditions),
|
||||
new Error("Conditions not met - Topic: test; Message: actual message without keyword")
|
||||
);
|
||||
});
|
||||
|
||||
test("check() sets status to UP with multiple conditions (AND)", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "topic",
|
||||
operator: "equals",
|
||||
value: "test"
|
||||
},
|
||||
{
|
||||
type: "expression",
|
||||
variable: "message",
|
||||
operator: "contains",
|
||||
value: "success",
|
||||
andOr: "and"
|
||||
}
|
||||
]);
|
||||
const heartbeat = await testMqtt("", null, "operation success", "test", "test", conditions);
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
});
|
||||
});
|
||||
test("check() sets status to UP with multiple conditions (AND)", async () => {
|
||||
const conditions = JSON.stringify([
|
||||
{
|
||||
type: "expression",
|
||||
variable: "topic",
|
||||
operator: "equals",
|
||||
value: "test",
|
||||
},
|
||||
{
|
||||
type: "expression",
|
||||
variable: "message",
|
||||
operator: "contains",
|
||||
value: "success",
|
||||
andOr: "and",
|
||||
},
|
||||
]);
|
||||
const heartbeat = await testMqtt("", null, "operation success", "test", "test", conditions);
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -9,9 +9,7 @@ const { UP, PENDING } = require("../../../src/util");
|
||||
* @returns {Promise<{container: MSSQLServerContainer, connectionString: string}>} The started container and connection string
|
||||
*/
|
||||
async function createAndStartMSSQLContainer() {
|
||||
const container = await new MSSQLServerContainer(
|
||||
"mcr.microsoft.com/mssql/server:2022-latest"
|
||||
)
|
||||
const container = 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)
|
||||
@@ -19,16 +17,14 @@ async function createAndStartMSSQLContainer() {
|
||||
|
||||
return {
|
||||
container,
|
||||
connectionString: container.getConnectionUri(false)
|
||||
connectionString: container.getConnectionUri(false),
|
||||
};
|
||||
}
|
||||
|
||||
describe(
|
||||
"MSSQL Monitor",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("check() sets status to UP when MSSQL server is reachable", async () => {
|
||||
@@ -47,11 +43,7 @@ describe(
|
||||
|
||||
try {
|
||||
await mssqlMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(
|
||||
heartbeat.status,
|
||||
UP,
|
||||
`Expected status ${UP} but got ${heartbeat.status}`
|
||||
);
|
||||
assert.strictEqual(heartbeat.status, UP, `Expected status ${UP} but got ${heartbeat.status}`);
|
||||
} finally {
|
||||
await container.stop();
|
||||
}
|
||||
@@ -76,11 +68,7 @@ describe(
|
||||
"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}`
|
||||
);
|
||||
assert.notStrictEqual(heartbeat.status, UP, `Expected status should not be ${heartbeat.status}`);
|
||||
});
|
||||
|
||||
test("check() sets status to UP when custom query returns single value", async () => {
|
||||
@@ -100,11 +88,7 @@ describe(
|
||||
|
||||
try {
|
||||
await mssqlMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(
|
||||
heartbeat.status,
|
||||
UP,
|
||||
`Expected status ${UP} but got ${heartbeat.status}`
|
||||
);
|
||||
assert.strictEqual(heartbeat.status, UP, `Expected status ${UP} but got ${heartbeat.status}`);
|
||||
} finally {
|
||||
await container.stop();
|
||||
}
|
||||
@@ -135,11 +119,7 @@ describe(
|
||||
|
||||
try {
|
||||
await mssqlMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(
|
||||
heartbeat.status,
|
||||
UP,
|
||||
`Expected status ${UP} but got ${heartbeat.status}`
|
||||
);
|
||||
assert.strictEqual(heartbeat.status, UP, `Expected status ${UP} but got ${heartbeat.status}`);
|
||||
} finally {
|
||||
await container.stop();
|
||||
}
|
||||
@@ -171,15 +151,9 @@ describe(
|
||||
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}`
|
||||
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 container.stop();
|
||||
}
|
||||
@@ -211,15 +185,9 @@ describe(
|
||||
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}`
|
||||
new Error("Database connection/query failed: Query returned no results")
|
||||
);
|
||||
assert.strictEqual(heartbeat.status, PENDING, `Expected status should not be ${heartbeat.status}`);
|
||||
} finally {
|
||||
await container.stop();
|
||||
}
|
||||
@@ -251,15 +219,9 @@ describe(
|
||||
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}`
|
||||
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 container.stop();
|
||||
}
|
||||
@@ -291,15 +253,9 @@ describe(
|
||||
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}`
|
||||
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 container.stop();
|
||||
}
|
||||
|
||||
@@ -9,24 +9,20 @@ const { UP, PENDING } = require("../../../src/util");
|
||||
* @returns {Promise<{container: MariaDbContainer, connectionString: string}>} The started container and connection string
|
||||
*/
|
||||
async function createAndStartMariaDBContainer() {
|
||||
const container = await new MariaDbContainer("mariadb:10.11")
|
||||
.withStartupTimeout(90000)
|
||||
.start();
|
||||
const container = await new MariaDbContainer("mariadb:10.11").withStartupTimeout(90000).start();
|
||||
|
||||
const connectionString = `mysql://${container.getUsername()}:${container.getUserPassword()}@${container.getHost()}:${container.getPort()}/${container.getDatabase()}`;
|
||||
|
||||
return {
|
||||
container,
|
||||
connectionString
|
||||
connectionString,
|
||||
};
|
||||
}
|
||||
|
||||
describe(
|
||||
"MySQL/MariaDB Monitor",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("check() sets status to UP when MariaDB server is reachable", async () => {
|
||||
@@ -45,11 +41,7 @@ describe(
|
||||
|
||||
try {
|
||||
await mysqlMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(
|
||||
heartbeat.status,
|
||||
UP,
|
||||
`Expected status ${UP} but got ${heartbeat.status}`
|
||||
);
|
||||
assert.strictEqual(heartbeat.status, UP, `Expected status ${UP} but got ${heartbeat.status}`);
|
||||
} finally {
|
||||
await container.stop();
|
||||
}
|
||||
@@ -58,8 +50,7 @@ describe(
|
||||
test("check() rejects when MariaDB server is not reachable", async () => {
|
||||
const mysqlMonitor = new MysqlMonitorType();
|
||||
const monitor = {
|
||||
databaseConnectionString:
|
||||
"mysql://invalid:invalid@localhost:13306/test",
|
||||
databaseConnectionString: "mysql://invalid:invalid@localhost:13306/test",
|
||||
conditions: "[]",
|
||||
};
|
||||
|
||||
@@ -68,21 +59,14 @@ describe(
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
mysqlMonitor.check(monitor, heartbeat, {}),
|
||||
(err) => {
|
||||
assert.ok(
|
||||
err.message.includes("Database connection/query failed"),
|
||||
`Expected error message to include "Database connection/query failed" but got: ${err.message}`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
assert.notStrictEqual(
|
||||
heartbeat.status,
|
||||
UP,
|
||||
`Expected status should not be ${UP}`
|
||||
);
|
||||
await assert.rejects(mysqlMonitor.check(monitor, heartbeat, {}), (err) => {
|
||||
assert.ok(
|
||||
err.message.includes("Database connection/query failed"),
|
||||
`Expected error message to include "Database connection/query failed" but got: ${err.message}`
|
||||
);
|
||||
return true;
|
||||
});
|
||||
assert.notStrictEqual(heartbeat.status, UP, `Expected status should not be ${UP}`);
|
||||
});
|
||||
|
||||
test("check() sets status to UP when custom query result meets condition", async () => {
|
||||
@@ -110,11 +94,7 @@ describe(
|
||||
|
||||
try {
|
||||
await mysqlMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(
|
||||
heartbeat.status,
|
||||
UP,
|
||||
`Expected status ${UP} but got ${heartbeat.status}`
|
||||
);
|
||||
assert.strictEqual(heartbeat.status, UP, `Expected status ${UP} but got ${heartbeat.status}`);
|
||||
} finally {
|
||||
await container.stop();
|
||||
}
|
||||
@@ -146,15 +126,9 @@ describe(
|
||||
try {
|
||||
await assert.rejects(
|
||||
mysqlMonitor.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}`
|
||||
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 container.stop();
|
||||
}
|
||||
|
||||
@@ -7,16 +7,12 @@ const { UP, PENDING } = require("../../../src/util");
|
||||
describe(
|
||||
"Postgres Single Node",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("check() sets status to UP when Postgres server is reachable", async () => {
|
||||
// The default timeout of 30 seconds might not be enough for the container to start
|
||||
const postgresContainer = await new PostgreSqlContainer(
|
||||
"postgres:latest"
|
||||
)
|
||||
const postgresContainer = await new PostgreSqlContainer("postgres:latest")
|
||||
.withStartupTimeout(60000)
|
||||
.start();
|
||||
const postgresMonitor = new PostgresMonitorType();
|
||||
@@ -51,10 +47,7 @@ describe(
|
||||
// regex match any string
|
||||
const regex = /.+/;
|
||||
|
||||
await assert.rejects(
|
||||
postgresMonitor.check(monitor, heartbeat, {}),
|
||||
regex
|
||||
);
|
||||
await assert.rejects(postgresMonitor.check(monitor, heartbeat, {}), regex);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,101 +4,99 @@ 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("check() sets status to UP when RabbitMQ server is reachable", 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)}`;
|
||||
describe(
|
||||
"RabbitMQ Single Node",
|
||||
{
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("check() sets status to UP when RabbitMQ server is reachable", 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",
|
||||
timeout: 10,
|
||||
};
|
||||
const monitor = {
|
||||
rabbitmqNodes: JSON.stringify([connectionString]),
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
};
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
try {
|
||||
await rabbitMQMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Node is reachable and there are no alerts in the cluster");
|
||||
} finally {
|
||||
rabbitMQContainer.stop();
|
||||
}
|
||||
});
|
||||
try {
|
||||
await rabbitMQMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Node is reachable and there are no alerts in the cluster");
|
||||
} finally {
|
||||
rabbitMQContainer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test("check() rejects when RabbitMQ server is not reachable", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),
|
||||
rabbitmqUsername: "rabbitmqUser",
|
||||
rabbitmqPassword: "rabbitmqPass",
|
||||
timeout: 10,
|
||||
};
|
||||
test("check() rejects when RabbitMQ server is not reachable", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
rabbitmqNodes: JSON.stringify(["http://localhost:15672"]),
|
||||
rabbitmqUsername: "rabbitmqUser",
|
||||
rabbitmqPassword: "rabbitmqPass",
|
||||
timeout: 10,
|
||||
};
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
// regex match any string
|
||||
const regex = /.+/;
|
||||
// regex match any string
|
||||
const regex = /.+/;
|
||||
|
||||
await assert.rejects(
|
||||
rabbitMQMonitor.check(monitor, heartbeat, {}),
|
||||
regex
|
||||
);
|
||||
});
|
||||
await assert.rejects(rabbitMQMonitor.check(monitor, heartbeat, {}), regex);
|
||||
});
|
||||
|
||||
test("checkSingleNode() succeeds when node is healthy", async () => {
|
||||
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const connectionString = `http://${rabbitMQContainer.getHost()}:${rabbitMQContainer.getMappedPort(15672)}`;
|
||||
test("checkSingleNode() succeeds when node is healthy", async () => {
|
||||
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const connectionString = `http://${rabbitMQContainer.getHost()}:${rabbitMQContainer.getMappedPort(15672)}`;
|
||||
|
||||
const monitor = {
|
||||
name: "Test Monitor",
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
};
|
||||
const monitor = {
|
||||
name: "Test Monitor",
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
};
|
||||
|
||||
try {
|
||||
// Should not throw - just validates the node is healthy
|
||||
await rabbitMQMonitor.checkSingleNode(monitor, connectionString, "1/1");
|
||||
} finally {
|
||||
rabbitMQContainer.stop();
|
||||
}
|
||||
});
|
||||
try {
|
||||
// Should not throw - just validates the node is healthy
|
||||
await rabbitMQMonitor.checkSingleNode(monitor, connectionString, "1/1");
|
||||
} finally {
|
||||
rabbitMQContainer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test("checkSingleNode() throws error when node is unreachable", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
name: "Test Monitor",
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
};
|
||||
test("checkSingleNode() throws error when node is unreachable", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
name: "Test Monitor",
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
};
|
||||
|
||||
// Should reject with any error (connection refused, timeout, etc.)
|
||||
await assert.rejects(
|
||||
rabbitMQMonitor.checkSingleNode(monitor, "http://localhost:15672", "1/1"),
|
||||
Error
|
||||
);
|
||||
});
|
||||
});
|
||||
// Should reject with any error (connection refused, timeout, etc.)
|
||||
await assert.rejects(rabbitMQMonitor.checkSingleNode(monitor, "http://localhost:15672", "1/1"), Error);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
test("check() succeeds when first node is healthy", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
rabbitmqNodes: JSON.stringify([ "http://node1:15672", "http://node2:15672" ]),
|
||||
rabbitmqNodes: JSON.stringify(["http://node1:15672", "http://node2:15672"]),
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
@@ -125,7 +123,7 @@ describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
test("check() succeeds when second node is healthy after first fails", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
rabbitmqNodes: JSON.stringify([ "http://node1:15672", "http://node2:15672" ]),
|
||||
rabbitmqNodes: JSON.stringify(["http://node1:15672", "http://node2:15672"]),
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
@@ -155,11 +153,7 @@ describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
test("check() fails with consolidated error when all nodes are down", async () => {
|
||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||
const monitor = {
|
||||
rabbitmqNodes: JSON.stringify([
|
||||
"http://node1:15672",
|
||||
"http://node2:15672",
|
||||
"http://node3:15672"
|
||||
]),
|
||||
rabbitmqNodes: JSON.stringify(["http://node1:15672", "http://node2:15672", "http://node3:15672"]),
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
timeout: 10,
|
||||
@@ -177,16 +171,13 @@ describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
throw new Error(`Connection failed to node ${callCount}`);
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
rabbitMQMonitor.check(monitor, heartbeat, {}),
|
||||
(error) => {
|
||||
assert.match(error.message, /All 3 nodes failed/);
|
||||
assert.match(error.message, /Node 1:/);
|
||||
assert.match(error.message, /Node 2:/);
|
||||
assert.match(error.message, /Node 3:/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
await assert.rejects(rabbitMQMonitor.check(monitor, heartbeat, {}), (error) => {
|
||||
assert.match(error.message, /All 3 nodes failed/);
|
||||
assert.match(error.message, /Node 1:/);
|
||||
assert.match(error.message, /Node 2:/);
|
||||
assert.match(error.message, /Node 3:/);
|
||||
return true;
|
||||
});
|
||||
assert.strictEqual(callCount, 3, "Should check all three nodes");
|
||||
});
|
||||
|
||||
@@ -204,10 +195,7 @@ describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
rabbitMQMonitor.check(monitor, heartbeat, {}),
|
||||
/No RabbitMQ nodes configured/
|
||||
);
|
||||
await assert.rejects(rabbitMQMonitor.check(monitor, heartbeat, {}), /No RabbitMQ nodes configured/);
|
||||
});
|
||||
|
||||
test("check() tries all nodes before failing", async () => {
|
||||
@@ -217,7 +205,7 @@ describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
"http://node1:15672",
|
||||
"http://node2:15672",
|
||||
"http://node3:15672",
|
||||
"http://node4:15672"
|
||||
"http://node4:15672",
|
||||
]),
|
||||
rabbitmqUsername: "guest",
|
||||
rabbitmqPassword: "guest",
|
||||
@@ -235,11 +223,8 @@ describe("RabbitMQ Multi-Node (Mocked)", () => {
|
||||
throw new Error(`Failed: ${url}`);
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
rabbitMQMonitor.check(monitor, heartbeat, {}),
|
||||
/All 4 nodes failed/
|
||||
);
|
||||
|
||||
await assert.rejects(rabbitMQMonitor.check(monitor, heartbeat, {}), /All 4 nodes failed/);
|
||||
|
||||
assert.strictEqual(checkedNodes.length, 4, "Should check all 4 nodes");
|
||||
assert.strictEqual(checkedNodes[0], "http://node1:15672");
|
||||
assert.strictEqual(checkedNodes[1], "http://node2:15672");
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("TCP Monitor", () => {
|
||||
heartbeat.status = PENDING;
|
||||
// Wait a bit before retrying with exponential backoff
|
||||
if (attempt < maxAttempts) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500 * 2 ** (attempt - 1)));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** (attempt - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ describe("TCP Monitor", () => {
|
||||
resolve(server);
|
||||
});
|
||||
|
||||
server.on("error", err => {
|
||||
server.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
@@ -91,10 +91,7 @@ describe("TCP Monitor", () => {
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
tcpMonitor.check(monitor, heartbeat, {}),
|
||||
new Error("Connection failed")
|
||||
);
|
||||
await assert.rejects(tcpMonitor.check(monitor, heartbeat, {}), new Error("Connection failed"));
|
||||
});
|
||||
|
||||
test("check() rejects when TLS certificate is expired or invalid", async () => {
|
||||
@@ -105,7 +102,7 @@ describe("TCP Monitor", () => {
|
||||
port: 443,
|
||||
smtpSecurity: "secure",
|
||||
isEnabledExpiryNotification: () => true,
|
||||
handleTlsInfo: async tlsInfo => {
|
||||
handleTlsInfo: async (tlsInfo) => {
|
||||
return tlsInfo;
|
||||
},
|
||||
};
|
||||
@@ -118,10 +115,7 @@ describe("TCP Monitor", () => {
|
||||
// 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
|
||||
);
|
||||
await assert.rejects(tcpMonitor.check(monitor, heartbeat, {}), regex);
|
||||
});
|
||||
|
||||
test("check() sets status to UP when TLS certificate is valid (SSL)", async () => {
|
||||
@@ -132,7 +126,7 @@ describe("TCP Monitor", () => {
|
||||
port: 465,
|
||||
smtpSecurity: "secure",
|
||||
isEnabledExpiryNotification: () => true,
|
||||
handleTlsInfo: async tlsInfo => {
|
||||
handleTlsInfo: async (tlsInfo) => {
|
||||
return tlsInfo;
|
||||
},
|
||||
};
|
||||
@@ -156,7 +150,7 @@ describe("TCP Monitor", () => {
|
||||
port: 587,
|
||||
smtpSecurity: "starttls",
|
||||
isEnabledExpiryNotification: () => true,
|
||||
handleTlsInfo: async tlsInfo => {
|
||||
handleTlsInfo: async (tlsInfo) => {
|
||||
return tlsInfo;
|
||||
},
|
||||
};
|
||||
@@ -180,7 +174,7 @@ describe("TCP Monitor", () => {
|
||||
port: 587,
|
||||
smtpSecurity: "starttls",
|
||||
isEnabledExpiryNotification: () => true,
|
||||
handleTlsInfo: async tlsInfo => {
|
||||
handleTlsInfo: async (tlsInfo) => {
|
||||
return tlsInfo;
|
||||
},
|
||||
};
|
||||
@@ -192,10 +186,7 @@ describe("TCP Monitor", () => {
|
||||
|
||||
const regex = /does not match certificate/;
|
||||
|
||||
await assert.rejects(
|
||||
tcpMonitor.check(monitor, heartbeat, {}),
|
||||
regex
|
||||
);
|
||||
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();
|
||||
@@ -205,7 +196,7 @@ describe("TCP Monitor", () => {
|
||||
port: 5222,
|
||||
smtpSecurity: "starttls",
|
||||
isEnabledExpiryNotification: () => true,
|
||||
handleTlsInfo: async tlsInfo => {
|
||||
handleTlsInfo: async (tlsInfo) => {
|
||||
return tlsInfo;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,16 +13,15 @@ const http = require("node:http");
|
||||
function nonCompliantWS() {
|
||||
const srv = net.createServer((socket) => {
|
||||
socket.once("data", (buf) => {
|
||||
socket.write("HTTP/1.1 101 Switching Protocols\r\n" +
|
||||
"Upgrade: websocket\r\n" +
|
||||
"Connection: Upgrade\r\n\r\n");
|
||||
socket.write(
|
||||
"HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n\r\n"
|
||||
);
|
||||
socket.destroy();
|
||||
});
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
srv.listen(0, () => {
|
||||
resolve({ server: srv,
|
||||
port: srv.address().port });
|
||||
resolve({ server: srv, port: srv.address().port });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -38,8 +37,7 @@ function httpServer() {
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
srv.listen(0, () => {
|
||||
resolve({ server: srv,
|
||||
port: srv.address().port });
|
||||
resolve({ server: srv, port: srv.address().port });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -51,17 +49,14 @@ function httpServer() {
|
||||
*/
|
||||
function createWebSocketServer(options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const wss = new WebSocketServer({ port: 0,
|
||||
...options });
|
||||
const wss = new WebSocketServer({ port: 0, ...options });
|
||||
wss.on("listening", () => {
|
||||
resolve({ server: wss,
|
||||
port: wss.address().port });
|
||||
resolve({ server: wss, port: wss.address().port });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("WebSocket Monitor", {
|
||||
}, () => {
|
||||
describe("WebSocket Monitor", {}, () => {
|
||||
test("check() rejects with unexpected server response when connecting to non-WebSocket server", {}, async (t) => {
|
||||
const websocketMonitor = new WebSocketMonitorType();
|
||||
const { server: srv, port } = await httpServer();
|
||||
@@ -92,7 +87,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -118,7 +113,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -144,7 +139,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1001" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1001"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -153,10 +148,7 @@ describe("WebSocket Monitor", {
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
websocketMonitor.check(monitor, heartbeat, {}),
|
||||
new Error("Unexpected status code: 1000")
|
||||
);
|
||||
await assert.rejects(websocketMonitor.check(monitor, heartbeat, {}), new Error("Unexpected status code: 1000"));
|
||||
});
|
||||
|
||||
test("check() rejects when expected status code is empty", async (t) => {
|
||||
@@ -167,7 +159,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
accepted_statuscodes_json: JSON.stringify([ "" ]),
|
||||
accepted_statuscodes_json: JSON.stringify([""]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -176,10 +168,7 @@ describe("WebSocket Monitor", {
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
websocketMonitor.check(monitor, heartbeat, {}),
|
||||
new Error("Unexpected status code: 1000")
|
||||
);
|
||||
await assert.rejects(websocketMonitor.check(monitor, heartbeat, {}), new Error("Unexpected status code: 1000"));
|
||||
});
|
||||
|
||||
test("check() rejects when Sec-WebSocket-Accept header is invalid", async (t) => {
|
||||
@@ -190,7 +179,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -213,7 +202,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: true,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -239,7 +228,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: true,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -265,7 +254,7 @@ describe("WebSocket Monitor", {
|
||||
const monitor = {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: true,
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -286,7 +275,7 @@ describe("WebSocket Monitor", {
|
||||
handleProtocols: (protocols) => {
|
||||
// Explicitly reject all subprotocols
|
||||
return null;
|
||||
}
|
||||
},
|
||||
});
|
||||
t.after(() => wss.close());
|
||||
|
||||
@@ -294,7 +283,7 @@ describe("WebSocket Monitor", {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
wsSubprotocol: "ocpp1.6",
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -303,10 +292,7 @@ describe("WebSocket Monitor", {
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
websocketMonitor.check(monitor, heartbeat, {}),
|
||||
new Error("Server sent no subprotocol")
|
||||
);
|
||||
await assert.rejects(websocketMonitor.check(monitor, heartbeat, {}), new Error("Server sent no subprotocol"));
|
||||
});
|
||||
|
||||
test("check() rejects when multiple subprotocols contain invalid characters", async (t) => {
|
||||
@@ -318,7 +304,7 @@ describe("WebSocket Monitor", {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
wsSubprotocol: " # & ,ocpp2.0 [] , ocpp1.6 , ,, ; ",
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -338,7 +324,7 @@ describe("WebSocket Monitor", {
|
||||
const { server: wss, port } = await createWebSocketServer({
|
||||
handleProtocols: (protocols) => {
|
||||
return Array.from(protocols).includes("test") ? "test" : null;
|
||||
}
|
||||
},
|
||||
});
|
||||
t.after(() => wss.close());
|
||||
|
||||
@@ -346,7 +332,7 @@ describe("WebSocket Monitor", {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
wsSubprotocol: "invalid , test ",
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
@@ -369,7 +355,7 @@ describe("WebSocket Monitor", {
|
||||
const { server: wss, port } = await createWebSocketServer({
|
||||
handleProtocols: (protocols) => {
|
||||
return Array.from(protocols).includes("test") ? "test" : null;
|
||||
}
|
||||
},
|
||||
});
|
||||
t.after(() => wss.close());
|
||||
|
||||
@@ -377,7 +363,7 @@ describe("WebSocket Monitor", {
|
||||
url: `ws://localhost:${port}`,
|
||||
wsIgnoreSecWebsocketAcceptHeader: false,
|
||||
wsSubprotocol: "invalid,test",
|
||||
accepted_statuscodes_json: JSON.stringify([ "1000" ]),
|
||||
accepted_statuscodes_json: JSON.stringify(["1000"]),
|
||||
timeout: 30,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const hash = require("../../../server/modules/axios-ntlm/lib/hash");
|
||||
|
||||
describe("createPseudoRandomValue()", () => {
|
||||
test("returns a hexadecimal string with the requested length", () => {
|
||||
for (const length of [ 0, 8, 16, 32, 64 ]) {
|
||||
for (const length of [0, 8, 16, 32, 64]) {
|
||||
const result = hash.createPseudoRandomValue(length);
|
||||
assert.strictEqual(typeof result, "string");
|
||||
assert.strictEqual(result.length, length);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
process.env.UPTIME_KUMA_HIDE_LOG = [ "info_db", "info_server" ].join(",");
|
||||
process.env.UPTIME_KUMA_HIDE_LOG = ["info_db", "info_server"].join(",");
|
||||
|
||||
const { describe, test, mock, before, after } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
@@ -16,7 +16,7 @@ describe("Domain Expiry", () => {
|
||||
const monHttpCom = {
|
||||
type: "http",
|
||||
url: "https://www.google.com",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
@@ -39,7 +39,7 @@ describe("Domain Expiry", () => {
|
||||
const supportInfo = await DomainExpiry.checkSupport(monHttpCom);
|
||||
let expected = {
|
||||
domain: "google.com",
|
||||
tld: "com"
|
||||
tld: "com",
|
||||
};
|
||||
assert.deepStrictEqual(supportInfo, expected);
|
||||
});
|
||||
@@ -49,7 +49,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -64,7 +64,7 @@ describe("Domain Expiry", () => {
|
||||
test("throws error for undefined target", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -80,7 +80,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: null,
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -98,7 +98,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -114,7 +114,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://192.168.1.1",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -130,7 +130,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://[2001:db8::1]",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -146,7 +146,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.x",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -164,7 +164,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://api.staging.example.com/v1/users",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.com");
|
||||
@@ -175,7 +175,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://mail.subdomain.example.org",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.org");
|
||||
@@ -186,7 +186,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.com:8080/api",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.com");
|
||||
@@ -197,7 +197,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.com/search?q=test&page=1",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.com");
|
||||
@@ -208,7 +208,7 @@ describe("Domain Expiry", () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.localhost",
|
||||
domainExpiryNotification: true
|
||||
domainExpiryNotification: true,
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
@@ -237,26 +237,26 @@ describe("Domain Expiry", () => {
|
||||
test("sendNotifications() triggers notification for expiring domain", async () => {
|
||||
await DomainExpiry.findByName("google.com");
|
||||
const hook = {
|
||||
"port": 3010,
|
||||
"url": "capture"
|
||||
port: 3010,
|
||||
url: "capture",
|
||||
};
|
||||
await setSetting("domainExpiryNotifyDays", [ 1, 2, 1500 ], "general");
|
||||
await setSetting("domainExpiryNotifyDays", [1, 2, 1500], "general");
|
||||
const notif = R.convertToBean("notification", {
|
||||
"config": JSON.stringify({
|
||||
config: JSON.stringify({
|
||||
type: "webhook",
|
||||
httpMethod: "post",
|
||||
webhookContentType: "json",
|
||||
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`
|
||||
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`,
|
||||
}),
|
||||
"active": 1,
|
||||
"user_id": 1,
|
||||
"name": "Testhook"
|
||||
active: 1,
|
||||
user_id: 1,
|
||||
name: "Testhook",
|
||||
});
|
||||
const manyDays = 3650;
|
||||
setSetting("domainExpiryNotifyDays", [ manyDays ], "general");
|
||||
const [ , data ] = await Promise.all([
|
||||
DomainExpiry.sendNotifications("google.com", [ notif ]),
|
||||
mockWebhook(hook.port, hook.url)
|
||||
setSetting("domainExpiryNotifyDays", [manyDays], "general");
|
||||
const [, data] = await Promise.all([
|
||||
DomainExpiry.sendNotifications("google.com", [notif]),
|
||||
mockWebhook(hook.port, hook.url),
|
||||
]);
|
||||
assert.match(data.msg, /will expire in/);
|
||||
});
|
||||
@@ -267,15 +267,15 @@ describe("Domain Expiry", () => {
|
||||
const mockDomain = {
|
||||
domain: "test-null.com",
|
||||
expiry: null,
|
||||
lastExpiryNotificationSent: null
|
||||
lastExpiryNotificationSent: null,
|
||||
};
|
||||
|
||||
mock.method(DomainExpiry, "findByDomainNameOrCreate", async () => mockDomain);
|
||||
|
||||
try {
|
||||
const hook = {
|
||||
"port": 3012,
|
||||
"url": "should-not-be-called-null"
|
||||
port: 3012,
|
||||
url: "should-not-be-called-null",
|
||||
};
|
||||
|
||||
const notif = {
|
||||
@@ -284,22 +284,24 @@ describe("Domain Expiry", () => {
|
||||
type: "webhook",
|
||||
httpMethod: "post",
|
||||
webhookContentType: "json",
|
||||
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`
|
||||
})
|
||||
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`,
|
||||
}),
|
||||
};
|
||||
|
||||
// Race between sendNotifications and mockWebhook timeout
|
||||
// If webhook is called, we fail. If it times out, we pass.
|
||||
const result = await Promise.race([
|
||||
DomainExpiry.sendNotifications("test-null.com", [ notif ]),
|
||||
mockWebhook(hook.port, hook.url, 500).then(() => {
|
||||
throw new Error("Webhook was called but should not have been for null expiry");
|
||||
}).catch((e) => {
|
||||
if (e.reason === "Timeout") {
|
||||
return "timeout"; // Expected - webhook was not called
|
||||
}
|
||||
throw e;
|
||||
})
|
||||
DomainExpiry.sendNotifications("test-null.com", [notif]),
|
||||
mockWebhook(hook.port, hook.url, 500)
|
||||
.then(() => {
|
||||
throw new Error("Webhook was called but should not have been for null expiry");
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.reason === "Timeout") {
|
||||
return "timeout"; // Expected - webhook was not called
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
]);
|
||||
|
||||
assert.ok(result === undefined || result === "timeout", "Should not send notification for null expiry");
|
||||
@@ -314,14 +316,14 @@ describe("Domain Expiry", () => {
|
||||
const mockDomain = {
|
||||
domain: "test-undefined.com",
|
||||
expiry: undefined,
|
||||
lastExpiryNotificationSent: null
|
||||
lastExpiryNotificationSent: null,
|
||||
};
|
||||
|
||||
mock.method(DomainExpiry, "findByDomainNameOrCreate", async () => mockDomain);
|
||||
|
||||
const hook = {
|
||||
"port": 3013,
|
||||
"url": "should-not-be-called-undefined"
|
||||
port: 3013,
|
||||
url: "should-not-be-called-undefined",
|
||||
};
|
||||
|
||||
const notif = {
|
||||
@@ -330,25 +332,30 @@ describe("Domain Expiry", () => {
|
||||
type: "webhook",
|
||||
httpMethod: "post",
|
||||
webhookContentType: "json",
|
||||
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`
|
||||
})
|
||||
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`,
|
||||
}),
|
||||
};
|
||||
|
||||
// Race between sendNotifications and mockWebhook timeout
|
||||
// If webhook is called, we fail. If it times out, we pass.
|
||||
const result = await Promise.race([
|
||||
DomainExpiry.sendNotifications("test-undefined.com", [ notif ]),
|
||||
mockWebhook(hook.port, hook.url, 500).then(() => {
|
||||
throw new Error("Webhook was called but should not have been for undefined expiry");
|
||||
}).catch((e) => {
|
||||
if (e.reason === "Timeout") {
|
||||
return "timeout"; // Expected - webhook was not called
|
||||
}
|
||||
throw e;
|
||||
})
|
||||
DomainExpiry.sendNotifications("test-undefined.com", [notif]),
|
||||
mockWebhook(hook.port, hook.url, 500)
|
||||
.then(() => {
|
||||
throw new Error("Webhook was called but should not have been for undefined expiry");
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.reason === "Timeout") {
|
||||
return "timeout"; // Expected - webhook was not called
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
]);
|
||||
|
||||
assert.ok(result === undefined || result === "timeout", "Should not send notification for undefined expiry");
|
||||
assert.ok(
|
||||
result === undefined || result === "timeout",
|
||||
"Should not send notification for undefined expiry"
|
||||
);
|
||||
} finally {
|
||||
mock.restoreAll();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("Database Migration", () => {
|
||||
const db = knex({
|
||||
client: Dialect,
|
||||
connection: {
|
||||
filename: testDbPath
|
||||
filename: testDbPath,
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
@@ -43,11 +43,10 @@ describe("Database Migration", () => {
|
||||
|
||||
// Run all migrations like production code does
|
||||
await R.knex.migrate.latest({
|
||||
directory: path.join(__dirname, "../../db/knex_migrations")
|
||||
directory: path.join(__dirname, "../../db/knex_migrations"),
|
||||
});
|
||||
|
||||
// Test passes if migrations complete successfully without errors
|
||||
|
||||
} finally {
|
||||
// Clean up
|
||||
await R.knex.destroy();
|
||||
@@ -60,18 +59,16 @@ describe("Database Migration", () => {
|
||||
test(
|
||||
"MariaDB migrations run successfully from fresh database",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
async () => {
|
||||
// Start MariaDB container (using MariaDB 12 to match current production)
|
||||
const mariadbContainer = await new GenericContainer("mariadb:12")
|
||||
.withEnvironment({
|
||||
"MYSQL_ROOT_PASSWORD": "root",
|
||||
"MYSQL_DATABASE": "kuma_test",
|
||||
"MYSQL_USER": "kuma",
|
||||
"MYSQL_PASSWORD": "kuma"
|
||||
MYSQL_ROOT_PASSWORD: "root",
|
||||
MYSQL_DATABASE: "kuma_test",
|
||||
MYSQL_USER: "kuma",
|
||||
MYSQL_PASSWORD: "kuma",
|
||||
})
|
||||
.withExposedPorts(3306)
|
||||
.withWaitStrategy(Wait.forLogMessage("ready for connections", 2))
|
||||
@@ -79,7 +76,7 @@ describe("Database Migration", () => {
|
||||
.start();
|
||||
|
||||
// Wait a bit more to ensure MariaDB is fully ready
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
const knex = require("knex");
|
||||
const knexInstance = knex({
|
||||
@@ -111,11 +108,10 @@ describe("Database Migration", () => {
|
||||
|
||||
// Run all migrations like production code does
|
||||
await R.knex.migrate.latest({
|
||||
directory: path.join(__dirname, "../../db/knex_migrations")
|
||||
directory: path.join(__dirname, "../../db/knex_migrations"),
|
||||
});
|
||||
|
||||
// Test passes if migrations complete successfully without errors
|
||||
|
||||
} finally {
|
||||
// Clean up
|
||||
try {
|
||||
@@ -135,15 +131,11 @@ describe("Database Migration", () => {
|
||||
test(
|
||||
"MySQL migrations run successfully from fresh database",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
async () => {
|
||||
// Start MySQL 8.0 container (the version mentioned in the issue)
|
||||
const mysqlContainer = await new MySqlContainer("mysql:8.0")
|
||||
.withStartupTimeout(120000)
|
||||
.start();
|
||||
const mysqlContainer = await new MySqlContainer("mysql:8.0").withStartupTimeout(120000).start();
|
||||
|
||||
const knex = require("knex");
|
||||
const knexInstance = knex({
|
||||
@@ -175,11 +167,10 @@ describe("Database Migration", () => {
|
||||
|
||||
// Run all migrations like production code does
|
||||
await R.knex.migrate.latest({
|
||||
directory: path.join(__dirname, "../../db/knex_migrations")
|
||||
directory: path.join(__dirname, "../../db/knex_migrations"),
|
||||
});
|
||||
|
||||
// Test passes if migrations complete successfully without errors
|
||||
|
||||
} finally {
|
||||
// Clean up
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const StatusPage = require("../../server/model/status_page");
|
||||
const { STATUS_PAGE_ALL_UP, STATUS_PAGE_ALL_DOWN, STATUS_PAGE_PARTIAL_DOWN, STATUS_PAGE_MAINTENANCE } = require("../../src/util");
|
||||
const {
|
||||
STATUS_PAGE_ALL_UP,
|
||||
STATUS_PAGE_ALL_DOWN,
|
||||
STATUS_PAGE_PARTIAL_DOWN,
|
||||
STATUS_PAGE_MAINTENANCE,
|
||||
} = require("../../src/util");
|
||||
|
||||
describe("StatusPage", () => {
|
||||
describe("getStatusDescription()", () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ function shouldSkip() {
|
||||
// -> Check if PID 1 is systemd (or init which maps to systemd)
|
||||
try {
|
||||
const pid1Comm = execSync("ps -p 1 -o comm=", { encoding: "utf-8" }).trim();
|
||||
return ![ "systemd", "init" ].includes(pid1Comm);
|
||||
return !["systemd", "init"].includes(pid1Comm);
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -348,61 +348,65 @@ describe("Uptime Calculator", () => {
|
||||
});
|
||||
|
||||
describe("Worst case scenario", () => {
|
||||
test("handles year-long simulation with various statuses", {
|
||||
skip: process.env.GITHUB_ACTIONS // Not stable on GitHub Actions"
|
||||
}, async (t) => {
|
||||
console.log("Memory usage before preparation", memoryUsage());
|
||||
|
||||
let c = new UptimeCalculator();
|
||||
let up = 0;
|
||||
let down = 0;
|
||||
let interval = 20;
|
||||
|
||||
await t.test("Prepare data", async () => {
|
||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||
|
||||
// Since 2023-08-12 will be out of 365 range, it starts from 2023-08-13 actually
|
||||
let actualStartDate = dayjs.utc("2023-08-13 00:00:00").unix();
|
||||
|
||||
// Simulate 1s interval for a year
|
||||
for (let i = 0; i < 365 * 24 * 60 * 60; i += interval) {
|
||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(interval, "second");
|
||||
|
||||
//Randomly UP, DOWN, MAINTENANCE, PENDING
|
||||
let rand = Math.random();
|
||||
if (rand < 0.25) {
|
||||
c.update(UP);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
up++;
|
||||
}
|
||||
} else if (rand < 0.5) {
|
||||
c.update(DOWN);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
down++;
|
||||
}
|
||||
} else if (rand < 0.75) {
|
||||
c.update(MAINTENANCE);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
//up++;
|
||||
}
|
||||
} else {
|
||||
c.update(PENDING);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
down++;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Final Date: ", UptimeCalculator.currentDate.format("YYYY-MM-DD HH:mm:ss"));
|
||||
test(
|
||||
"handles year-long simulation with various statuses",
|
||||
{
|
||||
skip: process.env.GITHUB_ACTIONS, // Not stable on GitHub Actions"
|
||||
},
|
||||
async (t) => {
|
||||
console.log("Memory usage before preparation", memoryUsage());
|
||||
|
||||
assert.strictEqual(c.minutelyUptimeDataList.length(), 1440);
|
||||
assert.strictEqual(c.dailyUptimeDataList.length(), 365);
|
||||
});
|
||||
let c = new UptimeCalculator();
|
||||
let up = 0;
|
||||
let down = 0;
|
||||
let interval = 20;
|
||||
|
||||
await t.test("get1YearUptime()", async () => {
|
||||
assert.strictEqual(c.get1Year().uptime, up / (up + down));
|
||||
});
|
||||
});
|
||||
await t.test("Prepare data", async () => {
|
||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||
|
||||
// Since 2023-08-12 will be out of 365 range, it starts from 2023-08-13 actually
|
||||
let actualStartDate = dayjs.utc("2023-08-13 00:00:00").unix();
|
||||
|
||||
// Simulate 1s interval for a year
|
||||
for (let i = 0; i < 365 * 24 * 60 * 60; i += interval) {
|
||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(interval, "second");
|
||||
|
||||
//Randomly UP, DOWN, MAINTENANCE, PENDING
|
||||
let rand = Math.random();
|
||||
if (rand < 0.25) {
|
||||
c.update(UP);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
up++;
|
||||
}
|
||||
} else if (rand < 0.5) {
|
||||
c.update(DOWN);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
down++;
|
||||
}
|
||||
} else if (rand < 0.75) {
|
||||
c.update(MAINTENANCE);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
//up++;
|
||||
}
|
||||
} else {
|
||||
c.update(PENDING);
|
||||
if (UptimeCalculator.currentDate.unix() > actualStartDate) {
|
||||
down++;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Final Date: ", UptimeCalculator.currentDate.format("YYYY-MM-DD HH:mm:ss"));
|
||||
console.log("Memory usage before preparation", memoryUsage());
|
||||
|
||||
assert.strictEqual(c.minutelyUptimeDataList.length(), 1440);
|
||||
assert.strictEqual(c.dailyUptimeDataList.length(), 365);
|
||||
});
|
||||
|
||||
await t.test("get1YearUptime()", async () => {
|
||||
assert.strictEqual(c.get1Year().uptime, up / (up + down));
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -411,7 +415,7 @@ describe("Uptime Calculator", () => {
|
||||
* @returns {{rss: string, heapTotal: string, heapUsed: string, external: string}} Current memory usage
|
||||
*/
|
||||
function memoryUsage() {
|
||||
const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
|
||||
const formatMemoryUsage = (data) => `${Math.round((data / 1024 / 1024) * 100) / 100} MB`;
|
||||
const memoryData = process.memoryUsage();
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { expect, test } from "@playwright/test";
|
||||
import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
|
||||
|
||||
test.describe("Example Spec", () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await restoreSqliteSnapshot(page);
|
||||
});
|
||||
@@ -35,5 +34,4 @@ test.describe("Example Spec", () => {
|
||||
await expect(page.getByTestId("monitor-list")).not.toContainText("example.com");
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { test } from "@playwright/test";
|
||||
import { getSqliteDatabaseExists, login, screenshot, takeSqliteSnapshot } from "../util-test";
|
||||
|
||||
test.describe("Uptime Kuma Setup", () => {
|
||||
|
||||
test.skip(() => getSqliteDatabaseExists(), "Must only run once per session");
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@@ -51,5 +50,4 @@ test.describe("Uptime Kuma Setup", () => {
|
||||
test("take sqlite snapshot", async ({ page }) => {
|
||||
await takeSqliteSnapshot(page);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { expect, test } from "@playwright/test";
|
||||
import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
|
||||
|
||||
test.describe("Status Page", () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await restoreSqliteSnapshot(page);
|
||||
});
|
||||
@@ -127,16 +126,21 @@ test.describe("Status Page", () => {
|
||||
await expect(page.getByTestId("monitor-name")).toHaveAttribute("href", monitorCustomUrl);
|
||||
|
||||
await expect(page.getByTestId("update-countdown-text")).toContainText("00:");
|
||||
const updateCountdown = Number((await page.getByTestId("update-countdown-text").textContent()).match(/(\d+):(\d+)/)[2]);
|
||||
const updateCountdown = Number(
|
||||
(await page.getByTestId("update-countdown-text").textContent()).match(/(\d+):(\d+)/)[2]
|
||||
);
|
||||
expect(updateCountdown).toBeGreaterThanOrEqual(refreshInterval - 10); // cant be certain when the timer will start, so ensure it's within expected range
|
||||
expect(updateCountdown).toBeLessThanOrEqual(refreshInterval);
|
||||
|
||||
await expect(page.locator("body")).toHaveClass(theme);
|
||||
|
||||
// Add Google Analytics ID to head and verify
|
||||
await page.waitForFunction(() => {
|
||||
return document.head.innerHTML.includes("https://www.googletagmanager.com/gtag/js?id=");
|
||||
}, { timeout: 5000 });
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
return document.head.innerHTML.includes("https://www.googletagmanager.com/gtag/js?id=");
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(await page.locator("head").innerHTML()).toContain(googleAnalyticsId);
|
||||
|
||||
const backgroundColor = await page.evaluate(() => window.getComputedStyle(document.body).backgroundColor);
|
||||
@@ -178,9 +182,13 @@ test.describe("Status Page", () => {
|
||||
await page.getByTestId("analytics-id-input").fill(plausibleAnalyticsDomainsUrls);
|
||||
await page.getByTestId("save-button").click();
|
||||
await screenshot(testInfo, page);
|
||||
await page.waitForFunction((scriptUrl) => {
|
||||
return document.head.innerHTML.includes(scriptUrl);
|
||||
}, plausibleAnalyticsScriptUrl, { timeout: 5000 });
|
||||
await page.waitForFunction(
|
||||
(scriptUrl) => {
|
||||
return document.head.innerHTML.includes(scriptUrl);
|
||||
},
|
||||
plausibleAnalyticsScriptUrl,
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsScriptUrl);
|
||||
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsDomainsUrls);
|
||||
|
||||
@@ -191,9 +199,13 @@ test.describe("Status Page", () => {
|
||||
await page.getByTestId("analytics-id-input").fill(matomoSiteId);
|
||||
await page.getByTestId("save-button").click();
|
||||
await screenshot(testInfo, page);
|
||||
await page.waitForFunction((url) => {
|
||||
return document.head.innerHTML.includes(url);
|
||||
}, matomoUrl, { timeout: 5000 });
|
||||
await page.waitForFunction(
|
||||
(url) => {
|
||||
return document.head.innerHTML.includes(url);
|
||||
},
|
||||
matomoUrl,
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
expect(await page.locator("head").innerHTML()).toContain(matomoUrl);
|
||||
expect(await page.locator("head").innerHTML()).toContain(matomoSiteId);
|
||||
});
|
||||
@@ -269,7 +281,7 @@ test.describe("Status Page", () => {
|
||||
// Attach RSS content for inspection
|
||||
await testInfo.attach("rss-feed.xml", {
|
||||
body: rssContent,
|
||||
contentType: "application/xml"
|
||||
contentType: "application/xml",
|
||||
});
|
||||
|
||||
// Verify all payloads are escaped using CDATA
|
||||
@@ -278,7 +290,7 @@ test.describe("Status Page", () => {
|
||||
expect(rssContent).toContain(`<title><![CDATA[${normalMonitorName} is down]]></title>`);
|
||||
|
||||
// Verify RSS feed structure is valid
|
||||
expect(rssContent).toContain("<?xml version=\"1.0\"");
|
||||
expect(rssContent).toContain('<?xml version="1.0"');
|
||||
expect(rssContent).toContain("<rss");
|
||||
expect(rssContent).toContain("</rss>");
|
||||
|
||||
@@ -306,10 +318,9 @@ test.describe("Status Page", () => {
|
||||
|
||||
await testInfo.attach("rss-feed-custom-title.xml", {
|
||||
body: rssContentCustom,
|
||||
contentType: "application/xml"
|
||||
contentType: "application/xml",
|
||||
});
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function screenshot(testInfo, page) {
|
||||
const screenshot = await page.screenshot();
|
||||
await testInfo.attach("screenshot", {
|
||||
body: screenshot,
|
||||
contentType: "image/png"
|
||||
contentType: "image/png",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
const { sync: rimrafSync } = require("rimraf");
|
||||
const Database = require("../server/database");
|
||||
|
||||
class TestDB {
|
||||
dataDir;
|
||||
|
||||
constructor(dir = "./data/test") {
|
||||
this.dataDir = dir;
|
||||
}
|
||||
|
||||
async create() {
|
||||
Database.initDataDir({ "data-dir": this.dataDir });
|
||||
Database.dbConfig = {
|
||||
type: "sqlite"
|
||||
};
|
||||
Database.writeDBConfig(Database.dbConfig);
|
||||
await Database.connect(true);
|
||||
await Database.patch();
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
await Database.close();
|
||||
this.dataDir && rimrafSync(this.dataDir);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TestDB;
|
||||
const { sync: rimrafSync } = require("rimraf");
|
||||
const Database = require("../server/database");
|
||||
|
||||
class TestDB {
|
||||
dataDir;
|
||||
|
||||
constructor(dir = "./data/test") {
|
||||
this.dataDir = dir;
|
||||
}
|
||||
|
||||
async create() {
|
||||
Database.initDataDir({ "data-dir": this.dataDir });
|
||||
Database.dbConfig = {
|
||||
type: "sqlite",
|
||||
};
|
||||
Database.writeDBConfig(Database.dbConfig);
|
||||
await Database.connect(true);
|
||||
await Database.patch();
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
await Database.close();
|
||||
this.dataDir && rimrafSync(this.dataDir);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TestDB;
|
||||
|
||||
Reference in New Issue
Block a user