mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
fix: serve certification page on direct load, read certify key from env, fix sequential note
This commit is contained in:
+15
-12
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 == [""]
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user