#!/usr/bin/env python3 """Offline source contracts for RustProxy 0.32.5 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']*\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.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')