"""Behavioral test for issue #353 — Local LLM endpoints behind an API key. The admin "Local" add/test form previously sent only `base_url` (+ model_type), so a self-hosted endpoint protected by an API key could never be added — it just errored out. The backend `POST /api/model-endpoints` and `/model-endpoints/test` already accept an `api_key` form field; the fix wires the new `adm-epLocalApiKey` input into the local Test and Add handlers. admin.js can't be imported standalone (browser-only deps), so — same approach as tests/test_local_endpoint_js.py — we extract the two click-handler bodies from source and run them under node with mocked DOM/FormData/fetch, asserting the outgoing form data contains `api_key` exactly when the key field is filled. """ import json import shutil import subprocess from pathlib import Path import pytest _REPO = Path(__file__).resolve().parent.parent _ADMIN_JS = _REPO / "static" / "js" / "admin.js" _INDEX_HTML = _REPO / "static" / "index.html" _HAS_NODE = shutil.which("node") is not None def _extract_handler_body(src: str, marker: str) -> str: """Return the body (without the outer braces) of the arrow function that immediately follows `marker` in `src`, using a quote-aware brace matcher.""" start = src.index(marker) + len(marker) brace = src.index("{", start) i = brace + 1 depth = 1 quote = None escaped = False while i < len(src): c = src[i] if quote: if escaped: escaped = False elif c == "\\": escaped = True elif c == quote: quote = None elif c in "'\"`": quote = c elif c == "{": depth += 1 elif c == "}": depth -= 1 if depth == 0: return src[brace + 1:i] i += 1 raise AssertionError(f"unbalanced braces after marker: {marker!r}") _HARNESS = """ let appended = []; class FormData {{ append(k, v) {{ appended.push([k, String(v)]); }} }} const FIELDS = {fields}; function el(id) {{ if (!(id in FIELDS)) return null; return {{ get value() {{ return FIELDS[id]; }}, set value(x) {{ FIELDS[id] = x; }}, disabled: false, textContent: '', classList: {{ add() {{}}, remove() {{}} }}, }}; }} function _endpointMsg() {{ return {{ textContent: '', className: '' }}; }} function _normalizeBaseUrl(u) {{ return u; }} function _renderEndpointTestResult() {{}} async function loadEndpoints() {{}} async function _selectAddedModelInChat() {{}} let _recentlyAddedEpId = null; const localTestBtn = {{ disabled: false, textContent: '' }}; const localAddBtn = {{ disabled: false, textContent: '' }}; async function fetch() {{ return {{ ok: true, async json() {{ return {{ id: 'x', models: [], online: true, status: 'ok' }}; }} }}; }} async function run() {{ {body} }} run().then(() => console.log(JSON.stringify(appended))) .catch((e) => {{ console.error(e); process.exit(2); }}); """ def _run_handler(body: str, fields: dict) -> list: js = _HARNESS.format(fields=json.dumps(fields), body=body) proc = subprocess.run( ["node", "--input-type=module"], input=js, capture_output=True, text=True, cwd=str(_REPO), timeout=30, ) assert proc.returncode == 0, f"node failed: {proc.stderr}\n---\n{js}" return json.loads(proc.stdout.strip()) def _handler(marker: str) -> str: return _extract_handler_body(_ADMIN_JS.read_text(encoding="utf-8"), marker) _TEST_MARKER = "localTestBtn.addEventListener('click', async () => " _ADD_MARKER = "localAddBtn.addEventListener('click', async () => " def test_local_form_has_api_key_input(): html = _INDEX_HTML.read_text(encoding="utf-8") pos = html.find('id="adm-epLocalApiKey"') assert pos != -1, "adm-epLocalApiKey input missing from index.html" # Isolate the enclosing tag and require it to be a masked field, # like the cloud form's API-key input. tag = html[html.rfind("", pos) + 1] assert 'type="password"' in tag, f"local API key must be a password input: {tag}" @pytest.mark.skipif(not _HAS_NODE, reason="node binary not on PATH") @pytest.mark.parametrize("marker", [_TEST_MARKER, _ADD_MARKER]) def test_api_key_sent_when_filled(marker): fields = {"adm-epLocalUrl": "http://localhost:8002/v1", "adm-epLocalApiKey": "sk-secret", "adm-epLocalType": "llm"} appended = dict(_run_handler(_handler(marker), fields)) assert appended.get("base_url") == "http://localhost:8002/v1" assert appended.get("api_key") == "sk-secret", f"api_key not sent: {appended}" @pytest.mark.skipif(not _HAS_NODE, reason="node binary not on PATH") @pytest.mark.parametrize("marker", [_TEST_MARKER, _ADD_MARKER]) def test_api_key_omitted_when_blank(marker): fields = {"adm-epLocalUrl": "http://localhost:8002/v1", "adm-epLocalApiKey": "", "adm-epLocalType": "llm"} keys = [k for k, _ in _run_handler(_handler(marker), fields)] assert "base_url" in keys assert "api_key" not in keys, "blank key must not be appended (avoids empty Bearer)"