148 lines
9.3 KiB
Python
Executable File
148 lines
9.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""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.
|
|
"""
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
root=Path(__file__).resolve().parents[2]
|
|
read=lambda name:(root/name).read_text(encoding='utf-8')
|
|
registry=json.loads(read('src/admin/web/dashboard-widgets.json'))
|
|
ids=[widget['id'] for widget in registry]
|
|
assert len(ids)==len(set(ids)) and len(ids)>=16
|
|
assert all(re.fullmatch(r'[a-z0-9_]+',id) for id in ids)
|
|
html=read('src/admin/web/index.html')
|
|
html_ids=re.findall(r'<article\b[^>]*\bdata-dashboard-widget="([a-z0-9_]+)"',html)
|
|
assert len(html_ids)==len(set(html_ids)), 'duplicate Overview widget element'
|
|
assert set(ids)==set(html_ids), f'Registry diverges from rendered Overview cards: {set(ids)^set(html_ids)}'
|
|
assert ids==html_ids, 'registry should follow the default displayed card order'
|
|
for language in ['en_US','pl_pl']:
|
|
translations=json.loads(read(f'langs/{language}.json'))['translations']
|
|
assert 'Auto' in translations['vhProtocolAuto']
|
|
assert 'H2' in translations['vhProtocolHttp2']
|
|
assert 'H2' in translations['vhProtocolH2Strict']
|
|
assert all(key in translations for key in (
|
|
'vhProtocolCompatibilityWarning', 'vhProtocolAutoHttpsDetails', 'vhProtocolH2HttpDetails',
|
|
'vhProtocolLegacyHttp2HttpsDetails', 'vhProtocolLegacyHttp2HttpDetails',
|
|
'backendWidgetModeCompat', 'backendWidgetModeStrict',
|
|
'backendWidgetRequests', 'backendWidgetCapacity'))
|
|
for widget in registry:
|
|
assert widget['label_key'] in translations, (language,widget['label_key'])
|
|
if widget['description_key'] is not None:
|
|
assert widget['description_key'] in translations
|
|
for widget in registry:
|
|
assert widget['element_selector']==f'[data-dashboard-widget="{widget["id"]}"]'
|
|
assert widget['data_source'] in ('status_metrics','stream_status','backend_status','system_status','cache_status')
|
|
assert widget['no_data']=='neutral_dash'
|
|
assert isinstance(widget['default_hidden'],bool)
|
|
assert widget['category'] in ('overview','streams','backends','performance','panels')
|
|
assert {v['id'] for v in registry if v['default_hidden']}=={
|
|
'stream_overview','stream_traffic','stream_sessions','stream_backend_health',
|
|
'backend_health','backend_activity','backend_errors','backend_latency',
|
|
'backend_traffic','backend_protocols','backend_requests','backend_capacity',
|
|
'backend_http5xx','backend_timeouts','backend_network','backend_retries','backend_queue','backend_rotations',
|
|
'extra_process_cpu','extra_process_rss','extra_process_fds','extra_open_connections',
|
|
'extra_websocket_active','extra_tls_error_rate','extra_request_error_rate',
|
|
'extra_cache_hit_rate','extra_cache_memory','extra_h2_share','extra_response_latency',
|
|
'extra_backend_unhealthy',
|
|
'extra_total_rps', 'extra_active_exchanges', 'extra_h1_share', 'extra_h3_share',
|
|
'extra_cache_miss_rate', 'extra_backend_active',
|
|
'wide_protocol_rates', 'wide_cpu', 'wide_memory', 'wide_cache_activity',
|
|
'wide_connections', 'wide_backend_health', 'wide_latency'
|
|
}
|
|
|
|
backend=read('src/config/model.rs')
|
|
client=read('src/upstream/client.rs')
|
|
# Existing saved `http2` is NOT strict on HTTPS: only new `h2` is.
|
|
assert re.search(r'enum UpstreamProtocol\s*\{\s*#\[default\]\s*Http1,',backend)
|
|
for name in ['Http2,', 'Auto,', 'H2,']:
|
|
assert name in backend
|
|
assert 'is_https && backend.protocol == UpstreamProtocol::Http2' in client
|
|
assert 'let protocol = if websocket {' in client and 'UpstreamProtocol::Http1' in client
|
|
assert 'if !is_https && protocol == UpstreamProtocol::Auto' in client
|
|
assert 'UpstreamProtocol::Http1 => builder.enable_http1().wrap_connector(http)' in client
|
|
assert re.search(r'UpstreamProtocol::Http2 \| UpstreamProtocol::H2 => \{?\s*builder\.enable_http2\(\)\.wrap_connector\(http\)',client)
|
|
assert 'UpstreamProtocol::Auto => builder.enable_http1().enable_http2().wrap_connector(http)' in client
|
|
assert re.search(r'\.http2_only\(matches!\(\s*protocol,\s*UpstreamProtocol::Http2 \| UpstreamProtocol::H2\s*\)\s*\)',client)
|
|
assert 'backend.origin.starts_with("http://")' in read('src/proxy/types.rs')
|
|
assert 'UpstreamProtocol::Http2 | crate::config::UpstreamProtocol::H2' in read('src/proxy/forward/upstream.rs')
|
|
assert '.retry_canceled_requests(false)' in client
|
|
assert '.pool_idle_timeout(' in client and '.pool_max_idle_per_host(' in client
|
|
assert 'generation_requests' in client and 'rotating' in client
|
|
assert 'include!("protocol_tests.rs");' in read('src/upstream/tests.rs')
|
|
protocol_tests=read('src/upstream/protocol_tests.rs')
|
|
for test in ['https_auto_chooses_h2_by_alpn_without_replaying_request',
|
|
'https_auto_negotiates_http1_before_sending_to_h1_only_peer',
|
|
'saved_legacy_https_http2_keeps_h1_fallback',
|
|
'saved_legacy_https_http2_still_prefers_h2',
|
|
'explicit_https_h2_requires_h2_and_refuses_h1_only_peer',
|
|
'explicit_https_h2_selects_h2_via_alpn',
|
|
'explicit_https_http1_does_not_offer_h2_to_h2_capable_peer',
|
|
'websocket_isolated_client_stays_on_http1_even_with_h2_backend',
|
|
'cleartext_auto_does_not_attempt_unsafe_h2c_probe',
|
|
'default_protocol_and_serialization_are_backward_compatible']:
|
|
assert f'fn {test}' in protocol_tests, test
|
|
assert 'fn explicit_h2c_backend_uses_http2_prior_knowledge' in read('src/upstream/tests.rs')
|
|
assert 'fn upstream_request_version(' in read('src/proxy/types.rs')
|
|
assert 'fn legacy_https_http2_version_is_h1_safe_and_strict_h2_is_required' in read('src/proxy/tests/authority_tests.rs')
|
|
|
|
server=read('src/admin/preferences_columns.rs')
|
|
api=read('src/admin/preferences.rs')
|
|
router=read('src/admin/router.rs')
|
|
js=read('src/admin/web/app/preferences.js')
|
|
assert 'include_str!("web/dashboard-widgets.json")' in server
|
|
assert 'include_str!("web/dashboard-widgets.json")' in router
|
|
assert 'const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze(' in router
|
|
assert 'ADMIN_DASHBOARD_WIDGET_REGISTRY.map(widget => widget.id)' in js
|
|
assert 'ADMIN_DASHBOARD_WIDGET_REGISTRY.filter(widget => widget.default_hidden)' in js
|
|
assert 'ADMIN_DASHBOARD_BY_ID.get(id)?.description_key' in js
|
|
assert 'reconcile_dashboard_widgets(&mut preferences);' in api
|
|
assert 'prefs.dashboard_widgets.retain(' in api and 'prefs.dashboard_hidden_widgets.retain(' in api
|
|
assert 'fn old_widget_preferences_preserve_order_opt_in_and_survive_unknown_ids' in read('src/admin/preferences_tests.rs')
|
|
assert 'include!("preferences_tests.rs");' in read('src/admin/tests.rs')
|
|
|
|
assert 'item.insert("protocol".into(), json!(m.definition.protocol));' in read('src/balance/pool_view.rs')
|
|
assert 'include_str!("web/app/backend-dashboard.js")' in router
|
|
assert 'window.rustProxyBackendDashboard?.render(s)' in read('src/admin/web/app/core.js')
|
|
assert 'window.rustProxyBackendDashboard?.syncVisibility' in js
|
|
assert 'window.rustProxyBackendDashboard?.reset()' in read('src/admin/web/app/core.js')
|
|
assert 'vhProtocolCompatibilityWarning' in read('src/admin/web/vhosts/protocol.js')
|
|
assert '["h2", "vhProtocolH2Strict"]' in read('src/admin/web/vhosts/core.js')
|
|
assert 'backendWidgetRequestsTotal' in read('src/admin/web/app/backend-dashboard.js')
|
|
assert 'backendWidgetCapacityLimits' in read('src/admin/web/app/backend-dashboard.js')
|
|
assert 'include_str!("web/app/dashboard-layout-editor.js")' in router
|
|
assert 'include_str!("web/app/dashboard-inline-layout.js")' in router
|
|
assert all(widget.get('display_type') in {'value','value_chart','chart','panel','table'} for widget in registry)
|
|
assert 'dashboardWidgetTypeBadge' in js
|
|
assert 'overview-layout-unlock' in html and 'overview-layout-type-filter' in html
|
|
assert 'dashboard_widgets: [...draft.order]' in read('src/admin/web/app/dashboard-layout-editor.js')
|
|
assert 'dashboard_hidden_widgets: [...draft.hidden]' in read('src/admin/web/app/dashboard-layout-editor.js')
|
|
assert 'overview-layout-dialog' in html and 'overview-layout-open' in html
|
|
assert 'tls_errors_total' in read('src/admin/web/app/backend-dashboard.js')
|
|
assert len(registry) == 62
|
|
assert len([w for w in registry if w['category'] == 'panels']) == 14
|
|
assert 'include_str!("web/app/overview-extra-metrics.js")' in router
|
|
assert 'include_str!("web/app/overview-wide-metrics.js")' in router
|
|
assert 'include_str!("web/app/overview-masonry.js")' in router
|
|
assert 'window.rustProxyExtraOverview?.render(s)' in read('src/admin/web/app/core.js')
|
|
assert all(f'id="{id}"' in html for id in ('dashboard-overview-grid','overview-layout-default','overview-layout-reset','overview-reset-confirm'))
|
|
openapi = json.loads(read('src/admin/openapi.json'))
|
|
assert openapi['components']['schemas']['VhostBackend']['properties']['protocol']['enum'] == ['http1', 'http2', 'auto', 'h2']
|
|
prefs_schema = openapi['components']['schemas']['AdminPreferences']['properties']
|
|
for field in ('dashboard_widgets', 'dashboard_hidden_widgets'):
|
|
schema = prefs_schema[field]
|
|
assert schema['maxItems'] == len(registry), field
|
|
assert schema['items']['enum'] == ids, field
|
|
expected = ids if field == 'dashboard_widgets' else [widget['id'] for widget in registry if widget['default_hidden']]
|
|
assert schema['default'] == expected, field
|
|
|
|
|
|
|
|
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')
|