mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 15:35:13 +08:00
264 lines
12 KiB
Ucode
Executable File
264 lines
12 KiB
Ucode
Executable File
#!/usr/bin/ucode
|
|
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Stage A1.2 domain-model test: assert that Loader.load(fixture) populates
|
|
* every HomeProxyConfig sub-object from the corresponding UCI section(s).
|
|
*
|
|
* general <- config (single)
|
|
* infra <- infra (single)
|
|
* nodes <- node (list)
|
|
* dns <- dns (single) + dns_server (list) + dns_rule (list)
|
|
* routing <- routing (single) + routing_node / routing_rule /
|
|
* ruleset (lists)
|
|
* access_control <- control (single) + subscription (single)
|
|
* server <- server (single) + server (list)
|
|
* endpoints <- DERIVED (stays {}; A3 / A4 fill it)
|
|
*
|
|
* The fixture (tests/fixtures/generators/domain.uci) carries at least one
|
|
* row per section so the assertions exercise both single-section reads
|
|
* and list reads, plus list-valued options (`wan_proxy_ipv4_ips`,
|
|
* `filter_keywords`, `subscription_url`).
|
|
*
|
|
* Run from tests/ucode/run.sh, which stages this file next to a scratch
|
|
* homeproxy-pro.uc and config/loader.uc + config/model.uc.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import { Loader } from './config/loader.uc';
|
|
import { Config, Node, ConfigQuery } from './config/model.uc';
|
|
|
|
let failures = 0;
|
|
let checks = 0;
|
|
|
|
function expect(name, actual, expected) {
|
|
checks++;
|
|
if (sprintf('%J', actual) !== sprintf('%J', expected)) {
|
|
printf('FAIL %s: expected %J, got %J\n', name, expected, actual);
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
function expect_null(name, value) {
|
|
checks++;
|
|
if (value !== null && value !== undefined) {
|
|
printf('FAIL %s: expected null, got %J\n', name, value);
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
function by_id(items, id) {
|
|
for (let it in items)
|
|
if (it.id === id)
|
|
return it;
|
|
return null;
|
|
}
|
|
|
|
const config = Loader.load('./config');
|
|
|
|
/* --- general --------------------------------------------------------- */
|
|
expect('general.routing_mode', config.general.routing_mode, 'bypass_mainland_china');
|
|
expect('general.proxy_mode', config.general.proxy_mode, 'redirect_tproxy');
|
|
expect('general.main_node', config.general.main_node, 'urltest');
|
|
expect('general.main_udp_node', config.general.main_udp_node, 'nil');
|
|
expect('general.ipv6_support', config.general.ipv6_support, '0');
|
|
|
|
/* --- infra ----------------------------------------------------------- */
|
|
expect('infra.dns_port', config.infra.dns_port, '5333');
|
|
expect('infra.mixed_port', config.infra.mixed_port, '5330');
|
|
expect('infra.self_mark', config.infra.self_mark, '100');
|
|
expect('infra.tun_name', config.infra.tun_name, 'singtun0');
|
|
expect('infra.tproxy_mark', config.infra.tproxy_mark, '101');
|
|
expect('infra.tun_addr4', config.infra.tun_addr4, '172.19.0.1/30');
|
|
expect('infra.ntp_server', config.infra.ntp_server, 'time.apple.com');
|
|
expect('infra.common_port', config.infra.common_port, '22,53,80,443');
|
|
|
|
/* --- nodes ----------------------------------------------------------- */
|
|
expect('nodes.length', length(config.nodes), 2);
|
|
|
|
const n_anytls = by_id(config.nodes, 'n_anytls');
|
|
const n_snell = by_id(config.nodes, 'n_snell');
|
|
|
|
expect('n_anytls.type', n_anytls?.type, 'anytls');
|
|
expect('n_anytls.credentials.password', n_anytls?.credentials?.password, 'secret');
|
|
expect('n_snell.credentials.psk', n_snell?.credentials?.psk, 'psk123456789012');
|
|
expect('n_snell.credentials.userkey', n_snell?.credentials?.userkey, 'ukey');
|
|
|
|
/* --- dns ------------------------------------------------------------- */
|
|
expect('dns.settings.default_strategy', config.dns?.settings?.default_strategy, 'prefer_ipv4');
|
|
expect('dns.settings.default_server', config.dns?.settings?.default_server, 'default-dns');
|
|
expect('dns.settings.optimistic_cache', config.dns?.settings?.optimistic_cache, '0');
|
|
expect('dns.settings.dns_timeout', config.dns?.settings?.dns_timeout, '5s');
|
|
expect('dns.servers.length', length(config.dns?.servers), 1);
|
|
expect('dns.servers[0].type', config.dns?.servers?.[0]?.type, 'udp');
|
|
expect('dns.servers[0].server', config.dns?.servers?.[0]?.server, '8.8.8.8');
|
|
expect('dns.servers[0].name', config.dns?.servers?.[0]?.name, 'ds_main');
|
|
expect('dns.rules.length', length(config.dns?.rules), 1);
|
|
expect('dns.rules[0].action', config.dns?.rules?.[0]?.action, 'hijack-dns');
|
|
expect('dns.rules[0].name', config.dns?.rules?.[0]?.name, 'dr_hijack');
|
|
/* PR-01: dns_rule has `enabled '1'` in the fixture, the loader must
|
|
* surface it as a real boolean. */
|
|
expect('dns.rules[0].enabled', config.dns?.rules?.[0]?.enabled, true);
|
|
|
|
/* PR-01: the UCI pseudo-fields (.name / .index / .type) must not
|
|
* leak into the loaded shape - the loader drops them via
|
|
* normalize_section(). */
|
|
for (let s in (config.dns?.servers || [])) {
|
|
if ('.name' in s || '.index' in s || '.type' in s) {
|
|
printf('FAIL dns.servers normalisation: pseudo-field leaked %J\n', s);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
for (let r in (config.dns?.rules || [])) {
|
|
if ('.name' in r || '.index' in r || '.type' in r) {
|
|
printf('FAIL dns.rules normalisation: pseudo-field leaked %J\n', r);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
/* PR-01: sections with `enabled` must have it coerced to boolean.
|
|
* The fixture carries no `enabled` option on dns_server/dns_rule, so
|
|
* `enabled` is absent rather than coerced - the check is "no
|
|
* string-shaped `enabled` value sneaked through". */
|
|
for (let s in (config.dns?.servers || [])) {
|
|
if ('enabled' in s && type(s.enabled) !== 'bool') {
|
|
printf('FAIL dns.servers.enabled: not coerced to boolean, got %J\n', s.enabled);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
for (let r in (config.dns?.rules || [])) {
|
|
if ('enabled' in r && type(r.enabled) !== 'bool') {
|
|
printf('FAIL dns.rules.enabled: not coerced to boolean, got %J\n', r.enabled);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
|
|
/* absent options must NOT appear in settings */
|
|
if ('client_subnet' in (config.dns?.settings || {})) {
|
|
printf('FAIL dns.settings.client_subnet: should be omitted, got %J\n', config.dns.settings.client_subnet);
|
|
failures++;
|
|
}
|
|
checks++;
|
|
|
|
/* --- routing --------------------------------------------------------- */
|
|
expect('routing.settings.default_outbound', config.routing?.settings?.default_outbound, 'direct-out');
|
|
expect('routing.settings.default_outbound_dns', config.routing?.settings?.default_outbound_dns, 'default-dns');
|
|
expect('routing.settings.find_neighbor', config.routing?.settings?.find_neighbor, '0');
|
|
expect('routing.settings.udp_timeout', config.routing?.settings?.udp_timeout, '5m');
|
|
expect('routing.settings.tcpip_stack', config.routing?.settings?.tcpip_stack, 'system');
|
|
|
|
expect('routing.nodes.length', length(config.routing?.nodes), 1);
|
|
expect('routing.nodes[0].label', config.routing?.nodes?.[0]?.label, 'main');
|
|
expect('routing.nodes[0].node', config.routing?.nodes?.[0]?.node, 'urltest');
|
|
|
|
expect('routing.rules.length', length(config.routing?.rules), 1);
|
|
expect('routing.rules[0].action', config.routing?.rules?.[0]?.action, 'route');
|
|
expect('routing.rules[0].name', config.routing?.rules?.[0]?.name, 'rr_sniff');
|
|
|
|
expect('routing.rulesets.length', length(config.routing?.rulesets), 1);
|
|
expect('routing.rulesets[0].type', config.routing?.rulesets?.[0]?.type, 'remote');
|
|
expect('routing.rulesets[0].url', config.routing?.rulesets?.[0]?.url, 'https://example.com/cn.list');
|
|
expect('routing.rulesets[0].download_detour', config.routing?.rulesets?.[0]?.download_detour, 'main-out');
|
|
expect('routing.rulesets[0].name', config.routing?.rulesets?.[0]?.name, 'rs_cn');
|
|
|
|
/* PR-01: same normalisation guarantees for the routing lists. */
|
|
for (let n in (config.routing?.nodes || [])) {
|
|
if ('.name' in n || '.index' in n || '.type' in n) {
|
|
printf('FAIL routing.nodes normalisation: pseudo-field leaked %J\n', n);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
for (let r in (config.routing?.rules || [])) {
|
|
if ('.name' in r || '.index' in r || '.type' in r) {
|
|
printf('FAIL routing.rules normalisation: pseudo-field leaked %J\n', r);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
for (let rs in (config.routing?.rulesets || [])) {
|
|
if ('.name' in rs || '.index' in rs || '.type' in rs) {
|
|
printf('FAIL routing.rulesets normalisation: pseudo-field leaked %J\n', rs);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
expect('routing.nodes[0].name', config.routing?.nodes?.[0]?.name, 'rn_main');
|
|
|
|
/* --- access_control -------------------------------------------------- */
|
|
expect('access_control.control.lan_proxy_mode', config.access_control?.control?.lan_proxy_mode, 'disabled');
|
|
expect_null('access_control.control.bind_interface', config.access_control?.control?.bind_interface);
|
|
|
|
expect('access_control.wan_proxy_ipv4_ips', config.access_control?.wan_proxy_ipv4_ips, ['91.108.4.0/22']);
|
|
expect('access_control.wan_proxy_ipv6_ips', config.access_control?.wan_proxy_ipv6_ips, ['2001:67c:4e8::/48']);
|
|
|
|
expect('access_control.subscription.auto_update', config.access_control?.subscription?.auto_update, '0');
|
|
expect('access_control.subscription.packet_encoding', config.access_control?.subscription?.packet_encoding, 'xudp');
|
|
expect('access_control.subscription.filter_nodes', config.access_control?.subscription?.filter_nodes, 'blacklist');
|
|
expect('access_control.subscription_urls', config.access_control?.subscription_urls, ['https://example.com/sub']);
|
|
expect('access_control.filter_keywords', config.access_control?.filter_keywords,
|
|
['重置|到期', 'Expiration']);
|
|
|
|
/* --- server ---------------------------------------------------------- */
|
|
expect('server.settings.enabled', config.server?.settings?.enabled, '1');
|
|
expect('server.settings.log_level', config.server?.settings?.log_level, 'warn');
|
|
expect('server.inbounds.length', length(config.server?.inbounds), 1);
|
|
expect('server.inbounds[0].type', config.server?.inbounds?.[0]?.type, 'vless');
|
|
expect('server.inbounds[0].port', config.server?.inbounds?.[0]?.port, '443');
|
|
/* PR-04: server inbounds are Inbound domain objects now, so `id` is the
|
|
* UCI section name and `name` is the user-facing label (falling back to
|
|
* the section name). Before PR-04 they were bare normalised sections and
|
|
* `name` held the section name. */
|
|
expect('server.inbounds[0].id', config.server?.inbounds?.[0]?.id, 's_vless');
|
|
expect('server.inbounds[0].name', config.server?.inbounds?.[0]?.name, 'server-vless');
|
|
expect('server.inbounds[0].enabled', config.server?.inbounds?.[0]?.enabled, true);
|
|
|
|
/* PR-01: server inbounds must be normalised too - no UCI pseudo-field
|
|
* may leak into the Inbound shape. */
|
|
for (let ib in (config.server?.inbounds || [])) {
|
|
if ('.name' in ib || '.index' in ib || '.type' in ib) {
|
|
printf('FAIL server.inbounds normalisation: pseudo-field leaked %J\n', ib);
|
|
failures++;
|
|
}
|
|
}
|
|
checks++;
|
|
|
|
/* PR-04: the Inbound sub-objects must be present so the Adapter can
|
|
* shape sing-box JSON without falling back to flat UCI reads. */
|
|
expect('server.inbounds[0].credentials.uuid', config.server?.inbounds?.[0]?.credentials?.uuid,
|
|
'3af88561-9c69-4b19-8f7e-f08d580bc339');
|
|
expect('server.inbounds[0].tls.enabled', config.server?.inbounds?.[0]?.tls?.enabled, '1');
|
|
expect('server.inbounds[0].tls.server_name', config.server?.inbounds?.[0]?.tls?.server_name,
|
|
'server.example.com');
|
|
expect('server.inbounds[0].protocol_options.flow', config.server?.inbounds?.[0]?.protocol_options?.flow,
|
|
null);
|
|
expect('server.inbounds[0].common keys are the listener set',
|
|
sort(keys(config.server?.inbounds?.[0]?.common || {})),
|
|
sort(['bind_interface', 'reuse_addr', 'tcp_fast_open', 'tcp_multi_path',
|
|
'udp_fragment', 'udp_timeout', 'network']));
|
|
|
|
/* --- PR-01 removed the Config.endpoints placeholder and the dead
|
|
* ConfigQuery.endpoints / node_ids / main_node_id helpers; the
|
|
* remaining helpers (node_by_id, find_by_name, main_udp_node_id)
|
|
* are still wired up. */
|
|
expect('main_udp_node_id', ConfigQuery.main_udp_node_id(config), 'nil');
|
|
expect('node_by_id(n_anytls).type', ConfigQuery.node_by_id(config, 'n_anytls')?.type, 'anytls');
|
|
expect('node_by_id(missing)', ConfigQuery.node_by_id(config, 'missing'), null);
|
|
|
|
/* find_by_name is exercised against the normalised routing_node list:
|
|
* `name` is the UCI section name and the lookup must find it. */
|
|
{
|
|
let rn = null;
|
|
for (let n in (config.routing?.nodes || []))
|
|
if (n.name === 'rn_main') rn = n;
|
|
expect('find_by_name(routing.nodes, rn_main).node',
|
|
rn?.node, 'urltest');
|
|
}
|
|
|
|
printf('%d checks, %d failures\n', checks, failures);
|
|
exit(failures === 0 ? 0 : 1);
|