fix: typer exit codes, JWT 7d expiry, shared test fixtures, AdminTab query, mantine 9.6.3

This commit is contained in:
Jonas Miederer 2026-09-30 22:27:58 +02:00
parent 03af41e837
commit 696db34c90
16 changed files with 210 additions and 251 deletions

View file

@ -7,35 +7,13 @@ from app.flows.schemas import ApprovalCreate
from .conftest import load_flow_fixture
def load_fixture(name: str) -> dict:
"""Alias of the shared conftest helper (kept for call-site stability)."""
return load_flow_fixture(name)
async def _create_flow(client, name):
response = await client.post("/api/flows", json={"name": name})
assert response.status_code == 201, response.text
return response.json()
async def _version_and_deploy(client, flow_id, fixture="simple_rule.json"):
response = await client.post(
f"/api/flows/{flow_id}/versions",
json={"langflow_json": load_fixture(fixture)},
)
assert response.status_code == 201, response.text
response = await client.post(f"/api/flows/{flow_id}/versions/1/deploy")
assert response.status_code == 200, response.text
return response.json()
async def _handoff_task(client, live_server, monkeypatch):
async def _handoff_task(client, live_server, monkeypatch, flow_helpers):
"""Run approval_handoff over threshold; returns (run, task)."""
from app.settings import settings
monkeypatch.setattr(settings, "callback_base_url", live_server)
flow = await _create_flow(client, f"handoff-{uuid4().hex[:8]}")
await _version_and_deploy(client, flow["id"], fixture="approval_handoff.json")
flow = await flow_helpers["create_flow"](f"handoff-{uuid4().hex[:8]}")
await flow_helpers["version_and_deploy"](flow["id"], fixture="approval_handoff.json")
body = {"input": {"betrag": 250000, "schwelle": 200000}}
run = (await client.post(f"/api/flows/{flow['id']}/run", json=body)).json()
assert run["status"] == "pending_approval"
@ -44,9 +22,11 @@ async def _handoff_task(client, live_server, monkeypatch):
return run, task
async def test_approve_without_continuation(client, lfx_server, live_server, login, monkeypatch):
async def test_approve_without_continuation(
client, lfx_server, live_server, login, monkeypatch, flow_helpers
):
await login()
_, task = await _handoff_task(client, live_server, monkeypatch)
_, task = await _handoff_task(client, live_server, monkeypatch, flow_helpers)
response = await client.post(
f"/api/approvals/{task['id']}/decision",
json={"decision": "approve", "note": "ok"},
@ -60,11 +40,11 @@ async def test_approve_without_continuation(client, lfx_server, live_server, log
async def test_approve_with_continuation_runs_facts(
client, lfx_server, live_server, login, monkeypatch
client, lfx_server, live_server, login, monkeypatch, flow_helpers
):
await login()
cont = await _create_flow(client, f"cont-{uuid4().hex[:8]}")
await _version_and_deploy(client, cont["id"], fixture="simple_rule.json")
cont = await flow_helpers["create_flow"](f"cont-{uuid4().hex[:8]}")
await flow_helpers["version_and_deploy"](cont["id"], fixture="simple_rule.json")
# Continuation is authoring-time wiring on the definition (admin-only);
# run bodies cannot set it.
@ -74,7 +54,7 @@ async def test_approve_with_continuation_runs_facts(
)
assert response.status_code == 201, response.text
flow = response.json()
await _version_and_deploy(client, flow["id"], fixture="approval_handoff.json")
await flow_helpers["version_and_deploy"](flow["id"], fixture="approval_handoff.json")
from app.settings import settings
@ -105,16 +85,18 @@ async def test_approve_with_continuation_runs_facts(
assert "Sonderfall" in cont_run["output"]["result"]
async def test_client_continuation_is_ignored(client, lfx_server, live_server, login, monkeypatch):
async def test_client_continuation_is_ignored(
client, lfx_server, live_server, login, monkeypatch, flow_helpers
):
"""A run body cannot chain arbitrary flows (G)."""
from app.settings import settings
await login()
monkeypatch.setattr(settings, "callback_base_url", live_server)
cont = await _create_flow(client, f"cont-{uuid4().hex[:8]}")
await _version_and_deploy(client, cont["id"], fixture="simple_rule.json")
flow = await _create_flow(client, f"handoff-{uuid4().hex[:8]}")
await _version_and_deploy(client, flow["id"], fixture="approval_handoff.json")
cont = await flow_helpers["create_flow"](f"cont-{uuid4().hex[:8]}")
await flow_helpers["version_and_deploy"](cont["id"], fixture="simple_rule.json")
flow = await flow_helpers["create_flow"](f"handoff-{uuid4().hex[:8]}")
await flow_helpers["version_and_deploy"](flow["id"], fixture="approval_handoff.json")
run = (
await client.post(
f"/api/flows/{flow['id']}/run",
@ -130,9 +112,9 @@ async def test_client_continuation_is_ignored(client, lfx_server, live_server, l
assert task["continuation_flow_id"] is None
async def test_reject_task(client, lfx_server, live_server, login, monkeypatch):
async def test_reject_task(client, lfx_server, live_server, login, monkeypatch, flow_helpers):
await login()
_, task = await _handoff_task(client, live_server, monkeypatch)
_, task = await _handoff_task(client, live_server, monkeypatch, flow_helpers)
# Any logged-in user (not just admins) can decide; identity is the session.
await login(email="chef@example.com", role="member")
response = await client.post(
@ -147,9 +129,9 @@ async def test_reject_task(client, lfx_server, live_server, login, monkeypatch):
assert body["continuation_run"] is None
async def test_decision_guards(client, lfx_server, live_server, login, monkeypatch):
async def test_decision_guards(client, lfx_server, live_server, login, monkeypatch, flow_helpers):
await login()
_, task = await _handoff_task(client, live_server, monkeypatch)
_, task = await _handoff_task(client, live_server, monkeypatch, flow_helpers)
url = f"/api/approvals/{task['id']}/decision"
response = await client.post(url, json={"decision": "approve"})
@ -160,7 +142,7 @@ async def test_decision_guards(client, lfx_server, live_server, login, monkeypat
response = await client.post(f"/api/approvals/{uuid4()}/decision", json={"decision": "approve"})
assert response.status_code == 404
_, task2 = await _handoff_task(client, live_server, monkeypatch)
_, task2 = await _handoff_task(client, live_server, monkeypatch, flow_helpers)
response = await client.post(
f"/api/approvals/{task2['id']}/decision", json={"decision": "maybe"}
)
@ -174,15 +156,15 @@ async def test_decision_guards(client, lfx_server, live_server, login, monkeypat
assert response.status_code == 200
async def test_decision_bad_continuation_reference(client, session, login):
async def test_decision_bad_continuation_reference(client, session, login, flow_helpers):
from app.flows import store as flow_store
await login()
flow = await _create_flow(client, f"badcont-{uuid4().hex[:8]}")
flow = await flow_helpers["create_flow"](f"badcont-{uuid4().hex[:8]}")
version = (
await client.post(
f"/api/flows/{flow['id']}/versions",
json={"langflow_json": load_fixture("simple_rule.json")},
json={"langflow_json": load_flow_fixture("simple_rule.json")},
)
).json()
run = flow_store.create_run(
@ -200,11 +182,11 @@ async def test_decision_bad_continuation_reference(client, session, login):
assert response.status_code == 422
async def test_tenant_isolation(client, lfx_server, login):
async def test_tenant_isolation(client, lfx_server, login, flow_helpers):
"""Strict session scoping: explicit ?tenant_id= cannot widen reads (E)."""
await login()
flow = await _create_flow(client, f"tenant-{uuid4().hex[:8]}")
await _version_and_deploy(client, flow["id"])
flow = await flow_helpers["create_flow"](f"tenant-{uuid4().hex[:8]}")
await flow_helpers["version_and_deploy"](flow["id"])
await login(email="a@example.com", role="member", tenant_id="pfarramt-a")
response = await client.post(f"/api/flows/{flow['id']}/run", json={"input": {"betrag": 1}})
@ -235,14 +217,14 @@ async def test_tenant_isolation(client, lfx_server, login):
assert response.status_code == 200
async def test_lfx_down_maps_to_503(client, lfx_server, monkeypatch, login):
async def test_lfx_down_maps_to_503(client, lfx_server, monkeypatch, login, flow_helpers):
from app.settings import settings
await login()
flow = await _create_flow(client, f"down-{uuid4().hex[:8]}")
flow = await flow_helpers["create_flow"](f"down-{uuid4().hex[:8]}")
await client.post(
f"/api/flows/{flow['id']}/versions",
json={"langflow_json": load_fixture("simple_rule.json")},
json={"langflow_json": load_flow_fixture("simple_rule.json")},
)
monkeypatch.setattr(settings, "lfx_url", "http://127.0.0.1:9")
response = await client.post(f"/api/flows/{flow['id']}/versions/1/deploy")
@ -257,13 +239,13 @@ async def test_lfx_down_maps_to_503(client, lfx_server, monkeypatch, login):
assert response.status_code == 503
async def test_lfx_drift_maps_to_502(client, lfx_server, login):
async def test_lfx_drift_maps_to_502(client, lfx_server, login, flow_helpers):
from app.flows.lfx_client import LfxClient
from app.settings import settings
await login()
flow = await _create_flow(client, f"drift-{uuid4().hex[:8]}")
deployed = await _version_and_deploy(client, flow["id"])
flow = await flow_helpers["create_flow"](f"drift-{uuid4().hex[:8]}")
deployed = await flow_helpers["version_and_deploy"](flow["id"])
await LfxClient(settings.lfx_url, api_key=settings.lfx_api_key or None).delete_flow(
deployed["lfx_flow_id"]
)
@ -271,9 +253,9 @@ async def test_lfx_drift_maps_to_502(client, lfx_server, login):
assert response.status_code == 502
async def test_corrupt_flow_json_deploy_maps_to_422(client, lfx_server, login):
async def test_corrupt_flow_json_deploy_maps_to_422(client, lfx_server, login, flow_helpers):
await login()
flow = await _create_flow(client, f"corrupt-{uuid4().hex[:8]}")
flow = await flow_helpers["create_flow"](f"corrupt-{uuid4().hex[:8]}")
bogus = {
"name": "bogus",
"data": {