1195 lines
91 KiB
Python
Executable File
1195 lines
91 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Static package checks only. Does not compile or validate Rust types/behavior."""
|
||
from pathlib import Path
|
||
import ast
|
||
import json
|
||
import re
|
||
import sqlite3
|
||
import subprocess
|
||
import tomllib
|
||
from preflight import check as preflight
|
||
|
||
root = Path(__file__).resolve().parents[1]
|
||
|
||
def read_cache_source() -> str:
|
||
return "\n".join(path.read_text() for path in sorted((root / "src/cache").glob("*.rs")))
|
||
|
||
preflight(root)
|
||
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
||
assert manifest["package"]["version"] == "0.25.0"
|
||
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
||
h3dep = manifest["dependencies"]["h3"]
|
||
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
|
||
assert "h3-quinn" not in manifest["dependencies"]
|
||
lock_path = root / "Cargo.lock"
|
||
if lock_path.exists():
|
||
lock = lock_path.read_text()
|
||
assert 'name = "rust-proxy"\nversion = "0.25.0"' in lock
|
||
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
|
||
for release_doc, marker in {
|
||
"README.md": "# RustProxy 0.25.0",
|
||
"CHANGELOG.md": "## 0.25.0",
|
||
"ROADMAP.md": "## 0.25.0",
|
||
"AGENTS.md": "Current source version: 0.25.0.",
|
||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.25.0",
|
||
}.items():
|
||
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
|
||
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
|
||
update_defaults = tomllib.loads((root / "UPDATE_CHECK.toml").read_text())
|
||
assert update_defaults == {
|
||
"repository_url": "https://git.linuxiarz.pl/gru/RustProxy",
|
||
"version_url": "https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/Cargo.toml",
|
||
"interval_seconds": 3600,
|
||
}
|
||
update_check_source = (root / "src/update_check.rs").read_text()
|
||
for marker in [
|
||
'include_str!("../UPDATE_CHECK.toml")',
|
||
'pub async fn poll_loop',
|
||
'tokio::time::sleep(interval)',
|
||
'Cargo.toml [package] version was not found',
|
||
'MAX_VERSION_DOCUMENT_BYTES',
|
||
'update check failed; retrying on the next interval',
|
||
]:
|
||
assert marker in update_check_source, marker
|
||
assert 'update version document is too large or incomplete: {error}' in update_check_source
|
||
assert '.context("update version document is too large or incomplete")' not in update_check_source
|
||
for marker in [
|
||
'tracing::error!(',
|
||
'MAX_REDIRECTS: usize = 5',
|
||
'SSL_CERT_FILE',
|
||
'CURL_CA_BUNDLE',
|
||
'/usr/local/share/certs/ca-root-nss.crt',
|
||
'response.status().is_redirection()',
|
||
'redirect_target(¤t, location)',
|
||
'update version redirect must not downgrade HTTPS to HTTP',
|
||
]:
|
||
assert marker in update_check_source, marker
|
||
assert 'pub last_error:' not in update_check_source, "update-check errors must not change dashboard/API state"
|
||
for name in ["README.md", "ROADMAP.md", "AGENTS.md", "CHANGELOG.md", "UPDATE_CHECK.toml", "Dockerfile", ".dockerignore", "docs/TEST_REPORT.md", "docs/V0.24.8_AUDIT.md", "docs/V0.24.7_AUDIT.md", "docs/V0.24.6_AUDIT.md", "docs/V0.24.5_AUDIT.md", "docs/V0.24.1_AUDIT.md", "docs/V0.24.0_AUDIT.md", "docs/V0.23.2_AUDIT.md", "docs/V0.23.1_AUDIT.md", "docs/V0.22.3_AUDIT.md", "docs/LIVE_LOGS.md", "docs/README.md", "docs/API.md", "docs/ARCHITECTURE.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/VHOSTS.md", "docs/STREAMS.md", "docs/REDIRECTS.md", "docs/CONTAINER.md", "docs/NPM_MIGRATION.md", "docs/DOCKER_DISCOVERY.md", "docker-agent/main.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/docker-agent.env.example", "docker-agent/install.sh", "src/docker.rs", "src/admin/web/docker.js", "scripts/migrate_npm.py", "docs/CUSTOM_LOCATIONS.md", "nagios/README.md", "nagios/check_rustproxy.py", "nagios/check_rustproxy_api", "nagios/check_rustproxy_backends", "nagios/check_rustproxy_latency", "nagios/check_rustproxy_queues", "src/admin/web/error_401.html", "src/admin/web/error_404.html", "src/admin/web/streams.js", "src/admin/web/redirects.js", "src/admin/web/host404.js", "docs/NOT_FOUND_HOSTS.md", "debian/control", "debian/rules", "debian/rustproxy.service", "debian/rustproxy.logrotate", "debian/build-package.sh", "debian/build-repository.sh", "grafana/rustproxy-overview.json", "grafana/README.md"]:
|
||
assert (root / name).is_file(), name
|
||
for path in (root / "examples").glob("*.json"):
|
||
data = json.loads(path.read_text())
|
||
assert isinstance(data, dict), path
|
||
assert isinstance(data.get("vhosts"), list), path
|
||
assert "backends" not in data and "routes" not in data, path
|
||
for key in ("backends", "routes", "vhosts"):
|
||
assert isinstance(data.get(key, []), list), (path, key)
|
||
for path in (root / "src").rglob("*.rs"):
|
||
source = path.read_text()
|
||
for target in re.findall(r'include_str!\("([^"]+)"\)', source):
|
||
assert (path.parent / target).is_file(), (path, target)
|
||
lines = source.splitlines()
|
||
for index in range(len(lines) - 1):
|
||
assert not (lines[index].lstrip().startswith("#[derive(") and lines[index + 1].lstrip().startswith("#[derive(")), f"consecutive derive attributes in {path}:{index + 1}"
|
||
html = (root / "src/admin/web/index.html").read_text()
|
||
js = "\n".join(p.read_text() for p in (root / "src/admin/web").glob("*.js") if p.name not in ("login.js", "docs.js"))
|
||
ids = re.findall(r'id="([^"]+)"', html)
|
||
assert len(ids) == len(set(ids)), "duplicate HTML IDs"
|
||
for identifier in set(re.findall(r'\$\("([^"]+)"\)', js)):
|
||
assert identifier in ids, f"unknown frontend ID: {identifier}"
|
||
for path in [root / "make_zip.py", *(root / "scripts").glob("*.py")]:
|
||
ast.parse(path.read_text(), str(path))
|
||
subprocess.run(["bash", "-n", str(root / "dev.sh")], check=True)
|
||
subprocess.run(["bash", "-n", str(root / "upgrade.sh")], check=True)
|
||
for name in ["install.sh", "install-debian13.sh", "update.sh", "debian/build-package.sh", "debian/build-repository.sh", "debian/rustproxy.postinst", "nagios/check_rustproxy_api", "nagios/check_rustproxy_backends", "nagios/check_rustproxy_latency", "nagios/check_rustproxy_queues", "docker-agent/install.sh"]:
|
||
subprocess.run(["bash", "-n", str(root / name)], check=True)
|
||
bootstrap = (root / "install-debian13.sh").read_text()
|
||
for marker in [
|
||
'MODE="${1:-auto}"',
|
||
'auto|install|update',
|
||
'MODE=update',
|
||
'bash update.sh',
|
||
'--prefix "$PREFIX"',
|
||
'python3-certbot-dns-rfc2136',
|
||
'python3-certbot-dns-cloudflare',
|
||
]:
|
||
assert marker in bootstrap, marker
|
||
assert 'curl ' not in bootstrap, 'Debian bootstrap must use wget instead of curl'
|
||
dev = (root / "dev.sh").read_text()
|
||
assert 'cargo generate-lockfile' in dev and r'version = "0\.9\.0"' not in dev
|
||
assert dev.count('cargo generate-lockfile') == 1, "dev.sh must only create Cargo.lock when it is missing"
|
||
unit = (root / "deploy/rustproxy.service").read_text()
|
||
assert "LogsDirectory=rustproxy" in unit and "/var/log/rustproxy" in unit
|
||
assert "Environment=MALLOC_ARENA_MAX=2" in unit
|
||
assert "Environment=MALLOC_ARENA_MAX=2" in (root / "debian/rustproxy.service").read_text()
|
||
assert "Environment=MALLOC_ARENA_MAX=2" in (root / "scripts/deploy.py").read_text()
|
||
assert not re.search(r"\b20\d{2}[-/]\d{1,2}[-/]\d{1,2}\b", (root / "CHANGELOG.md").read_text()), "changelog contains a release date"
|
||
subprocess.run(["python3", str(root / "scripts/langs_check.py")], check=True)
|
||
subprocess.run(["python3", str(root / "scripts/web_server_contracts.py")], check=True)
|
||
dockerfile = (root / "Dockerfile").read_text()
|
||
assert "cargo generate-lockfile" in dockerfile and "cargo build --locked --release" in dockerfile
|
||
assert "USER rustproxy:rustproxy" in dockerfile
|
||
assert 'VOLUME ["/var/lib/rustproxy"]' in dockerfile
|
||
assert "COPY . ." in dockerfile
|
||
assert "data" in (root / ".dockerignore").read_text().splitlines()
|
||
css = (root / "src/admin/web/style.css").read_text()
|
||
assert ".logging-grid fieldset" not in css, "logging sinks must reuse shared card styling"
|
||
assert ".vhost-chart-line" in css and ".host-chip-editor" in css
|
||
assert 'class="vhost-dialog logging-format-dialog"' in html
|
||
assert 'data-panel="streams"' in html and 'id="panel-streams"' in html
|
||
assert 'data-panel="docker-vhosts"' in html and 'id="panel-docker-vhosts"' in html
|
||
assert 'id="settings-tab-docker-agents"' in html and 'id="settings-page-docker-agents"' in html
|
||
assert '/assets/docker.js' in html and 'cache-live-toggle' in html
|
||
assert 'expose-revision' not in html and 'hideRevision' not in js and 'diagRevision' not in js
|
||
for path in ['src/settings.rs', 'src/admin/mod.rs', 'src/cache/mod.rs', 'src/headers.rs', 'src/tinyauth.rs', 'src/admin/web/vhosts.js']:
|
||
assert 'x-rust-proxy-revision' not in (root / path).read_text().lower(), f'legacy revision-header logic remains in {path}'
|
||
assert './dev.sh admin reset-password' not in html and re.search(r'rust-proxy\s+admin reset-password locally', html)
|
||
assert 'data-panel="redirects"' in html and 'id="panel-redirects"' in html
|
||
assert 'data-panel="configuration"' not in html and 'id="panel-configuration"' not in html
|
||
assert 'id="settings-tab-manual-config"' in html and 'id="settings-page-manual-config"' in html
|
||
assert 'id="security-audit-open"' in html and 'id="audit-dialog"' in html and 'id="audit-dialog-close"' in html
|
||
assert 'id="security-password-editor"' in html and 'id="security-policy-editor"' in html
|
||
assert 'docker-service-editor' in (root / "src/admin/web/docker.js").read_text() and 'docker-agent-details' in (root / "src/admin/web/docker.js").read_text()
|
||
balance_source = (root / "src/balance.rs").read_text()
|
||
assert 'mode: HealthMode::Tcp' in balance_source
|
||
for marker in [
|
||
'pub struct PassiveHealth', 'enabled: false', 'pub passive_health: PassiveHealth',
|
||
'passive_health_failures_total', 'http_5xx_total', 'timeouts_total',
|
||
'connect_errors_total', 'io_errors_total', 'slow_responses_total',
|
||
'passive_health_is_opt_in_and_uses_separate_thresholds',
|
||
'unhealthy_source_hands_off_when_the_original_health_domain_recovers',
|
||
]:
|
||
assert marker in balance_source, marker
|
||
assert 'id="config-editor"' in html and 'id="save-json"' in html and 'id="export-config"' in html
|
||
assert 'toast(t("saved"));' in js and 'toast(t("saved") + envelope.revision);' not in js
|
||
for lang_name in ["en_US.json", "pl_pl.json"]:
|
||
translations = json.loads((root / "langs" / lang_name).read_text())["translations"]
|
||
assert translations["saved"] in {"Configuration applied.", "Zastosowano konfigurację."}
|
||
assert "revision" not in translations["saved"].lower() and "rewizj" not in translations["saved"].lower()
|
||
assert html.index('data-panel="vhosts"') < html.index('data-panel="redirects"') < html.index('data-panel="notfound"') < html.index('data-panel="streams"') < html.index('data-panel="acl-profiles"')
|
||
assert 'id="stream-dialog"' in html and 'id="stream-list"' in html and 'id="stream-search"' in html
|
||
assert 'id="settings-page-streams"' not in html and 'id="streams-form"' not in html
|
||
assert '/assets/streams.js' in html and '/assets/redirects.js' in html and '/assets/host404.js' in html
|
||
vhosts_js = (root / "src/admin/web/vhosts.js").read_text()
|
||
assert '"force-ssl", "vhForceSsl"' in vhosts_js and 'force_ssl: yes("force-ssl")' in vhosts_js
|
||
assert 'vhForceSslNeedsHttp' in vhosts_js and 'tlsPorts.has(443)' in vhosts_js
|
||
assert '"forward-client-ip", "vhForwardClientIp"' in vhosts_js and 'forward_client_ip: yes("forward-client-ip")' in vhosts_js
|
||
assert 'id="vhost-search"' in html and '$("vhost-search").addEventListener("input", render)' in vhosts_js
|
||
assert 'id="admin-session-days"' in html and 'min="7" max="30"' in html
|
||
assert 'member-local-risk' in vhosts_js and 'vhLocalListenerRiskConfirm' in vhosts_js
|
||
assert 'accept_local_listener_risk: yes("member-local-risk", row)' in vhosts_js
|
||
assert 'function administrationListenCandidates()' in vhosts_js and 'function adminListenerBackendCandidate(raw)' in vhosts_js
|
||
for marker in ['latestStatus?.administration?.locked_listen', 'latestStatus?.administration?.active?.listen', 'latestStatus?.admin_listen', "value === 'localhost'", '/^127(?:\\.\\d{1,3}){3}$/.test(value)', 'riskGate.hidden = show !== true', 'row.syncLocalRisk = () => syncRisk(false)']:
|
||
assert marker in vhosts_js, marker
|
||
assert 'const saved = editing ? (envelope?.config.vhosts || []).find(item => item.id === editing) : null;' in vhosts_js
|
||
assert 'load(saved ? structuredClone(saved) : defaults());' in vhosts_js
|
||
assert 'backends: [{ id: "origin-1", url: ""' in vhosts_js, "new backend drafts must require an explicit origin URL"
|
||
assert 'preserve_host: true' in vhosts_js, "new ordinary HTTP/1.1 backends must preserve public Host by default"
|
||
for marker in ['vhBackendDiagnostics', 'backend_header: yes("backend-header")', 'backend_timing_header: yes("backend-timing-header")', 'protocol-toggle-row', 'auth-subcard', 'vh-cache-options', 'syncCacheVisibility']:
|
||
assert marker in vhosts_js, marker
|
||
assert 'cache: { enabled: false }' in vhosts_js, "new vhost drafts must default cache off"
|
||
docker_js = (root / "src/admin/web/docker.js").read_text()
|
||
assert 'preserve.checked = assignment?.preserve_host !== false' in docker_js, "new Docker backends must preserve public Host by default"
|
||
for marker in ['saveSection("docker-agents"', '/api/docker/refresh', 'docker-publish', 'docker-hostname', 'host-chip-editor docker-alias-editor', 'docker-target-ip', 'default_target_ip', 'docker-agent-fingerprint', 'docker-agent-certificate', 'docker-health-mode', 'docker-passive-enabled', 'docker-passive-5xx', 'docker-search', 'docker-show-hidden', 'dockerHideVhost', 'pool_routes', 'setInterval']:
|
||
assert marker in docker_js, marker
|
||
docker_source = (root / "src/docker.rs").read_text()
|
||
for marker in ['pub struct Settings', 'pub struct Observation', 'pub struct HostAddress', 'pub struct Binding', 'aliases: Vec<String>', 'target_ip: Option<IpAddr>', 'tls_fingerprint_sha256', 'certificate_id: Option<String>', 'health_check: crate::balance::HealthCheck', 'passive_health: crate::balance::PassiveHealth', 'PinnedCertificateVerifier', 'fingerprint mismatch', 'effective_config', 'poll_loop', 'address_allowlist']:
|
||
assert marker in docker_source, marker
|
||
agent_source = (root / "docker-agent/main.go").read_text()
|
||
for marker in ['/v1/inventory', 'Authorization', '/containers/json?all=0', 'IsLoopback', 'localHostAddresses', 'detectedAddresses', 'Bindings', 'RUSTPROXY_DOCKER_AGENT_TOKEN', 'ensureSelfSignedCertificate', 'ListenAndServeTLS', 'RUSTPROXY_DOCKER_AGENT_TLS', 'CertificateSHA256']:
|
||
assert marker in agent_source, marker
|
||
assert 'pool-vhost-disabled' in vhosts_js and '.pool-vhost-disabled' in css
|
||
streams_js = (root / "src/admin/web/streams.js").read_text()
|
||
redirects_js = (root / "src/admin/web/redirects.js").read_text()
|
||
for marker in ["rustProxyRedirects", "redirectTargetType", "status_code", "cache_seconds", 'saveSection("redirects"']:
|
||
assert marker in redirects_js, marker
|
||
for marker in ["rustProxyStreams", "streamTrafficChart", "streamPendingRestart", "stream-listener-add", "chartHistory", "sessions_total", "bytes_in", "bytes_out"]:
|
||
assert marker in streams_js, marker
|
||
integrations_js = (root / "src/admin/web/integrations.js").read_text()
|
||
assert "addressTagEditor" in integrations_js and "listener-address-chip-editor" in integrations_js
|
||
assert "stream-host-rows" not in integrations_js
|
||
assert 'const http80 = prefer(false, 80);' in integrations_js
|
||
assert 'if (http80) autoIds.add(http80.id);' in integrations_js and 'candidates.find(endpoint => !endpoint.binding?.tls)' in integrations_js
|
||
assert 'https443' not in integrations_js
|
||
assert 'window.rustProxyIntegrations.bindings(holder, selected, !editing)' in redirects_js
|
||
assert 'window.rustProxyIntegrations.hostnames(hostHolder' in redirects_js
|
||
assert 'id="redirect-hosts-editor"' in html
|
||
assert 'class="protocol-toggle-row"' in html
|
||
for marker in ['.vh-backend-card', '.auth-subcard', '.vh-cache-options[hidden]', '.protocol-toggle-row', '.listener-choices']:
|
||
assert marker in css, marker
|
||
openapi = (root / "src/admin/openapi.json").read_text()
|
||
assert '"load_balancing"' in openapi and '"LoadBalancingConfig"' in openapi and '"PassiveHealthConfig"' in openapi
|
||
assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disabled.' in openapi
|
||
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
||
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
|
||
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
|
||
for marker in ['"version": "0.25.0"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||
assert marker in openapi, marker
|
||
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||
assert marker in openapi, marker
|
||
assert 'id="panel-notfound"' in html and 'id="notfound-dialog"' in html and 'id="notfound-hosts-editor"' in html
|
||
host404_js = (root / "src/admin/web/host404.js").read_text()
|
||
for marker in ['rustProxyNotFound', 'saveSection("not_found_hosts"', 'rustProxyIntegrations.hostnames', 'rustProxyIntegrations.bindings', 'BUILTIN_STATUSES', 'ERROR_STATUSES']:
|
||
assert marker in host404_js, marker
|
||
assert 'for (let code = 200; code <= 599; code++)' not in host404_js
|
||
assert 'preserve_host: true' in vhosts_js, "new HTTP/1.1 backend drafts must preserve the public Host"
|
||
assert 'id="panel-acl-profiles"' in html and 'id="acl-profile-form"' in html
|
||
for marker in ['id="acl-profile-rule-rows"', 'id="acl-profile-rule-add"', 'id="acl-profile-import-rules"']:
|
||
assert marker in html, marker
|
||
management = (root / "src/admin/web/management.js").read_text()
|
||
assert 'collectRuleRows' in management and 'parseRules(rules.value, rules)' in management
|
||
assert 'normalizeAclRuleText' in js and 'ruleSyntaxLine' in js
|
||
cache_source = read_cache_source()
|
||
assert not (root / "src/cache.rs").exists()
|
||
for fragment in ["mod.rs", "model.rs", "lifecycle.rs", "cold.rs", "dashboard.rs", "tickets.rs", "lookup.rs", "policy.rs", "fill.rs", "collapse.rs", "range.rs", "invalidation.rs", "tests.rs"]:
|
||
assert (root / "src/cache" / fragment).is_file(), fragment
|
||
cache_mod = (root / "src/cache/mod.rs").read_text()
|
||
assert 'impl Cache {\n include!(' not in cache_mod, "include! cannot be used directly as Cache impl items"
|
||
for fragment in ["lifecycle.rs", "cold.rs", "dashboard.rs", "tickets.rs", "lookup.rs"]:
|
||
assert (root / "src/cache" / fragment).read_text().lstrip().startswith("impl Cache {"), fragment
|
||
assert 'disabled_cache_strips_diagnostics_without_exposing_bypass_headers' in cache_source
|
||
for marker in ['pub enum MemoryPreset', 'pub enum ColdPreset', 'BACKGROUND_REVALIDATION_LIMIT', 'cleanup_slots']:
|
||
assert marker in cache_source, marker
|
||
for marker in ['selectedCacheMemoryPreset', 'selectedCacheColdPreset', 'applyRecommendedColdCachePreset', 'data-cache-cold-preset="recommended"']:
|
||
assert marker in js or marker in html, marker
|
||
assert 'if !self.enabled' in cache_source
|
||
proxy_source = (root / "src/proxy/mod.rs").read_text()
|
||
assert 'response.extensions().get::<LocalHostResponse>().is_none()' in proxy_source
|
||
assert '"cdn-cache-control"' in proxy_source and '"x-cache-resident-age"' in proxy_source
|
||
for marker in [
|
||
'cold_policy_can_disable_lru_demotion',
|
||
'cold_policy_can_disable_idle_demotion',
|
||
'cold_policy_can_demote_by_total_hot_residency_age',
|
||
'cold_on_age',
|
||
'cold_max_hot_seconds',
|
||
'graceful_shutdown_flush_moves_hot_objects_and_releases_resident_bytes',
|
||
'graceful_shutdown_flush_can_be_disabled',
|
||
'managed_update_flush_ignores_shutdown_toggle_when_cold_tier_is_enabled',
|
||
'pub async fn flush_hot_to_cold',
|
||
'pub async fn flush_hot_to_cold_for_update',
|
||
'cold_on_shutdown',
|
||
'vary_selects_variants_without_fragmenting_the_primary_hash',
|
||
'vary_schema_change_removes_incompatible_fallbacks',
|
||
'varnish_primary_hash_uses_host_and_url_not_unrelated_request_headers',
|
||
'resident_age_survives_successful_revalidation',
|
||
'COLD_FORMAT_VERSION: u8 = 3',
|
||
'cold_cache_uses_two_hex_character_shards',
|
||
'legacy_flat_cold_cache_is_not_migrated',
|
||
'manual_cold_cleanup_reports_removed_objects_and_freed_bytes',
|
||
'rfc5861_windows_and_revalidation_flags_are_separate_metadata',
|
||
'request_max_stale_is_bounded_and_respects_revalidation_flags',
|
||
'stale_if_error_is_only_available_inside_its_window',
|
||
'transformed_etag_is_never_used_as_an_origin_validator',
|
||
'Deduplicate only the exact cached',
|
||
'matching_hot_key',
|
||
'matching_cold_key',
|
||
'concurrent_miss_single_flight_waits_until_fill_is_published',
|
||
'single_flight_timeout_and_leader_drop_release_the_key',
|
||
'cancelled_single_flight_waiter_releases_waiter_metric',
|
||
'single_flight_retries_after_another_vary_representation_is_published',
|
||
'cached_single_range_returns_206_and_preserves_validators',
|
||
'cached_range_supports_suffix_open_unsatisfied_and_if_range',
|
||
'range_parser_rejects_multipart_and_duplicate_fields_and_if_range_date_allows_older_representation',
|
||
'stale_if_error_policy_is_configurable_by_error_class_and_status',
|
||
'proactive_refresh_guards_deduplicate_and_bound_parallel_work',
|
||
'selective_purge_exact_prefix_tag_cache_key_and_query_are_precise',
|
||
'selective_purge_is_a_barrier_for_a_fill_started_before_the_purge',
|
||
'purge_of_one_vary_variant_removes_the_whole_variant_family',
|
||
'ban_is_lazy_blocks_old_object_allows_new_fill_and_can_be_removed',
|
||
'regex_and_root_prefix_require_explicit_wide_confirmation',
|
||
'selective_purge_removes_matching_cold_object',
|
||
]:
|
||
assert marker in cache_source, marker
|
||
transform_source = (root / "src/transform/mod.rs").read_text()
|
||
for marker in [
|
||
'pub struct Settings',
|
||
'pub struct Overrides',
|
||
'resolved.enabled = global.enabled && self.enabled',
|
||
'pub fn no_transform',
|
||
'OriginValidators',
|
||
"fn decode<'a>",
|
||
'Cow::Borrowed(body)',
|
||
'trim_allocator_after_large_transform',
|
||
'pub memory_budget_bytes: usize',
|
||
'pub max_inflight: usize',
|
||
'Preset::Uber',
|
||
'try_reserve_transform_memory(required_memory, settings.memory_budget_bytes)',
|
||
'try_reserve_transform_slot(settings.max_inflight)',
|
||
'drop(decoded);',
|
||
'drop(raw);',
|
||
'rustproxy-',
|
||
]:
|
||
assert marker in transform_source, marker
|
||
assert 'collect bounded static response for transformation: {error}' in transform_source
|
||
assert '.context("collect bounded static response for transformation")' not in transform_source
|
||
lookup_source = (root / 'src/cache/lookup.rs').read_text()
|
||
model_source = (root / 'src/cache/model.rs').read_text()
|
||
css_minifier_source = (root / 'src/transform/css.rs').read_text()
|
||
assert 'let Some(_read_lease) = self.try_reserve_transient' in lookup_source
|
||
assert 'struct TransientLease {\n _inner: Arc<TransientLeaseInner>,' in model_source
|
||
assert 'struct TransientLease(Arc<TransientLeaseInner>);' not in model_source
|
||
assert 'last_char = out.chars().last();' not in css_minifier_source
|
||
assert css_minifier_source.count("last_char = Some(' ');") == 1
|
||
for name in ['js.rs', 'css.rs', 'html.rs', 'svg.rs', 'json.rs']:
|
||
assert (root / 'src/transform' / name).is_file(), name
|
||
for name in ['js.rs', 'css.rs']:
|
||
minifier_source = (root / 'src/transform' / name).read_text()
|
||
assert 'Vec<char>' not in minifier_source and '.chars().collect' not in minifier_source, name
|
||
assert 'into_bytes()' in minifier_source, name
|
||
assert 'pub mod transform;' in (root / 'src/lib.rs').read_text()
|
||
for marker in ['cache-cold-gc-interval', 'cache-cold-gc-max-delete', 'settings-tab-transform', 'transform-form', 'transform-memory-budget', 'transform-max-inflight', 'data-transform-preset="uber"', 'cache-cleanup-dialog']:
|
||
assert marker in html, marker
|
||
for marker in ['cache-transient-memory', 'cache-cold-queue-bytes', 'cache-eviction-limit', 'data-cache-memory-preset="recommended"', 'cache-memory-warning']:
|
||
assert marker in html, marker
|
||
for marker in ['effectiveCacheMemory', 'cacheMemoryHeadroom', 'cachePresetValues', 'applyCacheMemoryPreset', 'cacheMemoryPresetTooSmall', 'cacheObjectOverTransientWarning', 'cgroup_memory_max_bytes', 'transient_max_bytes', 'cold_queue_max_bytes', 'hot_eviction_max_objects']:
|
||
assert marker in js, marker
|
||
for marker in ['selectedTransformPreset', 'transformPresetValues', 'applyTransformPreset', 'updateTransformResourceSizing', 'memory_budget_bytes', 'max_inflight']:
|
||
assert marker in js, marker
|
||
assert 'envelope?.config.cache?.enabled === true && cacheToggle.value !== "off"' in vhosts_js
|
||
for marker in ['transform: { enabled: false }', 'vhTransform', 'collectTransform']:
|
||
assert marker in vhosts_js, marker
|
||
docker_js = (root / 'src/admin/web/docker.js').read_text()
|
||
assert 'transform_enabled' in docker_js
|
||
cache_panel_js = (root / 'src/admin/web/cache-panel.js').read_text()
|
||
for marker in ['/api/cache/cold/cleanup', 'removed_entries', 'freed_bytes', '/api/cache/invalidate', '/api/cache/bans', 'cache-invalidation-operation', 'cache-invalidation-selector', 'cache-ban-ttl', 'confirm_wide', 'cachePurgeUrl']:
|
||
assert marker in cache_panel_js or marker in html, marker
|
||
transform_panel_js = (root / 'src/admin/web/transform-panel.js').read_text()
|
||
for marker in ['/api/transform', 'tp-cache-hits', 'transform-reason-list']:
|
||
assert marker in transform_panel_js, marker
|
||
for marker in ['id="cache-live-chart"', 'id="cache-live-activity-chart"', 'id="cache-live-hit-line"', 'id="cache-live-miss-line"', 'id="cache-live-bypass-line"', 'id="cache-live-samples"', 'data-i18n="cacheLiveChartTitle"']:
|
||
assert marker in html, marker
|
||
for marker in ['id="transform-live-chart"', 'id="transform-live-activity-chart"', 'id="transform-live-applied-line"', 'id="transform-live-cache-line"', 'id="transform-live-skipped-line"', 'id="transform-live-error-line"', 'id="transform-live-samples"', 'data-i18n="transformLiveChartTitle"']:
|
||
assert marker in html, marker
|
||
assert '<details class="card function-live-chart" id="cache-live-chart">' in html and '<details class="card function-live-chart" id="transform-live-chart">' in html
|
||
for marker in ['liveChartSamples = { hits: [], misses: [], bypasses: [] }', 'LIVE_CHART_LIMITS = new Set([60, 120, 300, 600])', 'rustproxy.cache.liveChartSamples', 'performance.now()', 'sparklinePoints(liveChartSamples.hits, 900, 180, 10, peak)', 'slice(-liveChartLimit)']:
|
||
assert marker in cache_panel_js, marker
|
||
for marker in ['liveChartSamples = { applied: [], cache_hits: [], skipped: [], errors: [] }', 'LIVE_CHART_LIMITS = new Set([60, 120, 300, 600])', 'rustproxy.transform.liveChartSamples', 'performance.now()', 'sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak)', 'slice(-liveChartLimit)']:
|
||
assert marker in transform_panel_js, marker
|
||
admin_cache_source = (root / 'src/admin/mod.rs').read_text()
|
||
for marker in ['"transform" => {', 'transform_status', 'cache_cold_cleanup', 'cache_cold_cleanup_job', 'cache_invalidate', 'cache_bans', 'cache_ban_delete', 'selective_cache_invalidation_api_manages_bans_and_rejects_wide_rules_without_confirmation']:
|
||
assert marker in admin_cache_source, marker
|
||
for marker in [
|
||
'fill_bytes_active',
|
||
'cold_queue_items',
|
||
'cold_queue_bytes',
|
||
'cold_write_bytes_active',
|
||
'cold_read_bytes_active',
|
||
'transient_bytes',
|
||
'transient_max_bytes',
|
||
'cold_queue_max_bytes',
|
||
'hot_eviction_max_objects',
|
||
'hot_logical_bytes',
|
||
'hot_allocated_bytes',
|
||
'memory_pressure_rejections',
|
||
'struct FillGauge',
|
||
'struct TransientLease',
|
||
'reserve_fill_allocation',
|
||
'release_allocator_memory()',
|
||
]:
|
||
assert marker in cache_source, marker
|
||
assert 'impl Drop for Candidate' not in cache_source, 'fill accounting must not block moving Candidate fields during finish'
|
||
redirect_source = (root / "src/redirect.rs").read_text()
|
||
for marker in ["pub struct Host", "pub struct Rule", "strip_prefix", "cache_seconds", "unconditional self-redirect"]:
|
||
assert marker in redirect_source, marker
|
||
proxy_source = (root / "src/proxy/mod.rs").read_text()
|
||
assert 'state.cache.purge(None)' not in proxy_source, 'proxy requests must not globally purge cache'
|
||
assert 'request.method() == Method::HEAD && cache_ticket.is_some()' in proxy_source
|
||
assert 'RangeSelection::Single' in proxy_source and 'request_range(request.method(), request.headers())' in proxy_source
|
||
assert 'claim_fill(ticket, cache_settings)' in proxy_source and 'cache_collapse_timeout_origin' in proxy_source
|
||
assert 'schedule_proactive_refresh' in proxy_source and 'cache_proactive_refreshed' in proxy_source
|
||
assert 'classify_stale_error' in proxy_source and 'allows_stale_error_status' in proxy_source
|
||
assert 'Varnish built-in behavior treats non-GET/HEAD methods as pass' in proxy_source
|
||
config_source = (root / "src/config.rs").read_text()
|
||
assert 'plain HTTP listener' in config_source and 'tls_ports.contains(&443)' in config_source
|
||
assert '#[serde(default)]\n pub tls_verify: bool' in config_source
|
||
assert 'backend_tls_verification_defaults_off_and_can_be_enabled_explicitly' in config_source
|
||
assert 'backend TLS server name must be the DNS name present in the certificate' in config_source
|
||
assert 'pub accept_local_listener_risk: bool' in config_source
|
||
assert 'accept_local_listener_risk = true' in config_source
|
||
for marker in [
|
||
'fallbacks: HashMap<String, Vec<CompiledRoute>>',
|
||
'id: format!("{}/main", host.id)',
|
||
'custom_locations_override_but_main_vhost_remains_fallback',
|
||
'direct_admin_listener_backend_is_marked_for_guarded_control_plane_use',
|
||
'pub targets_admin_listener: bool',
|
||
'backend_diagnostic_flags_compile_to_frontend',
|
||
'backend_header: host.backend_header',
|
||
'backend_timing_header: host.backend_timing_header',
|
||
]:
|
||
assert marker in config_source, marker
|
||
vhost_source = (root / "src/vhost.rs").read_text()
|
||
assert 'is_legacy_main_fallback' in vhost_source
|
||
assert 'values.length ? values : [{ id: "root"' not in vhosts_js
|
||
for marker in [
|
||
'OriginBackend',
|
||
'apply_backend_diagnostic_headers',
|
||
'x-rustproxy-backend-time',
|
||
'backend_diagnostics_are_opt_in_and_strip_spoofed_values',
|
||
'admin_session_passthrough',
|
||
'admin_control_plane',
|
||
'Some("admin_control_plane")',
|
||
'restore_admin_cookie',
|
||
'BEFORE returning a successful RFC 9220 response',
|
||
'websocket_phase("tunnel_ready")',
|
||
]:
|
||
assert marker in proxy_source, marker
|
||
generic_headers_source = (root / "src/headers.rs").read_text()
|
||
assert '"x-rust-proxy-backend"' in generic_headers_source and '"x-rustproxy-backend-time"' in generic_headers_source
|
||
headers_source = (root / "src/proxy/headers.rs").read_text()
|
||
for marker in ['pub fn restore_admin_cookie', 'admin_cookie_can_be_restored_only_from_captured_fragment']:
|
||
assert marker in headers_source, marker
|
||
settings_source = (root / "src/settings.rs").read_text()
|
||
for marker in ['pub session_days: u16', 'session_days: 30', '(7..=30).contains(&self.session_days)', 'administration_session_lifetime_is_7_to_30_days_and_hot_apply_only']:
|
||
assert marker in settings_source, marker
|
||
auth_source = (root / "src/auth/mod.rs").read_text()
|
||
auth_store = (root / "src/auth/store.rs").read_text()
|
||
for marker in ['30 * 24 * 60 * 60', 'session_cookie(secret: &str, secure: bool, max_age_seconds: i64)', 'browser_session_lifetime_is_configurable_without_sliding_expiry', 'session_cookie_uses_configured_remaining_lifetime']:
|
||
assert marker in auth_source, marker
|
||
for marker in ['create_browser_session', 'session_at_with_browser_lifetime', 'browser session lifetime must be 7..30 days', "created_at + ?"]:
|
||
assert marker in auth_store, marker
|
||
upstream_source = (root / "src/upstream.rs").read_text()
|
||
for marker in ['pub accept_local_listener_risk: bool', '!self_target || self.accept_local_listener_risk', 'explicit_risk_acceptance_allows_local_listener_but_not_private_policy_bypass']:
|
||
assert marker in upstream_source, marker
|
||
transport_tests = (root / "tests/transport_config.rs").read_text()
|
||
assert 'local_listener_backend_requires_explicit_risk_acceptance' in transport_tests and '127.0.0.1:9090' in transport_tests
|
||
runtime_source = (root / "src/runtime.rs").read_text()
|
||
for marker in ['fn cache_revision', 'CacheInvalidation::None', 'operational_saves_keep_cache_identity_but_cache_affecting_saves_rotate_it']:
|
||
assert marker in runtime_source, marker
|
||
admin = (root / "src/admin/mod.rs").read_text()
|
||
for marker in [
|
||
"fn admin_request_authority",
|
||
"Version::HTTP_2 | Version::HTTP_3",
|
||
"request.uri().authority().ok_or(())?",
|
||
"administration_https_health_accepts_http2_authority_without_host_header",
|
||
]:
|
||
assert marker in admin, marker
|
||
assert '"/assets/streams.js"' in admin and '"metrics": stream_metrics' in admin
|
||
for marker in ['"/api/config/{section}"', 'async fn update_section', 'section_updates_are_revisioned_and_do_not_require_full_config_payloads', 'panel_subpage_routes_survive_browser_refresh', 'fn panel_document_path', 'panel_document_path(&path)', 'certificate_metadata', 'inspected_metadata', 'session_at_with_browser_lifetime', 'session_lifetime_seconds', 'HeaderValue::from_str(&auth::session_cookie(']:
|
||
assert marker in admin, marker
|
||
for route in ['/overview', '/vhosts', '/redirects', '/404-hosts', '/streams', '/acl-profiles', '/configuration', '/tls', '/cache', '/transform', '/logging', '/settings']:
|
||
assert f'.route("{route}", get(public::index))' in admin, route
|
||
app_js = (root / "src/admin/web/app.js").read_text()
|
||
for marker in ['const PANEL_PATHS', 'history.pushState', 'window.addEventListener("popstate"', 'async function saveSection', 'async function revalidateSession', 'Object.values(PANEL_PATHS), "/configuration"', 'certificateCovers', 'covered_hosts', 'refreshCertificateInventory', 'certificateDetails', 'certificate-domain-pills', 'certificateIssuerName', 'certificateDaysRemaining', 'certificateRenewalStatus']:
|
||
assert marker in app_js, marker
|
||
for marker in ['.certificate-details', '.certificate-domain-pills', '.certificate-card .pill', '.certificate-operation-card', '.renewal-meta', '#panel-tls .certificate-tabs']:
|
||
assert marker in css, marker
|
||
acme_js = (root / "src/admin/web/acme.js").read_text()
|
||
for marker in ['data-certificate-tab="installed"', 'data-certificate-tab="renewal"', 'data-certificate-tab="generate"', 'id="certificate-page-installed"', 'id="certificate-page-renewal"', 'id="certificate-page-generate"']:
|
||
assert marker in html, marker
|
||
assert 'id="certificate-dialog"' not in html and 'id="certificate-close"' not in html
|
||
for marker in ['function selectTab(name, focus = false, persist = true)', 'log_directories', 'certbotLogs', 'value?.kind?.startsWith("renewal")', 'selectTab("installed")']:
|
||
assert marker in acme_js, marker
|
||
login_js = (root / "src/admin/web/login.js").read_text()
|
||
assert 'const panelPaths = new Set' in login_js and 'panelPaths.has(location.pathname)' in login_js
|
||
assert '"/404-hosts"' in login_js
|
||
assert 'async function redirectExistingSession' in login_js and '/api/auth/session' in login_js
|
||
settings_js = (root / "src/admin/web/settings.js").read_text()
|
||
assert '["/settings", "/configuration", "/logging"].includes(location.pathname)' in settings_js
|
||
assert 'new URLSearchParams(location.search).get("tab")' in settings_js and 'function syncLocation()' in settings_js and 'history.replaceState' in settings_js
|
||
assert 'url.pathname = "/settings"' in settings_js and 'url.searchParams.set("tab", active)' in settings_js
|
||
assert 'data-settings-tab="certificate-api"' in html and 'id="settings-page-certificate-api"' in html
|
||
for marker in ['data-cache-settings-tab="general"', 'data-cache-settings-tab="resilience"', 'data-cache-settings-tab="cold"', 'data-cache-settings-tab="diagnostics"']:
|
||
assert marker in html, marker
|
||
for marker in ['data-settings-tab="connections"', 'data-http-settings-tab="http2"', 'data-http-settings-tab="http3"', 'id="http2-form"', 'id="http2-listener-rows"', 'id="http3-form"', 'id="http3-listener-rows"']:
|
||
assert marker in html, marker
|
||
for marker in ['const tabAliases = { http2: "connections" }', 'data-http-settings-tab', 'selectHttpTab("http2")']:
|
||
assert marker in settings_js or marker in html, marker
|
||
for marker in ['renderHttp2Settings', 'collectHttp2Listeners', 'http2SettingsControls', 'preservedHttp2', 'listener-h2', 'h2Configure', 'selectHttpTab?.("http2")', 'renderHttp3Settings', 'collectHttp3Listeners', 'http3SettingsControls', 'preservedHttp3', 'listener-h3', 'h3Configure', 'selectHttpTab?.("http3")']:
|
||
assert marker in integrations_js, marker
|
||
h2_settings_controls = integrations_js.split('function http2SettingsControls', 1)[1].split('const h3Defaults', 1)[0]
|
||
assert 'listener-h2' not in h2_settings_controls, 'HTTP/2 enablement belongs to the listener editor, not the tuning page'
|
||
h3_settings_controls = integrations_js.split('function http3SettingsControls', 1)[1].split('function addRow', 1)[0]
|
||
assert 'h3Enable' not in h3_settings_controls, 'HTTP/3 enablement belongs to the listener editor, not the tuning page'
|
||
assert 'data-i18n="processDiagnostics">Process<' in html
|
||
for marker in ['function setOptionalMetric', 'setOptionalMetric("process-rss-anon"', 'setOptionalMetric("system-swap"', 'closest(".mini-live-chart").hidden']:
|
||
assert marker in app_js, marker
|
||
for marker in ['/api/certificate-api', 'certificate-api-form', 'certificate-api-revoke-all', 'certificate-api-credentials', 'data-cache-settings-tab']:
|
||
assert marker in settings_js or marker in html, marker
|
||
db_source = (root / "src/db.rs").read_text()
|
||
acme_source = (root / "src/acme.rs").read_text()
|
||
main_source = (root / "src/main.rs").read_text()
|
||
logging_source = (root / "src/logging.rs").read_text()
|
||
secret_store_source = (root / "src/secret_store.rs").read_text()
|
||
backup_source = (root / "src/backup.rs").read_text()
|
||
config_source = (root / "src/config.rs").read_text()
|
||
acme_worker_source = (root / "scripts/acme_provider.py").read_text()
|
||
for feature_marker in [
|
||
"AES_256_GCM", "master.key", "O_NOFOLLOW", "migrate_acme_credentials",
|
||
"secret_store", "SecretMetadata",
|
||
]:
|
||
assert feature_marker in secret_store_source or feature_marker in db_source, feature_marker
|
||
for feature_marker in [
|
||
"Argon2", "RPROXYB1", "RPROXYA1", "backup create", "secret-store-key.enc",
|
||
"validate_backup_version", "stage",
|
||
]:
|
||
assert feature_marker in backup_source or feature_marker in main_source, feature_marker
|
||
for feature_marker in [
|
||
"ErrorLogSettings", "ERROR_LOG_QUEUE", "RedactingStderr", "open_error_file",
|
||
"ErrorLogLayer", "ERROR",
|
||
]:
|
||
assert feature_marker in logging_source, feature_marker
|
||
assert 'regex::Regex::new(r#"(?i)((?:authorization|' in logging_source, "KV redaction regex must use a hash-delimited raw string"
|
||
assert '[^\\s,;\"\']+" )' not in logging_source, "do not reintroduce the unterminated raw-string form in KV redaction"
|
||
assert "pub error_log: crate::logging::ErrorLogSettings" in config_source
|
||
assert "credential_ini_b64" in acme_source and "credential_ini_b64" in acme_worker_source
|
||
assert "STANDARD.encode(credential.as_slice())" in acme_source, "Base64 input must be an unambiguous byte slice"
|
||
assert "STANDARD.encode(credential.as_ref())" not in acme_source, "Zeroizing<Vec<u8>>::as_ref() is ambiguous for Engine::encode"
|
||
assert "&mut *ciphertext" in secret_store_source, "ring seal must receive the underlying Vec<u8>, not an ambiguous AsMut target"
|
||
assert "&mut *encrypted" in backup_source, "ring seal must receive the underlying Vec<u8>, not an ambiguous AsMut target"
|
||
assert "seal_in_place_append_tag(\n Nonce::assume_unique_for_key(nonce),\n Aad::from(aad(secret_id, provider)),\n ciphertext.as_mut()," not in secret_store_source
|
||
assert "seal_in_place_append_tag(\n Nonce::assume_unique_for_key(nonce_bytes),\n Aad::from(KEY_AAD),\n encrypted.as_mut()," not in backup_source
|
||
assert "finally:" in acme_worker_source and "credential_file.unlink" in acme_worker_source
|
||
for marker in ['certificate_api_credentials', 'upsert_certificate_api_credential', 'revoke_certificate_api_credential', 'verify_certificate_api_credential', '0005_certificate_api.sql', '0006_certificate_api_credentials.sql']:
|
||
assert marker in db_source, marker
|
||
for marker in ['/api/certificate-api', 'certificate_api_credentials', 'certificate_api_operation', '/api/live-logs', 'CertificateApiBearer']:
|
||
assert marker in admin or marker in openapi, marker
|
||
for marker in ['/api/certificates/{certificate_id}/reload', 'certificate_reload_one', 'reload_certificate(&certificate_id)', 'CertificateReloadOneResult', '/api/certificate-api/{key_id}']:
|
||
assert marker in admin or marker in openapi, marker
|
||
for marker in ['metric-bytes-down', 'metric-bytes-up', 'connection-chart', 'network-interface-hide', 'rp-hidden-network-interfaces']:
|
||
assert marker in html or marker in app_js or marker in (root / 'src/admin/web/style.css').read_text(), marker
|
||
assert 'valid_certificate_api_key_id' in admin and 'sleep_until(next_auth_check)' in admin
|
||
for marker in [
|
||
'livelogs: bool', 'LiveLogFilters', 'handle_live_command', 'live_logs_command',
|
||
'create_live_log_session', 'command_input_open', 'buffer.extend_from_slice(&data)',
|
||
'sse_parser_tolerates_utf8_split_across_transport_frames',
|
||
'LocalAdminCertificateVerifier', 'detect_local_admin_scheme',
|
||
'local_admin_scheme_from_probe', 'admin.uri("/api/live-logs")',
|
||
]:
|
||
assert marker in main_source or marker in (root / 'src/auth/store.rs').read_text(), marker
|
||
assert '.uri(format!("http://{addr}/api/live-logs"))' not in main_source
|
||
assert 'http://{addr}/healthz' in main_source and 'StatusCode::PERMANENT_REDIRECT' in main_source
|
||
assert 'Ok(line) if command_tx.send(line).is_ok()' not in main_source, 'do not move String from a match guard in --livelogs stdin handling'
|
||
assert 'Ok(line) => {' in main_source and 'command_tx.send(line).is_err()' in main_source
|
||
for marker in ['LIVE_STREAM_CAPACITY', 'broadcast::Sender<LogEntry>', 'pub fn subscribe']:
|
||
assert marker in logging_source, marker
|
||
assert 'SCHEMA_VERSION: i64 = 7' in db_source and 'certificate_renewals' in db_source and '0004_admin_preferences.sql' in db_source and '0005_certificate_api.sql' in db_source and '0006_certificate_api_credentials.sql' in db_source and '0007_secret_store.sql' in db_source
|
||
assert 'load_renewal_observations' not in acme_source and 'renewals.json' not in acme_source
|
||
for marker in ['pub kind: &\'static str', 'pub log_directory: String', 'pub(crate) fn certbot_log_directory', 'kind: "issue"', '"renewal-dry-run"']:
|
||
assert marker in acme_source, marker
|
||
assert '"log_directories":log_directories' in admin and 'crate::acme::certbot_log_directory' in admin
|
||
assert 'let cert_dir = Some(directory.clone());' in main_source, 'certificate directory must remain available after ValidationPolicy construction'
|
||
assert 'accept-remote-admin-risk' in main_source and 'hide = true' in main_source
|
||
for marker in [
|
||
'RUST_PROXY_UPDATE_REPOSITORY_URL',
|
||
'RUST_PROXY_UPDATE_VERSION_URL',
|
||
'RUST_PROXY_UPDATE_CHECK_INTERVAL_SECS',
|
||
'rust_proxy::update_check::Config::embedded()?.with_overrides(',
|
||
'state.start_update_check(update_check);',
|
||
]:
|
||
assert marker in main_source, marker
|
||
runtime_source = (root / "src/runtime.rs").read_text()
|
||
assert 'pub software_update: Arc<crate::update_check::SharedStatus>' in runtime_source
|
||
assert 'pub fn start_update_check(self: &Arc<Self>, config: crate::update_check::Config)' in runtime_source
|
||
assert 'payload["software_update"] = json!(state.software_update.snapshot());' in admin
|
||
settings_rust = (root / 'src/settings.rs').read_text()
|
||
assert 'pub struct StartupLocks' in settings_rust and 'fixed by systemd/startup' in settings_rust
|
||
logging_rust = (root / 'src/logging.rs').read_text()
|
||
assert 'pub per_vhost: bool' in logging_rust and 'default_vhost_log_directory' in logging_rust and '_unmatched' in logging_rust
|
||
pages_rust = (root / 'src/pages.rs').read_text()
|
||
assert 'validate_local_response' in pages_rust and '(200..=599).contains(&status)' in pages_rust and '204 | 205 | 304' in pages_rust
|
||
assert 'store.load_certificate_renewals().await?' in main_source and 'state.acme.restore_renewals(renewal_observations)' in main_source
|
||
assert 'renewals.json' not in main_source
|
||
upgrade_source = (root / 'scripts/upgrade.py').read_text()
|
||
assert 'legacy = certs_dir / "renewals.json"' in upgrade_source and 'removed_obsolete_runtime_state' in upgrade_source and 'legacy-data' not in upgrade_source and 'certs_dir.is_symlink()' in upgrade_source
|
||
assert '.managed-update' in main_source and 'flush_hot_to_cold_for_update' in main_source
|
||
tls_source = (root / "src/tls.rs").read_text()
|
||
assert 'pub fn validate_certificate_removals' in tls_source and 'administration panel' in tls_source
|
||
assert 'pub fn effective_cipher_suites' in tls_source
|
||
vhost_source = (root / 'src/vhost.rs').read_text()
|
||
assert 'pub websocket_rfc9220: bool' in vhost_source and 'RFC 9220 requires WebSocket support to be enabled' in vhost_source
|
||
for marker in ['pub struct ProxyTimeouts', 'connect_timeout_seconds', 'response_header_timeout_seconds', 'read_timeout_seconds', 'send_timeout_seconds']:
|
||
assert marker in vhost_source, marker
|
||
docker_source = (root / 'src/docker.rs').read_text()
|
||
for marker in ['pub poll_interval_seconds: u64', 'pub acl_profile_id: Option<String>', 'pub proxy_timeouts: crate::vhost::ProxyTimeouts', 'refresh(&self, settings: &Settings, force: bool)', 'poll_interval_seconds.saturating_mul(1000)']:
|
||
assert marker in docker_source, marker
|
||
docker_js = (root / 'src/admin/web/docker.js').read_text()
|
||
for marker in ['docker-live-refresh', 'saveAssignment(agentId, row, publish)', 'docker-acl-profile', 'docker-proxy-read', 'poll_interval_seconds']:
|
||
assert marker in docker_js, marker
|
||
agent_installer = (root / 'docker-agent/install.sh').read_text()
|
||
assert 'Bearer token:' in agent_installer and 'RUSTPROXY_DOCKER_AGENT_TOKEN=' in agent_installer
|
||
assert 'Kind::Builtin' in pages_rust and 'builtin_application_page' in pages_rust
|
||
assert 'pub covered_hosts: Vec<String>' in tls_source and 'fn certificate_dns_names' in tls_source and 'pub fn certificate_covered_hosts' in tls_source and 'legacy_certificate_coverage_can_be_read_from_existing_pem' in tls_source and 'assert_eq!(a.covered_hosts' in tls_source
|
||
acl_source = (root / 'src/acl.rs').read_text()
|
||
assert '/Ubuntu%2014.04%20LTS/' in acl_source and 'repeated_slashes_are_normalized_for_routing' in acl_source and 'merge_slashes' in acl_source
|
||
policies_source = (root / 'tests/policies.rs').read_text()
|
||
assert 'percent_encoded_spaces_are_valid_request_paths' in policies_source and 'repeated_slashes_route_normally_but_reach_upstream_unchanged' in policies_source and 'local_location_response_uses_response_rules_without_opening_backend' in policies_source and 'local_location_response_still_respects_route_acl' in policies_source
|
||
http3_source = (root / "src/http3.rs").read_text()
|
||
drop_impl = http3_source.split('impl Drop for ReceiveBody', 1)[1].split('#[cfg(test)]', 1)[0]
|
||
assert 'self.stream.stop_sending(' not in drop_impl
|
||
for marker in ['h3::quinn::RecvStream', 'websocket_rfc9220_on(local)', 'run_h3', 'H3_REQUEST_CANCELLED']:
|
||
assert marker in http3_source or marker in (root / 'src/proxy/websocket.rs').read_text(), marker
|
||
assert 'h3_quinn' not in http3_source and 'h3_quinn' not in (root / 'tests/http3.rs').read_text()
|
||
h3_test = (root / 'tests/http3.rs').read_text()
|
||
assert 'RFC 9220: Extended CONNECT negotiates WebSocket on the same H3 stream.' in h3_test and '"websocket_rfc9220":true' in h3_test
|
||
assert 'Method::CONNECT' in h3_test and 'h3::ext::Protocol' in h3_test and '.send_data(' in h3_test
|
||
admin_source = (root / 'src/admin/mod.rs').read_text()
|
||
for marker in ['external_admin_authority', 'external_admin_scheme', 'trusted_admin_forwarded_proto', 'reverse_proxy_dns_allowlist_uses_external_port_and_keeps_direct_port_available', 'trusted_https_reverse_proxy_accepts_external_origin_and_refreshes_secure_cookie']:
|
||
assert marker in admin_source, marker
|
||
ws_source = (root / 'src/proxy/websocket.rs').read_text()
|
||
assert 'upstream: Upgraded' in ws_source
|
||
assert 'timeout(Duration::from_secs(10), upstream)' not in ws_source
|
||
for marker in ['websocket_phase("tunnel_started")', 'websocket_phase("tunnel_closed")', 'h3_reset("H3_REQUEST_CANCELLED")']:
|
||
assert marker in ws_source, marker
|
||
for marker in ['http_version', 'transport', 'websocket_bootstrap', 'websocket_phase', 'websocket_error', 'h3_reset_code', 'upstream_status']:
|
||
assert marker in (root / 'src/logging.rs').read_text(), marker
|
||
diagnostics_source = (root / 'src/diagnostics.rs').read_text() + (root / 'src/system_stats/mod.rs').read_text() + (root / 'src/system_stats/linux.rs').read_text()
|
||
for marker in ['process_rss_anon_bytes', 'process_rss_file_bytes', 'process_rss_shmem_bytes', 'process_data_bytes', 'process_swap_bytes', 'RssAnon', 'RssFile', 'RssShmem', 'VmData', 'VmSwap']:
|
||
assert marker in diagnostics_source, marker
|
||
prometheus_source = (root / 'src/prometheus.rs').read_text()
|
||
for marker in ['allow_trusted_network_without_token', 'include_docker_vhosts', 'include_streams', 'include_logging', 'rust_proxy_docker_vhost_{name}', 'rust_proxy_docker_backend_{name}', 'rust_proxy_geoip_loaded', 'not_after_timestamp_seconds', 'ewma_latency_seconds', 'passive_failures', 'active_health_failures_total', 'passive_health_failures_total', 'passive_http_5xx_total', 'passive_timeouts_total', 'passive_connect_errors_total', 'passive_io_errors_total', 'passive_slow_total', 'http_5xx_total', 'timeouts_total', 'connect_errors_total', 'io_errors_total', 'slow_responses_total', 'rust_proxy_log_sink_queue_dropped_total', 'rust_proxy_not_found_hosts_configured']:
|
||
assert marker in prometheus_source, marker
|
||
assert 'if docker_targets.contains_key(&pool.id)' in prometheus_source
|
||
assert 'if docker_targets.contains_key(id)' in prometheus_source
|
||
assert 'rust_proxy_docker_vhost_exchange_duration_seconds' in prometheus_source
|
||
settings_source = (root / 'src/admin/web/settings.js').read_text()
|
||
assert 'prom-trusted-no-token' in settings_source and 'include_docker_vhosts' in settings_source and 'include_streams' in settings_source and 'include_logging' in settings_source
|
||
for marker in ['cache-memory-breakdown', 'cache-transient-breakdown', 'cache-cold-storage', 'cache-transient-sparkline', 'cache-fill-sparkline', 'cache-queue-sparkline', 'cache-io-sparkline', 'cache-cold-sparkline', 'metric-info', 'process-rss-total', 'memory-cache-hot', 'memory-cache-transient', 'memory-transform', 'memory-transform-inflight', 'memory-non-cache-rss', 'process-rss-anon', 'process-rss-file', 'process-data-swap', 'memoryAccountingHint']:
|
||
assert marker in html, marker
|
||
for marker in ['validate_h3', 'H3Tunnel', 'h3_client_to_backend', 'backend_to_h3', 'rfc9220_bootstrap_uses_extended_connect_without_h1_proof_headers']:
|
||
assert marker in ws_source, marker
|
||
for marker in ['PrefixPriority', 'RegexInsensitive', 'LocationResponse', 'pub rewrite: PathRewrite']:
|
||
assert marker in (root / 'src/vhost.rs').read_text(), marker
|
||
for marker in ['rewritten_path', 'nginx_style_location_precedence_exact_priority_regex_prefix', 'location_rewrites_are_normalized_and_regex_captures_expand', 'custom_location_response_is_validated']:
|
||
assert marker in config_source, marker
|
||
for marker in ['local_location_response', 'route.rewritten_path(&path)', 'encoded_origin_path', 'client_ip.to_string()']:
|
||
assert marker in proxy_source, marker
|
||
assert proxy_source.index('RustProxy: denied by access policy') < proxy_source.index('if let Some(spec) = &route.response') < proxy_source.index('let (backend, backend_lease, affinity_cookie)')
|
||
for marker in ['location-help-dialog', 'cache-memory-sparkline', 'cache-hit-sparkline', 'cache-miss-sparkline', 'cache-bypass-sparkline', 'process-cpu-sparkline', 'process-rss-sparkline', 'network-rx-sparkline', 'latency-sparkline', 'sourceIp']:
|
||
assert marker in html, marker
|
||
for marker in ['locationMatchRegex', 'headersVisualBuilder', 'headersAdvancedJson', 'openLocationHelp', 'locationPreviewTitle', 'location-preview-grid', 'vh-header-rule-fields', 'cloudflare-client-ip', 'timing-headers']:
|
||
assert marker in vhosts_js, marker
|
||
for marker in ['lastApiLatencyMs', 'cacheMemorySamples', 'cacheTransientSamples', 'cacheColdStorageSamples', 'cacheHitSamples', 'processCpuSamples', 'processRssSamples', 'networkRxSamples', 'responseLatencySamples', 'client_ip']:
|
||
assert marker in app_js, marker
|
||
logging_source = (root / 'src/logging.rs').read_text()
|
||
assert 'pub client_ip: Option<String>' in logging_source and '"client_ip" => entry.client_ip' in logging_source
|
||
for marker in ['check_rustproxy_api', 'response time', 'backend']:
|
||
assert marker in (root / 'nagios/README.md').read_text(), marker
|
||
stream_source = (root / "src/stream.rs").read_text()
|
||
for marker in ["pub struct Metrics", "pub struct MetricsView", "struct ActiveSession", "sessions_total", "sessions_completed", "packets_in", "packets_out", "metrics_view_reports_l4_counters"]:
|
||
assert marker in stream_source, marker
|
||
transport_doc = (root / "docs/TRANSPORT.md").read_text()
|
||
security_doc = (root / "docs/SECURITY.md").read_text()
|
||
api_access_doc = (root / "docs/API_ACCESS.md").read_text()
|
||
assert 'accept_local_listener_risk: true' in transport_doc and '127.0.0.1:9090' in transport_doc
|
||
assert 'accept_local_listener_risk=true' in security_doc and 'including port 9090' in security_doc
|
||
assert '7–30' in api_access_doc and 'default 30' in api_access_doc
|
||
not_found_source = (root / 'src/not_found.rs').read_text()
|
||
for marker in ['pub struct Host', 'pub struct CompiledHost', 'default_status', 'crate::pages::Page']:
|
||
assert marker in not_found_source, marker
|
||
pages_source = (root / 'src/pages.rs').read_text()
|
||
assert 'pub struct MinimalWelcome' in pages_source and 'HeaderValue::from_static("no-store")' in pages_source
|
||
assert 'builtin_welcome_is_minimal_and_never_cacheable' in pages_source
|
||
assert 'minimal_welcome' in proxy_source and 'cache-control' in proxy_source
|
||
make_zip = (root / "make_zip.py").read_text()
|
||
for marker in ["SOURCE_DATE_EPOCH", "ZipInfo", "deterministic_mode", "compresslevel=9"]:
|
||
assert marker in make_zip, marker
|
||
assert "'debian'" in make_zip and "'grafana'" in make_zip and "'docker-agent'" in make_zip
|
||
assert "'UPDATE_CHECK.toml'" in make_zip
|
||
# Debian package contracts.
|
||
debian_service = (root / 'debian/rustproxy.service').read_text()
|
||
for marker in ['User=rustproxy', 'AmbientCapabilities=CAP_NET_BIND_SERVICE', 'StateDirectory=rustproxy', 'LogsDirectory=rustproxy', 'ProtectSystem=strict', 'RUST_PROXY_ADMIN_LISTEN']:
|
||
if marker.startswith('RUST_PROXY_'):
|
||
assert marker in (root / 'debian/rustproxy.environment').read_text(), marker
|
||
else:
|
||
assert marker in debian_service, marker
|
||
logrotate = (root / 'debian/rustproxy.logrotate').read_text()
|
||
for marker in ['daily', 'rotate 3', 'maxage 3', 'compresscmd /usr/bin/gzip', 'compressoptions -9', 'copytruncate', 'su rustproxy rustproxy']:
|
||
assert marker in logrotate, marker
|
||
repo_script = (root / 'debian/build-repository.sh').read_text()
|
||
for marker in ['dpkg-scanpackages', 'apt-ftparchive', 'Packages.gz', 'RUSTPROXY_REPO_GPG_KEY', 'rustproxy.linuxiarz.pl']:
|
||
assert marker in repo_script, marker
|
||
grafana = json.loads((root / 'grafana/rustproxy-overview.json').read_text())
|
||
assert grafana['uid'] == 'rustproxy-0170' and grafana['schemaVersion'] >= 39
|
||
expressions = '\n'.join(target.get('expr','') for panel in grafana['panels'] for target in panel.get('targets',[]))
|
||
for marker in ['rust_proxy_process_rss_bytes', 'rust_proxy_cache_transient_bytes', 'rust_proxy_backend_latency_seconds', 'rust_proxy_stream_active', 'rust_proxy_log_sink_queue_dropped_total']:
|
||
assert marker in expressions, marker
|
||
# 0.19.0 compression/protocol/dashboard UX regressions.
|
||
compression_source = (root / "src/compression.rs").read_text()
|
||
metrics_source = (root / "src/metrics.rs").read_text()
|
||
admin_source = (root / "src/admin/mod.rs").read_text()
|
||
style_source = (root / "src/admin/web/style.css").read_text()
|
||
docker_ui = (root / "src/admin/web/docker.js").read_text()
|
||
for marker in ["pub enum AppliedEncoding", "AppliedEncoding::Gzip", "AppliedEncoding::Brotli", "AppliedEncoding::Zstd", "AppliedEncoding::Deflate"]:
|
||
assert marker in compression_source, marker
|
||
assert "quality_weights_can_select_each_enabled_codec" in compression_source
|
||
diagnostics_source = (root / "src/diagnostics.rs").read_text() + (root / "src/system_stats/mod.rs").read_text() + (root / "src/system_stats/linux.rs").read_text()
|
||
for marker in ["pub struct NetworkInterfaceView", "pub network_interfaces: Vec<NetworkInterfaceView>", "sysinfo::Networks::new_with_refreshed_list", "system.network_stats", "collect_network_systemstat", "normalize_network_interfaces", "merge_network_interfaces"]:
|
||
assert marker in diagnostics_source, marker
|
||
assert "interfaces.truncate(64)" not in diagnostics_source, "network inventory must not silently truncate interfaces"
|
||
for marker in ["pub http1_requests: AtomicU64", "pub fn record_compression", "each_codec_has_an_independent_counter", "http1_requests_total"]:
|
||
assert marker in metrics_source, marker
|
||
assert "state.metrics.record_compression(encoding)" in proxy_source
|
||
assert "sparkline-h1" in html and "sparkline-h2" in html and "sparkline-h3" in html
|
||
assert 'id="open-proxy"' not in html and '$("open-proxy")' not in app_js
|
||
assert "protocolSamples" in app_js and "m.http1_requests" in app_js
|
||
assert "renderNetworkInterfaceCharts" in app_js and "networkInterfaceSamples" in app_js
|
||
assert 'id="network-interface-charts"' in html
|
||
assert 'compressionCountersHelp' not in html
|
||
assert "dockerOpenApp" in docker_ui and "proxiedUrl(agent, hostname)" in docker_ui
|
||
# 0.19.0 Docker-vhost controls: chip aliases, default agent ACL, redirect/WebSockets/cache.
|
||
docker_source = (root / "src/docker.rs").read_text()
|
||
management_ui = (root / "src/admin/web/management.js").read_text()
|
||
for marker in ["function aliasEditor(", "host-chip-editor docker-alias-editor", ".docker-force-ssl", ".docker-websockets", ".docker-cache-enabled", ".docker-agent-acl-profile"]:
|
||
assert marker in docker_ui, marker
|
||
for marker in ['"force_ssl": assignment.force_ssl.unwrap_or(false)', '"websockets": assignment.websockets', '"cache": {"enabled": assignment.cache_enabled}', 'assignment.acl_profile_id.clone().or_else(|| agent.acl_profile_id.clone())']:
|
||
assert marker in docker_source, marker
|
||
assert "agent.acl_profile_id === profile.id" in management_ui
|
||
assert 'payload["backend_pool_domains"]' in admin_source and "status.backend_pool_domains" in vhosts_js
|
||
assert 'body[data-density="compact"]' in style_source and '--ui-control: 22px' in style_source
|
||
assert 'rust_proxy_http1_requests_total' in expressions
|
||
vhost_source = (root / "src/vhost.rs").read_text()
|
||
for marker in ["DEFAULT_PROXY_CONNECT_TIMEOUT_SECONDS: u64 = 10", "DEFAULT_PROXY_RESPONSE_HEADER_TIMEOUT_SECONDS: u64 = 120", "DEFAULT_PROXY_READ_TIMEOUT_SECONDS: u64 = 60", "DEFAULT_PROXY_SEND_TIMEOUT_SECONDS: u64 = 60", "proxy_timeouts_have_explicit_safe_defaults_but_keep_null_compatibility"]:
|
||
assert marker in vhost_source, marker
|
||
assert 'defaultProxyTimeouts = () => ({ connect_timeout_seconds: 10, response_header_timeout_seconds: 120, read_timeout_seconds: 60, send_timeout_seconds: 60 })' in vhosts_js
|
||
assert 'actions.append(enabledLabel, stateBadge, remove)' in docker_ui
|
||
assert 'row._readListeners = listenerControl.read' in docker_ui and '.docker-agent-listeners' not in docker_ui
|
||
assert 'docker-agent-status-badge' in style_source and 'justify-content: center' in style_source
|
||
assert '.docker-agent-grid>label' in style_source and '#docker-agent-add' in style_source and 'margin-top: 14px' in style_source
|
||
assert '<details class="mini-live-chart mini-live-chart-collapsible">' not in html
|
||
assert 'data-i18n="responseLatencyHistory"' in html and 'data-i18n="responseLatencyHint"' in html and 'data-i18n-aria="responseLatencyInfo"' in html
|
||
assert 'viewBox: "0 0 300 60"' in app_js and 'M0 15H300 M0 30H300 M0 45H300' in app_js
|
||
assert '.network-interface-row {' in style_source and 'background: transparent' in style_source
|
||
assert 'function checkboxField(key, control)' in docker_ui and 'checkboxField("dockerPreserveHost", preserve)' in docker_ui and 'checkboxField("dockerAclInline", inline)' in docker_ui
|
||
assert 'label = el("label", undefined, "check vh-check")' in docker_ui and 'check compact-check' not in docker_ui.split('function checkboxField(key, control)', 1)[1].split('function aliasEditor(', 1)[0]
|
||
assert '.docker-route-grid input:not([type="checkbox"])' in style_source and '.docker-checkbox-field .vh-check {' in style_source
|
||
assert 'docker-switch-row' in docker_ui and '.docker-switch-row {' in style_source and 'repeat(5, minmax(110px, 160px))' in style_source
|
||
assert '.docker-passive-health-editor .docker-health-grid {' in style_source
|
||
assert '.docker-health-grid input:not([type="checkbox"])' in style_source and '.docker-passive-health-editor .docker-checkbox-field input[type="checkbox"]' in style_source
|
||
assert 'id="sparkline-transform"' in html and 'id="rate-transform"' in html and 'liveTransformed' in html
|
||
assert 'payload["transform_metrics"] = json!(state.transform_metrics.total())' in admin_source
|
||
for marker in ['payload["transform_resources"]', 'payload["memory_accounting"]', 'non_cache_rss_includes_transform']:
|
||
assert marker in admin_source, marker
|
||
assert 'const transformed = s.transform_metrics || {}' in app_js and 'transformSamples.push(rates.transform)' in app_js
|
||
for marker in ['status.memory_accounting || {}', 'memory.cache_hot_bytes', 'memory.transform_reserved_bytes', 'cache-cold-storage']:
|
||
assert marker in app_js, marker
|
||
for marker in ['data?.resources || {}', 'tp-memory', 'tp-inflight']:
|
||
assert marker in transform_panel_js, marker
|
||
assert 'function uptime(value)' in app_js and 'setOptionalMetric("process-uptime", status.uptime_seconds != null' in app_js
|
||
assert 'connection-uptime-footer' not in html and 'id="process-uptime"' in html
|
||
assert 'id="panel-transform"' in html and 'data-panel="transform"' in html and '/assets/transform-panel.js' in html
|
||
assert '/api/transform' in transform_panel_js and 'tp-cache-hits' in transform_panel_js and 'transform-reason-list' in transform_panel_js
|
||
assert '--transform-chart:' in css and 'stroke: var(--transform-chart)' in css and 'background: var(--transform-chart)' in css
|
||
assert '.cache-transform-badge {' in css and 'border: 1px solid var(--pill-line)' in css and 'border-radius: 3px' in css
|
||
assert 'pub transformed: bool' in cache_source and 'annotate_transformed' in cache_source and 'format!("{base}; transformed")' in cache_source
|
||
assert 'x-cache-status' in cache_source and 'Cache-Status (RFC 9211)' in html
|
||
assert 'generated_vhost_inherits_global_server_header_policy' in docker_source and 'effective.vhosts[0].server_header.is_none()' in docker_source
|
||
assert '.insert("x-rustproxy-backend-time", value)' in proxy_source and '.insert("x-rust-proxy-backend-time", value)' not in proxy_source
|
||
assert 'x-rustproxy-backend-time' in (root / "src/headers.rs").read_text()
|
||
# Expanded upstream connection diagnostics must retain nested Hyper/rustls/io causes.
|
||
for marker in [
|
||
"fn format_error_chain(",
|
||
"MAX_UPSTREAM_ERROR_DETAIL: usize = 2048",
|
||
"error_chain={chain}",
|
||
"request_origin={}",
|
||
"tls_verify={}",
|
||
"upstream_error_chain_keeps_nested_connector_cause",
|
||
"tracing::warn!(",
|
||
]:
|
||
assert marker in proxy_source, marker
|
||
assert "pub tls_verify: bool" in config_source and "tls_verify: backend.tls_verify" in config_source
|
||
assert 'data-i18n="logUpstreamError"' in html and 'e.upstream_error || "-"' in management_ui
|
||
assert '.route("/api/preferences", get(preferences).put(update_preferences))' in admin
|
||
assert 'admin_preferences' in (root / "migrations/0004_admin_preferences.sql").read_text()
|
||
assert 'id="log-column-options"' in html and 'data-log-column="upstream_error"' in html
|
||
assert 'check log-column-option' in management_ui
|
||
assert 'input[type="checkbox"],\ninput[type="radio"] {' in style_source and 'width: 15px;' in style_source and '.log-column-option input[type="checkbox"]' not in style_source
|
||
assert '.log-tools input:not([type="checkbox"]):not([type="radio"])' in style_source
|
||
assert '.log-tools input {\n' not in style_source, "Live Logs text-input rules must not match checkbox/radio controls"
|
||
footer_block = app_js.split('function renderFooter(s) {', 1)[1].split('let lastLogs', 1)[0]
|
||
assert 'SQLite / HTTP/1.1' not in footer_block and 'coldQ:' not in footer_block and 'streamFallback' not in footer_block
|
||
assert '`${apiLatency} / ${t("logsDropped")}: ${s.logs_dropped}`' in footer_block
|
||
assert 'update?.update_available && update.latest_version' in footer_block and 'footer-update' in footer_block
|
||
assert '.page-footer .footer-update {' in style_source
|
||
assert '.logs-dialog {' in style_source and 'overflow: visible' in style_source
|
||
assert '/api/preferences' in app_js and 'live_log_columns' in management_ui and 'ADMIN_THEMES = new Set(["dark", "light"])' in app_js
|
||
assert 'id="ui-theme"' in html and 'data-i18n="themeLight"' in html
|
||
assert 'html[data-theme="light"] {' in style_source and '--field-bg:' in style_source and '--sidebar-bg:' in style_source
|
||
assert 'overflow: visible;' in style_source and 'max-height: min(38dvh, 220px)' not in style_source
|
||
assert 'body[data-density="compact"] input:not([type="checkbox"]):not([type="radio"])' in style_source
|
||
assert 'input:not([type="checkbox"]):not([type="radio"]),\nselect,\ntextarea {' in style_source
|
||
assert '#[serde(default = "default_admin_theme")]' in admin_source and 'matches!(preferences.theme.as_str(), "dark" | "light")' in admin_source
|
||
# 0.22.3 dashboard/config/cache/log-history regressions.
|
||
assert 'bindSettingsForm("pages-form", next => { next.pages = global.read(); }, "pages")' in management_ui
|
||
assert '"pages" => {' in admin_source and 'config.pages =' in admin_source
|
||
assert 'default_pages_section_accepts_the_standard_value_payload' in admin_source
|
||
logging_source = (root / "src/logging.rs").read_text()
|
||
assert 'pub const LIVE_WINDOW: usize = 256;' in logging_source
|
||
assert 'pub const HISTORY_CAPACITY: usize = 4096;' in logging_source
|
||
assert 'retained_history_is_larger_than_the_live_snapshot_but_still_bounded' in logging_source
|
||
assert 'id="logs-load-history"' in html and 'api("/api/logs?limit=4096")' in management_ui
|
||
assert 'const STATUS_NAMES = { 200: "OK"' in management_ui and '404: "Not Found"' in management_ui and 'statusLabel(code)' in management_ui
|
||
assert 'let paused = null, expanded = null' in management_ui and 'function mergeExpanded(data)' in management_ui and 'entries.length >= 4096' in management_ui
|
||
assert 'dialog.addEventListener("close", () => { if (pinning) return; expanded = null; paused = null;' in management_ui
|
||
cache_source = read_cache_source()
|
||
assert 'hash_scheme.as_bytes()' in cache_source and 'cache_key_separates_frontend_scheme' in cache_source
|
||
assert 'function rateBytes(value)' in app_js and 'if (value === 0) return "0.0 KiB";' in app_js and 'return "<0.1 KiB";' in app_js
|
||
assert 'function backendTrafficRate(' in vhosts_js and 'member.bytes_in' in vhosts_js and 'member.bytes_out' in vhosts_js and 'rateBytes(memberTraffic)' in vhosts_js and 'rateBytes(d.traffic)' in vhosts_js
|
||
cache_panel_js = (root / "src/admin/web/cache-panel.js").read_text()
|
||
traffic_panel_js = (root / "src/admin/web/traffic-panel.js").read_text()
|
||
assert 'id="panel-traffic"' in html and 'data-panel="traffic"' in html
|
||
for marker in ['/api/traffic', 'trafficTopCountries', 'trafficTopUserAgents', 'trafficTopClientIps', 'traffic-donut', 'rustProxyTrafficPanel']:
|
||
assert marker in traffic_panel_js or marker in html, marker
|
||
traffic_source = (root / "src/traffic.rs").read_text()
|
||
for marker in ['QUEUE_CAPACITY: usize = 2048', 'TOP_CAPACITY: usize = 32', 'MAX_VHOSTS: usize = 64', 'self.tx.try_send(sample)', 'client_ips: TopTable']:
|
||
assert marker in traffic_source, marker
|
||
assert 'if (envelope) render();' in (root / "src/admin/web/web-server.js").read_text()
|
||
assert 'pub capture_user_agent: bool' in logging_source and 'pub capture_country: bool' in logging_source
|
||
assert 'spawn_blocking(crate::diagnostics::trim_allocator)' in (root / 'src/runtime.rs').read_text()
|
||
assert 'fn trim_hot_idle(&self)' in cache_source and 'hot_auto_trim_target_percent' in cache_source
|
||
assert 'id="cache-domain-filter"' in html and 'cacheDomainFilterEnabled' in html and 'cacheDomainFilterDisabled' in html
|
||
assert 'const visibleDomains = (data, groups) =>' in cache_panel_js and 'scoped?.settings?.enabled ?? data.settings?.enabled ?? false' in cache_panel_js
|
||
assert 'groups = d.domains || [], shownGroups = visibleDomains(data, groups)' in cache_panel_js
|
||
assert 'replaceChildren(...shownGroups.map(row =>' in cache_panel_js
|
||
balance_source = (root / 'src/balance.rs').read_text()
|
||
assert 'traffic_rate_drops_to_zero_after_an_idle_sample_window' in balance_source and 'if delta == 0 {' in balance_source and 'sample.rate = 0.0;' in balance_source
|
||
all_admin_js = "\n".join(path.read_text() for path in (root / "src/admin/web").glob("*.js"))
|
||
assert not re.search(r'(?<![A-Za-z])(?:window\.)?(?:alert|confirm)\s*\(', all_admin_js), "native browser alert/confirm returned"
|
||
assert 'function appConfirm(message, options = {})' in app_js and 'function apiErrorMessage(path, response, value)' in app_js
|
||
assert '.app-confirm-dialog {' in style_source and '.login-section #auth-message.error {' in style_source
|
||
assert 'classList.toggle("error", Boolean(value))' in login_js
|
||
assert 'class="auth-page auth-loading"' in (root / 'src/admin/web/login.html').read_text() and 'const reveal = () => document.body.classList.remove("auth-loading")' in login_js
|
||
assert '"/docker-vhosts"' in login_js and 'settingsTabs = new Set' in login_js and '`/settings?tab=${encodeURIComponent(tab)}`' in login_js
|
||
assert 'class="app-loading"' in html and 'document.documentElement.classList.remove("app-loading")' in app_js
|
||
assert 'function withFormSubmitState(form, submitter, task)' in app_js and 'button.textContent = t("saving")' in app_js
|
||
assert 'function fieldError(target, message)' in app_js and 'function showFieldError(error, form = null)' in app_js
|
||
assert 'function authReturnDestination()' in app_js and 'pendingAuthDestination || authReturnDestination()' in app_js
|
||
assert '$("login").hidden = navigating' in app_js and 'id="auth-message"' in html
|
||
assert 'settings-tabs button.has-unsaved::after' in style_source and 'function syncSettingsDirtyIndicators()' in app_js
|
||
assert 'setSettingsDirty("config-editor")' in app_js and '!settingsDirty.has("config-editor")' in app_js
|
||
assert 'throw fieldError($("tls-policy-12")' in settings_js
|
||
integrations_ui_0194 = (root / 'src/admin/web/integrations.js').read_text()
|
||
docker_ui_0194 = (root / 'src/admin/web/docker.js').read_text()
|
||
assert 'readAddresses.input' in integrations_ui_0194
|
||
assert 'form.reset(); window.rustProxyCertificates?.close();' in integrations_ui_0194
|
||
assert '$("cert-import-pem").value = ""' not in integrations_ui_0194 and '$("cert-import-key").value = ""' not in integrations_ui_0194
|
||
assert 'throw fieldError(row.querySelector(".docker-agent-listener-choices")' in docker_ui_0194
|
||
assert '#docker-agents-form>.form-actions {' in style_source and 'justify-content: flex-start' in style_source
|
||
deploy_source = (root / "scripts/deploy.py").read_text()
|
||
assert "SHELL_PATH_PROFILE = Path('/etc/profile.d/rustproxy-path.sh')" in deploy_source
|
||
assert 'install_shell_path(prefix)' in deploy_source and 'check_shell_path(prefix)' in deploy_source and "'shell_path_action':'install' if args.mode == 'install' else 'check-only'" in deploy_source
|
||
assert 'grid-template-columns: minmax(0, 1fr)' in style_source and '.log-column-option:hover' in style_source
|
||
assert '.check>span {' in style_source and 'transform: translateY(-1px)' in style_source and '.cache-toolbar-status .check' in style_source and '.docker-toolbar .check' in style_source and 'grid-column: span 3' in style_source
|
||
assert '.docker-alias-field>span:first-child {' in style_source and 'margin-bottom: 0' in style_source and '.docker-alias-editor {' in style_source and 'min-height: 38px' in style_source and 'height: 30px' in style_source
|
||
assert 'field.append(box, el("small", t("dockerAliasesHelp")' not in docker_ui
|
||
assert 'readOnly: !!editing' not in vhosts_js and 'vhIdRenameConfirm' in vhosts_js and 'v.id === value.id && v.id !== editing' in vhosts_js
|
||
assert 'enforce_local_page_no_cache' in proxy_source and 'LocalGeneratedResponse' in proxy_source and 'get::<crate::pages::PageServed>().is_none()' in proxy_source
|
||
assert 'locally_generated_pages_remove_cache_metadata' in proxy_source and '"x-cache-reason"' in proxy_source and '"cache-status"' in proxy_source
|
||
assert 'local_page_method_rejection_is_still_marked_non_cacheable' in pages_rust and 'response.extensions_mut().insert(PageServed);' in pages_rust
|
||
assert "rel.as_posix() == 'docker-agent/docker-agent'" in (root / 'make_zip.py').read_text()
|
||
screenshots = {path.name for path in (root / "docs/screenshots").iterdir() if path.is_file()}
|
||
assert "README.md" in screenshots, screenshots
|
||
assert screenshots <= {"README.md", "login.png", "location-headers.png"}, screenshots
|
||
assert 'Historical or version-labelled context' in (root / "docs/README.md").read_text()
|
||
assert '<span class="version">' not in html, "duplicate top-right version label returned"
|
||
assert '<div class="edition">INDEPENDENT BY DESIGN <span>' not in html, "release version returned beside the dashboard brand"
|
||
assert '<div class="edition">INDEPENDENT BY DESIGN</div>' in html
|
||
# tls_verify=false must not feed legacy appliance certificates back through webpki.
|
||
upstream_source = (root / "src/upstream.rs").read_text(encoding="utf-8")
|
||
assert "BackendCertificateVerificationDisabled { supported_schemes }" in upstream_source
|
||
disabled_verifier = upstream_source.split("impl ServerCertVerifier for BackendCertificateVerificationDisabled", 1)[1].split("pub fn client(", 1)[0]
|
||
assert disabled_verifier.count("Ok(HandshakeSignatureValid::assertion())") == 2
|
||
assert "WebPkiServerVerifier" not in disabled_verifier
|
||
assert '.docker-route-grid {' in style_source and 'align-items: start' in style_source and '.docker-acl-grid {' in style_source
|
||
|
||
# 0.22.3 cache-modal, RFC8441/h2c and active-tunnel contracts.
|
||
server_source = (root / "src/server.rs").read_text()
|
||
websocket_source = (root / "src/proxy/websocket.rs").read_text()
|
||
vhost_source = (root / "src/vhost.rs").read_text()
|
||
integrations_0221 = (root / "src/admin/web/integrations.js").read_text()
|
||
http2_test = (root / "tests/http2.rs").read_text()
|
||
transport_test = (root / "tests/transport_config.rs").read_text()
|
||
for marker_0221 in [
|
||
'id="cache-invalidation-open"', 'id="cache-bans-open"',
|
||
'id="cache-invalidation-dialog"', 'id="cache-bans-dialog"',
|
||
'id="cache-toolbar-operation-status"', 'id="tunnels-dialog"',
|
||
'id="tunnel-kill-reason"', 'id="metric-ws-h1"', 'id="metric-ws-h2"',
|
||
'id="metric-ws-h3"', 'id="metric-ws-total"',
|
||
]:
|
||
assert marker_0221 in html, marker_0221
|
||
for marker_0221 in [
|
||
'const operationMessage = (id, value = "", error = false) => {',
|
||
'if (!box) return;', 'cache-toolbar-operation-status',
|
||
'runInvalidation(payload, messageTarget = "cache-invalidation-status")',
|
||
'async function copyText(value)', 'function makeCopyCell(value)',
|
||
'target = makeCopyCell(targetValue)', 'makeCopyCell(keyTags)',
|
||
]:
|
||
assert marker_0221 in cache_panel_js, marker_0221
|
||
assert html.count('class="stats-grid"') >= 1
|
||
assert 'stats-grid transport-stats' not in html and 'transport-websocket' not in html
|
||
assert 'transport-action' not in html and '.transport-grid .transport-action' not in style_source
|
||
assert '#panel-cache .cache-copy-cell' in style_source and 'cursor: copy' in style_source
|
||
assert '.operation-message' in style_source and '.operation-message.error' in style_source
|
||
for marker_0221 in [
|
||
'api("/api/tunnels")', 'tunnel-kill-reason', 'tunnelKillReasonDefault',
|
||
'api(`/api/tunnels/${encodeURIComponent(item.id)}/kill`', 'startTunnelRefresh()',
|
||
]:
|
||
assert marker_0221 in app_js, marker_0221
|
||
assert 'pub websocket_rfc8441: bool' in vhost_source
|
||
assert 'pub h2c: bool' in vhost_source
|
||
assert 'h2c is available only on cleartext listeners' in vhost_source
|
||
for marker_0221 in [
|
||
'websocket_rfc8441_on(&self, local: SocketAddr, tls: bool)',
|
||
'binding.h2c', 'HTTP/2-capable listener',
|
||
'websocket_rfc8441: host.websocket_rfc8441',
|
||
]:
|
||
assert marker_0221 in config_source, marker_0221
|
||
for marker_0221 in [
|
||
'HTTP2_PRIOR_KNOWLEDGE_PREFACE', 'h2_prior_knowledge_prefix',
|
||
'builder.enable_connect_protocol()', 'websocket_rfc8441_on(local, false)',
|
||
'websocket_rfc8441_on(local, true)',
|
||
]:
|
||
assert marker_0221 in server_source, marker_0221
|
||
for marker_0223 in [
|
||
'StatusCode::SWITCHING_PROTOCOLS', 'H2cUpgradeIo', 'HTTP2-Settings',
|
||
'h2c_upgrade_bootstraps_request_as_http2_stream_one',
|
||
'malformed_h2c_upgrade_is_rejected_before_switching_protocols',
|
||
]:
|
||
assert marker_0223 in server_source, marker_0223
|
||
for memory_marker_0230 in [
|
||
'H2ConnectionState', 'H2ActivityIo', 'ActiveHttp2Connection',
|
||
'http2_active_connections',
|
||
]:
|
||
assert memory_marker_0230 in server_source or memory_marker_0230 in metrics_source, memory_marker_0230
|
||
cache_fill_0230 = (root / 'src/cache/fill.rs').read_text()
|
||
assert 'let body_allocation = self.data.capacity().max(self.data.len());' in cache_fill_0230
|
||
assert 'let cost = hot_entry_cost(' in cache_fill_0230
|
||
assert 'fn hot_entry_cost(' in cache_source
|
||
for marker_0221 in [
|
||
'pub fn validate_h2(request: &Request)', 'hyper::ext::Protocol',
|
||
'bridge_h2_to_h1', 'pub struct TunnelInfo', 'pub struct TunnelRegistry',
|
||
'register_pending', 'spawn_h2_idle_watch', 'h2-rfc8441-h1-upstream',
|
||
'h2-rfc8441-h2-upstream', 'h3-rfc9220',
|
||
]:
|
||
assert marker_0221 in websocket_source, marker_0221
|
||
for marker_0221 in [
|
||
'websocket_upstream_h2', 'validate_h2_response', 'wrap_h2_request_body',
|
||
'wrap_h2_response_body', 'bridge_h2_to_h1',
|
||
]:
|
||
assert marker_0221 in proxy_source, marker_0221
|
||
for marker_0221 in [
|
||
'.route("/api/tunnels", get(active_tunnels))',
|
||
'"/api/tunnels/{id}/kill"', '"tunnel.kill"',
|
||
'reason.len() > 256', 'state.tunnel_registry.kill(id)',
|
||
]:
|
||
assert marker_0221 in admin, marker_0221
|
||
for marker_0221 in [
|
||
'websocket_h1_tunnels_total', 'websocket_h1_tunnels_active',
|
||
'websocket_h2_rfc8441_tunnels_total', 'websocket_h2_rfc8441_tunnels_active',
|
||
'websocket_h3_rfc9220_tunnels_total', 'websocket_h3_rfc9220_tunnels_active',
|
||
]:
|
||
assert marker_0221 in metrics_source, marker_0221
|
||
for marker_0221 in [
|
||
'"websocket_rfc8441"', '"/api/tunnels"', '"/api/tunnels/{id}/kill"',
|
||
'"TunnelInfo"', '"TunnelKillRequest"',
|
||
]:
|
||
assert marker_0221 in openapi, marker_0221
|
||
for marker_0233 in [
|
||
'pub websocket_legacy_support: bool',
|
||
'legacy WebSocket compatibility requires WebSocket support to be enabled',
|
||
]:
|
||
assert marker_0233 in vhost_source, marker_0233
|
||
for marker_0233 in [
|
||
'pub websocket_legacy_support: bool',
|
||
'websocket_legacy_support: host.websocket_legacy_support',
|
||
]:
|
||
assert marker_0233 in config_source, marker_0233
|
||
for marker_0233 in [
|
||
'fn legacy_websocket_origin(',
|
||
'websocket_legacy_support && !websocket_upstream_h2',
|
||
'request.headers_mut().insert("origin", origin)',
|
||
'websocket::validate_response(',
|
||
]:
|
||
assert marker_0233 in proxy_source, marker_0233
|
||
for marker_0233 in [
|
||
'legacy_support: bool',
|
||
'legacy 101 response declared a non-zero body',
|
||
'legacy_upstream_response_accepts_old_101_syntax_but_keeps_accept_proof_strict',
|
||
]:
|
||
assert marker_0233 in websocket_source, marker_0233
|
||
for marker_0233 in [
|
||
'websocket_legacy_support', 'vhWebsocketLegacySupport', 'vhWebsocketLegacySupportHelp',
|
||
]:
|
||
assert marker_0233 in js or marker_0233 in openapi, marker_0233
|
||
assert 'hyper::ext::Protocol::from_static(protocol)' in http2_test
|
||
assert 'rfc8441_requires_an_h2_capable_listener_and_h2c_is_explicit' in transport_test
|
||
assert 'h2c' in integrations_0221 and 'H2C' in integrations_0221
|
||
transport_doc = (root / "docs/TRANSPORT.md").read_text()
|
||
assert 'prior knowledge' in transport_doc and 'Upgrade: h2c' in transport_doc and 'stream 1' in transport_doc
|
||
assert (root / "docs/V0.24.1_AUDIT.md").is_file() and (root / "docs/V0.24.0_AUDIT.md").is_file() and (root / "docs/V0.23.2_AUDIT.md").is_file() and (root / "docs/V0.23.1_AUDIT.md").is_file() and (root / "docs/V0.23.0_AUDIT.md").is_file() and (root / "docs/V0.22.3_AUDIT.md").is_file()
|
||
|
||
# 0.22.3 replay-safe retry and ordinary H2 upstream contracts.
|
||
balance_0222 = (root / "src/balance.rs").read_text()
|
||
for marker_0222 in [
|
||
"pub struct RetryPolicy", "pub max_retries: u8", "pub retry_statuses: Vec<u16>",
|
||
"pub fn select_retry", "retry_attempts_total", "retry_successes_total",
|
||
"failover_selection_never_reuses_an_attempted_backend",
|
||
]:
|
||
assert marker_0222 in balance_0222, marker_0222
|
||
for marker_0222 in [
|
||
"fn retry_safe_method", "Method::GET", "Method::HEAD", "Method::OPTIONS",
|
||
"request_body_empty", "route.backend_id.is_none()", "select_retry_wait(",
|
||
"upstream_request_version", "retrying replay-safe request on another backend",
|
||
"explicit_http2_backend_uses_http2_request_version",
|
||
]:
|
||
assert marker_0222 in proxy_source, marker_0222
|
||
upstream_0222 = (root / "src/upstream.rs").read_text()
|
||
assert "explicit_h2c_backend_uses_http2_prior_knowledge" in upstream_0222
|
||
vhosts_0222 = (root / "src/admin/web/vhosts.js").read_text()
|
||
for marker_0222 in ["retry-max", "retry-connect", "retry-timeouts", "retry-statuses"]:
|
||
assert marker_0222 in vhosts_0222, marker_0222
|
||
for marker_0222 in [
|
||
'"max_retries"', '"retry_connect_errors"', '"retry_timeouts"', '"retry_statuses"'
|
||
]:
|
||
assert marker_0222 in openapi, marker_0222
|
||
for marker_0222 in [
|
||
"max_concurrent_requests", "queue_timeout_ms", "capacity_rejections_total",
|
||
"dns_errors_total", "last_failure_detail", "last_recovered_ms",
|
||
"backend_concurrency_limit_queues_then_rejects_and_recovers",
|
||
]:
|
||
assert marker_0222 in balance_0222, marker_0222
|
||
assert "let mut item = serde_json::Map::new();" in balance_0222
|
||
assert 'json!({"id":m.definition.id, "url":m.definition.url' not in balance_0222
|
||
for marker_0222 in ["member-max-concurrent", "member-queue-timeout", "vhLastFailure", "vhLastRecovery"]:
|
||
assert marker_0222 in vhosts_0222 or marker_0222 in html, marker_0222
|
||
for marker_0222 in ["CertificateMetadata", "certificate_public_metadata", "x509_time_parser_handles_utc_and_generalized_time_with_offsets"]:
|
||
assert marker_0222 in (root / "src/tls.rs").read_text(), marker_0222
|
||
for marker_0222 in [
|
||
'"max_concurrent_requests"', '"queue_timeout_ms"', '"certificate_issuer"',
|
||
'"days_remaining"', '"expiry_status"', '"last_renewal_error"'
|
||
]:
|
||
assert marker_0222 in openapi, marker_0222
|
||
|
||
for path in (root / "src/admin/web").glob("*.js"):
|
||
subprocess.run(["node", "--check", str(path)], check=True)
|
||
connection = sqlite3.connect(":memory:")
|
||
connection.executescript((root / "migrations/0002_admin.sql").read_text())
|
||
connection.executescript((root / "migrations/0003_certificate_renewals.sql").read_text())
|
||
connection.executescript((root / "migrations/0004_admin_preferences.sql").read_text())
|
||
connection.executescript((root / "migrations/0005_certificate_api.sql").read_text())
|
||
connection.executescript((root / "migrations/0006_certificate_api_credentials.sql").read_text())
|
||
connection.executescript((root / "migrations/0007_secret_store.sql").read_text())
|
||
connection.execute("PRAGMA user_version = 7")
|
||
assert connection.execute("PRAGMA quick_check").fetchone()[0] == "ok"
|
||
assert connection.execute("SELECT COUNT(*) FROM auth_throttle WHERE id=1").fetchone()[0] == 1
|
||
assert connection.execute("SELECT COUNT(*) FROM certificate_renewals").fetchone()[0] == 0
|
||
assert connection.execute("SELECT COUNT(*) FROM admin_preferences").fetchone()[0] == 0
|
||
assert connection.execute("PRAGMA user_version").fetchone()[0] == 7
|
||
connection.close()
|
||
for marker_0223 in [
|
||
'"/api/certificates/reload"', 'certificate_reload',
|
||
'"/api/certificates/deploy"', 'certificate_deploy', 'certificate.deploy',
|
||
'certificate.reload', 'reload_certificates().await',
|
||
'"operation": "unchanged"', 'config.tls.certificates.push(crate::tls::Certificate',
|
||
]:
|
||
assert marker_0223 in admin, marker_0223
|
||
runtime_0223 = (root / 'src/runtime.rs').read_text()
|
||
for marker_0223 in ['pub async fn reload_certificates', 'configured certificates reloaded from disk']:
|
||
assert marker_0223 in runtime_0223, marker_0223
|
||
main_0223 = (root / 'src/main.rs').read_text()
|
||
for marker_0223 in ['TlsCommand::Deploy', '/api/certificates/deploy', '/api/certificates/reload', 'read_external_pem']:
|
||
assert marker_0223 in main_0223, marker_0223
|
||
tls_0223 = (root / 'src/tls.rs').read_text()
|
||
for marker_0223 in ['pub fn stage_external_pair', 'pub fn reconcile_external_pair', 'ExternalPairDeployment::Unchanged', 'external-', 'remove_external_pair']:
|
||
assert marker_0223 in tls_0223, marker_0223
|
||
for marker_0223 in ['"/api/certificates/reload"', '"CertificateReloadResult"', '"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"']:
|
||
assert marker_0223 in openapi, marker_0223
|
||
assert 'api("/api/certificates/reload"' in app_js
|
||
assert 'certificateAdminRestartRequired' in app_js
|
||
|
||
# 0.24.0 H2 lifecycle contracts retained by 0.24.1.
|
||
server_0240 = (root / "src/server.rs").read_text()
|
||
settings_0240 = (root / "src/settings.rs").read_text()
|
||
config_0240 = (root / "src/config.rs").read_text()
|
||
for marker_0240 in [
|
||
"H2ConnectionState", "H2StreamLease", "H2TrackedBody",
|
||
"wait_for_h2_idle", "graceful_shutdown()", "H2ShutdownCause::Lifetime",
|
||
"h2_idle_connection_sends_goaway_before_closing",
|
||
"h2_lifetime_goaway_is_bounded_by_drain_timeout",
|
||
"h2_lifetime_allows_active_stream_to_finish_during_drain",
|
||
]:
|
||
assert marker_0240 in server_0240, marker_0240
|
||
for marker_0240 in ["Http2Connections", "idle_timeout_seconds", "max_connection_lifetime_seconds", "drain_timeout_seconds"]:
|
||
assert marker_0240 in settings_0240, marker_0240
|
||
assert "pub http2: crate::settings::Http2Connections" in config_0240
|
||
assert "pub http2_settings: Option<crate::settings::Http2Connections>" in (root / "src/vhost.rs").read_text()
|
||
assert "serve_frontend_with_http2" in main_source and "binding.http2_settings.unwrap_or_default()" in main_source
|
||
assert 'renderHttp2Settings' in integrations_js and 'collectHttp2Listeners' in integrations_js
|
||
assert '"Http2ConnectionSettings"' in openapi and '"max_connection_lifetime_seconds"' in openapi
|
||
|
||
# 0.24.1 shared statistics/runtime-info/settings-navigation contracts.
|
||
system_stats_0241 = (root / "src/system_stats/mod.rs").read_text()
|
||
linux_stats_0241 = (root / "src/system_stats/linux.rs").read_text()
|
||
main_0241 = (root / "src/main.rs").read_text()
|
||
admin_0241 = (root / "src/admin/mod.rs").read_text()
|
||
settings_js_0241 = (root / "src/admin/web/settings.js").read_text()
|
||
assert not (root / "src/system_stats/bsd.rs").exists(), "per-BSD parser backend must not return"
|
||
assert manifest["target"]['cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))']["dependencies"]["systemstat"] == "=0.2.7"
|
||
metrics_dep = manifest["dependencies"]["metrics-process"]
|
||
assert metrics_dep["version"] == "=2.4.3" and metrics_dep["default-features"] is False
|
||
assert metrics_dep["features"] == ["dummy"]
|
||
sysinfo_dep = manifest["dependencies"]["sysinfo"]
|
||
assert sysinfo_dep["version"] == "=0.38.4" and sysinfo_dep["default-features"] is False
|
||
assert sysinfo_dep["features"] == ["network"]
|
||
for marker_0241 in [
|
||
"systemstat::{Platform, System}", "metrics_process::collector::collect",
|
||
"sysinfo::Networks::new_with_refreshed_list", "collect_network_systemstat", "merge_network_interfaces",
|
||
"system_swap_total_bytes", "system_uptime_seconds", "process_virtual_memory_bytes",
|
||
"process_start_time_seconds", "max_fds", "FreeBSD", "OpenBSD", "NetBSD", "DragonFly BSD",
|
||
]:
|
||
assert marker_0241 in system_stats_0241, marker_0241
|
||
for marker_0241 in ["/proc/stat", "/proc/meminfo", "RssAnon", "VmData", "VmSwap", "pub(crate) fn augment"]:
|
||
assert marker_0241 in linux_stats_0241, marker_0241
|
||
for forbidden in ["sysctl", "netstat", "Command::new"]:
|
||
assert forbidden not in system_stats_0241, f"shared statistics adapter must not shell/parse per platform: {forbidden}"
|
||
for marker_0241 in ["installed_data_dir_candidates", '"/var/lib/rustproxy"', '"/var/db/rustproxy"', "resolve_installed_data_dir"]:
|
||
assert marker_0241 in main_0241, marker_0241
|
||
for marker_0241 in ['"runtime"', '"platform"', '"statistics_source"', '"data_directory"', '"database_path"']:
|
||
assert marker_0241 in admin_0241 or marker_0241 in openapi, marker_0241
|
||
assert 'id="process-platform"' in html and 'd.platform' in app_js
|
||
assert 'id="panel-logging"' not in html
|
||
assert 'id="settings-tab-logging"' in html and 'id="settings-page-logging"' in html and 'id="logging-form"' in html and 'id="error-log-form"' in html
|
||
assert 'data-i18n="errorLogHelp"' not in html
|
||
assert 'bindSettingsForm("error-log-form"' in app_js and '}, "logging", selectLoggingSection);' in app_js
|
||
assert 'const selectLoggingSection = next => ({ logging: next.logging, error_log: next.error_log });' in app_js
|
||
assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log")' in admin_0241
|
||
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
||
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
|
||
assert 'location.pathname === "/logging"' in settings_js_0241
|
||
assert '"RuntimeInformation"' in openapi and '"version": "0.25.0"' in openapi
|
||
|
||
# 0.24.1 compiler and hostname-cardinality regressions.
|
||
tls_0241 = (root / "src/tls.rs").read_text()
|
||
vhost_0241 = (root / "src/vhost.rs").read_text()
|
||
redirect_0241 = (root / "src/redirect.rs").read_text()
|
||
not_found_0241 = (root / "src/not_found.rs").read_text()
|
||
docker_0241 = (root / "src/docker.rs").read_text()
|
||
integrations_0241 = (root / "src/admin/web/integrations.js").read_text()
|
||
docker_js_0241 = (root / "src/admin/web/docker.js").read_text()
|
||
assert "future::Future" not in server_0240
|
||
assert "uptime.as_secs()" in system_stats_0241 and "whole_seconds()" not in system_stats_0241
|
||
for forbidden_0241 in [
|
||
"cert.hosts.len() <= 32", "settings.certificates.len() <= 64",
|
||
"hosts.len() <= 32", "domains.len() <= 16",
|
||
]:
|
||
assert forbidden_0241 not in tls_0241, forbidden_0241
|
||
for source_0241 in [vhost_0241, redirect_0241, not_found_0241]:
|
||
assert "hosts.len() <= 16" not in source_0241
|
||
assert "allowed_hosts.len() <= 32" not in settings_0240
|
||
assert "MAX_ALIASES" not in docker_0241 and "aliases.len() <= 32" not in docker_0241
|
||
assert "const add = (raw, changed = true) => { const value = normalize(raw); if (!value || values.includes(value)) return; values.push(value);" in integrations_0241
|
||
assert "values.length >= 32" not in docker_js_0241
|
||
for marker_0241 in [
|
||
"certificate_hostname_bindings_are_not_artificially_capped",
|
||
"acme_validation_has_no_rustproxy_domain_count_ceiling",
|
||
]:
|
||
assert marker_0241 in tls_0241, marker_0241
|
||
assert "vhost_hostname_count_is_not_artificially_capped" in vhost_0241
|
||
assert "administration_hostname_count_is_not_artificially_capped" in settings_0240
|
||
assert "docker_alias_count_is_not_artificially_capped" in docker_0241
|
||
openapi_0241 = json.loads(openapi)
|
||
for schema_name, property_path in [
|
||
("CertificateUpload", ("hosts",)),
|
||
("AcmeIssue", ("domains",)),
|
||
("DockerAssignment", ("aliases",)),
|
||
]:
|
||
node = openapi_0241["components"]["schemas"][schema_name]["properties"]
|
||
for part in property_path:
|
||
node = node[part]
|
||
assert "maxItems" not in node, (schema_name, property_path)
|
||
for schema_name in ["RedirectHost", "NotFoundHost"]:
|
||
node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"]
|
||
assert "maxItems" not in node, schema_name
|
||
|
||
print("PASS: 0.25.0 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v7 migrations.")
|
||
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|