mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-28 16:09:36 +00:00
test(frontend): golden fixtures for DNS, Balancer, Rule schemas
Adds JSON fixtures under golden/fixtures/{dns,dns-server,balancer,rule}
plus three vitest files that parse them through the new schemas and
snapshot the result.
dns/: minimal (servers as strings) + full (every top-level field plus
hosts with geosite/domain/full prefixes and 5 mixed string/object
servers covering fakedns, localhost, https://, tcp://, quic+local://).
dns-server/: full (every DnsServerObject field) + legacy-expectips
(asserts the z.preprocess that migrates the legacy `expectIPs` key
into the canonical `expectedIPs`).
balancer/: random-minimal (default strategy by omission), roundrobin,
leastping, leastload-full (covers all StrategySettings fields and both
regexp=true|false costs).
rule/: minimal, full (exercises every RuleObject field including
localPort, localIP, process aliases like `self/`, all four protocol
enum values, ip negation `!geoip:`, attrs with regexp value, and the
WebhookObject with deduplication+headers), balancer-routed (uses
balancerTag instead of outboundTag), port-number (port as a number to
prove the union(number,string) accepts both).
This commit is contained in:
73
frontend/src/test/__snapshots__/balancer.test.ts.snap
Normal file
73
frontend/src/test/__snapshots__/balancer.test.ts.snap
Normal file
@@ -0,0 +1,73 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`BalancerObjectSchema fixtures > parses leastload-full byte-stably 1`] = `
|
||||
{
|
||||
"fallbackTag": "fallback-out",
|
||||
"selector": [
|
||||
"proxy-",
|
||||
],
|
||||
"strategy": {
|
||||
"settings": {
|
||||
"baselines": [
|
||||
"500ms",
|
||||
"1s",
|
||||
"2s",
|
||||
],
|
||||
"costs": [
|
||||
{
|
||||
"match": "proxy-premium",
|
||||
"regexp": false,
|
||||
"value": 0.1,
|
||||
},
|
||||
{
|
||||
"match": "^proxy-cheap-.+$",
|
||||
"regexp": true,
|
||||
"value": 5,
|
||||
},
|
||||
],
|
||||
"expected": 3,
|
||||
"maxRTT": "1s",
|
||||
"tolerance": 0.05,
|
||||
},
|
||||
"type": "leastLoad",
|
||||
},
|
||||
"tag": "balancer-load",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`BalancerObjectSchema fixtures > parses leastping byte-stably 1`] = `
|
||||
{
|
||||
"fallbackTag": "fallback-out",
|
||||
"selector": [
|
||||
"proxy-",
|
||||
],
|
||||
"strategy": {
|
||||
"type": "leastPing",
|
||||
},
|
||||
"tag": "balancer-ping",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`BalancerObjectSchema fixtures > parses random-minimal byte-stably 1`] = `
|
||||
{
|
||||
"selector": [
|
||||
"proxy-",
|
||||
],
|
||||
"tag": "balancer-random",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`BalancerObjectSchema fixtures > parses roundrobin byte-stably 1`] = `
|
||||
{
|
||||
"fallbackTag": "direct",
|
||||
"selector": [
|
||||
"proxy-a",
|
||||
"proxy-b",
|
||||
"proxy-c",
|
||||
],
|
||||
"strategy": {
|
||||
"type": "roundRobin",
|
||||
},
|
||||
"tag": "balancer-rr",
|
||||
}
|
||||
`;
|
||||
116
frontend/src/test/__snapshots__/dns.test.ts.snap
Normal file
116
frontend/src/test/__snapshots__/dns.test.ts.snap
Normal file
@@ -0,0 +1,116 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`DnsObjectSchema fixtures > parses full byte-stably 1`] = `
|
||||
{
|
||||
"clientIp": "1.2.3.4",
|
||||
"disableCache": false,
|
||||
"disableFallback": false,
|
||||
"disableFallbackIfMatch": true,
|
||||
"enableParallelQuery": true,
|
||||
"hosts": {
|
||||
"domain:example.com": [
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
],
|
||||
"full:dns.google": "8.8.8.8",
|
||||
"geosite:category-ads-all": "127.0.0.1",
|
||||
},
|
||||
"queryStrategy": "UseIP",
|
||||
"serveExpiredTTL": 300,
|
||||
"serveStale": true,
|
||||
"servers": [
|
||||
"fakedns",
|
||||
"localhost",
|
||||
"https://dns.google/dns-query",
|
||||
{
|
||||
"address": "tcp://1.1.1.1",
|
||||
"clientIP": "8.8.4.4",
|
||||
"domains": [
|
||||
"geosite:cn",
|
||||
],
|
||||
"expectedIPs": [
|
||||
"geoip:cn",
|
||||
],
|
||||
"port": 53,
|
||||
"queryStrategy": "UseIPv4",
|
||||
"skipFallback": true,
|
||||
"tag": "cn-dns",
|
||||
"timeoutMs": 3000,
|
||||
},
|
||||
{
|
||||
"address": "quic+local://dns.adguard.com",
|
||||
"disableCache": true,
|
||||
"finalQuery": true,
|
||||
"port": 53,
|
||||
"serveExpiredTTL": 60,
|
||||
"serveStale": false,
|
||||
"timeoutMs": 5000,
|
||||
"unexpectedIPs": [
|
||||
"geoip:private",
|
||||
],
|
||||
},
|
||||
],
|
||||
"tag": "dns_inbound",
|
||||
"useSystemHosts": true,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`DnsObjectSchema fixtures > parses minimal byte-stably 1`] = `
|
||||
{
|
||||
"disableCache": false,
|
||||
"disableFallback": false,
|
||||
"disableFallbackIfMatch": false,
|
||||
"enableParallelQuery": false,
|
||||
"queryStrategy": "UseIP",
|
||||
"serveExpiredTTL": 0,
|
||||
"serveStale": false,
|
||||
"servers": [
|
||||
"8.8.8.8",
|
||||
"1.1.1.1",
|
||||
],
|
||||
"useSystemHosts": false,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`DnsServerObjectSchema fixtures > parses full byte-stably 1`] = `
|
||||
{
|
||||
"address": "https://dns.google/dns-query",
|
||||
"clientIP": "9.9.9.9",
|
||||
"disableCache": false,
|
||||
"domains": [
|
||||
"domain:google.com",
|
||||
"domain:youtube.com",
|
||||
"geosite:google",
|
||||
],
|
||||
"expectedIPs": [
|
||||
"geoip:us",
|
||||
"1.2.3.0/24",
|
||||
],
|
||||
"finalQuery": false,
|
||||
"port": 443,
|
||||
"queryStrategy": "UseIPv6",
|
||||
"serveExpiredTTL": 600,
|
||||
"serveStale": true,
|
||||
"skipFallback": false,
|
||||
"tag": "google-doh",
|
||||
"timeoutMs": 4000,
|
||||
"unexpectedIPs": [
|
||||
"geoip:private",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`DnsServerObjectSchema fixtures > parses legacy-expectips byte-stably 1`] = `
|
||||
{
|
||||
"address": "8.8.8.8",
|
||||
"domains": [
|
||||
"geosite:cn",
|
||||
],
|
||||
"expectedIPs": [
|
||||
"geoip:cn",
|
||||
"10.0.0.0/8",
|
||||
],
|
||||
"port": 53,
|
||||
"timeoutMs": 4000,
|
||||
}
|
||||
`;
|
||||
91
frontend/src/test/__snapshots__/rule.test.ts.snap
Normal file
91
frontend/src/test/__snapshots__/rule.test.ts.snap
Normal file
@@ -0,0 +1,91 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`RuleObjectSchema fixtures > parses balancer-routed byte-stably 1`] = `
|
||||
{
|
||||
"balancerTag": "balancer-load",
|
||||
"domain": [
|
||||
"geosite:geolocation-!cn",
|
||||
],
|
||||
"ruleTag": "outbound-load-balance",
|
||||
"type": "field",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`RuleObjectSchema fixtures > parses full byte-stably 1`] = `
|
||||
{
|
||||
"attrs": {
|
||||
"Host": "example.com",
|
||||
"User-Agent": "regexp:^Mozilla.*",
|
||||
},
|
||||
"domain": [
|
||||
"domain:google.com",
|
||||
"full:example.com",
|
||||
"keyword:cdn",
|
||||
"regexp:^api\\.example\\.com$",
|
||||
"geosite:cn",
|
||||
],
|
||||
"inboundTag": [
|
||||
"inbound-1",
|
||||
"inbound-2",
|
||||
],
|
||||
"ip": [
|
||||
"10.0.0.0/8",
|
||||
"geoip:cn",
|
||||
"geoip:private",
|
||||
"!geoip:cn",
|
||||
],
|
||||
"localIP": [
|
||||
"10.10.10.0/24",
|
||||
],
|
||||
"localPort": "5353",
|
||||
"network": "tcp,udp",
|
||||
"outboundTag": "proxy-out",
|
||||
"port": "80,443,1000-2000",
|
||||
"process": [
|
||||
"chrome.exe",
|
||||
"curl",
|
||||
"self/",
|
||||
],
|
||||
"protocol": [
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"bittorrent",
|
||||
],
|
||||
"ruleTag": "main-policy-rule",
|
||||
"sourceIP": [
|
||||
"192.168.0.0/16",
|
||||
"geoip:private",
|
||||
],
|
||||
"sourcePort": "53",
|
||||
"type": "field",
|
||||
"user": [
|
||||
"user@example.com",
|
||||
"regexp:^.+@admin\\..+$",
|
||||
],
|
||||
"vlessRoute": "443,8443",
|
||||
"webhook": {
|
||||
"deduplication": 30,
|
||||
"headers": {
|
||||
"X-Auth-Token": "secret",
|
||||
},
|
||||
"url": "https://hook.example.com/events",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`RuleObjectSchema fixtures > parses minimal byte-stably 1`] = `
|
||||
{
|
||||
"outboundTag": "direct",
|
||||
"type": "field",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`RuleObjectSchema fixtures > parses port-number byte-stably 1`] = `
|
||||
{
|
||||
"network": "tcp",
|
||||
"outboundTag": "tls-out",
|
||||
"port": 443,
|
||||
"type": "field",
|
||||
}
|
||||
`;
|
||||
26
frontend/src/test/balancer.test.ts
Normal file
26
frontend/src/test/balancer.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/// <reference types="vite/client" />
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { BalancerObjectSchema } from '@/schemas/routing';
|
||||
|
||||
const fixtures = import.meta.glob<unknown>(
|
||||
'./golden/fixtures/balancer/*.json',
|
||||
{ eager: true, import: 'default' },
|
||||
);
|
||||
|
||||
function fixtureName(path: string): string {
|
||||
const file = path.split('/').pop() ?? path;
|
||||
return file.replace(/\.json$/, '');
|
||||
}
|
||||
|
||||
describe('BalancerObjectSchema fixtures', () => {
|
||||
const entries = Object.entries(fixtures).sort(([a], [b]) => a.localeCompare(b));
|
||||
expect(entries.length, 'expected at least one fixture under golden/fixtures/balancer').toBeGreaterThan(0);
|
||||
|
||||
for (const [path, raw] of entries) {
|
||||
it(`parses ${fixtureName(path)} byte-stably`, () => {
|
||||
const parsed = BalancerObjectSchema.parse(raw);
|
||||
expect(parsed).toMatchSnapshot();
|
||||
});
|
||||
}
|
||||
});
|
||||
43
frontend/src/test/dns.test.ts
Normal file
43
frontend/src/test/dns.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/// <reference types="vite/client" />
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { DnsObjectSchema, DnsServerObjectSchema } from '@/schemas/dns';
|
||||
|
||||
function fixtureName(path: string): string {
|
||||
const file = path.split('/').pop() ?? path;
|
||||
return file.replace(/\.json$/, '');
|
||||
}
|
||||
|
||||
const dnsFixtures = import.meta.glob<unknown>(
|
||||
'./golden/fixtures/dns/*.json',
|
||||
{ eager: true, import: 'default' },
|
||||
);
|
||||
|
||||
const serverFixtures = import.meta.glob<unknown>(
|
||||
'./golden/fixtures/dns-server/*.json',
|
||||
{ eager: true, import: 'default' },
|
||||
);
|
||||
|
||||
describe('DnsObjectSchema fixtures', () => {
|
||||
const entries = Object.entries(dnsFixtures).sort(([a], [b]) => a.localeCompare(b));
|
||||
expect(entries.length, 'expected at least one fixture under golden/fixtures/dns').toBeGreaterThan(0);
|
||||
|
||||
for (const [path, raw] of entries) {
|
||||
it(`parses ${fixtureName(path)} byte-stably`, () => {
|
||||
const parsed = DnsObjectSchema.parse(raw);
|
||||
expect(parsed).toMatchSnapshot();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('DnsServerObjectSchema fixtures', () => {
|
||||
const entries = Object.entries(serverFixtures).sort(([a], [b]) => a.localeCompare(b));
|
||||
expect(entries.length, 'expected at least one fixture under golden/fixtures/dns-server').toBeGreaterThan(0);
|
||||
|
||||
for (const [path, raw] of entries) {
|
||||
it(`parses ${fixtureName(path)} byte-stably`, () => {
|
||||
const parsed = DnsServerObjectSchema.parse(raw);
|
||||
expect(parsed).toMatchSnapshot();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"tag": "balancer-load",
|
||||
"selector": ["proxy-"],
|
||||
"fallbackTag": "fallback-out",
|
||||
"strategy": {
|
||||
"type": "leastLoad",
|
||||
"settings": {
|
||||
"expected": 3,
|
||||
"maxRTT": "1s",
|
||||
"tolerance": 0.05,
|
||||
"baselines": ["500ms", "1s", "2s"],
|
||||
"costs": [
|
||||
{ "regexp": false, "match": "proxy-premium", "value": 0.1 },
|
||||
{ "regexp": true, "match": "^proxy-cheap-.+$", "value": 5 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tag": "balancer-ping",
|
||||
"selector": ["proxy-"],
|
||||
"fallbackTag": "fallback-out",
|
||||
"strategy": {
|
||||
"type": "leastPing"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tag": "balancer-random",
|
||||
"selector": ["proxy-"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tag": "balancer-rr",
|
||||
"selector": ["proxy-a", "proxy-b", "proxy-c"],
|
||||
"fallbackTag": "direct",
|
||||
"strategy": {
|
||||
"type": "roundRobin"
|
||||
}
|
||||
}
|
||||
25
frontend/src/test/golden/fixtures/dns-server/full.json
Normal file
25
frontend/src/test/golden/fixtures/dns-server/full.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"address": "https://dns.google/dns-query",
|
||||
"port": 443,
|
||||
"domains": [
|
||||
"domain:google.com",
|
||||
"domain:youtube.com",
|
||||
"geosite:google"
|
||||
],
|
||||
"expectedIPs": [
|
||||
"geoip:us",
|
||||
"1.2.3.0/24"
|
||||
],
|
||||
"unexpectedIPs": [
|
||||
"geoip:private"
|
||||
],
|
||||
"skipFallback": false,
|
||||
"finalQuery": false,
|
||||
"tag": "google-doh",
|
||||
"clientIP": "9.9.9.9",
|
||||
"queryStrategy": "UseIPv6",
|
||||
"disableCache": false,
|
||||
"timeoutMs": 4000,
|
||||
"serveStale": true,
|
||||
"serveExpiredTTL": 600
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"address": "8.8.8.8",
|
||||
"port": 53,
|
||||
"domains": ["geosite:cn"],
|
||||
"expectIPs": ["geoip:cn", "10.0.0.0/8"]
|
||||
}
|
||||
42
frontend/src/test/golden/fixtures/dns/full.json
Normal file
42
frontend/src/test/golden/fixtures/dns/full.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"tag": "dns_inbound",
|
||||
"clientIp": "1.2.3.4",
|
||||
"queryStrategy": "UseIP",
|
||||
"disableCache": false,
|
||||
"disableFallback": false,
|
||||
"disableFallbackIfMatch": true,
|
||||
"enableParallelQuery": true,
|
||||
"useSystemHosts": true,
|
||||
"serveStale": true,
|
||||
"serveExpiredTTL": 300,
|
||||
"hosts": {
|
||||
"geosite:category-ads-all": "127.0.0.1",
|
||||
"domain:example.com": ["10.0.0.1", "10.0.0.2"],
|
||||
"full:dns.google": "8.8.8.8"
|
||||
},
|
||||
"servers": [
|
||||
"fakedns",
|
||||
"localhost",
|
||||
"https://dns.google/dns-query",
|
||||
{
|
||||
"address": "tcp://1.1.1.1",
|
||||
"port": 53,
|
||||
"domains": ["geosite:cn"],
|
||||
"expectedIPs": ["geoip:cn"],
|
||||
"queryStrategy": "UseIPv4",
|
||||
"skipFallback": true,
|
||||
"tag": "cn-dns",
|
||||
"clientIP": "8.8.4.4",
|
||||
"timeoutMs": 3000
|
||||
},
|
||||
{
|
||||
"address": "quic+local://dns.adguard.com",
|
||||
"unexpectedIPs": ["geoip:private"],
|
||||
"finalQuery": true,
|
||||
"disableCache": true,
|
||||
"serveStale": false,
|
||||
"serveExpiredTTL": 60,
|
||||
"timeoutMs": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
6
frontend/src/test/golden/fixtures/dns/minimal.json
Normal file
6
frontend/src/test/golden/fixtures/dns/minimal.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"servers": [
|
||||
"8.8.8.8",
|
||||
"1.1.1.1"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "field",
|
||||
"domain": ["geosite:geolocation-!cn"],
|
||||
"balancerTag": "balancer-load",
|
||||
"ruleTag": "outbound-load-balance"
|
||||
}
|
||||
60
frontend/src/test/golden/fixtures/rule/full.json
Normal file
60
frontend/src/test/golden/fixtures/rule/full.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"type": "field",
|
||||
"domain": [
|
||||
"domain:google.com",
|
||||
"full:example.com",
|
||||
"keyword:cdn",
|
||||
"regexp:^api\\.example\\.com$",
|
||||
"geosite:cn"
|
||||
],
|
||||
"ip": [
|
||||
"10.0.0.0/8",
|
||||
"geoip:cn",
|
||||
"geoip:private",
|
||||
"!geoip:cn"
|
||||
],
|
||||
"port": "80,443,1000-2000",
|
||||
"sourcePort": "53",
|
||||
"localPort": "5353",
|
||||
"network": "tcp,udp",
|
||||
"sourceIP": [
|
||||
"192.168.0.0/16",
|
||||
"geoip:private"
|
||||
],
|
||||
"localIP": [
|
||||
"10.10.10.0/24"
|
||||
],
|
||||
"user": [
|
||||
"user@example.com",
|
||||
"regexp:^.+@admin\\..+$"
|
||||
],
|
||||
"vlessRoute": "443,8443",
|
||||
"inboundTag": [
|
||||
"inbound-1",
|
||||
"inbound-2"
|
||||
],
|
||||
"protocol": [
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"bittorrent"
|
||||
],
|
||||
"attrs": {
|
||||
"User-Agent": "regexp:^Mozilla.*",
|
||||
"Host": "example.com"
|
||||
},
|
||||
"process": [
|
||||
"chrome.exe",
|
||||
"curl",
|
||||
"self/"
|
||||
],
|
||||
"outboundTag": "proxy-out",
|
||||
"ruleTag": "main-policy-rule",
|
||||
"webhook": {
|
||||
"url": "https://hook.example.com/events",
|
||||
"deduplication": 30,
|
||||
"headers": {
|
||||
"X-Auth-Token": "secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
frontend/src/test/golden/fixtures/rule/minimal.json
Normal file
4
frontend/src/test/golden/fixtures/rule/minimal.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "direct"
|
||||
}
|
||||
6
frontend/src/test/golden/fixtures/rule/port-number.json
Normal file
6
frontend/src/test/golden/fixtures/rule/port-number.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "field",
|
||||
"port": 443,
|
||||
"network": "tcp",
|
||||
"outboundTag": "tls-out"
|
||||
}
|
||||
26
frontend/src/test/rule.test.ts
Normal file
26
frontend/src/test/rule.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/// <reference types="vite/client" />
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { RuleObjectSchema } from '@/schemas/routing';
|
||||
|
||||
const fixtures = import.meta.glob<unknown>(
|
||||
'./golden/fixtures/rule/*.json',
|
||||
{ eager: true, import: 'default' },
|
||||
);
|
||||
|
||||
function fixtureName(path: string): string {
|
||||
const file = path.split('/').pop() ?? path;
|
||||
return file.replace(/\.json$/, '');
|
||||
}
|
||||
|
||||
describe('RuleObjectSchema fixtures', () => {
|
||||
const entries = Object.entries(fixtures).sort(([a], [b]) => a.localeCompare(b));
|
||||
expect(entries.length, 'expected at least one fixture under golden/fixtures/rule').toBeGreaterThan(0);
|
||||
|
||||
for (const [path, raw] of entries) {
|
||||
it(`parses ${fixtureName(path)} byte-stably`, () => {
|
||||
const parsed = RuleObjectSchema.parse(raw);
|
||||
expect(parsed).toMatchSnapshot();
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user