Improve local rules persistence and dirty-state handling
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 20s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-07-11 22:37:04 +03:00
parent 1304a22f1f
commit 387cc273e8
9 changed files with 256 additions and 38 deletions

View File

@@ -172,6 +172,7 @@ setInterval(() => {}, 60_000);
assert.deepEqual(initial.route.localRules, [
{ type: 'domain_suffix', value: 'ru', enabled: true },
]);
assert.equal(initial.route.localRulesPendingRestart, false);
assert.equal(JSON.stringify(initial).includes(subscriptionUrl), false);
const stateKeys = Object.keys(initial).sort();
let revision = initial.revision;
@@ -238,7 +239,7 @@ setInterval(() => {}, 60_000);
assert.equal((await mutation('/api/singbox/restart')).state.connection.desired, 'running');
const rulesRevision = revision;
const routed = await mutation('/api/route-rules', 'PUT', {
let routed = await mutation('/api/route-rules', 'PUT', {
expectedRevision: rulesRevision,
rules: [
{ type: 'domain_suffix', value: 'ru', enabled: false },
@@ -251,12 +252,26 @@ setInterval(() => {}, 60_000);
{ type: 'domain', value: 'example.com', enabled: true },
{ type: 'domain_suffix', value: 'example.org', enabled: true },
]);
assert.equal(routed.state.route.localRulesPendingRestart, false);
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(dir, 'sing-box-config.json'))).route.rules.slice(0, 3), [
{ domain: ['example.com'], outbound: 'direct' },
{ domain_suffix: ['example.org'], outbound: 'direct' },
{ inbound: ['mixed-in'], outbound: 'test-vpn' },
]);
await mutation('/api/singbox/stop');
routed = await mutation('/api/route-rules', 'PUT', {
expectedRevision: revision,
rules: [
...routed.state.route.localRules,
{ type: 'domain_keyword', value: 'media', enabled: true },
],
});
assert.equal(routed.state.connection.process, 'stopped');
assert.equal(routed.state.route.localRulesPendingRestart, true);
const restartedRules = await mutation('/api/singbox/restart');
assert.equal(restartedRules.state.route.localRulesPendingRestart, false);
const invalidRules = await rawRequest(port, '/api/route-rules', 'PUT', {
expectedRevision: revision,
rules: [{ type: 'domain_regex', value: '.*' }],

View File

@@ -0,0 +1,16 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
const root = path.resolve(import.meta.dirname, '../..');
const component = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.jsx'), 'utf8');
const styles = fs.readFileSync(path.join(root, 'src/web/styles.css'), 'utf8');
test('rule editor add latency stays constant and dirty exits are guarded', () => {
const rowRule = /\.client-local-rule \{([\s\S]*?)\n\}/.exec(styles)?.[1] || '';
assert.doesNotMatch(rowRule, /--rule-index|calc\(/);
assert.match(component, /addEventListener\('beforeunload', warnBeforeUnload\)/);
assert.match(component, /requestCloseLocalRules\(\)/);
assert.match(component, /localRulesPendingRestart/);
});