feat(config): add actionable startup diagnostics and WebUI recovery (#5110)

This commit is contained in:
chengyongru
2026-07-28 18:52:05 +08:00
committed by GitHub
parent 76ab04ac48
commit 0c6c0438d4
19 changed files with 1425 additions and 64 deletions
+59 -3
View File
@@ -1,5 +1,6 @@
from pathlib import Path
import pytest
import typer
from rich.console import Console
from typer.testing import CliRunner
@@ -27,6 +28,7 @@ class FakeRuntime:
self.restarted_options: GatewayStartOptions | None = None
self.stop_timeout: int | None = None
self.follow_tail: int | None = None
self.validated_configs: list[Config] = []
def start_background(self, options: GatewayStartOptions) -> RuntimeResult:
self.started_options = options
@@ -84,11 +86,15 @@ class FakeServiceInstaller:
)
def _test_app(tmp_path: Path, config: Config | None = None):
def _test_app(
tmp_path: Path,
config: Config | None = None,
startup_error: str | None = None,
):
app = typer.Typer()
fake_runtime = FakeRuntime(tmp_path)
fake_service = FakeServiceInstaller(tmp_path)
run_calls: list[tuple[Config, int | None, str | None]] = []
run_calls: list[tuple[Config, int | None, str | None, str | None]] = []
prepare_calls: list[tuple[Config, str]] = []
def load_runtime_config(_config_path: str | None, _workspace: str | None) -> Config:
@@ -99,18 +105,28 @@ def _test_app(tmp_path: Path, config: Config | None = None):
*,
port: int | None = None,
webui_bundle_mode: str | None = None,
unconfigured_provider_error: str | None = None,
) -> None:
run_calls.append((config, port, webui_bundle_mode))
run_calls.append(
(config, port, webui_bundle_mode, unconfigured_provider_error)
)
def prepare_webui_bundle(config: Config, mode: str) -> None:
prepare_calls.append((config, mode))
def validate_startup_config(config: Config) -> str | None:
fake_runtime.validated_configs.append(config)
return startup_error
app.add_typer(
create_gateway_app(
console=Console(),
log_handler_id=0,
load_runtime_config=load_runtime_config,
run_gateway=run_gateway,
validate_startup_config=(
validate_startup_config if startup_error is not None else None
),
runtime_factory=lambda **_kwargs: fake_runtime,
service_factory=lambda: fake_service,
prepare_webui_bundle=prepare_webui_bundle,
@@ -131,6 +147,46 @@ def test_gateway_default_still_runs_foreground(tmp_path):
assert calls[0][2] == "warn"
def test_gateway_foreground_passes_recoverable_provider_error_to_runner(tmp_path):
setup_error = "No provider is configured."
app, _runtime, _service, calls, _prepare_calls = _test_app(
tmp_path,
startup_error=setup_error,
)
result = runner.invoke(app, ["gateway"])
assert result.exit_code == 0
assert len(calls) == 1
assert calls[0][3] == setup_error
assert len(_runtime.validated_configs) == 1
@pytest.mark.parametrize(
("args", "runtime_attribute"),
[
(["gateway", "--background"], "started_options"),
(["gateway", "restart"], "restarted_options"),
],
)
def test_gateway_managed_start_allows_recoverable_provider_error(
tmp_path,
args: list[str],
runtime_attribute: str,
) -> None:
app, fake_runtime, _service, calls, _prepare_calls = _test_app(
tmp_path,
startup_error="No provider is configured.",
)
result = runner.invoke(app, args)
assert result.exit_code == 0
assert calls == []
assert len(fake_runtime.validated_configs) == 1
assert getattr(fake_runtime, runtime_attribute) is not None
def test_gateway_background_starts_detached_runtime(tmp_path):
config = Config()
config.gateway.port = 18792