fix: serve certification page on direct load, read certify key from env, fix sequential note

This commit is contained in:
9qeklajc
2026-10-03 13:28:36 +02:00
parent 4d65cd6f4c
commit fb7de40a87
5 changed files with 91 additions and 14 deletions
+15 -12
View File
@@ -340,6 +340,21 @@ async def providers() -> RedirectResponse:
UI_DIST_PATH = Path(__file__).parent.parent.parent / "ui_out" UI_DIST_PATH = Path(__file__).parent.parent.parent / "ui_out"
# Every `ui/app/**/page.tsx` route needs an entry, or a direct load 404s.
UI_PAGES = (
"dashboard",
"login",
"model",
"providers",
"providers/certification",
"settings",
"transactions",
"balances",
"logs",
"usage",
"unauthorized",
)
if UI_DIST_PATH.exists() and UI_DIST_PATH.is_dir(): if UI_DIST_PATH.exists() and UI_DIST_PATH.is_dir():
logger.info(f"Serving static UI from {UI_DIST_PATH}") logger.info(f"Serving static UI from {UI_DIST_PATH}")
@@ -362,18 +377,6 @@ if UI_DIST_PATH.exists() and UI_DIST_PATH.is_dir():
# with a slash (e.g. `/login/`). The proxy router catches `/{path:path}` # with a slash (e.g. `/login/`). The proxy router catches `/{path:path}`
# before FastAPI's `redirect_slashes` logic can normalize the URL, so we # before FastAPI's `redirect_slashes` logic can normalize the URL, so we
# must register both the with-slash and without-slash variants here. # must register both the with-slash and without-slash variants here.
UI_PAGES = (
"dashboard",
"login",
"model",
"providers",
"settings",
"transactions",
"balances",
"logs",
"usage",
"unauthorized",
)
def _register_ui_page(name: str) -> None: def _register_ui_page(name: str) -> None:
page_dir = UI_DIST_PATH / name page_dir = UI_DIST_PATH / name
+9 -1
View File
@@ -17,6 +17,7 @@ import argparse
import asyncio import asyncio
import json import json
import math import math
import os
import sys import sys
import time import time
from collections.abc import Callable from collections.abc import Callable
@@ -1245,7 +1246,14 @@ def main(argv: list[str] | None = None) -> int:
required=True, required=True,
help="Upstream base URL (repeatable), e.g. https://api.example.com/v1", help="Upstream base URL (repeatable), e.g. https://api.example.com/v1",
) )
parser.add_argument("--key", default="", help="Bearer API key for the upstream") parser.add_argument(
"--key",
default=os.environ.get("ROUTSTR_CERTIFY_KEY", ""),
help=(
"Bearer API key for the upstream (defaults to $ROUTSTR_CERTIFY_KEY; "
"prefer the env var so the key stays out of shell history and ps)"
),
)
parser.add_argument( parser.add_argument(
"--model", "--model",
default=None, default=None,
+46
View File
@@ -0,0 +1,46 @@
"""The certification CLI reads the upstream key from the environment."""
from __future__ import annotations
from typing import Any
import pytest
from routstr.upstream import certification
@pytest.fixture
def captured_keys(monkeypatch: pytest.MonkeyPatch) -> list[str]:
keys: list[str] = []
async def fake_certify(url: str, *, api_key: str, **_: Any) -> dict[str, Any]:
keys.append(api_key)
return {"url": url, "rows": []}
monkeypatch.setattr(certification, "certify_upstream_url", fake_certify)
monkeypatch.setattr(certification, "render_checklist", lambda _result: "")
return keys
def test_key_defaults_to_env_var(
monkeypatch: pytest.MonkeyPatch, captured_keys: list[str]
) -> None:
monkeypatch.setenv("ROUTSTR_CERTIFY_KEY", "sk-from-env")
assert certification.main(["--url", "http://localhost:1/v1"]) == 0
assert captured_keys == ["sk-from-env"]
def test_key_flag_overrides_env_var(
monkeypatch: pytest.MonkeyPatch, captured_keys: list[str]
) -> None:
monkeypatch.setenv("ROUTSTR_CERTIFY_KEY", "sk-from-env")
certification.main(["--url", "http://localhost:1/v1", "--key", "sk-flag"])
assert captured_keys == ["sk-flag"]
def test_key_is_empty_without_flag_or_env(
monkeypatch: pytest.MonkeyPatch, captured_keys: list[str]
) -> None:
monkeypatch.delenv("ROUTSTR_CERTIFY_KEY", raising=False)
certification.main(["--url", "http://localhost:1/v1"])
assert captured_keys == [""]
+19
View File
@@ -0,0 +1,19 @@
"""Every static UI page must be served on a direct load, not the proxy 404."""
from __future__ import annotations
from pathlib import Path
from routstr.core import main as core_main
UI_APP_DIR = Path(__file__).resolve().parents[2] / "ui" / "app"
def test_every_ui_app_page_is_in_ui_pages() -> None:
routes = {
page.parent.relative_to(UI_APP_DIR).as_posix()
for page in UI_APP_DIR.rglob("page.tsx")
if page.parent != UI_APP_DIR
}
assert routes, "no ui/app pages found"
assert routes - set(core_main.UI_PAGES) == set()
@@ -272,7 +272,8 @@ export function ProviderCertificationSetupPanel({
)} )}
{mode === 'all' && ( {mode === 'all' && (
<p className='text-muted-foreground text-xs'> <p className='text-muted-foreground text-xs'>
All {paths.length} paths will run in parallel. All {paths.length} paths will run one after another, each
with its own probe calls.
</p> </p>
)} )}
</> </>