v0.32.6_fix1
This commit is contained in:
1 parent
a3d183bd4b
commit
e3de084149
34 files changed
+559
-85
No files matched your search
@@ -0,0 +1,54 @@
|
||||
// Verifies real-status-to-badge mapping without guessing a configured Auto result.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
|
||||
const source = fs.readFileSync('src/admin/web/vhosts/render.js', 'utf8');
|
||||
const block = source.slice(source.indexOf('const VHOST_PROTOCOL_FRESH_MS = '), source.indexOf('function render() {'));
|
||||
assert(block.includes('function renderUpstreamProtocolBadges(status)'));
|
||||
const rustClient = fs.readFileSync('src/upstream/client.rs', 'utf8');
|
||||
const rustPool = fs.readFileSync('src/balance/pool_view.rs', 'utf8');
|
||||
assert(rustClient.includes('self.inner.observe_protocol(value.version())'));
|
||||
assert(rustClient.includes('let response = client.request(request).await;'));
|
||||
for (const name of ['upstream_protocol_observed','upstream_protocol_last_seen_ms','upstream_protocol_http1_total','upstream_protocol_http2_total']) {
|
||||
assert(rustPool.includes(`"${name}"`), name);
|
||||
}
|
||||
const en = JSON.parse(fs.readFileSync('langs/en_US.json', 'utf8')).translations;
|
||||
const pl = JSON.parse(fs.readFileSync('langs/pl_pl.json', 'utf8')).translations;
|
||||
for(const key of ['vhBackendProtocolRecent','vhBackendProtocolUnobserved']) assert(en[key] && pl[key]);
|
||||
const badge = (vhost, backend) => ({dataset:{upstreamProtocolVhost:vhost,upstreamProtocolBackend:backend},textContent:'',title:''});
|
||||
const visible = [badge('api','auto-backend'),badge('api','h1-backend'),badge('other','auto-backend')];
|
||||
const envelope = {config:{vhosts:[
|
||||
{id:'api',backends:[{id:'auto-backend',protocol:'auto'},{id:'h1-backend',protocol:'http1'}]},
|
||||
{id:'other',backends:[{id:'auto-backend',protocol:'auto'}]},
|
||||
]}};
|
||||
const ctx = {Date, Number, String, Map, envelope, document:{querySelectorAll:()=>visible},t:key=>en[key]};
|
||||
vm.createContext(ctx);
|
||||
vm.runInContext(block, ctx);
|
||||
const recent = Date.now() - 2000;
|
||||
let status = {backend_pools:[{id:'api',members:[
|
||||
{id:'auto-backend',upstream_protocol_observed:'h2',upstream_protocol_last_seen_ms:recent,upstream_protocol_http1_total:5,upstream_protocol_http2_total:9},
|
||||
{id:'h1-backend',upstream_protocol_observed:'h1',upstream_protocol_last_seen_ms:recent,upstream_protocol_http1_total:12},
|
||||
]}]};
|
||||
ctx.renderUpstreamProtocolBadges(status);
|
||||
assert.equal(visible[0].textContent,'auto-backend · AUTO → H2');
|
||||
assert.equal(visible[0].dataset.observed,'true');
|
||||
assert(visible[0].title.includes('H1: 5; H2: 9'));
|
||||
assert.equal(visible[1].textContent,'h1-backend · H1 → H1');
|
||||
assert.equal(visible[2].textContent,'auto-backend · AUTO → ?');
|
||||
assert.equal(visible[2].dataset.observed,'false');
|
||||
// Old/stale response must not masquerade as a recent ALPN observation.
|
||||
status.backend_pools[0].members[0].upstream_protocol_last_seen_ms = Date.now() - 3600000;
|
||||
ctx.renderUpstreamProtocolBadges(status);
|
||||
assert.equal(visible[0].textContent,'auto-backend · AUTO → ?');
|
||||
assert.equal(visible[0].dataset.observed,'false');
|
||||
assert(visible[0].title.startsWith(en.vhBackendProtocolUnobserved));
|
||||
// Fresh data for a similarly named backend from another vhost must not leak.
|
||||
ctx.renderUpstreamProtocolBadges({backend_pools:[{id:'other',members:[
|
||||
{id:'auto-backend',upstream_protocol_observed:'h1',upstream_protocol_last_seen_ms:recent}
|
||||
]}]});
|
||||
assert.equal(visible[0].textContent,'auto-backend · AUTO → ?');
|
||||
assert.equal(visible[2].textContent,'auto-backend · AUTO → H1');
|
||||
ctx.renderUpstreamProtocolBadges({backend_pools:[]});
|
||||
assert.equal(visible[2].textContent,'auto-backend · AUTO → ?');
|
||||
console.log('PASS: observed Auto/H1/H2 badges, freshness, cross-vhost isolation and unknown data');
|
||||
@@ -7,7 +7,7 @@ import tomllib
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
read = lambda path: (root / path).read_text(encoding='utf-8')
|
||||
version = tomllib.loads(read('Cargo.toml'))['package']['version']
|
||||
assert version == '0.32.5', version
|
||||
assert version == '0.32.6', version
|
||||
spec = json.loads(read('src/admin/openapi.json'))
|
||||
assert spec['info']['version'] == version
|
||||
paths = spec['paths']
|
||||
@@ -64,5 +64,5 @@ for language in ('en_US', 'pl_pl'):
|
||||
for key in ('certificateApiView', 'certificateApiEdit', 'certificateApiSave', 'certificateApiRegenerate',
|
||||
'certificateApiRegenerateConfirm', 'certificateApiTokenHidden', 'certificateApiCreatedAt'):
|
||||
assert strings.get(key), (language, key)
|
||||
assert '0.32.5' in read('docs/TEST_REPORT.md')
|
||||
assert '0.32.6' in read('docs/TEST_REPORT.md')
|
||||
print('PASS: 0.31.9 per-credential API routes, separated metadata editing/token regeneration, OpenAPI security, UI and locales (source-only).')
|
||||
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
|
||||
|
||||
preflight(root)
|
||||
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
||||
assert manifest["package"]["version"] == "0.32.5"
|
||||
assert manifest["package"]["version"] == "0.32.6"
|
||||
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
||||
h3dep = manifest["dependencies"]["h3"]
|
||||
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.20" and "quinn" in h3dep["features"]
|
||||
@@ -32,14 +32,14 @@ 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.32.5"' in lock
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.6"' 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.32.5",
|
||||
"CHANGELOG.md": "## 0.32.5",
|
||||
"ROADMAP.md": "## 0.32.5",
|
||||
"AGENTS.md": "Current source version: 0.32.5.",
|
||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.32.5",
|
||||
"README.md": "# RustProxy 0.32.6",
|
||||
"CHANGELOG.md": "## 0.32.6",
|
||||
"ROADMAP.md": "## 0.32.6",
|
||||
"AGENTS.md": "Current source version: 0.32.6.",
|
||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.32.6",
|
||||
}.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"
|
||||
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
|
||||
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.32.5"', '"/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']:
|
||||
for marker in ['"version": "0.32.6"', '"/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
|
||||
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
|
||||
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.32.5"' in openapi
|
||||
assert '"RuntimeInformation"' in openapi and '"version": "0.32.6"' in openapi
|
||||
|
||||
# 0.24.1 compiler and hostname-cardinality regressions.
|
||||
tls_0241 = rust_source(root, "src/tls.rs")
|
||||
|
||||
@@ -14,7 +14,7 @@ parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--require-lock', action='store_true', help='Require the post-upgrade Cargo.lock produced by cargo update')
|
||||
args = parser.parse_args()
|
||||
manifest = tomllib.loads((ROOT / 'Cargo.toml').read_text())
|
||||
assert manifest['package']['version'] == '0.32.5'
|
||||
assert manifest['package']['version'] == '0.32.6'
|
||||
assert manifest['package']['rust-version'] == '1.99'
|
||||
deps = manifest['dependencies']
|
||||
expected_requirements = {
|
||||
|
||||
@@ -51,7 +51,7 @@ assert '"/docs/content.json"' in admin
|
||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
||||
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
||||
assert readme.startswith("# RustProxy 0.32.5")
|
||||
assert readme.startswith("# RustProxy 0.32.6")
|
||||
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
||||
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
||||
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static source contract for DDNS ACL 0.32.5; NOT a Rust test."""
|
||||
"""Static source contract for DDNS ACL 0.32.6; NOT a Rust test."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import tomllib
|
||||
@@ -25,8 +25,8 @@ checks = {
|
||||
'visual selector and disabled DNS deny': '.acl-profile-rule-type' in js and 'aclDnsAllowOnly' in js,
|
||||
'old network field persisted': 'network: normalizeNetwork(' in js,
|
||||
'PL/EN translations': all(k in json.loads(read(f'langs/{loc}.json'))['translations'] for loc in ('en_US','pl_pl') for k in ('aclRuleType','aclRuleDns','aclDnsHelp','aclDnsAllowOnly')),
|
||||
'package version': tomllib.loads(read('Cargo.toml'))['package']['version'] == '0.32.5',
|
||||
'package version': tomllib.loads(read('Cargo.toml'))['package']['version'] == '0.32.6',
|
||||
}
|
||||
for name, ok in checks.items():
|
||||
if not ok: raise AssertionError(name)
|
||||
print(f'PASS: {len(checks)} DNS ACL 0.32.5 static source checks (not compiled or network-tested)')
|
||||
print(f'PASS: {len(checks)} DNS ACL 0.32.6 static source checks (not compiled or network-tested)')
|
||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
html = (ROOT / 'src/admin/web/index.html').read_text()
|
||||
assert '0.32.5' in (ROOT / "Cargo.toml").read_text()
|
||||
assert '0.32.6' in (ROOT / "Cargo.toml").read_text()
|
||||
assert '<a class="brand" href="/"' in html, 'logo must remain a true full-page navigation'
|
||||
assert 'addEventListener("click"' not in html.split('<a class="brand" href="/"',1)[1].split('</a>',1)[0]
|
||||
body = (ROOT / 'src/proxy/cache_helpers.rs').read_text()
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Source-only mobile/observed-backend UI invariants, no browser required."""
|
||||
from pathlib import Path
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
css=(root/'src/admin/web/style.css').read_text()
|
||||
preferences=(root/'src/admin/web/app/preferences.js').read_text()
|
||||
masonry=(root/'src/admin/web/app/overview-masonry.js').read_text()
|
||||
html=(root/'src/admin/web/index.html').read_text()
|
||||
render=(root/'src/admin/web/vhosts/render.js').read_text()
|
||||
client=(root/'src/upstream/client.rs').read_text()
|
||||
pool=(root/'src/balance/pool_view.rs').read_text()
|
||||
assert 'touch-action: pan-y pinch-zoom' in css
|
||||
assert 'overflow-anchor: none' in css
|
||||
assert 'grid-template-columns: repeat(2, minmax(0, 1fr))' in css
|
||||
assert '@container overview-canvas (max-width: 700px)' in css
|
||||
assert 'overview-canvas (max-width: 440px)' not in css
|
||||
assert '.vhost-backend-protocol-badge' in css
|
||||
assert '.vhost-card' in css[css.index('/* Global shape preference.'):]
|
||||
assert '.draft-dock,' not in css[css.index('/* Global shape preference.'):]
|
||||
assert 'grid.append(...orderedNodes)' in preferences
|
||||
assert 'orderedNodes.some((node, index) => node !== currentNodes[index])' in preferences
|
||||
assert 'if (ordered.some((node, i) => node !== previous[i])) grid.append(...ordered);' in masonry
|
||||
assert html.count('data-dashboard-widget=') >= 62
|
||||
assert 'function renderUpstreamProtocolBadges(status)' in render
|
||||
assert 'const VHOST_PROTOCOL_FRESH_MS = 5 * 60 * 1000' in render
|
||||
assert 'self.inner.observe_protocol(value.version())' in client
|
||||
assert '"upstream_protocol_observed"' in pool
|
||||
print('PASS: mobile grid, shape, stable reorder and observed backend protocol source contracts')
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Mobile Dashboard scroll/layout and appearance browser regression.
|
||||
|
||||
Uses the shipped static web assets in a Chromium touch-device emulation, not
|
||||
physical Safari, the authenticated server or a running Rust backend.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shutil
|
||||
from bs4 import BeautifulSoup
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WEB = ROOT / 'src/admin/web'
|
||||
soup = BeautifulSoup((WEB / 'index.html').read_text(), 'html.parser')
|
||||
widgets = soup.select('#dashboard-overview-grid > [data-dashboard-widget]')
|
||||
assert len(widgets) == 62
|
||||
widget_ids = [widget['data-dashboard-widget'] for widget in widgets]
|
||||
css = (WEB / 'style.css').read_text()
|
||||
source = (WEB / 'app/preferences.js').read_text()
|
||||
layout_code = source.split('function applyDashboardLayout(order, hidden) {')[1].split('function dashboardWidgetLabel(')[0]
|
||||
html = ('<!doctype html><html data-theme="ultra_dark" data-dashboard-density="compact" '
|
||||
'data-corner-style="default"><head><meta name="viewport" '
|
||||
'content="width=device-width,initial-scale=1"><style>' + css +
|
||||
'</style></head><body style="margin:0">'
|
||||
'<div style="height:80px">Mobile navigation</div>'
|
||||
'<section id="panel-overview" style="width:auto;padding:0 10px;box-sizing:border-box">'
|
||||
'<div id="dashboard-overview-grid" class="stats-grid dashboard-overview-grid">' +
|
||||
''.join(map(str, widgets)) + '</div></section>'
|
||||
'<div id="radii" style="padding:10px"><article class="vhost-card" id="vhost-test">Vhost</article>'
|
||||
'<div class="entity" id="entity-test">Entity</div>'
|
||||
'<span class="vhost-backend-protocol-badge" id="badge-test">AUTO → H2</span></div>'
|
||||
'</body></html>')
|
||||
|
||||
with sync_playwright() as browser_tool:
|
||||
chromium = browser_tool.chromium.launch(headless=True,
|
||||
executable_path=shutil.which('chromium') or shutil.which('chromium-browser'),
|
||||
args=['--no-sandbox'])
|
||||
context = chromium.new_context(viewport={'width': 393, 'height': 844},
|
||||
device_scale_factor=3, is_mobile=True, has_touch=True)
|
||||
page = context.new_page()
|
||||
page.set_content(html, wait_until='domcontentloaded')
|
||||
page.add_script_tag(content=(WEB / 'app/overview-masonry.js').read_text())
|
||||
preference_fixture = (
|
||||
'const ADMIN_DASHBOARD_WIDGETS=' + json.dumps(widget_ids) + ';' +
|
||||
'const ADMIN_DEFAULT_HIDDEN_WIDGETS=[];' +
|
||||
'const latestStatus=null; const currentPanel="overview";' +
|
||||
'const normalizeDashboardWidgets=(ids)=>ids;' +
|
||||
'const normalizeHiddenDashboardWidgets=(ids)=>ids;' +
|
||||
'function applyDashboardLayout(order, hidden) {' + layout_code
|
||||
)
|
||||
page.add_script_tag(content=preference_fixture)
|
||||
for width in (320, 375, 393, 402, 430, 550):
|
||||
page.set_viewport_size({'width': width, 'height': 844})
|
||||
snapshot = page.evaluate('''() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
const cards=[...grid.querySelectorAll('[data-dashboard-widget].stat')]
|
||||
.filter(node => !node.hidden).slice(0,6).map(node => {
|
||||
const r=node.getBoundingClientRect();
|
||||
return {left:r.left,top:r.top,right:r.right,bottom:r.bottom,width:r.width};
|
||||
});
|
||||
return {columns:getComputedStyle(grid).gridTemplateColumns.split(' ').length,
|
||||
cards,gridWidth:grid.clientWidth,gridScroll:grid.scrollWidth,
|
||||
screenScroll:document.documentElement.scrollWidth,
|
||||
screen:innerWidth,
|
||||
touch:getComputedStyle(grid.querySelector('.stat')).touchAction};
|
||||
}''')
|
||||
a,b,c,d,e,f = snapshot['cards']
|
||||
assert snapshot['columns'] == 2, (width, snapshot)
|
||||
assert abs(a['top']-b['top']) < 1 and abs(c['top']-d['top']) < 1
|
||||
assert abs(e['top']-f['top']) < 1 and c['top'] > a['top'] and e['top'] > c['top']
|
||||
assert b['left'] >= a['right'] and d['left'] >= c['right']
|
||||
assert a['width'] > 50 and b['width'] > 50
|
||||
assert snapshot['gridScroll'] <= snapshot['gridWidth']+2, (width,snapshot)
|
||||
assert snapshot['screenScroll'] <= snapshot['screen']+2, (width,snapshot)
|
||||
assert 'pan-y' in snapshot['touch']
|
||||
action = page.evaluate('''() => {
|
||||
const tile=document.querySelector('.stat-with-action');
|
||||
const label=tile.querySelector(':scope > span').getBoundingClientRect();
|
||||
const button=tile.querySelector('.stat-corner-action').getBoundingClientRect();
|
||||
const value=tile.querySelector('strong').getBoundingClientRect();
|
||||
const card=tile.getBoundingClientRect();
|
||||
return {labelBottom:label.bottom,buttonTop:button.top,
|
||||
buttonBottom:button.bottom,valueTop:value.top,
|
||||
buttonRight:button.right,cardRight:card.right};
|
||||
}''')
|
||||
assert action['buttonTop'] >= action['labelBottom'] - 2, (width,action)
|
||||
assert action['valueTop'] >= action['buttonBottom'] - 2, (width,action)
|
||||
assert action['buttonRight'] <= action['cardRight'] + 1, (width,action)
|
||||
page.evaluate('window.scrollTo(0,350)')
|
||||
before = page.evaluate('window.scrollY')
|
||||
assert before > 0, width
|
||||
page.evaluate('''() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
window.rustProxyMasonry.refresh();
|
||||
for(const [index,node] of [...grid.querySelectorAll('.stat strong')].entries()) {
|
||||
node.textContent=index%2 ? '26216' : '49,750';
|
||||
}
|
||||
}''')
|
||||
after = page.evaluate('window.scrollY')
|
||||
assert abs(after - before) <= 2, (width, before, after)
|
||||
# Reapplying an unchanged preference must NOT reparent the grid children.
|
||||
moves = page.evaluate('''() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
const order=[...grid.children].map(n=>n.dataset.dashboardWidget);
|
||||
let moves=0;
|
||||
const observer=new MutationObserver(items => { moves+=items.filter(i=>i.type==='childList').length; });
|
||||
observer.observe(grid,{childList:true});
|
||||
applyDashboardLayout(order,[]);
|
||||
observer.takeRecords().forEach(i=>{if(i.type==='childList')moves++});
|
||||
observer.disconnect();
|
||||
return moves;
|
||||
}''')
|
||||
assert moves == 0, (width, moves)
|
||||
assert abs(page.evaluate('window.scrollY')-before) <= 2
|
||||
print(f'PASS: {width}px touch emulation, two metric columns, no overflow or scroll jumps')
|
||||
for style,surface in (('square','0px'), ('subtle','5px'), ('rounded','12px'), ('extra','18px')):
|
||||
page.evaluate('(s) => document.documentElement.dataset.cornerStyle=s', style)
|
||||
for selector in ('#vhost-test','#entity-test','#badge-test','.stat'):
|
||||
radius = page.locator(selector).first.evaluate('node=>getComputedStyle(node).borderTopLeftRadius')
|
||||
assert radius == surface, (style,selector,radius)
|
||||
print('PASS: vhost, entity, backend badge and metric corner presets')
|
||||
chromium.close()
|
||||
@@ -147,7 +147,7 @@ with sync_playwright() as playwright:
|
||||
cases=[(1720,0,1,'normal',12),(1350,180,1,'compact',12),
|
||||
(1200,260,1,'normal',6),(900,0,1,'compact',6),
|
||||
(900,190,1,'normal',6),(720,190,1,'ultra',2),
|
||||
(620,0,1,'compact',2),(430,0,1,'ultra',1),
|
||||
(620,0,1,'compact',2),(430,0,1,'ultra',2),
|
||||
(1140,200,.9,'compact',6),(1140,200,1.1,'normal',6),
|
||||
(1140,200,1.3,'normal',6)]
|
||||
for width,sidebar,zoom,density,max_columns in cases:
|
||||
|
||||
@@ -51,6 +51,6 @@ for marker in ['margin: 0;', '--overview-gap: max(3px, calc(var(--ui-gap) - 1px)
|
||||
retry = re.search(r'fn retry_request\([\s\S]*?\n}\n', proxy)
|
||||
assert retry and '.uri(uri.clone())' in retry[0]
|
||||
assert 'upstream_request_target(&uri, request.headers_mut()' in retry[0]
|
||||
assert json.loads(read('src/admin/openapi.json'))['info']['version']=='0.32.5'
|
||||
assert 'version = "0.32.5"' in read('Cargo.toml')
|
||||
print('PASS: 0.32.5 origin pinning, authority, retry, CSS gutter source contracts (not Rust runtime)')
|
||||
assert json.loads(read('src/admin/openapi.json'))['info']['version']=='0.32.6'
|
||||
assert 'version = "0.32.6"' in read('Cargo.toml')
|
||||
print('PASS: 0.32.6 origin pinning, authority, retry, CSS gutter source contracts (not Rust runtime)')
|
||||
@@ -8,9 +8,9 @@ from html.parser import HTMLParser
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
def read(rel): return (ROOT / rel).read_text(encoding='utf-8')
|
||||
assert tomllib.loads(read('Cargo.toml'))['package']['version'] == '0.32.5'
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.5"' in read('Cargo.lock')
|
||||
assert json.loads(read('src/admin/openapi.json'))['info']['version'] == '0.32.5'
|
||||
assert tomllib.loads(read('Cargo.toml'))['package']['version'] == '0.32.6'
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.6"' in read('Cargo.lock')
|
||||
assert json.loads(read('src/admin/openapi.json'))['info']['version'] == '0.32.6'
|
||||
|
||||
pages = read('src/pages.rs')
|
||||
for code in (400, 401, 403, 404, 408, 410, 413, 429, 500, 502, 503, 504):
|
||||
|
||||
@@ -135,7 +135,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
||||
assert backup.is_dir()
|
||||
assert (backup / "src/obsolete.rs").is_file()
|
||||
assert not (target / "src/obsolete.rs").exists()
|
||||
assert 'version = "0.32.5"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert 'version = "0.32.6"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
|
||||
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
|
||||
assert (target / "langs/site.json").is_file()
|
||||
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
||||
assert (target / "docker-agent/certificate.go").is_file()
|
||||
assert (target / "docker-agent/server.go").is_file()
|
||||
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
|
||||
assert manifest["to_version"] == "0.32.5"
|
||||
assert manifest["to_version"] == "0.32.6"
|
||||
assert manifest["removed_obsolete_runtime_state"] is True
|
||||
assert not legacy.exists()
|
||||
assert not (backup / "legacy-data").exists()
|
||||
@@ -617,7 +617,7 @@ def source_contract_smoke() -> None:
|
||||
assert marker in admin, marker
|
||||
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
|
||||
assert '"/api/status"' not in anonymous_block
|
||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.32.5"' in openapi_0241
|
||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.32.6"' in openapi_0241
|
||||
assert 'id="panel-logging"' not in html
|
||||
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
|
||||
assert marker in html, marker
|
||||
@@ -1102,7 +1102,7 @@ def source_contract_smoke() -> None:
|
||||
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
|
||||
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
|
||||
assert '"/api/config/history/restore"' in openapi
|
||||
for marker in ['"version": "0.32.5"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
for marker in ['"version": "0.32.6"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
assert marker in openapi, marker
|
||||
assert '"software_update"' in openapi and '"update_available"' in openapi
|
||||
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
|
||||
|
||||
@@ -21,7 +21,7 @@ const openapi = json('src/admin/openapi.json');
|
||||
const en = json('langs/en_US.json').translations;
|
||||
const pl = json('langs/pl_pl.json').translations;
|
||||
|
||||
assert.match(manifest, /version = "0\.32\.5"/);
|
||||
assert.match(manifest, /version = "0\.32\.6"/);
|
||||
assert.match(lib, /pub mod user_agent;/);
|
||||
assert.match(model, /user_agent_blocking: crate::user_agent::Policy/);
|
||||
assert.match(vhost, /user_agent_blocking: crate::user_agent::Policy/);
|
||||
@@ -52,7 +52,7 @@ assert.match(fileLogs, /localStorage\.setItem\(CONSOLIDATION_STORAGE_KEY/);
|
||||
assert.match(fileLogs, /aggregate_min_count/);
|
||||
assert.match(fileLogs, /file-log-aggregate-copy/);
|
||||
|
||||
assert.equal(openapi.info.version, '0.32.5');
|
||||
assert.equal(openapi.info.version, '0.32.6');
|
||||
assert.ok(openapi.components.schemas.Config.properties.user_agent_blocking);
|
||||
assert.ok(openapi.components.schemas.Vhost.properties.user_agent_blocking);
|
||||
assert.ok(openapi.components.schemas.FileLogSource.properties.group_id);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline source contracts for RustProxy 0.32.5 upstream protocols and card registry.
|
||||
"""Offline source contracts for RustProxy 0.32.6 upstream protocols and card registry.
|
||||
|
||||
These assertions supplement, rather than replace, Rust ALPN, H2C and websocket
|
||||
runtime tests. No code execution against a RustProxy server is implied.
|
||||
@@ -141,7 +141,7 @@ for field in ('dashboard_widgets', 'dashboard_hidden_widgets'):
|
||||
|
||||
|
||||
|
||||
assert '"version": "0.32.5"' in read('src/admin/openapi.json')
|
||||
assert 'version = "0.32.5"' in read('Cargo.toml')
|
||||
assert 'version = "0.32.5"' in read('Cargo.lock')
|
||||
print(f'PASS: 0.32.5 upstream H1/Auto/compatible-HTTP2/strict-H2 source contracts and {len(ids)} registered Overview Cards')
|
||||
assert '"version": "0.32.6"' in read('src/admin/openapi.json')
|
||||
assert 'version = "0.32.6"' in read('Cargo.toml')
|
||||
assert 'version = "0.32.6"' in read('Cargo.lock')
|
||||
print(f'PASS: 0.32.6 upstream H1/Auto/compatible-HTTP2/strict-H2 source contracts and {len(ids)} registered Overview Cards')
|
||||
Reference in new issue
Block a user