177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from reyna_cli.web import WebSettings, create_app, normalize_args, RunRequest, run_web_server
|
|
from reyna_cli.tts import TTSError, _custom_tts
|
|
|
|
|
|
def test_web_health_without_auth_config():
|
|
app = create_app(
|
|
WebSettings(
|
|
auth_enabled=True,
|
|
dev_auth=False,
|
|
session_secret="",
|
|
authentik_issuer="https://auth.reynafamily.com/application/o/reyna-cli-web/",
|
|
client_id=None,
|
|
client_secret=None,
|
|
basic_user=None,
|
|
basic_password=None,
|
|
public_url=None,
|
|
command_timeout=5,
|
|
output_limit=1000,
|
|
workdir=Path.cwd(),
|
|
)
|
|
)
|
|
client = TestClient(app)
|
|
response = client.get("/healthz")
|
|
assert response.status_code == 200
|
|
assert response.json()["auth_enabled"] is True
|
|
assert response.json()["oidc_configured"] is False
|
|
|
|
|
|
def test_web_index_renders_with_dev_auth():
|
|
app = create_app(
|
|
WebSettings(
|
|
auth_enabled=True,
|
|
dev_auth=True,
|
|
session_secret="dev-secret",
|
|
authentik_issuer="https://auth.reynafamily.com/application/o/reyna-cli-web/",
|
|
client_id=None,
|
|
client_secret=None,
|
|
basic_user=None,
|
|
basic_password=None,
|
|
public_url=None,
|
|
command_timeout=5,
|
|
output_limit=1000,
|
|
workdir=Path.cwd(),
|
|
)
|
|
)
|
|
client = TestClient(app)
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert "Reyna CLI Web" in response.text
|
|
assert "doctor --json" in response.text
|
|
|
|
|
|
def test_web_refuses_dev_auth_on_lan_bind(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("HERMES_ENV_PATH", str(tmp_path / "missing.env"))
|
|
monkeypatch.setenv("REYNA_WEB_DEV_AUTH", "1")
|
|
monkeypatch.delenv("REYNA_WEB_ALLOW_INSECURE_LAN", raising=False)
|
|
try:
|
|
run_web_server(host="0.0.0.0", port=8765)
|
|
except SystemExit as exc:
|
|
assert "Refusing to run" in str(exc)
|
|
else:
|
|
raise AssertionError("LAN dev-auth server was allowed")
|
|
|
|
|
|
def test_web_refuses_lan_bind_without_oidc_config(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("HERMES_ENV_PATH", str(tmp_path / "missing.env"))
|
|
monkeypatch.delenv("REYNA_WEB_DEV_AUTH", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_AUTH_CLIENT_ID", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_AUTH_CLIENT_SECRET", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_BASIC_USER", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_BASIC_PASSWORD", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_SESSION_SECRET", raising=False)
|
|
try:
|
|
run_web_server(host="0.0.0.0", port=8765)
|
|
except SystemExit as exc:
|
|
assert "without configured authentication" in str(exc)
|
|
else:
|
|
raise AssertionError("LAN server without OIDC config was allowed")
|
|
|
|
|
|
def test_web_basic_auth_protects_index():
|
|
app = create_app(
|
|
WebSettings(
|
|
auth_enabled=True,
|
|
dev_auth=False,
|
|
session_secret="basic-secret",
|
|
authentik_issuer="https://auth.reynafamily.com/application/o/reyna-cli-web/",
|
|
client_id=None,
|
|
client_secret=None,
|
|
basic_user="adolfo",
|
|
basic_password="correct-password",
|
|
public_url=None,
|
|
command_timeout=5,
|
|
output_limit=1000,
|
|
workdir=Path.cwd(),
|
|
)
|
|
)
|
|
client = TestClient(app)
|
|
unauthenticated = client.get("/")
|
|
assert unauthenticated.status_code == 401
|
|
assert unauthenticated.headers["www-authenticate"] == 'Basic realm="Reyna CLI Web"'
|
|
|
|
authenticated = client.get("/", auth=("adolfo", "correct-password"))
|
|
assert authenticated.status_code == 200
|
|
assert "HTTP Basic auth" in authenticated.text
|
|
|
|
|
|
def test_web_allows_lan_bind_with_basic_auth(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("HERMES_ENV_PATH", str(tmp_path / "missing.env"))
|
|
called = {}
|
|
|
|
def fake_run(app_path, host, port, reload):
|
|
called.update({"app_path": app_path, "host": host, "port": port, "reload": reload})
|
|
|
|
monkeypatch.delenv("REYNA_WEB_DEV_AUTH", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_AUTH_CLIENT_ID", raising=False)
|
|
monkeypatch.delenv("REYNA_WEB_AUTH_CLIENT_SECRET", raising=False)
|
|
monkeypatch.setenv("REYNA_WEB_BASIC_USER", "adolfo")
|
|
monkeypatch.setenv("REYNA_WEB_BASIC_PASSWORD", "correct-password")
|
|
monkeypatch.setattr("uvicorn.run", fake_run)
|
|
|
|
run_web_server(host="0.0.0.0", port=8765)
|
|
assert called["host"] == "0.0.0.0"
|
|
|
|
|
|
def test_web_api_run_uses_argument_vector(monkeypatch):
|
|
app = create_app(
|
|
WebSettings(
|
|
auth_enabled=False,
|
|
dev_auth=False,
|
|
session_secret="dev-secret",
|
|
authentik_issuer="https://auth.reynafamily.com/application/o/reyna-cli-web/",
|
|
client_id=None,
|
|
client_secret=None,
|
|
basic_user=None,
|
|
basic_password=None,
|
|
public_url=None,
|
|
command_timeout=5,
|
|
output_limit=1000,
|
|
workdir=Path.cwd(),
|
|
)
|
|
)
|
|
|
|
def fake_run(args, settings):
|
|
return {"ok": True, "args": args, "exit_code": 0, "stdout": "done", "stderr": ""}
|
|
|
|
monkeypatch.setattr("reyna_cli.web.run_reyna_cli", fake_run)
|
|
client = TestClient(app)
|
|
response = client.post("/api/run", json={"command": "doctor --json"})
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload["ok"] is True
|
|
assert payload["args"] == ["doctor", "--json"]
|
|
assert payload["stdout"] == "done"
|
|
|
|
|
|
def test_normalize_args_refuses_nested_server():
|
|
try:
|
|
normalize_args(RunRequest(command="--help web"))
|
|
except Exception as exc:
|
|
assert getattr(exc, "status_code", None) == 400
|
|
else:
|
|
raise AssertionError("nested web command was not rejected")
|
|
|
|
|
|
def test_custom_tts_rejects_raw_text_placeholder(tmp_path):
|
|
try:
|
|
_custom_tts("hello", tmp_path / "audio", "printf {raw_text}", None)
|
|
except TTSError as exc:
|
|
assert "raw_text" in str(exc)
|
|
else:
|
|
raise AssertionError("raw_text placeholder was not rejected")
|