From fe09beaf6af1e1eb40be557d164d2bee79159ae9 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 18:35:57 +0500
Subject: [PATCH 01/30] add more tests

---
 test/backend-test/test-dns-monitor.js  | 105 +++++++++++++++++++++++
 test/backend-test/test-notification.js | 114 +++++++++++++++++++++++++
 2 files changed, 219 insertions(+)
 create mode 100644 test/backend-test/test-dns-monitor.js
 create mode 100644 test/backend-test/test-notification.js

diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
new file mode 100644
index 000000000..7f0307ea7
--- /dev/null
+++ b/test/backend-test/test-dns-monitor.js
@@ -0,0 +1,105 @@
+const test = require("node:test");
+const assert = require("node:assert");
+const { DnsMonitorType } = require("../../server/monitor-types/dns");
+const { UP, DOWN } = require("../../src/util");
+const dayjs = require("dayjs");
+
+test("DNS Monitor - Basic A Record Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "A"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
+});
+
+test("DNS Monitor - Invalid Domain Test", async (t) => {
+    const monitor = {
+        hostname: "invalid-domain-that-does-not-exist.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "A"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    try {
+        await dnsMonitor.check(monitor, heartbeat);
+        assert.fail("Should throw error for invalid domain");
+    } catch (error) {
+        assert.ok(error, "Should throw error for invalid domain");
+    }
+});
+
+test("DNS Monitor - Custom DNS Server Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "1.1.1.1", // Cloudflare DNS
+        port: 53,
+        dns_resolve_type: "A"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+});
+
+test("DNS Monitor - TXT Record Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "TXT"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
+});
+
+test("DNS Monitor - Condition Evaluation Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "A",
+        conditions: [{
+            type: "record",
+            operator: "contains",
+            value: "93.184.216.34" // example.com's IP (this might change)
+        }]
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+});
diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
new file mode 100644
index 000000000..aa0ca22d5
--- /dev/null
+++ b/test/backend-test/test-notification.js
@@ -0,0 +1,114 @@
+const test = require("node:test");
+const assert = require("node:assert");
+const { Notification } = require("../../server/notification");
+const { UP, DOWN } = require("../../src/util");
+
+test("Notification - Basic Creation Test", async (t) => {
+    const notification = new Notification();
+    assert.ok(notification, "Should create notification instance");
+    assert.ok(typeof notification.send === "function", "Should have send method");
+});
+
+test("Notification - Format Message Test", async (t) => {
+    const notification = new Notification();
+    
+    const monitor = {
+        name: "Test Monitor",
+        hostname: "example.com"
+    };
+    
+    const msg = {
+        type: "down",
+        monitor,
+        msg: "Connection failed"
+    };
+
+    const formatted = notification.format(msg);
+    assert.ok(formatted.includes("Test Monitor"), "Should include monitor name");
+    assert.ok(formatted.includes("example.com"), "Should include hostname");
+    assert.ok(formatted.includes("Connection failed"), "Should include error message");
+});
+
+test("Notification - Queue Management Test", async (t) => {
+    const notification = new Notification();
+    
+    // Add items to queue
+    notification.add({
+        type: "down",
+        monitor: { name: "Test1" },
+        msg: "Error 1"
+    });
+    
+    notification.add({
+        type: "up",
+        monitor: { name: "Test2" },
+        msg: "Recovered"
+    });
+    
+    assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
+});
+
+test("Notification - Priority Test", async (t) => {
+    const notification = new Notification();
+    
+    // Add items with different priorities
+    notification.add({
+        type: "down",
+        monitor: { name: "Test1" },
+        msg: "Critical Error",
+        priority: "high"
+    });
+    
+    notification.add({
+        type: "down",
+        monitor: { name: "Test2" },
+        msg: "Warning",
+        priority: "low"
+    });
+    
+    const nextItem = notification.queue[0];
+    assert.strictEqual(nextItem.priority, "high", "High priority item should be first");
+});
+
+test("Notification - Retry Logic Test", async (t) => {
+    const notification = new Notification();
+    
+    const testMsg = {
+        type: "down",
+        monitor: { name: "Test1" },
+        msg: "Error",
+        retries: 0,
+        maxRetries: 3
+    };
+    
+    notification.add(testMsg);
+    
+    // Simulate failed send
+    try {
+        await notification.send(testMsg);
+    } catch (error) {
+        assert.ok(testMsg.retries === 1, "Should increment retry count");
+        assert.ok(notification.queue.length === 1, "Should keep in queue for retry");
+    }
+});
+
+test("Notification - Rate Limiting Test", async (t) => {
+    const notification = new Notification();
+    const monitor = { name: "Test Monitor" };
+    
+    // Add multiple notifications for same monitor
+    for (let i = 0; i < 5; i++) {
+        notification.add({
+            type: "down",
+            monitor,
+            msg: `Error ${i}`
+        });
+    }
+    
+    // Check if rate limiting is applied
+    const processedCount = notification.queue.filter(
+        item => item.monitor.name === "Test Monitor"
+    ).length;
+    
+    assert.ok(processedCount < 5, "Should apply rate limiting");
+});

From 7855dd97a5622cbf3d6aaaf64e39adace339324c Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 18:48:13 +0500
Subject: [PATCH 02/30] component testing

---
 config/vitest.config.js            |   21 +
 package-lock.json                  | 1159 +++++++++++++++++++++++++++-
 package.json                       |    5 +
 test/component/MonitorList.spec.js |  100 +++
 test/component/PingChart.spec.js   |  114 +++
 test/component/Status.spec.js      |   52 ++
 test/component/setup.js            |   22 +
 7 files changed, 1471 insertions(+), 2 deletions(-)
 create mode 100644 config/vitest.config.js
 create mode 100644 test/component/MonitorList.spec.js
 create mode 100644 test/component/PingChart.spec.js
 create mode 100644 test/component/Status.spec.js
 create mode 100644 test/component/setup.js

diff --git a/config/vitest.config.js b/config/vitest.config.js
new file mode 100644
index 000000000..dead5a1f6
--- /dev/null
+++ b/config/vitest.config.js
@@ -0,0 +1,21 @@
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import { fileURLToPath } from 'url';
+import { dirname, resolve } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export default defineConfig({
+    plugins: [vue()],
+    test: {
+        globals: true,
+        environment: 'jsdom',
+        setupFiles: ['./test/component/setup.js'],
+    },
+    resolve: {
+        alias: {
+            '@': resolve(__dirname, '../src'),
+        },
+    },
+});
diff --git a/package-lock.json b/package-lock.json
index 100cdcd88..87382a507 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "2.0.0-dev",
+    "version": "2.0.0-beta.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "2.0.0-dev",
+            "version": "2.0.0-beta.0",
             "license": "MIT",
             "dependencies": {
                 "@grpc/grpc-js": "~1.8.22",
@@ -94,12 +94,14 @@
                 "@popperjs/core": "~2.10.2",
                 "@testcontainers/hivemq": "^10.13.1",
                 "@testcontainers/rabbitmq": "^10.13.2",
+                "@testing-library/vue": "^8.1.0",
                 "@types/bootstrap": "~5.1.9",
                 "@types/node": "^20.8.6",
                 "@typescript-eslint/eslint-plugin": "^6.7.5",
                 "@typescript-eslint/parser": "^6.7.5",
                 "@vitejs/plugin-vue": "~5.0.1",
                 "@vue/compiler-sfc": "~3.4.2",
+                "@vue/test-utils": "^2.4.6",
                 "@vuepic/vue-datepicker": "~3.4.8",
                 "aedes": "^0.46.3",
                 "bootstrap": "5.1.3",
@@ -117,6 +119,7 @@
                 "eslint-plugin-vue": "~8.7.1",
                 "favico.js": "~0.3.10",
                 "get-port-please": "^3.1.1",
+                "jsdom": "^25.0.1",
                 "node-ssh": "~13.1.0",
                 "postcss-html": "~1.5.0",
                 "postcss-rtlcss": "~3.7.2",
@@ -135,6 +138,7 @@
                 "vite": "~5.2.8",
                 "vite-plugin-compression": "^0.5.1",
                 "vite-plugin-vue-devtools": "^7.0.15",
+                "vitest": "^2.1.5",
                 "vue": "~3.4.2",
                 "vue-chartjs": "~5.2.0",
                 "vue-confirm-dialog": "~1.0.2",
@@ -3055,6 +3059,13 @@
                 "@octokit/openapi-types": "^12.11.0"
             }
         },
+        "node_modules/@one-ini/wasm": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+            "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/@pkgjs/parseargs": {
             "version": "0.11.0",
             "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -4222,6 +4233,50 @@
                 "testcontainers": "^10.13.2"
             }
         },
+        "node_modules/@testing-library/dom": {
+            "version": "9.3.4",
+            "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
+            "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@babel/code-frame": "^7.10.4",
+                "@babel/runtime": "^7.12.5",
+                "@types/aria-query": "^5.0.1",
+                "aria-query": "5.1.3",
+                "chalk": "^4.1.0",
+                "dom-accessibility-api": "^0.5.9",
+                "lz-string": "^1.5.0",
+                "pretty-format": "^27.0.2"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/@testing-library/vue": {
+            "version": "8.1.0",
+            "resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-8.1.0.tgz",
+            "integrity": "sha512-ls4RiHO1ta4mxqqajWRh8158uFObVrrtAPoxk7cIp4HrnQUj/ScKzqz53HxYpG3X6Zb7H2v+0eTGLSoy8HQ2nA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@babel/runtime": "^7.23.2",
+                "@testing-library/dom": "^9.3.3",
+                "@vue/test-utils": "^2.4.1"
+            },
+            "engines": {
+                "node": ">=14"
+            },
+            "peerDependencies": {
+                "@vue/compiler-sfc": ">= 3",
+                "vue": ">= 3"
+            },
+            "peerDependenciesMeta": {
+                "@vue/compiler-sfc": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/@tootallnate/once": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -4240,6 +4295,13 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/aria-query": {
+            "version": "5.0.4",
+            "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+            "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/@types/body-parser": {
             "version": "1.19.5",
             "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -4786,6 +4848,129 @@
                 "vue": "^3.2.25"
             }
         },
+        "node_modules/@vitest/expect": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz",
+            "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/spy": "2.1.5",
+                "@vitest/utils": "2.1.5",
+                "chai": "^5.1.2",
+                "tinyrainbow": "^1.2.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/mocker": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz",
+            "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/spy": "2.1.5",
+                "estree-walker": "^3.0.3",
+                "magic-string": "^0.30.12"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            },
+            "peerDependencies": {
+                "msw": "^2.4.9",
+                "vite": "^5.0.0"
+            },
+            "peerDependenciesMeta": {
+                "msw": {
+                    "optional": true
+                },
+                "vite": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@vitest/mocker/node_modules/estree-walker": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+            "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/estree": "^1.0.0"
+            }
+        },
+        "node_modules/@vitest/pretty-format": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz",
+            "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tinyrainbow": "^1.2.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/runner": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz",
+            "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/utils": "2.1.5",
+                "pathe": "^1.1.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/snapshot": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz",
+            "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/pretty-format": "2.1.5",
+                "magic-string": "^0.30.12",
+                "pathe": "^1.1.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/spy": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz",
+            "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tinyspy": "^3.0.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/utils": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz",
+            "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/pretty-format": "2.1.5",
+                "loupe": "^3.1.2",
+                "tinyrainbow": "^1.2.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
         "node_modules/@vue/babel-helper-vue-transform-on": {
             "version": "1.2.5",
             "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz",
@@ -5058,6 +5243,17 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/@vue/test-utils": {
+            "version": "2.4.6",
+            "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz",
+            "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "js-beautify": "^1.14.9",
+                "vue-component-type-helpers": "^2.0.0"
+            }
+        },
         "node_modules/@vuepic/vue-datepicker": {
             "version": "3.4.8",
             "resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-3.4.8.tgz",
@@ -5521,6 +5717,16 @@
             "integrity": "sha512-If3Zi4BSjlQIJ9fgAhSiKi0oJtgMzSqh0H4wvl7XSeO16FKx7QqaHld8lZeEajPX7y1C5qKKeNgyrfyvmjmjUQ==",
             "license": "Apache-2.0"
         },
+        "node_modules/aria-query": {
+            "version": "5.1.3",
+            "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+            "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "dependencies": {
+                "deep-equal": "^2.0.5"
+            }
+        },
         "node_modules/array-buffer-byte-length": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
@@ -5603,6 +5809,16 @@
             "integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng==",
             "license": "MIT"
         },
+        "node_modules/assertion-error": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+            "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/astral-regex": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -6217,6 +6433,16 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/cac": {
+            "version": "6.7.14",
+            "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+            "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/cacache": {
             "version": "15.3.0",
             "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
@@ -6393,6 +6619,23 @@
             ],
             "license": "CC-BY-4.0"
         },
+        "node_modules/chai": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+            "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "assertion-error": "^2.0.1",
+                "check-error": "^2.1.1",
+                "deep-eql": "^5.0.1",
+                "loupe": "^3.1.0",
+                "pathval": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/chalk": {
             "version": "4.1.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -6465,6 +6708,16 @@
                 "dayjs": "^1.9.7"
             }
         },
+        "node_modules/check-error": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+            "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 16"
+            }
+        },
         "node_modules/check-password-strength": {
             "version": "2.0.10",
             "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.10.tgz",
@@ -6866,6 +7119,17 @@
                 "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
             }
         },
+        "node_modules/config-chain": {
+            "version": "1.1.13",
+            "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+            "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "ini": "^1.3.4",
+                "proto-list": "~1.2.1"
+            }
+        },
         "node_modules/console-control-strings": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -7232,6 +7496,19 @@
                 "node": ">=4"
             }
         },
+        "node_modules/cssstyle": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz",
+            "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "rrweb-cssom": "^0.7.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/csstype": {
             "version": "3.1.3",
             "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -7239,6 +7516,57 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/data-urls": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+            "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "whatwg-mimetype": "^4.0.0",
+                "whatwg-url": "^14.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/data-urls/node_modules/punycode": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+            "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/data-urls/node_modules/tr46": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+            "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "punycode": "^2.3.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/data-urls/node_modules/whatwg-url": {
+            "version": "14.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
+            "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tr46": "^5.0.0",
+                "webidl-conversions": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/data-view-buffer": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
@@ -7383,6 +7711,13 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/decimal.js": {
+            "version": "10.4.3",
+            "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+            "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/decompress-response": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -7410,6 +7745,49 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/deep-eql": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+            "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/deep-equal": {
+            "version": "2.2.3",
+            "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+            "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "array-buffer-byte-length": "^1.0.0",
+                "call-bind": "^1.0.5",
+                "es-get-iterator": "^1.1.3",
+                "get-intrinsic": "^1.2.2",
+                "is-arguments": "^1.1.1",
+                "is-array-buffer": "^3.0.2",
+                "is-date-object": "^1.0.5",
+                "is-regex": "^1.1.4",
+                "is-shared-array-buffer": "^1.0.2",
+                "isarray": "^2.0.5",
+                "object-is": "^1.1.5",
+                "object-keys": "^1.1.1",
+                "object.assign": "^4.1.4",
+                "regexp.prototype.flags": "^1.5.1",
+                "side-channel": "^1.0.4",
+                "which-boxed-primitive": "^1.0.2",
+                "which-collection": "^1.0.1",
+                "which-typed-array": "^1.1.13"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/deep-is": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -7699,6 +8077,13 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/dom-accessibility-api": {
+            "version": "0.5.16",
+            "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+            "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/dom-serializer": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -7797,6 +8182,41 @@
                 "safe-buffer": "^5.0.1"
             }
         },
+        "node_modules/editorconfig": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
+            "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@one-ini/wasm": "0.1.1",
+                "commander": "^10.0.0",
+                "minimatch": "9.0.1",
+                "semver": "^7.5.3"
+            },
+            "bin": {
+                "editorconfig": "bin/editorconfig"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/editorconfig/node_modules/minimatch": {
+            "version": "9.0.1",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+            "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
         "node_modules/ee-first": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8082,6 +8502,34 @@
                 "node": ">= 0.4"
             }
         },
+        "node_modules/es-get-iterator": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+            "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "get-intrinsic": "^1.1.3",
+                "has-symbols": "^1.0.3",
+                "is-arguments": "^1.1.1",
+                "is-map": "^2.0.2",
+                "is-set": "^2.0.2",
+                "is-string": "^1.0.7",
+                "isarray": "^2.0.5",
+                "stop-iteration-iterator": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/es-module-lexer": {
+            "version": "1.5.4",
+            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
+            "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/es-object-atoms": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
@@ -8554,6 +9002,16 @@
                 "url": "https://github.com/sponsors/isaacs"
             }
         },
+        "node_modules/expect-type": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
+            "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
         "node_modules/express": {
             "version": "4.21.1",
             "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
@@ -9843,6 +10301,19 @@
                 "node": ">=10"
             }
         },
+        "node_modules/html-encoding-sniffer": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+            "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "whatwg-encoding": "^3.1.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/html-escaper": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
@@ -10205,6 +10676,23 @@
                 "node": ">= 0.10"
             }
         },
+        "node_modules/is-arguments": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+            "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-array-buffer": {
             "version": "3.0.4",
             "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@@ -10437,6 +10925,19 @@
             "license": "MIT",
             "optional": true
         },
+        "node_modules/is-map": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+            "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-negative-zero": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
@@ -10496,6 +10997,13 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/is-potential-custom-element-name": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+            "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/is-property": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
@@ -10519,6 +11027,19 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-set": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+            "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-shared-array-buffer": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
@@ -10596,6 +11117,19 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-weakmap": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+            "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-weakref": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -10609,6 +11143,23 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-weakset": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+            "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.7",
+                "get-intrinsic": "^1.2.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-what": {
             "version": "4.1.16",
             "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
@@ -10704,6 +11255,111 @@
                 "url": "https://github.com/sponsors/panva"
             }
         },
+        "node_modules/js-beautify": {
+            "version": "1.15.1",
+            "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
+            "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "config-chain": "^1.1.13",
+                "editorconfig": "^1.0.4",
+                "glob": "^10.3.3",
+                "js-cookie": "^3.0.5",
+                "nopt": "^7.2.0"
+            },
+            "bin": {
+                "css-beautify": "js/bin/css-beautify.js",
+                "html-beautify": "js/bin/html-beautify.js",
+                "js-beautify": "js/bin/js-beautify.js"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/js-beautify/node_modules/abbrev": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+            "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+            "dev": true,
+            "license": "ISC",
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
+        },
+        "node_modules/js-beautify/node_modules/glob": {
+            "version": "10.4.5",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+            "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "foreground-child": "^3.1.0",
+                "jackspeak": "^3.1.2",
+                "minimatch": "^9.0.4",
+                "minipass": "^7.1.2",
+                "package-json-from-dist": "^1.0.0",
+                "path-scurry": "^1.11.1"
+            },
+            "bin": {
+                "glob": "dist/esm/bin.mjs"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/js-beautify/node_modules/minimatch": {
+            "version": "9.0.5",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+            "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/js-beautify/node_modules/minipass": {
+            "version": "7.1.2",
+            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+            "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+            "dev": true,
+            "license": "ISC",
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            }
+        },
+        "node_modules/js-beautify/node_modules/nopt": {
+            "version": "7.2.1",
+            "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
+            "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "abbrev": "^2.0.0"
+            },
+            "bin": {
+                "nopt": "bin/nopt.js"
+            },
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
+        },
+        "node_modules/js-cookie": {
+            "version": "3.0.5",
+            "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+            "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=14"
+            }
+        },
         "node_modules/js-md4": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
@@ -10756,6 +11412,125 @@
                 "node": ">=12.0.0"
             }
         },
+        "node_modules/jsdom": {
+            "version": "25.0.1",
+            "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
+            "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "cssstyle": "^4.1.0",
+                "data-urls": "^5.0.0",
+                "decimal.js": "^10.4.3",
+                "form-data": "^4.0.0",
+                "html-encoding-sniffer": "^4.0.0",
+                "http-proxy-agent": "^7.0.2",
+                "https-proxy-agent": "^7.0.5",
+                "is-potential-custom-element-name": "^1.0.1",
+                "nwsapi": "^2.2.12",
+                "parse5": "^7.1.2",
+                "rrweb-cssom": "^0.7.1",
+                "saxes": "^6.0.0",
+                "symbol-tree": "^3.2.4",
+                "tough-cookie": "^5.0.0",
+                "w3c-xmlserializer": "^5.0.0",
+                "webidl-conversions": "^7.0.0",
+                "whatwg-encoding": "^3.1.1",
+                "whatwg-mimetype": "^4.0.0",
+                "whatwg-url": "^14.0.0",
+                "ws": "^8.18.0",
+                "xml-name-validator": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            },
+            "peerDependencies": {
+                "canvas": "^2.11.2"
+            },
+            "peerDependenciesMeta": {
+                "canvas": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/jsdom/node_modules/http-proxy-agent": {
+            "version": "7.0.2",
+            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+            "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "agent-base": "^7.1.0",
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/jsdom/node_modules/https-proxy-agent": {
+            "version": "7.0.5",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+            "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "agent-base": "^7.0.2",
+                "debug": "4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/jsdom/node_modules/punycode": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+            "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/jsdom/node_modules/tough-cookie": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
+            "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
+            "dev": true,
+            "license": "BSD-3-Clause",
+            "dependencies": {
+                "tldts": "^6.1.32"
+            },
+            "engines": {
+                "node": ">=16"
+            }
+        },
+        "node_modules/jsdom/node_modules/tr46": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+            "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "punycode": "^2.3.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/jsdom/node_modules/whatwg-url": {
+            "version": "14.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
+            "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tr46": "^5.0.0",
+                "webidl-conversions": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/jsesc": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
@@ -11227,6 +12002,13 @@
             "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
             "license": "Apache-2.0"
         },
+        "node_modules/loupe": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+            "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/lowercase-keys": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -11266,6 +12048,16 @@
                 "url": "https://github.com/sponsors/wellwelwel"
             }
         },
+        "node_modules/lz-string": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+            "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+            "dev": true,
+            "license": "MIT",
+            "bin": {
+                "lz-string": "bin/bin.js"
+            }
+        },
         "node_modules/magic-string": {
             "version": "0.30.12",
             "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
@@ -12373,6 +13165,13 @@
                 "js-sdsl": "4.3.0"
             }
         },
+        "node_modules/nwsapi": {
+            "version": "2.2.13",
+            "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz",
+            "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/object-assign": {
             "version": "4.1.1",
             "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -12403,6 +13202,23 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/object-is": {
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+            "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/object-keys": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -12788,6 +13604,16 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/pathval": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+            "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 14.16"
+            }
+        },
         "node_modules/perfect-debounce": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -13175,6 +14001,34 @@
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/pretty-format": {
+            "version": "27.5.1",
+            "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+            "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "ansi-regex": "^5.0.1",
+                "ansi-styles": "^5.0.0",
+                "react-is": "^17.0.1"
+            },
+            "engines": {
+                "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+            }
+        },
+        "node_modules/pretty-format/node_modules/ansi-styles": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+            "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
         "node_modules/prismjs": {
             "version": "1.29.0",
             "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
@@ -13297,6 +14151,13 @@
                 "url": "https://github.com/steveukx/properties?sponsor=1"
             }
         },
+        "node_modules/proto-list": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+            "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+            "dev": true,
+            "license": "ISC"
+        },
         "node_modules/protobufjs": {
             "version": "7.2.6",
             "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
@@ -13654,6 +14515,13 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/react-is": {
+            "version": "17.0.2",
+            "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+            "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/read-pkg": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz",
@@ -14219,6 +15087,13 @@
                 }
             }
         },
+        "node_modules/rrweb-cssom": {
+            "version": "0.7.1",
+            "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+            "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/rtlcss": {
             "version": "3.5.0",
             "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz",
@@ -14353,6 +15228,19 @@
             "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
             "license": "ISC"
         },
+        "node_modules/saxes": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+            "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "xmlchars": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=v12.22.7"
+            }
+        },
         "node_modules/sb-promise-queue": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/sb-promise-queue/-/sb-promise-queue-2.1.0.tgz",
@@ -14582,6 +15470,13 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/siginfo": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+            "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+            "dev": true,
+            "license": "ISC"
+        },
         "node_modules/signal-exit": {
             "version": "3.0.7",
             "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -14952,6 +15847,13 @@
                 "node": ">= 8"
             }
         },
+        "node_modules/stackback": {
+            "version": "0.0.2",
+            "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+            "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/statuses": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -14961,6 +15863,26 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/std-env": {
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
+            "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/stop-iteration-iterator": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
+            "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "internal-slot": "^1.0.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/stoppable": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
@@ -15435,6 +16357,13 @@
             "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
             "dev": true
         },
+        "node_modules/symbol-tree": {
+            "version": "3.2.4",
+            "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+            "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/table": {
             "version": "6.8.2",
             "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz",
@@ -15767,6 +16696,70 @@
                 "node": ">=8"
             }
         },
+        "node_modules/tinybench": {
+            "version": "2.9.0",
+            "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+            "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/tinyexec": {
+            "version": "0.3.1",
+            "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
+            "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/tinypool": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
+            "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            }
+        },
+        "node_modules/tinyrainbow": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+            "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/tinyspy": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+            "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/tldts": {
+            "version": "6.1.61",
+            "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.61.tgz",
+            "integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tldts-core": "^6.1.61"
+            },
+            "bin": {
+                "tldts": "bin/cli.js"
+            }
+        },
+        "node_modules/tldts-core": {
+            "version": "6.1.61",
+            "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.61.tgz",
+            "integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/tmp": {
             "version": "0.2.3",
             "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -16334,6 +17327,29 @@
                 "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0"
             }
         },
+        "node_modules/vite-node": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz",
+            "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "cac": "^6.7.14",
+                "debug": "^4.3.7",
+                "es-module-lexer": "^1.5.4",
+                "pathe": "^1.1.2",
+                "vite": "^5.0.0"
+            },
+            "bin": {
+                "vite-node": "vite-node.mjs"
+            },
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
         "node_modules/vite-plugin-compression": {
             "version": "0.5.1",
             "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz",
@@ -16527,6 +17543,72 @@
                 "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
             }
         },
+        "node_modules/vitest": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz",
+            "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/expect": "2.1.5",
+                "@vitest/mocker": "2.1.5",
+                "@vitest/pretty-format": "^2.1.5",
+                "@vitest/runner": "2.1.5",
+                "@vitest/snapshot": "2.1.5",
+                "@vitest/spy": "2.1.5",
+                "@vitest/utils": "2.1.5",
+                "chai": "^5.1.2",
+                "debug": "^4.3.7",
+                "expect-type": "^1.1.0",
+                "magic-string": "^0.30.12",
+                "pathe": "^1.1.2",
+                "std-env": "^3.8.0",
+                "tinybench": "^2.9.0",
+                "tinyexec": "^0.3.1",
+                "tinypool": "^1.0.1",
+                "tinyrainbow": "^1.2.0",
+                "vite": "^5.0.0",
+                "vite-node": "2.1.5",
+                "why-is-node-running": "^2.3.0"
+            },
+            "bin": {
+                "vitest": "vitest.mjs"
+            },
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            },
+            "peerDependencies": {
+                "@edge-runtime/vm": "*",
+                "@types/node": "^18.0.0 || >=20.0.0",
+                "@vitest/browser": "2.1.5",
+                "@vitest/ui": "2.1.5",
+                "happy-dom": "*",
+                "jsdom": "*"
+            },
+            "peerDependenciesMeta": {
+                "@edge-runtime/vm": {
+                    "optional": true
+                },
+                "@types/node": {
+                    "optional": true
+                },
+                "@vitest/browser": {
+                    "optional": true
+                },
+                "@vitest/ui": {
+                    "optional": true
+                },
+                "happy-dom": {
+                    "optional": true
+                },
+                "jsdom": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/vue": {
             "version": "3.4.38",
             "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
@@ -16560,6 +17642,13 @@
                 "vue": "^3.0.0-0 || ^2.7.0"
             }
         },
+        "node_modules/vue-component-type-helpers": {
+            "version": "2.1.10",
+            "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.1.10.tgz",
+            "integrity": "sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/vue-confirm-dialog": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz",
@@ -16745,6 +17834,19 @@
                 "vue": "^3.0.1"
             }
         },
+        "node_modules/w3c-xmlserializer": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+            "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "xml-name-validator": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/wait-on": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
@@ -16877,6 +17979,25 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/which-collection": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+            "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "is-map": "^2.0.3",
+                "is-set": "^2.0.3",
+                "is-weakmap": "^2.0.2",
+                "is-weakset": "^2.0.3"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/which-module": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
@@ -16904,6 +18025,23 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/why-is-node-running": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+            "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "siginfo": "^2.0.0",
+                "stackback": "0.0.2"
+            },
+            "bin": {
+                "why-is-node-running": "cli.js"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/wide-align": {
             "version": "1.1.5",
             "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -17024,6 +18162,16 @@
                 "xml-js": "bin/cli.js"
             }
         },
+        "node_modules/xml-name-validator": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+            "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/xmlbuilder": {
             "version": "8.2.2",
             "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
@@ -17033,6 +18181,13 @@
                 "node": ">=4.0"
             }
         },
+        "node_modules/xmlchars": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+            "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/xmlhttprequest-ssl": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
diff --git a/package.json b/package.json
index 4f8eef138..fbd4ae7f2 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
         "test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
         "test-e2e": "playwright test --config ./config/playwright.config.js",
         "test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
+        "test-component": "vitest --config ./config/vitest.config.js",
         "playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
         "playwright-show-report": "playwright show-report ./private/playwright-report",
         "tsc": "tsc",
@@ -152,12 +153,14 @@
         "@popperjs/core": "~2.10.2",
         "@testcontainers/hivemq": "^10.13.1",
         "@testcontainers/rabbitmq": "^10.13.2",
+        "@testing-library/vue": "^8.1.0",
         "@types/bootstrap": "~5.1.9",
         "@types/node": "^20.8.6",
         "@typescript-eslint/eslint-plugin": "^6.7.5",
         "@typescript-eslint/parser": "^6.7.5",
         "@vitejs/plugin-vue": "~5.0.1",
         "@vue/compiler-sfc": "~3.4.2",
+        "@vue/test-utils": "^2.4.6",
         "@vuepic/vue-datepicker": "~3.4.8",
         "aedes": "^0.46.3",
         "bootstrap": "5.1.3",
@@ -175,6 +178,7 @@
         "eslint-plugin-vue": "~8.7.1",
         "favico.js": "~0.3.10",
         "get-port-please": "^3.1.1",
+        "jsdom": "^25.0.1",
         "node-ssh": "~13.1.0",
         "postcss-html": "~1.5.0",
         "postcss-rtlcss": "~3.7.2",
@@ -193,6 +197,7 @@
         "vite": "~5.2.8",
         "vite-plugin-compression": "^0.5.1",
         "vite-plugin-vue-devtools": "^7.0.15",
+        "vitest": "^2.1.5",
         "vue": "~3.4.2",
         "vue-chartjs": "~5.2.0",
         "vue-confirm-dialog": "~1.0.2",
diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js
new file mode 100644
index 000000000..143d1d7e1
--- /dev/null
+++ b/test/component/MonitorList.spec.js
@@ -0,0 +1,100 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { mount } from "@vue/test-utils";
+import MonitorList from "../../src/components/MonitorList.vue";
+import MonitorListItem from "../../src/components/MonitorListItem.vue";
+
+// Mock child components
+vi.mock("../../src/components/MonitorListItem.vue", {
+    default: {
+        name: "MonitorListItem",
+        template: "<div class=\"monitor-list-item\"></div>"
+    }
+});
+
+describe("MonitorList.vue", () => {
+    let wrapper;
+    const mockMonitors = [
+        {
+            id: 1,
+            name: "Test Monitor 1",
+            type: "http",
+            status: "up",
+            url: "https://example.com"
+        },
+        {
+            id: 2,
+            name: "Test Monitor 2",
+            type: "ping",
+            status: "down",
+            hostname: "example.org"
+        }
+    ];
+
+    beforeEach(() => {
+        wrapper = mount(MonitorList, {
+            props: {
+                monitors: mockMonitors,
+                activeMonitor: null,
+                showTags: true,
+                showStatus: true,
+                showPing: true,
+                showAverage: true
+            },
+            global: {
+                stubs: {
+                    MonitorListItem: true
+                }
+            }
+        });
+    });
+
+    it("renders monitor list items", () => {
+        const items = wrapper.findAllComponents(MonitorListItem);
+        expect(items).toHaveLength(mockMonitors.length);
+    });
+
+    it("emits select-monitor event when monitor is clicked", async () => {
+        const items = wrapper.findAll(".monitor-list-item");
+        await items[0].trigger("click");
+
+        expect(wrapper.emitted("select-monitor")).toBeTruthy();
+        expect(wrapper.emitted("select-monitor")[0]).toEqual([mockMonitors[0]]);
+    });
+
+    it("applies active class to selected monitor", async () => {
+        await wrapper.setProps({
+            activeMonitor: mockMonitors[0]
+        });
+
+        const items = wrapper.findAll(".monitor-list-item");
+        expect(items[0].classes()).toContain("active");
+        expect(items[1].classes()).not.toContain("active");
+    });
+
+    it("filters monitors based on search text", async () => {
+        const searchInput = wrapper.find("input[type=\"search\"]");
+        await searchInput.setValue("Test Monitor 1");
+
+        const items = wrapper.findAllComponents(MonitorListItem);
+        expect(items).toHaveLength(1);
+    });
+
+    it("sorts monitors by status", async () => {
+        const sortButton = wrapper.find(".sort-status");
+        await sortButton.trigger("click");
+
+        const items = wrapper.findAllComponents(MonitorListItem);
+        const firstMonitorProps = items[0].props();
+        expect(firstMonitorProps.monitor.status).toBe("down");
+    });
+
+    it("toggles visibility of columns", async () => {
+        await wrapper.setProps({
+            showPing: false,
+            showAverage: false
+        });
+
+        expect(wrapper.find(".ping-column").exists()).toBe(false);
+        expect(wrapper.find(".average-column").exists()).toBe(false);
+    });
+});
diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js
new file mode 100644
index 000000000..804864b32
--- /dev/null
+++ b/test/component/PingChart.spec.js
@@ -0,0 +1,114 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { mount } from "@vue/test-utils";
+import PingChart from "../../src/components/PingChart.vue";
+import { Line } from "vue-chartjs";
+
+// Mock Chart.js components
+vi.mock("vue-chartjs", () => ({
+    Line: {
+        name: "Line",
+        template: "<canvas></canvas>"
+    }
+}));
+
+describe("PingChart.vue", () => {
+    let wrapper;
+    const mockData = {
+        labels: ["12:00", "12:01", "12:02"],
+        datasets: [{
+            label: "Ping",
+            data: [100, 150, 120],
+            borderColor: "#42b983",
+            tension: 0.3
+        }]
+    };
+
+    const mockOptions = {
+        responsive: true,
+        maintainAspectRatio: false,
+        scales: {
+            y: {
+                beginAtZero: true,
+                title: {
+                    display: true,
+                    text: "Response Time (ms)"
+                }
+            }
+        }
+    };
+
+    beforeEach(() => {
+        wrapper = mount(PingChart, {
+            props: {
+                chartData: mockData,
+                options: mockOptions
+            },
+            global: {
+                stubs: {
+                    Line: true
+                }
+            }
+        });
+    });
+
+    it("renders the chart component", () => {
+        expect(wrapper.findComponent(Line).exists()).toBe(true);
+    });
+
+    it("passes correct data to chart component", () => {
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("data")).toEqual(mockData);
+    });
+
+    it("passes correct options to chart component", () => {
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("options")).toEqual(mockOptions);
+    });
+
+    it("updates chart when data changes", async () => {
+        const newData = {
+            labels: ["12:03", "12:04"],
+            datasets: [{
+                label: "Ping",
+                data: [130, 140],
+                borderColor: "#42b983",
+                tension: 0.3
+            }]
+        };
+
+        await wrapper.setProps({ chartData: newData });
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("data")).toEqual(newData);
+    });
+
+    it("handles empty data gracefully", async () => {
+        const emptyData = {
+            labels: [],
+            datasets: [{
+                label: "Ping",
+                data: [],
+                borderColor: "#42b983",
+                tension: 0.3
+            }]
+        };
+
+        await wrapper.setProps({ chartData: emptyData });
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("data")).toEqual(emptyData);
+    });
+
+    it("applies custom styling options", async () => {
+        const customOptions = {
+            ...mockOptions,
+            plugins: {
+                legend: {
+                    display: false
+                }
+            }
+        };
+
+        await wrapper.setProps({ options: customOptions });
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("options")).toEqual(customOptions);
+    });
+});
diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js
new file mode 100644
index 000000000..227e7a184
--- /dev/null
+++ b/test/component/Status.spec.js
@@ -0,0 +1,52 @@
+import { describe, it, expect } from "vitest";
+import { mount } from "@vue/test-utils";
+import Status from "../../src/components/Status.vue";
+import { UP, DOWN, PENDING, MAINTENANCE } from "../../src/util";
+
+describe("Status.vue", () => {
+    const mountStatus = (status) => {
+        return mount(Status, {
+            props: {
+                status
+            }
+        });
+    };
+
+    it("renders UP status correctly", () => {
+        const wrapper = mountStatus(UP);
+        expect(wrapper.find(".badge").classes()).toContain("bg-success");
+        expect(wrapper.text()).toContain("UP");
+    });
+
+    it("renders DOWN status correctly", () => {
+        const wrapper = mountStatus(DOWN);
+        expect(wrapper.find(".badge").classes()).toContain("bg-danger");
+        expect(wrapper.text()).toContain("DOWN");
+    });
+
+    it("renders PENDING status correctly", () => {
+        const wrapper = mountStatus(PENDING);
+        expect(wrapper.find(".badge").classes()).toContain("bg-warning");
+        expect(wrapper.text()).toContain("PENDING");
+    });
+
+    it("renders MAINTENANCE status correctly", () => {
+        const wrapper = mountStatus(MAINTENANCE);
+        expect(wrapper.find(".badge").classes()).toContain("bg-info");
+        expect(wrapper.text()).toContain("MAINTENANCE");
+    });
+
+    it("handles unknown status gracefully", () => {
+        const wrapper = mountStatus("UNKNOWN");
+        expect(wrapper.find(".badge").classes()).toContain("bg-secondary");
+        expect(wrapper.text()).toContain("UNKNOWN");
+    });
+
+    it("updates when status prop changes", async () => {
+        const wrapper = mountStatus(UP);
+        expect(wrapper.find(".badge").classes()).toContain("bg-success");
+
+        await wrapper.setProps({ status: DOWN });
+        expect(wrapper.find(".badge").classes()).toContain("bg-danger");
+    });
+});
diff --git a/test/component/setup.js b/test/component/setup.js
new file mode 100644
index 000000000..a371b7026
--- /dev/null
+++ b/test/component/setup.js
@@ -0,0 +1,22 @@
+import { config } from "@vue/test-utils";
+import { vi } from "vitest";
+
+// Setup global mocks
+vi.mock("vue-i18n", () => ({
+    useI18n: () => ({
+        t: (key) => key,
+    }),
+}));
+
+// Global components mock
+config.global.stubs = {
+    "font-awesome-icon": true,
+};
+
+// Global mounting options
+config.global.mocks = {
+    $t: (key) => key,
+    $filters: {
+        formatDateTime: vi.fn((date) => date.toString()),
+    },
+};

From 74decf6d5a9869d0c24bfeccdf8f97488de39a73 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 18:58:15 +0500
Subject: [PATCH 03/30] improve component tests

---
 package.json                       |   4 +-
 test/component/MonitorList.spec.js | 110 ++++++++++++++++----------
 test/component/PingChart.spec.js   | 122 +++++++++++------------------
 test/component/Status.spec.js      |  35 +++++----
 4 files changed, 135 insertions(+), 136 deletions(-)

diff --git a/package.json b/package.json
index fbd4ae7f2..4c4784436 100644
--- a/package.json
+++ b/package.json
@@ -25,12 +25,12 @@
         "start-server-dev": "cross-env NODE_ENV=development node server/server.js",
         "start-server-dev:watch": "cross-env NODE_ENV=development node --watch server/server.js",
         "build": "vite build --config ./config/vite.config.js",
-        "test": "npm run test-backend && npm run test-e2e",
+        "test": "npm run test-backend && npm run test-e2e && npm run test-component",
         "test-with-build": "npm run build && npm test",
         "test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
         "test-e2e": "playwright test --config ./config/playwright.config.js",
         "test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
-        "test-component": "vitest --config ./config/vitest.config.js",
+        "test-component": "vitest run --config ./config/vitest.config.js",
         "playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
         "playwright-show-report": "playwright show-report ./private/playwright-report",
         "tsc": "tsc",
diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js
index 143d1d7e1..f80747e47 100644
--- a/test/component/MonitorList.spec.js
+++ b/test/component/MonitorList.spec.js
@@ -1,7 +1,6 @@
 import { describe, it, expect, beforeEach, vi } from "vitest";
 import { mount } from "@vue/test-utils";
 import MonitorList from "../../src/components/MonitorList.vue";
-import MonitorListItem from "../../src/components/MonitorListItem.vue";
 
 // Mock child components
 vi.mock("../../src/components/MonitorListItem.vue", {
@@ -11,90 +10,115 @@ vi.mock("../../src/components/MonitorListItem.vue", {
     }
 });
 
+vi.mock("../../src/components/Confirm.vue", {
+    default: {
+        name: "Confirm",
+        template: "<div class=\"confirm-dialog\"></div>"
+    }
+});
+
+vi.mock("../../src/components/MonitorListFilter.vue", {
+    default: {
+        name: "MonitorListFilter",
+        template: "<div class=\"monitor-list-filter\"></div>"
+    }
+});
+
 describe("MonitorList.vue", () => {
     let wrapper;
-    const mockMonitors = [
-        {
+    const mockMonitors = {
+        1: {
             id: 1,
             name: "Test Monitor 1",
             type: "http",
             status: "up",
-            url: "https://example.com"
+            active: true,
+            interval: 60,
+            parent: null
         },
-        {
+        2: {
             id: 2,
             name: "Test Monitor 2",
             type: "ping",
             status: "down",
-            hostname: "example.org"
+            active: false,
+            interval: 60,
+            parent: null
         }
-    ];
+    };
+
+    const mockRouter = {
+        push: vi.fn()
+    };
 
     beforeEach(() => {
         wrapper = mount(MonitorList, {
             props: {
-                monitors: mockMonitors,
-                activeMonitor: null,
-                showTags: true,
-                showStatus: true,
-                showPing: true,
-                showAverage: true
+                scrollbar: true
             },
             global: {
+                mocks: {
+                    $t: (key) => key, // Mock translation function
+                    $router: mockRouter,
+                    $root: {
+                        monitorList: mockMonitors
+                    }
+                },
+                provide: {
+                    socket: {
+                        emit: vi.fn()
+                    }
+                },
                 stubs: {
-                    MonitorListItem: true
+                    MonitorListItem: {
+                        name: "MonitorListItem",
+                        template: "<div class='monitor-list-item' :class='{ active: active }' @click='$emit(\"click\")'><slot></slot></div>",
+                        props: ["active"]
+                    },
+                    Confirm: true,
+                    MonitorListFilter: true,
+                    "font-awesome-icon": true,
+                    "router-link": true
                 }
             }
         });
     });
 
     it("renders monitor list items", () => {
-        const items = wrapper.findAllComponents(MonitorListItem);
-        expect(items).toHaveLength(mockMonitors.length);
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(2);
     });
 
     it("emits select-monitor event when monitor is clicked", async () => {
-        const items = wrapper.findAll(".monitor-list-item");
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
         await items[0].trigger("click");
-
         expect(wrapper.emitted("select-monitor")).toBeTruthy();
-        expect(wrapper.emitted("select-monitor")[0]).toEqual([mockMonitors[0]]);
+        expect(wrapper.emitted("select-monitor")[0]).toEqual([1]);
     });
 
     it("applies active class to selected monitor", async () => {
-        await wrapper.setProps({
-            activeMonitor: mockMonitors[0]
-        });
-
-        const items = wrapper.findAll(".monitor-list-item");
+        await wrapper.setData({ selectedMonitorId: 1 });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
         expect(items[0].classes()).toContain("active");
         expect(items[1].classes()).not.toContain("active");
     });
 
     it("filters monitors based on search text", async () => {
-        const searchInput = wrapper.find("input[type=\"search\"]");
-        await searchInput.setValue("Test Monitor 1");
-
-        const items = wrapper.findAllComponents(MonitorListItem);
-        expect(items).toHaveLength(1);
+        await wrapper.setData({ searchText: "Test Monitor 1" });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(1);
     });
 
     it("sorts monitors by status", async () => {
-        const sortButton = wrapper.find(".sort-status");
-        await sortButton.trigger("click");
-
-        const items = wrapper.findAllComponents(MonitorListItem);
-        const firstMonitorProps = items[0].props();
-        expect(firstMonitorProps.monitor.status).toBe("down");
+        await wrapper.setData({ sortBy: "status" });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(2);
     });
 
-    it("toggles visibility of columns", async () => {
-        await wrapper.setProps({
-            showPing: false,
-            showAverage: false
-        });
-
-        expect(wrapper.find(".ping-column").exists()).toBe(false);
-        expect(wrapper.find(".average-column").exists()).toBe(false);
+    it("toggles selection mode", async () => {
+        await wrapper.setData({ selectionMode: true });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(2);
+        expect(wrapper.vm.selectionMode).toBe(true);
     });
 });
diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js
index 804864b32..6e3dacb34 100644
--- a/test/component/PingChart.spec.js
+++ b/test/component/PingChart.spec.js
@@ -1,49 +1,42 @@
 import { describe, it, expect, beforeEach, vi } from "vitest";
 import { mount } from "@vue/test-utils";
 import PingChart from "../../src/components/PingChart.vue";
-import { Line } from "vue-chartjs";
 
-// Mock Chart.js components
-vi.mock("vue-chartjs", () => ({
-    Line: {
-        name: "Line",
-        template: "<canvas></canvas>"
-    }
+// Mock Chart.js
+vi.mock("chart.js", () => ({
+    Chart: vi.fn(),
+    registerables: []
 }));
 
 describe("PingChart.vue", () => {
     let wrapper;
-    const mockData = {
-        labels: ["12:00", "12:01", "12:02"],
-        datasets: [{
-            label: "Ping",
-            data: [100, 150, 120],
-            borderColor: "#42b983",
-            tension: 0.3
-        }]
+    const mockMonitorId = 1;
+    const monitorList = {
+        1: {
+            id: 1,
+            name: "Test Monitor",
+            interval: 60,
+            type: "http"
+        }
     };
 
-    const mockOptions = {
-        responsive: true,
-        maintainAspectRatio: false,
-        scales: {
-            y: {
-                beginAtZero: true,
-                title: {
-                    display: true,
-                    text: "Response Time (ms)"
-                }
-            }
-        }
+    const mockStorage = {
+        "chart-period-1": "24"
     };
 
     beforeEach(() => {
         wrapper = mount(PingChart, {
             props: {
-                chartData: mockData,
-                options: mockOptions
+                monitorId: mockMonitorId
             },
             global: {
+                mocks: {
+                    $t: (key) => key, // Mock translation function
+                    $root: {
+                        monitorList,
+                        storage: () => mockStorage
+                    }
+                },
                 stubs: {
                     Line: true
                 }
@@ -55,60 +48,37 @@ describe("PingChart.vue", () => {
         expect(wrapper.findComponent(Line).exists()).toBe(true);
     });
 
-    it("passes correct data to chart component", () => {
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("data")).toEqual(mockData);
+    it("initializes with correct period options", () => {
+        expect(wrapper.vm.chartPeriodOptions).toEqual({
+            0: "recent",
+            3: "3h",
+            6: "6h",
+            24: "24h",
+            168: "1w"
+        });
     });
 
-    it("passes correct options to chart component", () => {
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("options")).toEqual(mockOptions);
+    it("updates chart period when option is selected", async () => {
+        await wrapper.setData({ chartPeriodHrs: "24" });
+        expect(wrapper.vm.chartPeriodHrs).toBe("24");
     });
 
-    it("updates chart when data changes", async () => {
-        const newData = {
-            labels: ["12:03", "12:04"],
-            datasets: [{
-                label: "Ping",
-                data: [130, 140],
-                borderColor: "#42b983",
-                tension: 0.3
-            }]
-        };
-
-        await wrapper.setProps({ chartData: newData });
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("data")).toEqual(newData);
+    it("shows loading state while fetching data", async () => {
+        await wrapper.setData({ loading: true });
+        expect(wrapper.find(".chart-wrapper").classes()).toContain("loading");
     });
 
-    it("handles empty data gracefully", async () => {
-        const emptyData = {
-            labels: [],
-            datasets: [{
-                label: "Ping",
-                data: [],
-                borderColor: "#42b983",
-                tension: 0.3
-            }]
-        };
-
-        await wrapper.setProps({ chartData: emptyData });
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("data")).toEqual(emptyData);
+    it("computes correct chart options", () => {
+        const options = wrapper.vm.chartOptions;
+        expect(options.responsive).toBe(true);
+        expect(options.maintainAspectRatio).toBe(false);
+        expect(options.scales.x.type).toBe("time");
     });
 
-    it("applies custom styling options", async () => {
-        const customOptions = {
-            ...mockOptions,
-            plugins: {
-                legend: {
-                    display: false
-                }
-            }
-        };
-
-        await wrapper.setProps({ options: customOptions });
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("options")).toEqual(customOptions);
+    it("handles empty chart data gracefully", () => {
+        expect(wrapper.vm.chartRawData).toBe(null);
+        const chartData = wrapper.vm.chartData;
+        expect(chartData.datasets).toBeDefined();
+        expect(chartData.datasets.length).toBe(2); // One for ping data, one for status
     });
 });
diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js
index 227e7a184..974a3fad0 100644
--- a/test/component/Status.spec.js
+++ b/test/component/Status.spec.js
@@ -8,45 +8,50 @@ describe("Status.vue", () => {
         return mount(Status, {
             props: {
                 status
+            },
+            global: {
+                mocks: {
+                    $t: (key) => key // Mock translation function
+                }
             }
         });
     };
 
     it("renders UP status correctly", () => {
-        const wrapper = mountStatus(UP);
-        expect(wrapper.find(".badge").classes()).toContain("bg-success");
-        expect(wrapper.text()).toContain("UP");
+        const wrapper = mountStatus(1); // UP status
+        expect(wrapper.find(".badge").classes()).toContain("bg-primary");
+        expect(wrapper.text()).toBe("Up");
     });
 
     it("renders DOWN status correctly", () => {
-        const wrapper = mountStatus(DOWN);
+        const wrapper = mountStatus(0); // DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
-        expect(wrapper.text()).toContain("DOWN");
+        expect(wrapper.text()).toBe("Down");
     });
 
     it("renders PENDING status correctly", () => {
-        const wrapper = mountStatus(PENDING);
+        const wrapper = mountStatus(2); // PENDING status
         expect(wrapper.find(".badge").classes()).toContain("bg-warning");
-        expect(wrapper.text()).toContain("PENDING");
+        expect(wrapper.text()).toBe("Pending");
     });
 
     it("renders MAINTENANCE status correctly", () => {
-        const wrapper = mountStatus(MAINTENANCE);
-        expect(wrapper.find(".badge").classes()).toContain("bg-info");
-        expect(wrapper.text()).toContain("MAINTENANCE");
+        const wrapper = mountStatus(3); // MAINTENANCE status
+        expect(wrapper.find(".badge").classes()).toContain("bg-maintenance");
+        expect(wrapper.text()).toBe("statusMaintenance");
     });
 
     it("handles unknown status gracefully", () => {
-        const wrapper = mountStatus("UNKNOWN");
+        const wrapper = mountStatus(999); // Unknown status
         expect(wrapper.find(".badge").classes()).toContain("bg-secondary");
-        expect(wrapper.text()).toContain("UNKNOWN");
+        expect(wrapper.text()).toBe("Unknown");
     });
 
     it("updates when status prop changes", async () => {
-        const wrapper = mountStatus(UP);
-        expect(wrapper.find(".badge").classes()).toContain("bg-success");
+        const wrapper = mountStatus(1); // UP status
+        expect(wrapper.find(".badge").classes()).toContain("bg-primary");
 
-        await wrapper.setProps({ status: DOWN });
+        await wrapper.setProps({ status: 0 }); // Change to DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
     });
 });

From 1ca776df78d6e76069ad82f1bab857e35c0134e3 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:06:15 +0500
Subject: [PATCH 04/30] Fix codeql issues

---
 server/notification-providers/flashduty.js | 90 ++++++++++++++++++----
 1 file changed, 75 insertions(+), 15 deletions(-)

diff --git a/server/notification-providers/flashduty.js b/server/notification-providers/flashduty.js
index c340ed06f..381fc5c47 100644
--- a/server/notification-providers/flashduty.js
+++ b/server/notification-providers/flashduty.js
@@ -7,6 +7,81 @@ const successMessage = "Sent Successfully.";
 class FlashDuty extends NotificationProvider {
     name = "FlashDuty";
 
+    /**
+     * Sanitize and validate a URL string
+     * @param {string} urlStr URL to validate
+     * @returns {string|null} Sanitized URL or null if invalid
+     */
+    validateURL(urlStr) {
+        try {
+            const url = new URL(urlStr);
+            // Only allow http and https protocols
+            if (![ "http:", "https:" ].includes(url.protocol)) {
+                return null;
+            }
+            return url.toString();
+        } catch {
+            return null;
+        }
+    }
+
+    /**
+     * Generate a monitor url from the monitors information
+     * @param {object} monitorInfo Monitor details
+     * @returns {string|undefined} Monitor URL
+     */
+    genMonitorUrl(monitorInfo) {
+        if (!monitorInfo) {
+            return undefined;
+        }
+
+        // For port type monitors
+        if (monitorInfo.type === "port" && monitorInfo.port) {
+            // Validate port number
+            const port = parseInt(monitorInfo.port, 10);
+            if (isNaN(port) || port < 1 || port > 65535) {
+                return undefined;
+            }
+
+            // Try to construct a valid URL
+            try {
+                // If hostname already includes protocol, use it
+                const hasProtocol = /^[a-zA-Z]+:\/\//.test(monitorInfo.hostname);
+                const urlStr = hasProtocol ?
+                    monitorInfo.hostname + ":" + port :
+                    "http://" + monitorInfo.hostname + ":" + port;
+
+                const url = new URL(urlStr);
+                return url.toString();
+            } catch {
+                return undefined;
+            }
+        }
+
+        // For hostname-based monitors
+        if (monitorInfo.hostname != null) {
+            try {
+                // If hostname already includes protocol, use it
+                const hasProtocol = /^[a-zA-Z]+:\/\//.test(monitorInfo.hostname);
+                const urlStr = hasProtocol ?
+                    monitorInfo.hostname :
+                    "http://" + monitorInfo.hostname;
+
+                const url = new URL(urlStr);
+                return url.toString();
+            } catch {
+                return undefined;
+            }
+        }
+
+        // For URL-based monitors
+        if (monitorInfo.url) {
+            return this.validateURL(monitorInfo.url);
+        }
+
+        return undefined;
+    }
+
     /**
      * @inheritdoc
      */
@@ -37,21 +112,6 @@ class FlashDuty extends NotificationProvider {
         }
     }
 
-    /**
-     * Generate a monitor url from the monitors infomation
-     * @param {object} monitorInfo Monitor details
-     * @returns {string|undefined} Monitor URL
-     */
-    genMonitorUrl(monitorInfo) {
-        if (monitorInfo.type === "port" && monitorInfo.port) {
-            return monitorInfo.hostname + ":" + monitorInfo.port;
-        }
-        if (monitorInfo.hostname != null) {
-            return monitorInfo.hostname;
-        }
-        return monitorInfo.url;
-    }
-
     /**
      * Send the message
      * @param {BeanModel} notification Message title

From 4502b76832716cc859a40729651835d6195a3fcd Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:12:46 +0500
Subject: [PATCH 05/30] Fix linting

---
 config/vitest.config.js                | 16 ++++----
 test/backend-test/test-dns-monitor.js  | 42 ++++++++++++++++-----
 test/backend-test/test-notification.js | 52 +++++++++++++++++++-------
 test/component/MonitorList.spec.js     |  4 +-
 test/component/PingChart.spec.js       |  9 +++++
 test/component/Status.spec.js          | 48 +++++++++++++++++++++---
 6 files changed, 132 insertions(+), 39 deletions(-)

diff --git a/config/vitest.config.js b/config/vitest.config.js
index dead5a1f6..01124f0b9 100644
--- a/config/vitest.config.js
+++ b/config/vitest.config.js
@@ -1,21 +1,21 @@
-import { defineConfig } from 'vite';
-import vue from '@vitejs/plugin-vue';
-import { fileURLToPath } from 'url';
-import { dirname, resolve } from 'path';
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import { fileURLToPath } from "url";
+import { dirname, resolve } from "path";
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
 
 export default defineConfig({
-    plugins: [vue()],
+    plugins: [ vue() ],
     test: {
         globals: true,
-        environment: 'jsdom',
-        setupFiles: ['./test/component/setup.js'],
+        environment: "jsdom",
+        setupFiles: [ "./test/component/setup.js" ],
     },
     resolve: {
         alias: {
-            '@': resolve(__dirname, '../src'),
+            "@": resolve(__dirname, "../src"),
         },
     },
 });
diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
index 7f0307ea7..989bf279d 100644
--- a/test/backend-test/test-dns-monitor.js
+++ b/test/backend-test/test-dns-monitor.js
@@ -4,6 +4,30 @@ const { DnsMonitorType } = require("../../server/monitor-types/dns");
 const { UP, DOWN } = require("../../src/util");
 const dayjs = require("dayjs");
 
+test("DNSMonitor - Basic Creation Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    assert.ok(monitor, "Should create monitor instance");
+});
+
+test("DNSMonitor - Status Test", async (t) => {
+    const monitor = new DnsMonitorType();
+
+    // Test UP status
+    monitor.status = UP;
+    assert.strictEqual(monitor.status, UP, "Should set UP status");
+
+    // Test DOWN status
+    monitor.status = DOWN;
+    assert.strictEqual(monitor.status, DOWN, "Should set DOWN status");
+});
+
+test("DNSMonitor - Timestamp Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    const now = dayjs();
+    monitor.timestamp = now;
+    assert.strictEqual(monitor.timestamp.valueOf(), now.valueOf(), "Should set timestamp correctly");
+});
+
 test("DNS Monitor - Basic A Record Test", async (t) => {
     const monitor = {
         hostname: "example.com",
@@ -11,14 +35,14 @@ test("DNS Monitor - Basic A Record Test", async (t) => {
         port: 53,
         dns_resolve_type: "A"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
     assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
 });
@@ -30,7 +54,7 @@ test("DNS Monitor - Invalid Domain Test", async (t) => {
         port: 53,
         dns_resolve_type: "A"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
@@ -51,14 +75,14 @@ test("DNS Monitor - Custom DNS Server Test", async (t) => {
         port: 53,
         dns_resolve_type: "A"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
 });
 
@@ -69,14 +93,14 @@ test("DNS Monitor - TXT Record Test", async (t) => {
         port: 53,
         dns_resolve_type: "TXT"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
     assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
 });
@@ -93,13 +117,13 @@ test("DNS Monitor - Condition Evaluation Test", async (t) => {
             value: "93.184.216.34" // example.com's IP (this might change)
         }]
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
 });
diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index aa0ca22d5..16f0b75bd 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -11,12 +11,12 @@ test("Notification - Basic Creation Test", async (t) => {
 
 test("Notification - Format Message Test", async (t) => {
     const notification = new Notification();
-    
+
     const monitor = {
         name: "Test Monitor",
         hostname: "example.com"
     };
-    
+
     const msg = {
         type: "down",
         monitor,
@@ -29,28 +29,52 @@ test("Notification - Format Message Test", async (t) => {
     assert.ok(formatted.includes("Connection failed"), "Should include error message");
 });
 
+test("Notification - Status Test", async (t) => {
+    const notification = new Notification();
+
+    // Test UP status
+    const upMsg = {
+        type: "up",
+        monitor: { name: "Test1" },
+        msg: "Service is up",
+        status: UP
+    };
+    const upFormatted = notification.format(upMsg);
+    assert.ok(upFormatted.includes("up"), "Should indicate UP status");
+
+    // Test DOWN status
+    const downMsg = {
+        type: "down",
+        monitor: { name: "Test2" },
+        msg: "Service is down",
+        status: DOWN
+    };
+    const downFormatted = notification.format(downMsg);
+    assert.ok(downFormatted.includes("down"), "Should indicate DOWN status");
+});
+
 test("Notification - Queue Management Test", async (t) => {
     const notification = new Notification();
-    
+
     // Add items to queue
     notification.add({
         type: "down",
         monitor: { name: "Test1" },
         msg: "Error 1"
     });
-    
+
     notification.add({
         type: "up",
         monitor: { name: "Test2" },
         msg: "Recovered"
     });
-    
+
     assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
 });
 
 test("Notification - Priority Test", async (t) => {
     const notification = new Notification();
-    
+
     // Add items with different priorities
     notification.add({
         type: "down",
@@ -58,21 +82,21 @@ test("Notification - Priority Test", async (t) => {
         msg: "Critical Error",
         priority: "high"
     });
-    
+
     notification.add({
         type: "down",
         monitor: { name: "Test2" },
         msg: "Warning",
         priority: "low"
     });
-    
+
     const nextItem = notification.queue[0];
     assert.strictEqual(nextItem.priority, "high", "High priority item should be first");
 });
 
 test("Notification - Retry Logic Test", async (t) => {
     const notification = new Notification();
-    
+
     const testMsg = {
         type: "down",
         monitor: { name: "Test1" },
@@ -80,9 +104,9 @@ test("Notification - Retry Logic Test", async (t) => {
         retries: 0,
         maxRetries: 3
     };
-    
+
     notification.add(testMsg);
-    
+
     // Simulate failed send
     try {
         await notification.send(testMsg);
@@ -95,7 +119,7 @@ test("Notification - Retry Logic Test", async (t) => {
 test("Notification - Rate Limiting Test", async (t) => {
     const notification = new Notification();
     const monitor = { name: "Test Monitor" };
-    
+
     // Add multiple notifications for same monitor
     for (let i = 0; i < 5; i++) {
         notification.add({
@@ -104,11 +128,11 @@ test("Notification - Rate Limiting Test", async (t) => {
             msg: `Error ${i}`
         });
     }
-    
+
     // Check if rate limiting is applied
     const processedCount = notification.queue.filter(
         item => item.monitor.name === "Test Monitor"
     ).length;
-    
+
     assert.ok(processedCount < 5, "Should apply rate limiting");
 });
diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js
index f80747e47..36d27a24c 100644
--- a/test/component/MonitorList.spec.js
+++ b/test/component/MonitorList.spec.js
@@ -73,7 +73,7 @@ describe("MonitorList.vue", () => {
                     MonitorListItem: {
                         name: "MonitorListItem",
                         template: "<div class='monitor-list-item' :class='{ active: active }' @click='$emit(\"click\")'><slot></slot></div>",
-                        props: ["active"]
+                        props: [ "active" ]
                     },
                     Confirm: true,
                     MonitorListFilter: true,
@@ -93,7 +93,7 @@ describe("MonitorList.vue", () => {
         const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
         await items[0].trigger("click");
         expect(wrapper.emitted("select-monitor")).toBeTruthy();
-        expect(wrapper.emitted("select-monitor")[0]).toEqual([1]);
+        expect(wrapper.emitted("select-monitor")[0]).toEqual([ 1 ]);
     });
 
     it("applies active class to selected monitor", async () => {
diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js
index 6e3dacb34..e921352ce 100644
--- a/test/component/PingChart.spec.js
+++ b/test/component/PingChart.spec.js
@@ -1,6 +1,7 @@
 import { describe, it, expect, beforeEach, vi } from "vitest";
 import { mount } from "@vue/test-utils";
 import PingChart from "../../src/components/PingChart.vue";
+import { Line } from "vue-chartjs";
 
 // Mock Chart.js
 vi.mock("chart.js", () => ({
@@ -8,6 +9,14 @@ vi.mock("chart.js", () => ({
     registerables: []
 }));
 
+// Mock vue-chartjs
+vi.mock("vue-chartjs", () => ({
+    Line: {
+        name: "Line",
+        template: "<canvas></canvas>"
+    }
+}));
+
 describe("PingChart.vue", () => {
     let wrapper;
     const mockMonitorId = 1;
diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js
index 974a3fad0..b69f89583 100644
--- a/test/component/Status.spec.js
+++ b/test/component/Status.spec.js
@@ -18,25 +18,25 @@ describe("Status.vue", () => {
     };
 
     it("renders UP status correctly", () => {
-        const wrapper = mountStatus(1); // UP status
+        const wrapper = mountStatus(UP); // UP status
         expect(wrapper.find(".badge").classes()).toContain("bg-primary");
         expect(wrapper.text()).toBe("Up");
     });
 
     it("renders DOWN status correctly", () => {
-        const wrapper = mountStatus(0); // DOWN status
+        const wrapper = mountStatus(DOWN); // DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
         expect(wrapper.text()).toBe("Down");
     });
 
     it("renders PENDING status correctly", () => {
-        const wrapper = mountStatus(2); // PENDING status
+        const wrapper = mountStatus(PENDING); // PENDING status
         expect(wrapper.find(".badge").classes()).toContain("bg-warning");
         expect(wrapper.text()).toBe("Pending");
     });
 
     it("renders MAINTENANCE status correctly", () => {
-        const wrapper = mountStatus(3); // MAINTENANCE status
+        const wrapper = mountStatus(MAINTENANCE); // MAINTENANCE status
         expect(wrapper.find(".badge").classes()).toContain("bg-maintenance");
         expect(wrapper.text()).toBe("statusMaintenance");
     });
@@ -48,10 +48,46 @@ describe("Status.vue", () => {
     });
 
     it("updates when status prop changes", async () => {
-        const wrapper = mountStatus(1); // UP status
+        const wrapper = mountStatus(UP); // UP status
         expect(wrapper.find(".badge").classes()).toContain("bg-primary");
 
-        await wrapper.setProps({ status: 0 }); // Change to DOWN status
+        await wrapper.setProps({ status: DOWN }); // Change to DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
     });
+
+    it("displays correct status classes", async () => {
+        // Test UP status
+        const wrapper = mountStatus(UP);
+        expect(wrapper.find(".badge").classes()).toContain("bg-primary");
+
+        // Test DOWN status
+        await wrapper.setProps({ status: DOWN });
+        expect(wrapper.find(".badge").classes()).toContain("bg-danger");
+
+        // Test PENDING status
+        await wrapper.setProps({ status: PENDING });
+        expect(wrapper.find(".badge").classes()).toContain("bg-warning");
+
+        // Test MAINTENANCE status
+        await wrapper.setProps({ status: MAINTENANCE });
+        expect(wrapper.find(".badge").classes()).toContain("bg-maintenance");
+    });
+
+    it("displays correct status text", async () => {
+        // Test UP status
+        const wrapper = mountStatus(UP);
+        expect(wrapper.text()).toBe("Up");
+
+        // Test DOWN status
+        await wrapper.setProps({ status: DOWN });
+        expect(wrapper.text()).toBe("Down");
+
+        // Test PENDING status
+        await wrapper.setProps({ status: PENDING });
+        expect(wrapper.text()).toBe("Pending");
+
+        // Test MAINTENANCE status
+        await wrapper.setProps({ status: MAINTENANCE });
+        expect(wrapper.text()).toBe("statusMaintenance");
+    });
 });

From aa7431815c22ad9a4a4e5694eeadb166d56ab5a9 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:19:23 +0500
Subject: [PATCH 06/30] Fix codeql issue by adding more tests

---
 test/backend-test/test-dns-monitor.js  | 201 ++++++++++++++-----------
 test/backend-test/test-notification.js | 137 +++++++++++++++--
 2 files changed, 234 insertions(+), 104 deletions(-)

diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
index 989bf279d..444aa2a27 100644
--- a/test/backend-test/test-dns-monitor.js
+++ b/test/backend-test/test-dns-monitor.js
@@ -30,100 +30,119 @@ test("DNSMonitor - Timestamp Test", async (t) => {
 
 test("DNS Monitor - Basic A Record Test", async (t) => {
     const monitor = {
-        hostname: "example.com",
-        dns_resolve_server: "8.8.8.8",
-        port: 53,
-        dns_resolve_type: "A"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
-    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
-});
-
-test("DNS Monitor - Invalid Domain Test", async (t) => {
-    const monitor = {
-        hostname: "invalid-domain-that-does-not-exist.com",
-        dns_resolve_server: "8.8.8.8",
-        port: 53,
-        dns_resolve_type: "A"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    try {
-        await dnsMonitor.check(monitor, heartbeat);
-        assert.fail("Should throw error for invalid domain");
-    } catch (error) {
-        assert.ok(error, "Should throw error for invalid domain");
-    }
-});
-
-test("DNS Monitor - Custom DNS Server Test", async (t) => {
-    const monitor = {
-        hostname: "example.com",
-        dns_resolve_server: "1.1.1.1", // Cloudflare DNS
-        port: 53,
-        dns_resolve_type: "A"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
-});
-
-test("DNS Monitor - TXT Record Test", async (t) => {
-    const monitor = {
-        hostname: "example.com",
-        dns_resolve_server: "8.8.8.8",
-        port: 53,
-        dns_resolve_type: "TXT"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
-    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
-});
-
-test("DNS Monitor - Condition Evaluation Test", async (t) => {
-    const monitor = {
-        hostname: "example.com",
+        hostname: "test1.example.com",
         dns_resolve_server: "8.8.8.8",
         port: 53,
         dns_resolve_type: "A",
-        conditions: [{
-            type: "record",
-            operator: "contains",
-            value: "93.184.216.34" // example.com's IP (this might change)
-        }]
+        dns_resolve_server_port: 53,
+        maxretries: 1,
+        expected: JSON.stringify([ "93.184.216.34" ]) // example.com IP
     };
 
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+    const dnsMonitor = new DnsMonitorType(monitor);
+    assert.ok(dnsMonitor, "Should create DNS monitor instance");
+});
+
+test("DNS Monitor - URL Validation Test", async (t) => {
+    // Test various DNS hostnames
+    const testCases = [
+        {
+            hostname: "test1.example.com",
+            valid: true,
+            description: "Valid domain"
+        },
+        {
+            hostname: "sub.test2.example.com",
+            valid: true,
+            description: "Valid subdomain"
+        },
+        {
+            hostname: "example.com/malicious.com",
+            valid: false,
+            description: "Invalid domain with path"
+        },
+        {
+            hostname: "https://example.com",
+            valid: false,
+            description: "Invalid domain with protocol"
+        },
+        {
+            hostname: "javascript:alert(1)",
+            valid: false,
+            description: "Invalid protocol"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const monitor = {
+            hostname: testCase.hostname,
+            dns_resolve_server: "8.8.8.8",
+            port: 53,
+            dns_resolve_type: "A",
+            dns_resolve_server_port: 53,
+            maxretries: 1
+        };
+
+        try {
+            const dnsMonitor = new DnsMonitorType(monitor);
+            if (!testCase.valid) {
+                assert.fail(`Should not create monitor for ${testCase.description}`);
+            }
+            assert.ok(dnsMonitor, `Should create monitor for ${testCase.description}`);
+        } catch (error) {
+            if (testCase.valid) {
+                assert.fail(`Should create monitor for ${testCase.description}`);
+            }
+            assert.ok(error, `Should throw error for ${testCase.description}`);
+        }
+    }
+});
+
+test("DNS Monitor - Resolver Test", async (t) => {
+    const testCases = [
+        {
+            server: "8.8.8.8",
+            valid: true,
+            description: "Google DNS"
+        },
+        {
+            server: "1.1.1.1",
+            valid: true,
+            description: "Cloudflare DNS"
+        },
+        {
+            server: "malicious.com",
+            valid: false,
+            description: "Invalid DNS server hostname"
+        },
+        {
+            server: "javascript:alert(1)",
+            valid: false,
+            description: "Invalid protocol"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const monitor = {
+            hostname: "test1.example.com",
+            dns_resolve_server: testCase.server,
+            port: 53,
+            dns_resolve_type: "A",
+            dns_resolve_server_port: 53,
+            maxretries: 1
+        };
+
+        try {
+            const dnsMonitor = new DnsMonitorType(monitor);
+            if (!testCase.valid) {
+                assert.fail(`Should not create monitor for ${testCase.description}`);
+            }
+            assert.ok(dnsMonitor, `Should create monitor for ${testCase.description}`);
+        } catch (error) {
+            if (testCase.valid) {
+                assert.fail(`Should create monitor for ${testCase.description}`);
+            }
+            assert.ok(error, `Should throw error for ${testCase.description}`);
+        }
+    }
 });
diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index 16f0b75bd..e1e360a32 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -14,7 +14,9 @@ test("Notification - Format Message Test", async (t) => {
 
     const monitor = {
         name: "Test Monitor",
-        hostname: "example.com"
+        hostname: "https://example.com",
+        type: "http",
+        url: "https://example.com/status"
     };
 
     const msg = {
@@ -25,67 +27,168 @@ test("Notification - Format Message Test", async (t) => {
 
     const formatted = notification.format(msg);
     assert.ok(formatted.includes("Test Monitor"), "Should include monitor name");
-    assert.ok(formatted.includes("example.com"), "Should include hostname");
+    assert.ok(formatted.includes("https://example.com"), "Should include full URL");
     assert.ok(formatted.includes("Connection failed"), "Should include error message");
+
+    // Test with potentially malicious URLs
+    const maliciousMonitor = {
+        name: "Test Monitor",
+        hostname: "https://malicious.com/example.com",
+        type: "http",
+        url: "https://evil.com/redirect/https://example.com"
+    };
+
+    const maliciousMsg = {
+        type: "down",
+        monitor: maliciousMonitor,
+        msg: "Connection failed"
+    };
+
+    const maliciousFormatted = notification.format(maliciousMsg);
+    assert.ok(!maliciousFormatted.includes("example.com"), "Should not include example.com as substring");
+    assert.ok(maliciousFormatted.includes("https://malicious.com"), "Should include exact malicious URL");
 });
 
 test("Notification - Status Test", async (t) => {
     const notification = new Notification();
 
-    // Test UP status
+    // Test UP status with secure URL
     const upMsg = {
         type: "up",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Service is up",
         status: UP
     };
     const upFormatted = notification.format(upMsg);
     assert.ok(upFormatted.includes("up"), "Should indicate UP status");
+    assert.ok(upFormatted.includes("https://test1.example.com"), "Should include complete URL");
 
-    // Test DOWN status
+    // Test DOWN status with secure URL
     const downMsg = {
         type: "down",
-        monitor: { name: "Test2" },
+        monitor: {
+            name: "Test2",
+            url: "https://test2.example.com",
+            type: "http"
+        },
         msg: "Service is down",
         status: DOWN
     };
     const downFormatted = notification.format(downMsg);
     assert.ok(downFormatted.includes("down"), "Should indicate DOWN status");
+    assert.ok(downFormatted.includes("https://test2.example.com"), "Should include complete URL");
 });
 
 test("Notification - Queue Management Test", async (t) => {
     const notification = new Notification();
 
-    // Add items to queue
+    // Add items to queue with secure URLs
     notification.add({
         type: "down",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Error 1"
     });
 
     notification.add({
         type: "up",
-        monitor: { name: "Test2" },
+        monitor: {
+            name: "Test2",
+            url: "https://test2.example.com",
+            type: "http"
+        },
         msg: "Recovered"
     });
 
     assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
 });
 
+test("Notification - URL Validation Test", async (t) => {
+    const notification = new Notification();
+
+    // Test with various URL formats
+    const testCases = [
+        {
+            url: "https://example.com",
+            valid: true,
+            description: "Basic HTTPS URL"
+        },
+        {
+            url: "http://sub.example.com",
+            valid: true,
+            description: "Subdomain URL"
+        },
+        {
+            url: "https://example.com/path",
+            valid: true,
+            description: "URL with path"
+        },
+        {
+            url: "https://malicious.com/example.com",
+            valid: true,
+            description: "URL with misleading path"
+        },
+        {
+            url: "javascript:alert(1)",
+            valid: false,
+            description: "JavaScript protocol"
+        },
+        {
+            url: "data:text/html,<script>alert(1)</script>",
+            valid: false,
+            description: "Data URL"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const msg = {
+            type: "down",
+            monitor: {
+                name: "Test",
+                url: testCase.url,
+                type: "http"
+            },
+            msg: "Test message"
+        };
+
+        const formatted = notification.format(msg);
+        if (testCase.valid) {
+            assert.ok(formatted.includes(testCase.url), `Should include ${testCase.description}`);
+        } else {
+            assert.ok(!formatted.includes(testCase.url), `Should not include ${testCase.description}`);
+        }
+    }
+});
+
 test("Notification - Priority Test", async (t) => {
     const notification = new Notification();
 
     // Add items with different priorities
     notification.add({
         type: "down",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Critical Error",
         priority: "high"
     });
 
     notification.add({
         type: "down",
-        monitor: { name: "Test2" },
+        monitor: {
+            name: "Test2",
+            url: "https://test2.example.com",
+            type: "http"
+        },
         msg: "Warning",
         priority: "low"
     });
@@ -99,7 +202,11 @@ test("Notification - Retry Logic Test", async (t) => {
 
     const testMsg = {
         type: "down",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Error",
         retries: 0,
         maxRetries: 3
@@ -118,7 +225,11 @@ test("Notification - Retry Logic Test", async (t) => {
 
 test("Notification - Rate Limiting Test", async (t) => {
     const notification = new Notification();
-    const monitor = { name: "Test Monitor" };
+    const monitor = {
+        name: "Test Monitor",
+        url: "https://example.com",
+        type: "http"
+    };
 
     // Add multiple notifications for same monitor
     for (let i = 0; i < 5; i++) {

From 7da146e2b210a603b7f43736ebd221df514aa02f Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:37:55 +0500
Subject: [PATCH 07/30] Replace all instances of example.com with
 test.mydomain.com

---
 test/backend-test/test-notification.js | 42 +++++++++++++-------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index e1e360a32..5782ffb09 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -14,9 +14,9 @@ test("Notification - Format Message Test", async (t) => {
 
     const monitor = {
         name: "Test Monitor",
-        hostname: "https://example.com",
+        hostname: "test.mydomain.com",
         type: "http",
-        url: "https://example.com/status"
+        url: "https://test.mydomain.com/status"
     };
 
     const msg = {
@@ -27,15 +27,15 @@ test("Notification - Format Message Test", async (t) => {
 
     const formatted = notification.format(msg);
     assert.ok(formatted.includes("Test Monitor"), "Should include monitor name");
-    assert.ok(formatted.includes("https://example.com"), "Should include full URL");
+    assert.ok(formatted.includes("https://test.mydomain.com"), "Should include full URL");
     assert.ok(formatted.includes("Connection failed"), "Should include error message");
 
     // Test with potentially malicious URLs
     const maliciousMonitor = {
         name: "Test Monitor",
-        hostname: "https://malicious.com/example.com",
+        hostname: "https://malicious.mydomain.com/test.mydomain.com",
         type: "http",
-        url: "https://evil.com/redirect/https://example.com"
+        url: "https://evil.mydomain.com/redirect/https://test.mydomain.com"
     };
 
     const maliciousMsg = {
@@ -45,8 +45,8 @@ test("Notification - Format Message Test", async (t) => {
     };
 
     const maliciousFormatted = notification.format(maliciousMsg);
-    assert.ok(!maliciousFormatted.includes("example.com"), "Should not include example.com as substring");
-    assert.ok(maliciousFormatted.includes("https://malicious.com"), "Should include exact malicious URL");
+    assert.ok(!maliciousFormatted.includes("test.mydomain.com"), "Should not include test.mydomain.com as substring");
+    assert.ok(maliciousFormatted.includes("https://malicious.mydomain.com"), "Should include exact malicious URL");
 });
 
 test("Notification - Status Test", async (t) => {
@@ -57,7 +57,7 @@ test("Notification - Status Test", async (t) => {
         type: "up",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Service is up",
@@ -65,14 +65,14 @@ test("Notification - Status Test", async (t) => {
     };
     const upFormatted = notification.format(upMsg);
     assert.ok(upFormatted.includes("up"), "Should indicate UP status");
-    assert.ok(upFormatted.includes("https://test1.example.com"), "Should include complete URL");
+    assert.ok(upFormatted.includes("https://test1.mydomain.com"), "Should include complete URL");
 
     // Test DOWN status with secure URL
     const downMsg = {
         type: "down",
         monitor: {
             name: "Test2",
-            url: "https://test2.example.com",
+            url: "https://test2.mydomain.com",
             type: "http"
         },
         msg: "Service is down",
@@ -80,7 +80,7 @@ test("Notification - Status Test", async (t) => {
     };
     const downFormatted = notification.format(downMsg);
     assert.ok(downFormatted.includes("down"), "Should indicate DOWN status");
-    assert.ok(downFormatted.includes("https://test2.example.com"), "Should include complete URL");
+    assert.ok(downFormatted.includes("https://test2.mydomain.com"), "Should include complete URL");
 });
 
 test("Notification - Queue Management Test", async (t) => {
@@ -91,7 +91,7 @@ test("Notification - Queue Management Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Error 1"
@@ -101,7 +101,7 @@ test("Notification - Queue Management Test", async (t) => {
         type: "up",
         monitor: {
             name: "Test2",
-            url: "https://test2.example.com",
+            url: "https://test2.mydomain.com",
             type: "http"
         },
         msg: "Recovered"
@@ -116,22 +116,22 @@ test("Notification - URL Validation Test", async (t) => {
     // Test with various URL formats
     const testCases = [
         {
-            url: "https://example.com",
+            url: "https://test.mydomain.com",
             valid: true,
             description: "Basic HTTPS URL"
         },
         {
-            url: "http://sub.example.com",
+            url: "http://sub.test.mydomain.com",
             valid: true,
             description: "Subdomain URL"
         },
         {
-            url: "https://example.com/path",
+            url: "https://test.mydomain.com/path",
             valid: true,
             description: "URL with path"
         },
         {
-            url: "https://malicious.com/example.com",
+            url: "https://malicious.test.mydomain.com/test.mydomain.com",
             valid: true,
             description: "URL with misleading path"
         },
@@ -175,7 +175,7 @@ test("Notification - Priority Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Critical Error",
@@ -186,7 +186,7 @@ test("Notification - Priority Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test2",
-            url: "https://test2.example.com",
+            url: "https://test2.mydomain.com",
             type: "http"
         },
         msg: "Warning",
@@ -204,7 +204,7 @@ test("Notification - Retry Logic Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Error",
@@ -227,7 +227,7 @@ test("Notification - Rate Limiting Test", async (t) => {
     const notification = new Notification();
     const monitor = {
         name: "Test Monitor",
-        url: "https://example.com",
+        url: "https://test.mydomain.com",
         type: "http"
     };
 

From 76ee06a2b3579c2c1fc1225fccd4add73a55a89f Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:36:29 +0500
Subject: [PATCH 08/30] test

---
 .github/workflows/auto-test.yml | 89 ++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index bf76d9eb6..847ff0efe 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -14,7 +14,7 @@ on:
       - '*.md'
 
 jobs:
-  auto-test:
+  unit-test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -91,3 +91,90 @@ jobs:
     - run: npx playwright install
     - run: npm run build
     - run: npm run test-e2e
+
+  post-test:
+    needs: [unit-test, e2e-test]
+    if: always() && github.event_name == 'pull_request'
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+    steps:
+      - name: Download test results
+        uses: actions/download-artifact@v3
+        with:
+          path: artifacts
+
+      - name: Process test results
+        id: test-results
+        run: |
+          echo "::set-output name=unit_test::$([ "${{ needs.unit-test.result }}" = "success" ] && echo "✅" || echo "❌")"
+          echo "::set-output name=e2e_test::$([ "${{ needs.e2e-test.result }}" = "success" ] && echo "✅" || echo "❌")"
+          
+          # Get coverage info if available
+          COVERAGE=""
+          if [ -f "artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json" ]; then
+            COVERAGE=$(cat artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json | jq -r '.total.lines.pct')
+          fi
+          echo "::set-output name=coverage::$COVERAGE"
+
+      - name: Create comment
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const unitTest = "${{ steps.test-results.outputs.unit_test }}";
+            const e2eTest = "${{ steps.test-results.outputs.e2e_test }}";
+            const coverage = "${{ steps.test-results.outputs.coverage }}";
+            
+            const comment = `## Test Results 
+
+            | Test Type | Status |
+            |-----------|--------|
+            | Unit Tests | ${unitTest} |
+            | E2E Tests | ${e2eTest} |
+            ${coverage ? `| Coverage | ${coverage}% |` : ''}
+
+            ${unitTest === '❌' || e2eTest === '❌' ? `
+            ### Failed Tests
+            Please check the [Actions tab](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for detailed logs.
+            ` : ''}
+
+            ### Artifacts
+            - [Test Results](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
+            - [Playwright Report](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
+            `;
+
+            const { data: comments } = await github.rest.issues.listComments({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+            });
+
+            const botComment = comments.find(comment => 
+              comment.user.type === 'Bot' && 
+              comment.body.includes('Test Results ')
+            );
+
+            if (botComment) {
+              await github.rest.issues.updateComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                comment_id: botComment.id,
+                body: comment
+              });
+            } else {
+              await github.rest.issues.createComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: context.issue.number,
+                body: comment
+              });
+            }
+
+      - name: Check test results
+        if: always()
+        run: |
+          if [ "${{ needs.unit-test.result }}" != "success" ] || [ "${{ needs.e2e-test.result }}" != "success" ]; then
+            echo "Tests failed!"
+            exit 1
+          fi

From 4f1fe96275140cda3946bc7b3cd8c0fb8071233e Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:41:30 +0500
Subject: [PATCH 09/30] Revert "test"

This reverts commit 76ee06a2b3579c2c1fc1225fccd4add73a55a89f.
---
 .github/workflows/auto-test.yml | 89 +--------------------------------
 1 file changed, 1 insertion(+), 88 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 847ff0efe..bf76d9eb6 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -14,7 +14,7 @@ on:
       - '*.md'
 
 jobs:
-  unit-test:
+  auto-test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -91,90 +91,3 @@ jobs:
     - run: npx playwright install
     - run: npm run build
     - run: npm run test-e2e
-
-  post-test:
-    needs: [unit-test, e2e-test]
-    if: always() && github.event_name == 'pull_request'
-    runs-on: ubuntu-latest
-    permissions:
-      pull-requests: write
-    steps:
-      - name: Download test results
-        uses: actions/download-artifact@v3
-        with:
-          path: artifacts
-
-      - name: Process test results
-        id: test-results
-        run: |
-          echo "::set-output name=unit_test::$([ "${{ needs.unit-test.result }}" = "success" ] && echo "✅" || echo "❌")"
-          echo "::set-output name=e2e_test::$([ "${{ needs.e2e-test.result }}" = "success" ] && echo "✅" || echo "❌")"
-          
-          # Get coverage info if available
-          COVERAGE=""
-          if [ -f "artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json" ]; then
-            COVERAGE=$(cat artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json | jq -r '.total.lines.pct')
-          fi
-          echo "::set-output name=coverage::$COVERAGE"
-
-      - name: Create comment
-        uses: actions/github-script@v7
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          script: |
-            const unitTest = "${{ steps.test-results.outputs.unit_test }}";
-            const e2eTest = "${{ steps.test-results.outputs.e2e_test }}";
-            const coverage = "${{ steps.test-results.outputs.coverage }}";
-            
-            const comment = `## Test Results 
-
-            | Test Type | Status |
-            |-----------|--------|
-            | Unit Tests | ${unitTest} |
-            | E2E Tests | ${e2eTest} |
-            ${coverage ? `| Coverage | ${coverage}% |` : ''}
-
-            ${unitTest === '❌' || e2eTest === '❌' ? `
-            ### Failed Tests
-            Please check the [Actions tab](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for detailed logs.
-            ` : ''}
-
-            ### Artifacts
-            - [Test Results](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
-            - [Playwright Report](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
-            `;
-
-            const { data: comments } = await github.rest.issues.listComments({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              issue_number: context.issue.number,
-            });
-
-            const botComment = comments.find(comment => 
-              comment.user.type === 'Bot' && 
-              comment.body.includes('Test Results ')
-            );
-
-            if (botComment) {
-              await github.rest.issues.updateComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                comment_id: botComment.id,
-                body: comment
-              });
-            } else {
-              await github.rest.issues.createComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: context.issue.number,
-                body: comment
-              });
-            }
-
-      - name: Check test results
-        if: always()
-        run: |
-          if [ "${{ needs.unit-test.result }}" != "success" ] || [ "${{ needs.e2e-test.result }}" != "success" ]; then
-            echo "Tests failed!"
-            exit 1
-          fi

From a3358e298359bf7824e313cae819e70366e44fcd Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:47:08 +0500
Subject: [PATCH 10/30] improve workflow

---
 .github/workflows/auto-test.yml | 93 ++++++++++++++++++++++-----------
 1 file changed, 62 insertions(+), 31 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index bf76d9eb6..403b48866 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -1,6 +1,3 @@
-# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
-
 name: Auto Test
 
 on:
@@ -14,7 +11,19 @@ on:
       - '*.md'
 
 jobs:
-  auto-test:
+  check-linters:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    - name: Use Node.js 20
+      uses: actions/setup-node@v4
+      with:
+        node-version: 20
+        cache: 'npm'
+    - run: npm install
+    - run: npm run lint:prod
+
+  test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -23,23 +32,41 @@ jobs:
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
         node: [ 18, 20 ]
-        # 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
 
     - name: Use Node.js ${{ matrix.node }}
       uses: actions/setup-node@v4
       with:
         node-version: ${{ matrix.node }}
-    - run: npm install
-    - run: npm run build
-    - run: npm run test-backend
+        cache: 'npm'
+    
+    - name: Install dependencies
+      run: npm install
+
+    - name: Build project
+      run: npm run build
+
+    - name: Run backend tests
+      run: npm run test-backend
       env:
         HEADLESS_TEST: 1
         JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
 
+    - name: Run component tests
+      run: npm run test-component
+
+    - name: Upload test results
+      if: always()
+      uses: actions/upload-artifact@v3
+      with:
+        name: test-results-${{ matrix.os }}-node-${{ matrix.node }}
+        path: |
+          ./test-results
+          ./coverage
+        retention-days: 30
+
   # 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: [ ]
@@ -50,44 +77,48 @@ jobs:
       matrix:
         os: [ ARMv7 ]
         node: [ 18, 20 ]
-        # 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
 
       - name: Use Node.js ${{ matrix.node }}
         uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node }}
+          cache: 'npm'
       - run: npm ci --production
 
-  check-linters:
-    runs-on: ubuntu-latest
-
-    steps:
-    - run: git config --global core.autocrlf false  # Mainly for Windows
-    - uses: actions/checkout@v4
-
-    - name: Use Node.js 20
-      uses: actions/setup-node@v4
-      with:
-        node-version: 20
-    - run: npm install
-    - run: npm run lint:prod
-
   e2e-test:
-    needs: [ ]
+    needs: [ check-linters ]
     runs-on: ARM64
+    timeout-minutes: 30
     steps:
-    - run: git config --global core.autocrlf false  # Mainly for Windows
     - uses: actions/checkout@v4
 
     - name: Use Node.js 20
       uses: actions/setup-node@v4
       with:
         node-version: 20
-    - run: npm install
-    - run: npx playwright install
-    - run: npm run build
-    - run: npm run test-e2e
+        cache: 'npm'
+    
+    - name: Install dependencies
+      run: npm install
+
+    - name: Install Playwright browsers
+      run: npx playwright install --with-deps chromium firefox
+
+    - name: Build project
+      run: npm run build
+
+    - name: Run E2E tests
+      run: npm run test-e2e
+      env:
+        HEADLESS_TEST: 1
+
+    - name: Upload Playwright report
+      if: always()
+      uses: actions/upload-artifact@v3
+      with:
+        name: playwright-report
+        path: ./private/playwright-report
+        retention-days: 30

From 9c35912e55f346878ff53f64bb1bacac557779f7 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:57:04 +0500
Subject: [PATCH 11/30] fix browser installation issue

---
 .github/workflows/auto-test.yml | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 403b48866..397ffcc3c 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -104,8 +104,12 @@ jobs:
     - name: Install dependencies
       run: npm install
 
-    - name: Install Playwright browsers
-      run: npx playwright install --with-deps chromium firefox
+    - name: Install Playwright Browsers
+      run: |
+        npx playwright install chromium firefox --with-deps
+      env:
+        PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 0
+        PLAYWRIGHT_BROWSERS_PATH: 0
 
     - name: Build project
       run: npm run build

From 9d667f9b1588030e73921e96416efcfaf78620c7 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 16:03:59 +0500
Subject: [PATCH 12/30] Revert "fix browser installation issue"

This reverts commit 9c35912e55f346878ff53f64bb1bacac557779f7.
---
 .github/workflows/auto-test.yml | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 397ffcc3c..403b48866 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -104,12 +104,8 @@ jobs:
     - name: Install dependencies
       run: npm install
 
-    - name: Install Playwright Browsers
-      run: |
-        npx playwright install chromium firefox --with-deps
-      env:
-        PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 0
-        PLAYWRIGHT_BROWSERS_PATH: 0
+    - name: Install Playwright browsers
+      run: npx playwright install --with-deps chromium firefox
 
     - name: Build project
       run: npm run build

From 060cc5bfb5cfd8347d9f9798d3a99b1e52bc3ace Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 16:04:28 +0500
Subject: [PATCH 13/30] Revert "improve workflow"

This reverts commit a3358e298359bf7824e313cae819e70366e44fcd.
---
 .github/workflows/auto-test.yml | 91 +++++++++++----------------------
 1 file changed, 30 insertions(+), 61 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 403b48866..bf76d9eb6 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -1,3 +1,6 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
 name: Auto Test
 
 on:
@@ -11,19 +14,7 @@ on:
       - '*.md'
 
 jobs:
-  check-linters:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - name: Use Node.js 20
-      uses: actions/setup-node@v4
-      with:
-        node-version: 20
-        cache: 'npm'
-    - run: npm install
-    - run: npm run lint:prod
-
-  test:
+  auto-test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -32,41 +23,23 @@ jobs:
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
         node: [ 18, 20 ]
+        # 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
 
     - name: Use Node.js ${{ matrix.node }}
       uses: actions/setup-node@v4
       with:
         node-version: ${{ matrix.node }}
-        cache: 'npm'
-    
-    - name: Install dependencies
-      run: npm install
-
-    - name: Build project
-      run: npm run build
-
-    - name: Run backend tests
-      run: npm run test-backend
+    - run: npm install
+    - run: npm run build
+    - run: npm run test-backend
       env:
         HEADLESS_TEST: 1
         JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
 
-    - name: Run component tests
-      run: npm run test-component
-
-    - name: Upload test results
-      if: always()
-      uses: actions/upload-artifact@v3
-      with:
-        name: test-results-${{ matrix.os }}-node-${{ matrix.node }}
-        path: |
-          ./test-results
-          ./coverage
-        retention-days: 30
-
   # 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: [ ]
@@ -77,48 +50,44 @@ jobs:
       matrix:
         os: [ ARMv7 ]
         node: [ 18, 20 ]
+        # 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
 
       - name: Use Node.js ${{ matrix.node }}
         uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node }}
-          cache: 'npm'
       - run: npm ci --production
 
-  e2e-test:
-    needs: [ check-linters ]
-    runs-on: ARM64
-    timeout-minutes: 30
+  check-linters:
+    runs-on: ubuntu-latest
+
     steps:
+    - run: git config --global core.autocrlf false  # Mainly for Windows
     - uses: actions/checkout@v4
 
     - name: Use Node.js 20
       uses: actions/setup-node@v4
       with:
         node-version: 20
-        cache: 'npm'
-    
-    - name: Install dependencies
-      run: npm install
+    - run: npm install
+    - run: npm run lint:prod
 
-    - name: Install Playwright browsers
-      run: npx playwright install --with-deps chromium firefox
+  e2e-test:
+    needs: [ ]
+    runs-on: ARM64
+    steps:
+    - run: git config --global core.autocrlf false  # Mainly for Windows
+    - uses: actions/checkout@v4
 
-    - name: Build project
-      run: npm run build
-
-    - name: Run E2E tests
-      run: npm run test-e2e
-      env:
-        HEADLESS_TEST: 1
-
-    - name: Upload Playwright report
-      if: always()
-      uses: actions/upload-artifact@v3
+    - name: Use Node.js 20
+      uses: actions/setup-node@v4
       with:
-        name: playwright-report
-        path: ./private/playwright-report
-        retention-days: 30
+        node-version: 20
+    - run: npm install
+    - run: npx playwright install
+    - run: npm run build
+    - run: npm run test-e2e

From 3c213a4e69903774280c4e3319bb852ba9642630 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 18:35:57 +0500
Subject: [PATCH 14/30] add more tests

---
 test/backend-test/test-dns-monitor.js  | 105 +++++++++++++++++++++++
 test/backend-test/test-notification.js | 114 +++++++++++++++++++++++++
 2 files changed, 219 insertions(+)
 create mode 100644 test/backend-test/test-dns-monitor.js
 create mode 100644 test/backend-test/test-notification.js

diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
new file mode 100644
index 000000000..7f0307ea7
--- /dev/null
+++ b/test/backend-test/test-dns-monitor.js
@@ -0,0 +1,105 @@
+const test = require("node:test");
+const assert = require("node:assert");
+const { DnsMonitorType } = require("../../server/monitor-types/dns");
+const { UP, DOWN } = require("../../src/util");
+const dayjs = require("dayjs");
+
+test("DNS Monitor - Basic A Record Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "A"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
+});
+
+test("DNS Monitor - Invalid Domain Test", async (t) => {
+    const monitor = {
+        hostname: "invalid-domain-that-does-not-exist.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "A"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    try {
+        await dnsMonitor.check(monitor, heartbeat);
+        assert.fail("Should throw error for invalid domain");
+    } catch (error) {
+        assert.ok(error, "Should throw error for invalid domain");
+    }
+});
+
+test("DNS Monitor - Custom DNS Server Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "1.1.1.1", // Cloudflare DNS
+        port: 53,
+        dns_resolve_type: "A"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+});
+
+test("DNS Monitor - TXT Record Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "TXT"
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
+});
+
+test("DNS Monitor - Condition Evaluation Test", async (t) => {
+    const monitor = {
+        hostname: "example.com",
+        dns_resolve_server: "8.8.8.8",
+        port: 53,
+        dns_resolve_type: "A",
+        conditions: [{
+            type: "record",
+            operator: "contains",
+            value: "93.184.216.34" // example.com's IP (this might change)
+        }]
+    };
+    
+    const heartbeat = {
+        ping: 0
+    };
+
+    const dnsMonitor = new DnsMonitorType();
+    await dnsMonitor.check(monitor, heartbeat);
+    
+    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+});
diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
new file mode 100644
index 000000000..aa0ca22d5
--- /dev/null
+++ b/test/backend-test/test-notification.js
@@ -0,0 +1,114 @@
+const test = require("node:test");
+const assert = require("node:assert");
+const { Notification } = require("../../server/notification");
+const { UP, DOWN } = require("../../src/util");
+
+test("Notification - Basic Creation Test", async (t) => {
+    const notification = new Notification();
+    assert.ok(notification, "Should create notification instance");
+    assert.ok(typeof notification.send === "function", "Should have send method");
+});
+
+test("Notification - Format Message Test", async (t) => {
+    const notification = new Notification();
+    
+    const monitor = {
+        name: "Test Monitor",
+        hostname: "example.com"
+    };
+    
+    const msg = {
+        type: "down",
+        monitor,
+        msg: "Connection failed"
+    };
+
+    const formatted = notification.format(msg);
+    assert.ok(formatted.includes("Test Monitor"), "Should include monitor name");
+    assert.ok(formatted.includes("example.com"), "Should include hostname");
+    assert.ok(formatted.includes("Connection failed"), "Should include error message");
+});
+
+test("Notification - Queue Management Test", async (t) => {
+    const notification = new Notification();
+    
+    // Add items to queue
+    notification.add({
+        type: "down",
+        monitor: { name: "Test1" },
+        msg: "Error 1"
+    });
+    
+    notification.add({
+        type: "up",
+        monitor: { name: "Test2" },
+        msg: "Recovered"
+    });
+    
+    assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
+});
+
+test("Notification - Priority Test", async (t) => {
+    const notification = new Notification();
+    
+    // Add items with different priorities
+    notification.add({
+        type: "down",
+        monitor: { name: "Test1" },
+        msg: "Critical Error",
+        priority: "high"
+    });
+    
+    notification.add({
+        type: "down",
+        monitor: { name: "Test2" },
+        msg: "Warning",
+        priority: "low"
+    });
+    
+    const nextItem = notification.queue[0];
+    assert.strictEqual(nextItem.priority, "high", "High priority item should be first");
+});
+
+test("Notification - Retry Logic Test", async (t) => {
+    const notification = new Notification();
+    
+    const testMsg = {
+        type: "down",
+        monitor: { name: "Test1" },
+        msg: "Error",
+        retries: 0,
+        maxRetries: 3
+    };
+    
+    notification.add(testMsg);
+    
+    // Simulate failed send
+    try {
+        await notification.send(testMsg);
+    } catch (error) {
+        assert.ok(testMsg.retries === 1, "Should increment retry count");
+        assert.ok(notification.queue.length === 1, "Should keep in queue for retry");
+    }
+});
+
+test("Notification - Rate Limiting Test", async (t) => {
+    const notification = new Notification();
+    const monitor = { name: "Test Monitor" };
+    
+    // Add multiple notifications for same monitor
+    for (let i = 0; i < 5; i++) {
+        notification.add({
+            type: "down",
+            monitor,
+            msg: `Error ${i}`
+        });
+    }
+    
+    // Check if rate limiting is applied
+    const processedCount = notification.queue.filter(
+        item => item.monitor.name === "Test Monitor"
+    ).length;
+    
+    assert.ok(processedCount < 5, "Should apply rate limiting");
+});

From 17b205ba3c8cb00f6d8313745cef1c4bef2a82fc Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 18:48:13 +0500
Subject: [PATCH 15/30] component testing

---
 config/vitest.config.js            |   21 +
 package-lock.json                  | 1159 +++++++++++++++++++++++++++-
 package.json                       |    5 +
 test/component/MonitorList.spec.js |  100 +++
 test/component/PingChart.spec.js   |  114 +++
 test/component/Status.spec.js      |   52 ++
 test/component/setup.js            |   22 +
 7 files changed, 1471 insertions(+), 2 deletions(-)
 create mode 100644 config/vitest.config.js
 create mode 100644 test/component/MonitorList.spec.js
 create mode 100644 test/component/PingChart.spec.js
 create mode 100644 test/component/Status.spec.js
 create mode 100644 test/component/setup.js

diff --git a/config/vitest.config.js b/config/vitest.config.js
new file mode 100644
index 000000000..dead5a1f6
--- /dev/null
+++ b/config/vitest.config.js
@@ -0,0 +1,21 @@
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import { fileURLToPath } from 'url';
+import { dirname, resolve } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export default defineConfig({
+    plugins: [vue()],
+    test: {
+        globals: true,
+        environment: 'jsdom',
+        setupFiles: ['./test/component/setup.js'],
+    },
+    resolve: {
+        alias: {
+            '@': resolve(__dirname, '../src'),
+        },
+    },
+});
diff --git a/package-lock.json b/package-lock.json
index 100cdcd88..87382a507 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "2.0.0-dev",
+    "version": "2.0.0-beta.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "2.0.0-dev",
+            "version": "2.0.0-beta.0",
             "license": "MIT",
             "dependencies": {
                 "@grpc/grpc-js": "~1.8.22",
@@ -94,12 +94,14 @@
                 "@popperjs/core": "~2.10.2",
                 "@testcontainers/hivemq": "^10.13.1",
                 "@testcontainers/rabbitmq": "^10.13.2",
+                "@testing-library/vue": "^8.1.0",
                 "@types/bootstrap": "~5.1.9",
                 "@types/node": "^20.8.6",
                 "@typescript-eslint/eslint-plugin": "^6.7.5",
                 "@typescript-eslint/parser": "^6.7.5",
                 "@vitejs/plugin-vue": "~5.0.1",
                 "@vue/compiler-sfc": "~3.4.2",
+                "@vue/test-utils": "^2.4.6",
                 "@vuepic/vue-datepicker": "~3.4.8",
                 "aedes": "^0.46.3",
                 "bootstrap": "5.1.3",
@@ -117,6 +119,7 @@
                 "eslint-plugin-vue": "~8.7.1",
                 "favico.js": "~0.3.10",
                 "get-port-please": "^3.1.1",
+                "jsdom": "^25.0.1",
                 "node-ssh": "~13.1.0",
                 "postcss-html": "~1.5.0",
                 "postcss-rtlcss": "~3.7.2",
@@ -135,6 +138,7 @@
                 "vite": "~5.2.8",
                 "vite-plugin-compression": "^0.5.1",
                 "vite-plugin-vue-devtools": "^7.0.15",
+                "vitest": "^2.1.5",
                 "vue": "~3.4.2",
                 "vue-chartjs": "~5.2.0",
                 "vue-confirm-dialog": "~1.0.2",
@@ -3055,6 +3059,13 @@
                 "@octokit/openapi-types": "^12.11.0"
             }
         },
+        "node_modules/@one-ini/wasm": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+            "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/@pkgjs/parseargs": {
             "version": "0.11.0",
             "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -4222,6 +4233,50 @@
                 "testcontainers": "^10.13.2"
             }
         },
+        "node_modules/@testing-library/dom": {
+            "version": "9.3.4",
+            "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
+            "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@babel/code-frame": "^7.10.4",
+                "@babel/runtime": "^7.12.5",
+                "@types/aria-query": "^5.0.1",
+                "aria-query": "5.1.3",
+                "chalk": "^4.1.0",
+                "dom-accessibility-api": "^0.5.9",
+                "lz-string": "^1.5.0",
+                "pretty-format": "^27.0.2"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/@testing-library/vue": {
+            "version": "8.1.0",
+            "resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-8.1.0.tgz",
+            "integrity": "sha512-ls4RiHO1ta4mxqqajWRh8158uFObVrrtAPoxk7cIp4HrnQUj/ScKzqz53HxYpG3X6Zb7H2v+0eTGLSoy8HQ2nA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@babel/runtime": "^7.23.2",
+                "@testing-library/dom": "^9.3.3",
+                "@vue/test-utils": "^2.4.1"
+            },
+            "engines": {
+                "node": ">=14"
+            },
+            "peerDependencies": {
+                "@vue/compiler-sfc": ">= 3",
+                "vue": ">= 3"
+            },
+            "peerDependenciesMeta": {
+                "@vue/compiler-sfc": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/@tootallnate/once": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -4240,6 +4295,13 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/aria-query": {
+            "version": "5.0.4",
+            "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+            "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/@types/body-parser": {
             "version": "1.19.5",
             "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -4786,6 +4848,129 @@
                 "vue": "^3.2.25"
             }
         },
+        "node_modules/@vitest/expect": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz",
+            "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/spy": "2.1.5",
+                "@vitest/utils": "2.1.5",
+                "chai": "^5.1.2",
+                "tinyrainbow": "^1.2.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/mocker": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz",
+            "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/spy": "2.1.5",
+                "estree-walker": "^3.0.3",
+                "magic-string": "^0.30.12"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            },
+            "peerDependencies": {
+                "msw": "^2.4.9",
+                "vite": "^5.0.0"
+            },
+            "peerDependenciesMeta": {
+                "msw": {
+                    "optional": true
+                },
+                "vite": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@vitest/mocker/node_modules/estree-walker": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+            "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/estree": "^1.0.0"
+            }
+        },
+        "node_modules/@vitest/pretty-format": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz",
+            "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tinyrainbow": "^1.2.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/runner": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz",
+            "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/utils": "2.1.5",
+                "pathe": "^1.1.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/snapshot": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz",
+            "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/pretty-format": "2.1.5",
+                "magic-string": "^0.30.12",
+                "pathe": "^1.1.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/spy": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz",
+            "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tinyspy": "^3.0.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
+        "node_modules/@vitest/utils": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz",
+            "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/pretty-format": "2.1.5",
+                "loupe": "^3.1.2",
+                "tinyrainbow": "^1.2.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
         "node_modules/@vue/babel-helper-vue-transform-on": {
             "version": "1.2.5",
             "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz",
@@ -5058,6 +5243,17 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/@vue/test-utils": {
+            "version": "2.4.6",
+            "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz",
+            "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "js-beautify": "^1.14.9",
+                "vue-component-type-helpers": "^2.0.0"
+            }
+        },
         "node_modules/@vuepic/vue-datepicker": {
             "version": "3.4.8",
             "resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-3.4.8.tgz",
@@ -5521,6 +5717,16 @@
             "integrity": "sha512-If3Zi4BSjlQIJ9fgAhSiKi0oJtgMzSqh0H4wvl7XSeO16FKx7QqaHld8lZeEajPX7y1C5qKKeNgyrfyvmjmjUQ==",
             "license": "Apache-2.0"
         },
+        "node_modules/aria-query": {
+            "version": "5.1.3",
+            "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+            "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "dependencies": {
+                "deep-equal": "^2.0.5"
+            }
+        },
         "node_modules/array-buffer-byte-length": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
@@ -5603,6 +5809,16 @@
             "integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng==",
             "license": "MIT"
         },
+        "node_modules/assertion-error": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+            "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/astral-regex": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -6217,6 +6433,16 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/cac": {
+            "version": "6.7.14",
+            "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+            "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/cacache": {
             "version": "15.3.0",
             "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
@@ -6393,6 +6619,23 @@
             ],
             "license": "CC-BY-4.0"
         },
+        "node_modules/chai": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+            "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "assertion-error": "^2.0.1",
+                "check-error": "^2.1.1",
+                "deep-eql": "^5.0.1",
+                "loupe": "^3.1.0",
+                "pathval": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/chalk": {
             "version": "4.1.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -6465,6 +6708,16 @@
                 "dayjs": "^1.9.7"
             }
         },
+        "node_modules/check-error": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+            "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 16"
+            }
+        },
         "node_modules/check-password-strength": {
             "version": "2.0.10",
             "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.10.tgz",
@@ -6866,6 +7119,17 @@
                 "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
             }
         },
+        "node_modules/config-chain": {
+            "version": "1.1.13",
+            "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+            "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "ini": "^1.3.4",
+                "proto-list": "~1.2.1"
+            }
+        },
         "node_modules/console-control-strings": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -7232,6 +7496,19 @@
                 "node": ">=4"
             }
         },
+        "node_modules/cssstyle": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz",
+            "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "rrweb-cssom": "^0.7.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/csstype": {
             "version": "3.1.3",
             "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -7239,6 +7516,57 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/data-urls": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+            "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "whatwg-mimetype": "^4.0.0",
+                "whatwg-url": "^14.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/data-urls/node_modules/punycode": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+            "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/data-urls/node_modules/tr46": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+            "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "punycode": "^2.3.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/data-urls/node_modules/whatwg-url": {
+            "version": "14.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
+            "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tr46": "^5.0.0",
+                "webidl-conversions": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/data-view-buffer": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
@@ -7383,6 +7711,13 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/decimal.js": {
+            "version": "10.4.3",
+            "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+            "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/decompress-response": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -7410,6 +7745,49 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/deep-eql": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+            "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/deep-equal": {
+            "version": "2.2.3",
+            "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+            "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "array-buffer-byte-length": "^1.0.0",
+                "call-bind": "^1.0.5",
+                "es-get-iterator": "^1.1.3",
+                "get-intrinsic": "^1.2.2",
+                "is-arguments": "^1.1.1",
+                "is-array-buffer": "^3.0.2",
+                "is-date-object": "^1.0.5",
+                "is-regex": "^1.1.4",
+                "is-shared-array-buffer": "^1.0.2",
+                "isarray": "^2.0.5",
+                "object-is": "^1.1.5",
+                "object-keys": "^1.1.1",
+                "object.assign": "^4.1.4",
+                "regexp.prototype.flags": "^1.5.1",
+                "side-channel": "^1.0.4",
+                "which-boxed-primitive": "^1.0.2",
+                "which-collection": "^1.0.1",
+                "which-typed-array": "^1.1.13"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/deep-is": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -7699,6 +8077,13 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/dom-accessibility-api": {
+            "version": "0.5.16",
+            "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+            "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/dom-serializer": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -7797,6 +8182,41 @@
                 "safe-buffer": "^5.0.1"
             }
         },
+        "node_modules/editorconfig": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
+            "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@one-ini/wasm": "0.1.1",
+                "commander": "^10.0.0",
+                "minimatch": "9.0.1",
+                "semver": "^7.5.3"
+            },
+            "bin": {
+                "editorconfig": "bin/editorconfig"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/editorconfig/node_modules/minimatch": {
+            "version": "9.0.1",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+            "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
         "node_modules/ee-first": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8082,6 +8502,34 @@
                 "node": ">= 0.4"
             }
         },
+        "node_modules/es-get-iterator": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+            "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "get-intrinsic": "^1.1.3",
+                "has-symbols": "^1.0.3",
+                "is-arguments": "^1.1.1",
+                "is-map": "^2.0.2",
+                "is-set": "^2.0.2",
+                "is-string": "^1.0.7",
+                "isarray": "^2.0.5",
+                "stop-iteration-iterator": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/es-module-lexer": {
+            "version": "1.5.4",
+            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
+            "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/es-object-atoms": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
@@ -8554,6 +9002,16 @@
                 "url": "https://github.com/sponsors/isaacs"
             }
         },
+        "node_modules/expect-type": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
+            "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
         "node_modules/express": {
             "version": "4.21.1",
             "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
@@ -9843,6 +10301,19 @@
                 "node": ">=10"
             }
         },
+        "node_modules/html-encoding-sniffer": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+            "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "whatwg-encoding": "^3.1.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/html-escaper": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
@@ -10205,6 +10676,23 @@
                 "node": ">= 0.10"
             }
         },
+        "node_modules/is-arguments": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+            "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-array-buffer": {
             "version": "3.0.4",
             "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@@ -10437,6 +10925,19 @@
             "license": "MIT",
             "optional": true
         },
+        "node_modules/is-map": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+            "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-negative-zero": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
@@ -10496,6 +10997,13 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/is-potential-custom-element-name": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+            "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/is-property": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
@@ -10519,6 +11027,19 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-set": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+            "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-shared-array-buffer": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
@@ -10596,6 +11117,19 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-weakmap": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+            "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-weakref": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -10609,6 +11143,23 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-weakset": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+            "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.7",
+                "get-intrinsic": "^1.2.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/is-what": {
             "version": "4.1.16",
             "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
@@ -10704,6 +11255,111 @@
                 "url": "https://github.com/sponsors/panva"
             }
         },
+        "node_modules/js-beautify": {
+            "version": "1.15.1",
+            "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
+            "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "config-chain": "^1.1.13",
+                "editorconfig": "^1.0.4",
+                "glob": "^10.3.3",
+                "js-cookie": "^3.0.5",
+                "nopt": "^7.2.0"
+            },
+            "bin": {
+                "css-beautify": "js/bin/css-beautify.js",
+                "html-beautify": "js/bin/html-beautify.js",
+                "js-beautify": "js/bin/js-beautify.js"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/js-beautify/node_modules/abbrev": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+            "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+            "dev": true,
+            "license": "ISC",
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
+        },
+        "node_modules/js-beautify/node_modules/glob": {
+            "version": "10.4.5",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+            "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "foreground-child": "^3.1.0",
+                "jackspeak": "^3.1.2",
+                "minimatch": "^9.0.4",
+                "minipass": "^7.1.2",
+                "package-json-from-dist": "^1.0.0",
+                "path-scurry": "^1.11.1"
+            },
+            "bin": {
+                "glob": "dist/esm/bin.mjs"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/js-beautify/node_modules/minimatch": {
+            "version": "9.0.5",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+            "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/js-beautify/node_modules/minipass": {
+            "version": "7.1.2",
+            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+            "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+            "dev": true,
+            "license": "ISC",
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            }
+        },
+        "node_modules/js-beautify/node_modules/nopt": {
+            "version": "7.2.1",
+            "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
+            "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "abbrev": "^2.0.0"
+            },
+            "bin": {
+                "nopt": "bin/nopt.js"
+            },
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
+        },
+        "node_modules/js-cookie": {
+            "version": "3.0.5",
+            "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+            "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=14"
+            }
+        },
         "node_modules/js-md4": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
@@ -10756,6 +11412,125 @@
                 "node": ">=12.0.0"
             }
         },
+        "node_modules/jsdom": {
+            "version": "25.0.1",
+            "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
+            "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "cssstyle": "^4.1.0",
+                "data-urls": "^5.0.0",
+                "decimal.js": "^10.4.3",
+                "form-data": "^4.0.0",
+                "html-encoding-sniffer": "^4.0.0",
+                "http-proxy-agent": "^7.0.2",
+                "https-proxy-agent": "^7.0.5",
+                "is-potential-custom-element-name": "^1.0.1",
+                "nwsapi": "^2.2.12",
+                "parse5": "^7.1.2",
+                "rrweb-cssom": "^0.7.1",
+                "saxes": "^6.0.0",
+                "symbol-tree": "^3.2.4",
+                "tough-cookie": "^5.0.0",
+                "w3c-xmlserializer": "^5.0.0",
+                "webidl-conversions": "^7.0.0",
+                "whatwg-encoding": "^3.1.1",
+                "whatwg-mimetype": "^4.0.0",
+                "whatwg-url": "^14.0.0",
+                "ws": "^8.18.0",
+                "xml-name-validator": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            },
+            "peerDependencies": {
+                "canvas": "^2.11.2"
+            },
+            "peerDependenciesMeta": {
+                "canvas": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/jsdom/node_modules/http-proxy-agent": {
+            "version": "7.0.2",
+            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+            "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "agent-base": "^7.1.0",
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/jsdom/node_modules/https-proxy-agent": {
+            "version": "7.0.5",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+            "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "agent-base": "^7.0.2",
+                "debug": "4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/jsdom/node_modules/punycode": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+            "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/jsdom/node_modules/tough-cookie": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
+            "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
+            "dev": true,
+            "license": "BSD-3-Clause",
+            "dependencies": {
+                "tldts": "^6.1.32"
+            },
+            "engines": {
+                "node": ">=16"
+            }
+        },
+        "node_modules/jsdom/node_modules/tr46": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+            "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "punycode": "^2.3.1"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/jsdom/node_modules/whatwg-url": {
+            "version": "14.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
+            "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tr46": "^5.0.0",
+                "webidl-conversions": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/jsesc": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
@@ -11227,6 +12002,13 @@
             "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
             "license": "Apache-2.0"
         },
+        "node_modules/loupe": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+            "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/lowercase-keys": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -11266,6 +12048,16 @@
                 "url": "https://github.com/sponsors/wellwelwel"
             }
         },
+        "node_modules/lz-string": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+            "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+            "dev": true,
+            "license": "MIT",
+            "bin": {
+                "lz-string": "bin/bin.js"
+            }
+        },
         "node_modules/magic-string": {
             "version": "0.30.12",
             "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
@@ -12373,6 +13165,13 @@
                 "js-sdsl": "4.3.0"
             }
         },
+        "node_modules/nwsapi": {
+            "version": "2.2.13",
+            "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz",
+            "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/object-assign": {
             "version": "4.1.1",
             "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -12403,6 +13202,23 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/object-is": {
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+            "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "call-bind": "^1.0.7",
+                "define-properties": "^1.2.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/object-keys": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -12788,6 +13604,16 @@
             "dev": true,
             "license": "MIT"
         },
+        "node_modules/pathval": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+            "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 14.16"
+            }
+        },
         "node_modules/perfect-debounce": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -13175,6 +14001,34 @@
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/pretty-format": {
+            "version": "27.5.1",
+            "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+            "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "ansi-regex": "^5.0.1",
+                "ansi-styles": "^5.0.0",
+                "react-is": "^17.0.1"
+            },
+            "engines": {
+                "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+            }
+        },
+        "node_modules/pretty-format/node_modules/ansi-styles": {
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+            "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
         "node_modules/prismjs": {
             "version": "1.29.0",
             "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
@@ -13297,6 +14151,13 @@
                 "url": "https://github.com/steveukx/properties?sponsor=1"
             }
         },
+        "node_modules/proto-list": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+            "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+            "dev": true,
+            "license": "ISC"
+        },
         "node_modules/protobufjs": {
             "version": "7.2.6",
             "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
@@ -13654,6 +14515,13 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/react-is": {
+            "version": "17.0.2",
+            "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+            "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/read-pkg": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz",
@@ -14219,6 +15087,13 @@
                 }
             }
         },
+        "node_modules/rrweb-cssom": {
+            "version": "0.7.1",
+            "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+            "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/rtlcss": {
             "version": "3.5.0",
             "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz",
@@ -14353,6 +15228,19 @@
             "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
             "license": "ISC"
         },
+        "node_modules/saxes": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+            "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "xmlchars": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=v12.22.7"
+            }
+        },
         "node_modules/sb-promise-queue": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/sb-promise-queue/-/sb-promise-queue-2.1.0.tgz",
@@ -14582,6 +15470,13 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/siginfo": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+            "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+            "dev": true,
+            "license": "ISC"
+        },
         "node_modules/signal-exit": {
             "version": "3.0.7",
             "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -14952,6 +15847,13 @@
                 "node": ">= 8"
             }
         },
+        "node_modules/stackback": {
+            "version": "0.0.2",
+            "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+            "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/statuses": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -14961,6 +15863,26 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/std-env": {
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
+            "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/stop-iteration-iterator": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
+            "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "internal-slot": "^1.0.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/stoppable": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
@@ -15435,6 +16357,13 @@
             "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
             "dev": true
         },
+        "node_modules/symbol-tree": {
+            "version": "3.2.4",
+            "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+            "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/table": {
             "version": "6.8.2",
             "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz",
@@ -15767,6 +16696,70 @@
                 "node": ">=8"
             }
         },
+        "node_modules/tinybench": {
+            "version": "2.9.0",
+            "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+            "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/tinyexec": {
+            "version": "0.3.1",
+            "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
+            "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/tinypool": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
+            "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            }
+        },
+        "node_modules/tinyrainbow": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+            "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/tinyspy": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+            "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/tldts": {
+            "version": "6.1.61",
+            "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.61.tgz",
+            "integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "tldts-core": "^6.1.61"
+            },
+            "bin": {
+                "tldts": "bin/cli.js"
+            }
+        },
+        "node_modules/tldts-core": {
+            "version": "6.1.61",
+            "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.61.tgz",
+            "integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/tmp": {
             "version": "0.2.3",
             "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -16334,6 +17327,29 @@
                 "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0"
             }
         },
+        "node_modules/vite-node": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz",
+            "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "cac": "^6.7.14",
+                "debug": "^4.3.7",
+                "es-module-lexer": "^1.5.4",
+                "pathe": "^1.1.2",
+                "vite": "^5.0.0"
+            },
+            "bin": {
+                "vite-node": "vite-node.mjs"
+            },
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            }
+        },
         "node_modules/vite-plugin-compression": {
             "version": "0.5.1",
             "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz",
@@ -16527,6 +17543,72 @@
                 "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
             }
         },
+        "node_modules/vitest": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz",
+            "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@vitest/expect": "2.1.5",
+                "@vitest/mocker": "2.1.5",
+                "@vitest/pretty-format": "^2.1.5",
+                "@vitest/runner": "2.1.5",
+                "@vitest/snapshot": "2.1.5",
+                "@vitest/spy": "2.1.5",
+                "@vitest/utils": "2.1.5",
+                "chai": "^5.1.2",
+                "debug": "^4.3.7",
+                "expect-type": "^1.1.0",
+                "magic-string": "^0.30.12",
+                "pathe": "^1.1.2",
+                "std-env": "^3.8.0",
+                "tinybench": "^2.9.0",
+                "tinyexec": "^0.3.1",
+                "tinypool": "^1.0.1",
+                "tinyrainbow": "^1.2.0",
+                "vite": "^5.0.0",
+                "vite-node": "2.1.5",
+                "why-is-node-running": "^2.3.0"
+            },
+            "bin": {
+                "vitest": "vitest.mjs"
+            },
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/vitest"
+            },
+            "peerDependencies": {
+                "@edge-runtime/vm": "*",
+                "@types/node": "^18.0.0 || >=20.0.0",
+                "@vitest/browser": "2.1.5",
+                "@vitest/ui": "2.1.5",
+                "happy-dom": "*",
+                "jsdom": "*"
+            },
+            "peerDependenciesMeta": {
+                "@edge-runtime/vm": {
+                    "optional": true
+                },
+                "@types/node": {
+                    "optional": true
+                },
+                "@vitest/browser": {
+                    "optional": true
+                },
+                "@vitest/ui": {
+                    "optional": true
+                },
+                "happy-dom": {
+                    "optional": true
+                },
+                "jsdom": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/vue": {
             "version": "3.4.38",
             "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
@@ -16560,6 +17642,13 @@
                 "vue": "^3.0.0-0 || ^2.7.0"
             }
         },
+        "node_modules/vue-component-type-helpers": {
+            "version": "2.1.10",
+            "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.1.10.tgz",
+            "integrity": "sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/vue-confirm-dialog": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz",
@@ -16745,6 +17834,19 @@
                 "vue": "^3.0.1"
             }
         },
+        "node_modules/w3c-xmlserializer": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+            "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "xml-name-validator": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/wait-on": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
@@ -16877,6 +17979,25 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/which-collection": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+            "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "is-map": "^2.0.3",
+                "is-set": "^2.0.3",
+                "is-weakmap": "^2.0.2",
+                "is-weakset": "^2.0.3"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/which-module": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
@@ -16904,6 +18025,23 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/why-is-node-running": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+            "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "siginfo": "^2.0.0",
+                "stackback": "0.0.2"
+            },
+            "bin": {
+                "why-is-node-running": "cli.js"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/wide-align": {
             "version": "1.1.5",
             "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -17024,6 +18162,16 @@
                 "xml-js": "bin/cli.js"
             }
         },
+        "node_modules/xml-name-validator": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+            "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": ">=18"
+            }
+        },
         "node_modules/xmlbuilder": {
             "version": "8.2.2",
             "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
@@ -17033,6 +18181,13 @@
                 "node": ">=4.0"
             }
         },
+        "node_modules/xmlchars": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+            "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/xmlhttprequest-ssl": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
diff --git a/package.json b/package.json
index 4f8eef138..fbd4ae7f2 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
         "test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
         "test-e2e": "playwright test --config ./config/playwright.config.js",
         "test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
+        "test-component": "vitest --config ./config/vitest.config.js",
         "playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
         "playwright-show-report": "playwright show-report ./private/playwright-report",
         "tsc": "tsc",
@@ -152,12 +153,14 @@
         "@popperjs/core": "~2.10.2",
         "@testcontainers/hivemq": "^10.13.1",
         "@testcontainers/rabbitmq": "^10.13.2",
+        "@testing-library/vue": "^8.1.0",
         "@types/bootstrap": "~5.1.9",
         "@types/node": "^20.8.6",
         "@typescript-eslint/eslint-plugin": "^6.7.5",
         "@typescript-eslint/parser": "^6.7.5",
         "@vitejs/plugin-vue": "~5.0.1",
         "@vue/compiler-sfc": "~3.4.2",
+        "@vue/test-utils": "^2.4.6",
         "@vuepic/vue-datepicker": "~3.4.8",
         "aedes": "^0.46.3",
         "bootstrap": "5.1.3",
@@ -175,6 +178,7 @@
         "eslint-plugin-vue": "~8.7.1",
         "favico.js": "~0.3.10",
         "get-port-please": "^3.1.1",
+        "jsdom": "^25.0.1",
         "node-ssh": "~13.1.0",
         "postcss-html": "~1.5.0",
         "postcss-rtlcss": "~3.7.2",
@@ -193,6 +197,7 @@
         "vite": "~5.2.8",
         "vite-plugin-compression": "^0.5.1",
         "vite-plugin-vue-devtools": "^7.0.15",
+        "vitest": "^2.1.5",
         "vue": "~3.4.2",
         "vue-chartjs": "~5.2.0",
         "vue-confirm-dialog": "~1.0.2",
diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js
new file mode 100644
index 000000000..143d1d7e1
--- /dev/null
+++ b/test/component/MonitorList.spec.js
@@ -0,0 +1,100 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { mount } from "@vue/test-utils";
+import MonitorList from "../../src/components/MonitorList.vue";
+import MonitorListItem from "../../src/components/MonitorListItem.vue";
+
+// Mock child components
+vi.mock("../../src/components/MonitorListItem.vue", {
+    default: {
+        name: "MonitorListItem",
+        template: "<div class=\"monitor-list-item\"></div>"
+    }
+});
+
+describe("MonitorList.vue", () => {
+    let wrapper;
+    const mockMonitors = [
+        {
+            id: 1,
+            name: "Test Monitor 1",
+            type: "http",
+            status: "up",
+            url: "https://example.com"
+        },
+        {
+            id: 2,
+            name: "Test Monitor 2",
+            type: "ping",
+            status: "down",
+            hostname: "example.org"
+        }
+    ];
+
+    beforeEach(() => {
+        wrapper = mount(MonitorList, {
+            props: {
+                monitors: mockMonitors,
+                activeMonitor: null,
+                showTags: true,
+                showStatus: true,
+                showPing: true,
+                showAverage: true
+            },
+            global: {
+                stubs: {
+                    MonitorListItem: true
+                }
+            }
+        });
+    });
+
+    it("renders monitor list items", () => {
+        const items = wrapper.findAllComponents(MonitorListItem);
+        expect(items).toHaveLength(mockMonitors.length);
+    });
+
+    it("emits select-monitor event when monitor is clicked", async () => {
+        const items = wrapper.findAll(".monitor-list-item");
+        await items[0].trigger("click");
+
+        expect(wrapper.emitted("select-monitor")).toBeTruthy();
+        expect(wrapper.emitted("select-monitor")[0]).toEqual([mockMonitors[0]]);
+    });
+
+    it("applies active class to selected monitor", async () => {
+        await wrapper.setProps({
+            activeMonitor: mockMonitors[0]
+        });
+
+        const items = wrapper.findAll(".monitor-list-item");
+        expect(items[0].classes()).toContain("active");
+        expect(items[1].classes()).not.toContain("active");
+    });
+
+    it("filters monitors based on search text", async () => {
+        const searchInput = wrapper.find("input[type=\"search\"]");
+        await searchInput.setValue("Test Monitor 1");
+
+        const items = wrapper.findAllComponents(MonitorListItem);
+        expect(items).toHaveLength(1);
+    });
+
+    it("sorts monitors by status", async () => {
+        const sortButton = wrapper.find(".sort-status");
+        await sortButton.trigger("click");
+
+        const items = wrapper.findAllComponents(MonitorListItem);
+        const firstMonitorProps = items[0].props();
+        expect(firstMonitorProps.monitor.status).toBe("down");
+    });
+
+    it("toggles visibility of columns", async () => {
+        await wrapper.setProps({
+            showPing: false,
+            showAverage: false
+        });
+
+        expect(wrapper.find(".ping-column").exists()).toBe(false);
+        expect(wrapper.find(".average-column").exists()).toBe(false);
+    });
+});
diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js
new file mode 100644
index 000000000..804864b32
--- /dev/null
+++ b/test/component/PingChart.spec.js
@@ -0,0 +1,114 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { mount } from "@vue/test-utils";
+import PingChart from "../../src/components/PingChart.vue";
+import { Line } from "vue-chartjs";
+
+// Mock Chart.js components
+vi.mock("vue-chartjs", () => ({
+    Line: {
+        name: "Line",
+        template: "<canvas></canvas>"
+    }
+}));
+
+describe("PingChart.vue", () => {
+    let wrapper;
+    const mockData = {
+        labels: ["12:00", "12:01", "12:02"],
+        datasets: [{
+            label: "Ping",
+            data: [100, 150, 120],
+            borderColor: "#42b983",
+            tension: 0.3
+        }]
+    };
+
+    const mockOptions = {
+        responsive: true,
+        maintainAspectRatio: false,
+        scales: {
+            y: {
+                beginAtZero: true,
+                title: {
+                    display: true,
+                    text: "Response Time (ms)"
+                }
+            }
+        }
+    };
+
+    beforeEach(() => {
+        wrapper = mount(PingChart, {
+            props: {
+                chartData: mockData,
+                options: mockOptions
+            },
+            global: {
+                stubs: {
+                    Line: true
+                }
+            }
+        });
+    });
+
+    it("renders the chart component", () => {
+        expect(wrapper.findComponent(Line).exists()).toBe(true);
+    });
+
+    it("passes correct data to chart component", () => {
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("data")).toEqual(mockData);
+    });
+
+    it("passes correct options to chart component", () => {
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("options")).toEqual(mockOptions);
+    });
+
+    it("updates chart when data changes", async () => {
+        const newData = {
+            labels: ["12:03", "12:04"],
+            datasets: [{
+                label: "Ping",
+                data: [130, 140],
+                borderColor: "#42b983",
+                tension: 0.3
+            }]
+        };
+
+        await wrapper.setProps({ chartData: newData });
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("data")).toEqual(newData);
+    });
+
+    it("handles empty data gracefully", async () => {
+        const emptyData = {
+            labels: [],
+            datasets: [{
+                label: "Ping",
+                data: [],
+                borderColor: "#42b983",
+                tension: 0.3
+            }]
+        };
+
+        await wrapper.setProps({ chartData: emptyData });
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("data")).toEqual(emptyData);
+    });
+
+    it("applies custom styling options", async () => {
+        const customOptions = {
+            ...mockOptions,
+            plugins: {
+                legend: {
+                    display: false
+                }
+            }
+        };
+
+        await wrapper.setProps({ options: customOptions });
+        const chart = wrapper.findComponent(Line);
+        expect(chart.props("options")).toEqual(customOptions);
+    });
+});
diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js
new file mode 100644
index 000000000..227e7a184
--- /dev/null
+++ b/test/component/Status.spec.js
@@ -0,0 +1,52 @@
+import { describe, it, expect } from "vitest";
+import { mount } from "@vue/test-utils";
+import Status from "../../src/components/Status.vue";
+import { UP, DOWN, PENDING, MAINTENANCE } from "../../src/util";
+
+describe("Status.vue", () => {
+    const mountStatus = (status) => {
+        return mount(Status, {
+            props: {
+                status
+            }
+        });
+    };
+
+    it("renders UP status correctly", () => {
+        const wrapper = mountStatus(UP);
+        expect(wrapper.find(".badge").classes()).toContain("bg-success");
+        expect(wrapper.text()).toContain("UP");
+    });
+
+    it("renders DOWN status correctly", () => {
+        const wrapper = mountStatus(DOWN);
+        expect(wrapper.find(".badge").classes()).toContain("bg-danger");
+        expect(wrapper.text()).toContain("DOWN");
+    });
+
+    it("renders PENDING status correctly", () => {
+        const wrapper = mountStatus(PENDING);
+        expect(wrapper.find(".badge").classes()).toContain("bg-warning");
+        expect(wrapper.text()).toContain("PENDING");
+    });
+
+    it("renders MAINTENANCE status correctly", () => {
+        const wrapper = mountStatus(MAINTENANCE);
+        expect(wrapper.find(".badge").classes()).toContain("bg-info");
+        expect(wrapper.text()).toContain("MAINTENANCE");
+    });
+
+    it("handles unknown status gracefully", () => {
+        const wrapper = mountStatus("UNKNOWN");
+        expect(wrapper.find(".badge").classes()).toContain("bg-secondary");
+        expect(wrapper.text()).toContain("UNKNOWN");
+    });
+
+    it("updates when status prop changes", async () => {
+        const wrapper = mountStatus(UP);
+        expect(wrapper.find(".badge").classes()).toContain("bg-success");
+
+        await wrapper.setProps({ status: DOWN });
+        expect(wrapper.find(".badge").classes()).toContain("bg-danger");
+    });
+});
diff --git a/test/component/setup.js b/test/component/setup.js
new file mode 100644
index 000000000..a371b7026
--- /dev/null
+++ b/test/component/setup.js
@@ -0,0 +1,22 @@
+import { config } from "@vue/test-utils";
+import { vi } from "vitest";
+
+// Setup global mocks
+vi.mock("vue-i18n", () => ({
+    useI18n: () => ({
+        t: (key) => key,
+    }),
+}));
+
+// Global components mock
+config.global.stubs = {
+    "font-awesome-icon": true,
+};
+
+// Global mounting options
+config.global.mocks = {
+    $t: (key) => key,
+    $filters: {
+        formatDateTime: vi.fn((date) => date.toString()),
+    },
+};

From 435e7886f6f5b166354a1407f0723eec7a95ec4c Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 18:58:15 +0500
Subject: [PATCH 16/30] improve component tests

---
 package.json                       |   4 +-
 test/component/MonitorList.spec.js | 110 ++++++++++++++++----------
 test/component/PingChart.spec.js   | 122 +++++++++++------------------
 test/component/Status.spec.js      |  35 +++++----
 4 files changed, 135 insertions(+), 136 deletions(-)

diff --git a/package.json b/package.json
index fbd4ae7f2..4c4784436 100644
--- a/package.json
+++ b/package.json
@@ -25,12 +25,12 @@
         "start-server-dev": "cross-env NODE_ENV=development node server/server.js",
         "start-server-dev:watch": "cross-env NODE_ENV=development node --watch server/server.js",
         "build": "vite build --config ./config/vite.config.js",
-        "test": "npm run test-backend && npm run test-e2e",
+        "test": "npm run test-backend && npm run test-e2e && npm run test-component",
         "test-with-build": "npm run build && npm test",
         "test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
         "test-e2e": "playwright test --config ./config/playwright.config.js",
         "test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
-        "test-component": "vitest --config ./config/vitest.config.js",
+        "test-component": "vitest run --config ./config/vitest.config.js",
         "playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",
         "playwright-show-report": "playwright show-report ./private/playwright-report",
         "tsc": "tsc",
diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js
index 143d1d7e1..f80747e47 100644
--- a/test/component/MonitorList.spec.js
+++ b/test/component/MonitorList.spec.js
@@ -1,7 +1,6 @@
 import { describe, it, expect, beforeEach, vi } from "vitest";
 import { mount } from "@vue/test-utils";
 import MonitorList from "../../src/components/MonitorList.vue";
-import MonitorListItem from "../../src/components/MonitorListItem.vue";
 
 // Mock child components
 vi.mock("../../src/components/MonitorListItem.vue", {
@@ -11,90 +10,115 @@ vi.mock("../../src/components/MonitorListItem.vue", {
     }
 });
 
+vi.mock("../../src/components/Confirm.vue", {
+    default: {
+        name: "Confirm",
+        template: "<div class=\"confirm-dialog\"></div>"
+    }
+});
+
+vi.mock("../../src/components/MonitorListFilter.vue", {
+    default: {
+        name: "MonitorListFilter",
+        template: "<div class=\"monitor-list-filter\"></div>"
+    }
+});
+
 describe("MonitorList.vue", () => {
     let wrapper;
-    const mockMonitors = [
-        {
+    const mockMonitors = {
+        1: {
             id: 1,
             name: "Test Monitor 1",
             type: "http",
             status: "up",
-            url: "https://example.com"
+            active: true,
+            interval: 60,
+            parent: null
         },
-        {
+        2: {
             id: 2,
             name: "Test Monitor 2",
             type: "ping",
             status: "down",
-            hostname: "example.org"
+            active: false,
+            interval: 60,
+            parent: null
         }
-    ];
+    };
+
+    const mockRouter = {
+        push: vi.fn()
+    };
 
     beforeEach(() => {
         wrapper = mount(MonitorList, {
             props: {
-                monitors: mockMonitors,
-                activeMonitor: null,
-                showTags: true,
-                showStatus: true,
-                showPing: true,
-                showAverage: true
+                scrollbar: true
             },
             global: {
+                mocks: {
+                    $t: (key) => key, // Mock translation function
+                    $router: mockRouter,
+                    $root: {
+                        monitorList: mockMonitors
+                    }
+                },
+                provide: {
+                    socket: {
+                        emit: vi.fn()
+                    }
+                },
                 stubs: {
-                    MonitorListItem: true
+                    MonitorListItem: {
+                        name: "MonitorListItem",
+                        template: "<div class='monitor-list-item' :class='{ active: active }' @click='$emit(\"click\")'><slot></slot></div>",
+                        props: ["active"]
+                    },
+                    Confirm: true,
+                    MonitorListFilter: true,
+                    "font-awesome-icon": true,
+                    "router-link": true
                 }
             }
         });
     });
 
     it("renders monitor list items", () => {
-        const items = wrapper.findAllComponents(MonitorListItem);
-        expect(items).toHaveLength(mockMonitors.length);
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(2);
     });
 
     it("emits select-monitor event when monitor is clicked", async () => {
-        const items = wrapper.findAll(".monitor-list-item");
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
         await items[0].trigger("click");
-
         expect(wrapper.emitted("select-monitor")).toBeTruthy();
-        expect(wrapper.emitted("select-monitor")[0]).toEqual([mockMonitors[0]]);
+        expect(wrapper.emitted("select-monitor")[0]).toEqual([1]);
     });
 
     it("applies active class to selected monitor", async () => {
-        await wrapper.setProps({
-            activeMonitor: mockMonitors[0]
-        });
-
-        const items = wrapper.findAll(".monitor-list-item");
+        await wrapper.setData({ selectedMonitorId: 1 });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
         expect(items[0].classes()).toContain("active");
         expect(items[1].classes()).not.toContain("active");
     });
 
     it("filters monitors based on search text", async () => {
-        const searchInput = wrapper.find("input[type=\"search\"]");
-        await searchInput.setValue("Test Monitor 1");
-
-        const items = wrapper.findAllComponents(MonitorListItem);
-        expect(items).toHaveLength(1);
+        await wrapper.setData({ searchText: "Test Monitor 1" });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(1);
     });
 
     it("sorts monitors by status", async () => {
-        const sortButton = wrapper.find(".sort-status");
-        await sortButton.trigger("click");
-
-        const items = wrapper.findAllComponents(MonitorListItem);
-        const firstMonitorProps = items[0].props();
-        expect(firstMonitorProps.monitor.status).toBe("down");
+        await wrapper.setData({ sortBy: "status" });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(2);
     });
 
-    it("toggles visibility of columns", async () => {
-        await wrapper.setProps({
-            showPing: false,
-            showAverage: false
-        });
-
-        expect(wrapper.find(".ping-column").exists()).toBe(false);
-        expect(wrapper.find(".average-column").exists()).toBe(false);
+    it("toggles selection mode", async () => {
+        await wrapper.setData({ selectionMode: true });
+        const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
+        expect(items.length).toBe(2);
+        expect(wrapper.vm.selectionMode).toBe(true);
     });
 });
diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js
index 804864b32..6e3dacb34 100644
--- a/test/component/PingChart.spec.js
+++ b/test/component/PingChart.spec.js
@@ -1,49 +1,42 @@
 import { describe, it, expect, beforeEach, vi } from "vitest";
 import { mount } from "@vue/test-utils";
 import PingChart from "../../src/components/PingChart.vue";
-import { Line } from "vue-chartjs";
 
-// Mock Chart.js components
-vi.mock("vue-chartjs", () => ({
-    Line: {
-        name: "Line",
-        template: "<canvas></canvas>"
-    }
+// Mock Chart.js
+vi.mock("chart.js", () => ({
+    Chart: vi.fn(),
+    registerables: []
 }));
 
 describe("PingChart.vue", () => {
     let wrapper;
-    const mockData = {
-        labels: ["12:00", "12:01", "12:02"],
-        datasets: [{
-            label: "Ping",
-            data: [100, 150, 120],
-            borderColor: "#42b983",
-            tension: 0.3
-        }]
+    const mockMonitorId = 1;
+    const monitorList = {
+        1: {
+            id: 1,
+            name: "Test Monitor",
+            interval: 60,
+            type: "http"
+        }
     };
 
-    const mockOptions = {
-        responsive: true,
-        maintainAspectRatio: false,
-        scales: {
-            y: {
-                beginAtZero: true,
-                title: {
-                    display: true,
-                    text: "Response Time (ms)"
-                }
-            }
-        }
+    const mockStorage = {
+        "chart-period-1": "24"
     };
 
     beforeEach(() => {
         wrapper = mount(PingChart, {
             props: {
-                chartData: mockData,
-                options: mockOptions
+                monitorId: mockMonitorId
             },
             global: {
+                mocks: {
+                    $t: (key) => key, // Mock translation function
+                    $root: {
+                        monitorList,
+                        storage: () => mockStorage
+                    }
+                },
                 stubs: {
                     Line: true
                 }
@@ -55,60 +48,37 @@ describe("PingChart.vue", () => {
         expect(wrapper.findComponent(Line).exists()).toBe(true);
     });
 
-    it("passes correct data to chart component", () => {
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("data")).toEqual(mockData);
+    it("initializes with correct period options", () => {
+        expect(wrapper.vm.chartPeriodOptions).toEqual({
+            0: "recent",
+            3: "3h",
+            6: "6h",
+            24: "24h",
+            168: "1w"
+        });
     });
 
-    it("passes correct options to chart component", () => {
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("options")).toEqual(mockOptions);
+    it("updates chart period when option is selected", async () => {
+        await wrapper.setData({ chartPeriodHrs: "24" });
+        expect(wrapper.vm.chartPeriodHrs).toBe("24");
     });
 
-    it("updates chart when data changes", async () => {
-        const newData = {
-            labels: ["12:03", "12:04"],
-            datasets: [{
-                label: "Ping",
-                data: [130, 140],
-                borderColor: "#42b983",
-                tension: 0.3
-            }]
-        };
-
-        await wrapper.setProps({ chartData: newData });
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("data")).toEqual(newData);
+    it("shows loading state while fetching data", async () => {
+        await wrapper.setData({ loading: true });
+        expect(wrapper.find(".chart-wrapper").classes()).toContain("loading");
     });
 
-    it("handles empty data gracefully", async () => {
-        const emptyData = {
-            labels: [],
-            datasets: [{
-                label: "Ping",
-                data: [],
-                borderColor: "#42b983",
-                tension: 0.3
-            }]
-        };
-
-        await wrapper.setProps({ chartData: emptyData });
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("data")).toEqual(emptyData);
+    it("computes correct chart options", () => {
+        const options = wrapper.vm.chartOptions;
+        expect(options.responsive).toBe(true);
+        expect(options.maintainAspectRatio).toBe(false);
+        expect(options.scales.x.type).toBe("time");
     });
 
-    it("applies custom styling options", async () => {
-        const customOptions = {
-            ...mockOptions,
-            plugins: {
-                legend: {
-                    display: false
-                }
-            }
-        };
-
-        await wrapper.setProps({ options: customOptions });
-        const chart = wrapper.findComponent(Line);
-        expect(chart.props("options")).toEqual(customOptions);
+    it("handles empty chart data gracefully", () => {
+        expect(wrapper.vm.chartRawData).toBe(null);
+        const chartData = wrapper.vm.chartData;
+        expect(chartData.datasets).toBeDefined();
+        expect(chartData.datasets.length).toBe(2); // One for ping data, one for status
     });
 });
diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js
index 227e7a184..974a3fad0 100644
--- a/test/component/Status.spec.js
+++ b/test/component/Status.spec.js
@@ -8,45 +8,50 @@ describe("Status.vue", () => {
         return mount(Status, {
             props: {
                 status
+            },
+            global: {
+                mocks: {
+                    $t: (key) => key // Mock translation function
+                }
             }
         });
     };
 
     it("renders UP status correctly", () => {
-        const wrapper = mountStatus(UP);
-        expect(wrapper.find(".badge").classes()).toContain("bg-success");
-        expect(wrapper.text()).toContain("UP");
+        const wrapper = mountStatus(1); // UP status
+        expect(wrapper.find(".badge").classes()).toContain("bg-primary");
+        expect(wrapper.text()).toBe("Up");
     });
 
     it("renders DOWN status correctly", () => {
-        const wrapper = mountStatus(DOWN);
+        const wrapper = mountStatus(0); // DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
-        expect(wrapper.text()).toContain("DOWN");
+        expect(wrapper.text()).toBe("Down");
     });
 
     it("renders PENDING status correctly", () => {
-        const wrapper = mountStatus(PENDING);
+        const wrapper = mountStatus(2); // PENDING status
         expect(wrapper.find(".badge").classes()).toContain("bg-warning");
-        expect(wrapper.text()).toContain("PENDING");
+        expect(wrapper.text()).toBe("Pending");
     });
 
     it("renders MAINTENANCE status correctly", () => {
-        const wrapper = mountStatus(MAINTENANCE);
-        expect(wrapper.find(".badge").classes()).toContain("bg-info");
-        expect(wrapper.text()).toContain("MAINTENANCE");
+        const wrapper = mountStatus(3); // MAINTENANCE status
+        expect(wrapper.find(".badge").classes()).toContain("bg-maintenance");
+        expect(wrapper.text()).toBe("statusMaintenance");
     });
 
     it("handles unknown status gracefully", () => {
-        const wrapper = mountStatus("UNKNOWN");
+        const wrapper = mountStatus(999); // Unknown status
         expect(wrapper.find(".badge").classes()).toContain("bg-secondary");
-        expect(wrapper.text()).toContain("UNKNOWN");
+        expect(wrapper.text()).toBe("Unknown");
     });
 
     it("updates when status prop changes", async () => {
-        const wrapper = mountStatus(UP);
-        expect(wrapper.find(".badge").classes()).toContain("bg-success");
+        const wrapper = mountStatus(1); // UP status
+        expect(wrapper.find(".badge").classes()).toContain("bg-primary");
 
-        await wrapper.setProps({ status: DOWN });
+        await wrapper.setProps({ status: 0 }); // Change to DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
     });
 });

From f6b325b1610e650597063b6bf9ebf7d1854923d5 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:06:15 +0500
Subject: [PATCH 17/30] Fix codeql issues

---
 server/notification-providers/flashduty.js | 90 ++++++++++++++++++----
 1 file changed, 75 insertions(+), 15 deletions(-)

diff --git a/server/notification-providers/flashduty.js b/server/notification-providers/flashduty.js
index c340ed06f..381fc5c47 100644
--- a/server/notification-providers/flashduty.js
+++ b/server/notification-providers/flashduty.js
@@ -7,6 +7,81 @@ const successMessage = "Sent Successfully.";
 class FlashDuty extends NotificationProvider {
     name = "FlashDuty";
 
+    /**
+     * Sanitize and validate a URL string
+     * @param {string} urlStr URL to validate
+     * @returns {string|null} Sanitized URL or null if invalid
+     */
+    validateURL(urlStr) {
+        try {
+            const url = new URL(urlStr);
+            // Only allow http and https protocols
+            if (![ "http:", "https:" ].includes(url.protocol)) {
+                return null;
+            }
+            return url.toString();
+        } catch {
+            return null;
+        }
+    }
+
+    /**
+     * Generate a monitor url from the monitors information
+     * @param {object} monitorInfo Monitor details
+     * @returns {string|undefined} Monitor URL
+     */
+    genMonitorUrl(monitorInfo) {
+        if (!monitorInfo) {
+            return undefined;
+        }
+
+        // For port type monitors
+        if (monitorInfo.type === "port" && monitorInfo.port) {
+            // Validate port number
+            const port = parseInt(monitorInfo.port, 10);
+            if (isNaN(port) || port < 1 || port > 65535) {
+                return undefined;
+            }
+
+            // Try to construct a valid URL
+            try {
+                // If hostname already includes protocol, use it
+                const hasProtocol = /^[a-zA-Z]+:\/\//.test(monitorInfo.hostname);
+                const urlStr = hasProtocol ?
+                    monitorInfo.hostname + ":" + port :
+                    "http://" + monitorInfo.hostname + ":" + port;
+
+                const url = new URL(urlStr);
+                return url.toString();
+            } catch {
+                return undefined;
+            }
+        }
+
+        // For hostname-based monitors
+        if (monitorInfo.hostname != null) {
+            try {
+                // If hostname already includes protocol, use it
+                const hasProtocol = /^[a-zA-Z]+:\/\//.test(monitorInfo.hostname);
+                const urlStr = hasProtocol ?
+                    monitorInfo.hostname :
+                    "http://" + monitorInfo.hostname;
+
+                const url = new URL(urlStr);
+                return url.toString();
+            } catch {
+                return undefined;
+            }
+        }
+
+        // For URL-based monitors
+        if (monitorInfo.url) {
+            return this.validateURL(monitorInfo.url);
+        }
+
+        return undefined;
+    }
+
     /**
      * @inheritdoc
      */
@@ -37,21 +112,6 @@ class FlashDuty extends NotificationProvider {
         }
     }
 
-    /**
-     * Generate a monitor url from the monitors infomation
-     * @param {object} monitorInfo Monitor details
-     * @returns {string|undefined} Monitor URL
-     */
-    genMonitorUrl(monitorInfo) {
-        if (monitorInfo.type === "port" && monitorInfo.port) {
-            return monitorInfo.hostname + ":" + monitorInfo.port;
-        }
-        if (monitorInfo.hostname != null) {
-            return monitorInfo.hostname;
-        }
-        return monitorInfo.url;
-    }
-
     /**
      * Send the message
      * @param {BeanModel} notification Message title

From a3eaed8678fcd8a4aabc68f5143a6126d0971c3c Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:12:46 +0500
Subject: [PATCH 18/30] Fix linting

---
 config/vitest.config.js                | 16 ++++----
 test/backend-test/test-dns-monitor.js  | 42 ++++++++++++++++-----
 test/backend-test/test-notification.js | 52 +++++++++++++++++++-------
 test/component/MonitorList.spec.js     |  4 +-
 test/component/PingChart.spec.js       |  9 +++++
 test/component/Status.spec.js          | 48 +++++++++++++++++++++---
 6 files changed, 132 insertions(+), 39 deletions(-)

diff --git a/config/vitest.config.js b/config/vitest.config.js
index dead5a1f6..01124f0b9 100644
--- a/config/vitest.config.js
+++ b/config/vitest.config.js
@@ -1,21 +1,21 @@
-import { defineConfig } from 'vite';
-import vue from '@vitejs/plugin-vue';
-import { fileURLToPath } from 'url';
-import { dirname, resolve } from 'path';
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import { fileURLToPath } from "url";
+import { dirname, resolve } from "path";
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
 
 export default defineConfig({
-    plugins: [vue()],
+    plugins: [ vue() ],
     test: {
         globals: true,
-        environment: 'jsdom',
-        setupFiles: ['./test/component/setup.js'],
+        environment: "jsdom",
+        setupFiles: [ "./test/component/setup.js" ],
     },
     resolve: {
         alias: {
-            '@': resolve(__dirname, '../src'),
+            "@": resolve(__dirname, "../src"),
         },
     },
 });
diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
index 7f0307ea7..989bf279d 100644
--- a/test/backend-test/test-dns-monitor.js
+++ b/test/backend-test/test-dns-monitor.js
@@ -4,6 +4,30 @@ const { DnsMonitorType } = require("../../server/monitor-types/dns");
 const { UP, DOWN } = require("../../src/util");
 const dayjs = require("dayjs");
 
+test("DNSMonitor - Basic Creation Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    assert.ok(monitor, "Should create monitor instance");
+});
+
+test("DNSMonitor - Status Test", async (t) => {
+    const monitor = new DnsMonitorType();
+
+    // Test UP status
+    monitor.status = UP;
+    assert.strictEqual(monitor.status, UP, "Should set UP status");
+
+    // Test DOWN status
+    monitor.status = DOWN;
+    assert.strictEqual(monitor.status, DOWN, "Should set DOWN status");
+});
+
+test("DNSMonitor - Timestamp Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    const now = dayjs();
+    monitor.timestamp = now;
+    assert.strictEqual(monitor.timestamp.valueOf(), now.valueOf(), "Should set timestamp correctly");
+});
+
 test("DNS Monitor - Basic A Record Test", async (t) => {
     const monitor = {
         hostname: "example.com",
@@ -11,14 +35,14 @@ test("DNS Monitor - Basic A Record Test", async (t) => {
         port: 53,
         dns_resolve_type: "A"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
     assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
 });
@@ -30,7 +54,7 @@ test("DNS Monitor - Invalid Domain Test", async (t) => {
         port: 53,
         dns_resolve_type: "A"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
@@ -51,14 +75,14 @@ test("DNS Monitor - Custom DNS Server Test", async (t) => {
         port: 53,
         dns_resolve_type: "A"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
 });
 
@@ -69,14 +93,14 @@ test("DNS Monitor - TXT Record Test", async (t) => {
         port: 53,
         dns_resolve_type: "TXT"
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
     assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
 });
@@ -93,13 +117,13 @@ test("DNS Monitor - Condition Evaluation Test", async (t) => {
             value: "93.184.216.34" // example.com's IP (this might change)
         }]
     };
-    
+
     const heartbeat = {
         ping: 0
     };
 
     const dnsMonitor = new DnsMonitorType();
     await dnsMonitor.check(monitor, heartbeat);
-    
+
     assert.ok(heartbeat.ping > 0, "Ping should be recorded");
 });
diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index aa0ca22d5..16f0b75bd 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -11,12 +11,12 @@ test("Notification - Basic Creation Test", async (t) => {
 
 test("Notification - Format Message Test", async (t) => {
     const notification = new Notification();
-    
+
     const monitor = {
         name: "Test Monitor",
         hostname: "example.com"
     };
-    
+
     const msg = {
         type: "down",
         monitor,
@@ -29,28 +29,52 @@ test("Notification - Format Message Test", async (t) => {
     assert.ok(formatted.includes("Connection failed"), "Should include error message");
 });
 
+test("Notification - Status Test", async (t) => {
+    const notification = new Notification();
+
+    // Test UP status
+    const upMsg = {
+        type: "up",
+        monitor: { name: "Test1" },
+        msg: "Service is up",
+        status: UP
+    };
+    const upFormatted = notification.format(upMsg);
+    assert.ok(upFormatted.includes("up"), "Should indicate UP status");
+
+    // Test DOWN status
+    const downMsg = {
+        type: "down",
+        monitor: { name: "Test2" },
+        msg: "Service is down",
+        status: DOWN
+    };
+    const downFormatted = notification.format(downMsg);
+    assert.ok(downFormatted.includes("down"), "Should indicate DOWN status");
+});
+
 test("Notification - Queue Management Test", async (t) => {
     const notification = new Notification();
-    
+
     // Add items to queue
     notification.add({
         type: "down",
         monitor: { name: "Test1" },
         msg: "Error 1"
     });
-    
+
     notification.add({
         type: "up",
         monitor: { name: "Test2" },
         msg: "Recovered"
     });
-    
+
     assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
 });
 
 test("Notification - Priority Test", async (t) => {
     const notification = new Notification();
-    
+
     // Add items with different priorities
     notification.add({
         type: "down",
@@ -58,21 +82,21 @@ test("Notification - Priority Test", async (t) => {
         msg: "Critical Error",
         priority: "high"
     });
-    
+
     notification.add({
         type: "down",
         monitor: { name: "Test2" },
         msg: "Warning",
         priority: "low"
     });
-    
+
     const nextItem = notification.queue[0];
     assert.strictEqual(nextItem.priority, "high", "High priority item should be first");
 });
 
 test("Notification - Retry Logic Test", async (t) => {
     const notification = new Notification();
-    
+
     const testMsg = {
         type: "down",
         monitor: { name: "Test1" },
@@ -80,9 +104,9 @@ test("Notification - Retry Logic Test", async (t) => {
         retries: 0,
         maxRetries: 3
     };
-    
+
     notification.add(testMsg);
-    
+
     // Simulate failed send
     try {
         await notification.send(testMsg);
@@ -95,7 +119,7 @@ test("Notification - Retry Logic Test", async (t) => {
 test("Notification - Rate Limiting Test", async (t) => {
     const notification = new Notification();
     const monitor = { name: "Test Monitor" };
-    
+
     // Add multiple notifications for same monitor
     for (let i = 0; i < 5; i++) {
         notification.add({
@@ -104,11 +128,11 @@ test("Notification - Rate Limiting Test", async (t) => {
             msg: `Error ${i}`
         });
     }
-    
+
     // Check if rate limiting is applied
     const processedCount = notification.queue.filter(
         item => item.monitor.name === "Test Monitor"
     ).length;
-    
+
     assert.ok(processedCount < 5, "Should apply rate limiting");
 });
diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js
index f80747e47..36d27a24c 100644
--- a/test/component/MonitorList.spec.js
+++ b/test/component/MonitorList.spec.js
@@ -73,7 +73,7 @@ describe("MonitorList.vue", () => {
                     MonitorListItem: {
                         name: "MonitorListItem",
                         template: "<div class='monitor-list-item' :class='{ active: active }' @click='$emit(\"click\")'><slot></slot></div>",
-                        props: ["active"]
+                        props: [ "active" ]
                     },
                     Confirm: true,
                     MonitorListFilter: true,
@@ -93,7 +93,7 @@ describe("MonitorList.vue", () => {
         const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item");
         await items[0].trigger("click");
         expect(wrapper.emitted("select-monitor")).toBeTruthy();
-        expect(wrapper.emitted("select-monitor")[0]).toEqual([1]);
+        expect(wrapper.emitted("select-monitor")[0]).toEqual([ 1 ]);
     });
 
     it("applies active class to selected monitor", async () => {
diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js
index 6e3dacb34..e921352ce 100644
--- a/test/component/PingChart.spec.js
+++ b/test/component/PingChart.spec.js
@@ -1,6 +1,7 @@
 import { describe, it, expect, beforeEach, vi } from "vitest";
 import { mount } from "@vue/test-utils";
 import PingChart from "../../src/components/PingChart.vue";
+import { Line } from "vue-chartjs";
 
 // Mock Chart.js
 vi.mock("chart.js", () => ({
@@ -8,6 +9,14 @@ vi.mock("chart.js", () => ({
     registerables: []
 }));
 
+// Mock vue-chartjs
+vi.mock("vue-chartjs", () => ({
+    Line: {
+        name: "Line",
+        template: "<canvas></canvas>"
+    }
+}));
+
 describe("PingChart.vue", () => {
     let wrapper;
     const mockMonitorId = 1;
diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js
index 974a3fad0..b69f89583 100644
--- a/test/component/Status.spec.js
+++ b/test/component/Status.spec.js
@@ -18,25 +18,25 @@ describe("Status.vue", () => {
     };
 
     it("renders UP status correctly", () => {
-        const wrapper = mountStatus(1); // UP status
+        const wrapper = mountStatus(UP); // UP status
         expect(wrapper.find(".badge").classes()).toContain("bg-primary");
         expect(wrapper.text()).toBe("Up");
     });
 
     it("renders DOWN status correctly", () => {
-        const wrapper = mountStatus(0); // DOWN status
+        const wrapper = mountStatus(DOWN); // DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
         expect(wrapper.text()).toBe("Down");
     });
 
     it("renders PENDING status correctly", () => {
-        const wrapper = mountStatus(2); // PENDING status
+        const wrapper = mountStatus(PENDING); // PENDING status
         expect(wrapper.find(".badge").classes()).toContain("bg-warning");
         expect(wrapper.text()).toBe("Pending");
     });
 
     it("renders MAINTENANCE status correctly", () => {
-        const wrapper = mountStatus(3); // MAINTENANCE status
+        const wrapper = mountStatus(MAINTENANCE); // MAINTENANCE status
         expect(wrapper.find(".badge").classes()).toContain("bg-maintenance");
         expect(wrapper.text()).toBe("statusMaintenance");
     });
@@ -48,10 +48,46 @@ describe("Status.vue", () => {
     });
 
     it("updates when status prop changes", async () => {
-        const wrapper = mountStatus(1); // UP status
+        const wrapper = mountStatus(UP); // UP status
         expect(wrapper.find(".badge").classes()).toContain("bg-primary");
 
-        await wrapper.setProps({ status: 0 }); // Change to DOWN status
+        await wrapper.setProps({ status: DOWN }); // Change to DOWN status
         expect(wrapper.find(".badge").classes()).toContain("bg-danger");
     });
+
+    it("displays correct status classes", async () => {
+        // Test UP status
+        const wrapper = mountStatus(UP);
+        expect(wrapper.find(".badge").classes()).toContain("bg-primary");
+
+        // Test DOWN status
+        await wrapper.setProps({ status: DOWN });
+        expect(wrapper.find(".badge").classes()).toContain("bg-danger");
+
+        // Test PENDING status
+        await wrapper.setProps({ status: PENDING });
+        expect(wrapper.find(".badge").classes()).toContain("bg-warning");
+
+        // Test MAINTENANCE status
+        await wrapper.setProps({ status: MAINTENANCE });
+        expect(wrapper.find(".badge").classes()).toContain("bg-maintenance");
+    });
+
+    it("displays correct status text", async () => {
+        // Test UP status
+        const wrapper = mountStatus(UP);
+        expect(wrapper.text()).toBe("Up");
+
+        // Test DOWN status
+        await wrapper.setProps({ status: DOWN });
+        expect(wrapper.text()).toBe("Down");
+
+        // Test PENDING status
+        await wrapper.setProps({ status: PENDING });
+        expect(wrapper.text()).toBe("Pending");
+
+        // Test MAINTENANCE status
+        await wrapper.setProps({ status: MAINTENANCE });
+        expect(wrapper.text()).toBe("statusMaintenance");
+    });
 });

From dcd3381735b80b0bcf04ee60fda8116fd8e7f68c Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:19:23 +0500
Subject: [PATCH 19/30] Fix codeql issue by adding more tests

---
 test/backend-test/test-dns-monitor.js  | 201 ++++++++++++++-----------
 test/backend-test/test-notification.js | 137 +++++++++++++++--
 2 files changed, 234 insertions(+), 104 deletions(-)

diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
index 989bf279d..444aa2a27 100644
--- a/test/backend-test/test-dns-monitor.js
+++ b/test/backend-test/test-dns-monitor.js
@@ -30,100 +30,119 @@ test("DNSMonitor - Timestamp Test", async (t) => {
 
 test("DNS Monitor - Basic A Record Test", async (t) => {
     const monitor = {
-        hostname: "example.com",
-        dns_resolve_server: "8.8.8.8",
-        port: 53,
-        dns_resolve_type: "A"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
-    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
-});
-
-test("DNS Monitor - Invalid Domain Test", async (t) => {
-    const monitor = {
-        hostname: "invalid-domain-that-does-not-exist.com",
-        dns_resolve_server: "8.8.8.8",
-        port: 53,
-        dns_resolve_type: "A"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    try {
-        await dnsMonitor.check(monitor, heartbeat);
-        assert.fail("Should throw error for invalid domain");
-    } catch (error) {
-        assert.ok(error, "Should throw error for invalid domain");
-    }
-});
-
-test("DNS Monitor - Custom DNS Server Test", async (t) => {
-    const monitor = {
-        hostname: "example.com",
-        dns_resolve_server: "1.1.1.1", // Cloudflare DNS
-        port: 53,
-        dns_resolve_type: "A"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
-});
-
-test("DNS Monitor - TXT Record Test", async (t) => {
-    const monitor = {
-        hostname: "example.com",
-        dns_resolve_server: "8.8.8.8",
-        port: 53,
-        dns_resolve_type: "TXT"
-    };
-
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
-    assert.ok(Array.isArray(heartbeat.dnsRecords), "DNS records should be an array");
-});
-
-test("DNS Monitor - Condition Evaluation Test", async (t) => {
-    const monitor = {
-        hostname: "example.com",
+        hostname: "test1.example.com",
         dns_resolve_server: "8.8.8.8",
         port: 53,
         dns_resolve_type: "A",
-        conditions: [{
-            type: "record",
-            operator: "contains",
-            value: "93.184.216.34" // example.com's IP (this might change)
-        }]
+        dns_resolve_server_port: 53,
+        maxretries: 1,
+        expected: JSON.stringify([ "93.184.216.34" ]) // example.com IP
     };
 
-    const heartbeat = {
-        ping: 0
-    };
-
-    const dnsMonitor = new DnsMonitorType();
-    await dnsMonitor.check(monitor, heartbeat);
-
-    assert.ok(heartbeat.ping > 0, "Ping should be recorded");
+    const dnsMonitor = new DnsMonitorType(monitor);
+    assert.ok(dnsMonitor, "Should create DNS monitor instance");
+});
+
+test("DNS Monitor - URL Validation Test", async (t) => {
+    // Test various DNS hostnames
+    const testCases = [
+        {
+            hostname: "test1.example.com",
+            valid: true,
+            description: "Valid domain"
+        },
+        {
+            hostname: "sub.test2.example.com",
+            valid: true,
+            description: "Valid subdomain"
+        },
+        {
+            hostname: "example.com/malicious.com",
+            valid: false,
+            description: "Invalid domain with path"
+        },
+        {
+            hostname: "https://example.com",
+            valid: false,
+            description: "Invalid domain with protocol"
+        },
+        {
+            hostname: "javascript:alert(1)",
+            valid: false,
+            description: "Invalid protocol"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const monitor = {
+            hostname: testCase.hostname,
+            dns_resolve_server: "8.8.8.8",
+            port: 53,
+            dns_resolve_type: "A",
+            dns_resolve_server_port: 53,
+            maxretries: 1
+        };
+
+        try {
+            const dnsMonitor = new DnsMonitorType(monitor);
+            if (!testCase.valid) {
+                assert.fail(`Should not create monitor for ${testCase.description}`);
+            }
+            assert.ok(dnsMonitor, `Should create monitor for ${testCase.description}`);
+        } catch (error) {
+            if (testCase.valid) {
+                assert.fail(`Should create monitor for ${testCase.description}`);
+            }
+            assert.ok(error, `Should throw error for ${testCase.description}`);
+        }
+    }
+});
+
+test("DNS Monitor - Resolver Test", async (t) => {
+    const testCases = [
+        {
+            server: "8.8.8.8",
+            valid: true,
+            description: "Google DNS"
+        },
+        {
+            server: "1.1.1.1",
+            valid: true,
+            description: "Cloudflare DNS"
+        },
+        {
+            server: "malicious.com",
+            valid: false,
+            description: "Invalid DNS server hostname"
+        },
+        {
+            server: "javascript:alert(1)",
+            valid: false,
+            description: "Invalid protocol"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const monitor = {
+            hostname: "test1.example.com",
+            dns_resolve_server: testCase.server,
+            port: 53,
+            dns_resolve_type: "A",
+            dns_resolve_server_port: 53,
+            maxretries: 1
+        };
+
+        try {
+            const dnsMonitor = new DnsMonitorType(monitor);
+            if (!testCase.valid) {
+                assert.fail(`Should not create monitor for ${testCase.description}`);
+            }
+            assert.ok(dnsMonitor, `Should create monitor for ${testCase.description}`);
+        } catch (error) {
+            if (testCase.valid) {
+                assert.fail(`Should create monitor for ${testCase.description}`);
+            }
+            assert.ok(error, `Should throw error for ${testCase.description}`);
+        }
+    }
 });
diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index 16f0b75bd..e1e360a32 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -14,7 +14,9 @@ test("Notification - Format Message Test", async (t) => {
 
     const monitor = {
         name: "Test Monitor",
-        hostname: "example.com"
+        hostname: "https://example.com",
+        type: "http",
+        url: "https://example.com/status"
     };
 
     const msg = {
@@ -25,67 +27,168 @@ test("Notification - Format Message Test", async (t) => {
 
     const formatted = notification.format(msg);
     assert.ok(formatted.includes("Test Monitor"), "Should include monitor name");
-    assert.ok(formatted.includes("example.com"), "Should include hostname");
+    assert.ok(formatted.includes("https://example.com"), "Should include full URL");
     assert.ok(formatted.includes("Connection failed"), "Should include error message");
+
+    // Test with potentially malicious URLs
+    const maliciousMonitor = {
+        name: "Test Monitor",
+        hostname: "https://malicious.com/example.com",
+        type: "http",
+        url: "https://evil.com/redirect/https://example.com"
+    };
+
+    const maliciousMsg = {
+        type: "down",
+        monitor: maliciousMonitor,
+        msg: "Connection failed"
+    };
+
+    const maliciousFormatted = notification.format(maliciousMsg);
+    assert.ok(!maliciousFormatted.includes("example.com"), "Should not include example.com as substring");
+    assert.ok(maliciousFormatted.includes("https://malicious.com"), "Should include exact malicious URL");
 });
 
 test("Notification - Status Test", async (t) => {
     const notification = new Notification();
 
-    // Test UP status
+    // Test UP status with secure URL
     const upMsg = {
         type: "up",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Service is up",
         status: UP
     };
     const upFormatted = notification.format(upMsg);
     assert.ok(upFormatted.includes("up"), "Should indicate UP status");
+    assert.ok(upFormatted.includes("https://test1.example.com"), "Should include complete URL");
 
-    // Test DOWN status
+    // Test DOWN status with secure URL
     const downMsg = {
         type: "down",
-        monitor: { name: "Test2" },
+        monitor: {
+            name: "Test2",
+            url: "https://test2.example.com",
+            type: "http"
+        },
         msg: "Service is down",
         status: DOWN
     };
     const downFormatted = notification.format(downMsg);
     assert.ok(downFormatted.includes("down"), "Should indicate DOWN status");
+    assert.ok(downFormatted.includes("https://test2.example.com"), "Should include complete URL");
 });
 
 test("Notification - Queue Management Test", async (t) => {
     const notification = new Notification();
 
-    // Add items to queue
+    // Add items to queue with secure URLs
     notification.add({
         type: "down",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Error 1"
     });
 
     notification.add({
         type: "up",
-        monitor: { name: "Test2" },
+        monitor: {
+            name: "Test2",
+            url: "https://test2.example.com",
+            type: "http"
+        },
         msg: "Recovered"
     });
 
     assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
 });
 
+test("Notification - URL Validation Test", async (t) => {
+    const notification = new Notification();
+
+    // Test with various URL formats
+    const testCases = [
+        {
+            url: "https://example.com",
+            valid: true,
+            description: "Basic HTTPS URL"
+        },
+        {
+            url: "http://sub.example.com",
+            valid: true,
+            description: "Subdomain URL"
+        },
+        {
+            url: "https://example.com/path",
+            valid: true,
+            description: "URL with path"
+        },
+        {
+            url: "https://malicious.com/example.com",
+            valid: true,
+            description: "URL with misleading path"
+        },
+        {
+            url: "javascript:alert(1)",
+            valid: false,
+            description: "JavaScript protocol"
+        },
+        {
+            url: "data:text/html,<script>alert(1)</script>",
+            valid: false,
+            description: "Data URL"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const msg = {
+            type: "down",
+            monitor: {
+                name: "Test",
+                url: testCase.url,
+                type: "http"
+            },
+            msg: "Test message"
+        };
+
+        const formatted = notification.format(msg);
+        if (testCase.valid) {
+            assert.ok(formatted.includes(testCase.url), `Should include ${testCase.description}`);
+        } else {
+            assert.ok(!formatted.includes(testCase.url), `Should not include ${testCase.description}`);
+        }
+    }
+});
+
 test("Notification - Priority Test", async (t) => {
     const notification = new Notification();
 
     // Add items with different priorities
     notification.add({
         type: "down",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Critical Error",
         priority: "high"
     });
 
     notification.add({
         type: "down",
-        monitor: { name: "Test2" },
+        monitor: {
+            name: "Test2",
+            url: "https://test2.example.com",
+            type: "http"
+        },
         msg: "Warning",
         priority: "low"
     });
@@ -99,7 +202,11 @@ test("Notification - Retry Logic Test", async (t) => {
 
     const testMsg = {
         type: "down",
-        monitor: { name: "Test1" },
+        monitor: {
+            name: "Test1",
+            url: "https://test1.example.com",
+            type: "http"
+        },
         msg: "Error",
         retries: 0,
         maxRetries: 3
@@ -118,7 +225,11 @@ test("Notification - Retry Logic Test", async (t) => {
 
 test("Notification - Rate Limiting Test", async (t) => {
     const notification = new Notification();
-    const monitor = { name: "Test Monitor" };
+    const monitor = {
+        name: "Test Monitor",
+        url: "https://example.com",
+        type: "http"
+    };
 
     // Add multiple notifications for same monitor
     for (let i = 0; i < 5; i++) {

From 85f917cbd2e3b51ec6ed745fe35b1f674520e9dc Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 20 Nov 2024 19:37:55 +0500
Subject: [PATCH 20/30] Replace all instances of example.com with
 test.mydomain.com

---
 test/backend-test/test-notification.js | 42 +++++++++++++-------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index e1e360a32..5782ffb09 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -14,9 +14,9 @@ test("Notification - Format Message Test", async (t) => {
 
     const monitor = {
         name: "Test Monitor",
-        hostname: "https://example.com",
+        hostname: "test.mydomain.com",
         type: "http",
-        url: "https://example.com/status"
+        url: "https://test.mydomain.com/status"
     };
 
     const msg = {
@@ -27,15 +27,15 @@ test("Notification - Format Message Test", async (t) => {
 
     const formatted = notification.format(msg);
     assert.ok(formatted.includes("Test Monitor"), "Should include monitor name");
-    assert.ok(formatted.includes("https://example.com"), "Should include full URL");
+    assert.ok(formatted.includes("https://test.mydomain.com"), "Should include full URL");
     assert.ok(formatted.includes("Connection failed"), "Should include error message");
 
     // Test with potentially malicious URLs
     const maliciousMonitor = {
         name: "Test Monitor",
-        hostname: "https://malicious.com/example.com",
+        hostname: "https://malicious.mydomain.com/test.mydomain.com",
         type: "http",
-        url: "https://evil.com/redirect/https://example.com"
+        url: "https://evil.mydomain.com/redirect/https://test.mydomain.com"
     };
 
     const maliciousMsg = {
@@ -45,8 +45,8 @@ test("Notification - Format Message Test", async (t) => {
     };
 
     const maliciousFormatted = notification.format(maliciousMsg);
-    assert.ok(!maliciousFormatted.includes("example.com"), "Should not include example.com as substring");
-    assert.ok(maliciousFormatted.includes("https://malicious.com"), "Should include exact malicious URL");
+    assert.ok(!maliciousFormatted.includes("test.mydomain.com"), "Should not include test.mydomain.com as substring");
+    assert.ok(maliciousFormatted.includes("https://malicious.mydomain.com"), "Should include exact malicious URL");
 });
 
 test("Notification - Status Test", async (t) => {
@@ -57,7 +57,7 @@ test("Notification - Status Test", async (t) => {
         type: "up",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Service is up",
@@ -65,14 +65,14 @@ test("Notification - Status Test", async (t) => {
     };
     const upFormatted = notification.format(upMsg);
     assert.ok(upFormatted.includes("up"), "Should indicate UP status");
-    assert.ok(upFormatted.includes("https://test1.example.com"), "Should include complete URL");
+    assert.ok(upFormatted.includes("https://test1.mydomain.com"), "Should include complete URL");
 
     // Test DOWN status with secure URL
     const downMsg = {
         type: "down",
         monitor: {
             name: "Test2",
-            url: "https://test2.example.com",
+            url: "https://test2.mydomain.com",
             type: "http"
         },
         msg: "Service is down",
@@ -80,7 +80,7 @@ test("Notification - Status Test", async (t) => {
     };
     const downFormatted = notification.format(downMsg);
     assert.ok(downFormatted.includes("down"), "Should indicate DOWN status");
-    assert.ok(downFormatted.includes("https://test2.example.com"), "Should include complete URL");
+    assert.ok(downFormatted.includes("https://test2.mydomain.com"), "Should include complete URL");
 });
 
 test("Notification - Queue Management Test", async (t) => {
@@ -91,7 +91,7 @@ test("Notification - Queue Management Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Error 1"
@@ -101,7 +101,7 @@ test("Notification - Queue Management Test", async (t) => {
         type: "up",
         monitor: {
             name: "Test2",
-            url: "https://test2.example.com",
+            url: "https://test2.mydomain.com",
             type: "http"
         },
         msg: "Recovered"
@@ -116,22 +116,22 @@ test("Notification - URL Validation Test", async (t) => {
     // Test with various URL formats
     const testCases = [
         {
-            url: "https://example.com",
+            url: "https://test.mydomain.com",
             valid: true,
             description: "Basic HTTPS URL"
         },
         {
-            url: "http://sub.example.com",
+            url: "http://sub.test.mydomain.com",
             valid: true,
             description: "Subdomain URL"
         },
         {
-            url: "https://example.com/path",
+            url: "https://test.mydomain.com/path",
             valid: true,
             description: "URL with path"
         },
         {
-            url: "https://malicious.com/example.com",
+            url: "https://malicious.test.mydomain.com/test.mydomain.com",
             valid: true,
             description: "URL with misleading path"
         },
@@ -175,7 +175,7 @@ test("Notification - Priority Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Critical Error",
@@ -186,7 +186,7 @@ test("Notification - Priority Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test2",
-            url: "https://test2.example.com",
+            url: "https://test2.mydomain.com",
             type: "http"
         },
         msg: "Warning",
@@ -204,7 +204,7 @@ test("Notification - Retry Logic Test", async (t) => {
         type: "down",
         monitor: {
             name: "Test1",
-            url: "https://test1.example.com",
+            url: "https://test1.mydomain.com",
             type: "http"
         },
         msg: "Error",
@@ -227,7 +227,7 @@ test("Notification - Rate Limiting Test", async (t) => {
     const notification = new Notification();
     const monitor = {
         name: "Test Monitor",
-        url: "https://example.com",
+        url: "https://test.mydomain.com",
         type: "http"
     };
 

From a42c7cec7e84329ed7ab1bb3f260b74cd5df1561 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:36:29 +0500
Subject: [PATCH 21/30] test

---
 .github/workflows/auto-test.yml | 89 ++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index bf76d9eb6..847ff0efe 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -14,7 +14,7 @@ on:
       - '*.md'
 
 jobs:
-  auto-test:
+  unit-test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -91,3 +91,90 @@ jobs:
     - run: npx playwright install
     - run: npm run build
     - run: npm run test-e2e
+
+  post-test:
+    needs: [unit-test, e2e-test]
+    if: always() && github.event_name == 'pull_request'
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+    steps:
+      - name: Download test results
+        uses: actions/download-artifact@v3
+        with:
+          path: artifacts
+
+      - name: Process test results
+        id: test-results
+        run: |
+          echo "::set-output name=unit_test::$([ "${{ needs.unit-test.result }}" = "success" ] && echo "✅" || echo "❌")"
+          echo "::set-output name=e2e_test::$([ "${{ needs.e2e-test.result }}" = "success" ] && echo "✅" || echo "❌")"
+          
+          # Get coverage info if available
+          COVERAGE=""
+          if [ -f "artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json" ]; then
+            COVERAGE=$(cat artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json | jq -r '.total.lines.pct')
+          fi
+          echo "::set-output name=coverage::$COVERAGE"
+
+      - name: Create comment
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const unitTest = "${{ steps.test-results.outputs.unit_test }}";
+            const e2eTest = "${{ steps.test-results.outputs.e2e_test }}";
+            const coverage = "${{ steps.test-results.outputs.coverage }}";
+            
+            const comment = `## Test Results 
+
+            | Test Type | Status |
+            |-----------|--------|
+            | Unit Tests | ${unitTest} |
+            | E2E Tests | ${e2eTest} |
+            ${coverage ? `| Coverage | ${coverage}% |` : ''}
+
+            ${unitTest === '❌' || e2eTest === '❌' ? `
+            ### Failed Tests
+            Please check the [Actions tab](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for detailed logs.
+            ` : ''}
+
+            ### Artifacts
+            - [Test Results](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
+            - [Playwright Report](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
+            `;
+
+            const { data: comments } = await github.rest.issues.listComments({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+            });
+
+            const botComment = comments.find(comment => 
+              comment.user.type === 'Bot' && 
+              comment.body.includes('Test Results ')
+            );
+
+            if (botComment) {
+              await github.rest.issues.updateComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                comment_id: botComment.id,
+                body: comment
+              });
+            } else {
+              await github.rest.issues.createComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: context.issue.number,
+                body: comment
+              });
+            }
+
+      - name: Check test results
+        if: always()
+        run: |
+          if [ "${{ needs.unit-test.result }}" != "success" ] || [ "${{ needs.e2e-test.result }}" != "success" ]; then
+            echo "Tests failed!"
+            exit 1
+          fi

From 168fead2479b4d245a077eea0fe34db52b1c2a96 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:41:30 +0500
Subject: [PATCH 22/30] Revert "test"

This reverts commit 76ee06a2b3579c2c1fc1225fccd4add73a55a89f.
---
 .github/workflows/auto-test.yml | 89 +--------------------------------
 1 file changed, 1 insertion(+), 88 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 847ff0efe..bf76d9eb6 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -14,7 +14,7 @@ on:
       - '*.md'
 
 jobs:
-  unit-test:
+  auto-test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -91,90 +91,3 @@ jobs:
     - run: npx playwright install
     - run: npm run build
     - run: npm run test-e2e
-
-  post-test:
-    needs: [unit-test, e2e-test]
-    if: always() && github.event_name == 'pull_request'
-    runs-on: ubuntu-latest
-    permissions:
-      pull-requests: write
-    steps:
-      - name: Download test results
-        uses: actions/download-artifact@v3
-        with:
-          path: artifacts
-
-      - name: Process test results
-        id: test-results
-        run: |
-          echo "::set-output name=unit_test::$([ "${{ needs.unit-test.result }}" = "success" ] && echo "✅" || echo "❌")"
-          echo "::set-output name=e2e_test::$([ "${{ needs.e2e-test.result }}" = "success" ] && echo "✅" || echo "❌")"
-          
-          # Get coverage info if available
-          COVERAGE=""
-          if [ -f "artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json" ]; then
-            COVERAGE=$(cat artifacts/test-results-ubuntu-latest-node-20/coverage/coverage-summary.json | jq -r '.total.lines.pct')
-          fi
-          echo "::set-output name=coverage::$COVERAGE"
-
-      - name: Create comment
-        uses: actions/github-script@v7
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          script: |
-            const unitTest = "${{ steps.test-results.outputs.unit_test }}";
-            const e2eTest = "${{ steps.test-results.outputs.e2e_test }}";
-            const coverage = "${{ steps.test-results.outputs.coverage }}";
-            
-            const comment = `## Test Results 
-
-            | Test Type | Status |
-            |-----------|--------|
-            | Unit Tests | ${unitTest} |
-            | E2E Tests | ${e2eTest} |
-            ${coverage ? `| Coverage | ${coverage}% |` : ''}
-
-            ${unitTest === '❌' || e2eTest === '❌' ? `
-            ### Failed Tests
-            Please check the [Actions tab](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for detailed logs.
-            ` : ''}
-
-            ### Artifacts
-            - [Test Results](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
-            - [Playwright Report](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
-            `;
-
-            const { data: comments } = await github.rest.issues.listComments({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              issue_number: context.issue.number,
-            });
-
-            const botComment = comments.find(comment => 
-              comment.user.type === 'Bot' && 
-              comment.body.includes('Test Results ')
-            );
-
-            if (botComment) {
-              await github.rest.issues.updateComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                comment_id: botComment.id,
-                body: comment
-              });
-            } else {
-              await github.rest.issues.createComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: context.issue.number,
-                body: comment
-              });
-            }
-
-      - name: Check test results
-        if: always()
-        run: |
-          if [ "${{ needs.unit-test.result }}" != "success" ] || [ "${{ needs.e2e-test.result }}" != "success" ]; then
-            echo "Tests failed!"
-            exit 1
-          fi

From 3e4ecdf487856b92244a6302fce97bc9d2951224 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:47:08 +0500
Subject: [PATCH 23/30] improve workflow

---
 .github/workflows/auto-test.yml | 93 ++++++++++++++++++++++-----------
 1 file changed, 62 insertions(+), 31 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index bf76d9eb6..403b48866 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -1,6 +1,3 @@
-# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
-
 name: Auto Test
 
 on:
@@ -14,7 +11,19 @@ on:
       - '*.md'
 
 jobs:
-  auto-test:
+  check-linters:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    - name: Use Node.js 20
+      uses: actions/setup-node@v4
+      with:
+        node-version: 20
+        cache: 'npm'
+    - run: npm install
+    - run: npm run lint:prod
+
+  test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -23,23 +32,41 @@ jobs:
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
         node: [ 18, 20 ]
-        # 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
 
     - name: Use Node.js ${{ matrix.node }}
       uses: actions/setup-node@v4
       with:
         node-version: ${{ matrix.node }}
-    - run: npm install
-    - run: npm run build
-    - run: npm run test-backend
+        cache: 'npm'
+    
+    - name: Install dependencies
+      run: npm install
+
+    - name: Build project
+      run: npm run build
+
+    - name: Run backend tests
+      run: npm run test-backend
       env:
         HEADLESS_TEST: 1
         JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
 
+    - name: Run component tests
+      run: npm run test-component
+
+    - name: Upload test results
+      if: always()
+      uses: actions/upload-artifact@v3
+      with:
+        name: test-results-${{ matrix.os }}-node-${{ matrix.node }}
+        path: |
+          ./test-results
+          ./coverage
+        retention-days: 30
+
   # 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: [ ]
@@ -50,44 +77,48 @@ jobs:
       matrix:
         os: [ ARMv7 ]
         node: [ 18, 20 ]
-        # 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
 
       - name: Use Node.js ${{ matrix.node }}
         uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node }}
+          cache: 'npm'
       - run: npm ci --production
 
-  check-linters:
-    runs-on: ubuntu-latest
-
-    steps:
-    - run: git config --global core.autocrlf false  # Mainly for Windows
-    - uses: actions/checkout@v4
-
-    - name: Use Node.js 20
-      uses: actions/setup-node@v4
-      with:
-        node-version: 20
-    - run: npm install
-    - run: npm run lint:prod
-
   e2e-test:
-    needs: [ ]
+    needs: [ check-linters ]
     runs-on: ARM64
+    timeout-minutes: 30
     steps:
-    - run: git config --global core.autocrlf false  # Mainly for Windows
     - uses: actions/checkout@v4
 
     - name: Use Node.js 20
       uses: actions/setup-node@v4
       with:
         node-version: 20
-    - run: npm install
-    - run: npx playwright install
-    - run: npm run build
-    - run: npm run test-e2e
+        cache: 'npm'
+    
+    - name: Install dependencies
+      run: npm install
+
+    - name: Install Playwright browsers
+      run: npx playwright install --with-deps chromium firefox
+
+    - name: Build project
+      run: npm run build
+
+    - name: Run E2E tests
+      run: npm run test-e2e
+      env:
+        HEADLESS_TEST: 1
+
+    - name: Upload Playwright report
+      if: always()
+      uses: actions/upload-artifact@v3
+      with:
+        name: playwright-report
+        path: ./private/playwright-report
+        retention-days: 30

From 01233e3c73b9c4bc3995845594872891b1c8b487 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 15:57:04 +0500
Subject: [PATCH 24/30] fix browser installation issue

---
 .github/workflows/auto-test.yml | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 403b48866..397ffcc3c 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -104,8 +104,12 @@ jobs:
     - name: Install dependencies
       run: npm install
 
-    - name: Install Playwright browsers
-      run: npx playwright install --with-deps chromium firefox
+    - name: Install Playwright Browsers
+      run: |
+        npx playwright install chromium firefox --with-deps
+      env:
+        PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 0
+        PLAYWRIGHT_BROWSERS_PATH: 0
 
     - name: Build project
       run: npm run build

From dbbdfcaa6971fbc842a7c7244828881f21ef86cf Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 16:03:59 +0500
Subject: [PATCH 25/30] Revert "fix browser installation issue"

This reverts commit 9c35912e55f346878ff53f64bb1bacac557779f7.
---
 .github/workflows/auto-test.yml | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 397ffcc3c..403b48866 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -104,12 +104,8 @@ jobs:
     - name: Install dependencies
       run: npm install
 
-    - name: Install Playwright Browsers
-      run: |
-        npx playwright install chromium firefox --with-deps
-      env:
-        PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 0
-        PLAYWRIGHT_BROWSERS_PATH: 0
+    - name: Install Playwright browsers
+      run: npx playwright install --with-deps chromium firefox
 
     - name: Build project
       run: npm run build

From 030ff790268c963ede7f1d785deb492976f60642 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Thu, 21 Nov 2024 16:04:28 +0500
Subject: [PATCH 26/30] Revert "improve workflow"

This reverts commit a3358e298359bf7824e313cae819e70366e44fcd.
---
 .github/workflows/auto-test.yml | 91 +++++++++++----------------------
 1 file changed, 30 insertions(+), 61 deletions(-)

diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml
index 403b48866..bf76d9eb6 100644
--- a/.github/workflows/auto-test.yml
+++ b/.github/workflows/auto-test.yml
@@ -1,3 +1,6 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
 name: Auto Test
 
 on:
@@ -11,19 +14,7 @@ on:
       - '*.md'
 
 jobs:
-  check-linters:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - name: Use Node.js 20
-      uses: actions/setup-node@v4
-      with:
-        node-version: 20
-        cache: 'npm'
-    - run: npm install
-    - run: npm run lint:prod
-
-  test:
+  auto-test:
     needs: [ check-linters ]
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
@@ -32,41 +23,23 @@ jobs:
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
         node: [ 18, 20 ]
+        # 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
 
     - name: Use Node.js ${{ matrix.node }}
       uses: actions/setup-node@v4
       with:
         node-version: ${{ matrix.node }}
-        cache: 'npm'
-    
-    - name: Install dependencies
-      run: npm install
-
-    - name: Build project
-      run: npm run build
-
-    - name: Run backend tests
-      run: npm run test-backend
+    - run: npm install
+    - run: npm run build
+    - run: npm run test-backend
       env:
         HEADLESS_TEST: 1
         JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
 
-    - name: Run component tests
-      run: npm run test-component
-
-    - name: Upload test results
-      if: always()
-      uses: actions/upload-artifact@v3
-      with:
-        name: test-results-${{ matrix.os }}-node-${{ matrix.node }}
-        path: |
-          ./test-results
-          ./coverage
-        retention-days: 30
-
   # 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: [ ]
@@ -77,48 +50,44 @@ jobs:
       matrix:
         os: [ ARMv7 ]
         node: [ 18, 20 ]
+        # 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
 
       - name: Use Node.js ${{ matrix.node }}
         uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node }}
-          cache: 'npm'
       - run: npm ci --production
 
-  e2e-test:
-    needs: [ check-linters ]
-    runs-on: ARM64
-    timeout-minutes: 30
+  check-linters:
+    runs-on: ubuntu-latest
+
     steps:
+    - run: git config --global core.autocrlf false  # Mainly for Windows
     - uses: actions/checkout@v4
 
     - name: Use Node.js 20
       uses: actions/setup-node@v4
       with:
         node-version: 20
-        cache: 'npm'
-    
-    - name: Install dependencies
-      run: npm install
+    - run: npm install
+    - run: npm run lint:prod
 
-    - name: Install Playwright browsers
-      run: npx playwright install --with-deps chromium firefox
+  e2e-test:
+    needs: [ ]
+    runs-on: ARM64
+    steps:
+    - run: git config --global core.autocrlf false  # Mainly for Windows
+    - uses: actions/checkout@v4
 
-    - name: Build project
-      run: npm run build
-
-    - name: Run E2E tests
-      run: npm run test-e2e
-      env:
-        HEADLESS_TEST: 1
-
-    - name: Upload Playwright report
-      if: always()
-      uses: actions/upload-artifact@v3
+    - name: Use Node.js 20
+      uses: actions/setup-node@v4
       with:
-        name: playwright-report
-        path: ./private/playwright-report
-        retention-days: 30
+        node-version: 20
+    - run: npm install
+    - run: npx playwright install
+    - run: npm run build
+    - run: npm run test-e2e

From dabd36001639ef2ec343dc7500e2fc12b15dfe67 Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 27 Nov 2024 12:06:48 +0500
Subject: [PATCH 27/30] update dns monitor type to improve security

---
 server/monitor-types/dns.js | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/server/monitor-types/dns.js b/server/monitor-types/dns.js
index 8b87932fe..36eade1d1 100644
--- a/server/monitor-types/dns.js
+++ b/server/monitor-types/dns.js
@@ -17,10 +17,45 @@ class DnsMonitorType extends MonitorType {
         new ConditionVariable("record", defaultStringOperators ),
     ];
 
+    /**
+     * Validate hostname to ensure it's a valid domain without protocol or path
+     * @param {string} hostname Hostname to validate
+     * @returns {boolean} True if hostname is valid
+     */
+    validateHostname(hostname) {
+        try {
+            // First check if hostname contains protocol or path
+            if (hostname.includes("/") || hostname.includes(":")) {
+                return false;
+            }
+
+            // Try to construct a URL with a dummy protocol
+            const url = new URL(`http://${hostname}`);
+
+            // Ensure there's no path or query parameters
+            if (url.pathname !== "/" || url.search !== "") {
+                return false;
+            }
+
+            // Ensure the hostname matches the original input
+            // This catches cases where the URL constructor might "fix" invalid hostnames
+            return url.hostname === hostname;
+        } catch (error) {
+            return false;
+        }
+    }
+
     /**
      * @inheritdoc
      */
     async check(monitor, heartbeat, _server) {
+        // Validate hostname before proceeding
+        if (!this.validateHostname(monitor.hostname)) {
+            heartbeat.msg = "Invalid hostname format";
+            heartbeat.status = DOWN;
+            return;
+        }
+
         let startTime = dayjs().valueOf();
         let dnsMessage = "";
 

From 7668d3bd3efe006c90e14e014c20d670a3d2eb7a Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 27 Nov 2024 12:13:22 +0500
Subject: [PATCH 28/30] The tests now cover:

- All DNS record types
- Various DNS providers
- Edge cases and error conditions
- Response format validation
- Performance metrics
- Security validations
---
 test/backend-test/test-dns-monitor.js | 389 ++++++++++++++++++++++++--
 1 file changed, 367 insertions(+), 22 deletions(-)

diff --git a/test/backend-test/test-dns-monitor.js b/test/backend-test/test-dns-monitor.js
index 444aa2a27..798156c39 100644
--- a/test/backend-test/test-dns-monitor.js
+++ b/test/backend-test/test-dns-monitor.js
@@ -28,6 +28,201 @@ test("DNSMonitor - Timestamp Test", async (t) => {
     assert.strictEqual(monitor.timestamp.valueOf(), now.valueOf(), "Should set timestamp correctly");
 });
 
+test("DNS Monitor - Hostname Validation Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    const testCases = [
+        {
+            hostname: "example.com",
+            valid: true,
+            description: "Simple valid domain"
+        },
+        {
+            hostname: "sub1.sub2.example.com",
+            valid: true,
+            description: "Multiple subdomain levels"
+        },
+        {
+            hostname: "xn--bcher-kva.example", // bücher.example
+            valid: true,
+            description: "Punycode domain"
+        },
+        {
+            hostname: "example.com/path",
+            valid: false,
+            description: "Domain with path"
+        },
+        {
+            hostname: "http://example.com",
+            valid: false,
+            description: "Domain with protocol"
+        },
+        {
+            hostname: "example.com:80",
+            valid: false,
+            description: "Domain with port"
+        },
+        {
+            hostname: "example.com?query=1",
+            valid: false,
+            description: "Domain with query"
+        },
+        {
+            hostname: "example.com#fragment",
+            valid: false,
+            description: "Domain with fragment"
+        },
+        {
+            hostname: "javascript:alert(1)",
+            valid: false,
+            description: "XSS attempt"
+        },
+        {
+            hostname: "data:text/plain;base64,SGVsbG8=",
+            valid: false,
+            description: "Data URL"
+        },
+        {
+            hostname: "file:///etc/passwd",
+            valid: false,
+            description: "File protocol"
+        },
+        {
+            hostname: "localhost",
+            valid: true,
+            description: "Localhost"
+        },
+        {
+            hostname: "-invalid.com",
+            valid: false,
+            description: "Invalid starting character"
+        },
+        {
+            hostname: "example-.com",
+            valid: false,
+            description: "Invalid ending character"
+        },
+        {
+            hostname: "exa mple.com",
+            valid: false,
+            description: "Contains spaces"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const isValid = monitor.validateHostname(testCase.hostname);
+        assert.strictEqual(isValid, testCase.valid, `${testCase.description}: ${testCase.hostname}`);
+    }
+});
+
+test("DNS Monitor - Check Method Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    const testCases = [
+        {
+            config: {
+                hostname: "example.com",
+                dns_resolve_type: "A",
+                dns_resolve_server: "8.8.8.8",
+                port: 53
+            },
+            expectSuccess: true,
+            description: "Valid A record lookup"
+        },
+        {
+            config: {
+                hostname: "invalid.hostname.thisdoesnotexist",
+                dns_resolve_type: "A",
+                dns_resolve_server: "8.8.8.8",
+                port: 53
+            },
+            expectSuccess: false,
+            description: "Non-existent domain"
+        },
+        {
+            config: {
+                hostname: "example.com",
+                dns_resolve_type: "MX",
+                dns_resolve_server: "8.8.8.8",
+                port: 53
+            },
+            expectSuccess: true,
+            description: "MX record lookup"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const heartbeat = {};
+        try {
+            await monitor.check(testCase.config, heartbeat);
+            if (!testCase.expectSuccess) {
+                assert.fail(`Expected failure for ${testCase.description}`);
+            }
+            if (testCase.expectSuccess) {
+                assert.ok(heartbeat.status === UP || heartbeat.status === DOWN,
+                    `Should set heartbeat status for ${testCase.description}`);
+                assert.ok(heartbeat.msg,
+                    `Should set heartbeat message for ${testCase.description}`);
+            }
+        } catch (error) {
+            if (testCase.expectSuccess) {
+                assert.fail(`Expected success for ${testCase.description}: ${error.message}`);
+            }
+        }
+    }
+});
+
+test("DNS Monitor - Condition Evaluation Test", async (t) => {
+    const monitor = new DnsMonitorType();
+    const testCases = [
+        {
+            config: {
+                hostname: "example.com",
+                dns_resolve_type: "A",
+                dns_resolve_server: "8.8.8.8",
+                port: 53,
+                condition_expression_group: JSON.stringify({
+                    operator: "AND",
+                    expressions: [{
+                        variable: "record",
+                        operator: "contains",
+                        value: "93.184.216"
+                    }]
+                })
+            },
+            expectUp: true,
+            description: "IP address condition"
+        },
+        {
+            config: {
+                hostname: "example.com",
+                dns_resolve_type: "MX",
+                dns_resolve_server: "8.8.8.8",
+                port: 53,
+                condition_expression_group: JSON.stringify({
+                    operator: "AND",
+                    expressions: [{
+                        variable: "record",
+                        operator: "contains",
+                        value: "aspmx"
+                    }]
+                })
+            },
+            expectUp: true,
+            description: "MX record condition"
+        }
+    ];
+
+    for (const testCase of testCases) {
+        const heartbeat = {};
+        try {
+            await monitor.check(testCase.config, heartbeat);
+            assert.strictEqual(heartbeat.status, testCase.expectUp ? UP : DOWN,
+                `${testCase.description}: Expected status ${testCase.expectUp ? "UP" : "DOWN"}`);
+        } catch (error) {
+            assert.fail(`Test failed for ${testCase.description}: ${error.message}`);
+        }
+    }
+});
+
 test("DNS Monitor - Basic A Record Test", async (t) => {
     const monitor = {
         hostname: "test1.example.com",
@@ -83,18 +278,10 @@ test("DNS Monitor - URL Validation Test", async (t) => {
             maxretries: 1
         };
 
-        try {
-            const dnsMonitor = new DnsMonitorType(monitor);
-            if (!testCase.valid) {
-                assert.fail(`Should not create monitor for ${testCase.description}`);
-            }
-            assert.ok(dnsMonitor, `Should create monitor for ${testCase.description}`);
-        } catch (error) {
-            if (testCase.valid) {
-                assert.fail(`Should create monitor for ${testCase.description}`);
-            }
-            assert.ok(error, `Should throw error for ${testCase.description}`);
-        }
+        const dnsMonitor = new DnsMonitorType(monitor);
+        const isValid = dnsMonitor.validateHostname(testCase.hostname);
+        assert.strictEqual(isValid, testCase.valid,
+            `${testCase.description}: ${testCase.hostname} should be ${testCase.valid ? "valid" : "invalid"}`);
     }
 });
 
@@ -102,47 +289,205 @@ test("DNS Monitor - Resolver Test", async (t) => {
     const testCases = [
         {
             server: "8.8.8.8",
+            port: 53,
             valid: true,
+            expectUp: true,
             description: "Google DNS"
         },
         {
             server: "1.1.1.1",
+            port: 53,
             valid: true,
+            expectUp: true,
             description: "Cloudflare DNS"
         },
+        {
+            server: "9.9.9.9",
+            port: 53,
+            valid: true,
+            expectUp: true,
+            description: "Quad9 DNS"
+        },
+        {
+            server: "208.67.222.222",
+            port: 53,
+            valid: true,
+            expectUp: true,
+            description: "OpenDNS"
+        },
         {
             server: "malicious.com",
+            port: 53,
             valid: false,
+            expectUp: false,
             description: "Invalid DNS server hostname"
         },
         {
             server: "javascript:alert(1)",
+            port: 53,
             valid: false,
+            expectUp: false,
             description: "Invalid protocol"
+        },
+        {
+            server: "8.8.8.8",
+            port: 5353,
+            valid: true,
+            expectUp: false,
+            description: "Invalid port"
+        },
+        {
+            server: "192.168.0.1",
+            port: 53,
+            valid: true,
+            expectUp: false,
+            description: "Private IP address"
+        },
+        {
+            server: "256.256.256.256",
+            port: 53,
+            valid: false,
+            expectUp: false,
+            description: "Invalid IP address"
         }
     ];
 
+    const monitor = new DnsMonitorType();
+
     for (const testCase of testCases) {
-        const monitor = {
-            hostname: "test1.example.com",
+        const config = {
+            hostname: "example.com",
             dns_resolve_server: testCase.server,
-            port: 53,
+            port: testCase.port,
             dns_resolve_type: "A",
+            dns_resolve_server_port: testCase.port,
+            maxretries: 1
+        };
+
+        // Test hostname validation first
+        const isValidHostname = monitor.validateHostname(config.hostname);
+        assert.ok(isValidHostname, "Monitor hostname should be valid");
+
+        // Test DNS resolver
+        const heartbeat = {};
+        try {
+            await monitor.check(config, heartbeat);
+
+            if (!testCase.valid) {
+                assert.strictEqual(heartbeat.status, DOWN,
+                    `${testCase.description}: Should set status to DOWN for invalid DNS server`);
+            } else {
+                assert.ok(heartbeat.status === UP || heartbeat.status === DOWN,
+                    `${testCase.description}: Should set valid heartbeat status`);
+                assert.ok(heartbeat.msg,
+                    `${testCase.description}: Should set heartbeat message`);
+
+                if (testCase.expectUp) {
+                    assert.strictEqual(heartbeat.status, UP,
+                        `${testCase.description}: Should be UP for valid DNS server`);
+                } else {
+                    assert.strictEqual(heartbeat.status, DOWN,
+                        `${testCase.description}: Should be DOWN for problematic DNS server`);
+                }
+            }
+        } catch (error) {
+            if (testCase.valid && testCase.expectUp) {
+                assert.fail(`${testCase.description}: Unexpected error - ${error.message}`);
+            } else {
+                assert.ok(error,
+                    `${testCase.description}: Should handle error for invalid DNS server`);
+            }
+        }
+    }
+});
+
+test("DNS Monitor - Record Type Test", async (t) => {
+    const testCases = [
+        {
+            type: "A",
+            expectSuccess: true,
+            description: "A record lookup"
+        },
+        {
+            type: "AAAA",
+            expectSuccess: true,
+            description: "AAAA record lookup"
+        },
+        {
+            type: "MX",
+            expectSuccess: true,
+            description: "MX record lookup"
+        },
+        {
+            type: "TXT",
+            expectSuccess: true,
+            description: "TXT record lookup"
+        },
+        {
+            type: "NS",
+            expectSuccess: true,
+            description: "NS record lookup"
+        },
+        {
+            type: "CNAME",
+            expectSuccess: true,
+            description: "CNAME record lookup"
+        },
+        {
+            type: "SOA",
+            expectSuccess: true,
+            description: "SOA record lookup"
+        },
+        {
+            type: "CAA",
+            expectSuccess: true,
+            description: "CAA record lookup"
+        },
+        {
+            type: "SRV",
+            expectSuccess: true,
+            description: "SRV record lookup"
+        },
+        {
+            type: "INVALID",
+            expectSuccess: false,
+            description: "Invalid record type"
+        }
+    ];
+
+    const monitor = new DnsMonitorType();
+
+    for (const testCase of testCases) {
+        const config = {
+            hostname: "example.com",
+            dns_resolve_server: "8.8.8.8",
+            port: 53,
+            dns_resolve_type: testCase.type,
             dns_resolve_server_port: 53,
             maxretries: 1
         };
 
+        const heartbeat = {};
         try {
-            const dnsMonitor = new DnsMonitorType(monitor);
-            if (!testCase.valid) {
-                assert.fail(`Should not create monitor for ${testCase.description}`);
+            await monitor.check(config, heartbeat);
+
+            if (!testCase.expectSuccess) {
+                assert.fail(`${testCase.description}: Should fail for invalid record type`);
             }
-            assert.ok(dnsMonitor, `Should create monitor for ${testCase.description}`);
+
+            assert.ok(heartbeat.status === UP || heartbeat.status === DOWN,
+                `${testCase.description}: Should set valid heartbeat status`);
+            assert.ok(heartbeat.msg,
+                `${testCase.description}: Should set heartbeat message`);
+            assert.ok(heartbeat.ping > 0,
+                `${testCase.description}: Should measure response time`);
         } catch (error) {
-            if (testCase.valid) {
-                assert.fail(`Should create monitor for ${testCase.description}`);
+            if (testCase.expectSuccess) {
+                assert.fail(`${testCase.description}: Unexpected error - ${error.message}`);
+            } else {
+                assert.ok(error,
+                    `${testCase.description}: Should handle error for invalid record type`);
             }
-            assert.ok(error, `Should throw error for ${testCase.description}`);
         }
     }
 });

From ee70e245f00fbec162c60d4ab07cefff83b9d92a Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 27 Nov 2024 12:17:11 +0500
Subject: [PATCH 29/30] The test suite now provides:

- Better test organization
- More comprehensive coverage
- Improved error handling
- Better async cleanup
- More realistic test scenarios
---
 test/backend-test/test-mqtt.js | 258 +++++++++++++++++++++++++--------
 1 file changed, 201 insertions(+), 57 deletions(-)

diff --git a/test/backend-test/test-mqtt.js b/test/backend-test/test-mqtt.js
index 450310298..d26c7b9a0 100644
--- a/test/backend-test/test-mqtt.js
+++ b/test/backend-test/test-mqtt.js
@@ -3,41 +3,62 @@ 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 { UP, DOWN, PENDING } = require("../../src/util");
 
 /**
- * Runs an MQTT test with the
- * @param  {string} mqttSuccessMessage the message that the monitor expects
- * @param {null|"keyword"|"json-query"} mqttCheckType the type of check we perform
- * @param {string} receivedMessage what message is recieved from the mqtt channel
- * @returns {Promise<Heartbeat>} the heartbeat produced by the check
+ * Runs an MQTT test with the given parameters
+ * @param {object} options Test configuration options
+ * @param {string} options.mqttSuccessMessage The message that the monitor expects
+ * @param {null|"keyword"|"json-query"} options.mqttCheckType The type of check to perform
+ * @param {string} options.receivedMessage Message received from the MQTT channel
+ * @param {string} options.jsonPath JSON path for json-query checks
+ * @param {string} options.topic MQTT topic to subscribe to
+ * @param {number} options.interval Monitor check interval
+ * @param {string} options.username MQTT username
+ * @param {string} options.password MQTT password
+ * @returns {Promise<heartbeat>} The heartbeat produced by the check
  */
-async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
+async function testMqtt({
+    mqttSuccessMessage,
+    mqttCheckType,
+    receivedMessage,
+    jsonPath = "firstProp",
+    topic = "test",
+    interval = 20,
+    username = null,
+    password = null
+}) {
     const hiveMQContainer = await new HiveMQContainer().start();
     const connectionString = hiveMQContainer.getConnectionString();
     const mqttMonitorType = new MqttMonitorType();
+
     const monitor = {
-        jsonPath: "firstProp", // always return firstProp for the json-query monitor
+        jsonPath,
         hostname: connectionString.split(":", 2).join(":"),
-        mqttTopic: "test",
+        mqttTopic: topic,
         port: connectionString.split(":")[2],
-        mqttUsername: null,
-        mqttPassword: null,
-        interval: 20, // controls the timeout
-        mqttSuccessMessage: mqttSuccessMessage, // for keywords
-        expectedValue: mqttSuccessMessage, // for json-query
-        mqttCheckType: mqttCheckType,
+        mqttUsername: username,
+        mqttPassword: password,
+        interval,
+        mqttSuccessMessage,
+        expectedValue: mqttSuccessMessage,
+        mqttCheckType,
     };
+
     const heartbeat = {
         msg: "",
         status: PENDING,
     };
 
-    const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString());
+    const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString(), {
+        username,
+        password
+    });
+
     testMqttClient.on("connect", () => {
-        testMqttClient.subscribe("test", (error) => {
+        testMqttClient.subscribe(topic, (error) => {
             if (!error) {
-                testMqttClient.publish("test", receivedMessage);
+                testMqttClient.publish(topic, receivedMessage);
             }
         });
     });
@@ -46,7 +67,7 @@ async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
         await mqttMonitorType.check(monitor, heartbeat, {});
     } finally {
         testMqttClient.end();
-        hiveMQContainer.stop();
+        await hiveMQContainer.stop();
     }
     return heartbeat;
 }
@@ -55,48 +76,171 @@ describe("MqttMonitorType", {
     concurrency: true,
     skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
 }, () => {
-    test("valid keywords (type=default)", async () => {
-        const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-");
-        assert.strictEqual(heartbeat.status, UP);
-        assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
+    describe("Keyword Matching Tests", () => {
+        test("should match exact keyword (type=default)", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "KEYWORD",
+                mqttCheckType: null,
+                receivedMessage: "KEYWORD"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+            assert.strictEqual(heartbeat.msg, "Topic: test; Message: KEYWORD");
+        });
+
+        test("should match keyword within message (type=default)", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "KEYWORD",
+                mqttCheckType: null,
+                receivedMessage: "-> KEYWORD <-"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+            assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
+        });
+
+        test("should fail on missing keyword (type=default)", async () => {
+            await assert.rejects(
+                testMqtt({
+                    mqttSuccessMessage: "NOT_PRESENT",
+                    mqttCheckType: null,
+                    receivedMessage: "-> KEYWORD <-"
+                }),
+                new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-")
+            );
+        });
+
+        test("should handle special characters in keyword", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "特殊文字",
+                mqttCheckType: "keyword",
+                receivedMessage: "Message: 特殊文字"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+        });
     });
 
-    test("valid keywords (type=keyword)", async () => {
-        const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
-        assert.strictEqual(heartbeat.status, UP);
-        assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
-    });
-    test("invalid keywords (type=default)", async () => {
-        await assert.rejects(
-            testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
-            new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
-        );
+    describe("JSON Query Tests", () => {
+        test("should match simple JSON value", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "present",
+                mqttCheckType: "json-query",
+                receivedMessage: "{\"firstProp\":\"present\"}"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+            assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
+        });
+
+        test("should handle nested JSON paths", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "nested-value",
+                mqttCheckType: "json-query",
+                receivedMessage: "{\"parent\":{\"firstProp\":\"nested-value\"}}",
+                jsonPath: "parent.firstProp"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+        });
+
+        test("should fail on missing JSON path", async () => {
+            await assert.rejects(
+                testMqtt({
+                    mqttSuccessMessage: "value",
+                    mqttCheckType: "json-query",
+                    receivedMessage: "{}",
+                    jsonPath: "nonexistent"
+                }),
+                /Message received but value is not equal to expected value/
+            );
+        });
+
+        test("should fail on invalid JSON", async () => {
+            await assert.rejects(
+                testMqtt({
+                    mqttSuccessMessage: "value",
+                    mqttCheckType: "json-query",
+                    receivedMessage: "invalid-json"
+                }),
+                /Unexpected token/
+            );
+        });
+
+        test("should handle array values", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "item2",
+                mqttCheckType: "json-query",
+                receivedMessage: "{\"firstProp\":[\"item1\",\"item2\",\"item3\"]}",
+                jsonPath: "firstProp[1]"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+        });
     });
 
-    test("invalid keyword (type=keyword)", async () => {
-        await assert.rejects(
-            testMqtt("NOT_PRESENT", "keyword", "-> KEYWORD <-"),
-            new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
-        );
+    describe("Authentication Tests", () => {
+        test("should handle successful authentication", async () => {
+            const heartbeat = await testMqtt({
+                mqttSuccessMessage: "auth-success",
+                mqttCheckType: "keyword",
+                receivedMessage: "auth-success",
+                username: "testuser",
+                password: "testpass"
+            });
+            assert.strictEqual(heartbeat.status, UP);
+        });
+
+        test("should handle failed authentication", async () => {
+            await assert.rejects(
+                testMqtt({
+                    mqttSuccessMessage: "irrelevant",
+                    mqttCheckType: "keyword",
+                    receivedMessage: "irrelevant",
+                    username: "invalid",
+                    password: "invalid"
+                }),
+                /Authentication failed/
+            );
+        });
     });
-    test("valid json-query", async () => {
-        // works because the monitors' jsonPath is hard-coded to "firstProp"
-        const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
-        assert.strictEqual(heartbeat.status, UP);
-        assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
-    });
-    test("invalid (because query fails) json-query", async () => {
-        // works because the monitors' jsonPath is hard-coded to "firstProp"
-        await assert.rejects(
-            testMqtt("[not_relevant]", "json-query", "{}"),
-            new Error("Message received but value is not equal to expected value, value was: [undefined]"),
-        );
-    });
-    test("invalid (because successMessage fails) json-query", async () => {
-        // works because the monitors' jsonPath is hard-coded to "firstProp"
-        await assert.rejects(
-            testMqtt("[wrong_success_messsage]", "json-query", "{\"firstProp\":\"present\"}"),
-            new Error("Message received but value is not equal to expected value, value was: [present]")
-        );
+
+    describe("Error Handling Tests", () => {
+        test("should handle connection timeout", async () => {
+            await assert.rejects(
+                testMqtt({
+                    mqttSuccessMessage: "timeout",
+                    mqttCheckType: "keyword",
+                    receivedMessage: "timeout",
+                    interval: 1
+                }),
+                /Timeout/
+            );
+        });
+
+        test("should handle invalid topic format", async () => {
+            await assert.rejects(
+                testMqtt({
+                    mqttSuccessMessage: "invalid",
+                    mqttCheckType: "keyword",
+                    receivedMessage: "invalid",
+                    topic: "invalid/#/topic"
+                }),
+                /Invalid topic/
+            );
+        });
+
+        test("should handle disconnection", async () => {
+            const hiveMQContainer = await new HiveMQContainer().start();
+            const heartbeat = { status: PENDING,
+                msg: "" };
+            const monitor = new MqttMonitorType();
+
+            try {
+                await hiveMQContainer.stop();
+                await monitor.check({
+                    hostname: hiveMQContainer.getConnectionString().split(":")[0],
+                    port: hiveMQContainer.getConnectionString().split(":")[2],
+                    mqttTopic: "test"
+                }, heartbeat, {});
+                assert.fail("Should have thrown an error");
+            } catch (error) {
+                assert.ok(error.message.includes("connect"));
+            }
+        });
     });
 });

From d6f2d583b10aa83dbd20357b47c7ee76cd4116ba Mon Sep 17 00:00:00 2001
From: Zaid-maker <pzhafeez@gmail.com>
Date: Wed, 27 Nov 2024 12:40:48 +0500
Subject: [PATCH 30/30] improve URL validation

---
 test/backend-test/test-notification.js | 188 +++++++++++++++++++++++--
 1 file changed, 177 insertions(+), 11 deletions(-)

diff --git a/test/backend-test/test-notification.js b/test/backend-test/test-notification.js
index 5782ffb09..840d22dee 100644
--- a/test/backend-test/test-notification.js
+++ b/test/backend-test/test-notification.js
@@ -110,40 +110,147 @@ test("Notification - Queue Management Test", async (t) => {
     assert.strictEqual(notification.queue.length, 2, "Queue should have 2 items");
 });
 
-test("Notification - URL Validation Test", async (t) => {
+test("Notification - URL Validation and Sanitization Test", async (t) => {
     const notification = new Notification();
 
-    // Test with various URL formats
+    // Test with various URL formats and edge cases
     const testCases = [
+        // Valid URLs
         {
             url: "https://test.mydomain.com",
             valid: true,
-            description: "Basic HTTPS URL"
+            description: "Basic HTTPS URL",
+            expectedOutput: "https://test.mydomain.com"
         },
         {
             url: "http://sub.test.mydomain.com",
             valid: true,
-            description: "Subdomain URL"
+            description: "Subdomain URL",
+            expectedOutput: "http://sub.test.mydomain.com"
         },
         {
             url: "https://test.mydomain.com/path",
             valid: true,
-            description: "URL with path"
+            description: "URL with path",
+            expectedOutput: "https://test.mydomain.com/path"
         },
         {
-            url: "https://malicious.test.mydomain.com/test.mydomain.com",
+            url: "https://test.mydomain.com:8080",
             valid: true,
-            description: "URL with misleading path"
+            description: "URL with port",
+            expectedOutput: "https://test.mydomain.com:8080"
         },
+        {
+            url: "https://test.mydomain.com/path?query=1",
+            valid: true,
+            description: "URL with query parameters",
+            expectedOutput: "https://test.mydomain.com/path?query=1"
+        },
+        {
+            url: "https://test.mydomain.com/path#fragment",
+            valid: true,
+            description: "URL with fragment",
+            expectedOutput: "https://test.mydomain.com/path#fragment"
+        },
+        {
+            url: "https://test.mydomain.com/special%20chars",
+            valid: true,
+            description: "URL with encoded characters",
+            expectedOutput: "https://test.mydomain.com/special%20chars"
+        },
+
+        // Potentially malicious URLs
         {
             url: "javascript:alert(1)",
             valid: false,
-            description: "JavaScript protocol"
+            description: "JavaScript protocol",
+            expectedOutput: ""
         },
         {
             url: "data:text/html,<script>alert(1)</script>",
             valid: false,
-            description: "Data URL"
+            description: "Data URL",
+            expectedOutput: ""
+        },
+        {
+            url: "file:///etc/passwd",
+            valid: false,
+            description: "File protocol",
+            expectedOutput: ""
+        },
+        {
+            url: "https://malicious.com?redirect=https://test.mydomain.com",
+            valid: true,
+            description: "URL with redirect parameter",
+            expectedOutput: "https://malicious.com?redirect=https://test.mydomain.com"
+        },
+        {
+            url: "https://malicious.com/https://test.mydomain.com",
+            valid: true,
+            description: "URL with embedded URL in path",
+            expectedOutput: "https://malicious.com/https://test.mydomain.com"
+        },
+        {
+            url: "https://test.mydomain.com@malicious.com",
+            valid: false,
+            description: "URL with @ character",
+            expectedOutput: ""
+        },
+        {
+            url: "https://malicious.com\\@test.mydomain.com",
+            valid: false,
+            description: "URL with escaped @ character",
+            expectedOutput: ""
+        },
+        {
+            url: "https:\\\\test.mydomain.com",
+            valid: false,
+            description: "URL with backslashes",
+            expectedOutput: ""
+        },
+        {
+            url: "https://test.mydomain.com/path/<script>alert(1)</script>",
+            valid: true,
+            description: "URL with XSS in path",
+            expectedOutput: "https://test.mydomain.com/path/<script>alert(1)</script>"
+        },
+
+        // Edge cases
+        {
+            url: "https://test.mydomain.com//double//slashes",
+            valid: true,
+            description: "URL with double slashes",
+            expectedOutput: "https://test.mydomain.com//double//slashes"
+        },
+        {
+            url: "https://test.mydomain.com/./path/../test",
+            valid: true,
+            description: "URL with dot segments",
+            expectedOutput: "https://test.mydomain.com/./path/../test"
+        },
+        {
+            url: "https://test.mydomain.com/%2e%2e%2f",
+            valid: true,
+            description: "URL with encoded dot segments",
+            expectedOutput: "https://test.mydomain.com/%2e%2e%2f"
+        },
+        {
+            url: "https://test.mydomain.com/\u0000",
+            valid: false,
+            description: "URL with null byte",
+            expectedOutput: ""
+        },
+        {
+            url: "https://test.mydomain.com/path with spaces",
+            valid: true,
+            description: "URL with unencoded spaces",
+            expectedOutput: "https://test.mydomain.com/path%20with%20spaces"
+        },
+        {
+            url: "https://xn--mnich-kva.example.com",
+            valid: true,
+            description: "Punycode URL",
+            expectedOutput: "https://xn--mnich-kva.example.com"
         }
     ];
 
@@ -159,11 +266,70 @@ test("Notification - URL Validation Test", async (t) => {
         };
 
         const formatted = notification.format(msg);
+
         if (testCase.valid) {
-            assert.ok(formatted.includes(testCase.url), `Should include ${testCase.description}`);
+            // For valid URLs, check if the URL is included exactly as expected
+            if (testCase.expectedOutput) {
+                assert.ok(
+                    formatted.includes(testCase.expectedOutput),
+                    `${testCase.description}: Should include exact URL ${testCase.expectedOutput}`
+                );
+            }
+
+            // Check for potential URL substring issues
+            if (testCase.url.includes("test.mydomain.com")) {
+                const urlParts = testCase.url.split("test.mydomain.com");
+                if (urlParts.length > 1) {
+                    // Check that we don't have unintended URL substring matches
+                    const occurrences = formatted.split("test.mydomain.com").length - 1;
+                    assert.strictEqual(
+                        occurrences,
+                        1,
+                        `${testCase.description}: Should only include the URL once`
+                    );
+                }
+            }
         } else {
-            assert.ok(!formatted.includes(testCase.url), `Should not include ${testCase.description}`);
+            // For invalid URLs, ensure they're not included at all
+            assert.ok(
+                !formatted.includes(testCase.url),
+                `${testCase.description}: Should not include invalid URL`
+            );
+
+            // For invalid URLs with potentially dangerous parts, ensure those parts are not present
+            if (testCase.url.includes("javascript:") ||
+                testCase.url.includes("data:") ||
+                testCase.url.includes("file:")) {
+                assert.ok(
+                    !formatted.includes("javascript:") &&
+                    !formatted.includes("data:") &&
+                    !formatted.includes("file:"),
+                    `${testCase.description}: Should not include dangerous protocols`
+                );
+            }
         }
+
+        // Check for proper URL encoding
+        if (testCase.valid && testCase.url.includes(" ")) {
+            assert.ok(
+                !formatted.includes(" "),
+                `${testCase.description}: Spaces should be properly encoded`
+            );
+            assert.ok(
+                formatted.includes("%20"),
+                `${testCase.description}: Should use percent encoding for spaces`
+            );
+        }
+
+        // Additional security checks
+        assert.ok(
+            !formatted.includes("<script>"),
+            `${testCase.description}: Should not include unescaped script tags`
+        );
+        assert.ok(
+            !formatted.includes("javascript:"),
+            `${testCase.description}: Should not include javascript: protocol`
+        );
     }
 });