Files
RustProxy/tools/checks/panel_cache_http_0310_ui_test.mjs
T
2026-10-11 17:10:04 +02:00

122 lines
6.8 KiB
JavaScript

#!/usr/bin/env node
// Real browser form callbacks with fake DOM; protocol/security checks are
// source-level assertions only. Rust unit and live HTTP tests need Cargo.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { runInNewContext } from 'node:vm';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const read = path => readFileSync(resolve(root, path), 'utf8');
const html = read('src/admin/web/index.html');
const css = read('src/admin/web/style.css');
const administration = read('src/admin/web/settings.js');
const cache = read('src/admin/web/app/settings.js');
const router = read('src/admin/router.rs');
const assets = read('src/admin/panel_assets.rs');
const compression = read('src/admin/panel_content.rs');
const ip = read('src/cache/model/http_invalidation.rs');
const cacheMethods = read('src/proxy/cache_http_invalidation.rs');
for (const codec of ['zstd', 'br', 'gzip']) {
assert.match(html, new RegExp(`id="admin-compression-${codec}"[^>]*checked`));
assert(assets.includes('"/assets/app.js"'));
}
for (const path of ['app.js','management.js','vhosts.js','settings.js','cache-panel.js','traffic-panel.js','style.css','langs.json']) {
if (path === "langs.json") assert(router.includes(`"/assets/${path}", get(panel_assets::serve)`), path);
else assert(router.includes('"/assets/{fingerprinted}"'), path);
assert(assets.includes(`"/assets/${path}"`), path);
}
assert(compression.includes('preferred_encoding(headers, &settings)'));
assert(compression.includes('HeaderValue::from_static("Accept-Encoding")'));
assert(compression.includes('header::CONTENT_LENGTH'));
assert(compression.includes('header::CONTENT_ENCODING'));
assert(compression.includes('crate::compression::no_transform(headers)'));
assert(html.includes('id="cache-http-invalidation-cidrs"'));
assert(html.includes('class="metric-info cache-maintenance-info"'));
assert(html.includes('data-i18n-aria="cacheHttpInvalidationCurlLabel"'));
for (const snippet of ["curl -i -X PURGE", "curl -i -X BAN", "X-RustProxy-Confirm-Wide: 1"]) {
assert(html.includes(snippet), snippet);
}
assert(css.includes('.cache-maintenance-info .metric-info-popover'));
assert(cache.includes('http_invalidation_allowed_cidrs: $("cache-http-invalidation-cidrs")'));
assert(ip.includes('http_invalidation_allowed_cidrs.len() <= 32'));
assert(ip.includes('net.prefix_len() > 0'));
assert(cacheMethods.includes('snapshot.envelope.config.cache.permits_http_invalidation(client_ip)'));
assert(cacheMethods.includes('state.store.audit('));
assert(cacheMethods.includes('spawn_blocking'));
assert(cacheMethods.includes('x-rustproxy-confirm-wide'));
assert(cacheMethods.includes('InvalidationSelector::Exact'));
assert(cacheMethods.includes('InvalidationSelector::Prefix'));
assert(read('src/proxy/forward_entry.rs').includes('return cache_http_invalidation('));
const heading = html.lastIndexOf('data-i18n="systemInformation"');
assert(heading > 0);
const headerArea = html.slice(heading, heading + 600);
assert(headerArea.includes('<span class="pill">API</span>'));
assert(headerArea.indexOf('<span class="pill">API</span>') < headerArea.indexOf('id="system-info-refresh"'));
assert.match(css, /\.system-info-heading-actions\s*\{[^}]*display:\s*flex/);
for (const lang of ['en_US','pl_pl']) {
const strings = JSON.parse(read(`langs/${lang}.json`));
const t = strings.translations;
assert(t.panelCompressionTitle && t.panelCompressionHelp && t.cacheHttpInvalidationHelp, lang);
assert(t.cacheHttpInvalidationCurlLabel && t.cacheHttpInvalidationCurlIntro && t.cacheHttpInvalidationCurlNote, lang);
}
// Execute the actual callbacks captured from the shipped frontend settings JS.
const fields = new Map();
const field = name => {
if (!fields.has(name)) fields.set(name, { checked: false, value: '' });
return fields.get(name);
};
for (const name of ['zstd', 'br', 'gzip']) field(`admin-compression-${name}`).checked = true;
field('admin-listen').value = '127.0.0.1:9090';
field('admin-session-days').value = '30';
let handlers = new Map();
const bindSettingsForm = (id, handler) => handlers.set(id, handler);
const adminStart = administration.indexOf('bindSettingsForm("administration-form", next => {');
const adminEnd = administration.indexOf('bindSettingsForm("tls-policy-form",', adminStart);
assert(adminStart > 0 && adminEnd > adminStart);
// Use the shipped helper in the extracted callback environment as well.
const hostHelperStart = administration.indexOf(' function pinnedAdminHostname(value) {');
const hostHelperEnd = administration.indexOf(' const normalizedRequestedTab', hostHelperStart);
assert(hostHelperStart > 0 && hostHelperEnd > hostHelperStart);
runInNewContext(administration.slice(hostHelperStart, hostHelperEnd) + administration.slice(adminStart, adminEnd), {
$: field, bindSettingsForm, latestStatus: { administration: { listen_locked: false } }, URL,
});
const adminNext = {};
handlers.get('administration-form')(adminNext);
assert.deepEqual(JSON.parse(JSON.stringify(adminNext.administration.panel_compression)),
{ zstd: true, br: true, gzip: true });
field('admin-public-origin').value = 'https://panel.example.test';
field('admin-hosts').value = 'panel.example.test\nother.example.test';
const pinnedNext = {};
handlers.get('administration-form')(pinnedNext);
assert.deepEqual(JSON.parse(JSON.stringify(pinnedNext.administration.allowed_hosts)), ['other.example.test']);
field('admin-compression-br').checked = false;
field('admin-compression-zstd').checked = false;
const offNext = {};
handlers.get('administration-form')(offNext);
assert.deepEqual(JSON.parse(JSON.stringify(offNext.administration.panel_compression)),
{ zstd: false, br: false, gzip: true });
const cacheStart = cache.indexOf('bindSettingsForm("cache-form", next => {');
const cacheEnd = cache.indexOf('bindSettingsForm("transform-form",', cacheStart);
assert(cacheStart > 0 && cacheEnd > cacheStart);
handlers = new Map();
field('cache-http-invalidation-cidrs').value = ' 192.0.2.50\n 2001:db8:2::/64, 198.51.100.9 \n';
field('cache-sie-statuses').value = '500,502,503,504';
runInNewContext(cache.slice(cacheStart, cacheEnd), {
$: field, bindSettingsForm, selectedCacheMemoryPreset: null, selectedCacheColdPreset: null,
});
const cacheNext = { cache: { max_old_setting: 'kept' } };
handlers.get('cache-form')(cacheNext);
assert.deepEqual(JSON.parse(JSON.stringify(cacheNext.cache.http_invalidation_allowed_cidrs)),
['192.0.2.50', '2001:db8:2::/64', '198.51.100.9']);
assert.equal(cacheNext.cache.max_old_setting, 'kept');
field('cache-http-invalidation-cidrs').value = '\n';
handlers.get('cache-form')(cacheNext);
assert.deepEqual(JSON.parse(JSON.stringify(cacheNext.cache.http_invalidation_allowed_cidrs)), []);
console.log('PASS: cache CIDR/administration codec UI roundtrip, headers, security source wiring, API badge layout');