From a9359e921b5365c2fe22d678f2dc930c0220c69b Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 25 May 2026 23:22:12 +0200 Subject: [PATCH] test(frontend): vitest harness with golden-file fixtures for inbound protocols Stand up Phase 3 safety net before the models/ rewrite. The harness loads JSON fixtures via Vite's import.meta.glob, parses each through InboundSettingsSchema (the tagged-wrapper DU), and snapshots the canonical parsed shape. Snapshots stay byte-stable across the upcoming class-to- pure-function extraction, catching any normalization drift. Six representative inbound fixtures cover the high-traffic protocols: vless, vmess, trojan, shadowsocks (2022-blake3 multi-user), wireguard, hysteria2. Stream and security branches plus the remaining protocols (http, mixed, tunnel, hysteria) follow in subsequent turns. Uses /// instead of @types/node so we avoid pulling in another type package; import.meta.glob is enough to walk the fixtures directory at compile time. Adds vitest 4.1.7 as the only new dev dependency. test/test:watch scripts land in package.json; a standalone vitest.config.ts keeps the production vite.config.js (which reads from sqlite via DatabaseSync) out of the test runner. --- frontend/package-lock.json | 364 +++++++++++++++++- frontend/package.json | 5 +- .../test/__snapshots__/protocols.test.ts.snap | 144 +++++++ .../fixtures/inbound/hysteria2-basic.json | 20 + .../fixtures/inbound/shadowsocks-2022.json | 24 ++ .../golden/fixtures/inbound/trojan-basic.json | 20 + .../fixtures/inbound/vless-tcp-none.json | 23 ++ .../golden/fixtures/inbound/vmess-basic.json | 20 + .../fixtures/inbound/wireguard-basic.json | 16 + frontend/src/test/protocols.test.ts | 29 ++ frontend/vitest.config.ts | 16 + 11 files changed, 679 insertions(+), 2 deletions(-) create mode 100644 frontend/src/test/__snapshots__/protocols.test.ts.snap create mode 100644 frontend/src/test/golden/fixtures/inbound/hysteria2-basic.json create mode 100644 frontend/src/test/golden/fixtures/inbound/shadowsocks-2022.json create mode 100644 frontend/src/test/golden/fixtures/inbound/trojan-basic.json create mode 100644 frontend/src/test/golden/fixtures/inbound/vless-tcp-none.json create mode 100644 frontend/src/test/golden/fixtures/inbound/vmess-basic.json create mode 100644 frontend/src/test/golden/fixtures/inbound/wireguard-basic.json create mode 100644 frontend/src/test/protocols.test.ts create mode 100644 frontend/vitest.config.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9a745f3c..44b81dc3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -40,7 +40,8 @@ "globals": "^17.6.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.4", - "vite": "8.0.13" + "vite": "8.0.13", + "vitest": "^4.1.7" }, "engines": { "node": ">=22.0.0", @@ -2639,6 +2640,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -2702,6 +2714,13 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -3065,6 +3084,119 @@ } } }, + "node_modules/@vitest/expect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -3192,6 +3324,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "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/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3414,6 +3556,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -3856,6 +4008,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", @@ -4078,6 +4237,16 @@ "node": ">=4.0" } }, + "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/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4094,6 +4263,16 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5171,6 +5350,16 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5328,6 +5517,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/openapi-path-templating": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-2.2.1.tgz", @@ -5459,6 +5659,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/persian-calendar-suite": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/persian-calendar-suite/-/persian-calendar-suite-1.5.5.tgz", @@ -6221,6 +6428,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/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6247,6 +6461,20 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, + "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/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", @@ -6355,6 +6583,23 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "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": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -6372,6 +6617,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", @@ -6707,6 +6962,96 @@ } } }, + "node_modules/vitest": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -6766,6 +7111,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/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1f5e4759..68cdc664 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,8 @@ "preview": "vite preview", "lint": "eslint src", "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", "gen:api": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/build-openapi.mjs", "gen:zod": "cd .. && go run ./tools/openapigen" }, @@ -50,7 +52,8 @@ "globals": "^17.6.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.4", - "vite": "8.0.13" + "vite": "8.0.13", + "vitest": "^4.1.7" }, "overrides": { "react-copy-to-clipboard": "^5.1.1", diff --git a/frontend/src/test/__snapshots__/protocols.test.ts.snap b/frontend/src/test/__snapshots__/protocols.test.ts.snap new file mode 100644 index 00000000..6682fe48 --- /dev/null +++ b/frontend/src/test/__snapshots__/protocols.test.ts.snap @@ -0,0 +1,144 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`InboundSettingsSchema fixtures > parses hysteria2-basic byte-stably 1`] = ` +{ + "protocol": "hysteria2", + "settings": { + "clients": [ + { + "auth": "hyst3ria2-auth-token-XYZ", + "comment": "", + "email": "hy2-client@example.test", + "enable": true, + "expiryTime": 0, + "limitIp": 0, + "reset": 0, + "subId": "hy2-001", + "tgId": 0, + "totalGB": 0, + }, + ], + "version": 2, + }, +} +`; + +exports[`InboundSettingsSchema fixtures > parses shadowsocks-2022 byte-stably 1`] = ` +{ + "protocol": "shadowsocks", + "settings": { + "clients": [ + { + "comment": "multi-user shadowsocks 2022", + "email": "ss-client-1@example.test", + "enable": true, + "expiryTime": 0, + "limitIp": 0, + "method": "", + "password": "dGVzdC1jbGllbnQtcGFzc3dvcmQtMQ==", + "reset": 0, + "subId": "ssm001", + "tgId": 0, + "totalGB": 0, + }, + ], + "ivCheck": false, + "method": "2022-blake3-aes-256-gcm", + "network": "tcp,udp", + "password": "9oCBhTZxJ5wQa3fLs2vK7nM6pR4tY1uX", + }, +} +`; + +exports[`InboundSettingsSchema fixtures > parses trojan-basic byte-stably 1`] = ` +{ + "protocol": "trojan", + "settings": { + "clients": [ + { + "comment": "", + "email": "carol@example.test", + "enable": true, + "expiryTime": 0, + "limitIp": 0, + "password": "tr0jan-passw0rd-XyZ-123!", + "reset": 0, + "subId": "trj001", + "tgId": 0, + "totalGB": 0, + }, + ], + "fallbacks": [], + }, +} +`; + +exports[`InboundSettingsSchema fixtures > parses vless-tcp-none byte-stably 1`] = ` +{ + "protocol": "vless", + "settings": { + "clients": [ + { + "comment": "", + "email": "alice@example.test", + "enable": true, + "expiryTime": 0, + "flow": "", + "id": "8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02", + "limitIp": 0, + "reset": 0, + "subId": "abc123def", + "tgId": 0, + "totalGB": 0, + }, + ], + "decryption": "none", + "encryption": "none", + "fallbacks": [], + }, +} +`; + +exports[`InboundSettingsSchema fixtures > parses vmess-basic byte-stably 1`] = ` +{ + "protocol": "vmess", + "settings": { + "clients": [ + { + "comment": "primary tester", + "email": "bob@example.test", + "enable": true, + "expiryTime": 0, + "id": "c0aa1b9e-4d56-4e8b-9a01-bf2e5d7c4f31", + "limitIp": 2, + "reset": 0, + "security": "auto", + "subId": "vmess001", + "tgId": 0, + "totalGB": 0, + }, + ], + }, +} +`; + +exports[`InboundSettingsSchema fixtures > parses wireguard-basic byte-stably 1`] = ` +{ + "protocol": "wireguard", + "settings": { + "mtu": 1420, + "noKernelTun": false, + "peers": [ + { + "allowedIPs": [ + "10.0.0.2/32", + ], + "keepAlive": 25, + "privateKey": "iJ2cBkrSGqRwIfYIDIxk7hr5RXfdR93MfJUL7yqkkH8=", + "publicKey": "DGSYIcEKAUkA7HhzGSjxLZuV67BR3LeyU0BMLJzNVHQ=", + }, + ], + "secretKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=", + }, +} +`; diff --git a/frontend/src/test/golden/fixtures/inbound/hysteria2-basic.json b/frontend/src/test/golden/fixtures/inbound/hysteria2-basic.json new file mode 100644 index 00000000..1339df5d --- /dev/null +++ b/frontend/src/test/golden/fixtures/inbound/hysteria2-basic.json @@ -0,0 +1,20 @@ +{ + "protocol": "hysteria2", + "settings": { + "version": 2, + "clients": [ + { + "auth": "hyst3ria2-auth-token-XYZ", + "email": "hy2-client@example.test", + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": true, + "tgId": 0, + "subId": "hy2-001", + "comment": "", + "reset": 0 + } + ] + } +} diff --git a/frontend/src/test/golden/fixtures/inbound/shadowsocks-2022.json b/frontend/src/test/golden/fixtures/inbound/shadowsocks-2022.json new file mode 100644 index 00000000..57766ac4 --- /dev/null +++ b/frontend/src/test/golden/fixtures/inbound/shadowsocks-2022.json @@ -0,0 +1,24 @@ +{ + "protocol": "shadowsocks", + "settings": { + "method": "2022-blake3-aes-256-gcm", + "password": "9oCBhTZxJ5wQa3fLs2vK7nM6pR4tY1uX", + "network": "tcp,udp", + "clients": [ + { + "method": "", + "password": "dGVzdC1jbGllbnQtcGFzc3dvcmQtMQ==", + "email": "ss-client-1@example.test", + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": true, + "tgId": 0, + "subId": "ssm001", + "comment": "multi-user shadowsocks 2022", + "reset": 0 + } + ], + "ivCheck": false + } +} diff --git a/frontend/src/test/golden/fixtures/inbound/trojan-basic.json b/frontend/src/test/golden/fixtures/inbound/trojan-basic.json new file mode 100644 index 00000000..96574a07 --- /dev/null +++ b/frontend/src/test/golden/fixtures/inbound/trojan-basic.json @@ -0,0 +1,20 @@ +{ + "protocol": "trojan", + "settings": { + "clients": [ + { + "password": "tr0jan-passw0rd-XyZ-123!", + "email": "carol@example.test", + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": true, + "tgId": 0, + "subId": "trj001", + "comment": "", + "reset": 0 + } + ], + "fallbacks": [] + } +} diff --git a/frontend/src/test/golden/fixtures/inbound/vless-tcp-none.json b/frontend/src/test/golden/fixtures/inbound/vless-tcp-none.json new file mode 100644 index 00000000..906d069e --- /dev/null +++ b/frontend/src/test/golden/fixtures/inbound/vless-tcp-none.json @@ -0,0 +1,23 @@ +{ + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02", + "email": "alice@example.test", + "flow": "", + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": true, + "tgId": 0, + "subId": "abc123def", + "comment": "", + "reset": 0 + } + ], + "decryption": "none", + "encryption": "none", + "fallbacks": [] + } +} diff --git a/frontend/src/test/golden/fixtures/inbound/vmess-basic.json b/frontend/src/test/golden/fixtures/inbound/vmess-basic.json new file mode 100644 index 00000000..6cde4c5a --- /dev/null +++ b/frontend/src/test/golden/fixtures/inbound/vmess-basic.json @@ -0,0 +1,20 @@ +{ + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "c0aa1b9e-4d56-4e8b-9a01-bf2e5d7c4f31", + "security": "auto", + "email": "bob@example.test", + "limitIp": 2, + "totalGB": 0, + "expiryTime": 0, + "enable": true, + "tgId": 0, + "subId": "vmess001", + "comment": "primary tester", + "reset": 0 + } + ] + } +} diff --git a/frontend/src/test/golden/fixtures/inbound/wireguard-basic.json b/frontend/src/test/golden/fixtures/inbound/wireguard-basic.json new file mode 100644 index 00000000..72b64622 --- /dev/null +++ b/frontend/src/test/golden/fixtures/inbound/wireguard-basic.json @@ -0,0 +1,16 @@ +{ + "protocol": "wireguard", + "settings": { + "mtu": 1420, + "secretKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=", + "peers": [ + { + "privateKey": "iJ2cBkrSGqRwIfYIDIxk7hr5RXfdR93MfJUL7yqkkH8=", + "publicKey": "DGSYIcEKAUkA7HhzGSjxLZuV67BR3LeyU0BMLJzNVHQ=", + "allowedIPs": ["10.0.0.2/32"], + "keepAlive": 25 + } + ], + "noKernelTun": false + } +} diff --git a/frontend/src/test/protocols.test.ts b/frontend/src/test/protocols.test.ts new file mode 100644 index 00000000..45a8f6a5 --- /dev/null +++ b/frontend/src/test/protocols.test.ts @@ -0,0 +1,29 @@ +/// +import { describe, expect, it } from 'vitest'; + +import { InboundSettingsSchema } from '@/schemas/protocols'; + +// import.meta.glob (eager, default-import) gives us {path: parsedJson} at +// compile time — no fs, no @types/node. Vitest inherits the vite/client +// shape so this stays typed. +const inboundFixtures = import.meta.glob( + './golden/fixtures/inbound/*.json', + { eager: true, import: 'default' }, +); + +function fixtureName(path: string): string { + const file = path.split('/').pop() ?? path; + return file.replace(/\.json$/, ''); +} + +describe('InboundSettingsSchema fixtures', () => { + const entries = Object.entries(inboundFixtures).sort(([a], [b]) => a.localeCompare(b)); + expect(entries.length, 'expected at least one fixture under golden/fixtures/inbound').toBeGreaterThan(0); + + for (const [path, raw] of entries) { + it(`parses ${fixtureName(path)} byte-stably`, () => { + const parsed = InboundSettingsSchema.parse(raw); + expect(parsed).toMatchSnapshot(); + }); + } +}); diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts new file mode 100644 index 00000000..c81d8893 --- /dev/null +++ b/frontend/vitest.config.ts @@ -0,0 +1,16 @@ +import path from 'node:path'; + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + test: { + include: ['src/test/**/*.test.ts'], + environment: 'node', + globals: false, + }, +});