Merge branch 'master' into pin-gha
This commit is contained in:
commit
4e2b63a6c6
@ -172,9 +172,9 @@ class DomainExpiry extends BeanModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {(Date|null)} Expiry date from RDAP
|
* @returns {Promise<(Date|null)>} Expiry date from RDAP
|
||||||
*/
|
*/
|
||||||
getExpiryDate() {
|
async getExpiryDate() {
|
||||||
return getRdapDomainExpiryDate(this.domain);
|
return getRdapDomainExpiryDate(this.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1223,5 +1223,6 @@
|
|||||||
"labelDomainNameExpiryNotification": "Domain Name Expiry Notification",
|
"labelDomainNameExpiryNotification": "Domain Name Expiry Notification",
|
||||||
"domainExpiryDescription": "Trigger notification when domain names expires in:",
|
"domainExpiryDescription": "Trigger notification when domain names expires in:",
|
||||||
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
|
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
|
||||||
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors."
|
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",
|
||||||
|
"imageResetConfirmation": "Image reset to default"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,6 +138,9 @@
|
|||||||
<h1 class="mb-4 title-flex">
|
<h1 class="mb-4 title-flex">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<span class="logo-wrapper" @click="showImageCropUploadMethod">
|
<span class="logo-wrapper" @click="showImageCropUploadMethod">
|
||||||
|
<button v-if="editMode" type="button" class="p-0 bg-transparent border-0 small-reset-btn reset-top-left" @click.stop="resetToDefaultImage">
|
||||||
|
<font-awesome-icon icon="times" class="text-danger" />
|
||||||
|
</button>
|
||||||
<img :src="logoURL" alt class="logo me-2" :class="logoClass" />
|
<img :src="logoURL" alt class="logo me-2" :class="logoClass" />
|
||||||
<font-awesome-icon v-if="enableEditMode" class="icon-upload" icon="upload" />
|
<font-awesome-icon v-if="enableEditMode" class="icon-upload" icon="upload" />
|
||||||
</span>
|
</span>
|
||||||
@ -962,6 +965,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset logo image to default (public/icon.svg)
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
resetToDefaultImage() {
|
||||||
|
if (! this.editMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.imgDataUrl = "/icon.svg";
|
||||||
|
this.config.icon = this.imgDataUrl;
|
||||||
|
toast.success(this.$t("imageResetConfirmation"));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an incident for this status page
|
* Create an incident for this status page
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
@ -1181,6 +1198,58 @@ footer {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 15px 70px rgba(0, 0, 0, 0.9);
|
box-shadow: 0 15px 70px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reset button placed at top-left of the logo */
|
||||||
|
.reset-top-left {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -15px;
|
||||||
|
z-index: 2;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
transition: transform $easing-in 0.18s, box-shadow $easing-in 0.18s, background-color $easing-in 0.18s;
|
||||||
|
transform-origin: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
transform: scale(1.18);
|
||||||
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover ~ .icon-upload {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-reset-btn {
|
||||||
|
transition: transform $easing-in 0.18s, box-shadow $easing-in 0.18s, background-color $easing-in 0.18s;
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
|
transform: scale(1.18);
|
||||||
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
|||||||
@ -4,14 +4,26 @@ Documentation: https://nodejs.org/api/test.html
|
|||||||
|
|
||||||
Create a test file in this directory with the name `*.js`.
|
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
|
||||||
|
> - Use descriptive test names: `function() [behavior] [condition]`
|
||||||
|
> - Don't prefix with "Test" or "Should"
|
||||||
|
|
||||||
## Template
|
## Template
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const test = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
|
|
||||||
test("Test name", async (t) => {
|
describe("Feature Name", () => {
|
||||||
assert.strictEqual(1, 1);
|
test("function() returns expected value when condition is met", () => {
|
||||||
|
assert.strictEqual(1, 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +1,48 @@
|
|||||||
const test = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
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");
|
const { evaluateExpressionGroup, evaluateExpression } = require("../../../server/monitor-conditions/evaluator.js");
|
||||||
|
|
||||||
test("Test evaluateExpression", async (t) => {
|
describe("Expression Evaluator", () => {
|
||||||
const expr = new ConditionExpression("record", "contains", "mx1.example.com");
|
test("evaluateExpression() returns true when condition matches and false otherwise", () => {
|
||||||
assert.strictEqual(true, evaluateExpression(expr, { record: "mx1.example.com" }));
|
const expr = new ConditionExpression("record", "contains", "mx1.example.com");
|
||||||
assert.strictEqual(false, evaluateExpression(expr, { record: "mx2.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) => {
|
test("evaluateExpressionGroup() with AND logic requires all conditions to be true", () => {
|
||||||
const group = new ConditionExpressionGroup([
|
const group = new ConditionExpressionGroup([
|
||||||
new ConditionExpression("record", "contains", "mx1."),
|
new ConditionExpression("record", "contains", "mx1."),
|
||||||
new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
|
new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
|
||||||
]);
|
]);
|
||||||
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
|
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
|
||||||
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
|
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
|
||||||
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
|
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test evaluateExpressionGroup with logical OR", async (t) => {
|
test("evaluateExpressionGroup() with OR logic requires at least one condition to be true", () => {
|
||||||
const group = new ConditionExpressionGroup([
|
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.com"),
|
||||||
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
|
new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
|
||||||
]),
|
]);
|
||||||
]);
|
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.com" }));
|
||||||
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
|
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.org" }));
|
||||||
assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
|
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.net" }));
|
||||||
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" }));
|
test("evaluateExpressionGroup() evaluates nested groups correctly", () => {
|
||||||
assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1.example.net" }));
|
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" }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,108 +1,110 @@
|
|||||||
const test = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
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");
|
||||||
|
|
||||||
test("Test StringEqualsOperator", async (t) => {
|
describe("Expression Operators", () => {
|
||||||
const op = operatorMap.get(OP_STR_EQUALS);
|
test("StringEqualsOperator returns true for identical strings and false otherwise", () => {
|
||||||
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
|
const op = operatorMap.get(OP_STR_EQUALS);
|
||||||
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.org"));
|
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
|
||||||
assert.strictEqual(false, op.test("1", 1)); // strict equality
|
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) => {
|
test("StringNotEqualsOperator returns true for different strings and false for identical strings", () => {
|
||||||
const op = operatorMap.get(OP_STR_NOT_EQUALS);
|
const op = operatorMap.get(OP_STR_NOT_EQUALS);
|
||||||
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
|
assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
|
||||||
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
|
assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
|
||||||
assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
|
assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test ContainsOperator with scalar", async (t) => {
|
test("ContainsOperator returns true when scalar contains substring", () => {
|
||||||
const op = operatorMap.get(OP_CONTAINS);
|
const op = operatorMap.get(OP_CONTAINS);
|
||||||
assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
|
assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
|
||||||
assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
|
assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test ContainsOperator with array", async (t) => {
|
test("ContainsOperator returns true when array contains element", () => {
|
||||||
const op = operatorMap.get(OP_CONTAINS);
|
const op = operatorMap.get(OP_CONTAINS);
|
||||||
assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
|
assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
|
||||||
assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
|
assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test NotContainsOperator with scalar", async (t) => {
|
test("NotContainsOperator returns true when scalar does not contain substring", () => {
|
||||||
const op = operatorMap.get(OP_NOT_CONTAINS);
|
const op = operatorMap.get(OP_NOT_CONTAINS);
|
||||||
assert.strictEqual(true, op.test("example.org", ".com"));
|
assert.strictEqual(true, op.test("example.org", ".com"));
|
||||||
assert.strictEqual(false, op.test("example.org", ".org"));
|
assert.strictEqual(false, op.test("example.org", ".org"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test NotContainsOperator with array", async (t) => {
|
test("NotContainsOperator returns true when array does not contain element", () => {
|
||||||
const op = operatorMap.get(OP_NOT_CONTAINS);
|
const op = operatorMap.get(OP_NOT_CONTAINS);
|
||||||
assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
|
assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
|
||||||
assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
|
assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test StartsWithOperator", async (t) => {
|
test("StartsWithOperator returns true when string starts with prefix", () => {
|
||||||
const op = operatorMap.get(OP_STARTS_WITH);
|
const op = operatorMap.get(OP_STARTS_WITH);
|
||||||
assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
|
assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
|
||||||
assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
|
assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test NotStartsWithOperator", async (t) => {
|
test("NotStartsWithOperator returns true when string does not start with prefix", () => {
|
||||||
const op = operatorMap.get(OP_NOT_STARTS_WITH);
|
const op = operatorMap.get(OP_NOT_STARTS_WITH);
|
||||||
assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
|
assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
|
||||||
assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
|
assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test EndsWithOperator", async (t) => {
|
test("EndsWithOperator returns true when string ends with suffix", () => {
|
||||||
const op = operatorMap.get(OP_ENDS_WITH);
|
const op = operatorMap.get(OP_ENDS_WITH);
|
||||||
assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
|
assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
|
||||||
assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
|
assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test NotEndsWithOperator", async (t) => {
|
test("NotEndsWithOperator returns true when string does not end with suffix", () => {
|
||||||
const op = operatorMap.get(OP_NOT_ENDS_WITH);
|
const op = operatorMap.get(OP_NOT_ENDS_WITH);
|
||||||
assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
|
assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
|
||||||
assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
|
assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test NumberEqualsOperator", async (t) => {
|
test("NumberEqualsOperator returns true for equal numbers with type coercion", () => {
|
||||||
const op = operatorMap.get(OP_NUM_EQUALS);
|
const op = operatorMap.get(OP_NUM_EQUALS);
|
||||||
assert.strictEqual(true, op.test(1, 1));
|
assert.strictEqual(true, op.test(1, 1));
|
||||||
assert.strictEqual(true, op.test(1, "1"));
|
assert.strictEqual(true, op.test(1, "1"));
|
||||||
assert.strictEqual(false, op.test(1, "2"));
|
assert.strictEqual(false, op.test(1, "2"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test NumberNotEqualsOperator", async (t) => {
|
test("NumberNotEqualsOperator returns true for different numbers", () => {
|
||||||
const op = operatorMap.get(OP_NUM_NOT_EQUALS);
|
const op = operatorMap.get(OP_NUM_NOT_EQUALS);
|
||||||
assert.strictEqual(true, op.test(1, "2"));
|
assert.strictEqual(true, op.test(1, "2"));
|
||||||
assert.strictEqual(false, op.test(1, "1"));
|
assert.strictEqual(false, op.test(1, "1"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test LessThanOperator", async (t) => {
|
test("LessThanOperator returns true when first number is less than second", () => {
|
||||||
const op = operatorMap.get(OP_LT);
|
const op = operatorMap.get(OP_LT);
|
||||||
assert.strictEqual(true, op.test(1, 2));
|
assert.strictEqual(true, op.test(1, 2));
|
||||||
assert.strictEqual(true, op.test(1, "2"));
|
assert.strictEqual(true, op.test(1, "2"));
|
||||||
assert.strictEqual(false, op.test(1, 1));
|
assert.strictEqual(false, op.test(1, 1));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test GreaterThanOperator", async (t) => {
|
test("GreaterThanOperator returns true when first number is greater than second", () => {
|
||||||
const op = operatorMap.get(OP_GT);
|
const op = operatorMap.get(OP_GT);
|
||||||
assert.strictEqual(true, op.test(2, 1));
|
assert.strictEqual(true, op.test(2, 1));
|
||||||
assert.strictEqual(true, op.test(2, "1"));
|
assert.strictEqual(true, op.test(2, "1"));
|
||||||
assert.strictEqual(false, op.test(1, 1));
|
assert.strictEqual(false, op.test(1, 1));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test LessThanOrEqualToOperator", async (t) => {
|
test("LessThanOrEqualToOperator returns true when first number is less than or equal to second", () => {
|
||||||
const op = operatorMap.get(OP_LTE);
|
const op = operatorMap.get(OP_LTE);
|
||||||
assert.strictEqual(true, op.test(1, 1));
|
assert.strictEqual(true, op.test(1, 1));
|
||||||
assert.strictEqual(true, op.test(1, 2));
|
assert.strictEqual(true, op.test(1, 2));
|
||||||
assert.strictEqual(true, op.test(1, "2"));
|
assert.strictEqual(true, op.test(1, "2"));
|
||||||
assert.strictEqual(false, op.test(1, 0));
|
assert.strictEqual(false, op.test(1, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test GreaterThanOrEqualToOperator", async (t) => {
|
test("GreaterThanOrEqualToOperator returns true when first number is greater than or equal to second", () => {
|
||||||
const op = operatorMap.get(OP_GTE);
|
const op = operatorMap.get(OP_GTE);
|
||||||
assert.strictEqual(true, op.test(1, 1));
|
assert.strictEqual(true, op.test(1, 1));
|
||||||
assert.strictEqual(true, op.test(2, 1));
|
assert.strictEqual(true, op.test(2, 1));
|
||||||
assert.strictEqual(true, op.test(2, "2"));
|
assert.strictEqual(true, op.test(2, "2"));
|
||||||
assert.strictEqual(false, op.test(2, 3));
|
assert.strictEqual(false, op.test(2, 3));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,8 +2,8 @@ const { describe, test } = require("node:test");
|
|||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const grpc = require("@grpc/grpc-js");
|
const grpc = require("@grpc/grpc-js");
|
||||||
const protoLoader = require("@grpc/proto-loader");
|
const protoLoader = require("@grpc/proto-loader");
|
||||||
const { GrpcKeywordMonitorType } = require("../../server/monitor-types/grpc");
|
const { GrpcKeywordMonitorType } = require("../../../server/monitor-types/grpc");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
@ -82,7 +82,7 @@ async function createTestGrpcServer(port, methodHandlers) {
|
|||||||
describe("GrpcKeywordMonitorType", {
|
describe("GrpcKeywordMonitorType", {
|
||||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||||
}, () => {
|
}, () => {
|
||||||
test("gRPC keyword found in response", async () => {
|
test("check() sets status to UP when keyword is found in response", async () => {
|
||||||
const port = 50051;
|
const port = 50051;
|
||||||
const server = await createTestGrpcServer(port, {
|
const server = await createTestGrpcServer(port, {
|
||||||
Echo: (call, callback) => {
|
Echo: (call, callback) => {
|
||||||
@ -118,7 +118,7 @@ describe("GrpcKeywordMonitorType", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("gRPC keyword not found in response", async () => {
|
test("check() rejects when keyword is not found in response", async () => {
|
||||||
const port = 50052;
|
const port = 50052;
|
||||||
const server = await createTestGrpcServer(port, {
|
const server = await createTestGrpcServer(port, {
|
||||||
Echo: (call, callback) => {
|
Echo: (call, callback) => {
|
||||||
@ -158,7 +158,7 @@ describe("GrpcKeywordMonitorType", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("gRPC inverted keyword - keyword present (should fail)", async () => {
|
test("check() rejects when inverted keyword is present in response", async () => {
|
||||||
const port = 50053;
|
const port = 50053;
|
||||||
const server = await createTestGrpcServer(port, {
|
const server = await createTestGrpcServer(port, {
|
||||||
Echo: (call, callback) => {
|
Echo: (call, callback) => {
|
||||||
@ -198,7 +198,7 @@ describe("GrpcKeywordMonitorType", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("gRPC inverted keyword - keyword not present (should pass)", async () => {
|
test("check() sets status to UP when inverted keyword is not present in response", async () => {
|
||||||
const port = 50054;
|
const port = 50054;
|
||||||
const server = await createTestGrpcServer(port, {
|
const server = await createTestGrpcServer(port, {
|
||||||
Echo: (call, callback) => {
|
Echo: (call, callback) => {
|
||||||
@ -234,7 +234,7 @@ describe("GrpcKeywordMonitorType", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("gRPC connection failure", async () => {
|
test("check() rejects when gRPC server is unreachable", async () => {
|
||||||
const grpcMonitor = new GrpcKeywordMonitorType();
|
const grpcMonitor = new GrpcKeywordMonitorType();
|
||||||
const monitor = {
|
const monitor = {
|
||||||
grpcUrl: "localhost:50099",
|
grpcUrl: "localhost:50099",
|
||||||
@ -262,7 +262,7 @@ describe("GrpcKeywordMonitorType", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("gRPC response truncation for long messages", async () => {
|
test("check() truncates long response messages in error output", async () => {
|
||||||
const port = 50055;
|
const port = 50055;
|
||||||
const longMessage = "A".repeat(100) + " with SUCCESS keyword";
|
const longMessage = "A".repeat(100) + " with SUCCESS keyword";
|
||||||
|
|
||||||
@ -2,8 +2,8 @@ const { describe, test } = require("node:test");
|
|||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { HiveMQContainer } = require("@testcontainers/hivemq");
|
const { HiveMQContainer } = require("@testcontainers/hivemq");
|
||||||
const mqtt = require("mqtt");
|
const mqtt = require("mqtt");
|
||||||
const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
|
const { MqttMonitorType } = require("../../../server/monitor-types/mqtt");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an MQTT test with the
|
* Runs an MQTT test with the
|
||||||
@ -58,91 +58,91 @@ describe("MqttMonitorType", {
|
|||||||
concurrency: 4,
|
concurrency: 4,
|
||||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
|
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
|
||||||
}, () => {
|
}, () => {
|
||||||
test("valid keywords (type=default)", async () => {
|
test("check() sets status to UP when keyword is found in message (type=default)", async () => {
|
||||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
|
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid nested topic", async () => {
|
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");
|
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/b/c", "a/b/c");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid nested topic (with special chars)", async () => {
|
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/'/$/./*/%");
|
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/'/$/./*/%", "a/'/$/./*/%");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid wildcard topic (with #)", async () => {
|
test("check() sets status to UP when keyword is found using # wildcard", async () => {
|
||||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/#", "a/b/c");
|
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/#", "a/b/c");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid wildcard topic (with +)", async () => {
|
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");
|
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c", "a/b/c");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid wildcard topic (with + and #)", async () => {
|
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");
|
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c/#", "a/b/c/d/e");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c/d/e; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: a/b/c/d/e; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid topic", async () => {
|
test("check() rejects with timeout when topic does not match", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("keyword will not be checked anyway", null, "message", "x/y/z", "a/b/c"),
|
testMqtt("keyword will not be checked anyway", null, "message", "x/y/z", "a/b/c"),
|
||||||
new Error("Timeout, Message not received"),
|
new Error("Timeout, Message not received"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid wildcard topic (with #)", async () => {
|
test("check() rejects with timeout when # wildcard is not last character", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
|
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
|
||||||
new Error("Timeout, Message not received"),
|
new Error("Timeout, Message not received"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid wildcard topic (with +)", async () => {
|
test("check() rejects with timeout when + wildcard topic does not match", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("", null, "message", "x/+/z", "a/b/c"),
|
testMqtt("", null, "message", "x/+/z", "a/b/c"),
|
||||||
new Error("Timeout, Message not received"),
|
new Error("Timeout, Message not received"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid keywords (type=keyword)", async () => {
|
test("check() sets status to UP when keyword is found in message (type=keyword)", async () => {
|
||||||
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
|
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid keywords (type=default)", async () => {
|
test("check() rejects when keyword is not found in message (type=default)", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
|
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
|
||||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid keyword (type=keyword)", async () => {
|
test("check() rejects when keyword is not found in message (type=keyword)", async () => {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
|
testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
|
||||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("valid json-query", async () => {
|
test("check() sets status to UP when json-query finds expected value", async () => {
|
||||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
||||||
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
|
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
|
||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
|
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid (because query fails) json-query", async () => {
|
test("check() rejects when json-query path returns undefined", async () => {
|
||||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("[not_relevant]", "json-query", "{}"),
|
testMqtt("[not_relevant]", "json-query", "{}"),
|
||||||
@ -150,7 +150,7 @@ describe("MqttMonitorType", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invalid (because successMessage fails) json-query", async () => {
|
test("check() rejects when json-query value does not match expected value", async () => {
|
||||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
|
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
|
||||||
@ -1,8 +1,8 @@
|
|||||||
const { describe, test } = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { MSSQLServerContainer } = require("@testcontainers/mssqlserver");
|
const { MSSQLServerContainer } = require("@testcontainers/mssqlserver");
|
||||||
const { MssqlMonitorType } = require("../../server/monitor-types/mssql");
|
const { MssqlMonitorType } = require("../../../server/monitor-types/mssql");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to create and start a MSSQL container
|
* Helper function to create and start a MSSQL container
|
||||||
@ -26,7 +26,7 @@ describe(
|
|||||||
(process.platform !== "linux" || process.arch !== "x64"),
|
(process.platform !== "linux" || process.arch !== "x64"),
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
test("MSSQL is running", async () => {
|
test("check() sets status to UP when MSSQL server is reachable", async () => {
|
||||||
let mssqlContainer;
|
let mssqlContainer;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -69,7 +69,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL with custom query returning single value", async () => {
|
test("check() sets status to UP when custom query returns single value", async () => {
|
||||||
const mssqlContainer = await createAndStartMSSQLContainer();
|
const mssqlContainer = await createAndStartMSSQLContainer();
|
||||||
|
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
@ -97,7 +97,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL with custom query and condition that passes", async () => {
|
test("check() sets status to UP when custom query result meets condition", async () => {
|
||||||
const mssqlContainer = await createAndStartMSSQLContainer();
|
const mssqlContainer = await createAndStartMSSQLContainer();
|
||||||
|
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
@ -133,7 +133,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL with custom query and condition that fails", async () => {
|
test("check() rejects when custom query result does not meet condition", async () => {
|
||||||
const mssqlContainer = await createAndStartMSSQLContainer();
|
const mssqlContainer = await createAndStartMSSQLContainer();
|
||||||
|
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
@ -174,7 +174,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL query returns no results", async () => {
|
test("check() rejects when query returns no results", async () => {
|
||||||
const mssqlContainer = await createAndStartMSSQLContainer();
|
const mssqlContainer = await createAndStartMSSQLContainer();
|
||||||
|
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
@ -207,7 +207,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL query returns multiple rows", async () => {
|
test("check() rejects when query returns multiple rows", async () => {
|
||||||
const mssqlContainer = await createAndStartMSSQLContainer();
|
const mssqlContainer = await createAndStartMSSQLContainer();
|
||||||
|
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
@ -240,7 +240,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL query returns multiple columns", async () => {
|
test("check() rejects when query returns multiple columns", async () => {
|
||||||
const mssqlContainer = await createAndStartMSSQLContainer();
|
const mssqlContainer = await createAndStartMSSQLContainer();
|
||||||
|
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
@ -273,7 +273,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("MSSQL is not running", async () => {
|
test("check() rejects when MSSQL server is not reachable", async () => {
|
||||||
const mssqlMonitor = new MssqlMonitorType();
|
const mssqlMonitor = new MssqlMonitorType();
|
||||||
const monitor = {
|
const monitor = {
|
||||||
databaseConnectionString:
|
databaseConnectionString:
|
||||||
@ -1,8 +1,8 @@
|
|||||||
const { describe, test } = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
|
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
|
||||||
const { PostgresMonitorType } = require("../../server/monitor-types/postgres");
|
const { PostgresMonitorType } = require("../../../server/monitor-types/postgres");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
|
|
||||||
describe(
|
describe(
|
||||||
"Postgres Single Node",
|
"Postgres Single Node",
|
||||||
@ -12,7 +12,7 @@ describe(
|
|||||||
(process.platform !== "linux" || process.arch !== "x64"),
|
(process.platform !== "linux" || process.arch !== "x64"),
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
test("Postgres is running", async () => {
|
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
|
// The default timeout of 30 seconds might not be enough for the container to start
|
||||||
const postgresContainer = await new PostgreSqlContainer(
|
const postgresContainer = await new PostgreSqlContainer(
|
||||||
"postgres:latest"
|
"postgres:latest"
|
||||||
@ -37,7 +37,7 @@ describe(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Postgres is not running", async () => {
|
test("check() rejects when Postgres server is not reachable", async () => {
|
||||||
const postgresMonitor = new PostgresMonitorType();
|
const postgresMonitor = new PostgresMonitorType();
|
||||||
const monitor = {
|
const monitor = {
|
||||||
databaseConnectionString: "http://localhost:15432",
|
databaseConnectionString: "http://localhost:15432",
|
||||||
@ -1,13 +1,13 @@
|
|||||||
const { describe, test } = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { RabbitMQContainer } = require("@testcontainers/rabbitmq");
|
const { RabbitMQContainer } = require("@testcontainers/rabbitmq");
|
||||||
const { RabbitMqMonitorType } = require("../../server/monitor-types/rabbitmq");
|
const { RabbitMqMonitorType } = require("../../../server/monitor-types/rabbitmq");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
|
|
||||||
describe("RabbitMQ Single Node", {
|
describe("RabbitMQ Single Node", {
|
||||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
|
||||||
}, () => {
|
}, () => {
|
||||||
test("RabbitMQ is running", async () => {
|
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
|
// The default timeout of 30 seconds might not be enough for the container to start
|
||||||
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
|
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
|
||||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||||
@ -33,7 +33,7 @@ describe("RabbitMQ Single Node", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("RabbitMQ is not running", async () => {
|
test("check() rejects when RabbitMQ server is not reachable", async () => {
|
||||||
const rabbitMQMonitor = new RabbitMqMonitorType();
|
const rabbitMQMonitor = new RabbitMqMonitorType();
|
||||||
const monitor = {
|
const monitor = {
|
||||||
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),
|
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),
|
||||||
@ -1,14 +1,9 @@
|
|||||||
const { describe, test } = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { TCPMonitorType } = require("../../server/monitor-types/tcp");
|
const { TCPMonitorType } = require("../../../server/monitor-types/tcp");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
const net = require("net");
|
const net = require("net");
|
||||||
|
|
||||||
/**
|
|
||||||
* Test suite for TCP Monitor functionality
|
|
||||||
* This test suite checks the behavior of the TCPMonitorType class
|
|
||||||
* under different network connection scenarios.
|
|
||||||
*/
|
|
||||||
describe("TCP Monitor", () => {
|
describe("TCP Monitor", () => {
|
||||||
/**
|
/**
|
||||||
* Creates a TCP server on a specified port
|
* Creates a TCP server on a specified port
|
||||||
@ -29,11 +24,7 @@ describe("TCP Monitor", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
test("check() sets status to UP when TCP server is reachable", async () => {
|
||||||
* Test case to verify TCP monitor works when a server is running
|
|
||||||
* Checks that the monitor correctly identifies an active TCP server
|
|
||||||
*/
|
|
||||||
test("TCP server is running", async () => {
|
|
||||||
const port = 12345;
|
const port = 12345;
|
||||||
const server = await createTCPServer(port);
|
const server = await createTCPServer(port);
|
||||||
|
|
||||||
@ -59,11 +50,7 @@ describe("TCP Monitor", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
test("check() rejects with connection failed when TCP server is not running", async () => {
|
||||||
* Test case to verify TCP monitor handles non-running servers
|
|
||||||
* Checks that the monitor correctly identifies an inactive TCP server
|
|
||||||
*/
|
|
||||||
test("TCP server is not running", async () => {
|
|
||||||
const tcpMonitor = new TCPMonitorType();
|
const tcpMonitor = new TCPMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -83,11 +70,7 @@ describe("TCP Monitor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
test("check() rejects when TLS certificate is expired or invalid", async () => {
|
||||||
* Test case to verify TCP monitor handles servers with expired or invalid TLS certificates
|
|
||||||
* Checks that the monitor correctly identifies TLS certificate issues
|
|
||||||
*/
|
|
||||||
test("TCP server with expired or invalid TLS certificate", async t => {
|
|
||||||
const tcpMonitor = new TCPMonitorType();
|
const tcpMonitor = new TCPMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -114,7 +97,7 @@ describe("TCP Monitor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("TCP server with valid TLS certificate (SSL)", async t => {
|
test("check() sets status to UP when TLS certificate is valid (SSL)", async () => {
|
||||||
const tcpMonitor = new TCPMonitorType();
|
const tcpMonitor = new TCPMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -137,7 +120,7 @@ describe("TCP Monitor", () => {
|
|||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("TCP server with valid TLS certificate (STARTTLS)", async t => {
|
test("check() sets status to UP when TLS certificate is valid (STARTTLS)", async () => {
|
||||||
const tcpMonitor = new TCPMonitorType();
|
const tcpMonitor = new TCPMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -160,7 +143,7 @@ describe("TCP Monitor", () => {
|
|||||||
assert.strictEqual(heartbeat.status, UP);
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("TCP server with valid but name mismatching TLS certificate (STARTTLS)", async t => {
|
test("check() rejects when TLS certificate hostname does not match (STARTTLS)", async () => {
|
||||||
const tcpMonitor = new TCPMonitorType();
|
const tcpMonitor = new TCPMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -185,7 +168,7 @@ describe("TCP Monitor", () => {
|
|||||||
regex
|
regex
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("XMPP server with valid certificate (STARTTLS)", async t => {
|
test("check() sets status to UP for XMPP server with valid certificate (STARTTLS)", async () => {
|
||||||
const tcpMonitor = new TCPMonitorType();
|
const tcpMonitor = new TCPMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -1,8 +1,8 @@
|
|||||||
const { WebSocketServer } = require("ws");
|
const { WebSocketServer } = require("ws");
|
||||||
const { describe, test } = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { WebSocketMonitorType } = require("../../server/monitor-types/websocket-upgrade");
|
const { WebSocketMonitorType } = require("../../../server/monitor-types/websocket-upgrade");
|
||||||
const { UP, PENDING } = require("../../src/util");
|
const { UP, PENDING } = require("../../../src/util");
|
||||||
const net = require("node:net");
|
const net = require("node:net");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,9 +22,9 @@ function nonCompliantWS(port = 8080) {
|
|||||||
return new Promise((resolve) => srv.listen(port, () => resolve(srv)));
|
return new Promise((resolve) => srv.listen(port, () => resolve(srv)));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Websocket Test", {
|
describe("WebSocket Monitor", {
|
||||||
}, () => {
|
}, () => {
|
||||||
test("Non WS Server", {}, async () => {
|
test("check() rejects with unexpected server response when connecting to non-WebSocket server", {}, async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -44,7 +44,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Secure WS", async () => {
|
test("check() sets status to UP when connecting to secure WebSocket server", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -68,7 +68,7 @@ describe("Websocket Test", {
|
|||||||
assert.deepStrictEqual(heartbeat, expected);
|
assert.deepStrictEqual(heartbeat, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Insecure WS", async (t) => {
|
test("check() sets status to UP when connecting to insecure WebSocket server", async (t) => {
|
||||||
t.after(() => wss.close());
|
t.after(() => wss.close());
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
const wss = new WebSocketServer({ port: 8080 });
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
@ -94,7 +94,7 @@ describe("Websocket Test", {
|
|||||||
assert.deepStrictEqual(heartbeat, expected);
|
assert.deepStrictEqual(heartbeat, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Non compliant WS Server wrong status code", async () => {
|
test("check() rejects when status code does not match expected value", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -115,7 +115,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Secure WS Server no status code", async () => {
|
test("check() rejects when expected status code is empty", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -136,7 +136,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Non compliant WS server without IgnoreSecWebsocket", async (t) => {
|
test("check() rejects when Sec-WebSocket-Accept header is invalid", async (t) => {
|
||||||
t.after(() => wss.close());
|
t.after(() => wss.close());
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
const wss = await nonCompliantWS();
|
const wss = await nonCompliantWS();
|
||||||
@ -159,7 +159,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Non compliant WS server with IgnoreSecWebsocket", async (t) => {
|
test("check() sets status to UP when ignoring invalid Sec-WebSocket-Accept header", async (t) => {
|
||||||
t.after(() => wss.close());
|
t.after(() => wss.close());
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
const wss = await nonCompliantWS();
|
const wss = await nonCompliantWS();
|
||||||
@ -185,7 +185,7 @@ describe("Websocket Test", {
|
|||||||
assert.deepStrictEqual(heartbeat, expected);
|
assert.deepStrictEqual(heartbeat, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Compliant WS server with IgnoreSecWebsocket", async () => {
|
test("check() sets status to UP for compliant WebSocket server when ignoring Sec-WebSocket-Accept", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -209,7 +209,7 @@ describe("Websocket Test", {
|
|||||||
assert.deepStrictEqual(heartbeat, expected);
|
assert.deepStrictEqual(heartbeat, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Non WS server with IgnoreSecWebsocket", async () => {
|
test("check() rejects non-WebSocket server even when ignoring Sec-WebSocket-Accept", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -230,7 +230,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Secure WS no subprotocol support", async () => {
|
test("check() rejects when server does not support requested subprotocol", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -252,7 +252,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Multiple subprotocols invalid input", async () => {
|
test("check() rejects when multiple subprotocols contain invalid characters", async () => {
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
|
|
||||||
const monitor = {
|
const monitor = {
|
||||||
@ -274,7 +274,7 @@ describe("Websocket Test", {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Insecure WS subprotocol multiple spaces", async (t) => {
|
test("check() sets status to UP when subprotocol with multiple spaces is accepted", async (t) => {
|
||||||
t.after(() => wss.close());
|
t.after(() => wss.close());
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
const wss = new WebSocketServer({ port: 8080,
|
const wss = new WebSocketServer({ port: 8080,
|
||||||
@ -305,7 +305,7 @@ describe("Websocket Test", {
|
|||||||
assert.deepStrictEqual(heartbeat, expected);
|
assert.deepStrictEqual(heartbeat, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Insecure WS supports one subprotocol", async (t) => {
|
test("check() sets status to UP when server supports requested subprotocol", async (t) => {
|
||||||
t.after(() => wss.close());
|
t.after(() => wss.close());
|
||||||
const websocketMonitor = new WebSocketMonitorType();
|
const websocketMonitor = new WebSocketMonitorType();
|
||||||
const wss = new WebSocketServer({ port: 8080,
|
const wss = new WebSocketServer({ port: 8080,
|
||||||
@ -1,28 +1,28 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} port Port number
|
* @param {number} port Port number
|
||||||
* @param {string} url Webhook URL
|
* @param {string} url Webhook URL
|
||||||
* @param {number} timeout Timeout
|
* @param {number} timeout Timeout
|
||||||
* @returns {Promise<object>} Webhook data
|
* @returns {Promise<object>} Webhook data
|
||||||
*/
|
*/
|
||||||
async function mockWebhook(port, url, timeout = 2500) {
|
async function mockWebhook(port, url, timeout = 2500) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const app = express();
|
const app = express();
|
||||||
const tmo = setTimeout(() => {
|
const tmo = setTimeout(() => {
|
||||||
server.close();
|
server.close();
|
||||||
reject({ reason: "Timeout" });
|
reject({ reason: "Timeout" });
|
||||||
}, timeout);
|
}, timeout);
|
||||||
app.use(bodyParser.json()); // Middleware to parse JSON bodies
|
app.use(bodyParser.json()); // Middleware to parse JSON bodies
|
||||||
app.post(`/${url}`, (req, res) => {
|
app.post(`/${url}`, (req, res) => {
|
||||||
res.status(200).send("OK");
|
res.status(200).send("OK");
|
||||||
server.close();
|
server.close();
|
||||||
tmo && clearTimeout(tmo);
|
tmo && clearTimeout(tmo);
|
||||||
resolve(req.body);
|
resolve(req.body);
|
||||||
});
|
});
|
||||||
const server = app.listen(port);
|
const server = app.listen(port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = mockWebhook;
|
module.exports = mockWebhook;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const { test } = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
|
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
|
|
||||||
@ -36,12 +36,14 @@ iPenGDCg1awOyRnvxNq1MtMDkR9AHwksukzwiYNexYjyvE2t0UzXhFXwazQ3
|
|||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
`;
|
`;
|
||||||
|
|
||||||
test("Certificate and hostname match", () => {
|
describe("Certificate Hostname Validation", () => {
|
||||||
const result = checkCertificateHostname(testCert, "www.eff.org");
|
test("checkCertificateHostname() returns true when certificate matches hostname", () => {
|
||||||
assert.strictEqual(result, true);
|
const result = checkCertificateHostname(testCert, "www.eff.org");
|
||||||
});
|
assert.strictEqual(result, true);
|
||||||
|
});
|
||||||
|
|
||||||
test("Certificate and hostname mismatch", () => {
|
test("checkCertificateHostname() returns false when certificate does not match hostname", () => {
|
||||||
const result = checkCertificateHostname(testCert, "example.com");
|
const result = checkCertificateHostname(testCert, "example.com");
|
||||||
assert.strictEqual(result, false);
|
assert.strictEqual(result, false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
process.env.UPTIME_KUMA_HIDE_LOG = [ "info_db", "info_server" ].join(",");
|
process.env.UPTIME_KUMA_HIDE_LOG = [ "info_db", "info_server" ].join(",");
|
||||||
|
|
||||||
const test = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const DomainExpiry = require("../../server/model/domain_expiry");
|
const DomainExpiry = require("../../server/model/domain_expiry");
|
||||||
const mockWebhook = require("../mock-webhook");
|
const mockWebhook = require("./notification-providers/mock-webhook");
|
||||||
const TestDB = require("../mock-testdb");
|
const TestDB = require("../mock-testdb");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { Notification } = require("../../server/notification");
|
const { Notification } = require("../../server/notification");
|
||||||
@ -12,30 +12,34 @@ const { setSetting } = require("../../server/util-server");
|
|||||||
|
|
||||||
const testDb = new TestDB();
|
const testDb = new TestDB();
|
||||||
|
|
||||||
test("Domain Expiry", async (t) => {
|
describe("Domain Expiry", () => {
|
||||||
await testDb.create();
|
|
||||||
Notification.init();
|
|
||||||
|
|
||||||
const monHttpCom = {
|
const monHttpCom = {
|
||||||
type: "http",
|
type: "http",
|
||||||
url: "https://www.google.com",
|
url: "https://www.google.com",
|
||||||
domainExpiryNotification: true
|
domainExpiryNotification: true
|
||||||
};
|
};
|
||||||
await t.test("Should get expiry date for .wiki with no A record", async () => {
|
|
||||||
|
test("getExpiryDate() returns correct expiry date for .wiki domain with no A record", async () => {
|
||||||
|
await testDb.create();
|
||||||
|
Notification.init();
|
||||||
|
|
||||||
const d = DomainExpiry.createByName("google.wiki");
|
const d = DomainExpiry.createByName("google.wiki");
|
||||||
assert.deepEqual(await d.getExpiryDate(), new Date("2026-11-26T23:59:59.000Z"));
|
assert.deepEqual(await d.getExpiryDate(), new Date("2026-11-26T23:59:59.000Z"));
|
||||||
});
|
});
|
||||||
await t.test("Should get expiration date for .com from RDAP", async () => {
|
|
||||||
|
test("forMonitor() retrieves expiration date for .com domain from RDAP", async () => {
|
||||||
const domain = await DomainExpiry.forMonitor(monHttpCom);
|
const domain = await DomainExpiry.forMonitor(monHttpCom);
|
||||||
const expiryFromRdap = await domain.getExpiryDate(); // from RDAP
|
const expiryFromRdap = await domain.getExpiryDate(); // from RDAP
|
||||||
assert.deepEqual(expiryFromRdap, new Date("2028-09-14T04:00:00.000Z"));
|
assert.deepEqual(expiryFromRdap, new Date("2028-09-14T04:00:00.000Z"));
|
||||||
});
|
});
|
||||||
await t.test("Should have expiration date cached in database", async () => {
|
|
||||||
|
test("checkExpiry() caches expiration date in database", async () => {
|
||||||
await DomainExpiry.checkExpiry(monHttpCom); // RDAP -> Cache
|
await DomainExpiry.checkExpiry(monHttpCom); // RDAP -> Cache
|
||||||
const domain = await DomainExpiry.findByName("google.com");
|
const domain = await DomainExpiry.findByName("google.com");
|
||||||
assert(Date.now() - domain.lastCheck < 5 * 1000);
|
assert(Date.now() - domain.lastCheck < 5 * 1000);
|
||||||
});
|
});
|
||||||
await t.test("Should trigger notify for expiring domain", async () => {
|
|
||||||
|
test("sendNotifications() triggers notification for expiring domain", async () => {
|
||||||
await DomainExpiry.findByName("google.com");
|
await DomainExpiry.findByName("google.com");
|
||||||
const hook = {
|
const hook = {
|
||||||
"port": 3010,
|
"port": 3010,
|
||||||
@ -60,10 +64,10 @@ test("Domain Expiry", async (t) => {
|
|||||||
mockWebhook(hook.port, hook.url)
|
mockWebhook(hook.port, hook.url)
|
||||||
]);
|
]);
|
||||||
assert.match(data.msg, /will expire in/);
|
assert.match(data.msg, /will expire in/);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
Settings.stopCacheCleaner();
|
||||||
|
await testDb.destroy();
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
}).finally(() => {
|
|
||||||
setTimeout(async () => {
|
|
||||||
Settings.stopCacheCleaner();
|
|
||||||
await testDb.destroy();
|
|
||||||
}, 200);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
const test = require("node:test");
|
|
||||||
const assert = require("node:assert");
|
|
||||||
const dayjs = require("dayjs");
|
|
||||||
const { SQL_DATETIME_FORMAT } = require("../../src/util");
|
|
||||||
|
|
||||||
dayjs.extend(require("dayjs/plugin/utc"));
|
|
||||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for maintenance date formatting to ensure compatibility with MariaDB/MySQL.
|
|
||||||
* Issue: MariaDB rejects ISO format dates like '2025-12-19T01:04:02.129Z'
|
|
||||||
* Fix: Use SQL_DATETIME_FORMAT ('YYYY-MM-DD HH:mm:ss') instead of toISOString()
|
|
||||||
*/
|
|
||||||
test("Maintenance Date Format - MariaDB Compatibility", async (t) => {
|
|
||||||
|
|
||||||
await t.test("SQL_DATETIME_FORMAT constant should match MariaDB format", async () => {
|
|
||||||
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.test("Format date using SQL_DATETIME_FORMAT", async () => {
|
|
||||||
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
|
||||||
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
|
||||||
|
|
||||||
assert.strictEqual(sqlFormat, "2025-12-19 01:04:02");
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.test("SQL format should not contain ISO markers (T, Z)", async () => {
|
|
||||||
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
|
||||||
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
|
||||||
|
|
||||||
assert.strictEqual(sqlFormat.includes("T"), false, "SQL format should not contain 'T'");
|
|
||||||
assert.strictEqual(sqlFormat.includes("Z"), false, "SQL format should not contain 'Z'");
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.test("SQL format should match YYYY-MM-DD HH:mm:ss pattern", async () => {
|
|
||||||
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
|
||||||
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
|
||||||
const sqlDateTimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
||||||
|
|
||||||
assert.strictEqual(sqlDateTimeRegex.test(sqlFormat), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.test("Parse SQL datetime back to dayjs preserves timestamp", async () => {
|
|
||||||
const originalDate = dayjs.utc("2025-12-19T01:04:02.000Z");
|
|
||||||
const sqlFormat = originalDate.utc().format(SQL_DATETIME_FORMAT);
|
|
||||||
const parsedDate = dayjs.utc(sqlFormat, SQL_DATETIME_FORMAT);
|
|
||||||
|
|
||||||
assert.strictEqual(parsedDate.unix(), originalDate.unix());
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.test("Edge case: midnight timestamp", async () => {
|
|
||||||
const midnight = dayjs.utc("2025-01-01T00:00:00.000Z");
|
|
||||||
const sqlFormat = midnight.utc().format(SQL_DATETIME_FORMAT);
|
|
||||||
|
|
||||||
assert.strictEqual(sqlFormat, "2025-01-01 00:00:00");
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.test("Edge case: end of day timestamp", async () => {
|
|
||||||
const endOfDay = dayjs.utc("2025-12-31T23:59:59.999Z");
|
|
||||||
const sqlFormat = endOfDay.utc().format(SQL_DATETIME_FORMAT);
|
|
||||||
|
|
||||||
assert.strictEqual(sqlFormat, "2025-12-31 23:59:59");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -3,8 +3,8 @@ const fs = require("fs");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { GenericContainer, Wait } = require("testcontainers");
|
const { GenericContainer, Wait } = require("testcontainers");
|
||||||
|
|
||||||
describe("Database Migration - Optimize Important Indexes", () => {
|
describe("Database Migration", () => {
|
||||||
test("SQLite: All migrations run successfully", async () => {
|
test("SQLite migrations run successfully from fresh database", async () => {
|
||||||
const testDbPath = path.join(__dirname, "../../data/test-migration.db");
|
const testDbPath = path.join(__dirname, "../../data/test-migration.db");
|
||||||
const testDbDir = path.dirname(testDbPath);
|
const testDbDir = path.dirname(testDbPath);
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ describe("Database Migration - Optimize Important Indexes", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"MariaDB: All migrations run successfully",
|
"MariaDB migrations run successfully from fresh database",
|
||||||
{
|
{
|
||||||
skip:
|
skip:
|
||||||
!!process.env.CI &&
|
!!process.env.CI &&
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const test = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const { UptimeCalculator } = require("../../server/uptime-calculator");
|
const { UptimeCalculator } = require("../../server/uptime-calculator");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
@ -7,344 +7,403 @@ dayjs.extend(require("dayjs/plugin/utc"));
|
|||||||
dayjs.extend(require("../../server/modules/dayjs/plugin/timezone"));
|
dayjs.extend(require("../../server/modules/dayjs/plugin/timezone"));
|
||||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||||
|
|
||||||
test("Test Uptime Calculator - custom date", async (t) => {
|
describe("Uptime Calculator", () => {
|
||||||
let c1 = new UptimeCalculator();
|
test("getCurrentDate() returns custom date when set", () => {
|
||||||
|
let c1 = new UptimeCalculator();
|
||||||
|
|
||||||
// Test custom date
|
// Test custom date
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2021-01-01T00:00:00.000Z");
|
UptimeCalculator.currentDate = dayjs.utc("2021-01-01T00:00:00.000Z");
|
||||||
assert.strictEqual(c1.getCurrentDate().unix(), dayjs.utc("2021-01-01T00:00:00.000Z").unix());
|
assert.strictEqual(c1.getCurrentDate().unix(), dayjs.utc("2021-01-01T00:00:00.000Z").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test update - UP", async (t) => {
|
test("update() with UP status returns correct timestamp", async () => {
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let date = await c2.update(UP);
|
let date = await c2.update(UP);
|
||||||
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:46:59").unix());
|
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:46:59").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test update - MAINTENANCE", async (t) => {
|
test("update() with MAINTENANCE status returns correct timestamp", async () => {
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let date = await c2.update(MAINTENANCE);
|
let date = await c2.update(MAINTENANCE);
|
||||||
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
|
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test update - DOWN", async (t) => {
|
test("update() with DOWN status returns correct timestamp", async () => {
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let date = await c2.update(DOWN);
|
let date = await c2.update(DOWN);
|
||||||
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
|
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test update - PENDING", async (t) => {
|
test("update() with PENDING status returns correct timestamp", async () => {
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:47:20");
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let date = await c2.update(PENDING);
|
let date = await c2.update(PENDING);
|
||||||
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
|
assert.strictEqual(date.unix(), dayjs.utc("2023-08-12 20:47:20").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test flatStatus", async (t) => {
|
test("flatStatus() converts statuses correctly", () => {
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
assert.strictEqual(c2.flatStatus(UP), UP);
|
assert.strictEqual(c2.flatStatus(UP), UP);
|
||||||
//assert.strictEqual(c2.flatStatus(MAINTENANCE), UP);
|
//assert.strictEqual(c2.flatStatus(MAINTENANCE), UP);
|
||||||
assert.strictEqual(c2.flatStatus(DOWN), DOWN);
|
assert.strictEqual(c2.flatStatus(DOWN), DOWN);
|
||||||
assert.strictEqual(c2.flatStatus(PENDING), DOWN);
|
assert.strictEqual(c2.flatStatus(PENDING), DOWN);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test getMinutelyKey", async (t) => {
|
test("getMinutelyKey() returns correct timestamp for start of minute", () => {
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:00"));
|
let divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:00"));
|
||||||
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
|
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
|
||||||
|
|
||||||
// Edge case 1
|
// Edge case 1
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:01"));
|
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:01"));
|
||||||
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
|
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
|
||||||
|
|
||||||
// Edge case 2
|
// Edge case 2
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:59"));
|
divisionKey = c2.getMinutelyKey(dayjs.utc("2023-08-12 20:46:59"));
|
||||||
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
|
assert.strictEqual(divisionKey, dayjs.utc("2023-08-12 20:46:00").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test getDailyKey", async (t) => {
|
test("getDailyKey() returns correct timestamp for start of day", () => {
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 20:46:00"));
|
let dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 20:46:00"));
|
||||||
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
||||||
|
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:45:30"));
|
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:45:30"));
|
||||||
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
||||||
|
|
||||||
// Edge case 1
|
// Edge case 1
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:59:59"));
|
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 23:59:59"));
|
||||||
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
||||||
|
|
||||||
// Edge case 2
|
// Edge case 2
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 00:00:00"));
|
dailyKey = c2.getDailyKey(dayjs.utc("2023-08-12 00:00:00"));
|
||||||
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
assert.strictEqual(dailyKey, dayjs.utc("2023-08-12").unix());
|
||||||
|
|
||||||
// Test timezone
|
// Test timezone
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
dailyKey = c2.getDailyKey(dayjs("Sat Dec 23 2023 05:38:39 GMT+0800 (Hong Kong Standard Time)"));
|
dailyKey = c2.getDailyKey(dayjs("Sat Dec 23 2023 05:38:39 GMT+0800 (Hong Kong Standard Time)"));
|
||||||
assert.strictEqual(dailyKey, dayjs.utc("2023-12-22").unix());
|
assert.strictEqual(dailyKey, dayjs.utc("2023-12-22").unix());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test lastDailyUptimeData", async (t) => {
|
test("lastDailyUptimeData tracks UP status correctly", async () => {
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
assert.strictEqual(c2.lastDailyUptimeData.up, 1);
|
assert.strictEqual(c2.lastDailyUptimeData.up, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test get24Hour Uptime and Avg Ping", async (t) => {
|
test("get24Hour() calculates uptime and average ping correctly", async () => {
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||||
|
|
||||||
// No data
|
// No data
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let data = c2.get24Hour();
|
let data = c2.get24Hour();
|
||||||
assert.strictEqual(data.uptime, 0);
|
assert.strictEqual(data.uptime, 0);
|
||||||
assert.strictEqual(data.avgPing, null);
|
assert.strictEqual(data.avgPing, null);
|
||||||
|
|
||||||
// 1 Up
|
// 1 Up
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP, 100);
|
await c2.update(UP, 100);
|
||||||
let uptime = c2.get24Hour().uptime;
|
let uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 1);
|
assert.strictEqual(uptime, 1);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 100);
|
assert.strictEqual(c2.get24Hour().avgPing, 100);
|
||||||
|
|
||||||
// 2 Up
|
// 2 Up
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP, 100);
|
await c2.update(UP, 100);
|
||||||
await c2.update(UP, 200);
|
await c2.update(UP, 200);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 1);
|
assert.strictEqual(uptime, 1);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 150);
|
assert.strictEqual(c2.get24Hour().avgPing, 150);
|
||||||
|
|
||||||
// 3 Up
|
// 3 Up
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP, 0);
|
await c2.update(UP, 0);
|
||||||
await c2.update(UP, 100);
|
await c2.update(UP, 100);
|
||||||
await c2.update(UP, 400);
|
await c2.update(UP, 400);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 1);
|
assert.strictEqual(uptime, 1);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 166.66666666666666);
|
assert.strictEqual(c2.get24Hour().avgPing, 166.66666666666666);
|
||||||
|
|
||||||
// 1 MAINTENANCE
|
// 1 MAINTENANCE
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(MAINTENANCE);
|
await c2.update(MAINTENANCE);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, null);
|
assert.strictEqual(c2.get24Hour().avgPing, null);
|
||||||
|
|
||||||
// 1 PENDING
|
// 1 PENDING
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(PENDING);
|
await c2.update(PENDING);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, null);
|
assert.strictEqual(c2.get24Hour().avgPing, null);
|
||||||
|
|
||||||
// 1 DOWN
|
// 1 DOWN
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, null);
|
assert.strictEqual(c2.get24Hour().avgPing, null);
|
||||||
|
|
||||||
// 2 DOWN
|
// 2 DOWN
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, null);
|
assert.strictEqual(c2.get24Hour().avgPing, null);
|
||||||
|
|
||||||
// 1 DOWN, 1 UP
|
// 1 DOWN, 1 UP
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
await c2.update(UP, 0.5);
|
await c2.update(UP, 0.5);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0.5);
|
assert.strictEqual(uptime, 0.5);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 0.5);
|
assert.strictEqual(c2.get24Hour().avgPing, 0.5);
|
||||||
|
|
||||||
// 1 UP, 1 DOWN
|
// 1 UP, 1 DOWN
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP, 123);
|
await c2.update(UP, 123);
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0.5);
|
assert.strictEqual(uptime, 0.5);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 123);
|
assert.strictEqual(c2.get24Hour().avgPing, 123);
|
||||||
|
|
||||||
// Add 24 hours
|
// Add 24 hours
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP, 0);
|
await c2.update(UP, 0);
|
||||||
await c2.update(UP, 0);
|
await c2.update(UP, 0);
|
||||||
await c2.update(UP, 0);
|
await c2.update(UP, 0);
|
||||||
await c2.update(UP, 1);
|
await c2.update(UP, 1);
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0.8);
|
assert.strictEqual(uptime, 0.8);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
|
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
|
||||||
|
|
||||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
|
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
|
||||||
|
|
||||||
// After 24 hours, even if there is no data, the uptime should be still 80%
|
// After 24 hours, even if there is no data, the uptime should be still 80%
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0.8);
|
assert.strictEqual(uptime, 0.8);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
|
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
|
||||||
|
|
||||||
// Add more 24 hours (48 hours)
|
// Add more 24 hours (48 hours)
|
||||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
|
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(24, "hour");
|
||||||
|
|
||||||
// After 48 hours, even if there is no data, the uptime should be still 80%
|
// After 48 hours, even if there is no data, the uptime should be still 80%
|
||||||
uptime = c2.get24Hour().uptime;
|
uptime = c2.get24Hour().uptime;
|
||||||
assert.strictEqual(uptime, 0.8);
|
assert.strictEqual(uptime, 0.8);
|
||||||
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
|
assert.strictEqual(c2.get24Hour().avgPing, 0.25);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test get7DayUptime", async (t) => {
|
test("get7Day() calculates 7-day uptime correctly", async () => {
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||||
|
|
||||||
// No data
|
// No data
|
||||||
let c2 = new UptimeCalculator();
|
let c2 = new UptimeCalculator();
|
||||||
let uptime = c2.get7Day().uptime;
|
let uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
|
|
||||||
// 1 Up
|
// 1 Up
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 1);
|
assert.strictEqual(uptime, 1);
|
||||||
|
|
||||||
// 2 Up
|
// 2 Up
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 1);
|
assert.strictEqual(uptime, 1);
|
||||||
|
|
||||||
// 3 Up
|
// 3 Up
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 1);
|
assert.strictEqual(uptime, 1);
|
||||||
|
|
||||||
// 1 MAINTENANCE
|
// 1 MAINTENANCE
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(MAINTENANCE);
|
await c2.update(MAINTENANCE);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
|
|
||||||
// 1 PENDING
|
// 1 PENDING
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(PENDING);
|
await c2.update(PENDING);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
|
|
||||||
// 1 DOWN
|
// 1 DOWN
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
|
|
||||||
// 2 DOWN
|
// 2 DOWN
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0);
|
assert.strictEqual(uptime, 0);
|
||||||
|
|
||||||
// 1 DOWN, 1 UP
|
// 1 DOWN, 1 UP
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0.5);
|
assert.strictEqual(uptime, 0.5);
|
||||||
|
|
||||||
// 1 UP, 1 DOWN
|
// 1 UP, 1 DOWN
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0.5);
|
assert.strictEqual(uptime, 0.5);
|
||||||
|
|
||||||
// Add 7 days
|
// Add 7 days
|
||||||
c2 = new UptimeCalculator();
|
c2 = new UptimeCalculator();
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(UP);
|
await c2.update(UP);
|
||||||
await c2.update(DOWN);
|
await c2.update(DOWN);
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0.8);
|
assert.strictEqual(uptime, 0.8);
|
||||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(7, "day");
|
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(7, "day");
|
||||||
|
|
||||||
// After 7 days, even if there is no data, the uptime should be still 80%
|
// After 7 days, even if there is no data, the uptime should be still 80%
|
||||||
uptime = c2.get7Day().uptime;
|
uptime = c2.get7Day().uptime;
|
||||||
assert.strictEqual(uptime, 0.8);
|
assert.strictEqual(uptime, 0.8);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
test("get30Day() calculates 30-day uptime correctly with 1 check per day", async () => {
|
||||||
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||||
|
|
||||||
test("Test get30DayUptime (1 check per day)", async (t) => {
|
let c2 = new UptimeCalculator();
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
let uptime = c2.get30Day().uptime;
|
||||||
|
assert.strictEqual(uptime, 0);
|
||||||
|
|
||||||
let c2 = new UptimeCalculator();
|
let up = 0;
|
||||||
let uptime = c2.get30Day().uptime;
|
let down = 0;
|
||||||
assert.strictEqual(uptime, 0);
|
let flip = true;
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
|
||||||
|
|
||||||
let up = 0;
|
if (flip) {
|
||||||
let down = 0;
|
await c2.update(UP);
|
||||||
let flip = true;
|
up++;
|
||||||
for (let i = 0; i < 30; i++) {
|
} else {
|
||||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
|
await c2.update(DOWN);
|
||||||
|
down++;
|
||||||
|
}
|
||||||
|
|
||||||
if (flip) {
|
uptime = c2.get30Day().uptime;
|
||||||
await c2.update(UP);
|
assert.strictEqual(uptime, up / (up + down));
|
||||||
up++;
|
|
||||||
} else {
|
flip = !flip;
|
||||||
await c2.update(DOWN);
|
|
||||||
down++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uptime = c2.get30Day().uptime;
|
// Last 7 days
|
||||||
assert.strictEqual(uptime, up / (up + down));
|
// Down, Up, Down, Up, Down, Up, Down
|
||||||
|
// So 3 UP
|
||||||
|
assert.strictEqual(c2.get7Day().uptime, 3 / 7);
|
||||||
|
});
|
||||||
|
|
||||||
flip = !flip;
|
test("get1Year() calculates 1-year uptime correctly with 1 check per day", async () => {
|
||||||
}
|
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
||||||
|
|
||||||
// Last 7 days
|
let c2 = new UptimeCalculator();
|
||||||
// Down, Up, Down, Up, Down, Up, Down
|
let uptime = c2.get1Year().uptime;
|
||||||
// So 3 UP
|
assert.strictEqual(uptime, 0);
|
||||||
assert.strictEqual(c2.get7Day().uptime, 3 / 7);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test get1YearUptime (1 check per day)", async (t) => {
|
let flip = true;
|
||||||
UptimeCalculator.currentDate = dayjs.utc("2023-08-12 20:46:59");
|
for (let i = 0; i < 365; i++) {
|
||||||
|
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
|
||||||
|
|
||||||
let c2 = new UptimeCalculator();
|
if (flip) {
|
||||||
let uptime = c2.get1Year().uptime;
|
await c2.update(UP);
|
||||||
assert.strictEqual(uptime, 0);
|
} else {
|
||||||
|
await c2.update(DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
let flip = true;
|
uptime = c2.get30Day().time;
|
||||||
for (let i = 0; i < 365; i++) {
|
flip = !flip;
|
||||||
UptimeCalculator.currentDate = UptimeCalculator.currentDate.add(1, "day");
|
|
||||||
|
|
||||||
if (flip) {
|
|
||||||
await c2.update(UP);
|
|
||||||
} else {
|
|
||||||
await c2.update(DOWN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uptime = c2.get30Day().time;
|
assert.strictEqual(c2.get1Year().uptime, 183 / 365);
|
||||||
flip = !flip;
|
assert.strictEqual(c2.get30Day().uptime, 15 / 30);
|
||||||
}
|
assert.strictEqual(c2.get7Day().uptime, 4 / 7);
|
||||||
|
});
|
||||||
|
|
||||||
assert.strictEqual(c2.get1Year().uptime, 183 / 365);
|
describe("Worst case scenario", () => {
|
||||||
assert.strictEqual(c2.get30Day().uptime, 15 / 30);
|
test("handles year-long simulation with various statuses", {
|
||||||
assert.strictEqual(c2.get7Day().uptime, 4 / 7);
|
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"));
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -362,64 +421,3 @@ function memoryUsage() {
|
|||||||
external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`,
|
external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test("Worst case", async (t) => {
|
|
||||||
|
|
||||||
// Disable on GitHub Actions, as it is not stable on it
|
|
||||||
if (process.env.GITHUB_ACTIONS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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"));
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@ -1,18 +1,46 @@
|
|||||||
const test = require("node:test");
|
const { describe, test } = require("node:test");
|
||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
const { getDaysRemaining, getDaysBetween } = require("../../server/util-server");
|
const { getDaysRemaining, getDaysBetween } = require("../../server/util-server");
|
||||||
|
const { SQL_DATETIME_FORMAT } = require("../../src/util");
|
||||||
|
|
||||||
test("Test getDaysBetween", async (t) => {
|
dayjs.extend(require("dayjs/plugin/utc"));
|
||||||
let days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
|
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||||
assert.strictEqual(days, 3);
|
|
||||||
days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
|
|
||||||
assert.strictEqual(days, 368);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test getDaysRemaining", async (t) => {
|
describe("Server Utilities", () => {
|
||||||
let days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
|
test("getDaysBetween() calculates days between dates within same month", () => {
|
||||||
assert.strictEqual(days, 3);
|
const days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
|
||||||
days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
|
assert.strictEqual(days, 3);
|
||||||
assert.strictEqual(days, -3);
|
});
|
||||||
|
|
||||||
|
test("getDaysBetween() calculates days between dates across years", () => {
|
||||||
|
const days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
|
||||||
|
assert.strictEqual(days, 368);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getDaysRemaining() returns positive value when target date is in future", () => {
|
||||||
|
const days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
|
||||||
|
assert.strictEqual(days, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getDaysRemaining() returns negative value when target date is in past", () => {
|
||||||
|
const days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
|
||||||
|
assert.strictEqual(days, -3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => {
|
||||||
|
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SQL_DATETIME_FORMAT produces valid SQL datetime string", () => {
|
||||||
|
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
||||||
|
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
||||||
|
|
||||||
|
assert.strictEqual(sqlFormat, "2025-12-19 01:04:02");
|
||||||
|
|
||||||
|
// Verify it can be parsed back
|
||||||
|
const parsedDate = dayjs.utc(sqlFormat, SQL_DATETIME_FORMAT);
|
||||||
|
assert.strictEqual(parsedDate.unix(), current.unix());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user