events/tests/test_frontend_root.py
Padreug f77ad28bdd feat: return buyers to the calling app after Stripe, branded QR endpoint
`_resolve_frontend_root` honours `CreateTicket.frontend_url` when its
origin is one of LNBITS_CORS_ALLOWED_ORIGINS, the LNbits base URL or
LNBITS_CUSTOM_FRONTEND_URL (400 otherwise — a silent fallback would send
the buyer to the wrong app), and falls back to request.base_url as before.
Under that root the fiat path now parameterises the hosted checkout via
`extra["checkout"]` (lnbits StripeCheckoutOptions): success_url
`/events/{id}?checkout=success&tickets=<ids>`, cancel_url
`/events/{id}?checkout=cancelled`, customer_email, an event-named line
item and event_id/quantity/ticket_ids metadata. Ticket ids are minted
before the invoice so the success URL can carry them (the payment_hash
only exists afterwards). ticket_base_url on the rows uses the same root,
so the emailed link lands in the webapp when the webapp was the client.

Purchases are gated on `effective_payment_methods(event)` (checked after
the free-ticket short-circuit, which charges nothing on any rail).

New anonymous `GET /events/api/v1/qr/{ticket_id}` returns a PNG of
`ticket://<id>` — port of upstream v1.6.8's endpoint without ticket-image
compositing — built at error-correction H with the instance QR logo
(`lnbits_qr_logo`) pasted in the centre, matching the client-side QRs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
2026-09-06 19:53:05 +02:00

52 lines
1.7 KiB
Python

from types import SimpleNamespace
import pytest
from fastapi import HTTPException
from lnbits.settings import settings
from ..models import CreateTicket
from ..views_api import _allowed_frontend_origins, _resolve_frontend_root
@pytest.fixture
def lnbits_settings(monkeypatch):
monkeypatch.setattr(settings, "lnbits_baseurl", "https://lnbits.example/")
monkeypatch.setattr(
settings, "lnbits_cors_allowed_origins", ["https://app.example"], raising=False
)
monkeypatch.setattr(
settings,
"lnbits_custom_frontend_url",
"https://Front.Example/login",
raising=False,
)
def _request(base_url: str = "https://lnbits.example/"):
return SimpleNamespace(base_url=base_url)
def test_allowlist_collects_every_configured_origin(lnbits_settings):
assert _allowed_frontend_origins() == {
"https://lnbits.example",
"https://app.example",
"https://front.example",
}
def test_absent_frontend_url_falls_back_to_the_request_host(lnbits_settings):
data = CreateTicket(user_id="u1")
assert _resolve_frontend_root(data, _request()) == "https://lnbits.example"
def test_allowed_origin_is_returned_without_trailing_slash(lnbits_settings):
data = CreateTicket(user_id="u1", frontend_url="https://app.example/events/")
assert _resolve_frontend_root(data, _request()) == "https://app.example/events"
def test_unlisted_origin_is_rejected_loudly(lnbits_settings):
data = CreateTicket(user_id="u1", frontend_url="https://evil.example/events")
with pytest.raises(HTTPException) as exc:
_resolve_frontend_root(data, _request())
assert exc.value.status_code == 400
assert "frontend_url" in exc.value.detail