diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..90eb809ab
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+# Dependabot configuration for Uptime Kuma
+# See: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ # Enable version updates for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ # Group all GitHub Actions updates into a single PR
+ groups:
+ github-actions:
+ patterns:
+ - "*"
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+ cooldown:
+ default-days: 7
diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index f0dfdfa55..e86d7dd00 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -12,16 +12,18 @@ on:
branches: [ master, 1.23.X ]
paths-ignore:
- '*.md'
+permissions: {}
jobs:
auto-test:
- needs: [ e2e-test ]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
+ permissions:
+ contents: read
strategy:
matrix:
- os: [macos-latest, ubuntu-22.04, windows-latest, ARM64]
+ os: [macos-latest, ubuntu-22.04, windows-latest, ubuntu-22.04-arm]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node: [ 20, 24 ]
# Also test non-LTS, but only on Ubuntu.
@@ -31,20 +33,26 @@ jobs:
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- name: Cache/Restore node_modules
- uses: actions/cache@v4
+ uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
id: node-modules-cache
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Use Node.js ${{ matrix.node }}
- uses: actions/setup-node@v6
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ matrix.node }}
- run: npm install
+
+ - name: Rebuild native modules for ARM64
+ if: matrix.os == 'ubuntu-22.04-arm'
+ run: npm rebuild @louislam/sqlite3
+
- run: npm run build
- run: npm run test-backend
env:
@@ -53,75 +61,84 @@ jobs:
# As a lot of dev dependencies are not supported on ARMv7, we have to test it separately and just test if `npm ci --production` works
armv7-simple-test:
- needs: [ e2e-test ]
- runs-on: ${{ matrix.os }}
- timeout-minutes: 15
- if: ${{ github.repository == 'louislam/uptime-kuma' }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
strategy:
matrix:
- os: [ ARMv7 ]
node: [ 20, 22 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- - run: git config --global core.autocrlf false # Mainly for Windows
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- - name: Cache/Restore node_modules
- uses: actions/cache@v4
- id: node-modules-cache
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
- path: node_modules
- key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
+ platforms: linux/arm/v7
- - name: Use Node.js ${{ matrix.node }}
- uses: actions/setup-node@v6
- with:
- node-version: ${{ matrix.node }}
- - run: npm install --production
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
+
+ - name: Test on ARMv7 using Docker with QEMU
+ run: |
+ docker run --rm --platform linux/arm/v7 \
+ -v $PWD:/workspace \
+ -w /workspace \
+ arm32v7/node:${{ matrix.node }}-slim \
+ bash -c "npm install --production"
check-linters:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- name: Cache/Restore node_modules
- uses: actions/cache@v4
+ uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
id: node-modules-cache
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Use Node.js 20
- uses: actions/setup-node@v6
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 20
- run: npm install
- run: npm run lint:prod
e2e-test:
- needs: [ check-linters ]
- runs-on: ARM64
+ runs-on: ubuntu-22.04-arm
+ permissions:
+ contents: read
env:
PLAYWRIGHT_VERSION: ~1.39.0
steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- name: Cache/Restore node_modules
- uses: actions/cache@v4
+ uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
id: node-modules-cache
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Setup Node.js
- uses: actions/setup-node@v6
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22
- run: npm install
+
+ - name: Rebuild native modules for ARM64
+ run: npm rebuild @louislam/sqlite3
- name: Install Playwright ${{ env.PLAYWRIGHT_VERSION }}
run: npx playwright@${{ env.PLAYWRIGHT_VERSION }} install
diff --git a/.github/workflows/close-incorrect-issue.yml b/.github/workflows/close-incorrect-issue.yml
index f618cd7c2..aa7113ed7 100644
--- a/.github/workflows/close-incorrect-issue.yml
+++ b/.github/workflows/close-incorrect-issue.yml
@@ -3,10 +3,13 @@ name: Close Incorrect Issue
on:
issues:
types: [opened]
+permissions: {}
jobs:
close-incorrect-issue:
runs-on: ${{ matrix.os }}
+ permissions:
+ issues: write
strategy:
matrix:
@@ -14,11 +17,15 @@ jobs:
node-version: [20]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v6
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- - run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}
+ - name: Close incorrect issue
+ run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} "$ISSUE_USER_LOGIN"
+ env:
+ ISSUE_USER_LOGIN: ${{ github.event.issue.user.login }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 0e3b72c4b..aa0b3c860 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -25,19 +25,19 @@ jobs:
language: [ 'go', 'javascript-typescript' ]
steps:
- - name: Checkout repository
- uses: actions/checkout@v3
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/conflict_labeler.yml b/.github/workflows/conflict_labeler.yml
index fdcc9c551..65634d11e 100644
--- a/.github/workflows/conflict_labeler.yml
+++ b/.github/workflows/conflict_labeler.yml
@@ -1,6 +1,11 @@
name: Merge Conflict Labeler
-on:
+# pull_request_target is safe here because:
+# 1. Only uses a pinned trusted action (by SHA)
+# 2. Has minimal permissions (contents: read, pull-requests: write)
+# 3. Doesn't checkout or execute any untrusted code from PRs
+# 4. Only adds/removes labels based on merge conflict status
+on: # zizmor: ignore[dangerous-triggers]
push:
branches:
- master
@@ -19,7 +24,7 @@ jobs:
pull-requests: write
steps:
- name: Apply label
- uses: eps1lon/actions-label-merge-conflict@v3
+ uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
with:
dirtyLabel: 'needs:resolve-merge-conflict'
repoToken: '${{ secrets.GITHUB_TOKEN }}'
diff --git a/.github/workflows/prevent-file-change.yml b/.github/workflows/prevent-file-change.yml
index 0af3a6cbf..3c48dec1b 100644
--- a/.github/workflows/prevent-file-change.yml
+++ b/.github/workflows/prevent-file-change.yml
@@ -2,13 +2,16 @@ name: prevent-file-change
on:
pull_request:
+permissions: {}
jobs:
check-file-changes:
runs-on: ubuntu-latest
+ permissions:
+ pull-requests: read
steps:
- name: Prevent file change
- uses: xalvarez/prevent-file-change-action@v1
+ uses: xalvarez/prevent-file-change-action@004d9f17c2e4a7afa037cda5f38dc55a5e9c9c06 # v1.9.1
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Regex, /src/lang/*.json is not allowed to be changed, except for /src/lang/en.json
diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml
index 60eca6403..8cb8dd55d 100644
--- a/.github/workflows/stale-bot.yml
+++ b/.github/workflows/stale-bot.yml
@@ -4,12 +4,16 @@ on:
schedule:
- cron: '0 */6 * * *'
#Run every 6 hours
+permissions: {}
jobs:
stale:
runs-on: ubuntu-latest
+ permissions:
+ actions: write
+ issues: write
steps:
- - uses: actions/stale@v9
+ - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
stale-issue-message: |-
We are clearing up our old `help`-issues and your issue has been open for 60 days with no activity.
@@ -18,10 +22,10 @@ jobs:
days-before-close: 7
days-before-pr-stale: -1
days-before-pr-close: -1
- exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request'
+ exempt-issue-labels: 'News,discussion,bug,doc,feature-request'
exempt-issue-assignees: 'louislam'
operations-per-run: 200
- - uses: actions/stale@v9
+ - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
stale-issue-message: |-
This issue was marked as `cannot-reproduce` by a maintainer.
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 4dff3689d..3da0f9060 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -8,20 +8,21 @@ on:
- master
- 1.23.X
workflow_dispatch:
-
-permissions:
- contents: read
- pull-requests: write # enable write permissions for pull request comments
+permissions: {}
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write # enable write permissions for pull request comments
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- name: json-yaml-validate
id: json-yaml-validate
- uses: GrantBirki/json-yaml-validate@v2.4.0
+ uses: GrantBirki/json-yaml-validate@9bbaa8474e3af4e91f25eda8ac194fdc30564d96 # v4.0.0
with:
comment: "true" # enable comment mode
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
@@ -29,10 +30,13 @@ jobs:
# General validations
validate:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with: { persist-credentials: false }
- name: Use Node.js 20
- uses: actions/setup-node@v6
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 20
diff --git a/server/model/domain_expiry.js b/server/model/domain_expiry.js
index b796a81d1..d365c03ab 100644
--- a/server/model/domain_expiry.js
+++ b/server/model/domain_expiry.js
@@ -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);
}
diff --git a/server/model/status_page.js b/server/model/status_page.js
index 224441127..7087e4e09 100644
--- a/server/model/status_page.js
+++ b/server/model/status_page.js
@@ -132,6 +132,9 @@ class StatusPage extends BeanModel {
let ogDescription = $("").attr("content", description155);
head.append(ogDescription);
+ let ogType = $("");
+ head.append(ogType);
+
// Preload data
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
diff --git a/src/lang/en.json b/src/lang/en.json
index 3d01267e7..b6cefcefb 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1223,5 +1223,6 @@
"labelDomainNameExpiryNotification": "Domain Name Expiry Notification",
"domainExpiryDescription": "Trigger notification when domain names expires in:",
"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"
}
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index 74a5c3f66..7b2426a3c 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -138,6 +138,9 @@
+
@@ -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
* @returns {void}
@@ -1181,6 +1198,58 @@ footer {
cursor: pointer;
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 {
diff --git a/test/backend-test/README.md b/test/backend-test/README.md
index 775ffb7a8..8822562ab 100644
--- a/test/backend-test/README.md
+++ b/test/backend-test/README.md
@@ -4,14 +4,26 @@ Documentation: https://nodejs.org/api/test.html
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
```js
-const test = require("node:test");
+const { describe, test } = require("node:test");
const assert = require("node:assert");
-test("Test name", async (t) => {
- assert.strictEqual(1, 1);
+describe("Feature Name", () => {
+ test("function() returns expected value when condition is met", () => {
+ assert.strictEqual(1, 1);
+ });
});
```
diff --git a/test/backend-test/monitor-conditions/test-evaluator.js b/test/backend-test/monitor-conditions/test-evaluator.js
index da7c7fabf..c731a6792 100644
--- a/test/backend-test/monitor-conditions/test-evaluator.js
+++ b/test/backend-test/monitor-conditions/test-evaluator.js
@@ -1,46 +1,48 @@
-const test = require("node:test");
+const { describe, test } = require("node:test");
const assert = require("node:assert");
const { ConditionExpressionGroup, ConditionExpression, LOGICAL } = require("../../../server/monitor-conditions/expression.js");
const { evaluateExpressionGroup, evaluateExpression } = require("../../../server/monitor-conditions/evaluator.js");
-test("Test evaluateExpression", async (t) => {
- const expr = new ConditionExpression("record", "contains", "mx1.example.com");
- assert.strictEqual(true, evaluateExpression(expr, { record: "mx1.example.com" }));
- assert.strictEqual(false, evaluateExpression(expr, { record: "mx2.example.com" }));
-});
+describe("Expression Evaluator", () => {
+ test("evaluateExpression() returns true when condition matches and false otherwise", () => {
+ const expr = new ConditionExpression("record", "contains", "mx1.example.com");
+ assert.strictEqual(true, evaluateExpression(expr, { record: "mx1.example.com" }));
+ assert.strictEqual(false, evaluateExpression(expr, { record: "mx2.example.com" }));
+ });
-test("Test evaluateExpressionGroup with logical AND", async (t) => {
- const group = new ConditionExpressionGroup([
- new ConditionExpression("record", "contains", "mx1."),
- new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
- ]);
- assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
- assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
- assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
-});
+ test("evaluateExpressionGroup() with AND logic requires all conditions to be true", () => {
+ const group = new ConditionExpressionGroup([
+ new ConditionExpression("record", "contains", "mx1."),
+ new ConditionExpression("record", "contains", "example.com", LOGICAL.AND),
+ ]);
+ assert.strictEqual(true, evaluateExpressionGroup(group, { record: "mx1.example.com" }));
+ assert.strictEqual(false, evaluateExpressionGroup(group, { record: "mx1." }));
+ assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.com" }));
+ });
-test("Test evaluateExpressionGroup with logical OR", async (t) => {
- const group = new ConditionExpressionGroup([
- new ConditionExpression("record", "contains", "example.com"),
- new ConditionExpression("record", "contains", "example.org", LOGICAL.OR),
- ]);
- assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.com" }));
- assert.strictEqual(true, evaluateExpressionGroup(group, { record: "example.org" }));
- assert.strictEqual(false, evaluateExpressionGroup(group, { record: "example.net" }));
-});
-
-test("Test evaluateExpressionGroup with nested group", async (t) => {
- const group = new ConditionExpressionGroup([
- new ConditionExpression("record", "contains", "mx1."),
- new ConditionExpressionGroup([
+ test("evaluateExpressionGroup() with OR logic requires at least one condition to be true", () => {
+ const group = 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" }));
+ ]);
+ 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("evaluateExpressionGroup() evaluates nested groups correctly", () => {
+ 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" }));
+ });
});
diff --git a/test/backend-test/monitor-conditions/test-operators.js b/test/backend-test/monitor-conditions/test-operators.js
index e663c9a50..6a6739631 100644
--- a/test/backend-test/monitor-conditions/test-operators.js
+++ b/test/backend-test/monitor-conditions/test-operators.js
@@ -1,108 +1,110 @@
-const test = require("node:test");
+const { describe, test } = require("node:test");
const assert = require("node:assert");
const { operatorMap, OP_CONTAINS, OP_NOT_CONTAINS, OP_LT, OP_GT, OP_LTE, OP_GTE, OP_STR_EQUALS, OP_STR_NOT_EQUALS, OP_NUM_EQUALS, OP_NUM_NOT_EQUALS, OP_STARTS_WITH, OP_ENDS_WITH, OP_NOT_STARTS_WITH, OP_NOT_ENDS_WITH } = require("../../../server/monitor-conditions/operators.js");
-test("Test StringEqualsOperator", async (t) => {
- const op = operatorMap.get(OP_STR_EQUALS);
- assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
- assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.org"));
- assert.strictEqual(false, op.test("1", 1)); // strict equality
-});
+describe("Expression Operators", () => {
+ test("StringEqualsOperator returns true for identical strings and false otherwise", () => {
+ const op = operatorMap.get(OP_STR_EQUALS);
+ assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.com"));
+ assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.org"));
+ assert.strictEqual(false, op.test("1", 1)); // strict equality
+ });
-test("Test StringNotEqualsOperator", async (t) => {
- const op = operatorMap.get(OP_STR_NOT_EQUALS);
- assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
- assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
- assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
-});
+ test("StringNotEqualsOperator returns true for different strings and false for identical strings", () => {
+ const op = operatorMap.get(OP_STR_NOT_EQUALS);
+ assert.strictEqual(true, op.test("mx1.example.com", "mx1.example.org"));
+ assert.strictEqual(false, op.test("mx1.example.com", "mx1.example.com"));
+ assert.strictEqual(true, op.test(1, "1")); // variable is not typecasted (strict equality)
+ });
-test("Test ContainsOperator with scalar", async (t) => {
- const op = operatorMap.get(OP_CONTAINS);
- assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
- assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
-});
+ test("ContainsOperator returns true when scalar contains substring", () => {
+ const op = operatorMap.get(OP_CONTAINS);
+ assert.strictEqual(true, op.test("mx1.example.org", "example.org"));
+ assert.strictEqual(false, op.test("mx1.example.org", "example.com"));
+ });
-test("Test ContainsOperator with array", async (t) => {
- const op = operatorMap.get(OP_CONTAINS);
- assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
- assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
-});
+ test("ContainsOperator returns true when array contains element", () => {
+ const op = operatorMap.get(OP_CONTAINS);
+ assert.strictEqual(true, op.test([ "example.org" ], "example.org"));
+ assert.strictEqual(false, op.test([ "example.org" ], "example.com"));
+ });
-test("Test NotContainsOperator with scalar", async (t) => {
- const op = operatorMap.get(OP_NOT_CONTAINS);
- assert.strictEqual(true, op.test("example.org", ".com"));
- assert.strictEqual(false, op.test("example.org", ".org"));
-});
+ test("NotContainsOperator returns true when scalar does not contain substring", () => {
+ const op = operatorMap.get(OP_NOT_CONTAINS);
+ assert.strictEqual(true, op.test("example.org", ".com"));
+ assert.strictEqual(false, op.test("example.org", ".org"));
+ });
-test("Test NotContainsOperator with array", async (t) => {
- const op = operatorMap.get(OP_NOT_CONTAINS);
- assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
- assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
-});
+ test("NotContainsOperator returns true when array does not contain element", () => {
+ const op = operatorMap.get(OP_NOT_CONTAINS);
+ assert.strictEqual(true, op.test([ "example.org" ], "example.com"));
+ assert.strictEqual(false, op.test([ "example.org" ], "example.org"));
+ });
-test("Test StartsWithOperator", async (t) => {
- const op = operatorMap.get(OP_STARTS_WITH);
- assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
- assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
-});
+ test("StartsWithOperator returns true when string starts with prefix", () => {
+ const op = operatorMap.get(OP_STARTS_WITH);
+ assert.strictEqual(true, op.test("mx1.example.com", "mx1"));
+ assert.strictEqual(false, op.test("mx1.example.com", "mx2"));
+ });
-test("Test NotStartsWithOperator", async (t) => {
- const op = operatorMap.get(OP_NOT_STARTS_WITH);
- assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
- assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
-});
+ test("NotStartsWithOperator returns true when string does not start with prefix", () => {
+ const op = operatorMap.get(OP_NOT_STARTS_WITH);
+ assert.strictEqual(true, op.test("mx1.example.com", "mx2"));
+ assert.strictEqual(false, op.test("mx1.example.com", "mx1"));
+ });
-test("Test EndsWithOperator", async (t) => {
- const op = operatorMap.get(OP_ENDS_WITH);
- assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
- assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
-});
+ test("EndsWithOperator returns true when string ends with suffix", () => {
+ const op = operatorMap.get(OP_ENDS_WITH);
+ assert.strictEqual(true, op.test("mx1.example.com", "example.com"));
+ assert.strictEqual(false, op.test("mx1.example.com", "example.net"));
+ });
-test("Test NotEndsWithOperator", async (t) => {
- const op = operatorMap.get(OP_NOT_ENDS_WITH);
- assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
- assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
-});
+ test("NotEndsWithOperator returns true when string does not end with suffix", () => {
+ const op = operatorMap.get(OP_NOT_ENDS_WITH);
+ assert.strictEqual(true, op.test("mx1.example.com", "example.net"));
+ assert.strictEqual(false, op.test("mx1.example.com", "example.com"));
+ });
-test("Test NumberEqualsOperator", async (t) => {
- const op = operatorMap.get(OP_NUM_EQUALS);
- assert.strictEqual(true, op.test(1, 1));
- assert.strictEqual(true, op.test(1, "1"));
- assert.strictEqual(false, op.test(1, "2"));
-});
+ test("NumberEqualsOperator returns true for equal numbers with type coercion", () => {
+ const op = operatorMap.get(OP_NUM_EQUALS);
+ assert.strictEqual(true, op.test(1, 1));
+ assert.strictEqual(true, op.test(1, "1"));
+ assert.strictEqual(false, op.test(1, "2"));
+ });
-test("Test NumberNotEqualsOperator", async (t) => {
- const op = operatorMap.get(OP_NUM_NOT_EQUALS);
- assert.strictEqual(true, op.test(1, "2"));
- assert.strictEqual(false, op.test(1, "1"));
-});
+ test("NumberNotEqualsOperator returns true for different numbers", () => {
+ const op = operatorMap.get(OP_NUM_NOT_EQUALS);
+ assert.strictEqual(true, op.test(1, "2"));
+ assert.strictEqual(false, op.test(1, "1"));
+ });
-test("Test LessThanOperator", async (t) => {
- const op = operatorMap.get(OP_LT);
- assert.strictEqual(true, op.test(1, 2));
- assert.strictEqual(true, op.test(1, "2"));
- assert.strictEqual(false, op.test(1, 1));
-});
+ test("LessThanOperator returns true when first number is less than second", () => {
+ const op = operatorMap.get(OP_LT);
+ assert.strictEqual(true, op.test(1, 2));
+ assert.strictEqual(true, op.test(1, "2"));
+ assert.strictEqual(false, op.test(1, 1));
+ });
-test("Test GreaterThanOperator", async (t) => {
- const op = operatorMap.get(OP_GT);
- assert.strictEqual(true, op.test(2, 1));
- assert.strictEqual(true, op.test(2, "1"));
- assert.strictEqual(false, op.test(1, 1));
-});
+ test("GreaterThanOperator returns true when first number is greater than second", () => {
+ const op = operatorMap.get(OP_GT);
+ assert.strictEqual(true, op.test(2, 1));
+ assert.strictEqual(true, op.test(2, "1"));
+ assert.strictEqual(false, op.test(1, 1));
+ });
-test("Test LessThanOrEqualToOperator", async (t) => {
- const op = operatorMap.get(OP_LTE);
- assert.strictEqual(true, op.test(1, 1));
- assert.strictEqual(true, op.test(1, 2));
- assert.strictEqual(true, op.test(1, "2"));
- assert.strictEqual(false, op.test(1, 0));
-});
+ test("LessThanOrEqualToOperator returns true when first number is less than or equal to second", () => {
+ const op = operatorMap.get(OP_LTE);
+ assert.strictEqual(true, op.test(1, 1));
+ assert.strictEqual(true, op.test(1, 2));
+ assert.strictEqual(true, op.test(1, "2"));
+ assert.strictEqual(false, op.test(1, 0));
+ });
-test("Test GreaterThanOrEqualToOperator", async (t) => {
- const op = operatorMap.get(OP_GTE);
- assert.strictEqual(true, op.test(1, 1));
- assert.strictEqual(true, op.test(2, 1));
- assert.strictEqual(true, op.test(2, "2"));
- assert.strictEqual(false, op.test(2, 3));
+ test("GreaterThanOrEqualToOperator returns true when first number is greater than or equal to second", () => {
+ const op = operatorMap.get(OP_GTE);
+ assert.strictEqual(true, op.test(1, 1));
+ assert.strictEqual(true, op.test(2, 1));
+ assert.strictEqual(true, op.test(2, "2"));
+ assert.strictEqual(false, op.test(2, 3));
+ });
});
diff --git a/test/backend-test/test-grpc.js b/test/backend-test/monitors/test-grpc.js
similarity index 93%
rename from test/backend-test/test-grpc.js
rename to test/backend-test/monitors/test-grpc.js
index 31b588cff..def839044 100644
--- a/test/backend-test/test-grpc.js
+++ b/test/backend-test/monitors/test-grpc.js
@@ -2,8 +2,8 @@ const { describe, test } = require("node:test");
const assert = require("node:assert");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
-const { GrpcKeywordMonitorType } = require("../../server/monitor-types/grpc");
-const { UP, PENDING } = require("../../src/util");
+const { GrpcKeywordMonitorType } = require("../../../server/monitor-types/grpc");
+const { UP, PENDING } = require("../../../src/util");
const fs = require("fs");
const path = require("path");
const os = require("os");
@@ -82,7 +82,7 @@ async function createTestGrpcServer(port, methodHandlers) {
describe("GrpcKeywordMonitorType", {
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 server = await createTestGrpcServer(port, {
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 server = await createTestGrpcServer(port, {
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 server = await createTestGrpcServer(port, {
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 server = await createTestGrpcServer(port, {
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 monitor = {
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 longMessage = "A".repeat(100) + " with SUCCESS keyword";
diff --git a/test/backend-test/test-mqtt.js b/test/backend-test/monitors/test-mqtt.js
similarity index 79%
rename from test/backend-test/test-mqtt.js
rename to test/backend-test/monitors/test-mqtt.js
index 921df48fc..a361e868e 100644
--- a/test/backend-test/test-mqtt.js
+++ b/test/backend-test/monitors/test-mqtt.js
@@ -2,8 +2,8 @@ const { describe, test } = require("node:test");
const assert = require("node:assert");
const { HiveMQContainer } = require("@testcontainers/hivemq");
const mqtt = require("mqtt");
-const { MqttMonitorType } = require("../../server/monitor-types/mqtt");
-const { UP, PENDING } = require("../../src/util");
+const { MqttMonitorType } = require("../../../server/monitor-types/mqtt");
+const { UP, PENDING } = require("../../../src/util");
/**
* Runs an MQTT test with the
@@ -58,91 +58,91 @@ describe("MqttMonitorType", {
concurrency: 4,
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 <-");
assert.strictEqual(heartbeat.status, UP);
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");
assert.strictEqual(heartbeat.status, UP);
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/'/$/./*/%");
assert.strictEqual(heartbeat.status, UP);
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");
assert.strictEqual(heartbeat.status, UP);
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");
assert.strictEqual(heartbeat.status, UP);
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");
assert.strictEqual(heartbeat.status, UP);
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(
testMqtt("keyword will not be checked anyway", null, "message", "x/y/z", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
- test("invalid wildcard topic (with #)", async () => {
+ test("check() rejects with timeout when # wildcard is not last character", async () => {
await assert.rejects(
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
- test("invalid wildcard topic (with +)", async () => {
+ test("check() rejects with timeout when + wildcard topic does not match", async () => {
await assert.rejects(
testMqtt("", null, "message", "x/+/z", "a/b/c"),
new Error("Timeout, Message not received"),
);
});
- test("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 <-");
assert.strictEqual(heartbeat.status, UP);
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(
testMqtt("NOT_PRESENT", null, "-> 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(
testMqtt("NOT_PRESENT", "keyword", "-> 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"
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
});
- test("invalid (because query fails) json-query", async () => {
+ test("check() rejects when json-query path returns undefined", async () => {
// works because the monitors' jsonPath is hard-coded to "firstProp"
await assert.rejects(
testMqtt("[not_relevant]", "json-query", "{}"),
@@ -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"
await assert.rejects(
testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
diff --git a/test/backend-test/test-mssql.js b/test/backend-test/monitors/test-mssql.js
similarity index 92%
rename from test/backend-test/test-mssql.js
rename to test/backend-test/monitors/test-mssql.js
index afdd1faf8..f265bcdff 100644
--- a/test/backend-test/test-mssql.js
+++ b/test/backend-test/monitors/test-mssql.js
@@ -1,8 +1,8 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { MSSQLServerContainer } = require("@testcontainers/mssqlserver");
-const { MssqlMonitorType } = require("../../server/monitor-types/mssql");
-const { UP, PENDING } = require("../../src/util");
+const { MssqlMonitorType } = require("../../../server/monitor-types/mssql");
+const { UP, PENDING } = require("../../../src/util");
/**
* Helper function to create and start a MSSQL container
@@ -26,7 +26,7 @@ describe(
(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;
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 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 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 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 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 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 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 monitor = {
databaseConnectionString:
diff --git a/test/backend-test/test-postgres.js b/test/backend-test/monitors/test-postgres.js
similarity index 84%
rename from test/backend-test/test-postgres.js
rename to test/backend-test/monitors/test-postgres.js
index 71d26d3a1..098c862a1 100644
--- a/test/backend-test/test-postgres.js
+++ b/test/backend-test/monitors/test-postgres.js
@@ -1,8 +1,8 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
-const { PostgresMonitorType } = require("../../server/monitor-types/postgres");
-const { UP, PENDING } = require("../../src/util");
+const { PostgresMonitorType } = require("../../../server/monitor-types/postgres");
+const { UP, PENDING } = require("../../../src/util");
describe(
"Postgres Single Node",
@@ -12,7 +12,7 @@ describe(
(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
const postgresContainer = await new PostgreSqlContainer(
"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 monitor = {
databaseConnectionString: "http://localhost:15432",
diff --git a/test/backend-test/test-rabbitmq.js b/test/backend-test/monitors/test-rabbitmq.js
similarity index 85%
rename from test/backend-test/test-rabbitmq.js
rename to test/backend-test/monitors/test-rabbitmq.js
index 31f018aa9..63d358dfd 100644
--- a/test/backend-test/test-rabbitmq.js
+++ b/test/backend-test/monitors/test-rabbitmq.js
@@ -1,13 +1,13 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
const { RabbitMQContainer } = require("@testcontainers/rabbitmq");
-const { RabbitMqMonitorType } = require("../../server/monitor-types/rabbitmq");
-const { UP, PENDING } = require("../../src/util");
+const { RabbitMqMonitorType } = require("../../../server/monitor-types/rabbitmq");
+const { UP, PENDING } = require("../../../src/util");
describe("RabbitMQ Single Node", {
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64"),
}, () => {
- test("RabbitMQ is running", async () => {
+ test("check() sets status to UP when RabbitMQ server is reachable", async () => {
// The default timeout of 30 seconds might not be enough for the container to start
const rabbitMQContainer = await new RabbitMQContainer().withStartupTimeout(60000).start();
const rabbitMQMonitor = new RabbitMqMonitorType();
@@ -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 monitor = {
rabbitmqNodes: JSON.stringify([ "http://localhost:15672" ]),
diff --git a/test/backend-test/test-tcp.js b/test/backend-test/monitors/test-tcp.js
similarity index 78%
rename from test/backend-test/test-tcp.js
rename to test/backend-test/monitors/test-tcp.js
index 98f168c24..4626fe34f 100644
--- a/test/backend-test/test-tcp.js
+++ b/test/backend-test/monitors/test-tcp.js
@@ -1,14 +1,9 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
-const { TCPMonitorType } = require("../../server/monitor-types/tcp");
-const { UP, PENDING } = require("../../src/util");
+const { TCPMonitorType } = require("../../../server/monitor-types/tcp");
+const { UP, PENDING } = require("../../../src/util");
const net = require("net");
-/**
- * Test suite for TCP Monitor functionality
- * This test suite checks the behavior of the TCPMonitorType class
- * under different network connection scenarios.
- */
describe("TCP Monitor", () => {
/**
* Creates a TCP server on a specified port
@@ -29,11 +24,7 @@ describe("TCP Monitor", () => {
});
}
- /**
- * 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 () => {
+ test("check() sets status to UP when TCP server is reachable", async () => {
const port = 12345;
const server = await createTCPServer(port);
@@ -59,11 +50,7 @@ describe("TCP Monitor", () => {
}
});
- /**
- * 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 () => {
+ test("check() rejects with connection failed when TCP server is not running", async () => {
const tcpMonitor = new TCPMonitorType();
const monitor = {
@@ -83,11 +70,7 @@ describe("TCP Monitor", () => {
);
});
- /**
- * 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 => {
+ test("check() rejects when TLS certificate is expired or invalid", async () => {
const tcpMonitor = new TCPMonitorType();
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 monitor = {
@@ -137,7 +120,7 @@ describe("TCP Monitor", () => {
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 monitor = {
@@ -160,7 +143,7 @@ describe("TCP Monitor", () => {
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 monitor = {
@@ -185,7 +168,7 @@ describe("TCP Monitor", () => {
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 monitor = {
diff --git a/test/backend-test/test-websocket.js b/test/backend-test/monitors/test-websocket.js
similarity index 86%
rename from test/backend-test/test-websocket.js
rename to test/backend-test/monitors/test-websocket.js
index 3eeeb3243..d2923f1e7 100644
--- a/test/backend-test/test-websocket.js
+++ b/test/backend-test/monitors/test-websocket.js
@@ -1,8 +1,8 @@
const { WebSocketServer } = require("ws");
const { describe, test } = require("node:test");
const assert = require("node:assert");
-const { WebSocketMonitorType } = require("../../server/monitor-types/websocket-upgrade");
-const { UP, PENDING } = require("../../src/util");
+const { WebSocketMonitorType } = require("../../../server/monitor-types/websocket-upgrade");
+const { UP, PENDING } = require("../../../src/util");
const net = require("node:net");
/**
@@ -22,9 +22,9 @@ function nonCompliantWS(port = 8080) {
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 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 monitor = {
@@ -68,7 +68,7 @@ describe("Websocket Test", {
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());
const websocketMonitor = new WebSocketMonitorType();
const wss = new WebSocketServer({ port: 8080 });
@@ -94,7 +94,7 @@ describe("Websocket Test", {
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 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 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());
const websocketMonitor = new WebSocketMonitorType();
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());
const websocketMonitor = new WebSocketMonitorType();
const wss = await nonCompliantWS();
@@ -185,7 +185,7 @@ describe("Websocket Test", {
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 monitor = {
@@ -209,7 +209,7 @@ describe("Websocket Test", {
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 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 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 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());
const websocketMonitor = new WebSocketMonitorType();
const wss = new WebSocketServer({ port: 8080,
@@ -305,7 +305,7 @@ describe("Websocket Test", {
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());
const websocketMonitor = new WebSocketMonitorType();
const wss = new WebSocketServer({ port: 8080,
diff --git a/test/mock-webhook.js b/test/backend-test/notification-providers/mock-webhook.js
similarity index 96%
rename from test/mock-webhook.js
rename to test/backend-test/notification-providers/mock-webhook.js
index 23bf192c7..70cc5fdf9 100644
--- a/test/mock-webhook.js
+++ b/test/backend-test/notification-providers/mock-webhook.js
@@ -1,28 +1,28 @@
-const express = require("express");
-const bodyParser = require("body-parser");
-
-/**
- * @param {number} port Port number
- * @param {string} url Webhook URL
- * @param {number} timeout Timeout
- * @returns {Promise