From fe09beaf6af1e1eb40be557d164d2bee79159ae9 Mon Sep 17 00:00:00 2001 From: Zaid-maker Date: Wed, 20 Nov 2024 18:35:57 +0500 Subject: [PATCH 1/7] 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 Date: Wed, 20 Nov 2024 18:48:13 +0500 Subject: [PATCH 2/7] 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: "
" + } +}); + +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: "" + } +})); + +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 Date: Wed, 20 Nov 2024 18:58:15 +0500 Subject: [PATCH 3/7] 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: "
" + } +}); + +vi.mock("../../src/components/MonitorListFilter.vue", { + default: { + name: "MonitorListFilter", + template: "
" + } +}); + 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: "
", + 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: "" - } +// 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 Date: Wed, 20 Nov 2024 19:06:15 +0500 Subject: [PATCH 4/7] 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 Date: Wed, 20 Nov 2024 19:12:46 +0500 Subject: [PATCH 5/7] 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: "
", - 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: "" + } +})); + 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 Date: Wed, 20 Nov 2024 19:19:23 +0500 Subject: [PATCH 6/7] 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,", + 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 Date: Wed, 20 Nov 2024 19:37:55 +0500 Subject: [PATCH 7/7] 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" };