Monitor Conditions (#5048)

This commit is contained in:
Shaun
2024-08-30 15:48:13 -04:00
committed by GitHub
parent 032ac161f7
commit 36f8be040d
21 changed files with 1526 additions and 35 deletions

View File

@@ -0,0 +1,46 @@
const test = require("node:test");
const assert = require("node:assert");
const { ConditionExpressionGroup, ConditionExpression, LOGICAL } = require("../../../server/monitor-conditions/expression.js");
const { evaluateExpressionGroup, evaluateExpression } = require("../../../server/monitor-conditions/evaluator.js");
test("Test evaluateExpression", async (t) => {
const expr = new ConditionExpression("record", "contains", "mx1.example.com");
assert.strictEqual(true, evaluateExpression(expr, { record: "mx1.example.com" }));
assert.strictEqual(false, evaluateExpression(expr, { record: "mx2.example.com" }));
});
test("Test evaluateExpressionGroup with logical AND", async (t) => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "mx1."),
new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
]);
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
});
test("Test evaluateExpressionGroup with logical OR", async (t) => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "example.com"),
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
]);
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.com" }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.net" }));
});
test("Test evaluateExpressionGroup with nested group", async (t) => {
const group = new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "mx1."),
new ConditionExpressionGroup([
new ConditionExpression("record", "contains", "example.com"),
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
]),
]);
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.org" }));
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1.example.net" }));
});

View File

@@ -0,0 +1,55 @@
const test = require("node:test");
const assert = require("node:assert");
const { ConditionExpressionGroup, ConditionExpression } = require("../../../server/monitor-conditions/expression.js");
test("Test ConditionExpressionGroup.fromMonitor", async (t) => {
const monitor = {
conditions: JSON.stringify([
{
"type": "expression",
"andOr": "and",
"operator": "contains",
"value": "foo",
"variable": "record"
},
{
"type": "group",
"andOr": "and",
"children": [
{
"type": "expression",
"andOr": "and",
"operator": "contains",
"value": "bar",
"variable": "record"
},
{
"type": "group",
"andOr": "and",
"children": [
{
"type": "expression",
"andOr": "and",
"operator": "contains",
"value": "car",
"variable": "record"
}
]
},
]
},
]),
};
const root = ConditionExpressionGroup.fromMonitor(monitor);
assert.strictEqual(true, root.children.length === 2);
assert.strictEqual(true, root.children[0] instanceof ConditionExpression);
assert.strictEqual(true, root.children[0].value === "foo");
assert.strictEqual(true, root.children[1] instanceof ConditionExpressionGroup);
assert.strictEqual(true, root.children[1].children.length === 2);
assert.strictEqual(true, root.children[1].children[0] instanceof ConditionExpression);
assert.strictEqual(true, root.children[1].children[0].value === "bar");
assert.strictEqual(true, root.children[1].children[1] instanceof ConditionExpressionGroup);
assert.strictEqual(true, root.children[1].children[1].children.length === 1);
assert.strictEqual(true, root.children[1].children[1].children[0] instanceof ConditionExpression);
assert.strictEqual(true, root.children[1].children[1].children[0].value === "car");
});

View File

@@ -0,0 +1,108 @@
const 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");
test("Test StringEqualsOperator", async (t) => {
const op = operatorMap.get(OP_STR_EQUALS);
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.org"));
assert.strictEqual(false, op.test("1", 1)); // strict equality
});
test("Test StringNotEqualsOperator", async (t) => {
const op = operatorMap.get(OP_STR_NOT_EQUALS);
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
});
test("Test ContainsOperator with scalar", async (t) => {
const op = operatorMap.get(OP_CONTAINS);
assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
});
test("Test ContainsOperator with array", async (t) => {
const op = operatorMap.get(OP_CONTAINS);
assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
});
test("Test NotContainsOperator with scalar", async (t) => {
const op = operatorMap.get(OP_NOT_CONTAINS);
assert.strictEqual(true, op.test("example.org", ".com"));
assert.strictEqual(false, op.test("example.org", ".org"));
});
test("Test NotContainsOperator with array", async (t) => {
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"));
});
test("Test StartsWithOperator", async (t) => {
const op = operatorMap.get(OP_STARTS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
});
test("Test NotStartsWithOperator", async (t) => {
const op = operatorMap.get(OP_NOT_STARTS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
});
test("Test EndsWithOperator", async (t) => {
const op = operatorMap.get(OP_ENDS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
});
test("Test NotEndsWithOperator", async (t) => {
const op = operatorMap.get(OP_NOT_ENDS_WITH);
assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
});
test("Test NumberEqualsOperator", async (t) => {
const op = operatorMap.get(OP_NUM_EQUALS);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(1, "1"));
assert.strictEqual(false, op.test(1, "2"));
});
test("Test NumberNotEqualsOperator", async (t) => {
const op = operatorMap.get(OP_NUM_NOT_EQUALS);
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, "1"));
});
test("Test LessThanOperator", async (t) => {
const op = operatorMap.get(OP_LT);
assert.strictEqual(true, op.test(1, 2));
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, 1));
});
test("Test GreaterThanOperator", async (t) => {
const op = operatorMap.get(OP_GT);
assert.strictEqual(true, op.test(2, 1));
assert.strictEqual(true, op.test(2, "1"));
assert.strictEqual(false, op.test(1, 1));
});
test("Test LessThanOrEqualToOperator", async (t) => {
const op = operatorMap.get(OP_LTE);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(1, 2));
assert.strictEqual(true, op.test(1, "2"));
assert.strictEqual(false, op.test(1, 0));
});
test("Test GreaterThanOrEqualToOperator", async (t) => {
const op = operatorMap.get(OP_GTE);
assert.strictEqual(true, op.test(1, 1));
assert.strictEqual(true, op.test(2, 1));
assert.strictEqual(true, op.test(2, "2"));
assert.strictEqual(false, op.test(2, 3));
});

View File

@@ -0,0 +1,109 @@
import { expect, test } from "@playwright/test";
import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
test.describe("Monitor Form", () => {
test.beforeEach(async ({ page }) => {
await restoreSqliteSnapshot(page);
});
test("condition ui", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
await screenshot(testInfo, page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await expect(monitorTypeSelect).toBeVisible();
await monitorTypeSelect.selectOption("dns");
const selectedValue = await monitorTypeSelect.evaluate(select => select.value);
expect(selectedValue).toBe("dns");
// Add Conditions & verify:
await page.getByTestId("add-condition-button").click();
expect(await page.getByTestId("condition").count()).toEqual(2); // 1 added by default + 1 explicitly added
// Add a Condition Group & verify:
await page.getByTestId("add-group-button").click();
expect(await page.getByTestId("condition-group").count()).toEqual(1);
expect(await page.getByTestId("condition").count()).toEqual(3); // 2 solo conditions + 1 condition in group
await screenshot(testInfo, page);
// Remove a condition & verify:
await page.getByTestId("remove-condition").first().click();
expect(await page.getByTestId("condition").count()).toEqual(2); // 1 solo condition + 1 condition in group
// Remove a condition group & verify:
await page.getByTestId("remove-condition-group").first().click();
expect(await page.getByTestId("condition-group").count()).toEqual(0);
await screenshot(testInfo, page);
});
test("successful condition", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
await screenshot(testInfo, page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await expect(monitorTypeSelect).toBeVisible();
await monitorTypeSelect.selectOption("dns");
const selectedValue = await monitorTypeSelect.evaluate(select => select.value);
expect(selectedValue).toBe("dns");
const friendlyName = "Example DNS NS";
await page.getByTestId("friendly-name-input").fill(friendlyName);
await page.getByTestId("hostname-input").fill("example.com");
// Vue-Multiselect component
const resolveTypeSelect = page.getByTestId("resolve-type-select");
await resolveTypeSelect.click();
await resolveTypeSelect.getByRole("option", { name: "NS" }).click();
await page.getByTestId("add-condition-button").click();
expect(await page.getByTestId("condition").count()).toEqual(2); // 1 added by default + 1 explicitly added
await page.getByTestId("condition-value").nth(0).fill("a.iana-servers.net");
await page.getByTestId("condition-and-or").nth(0).selectOption("or");
await page.getByTestId("condition-value").nth(1).fill("b.iana-servers.net");
await screenshot(testInfo, page);
await page.getByTestId("save-button").click();
await page.waitForURL("/dashboard/*"); // wait for the monitor to be created
await expect(page.getByTestId("monitor-status")).toHaveText("up", { ignoreCase: true });
await screenshot(testInfo, page);
});
test("failing condition", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
await screenshot(testInfo, page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await expect(monitorTypeSelect).toBeVisible();
await monitorTypeSelect.selectOption("dns");
const selectedValue = await monitorTypeSelect.evaluate(select => select.value);
expect(selectedValue).toBe("dns");
const friendlyName = "Example DNS NS";
await page.getByTestId("friendly-name-input").fill(friendlyName);
await page.getByTestId("hostname-input").fill("example.com");
// Vue-Multiselect component
const resolveTypeSelect = page.getByTestId("resolve-type-select");
await resolveTypeSelect.click();
await resolveTypeSelect.getByRole("option", { name: "NS" }).click();
expect(await page.getByTestId("condition").count()).toEqual(1); // 1 added by default
await page.getByTestId("condition-value").nth(0).fill("definitely-not.net");
await screenshot(testInfo, page);
await page.getByTestId("save-button").click();
await page.waitForURL("/dashboard/*"); // wait for the monitor to be created
await expect(page.getByTestId("monitor-status")).toHaveText("down", { ignoreCase: true });
await screenshot(testInfo, page);
});
});