Compare commits
No commits in common. "e706b46003eaaac30d1fb1df9262ae777f272672" and "6bdebe456298810e79f5a1b84893c88b7122e040" have entirely different histories.
e706b46003
...
6bdebe4562
2 changed files with 0 additions and 138 deletions
|
|
@ -1,124 +0,0 @@
|
||||||
"""Editing an event must not destroy its ticket waves.
|
|
||||||
|
|
||||||
`api_event_update` replaces `extra` wholesale, so a client that rebuilds the
|
|
||||||
envelope rather than round-tripping it used to wipe every wave: the list
|
|
||||||
landed empty, `ensure_ticket_waves` synthesized one primary wave from the
|
|
||||||
event-level `amount_tickets`, and a multi-wave event silently collapsed to a
|
|
||||||
single tier carrying whatever that client happened to send. Same hazard the
|
|
||||||
`promo_codes` guard already covered.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from ..models import CreateEvent, Event, EventExtra, PromoCode, TicketWave
|
|
||||||
|
|
||||||
TODAY = datetime.now(timezone.utc).date()
|
|
||||||
|
|
||||||
|
|
||||||
def _day(offset: int) -> str:
|
|
||||||
return (TODAY + timedelta(days=offset)).isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
def _wave(wave_id: str, price: float, stock: int) -> TicketWave:
|
|
||||||
return TicketWave(
|
|
||||||
id=wave_id,
|
|
||||||
title=wave_id,
|
|
||||||
opening_date=_day(0),
|
|
||||||
closing_date=_day(20),
|
|
||||||
currency="sat",
|
|
||||||
price_per_ticket=price,
|
|
||||||
amount_tickets=stock,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
STORED = [_wave("early", 10, 5), _wave("regular", 25, 40)]
|
|
||||||
|
|
||||||
|
|
||||||
def _stored_event() -> Event:
|
|
||||||
return Event(
|
|
||||||
id="evt",
|
|
||||||
wallet="w",
|
|
||||||
name="Fete",
|
|
||||||
info="",
|
|
||||||
closing_date=_day(20),
|
|
||||||
event_start_date=_day(30),
|
|
||||||
currency="sat",
|
|
||||||
price_per_ticket=10,
|
|
||||||
amount_tickets=45,
|
|
||||||
time=datetime.now(timezone.utc),
|
|
||||||
extra=EventExtra(
|
|
||||||
ticket_waves=list(STORED), promo_codes=[PromoCode(code="KEEP")]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _incoming(extra_payload: dict) -> CreateEvent:
|
|
||||||
"""A request built from raw JSON, so `__fields_set__` reflects exactly
|
|
||||||
which keys the client actually sent."""
|
|
||||||
return CreateEvent(
|
|
||||||
wallet="w",
|
|
||||||
name="Fete",
|
|
||||||
info="",
|
|
||||||
event_start_date=_day(30),
|
|
||||||
currency="sat",
|
|
||||||
price_per_ticket=77,
|
|
||||||
amount_tickets=999,
|
|
||||||
extra=EventExtra(**extra_payload),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_guard(data: CreateEvent, event: Event) -> CreateEvent:
|
|
||||||
"""The carry-over as `api_event_update` performs it."""
|
|
||||||
if "ticket_waves" not in data.extra.__fields_set__:
|
|
||||||
data.extra.ticket_waves = event.extra.ticket_waves
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_that_omits_waves_keeps_them():
|
|
||||||
"""The regression: a client rebuilding `extra` from scratch."""
|
|
||||||
data = _apply_guard(_incoming({"email_notifications": False}), _stored_event())
|
|
||||||
assert [w.id for w in data.extra.ticket_waves] == ["early", "regular"]
|
|
||||||
assert [w.price_per_ticket for w in data.extra.ticket_waves] == [10, 25]
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_that_sends_waves_still_wins():
|
|
||||||
replacement = [_wave("solo", 30, 12)]
|
|
||||||
data = _apply_guard(_incoming({"ticket_waves": replacement}), _stored_event())
|
|
||||||
assert [w.id for w in data.extra.ticket_waves] == ["solo"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_explicit_empty_list_still_resets():
|
|
||||||
"""Matches the promo_codes contract: naming the key means you meant it."""
|
|
||||||
data = _apply_guard(_incoming({"ticket_waves": []}), _stored_event())
|
|
||||||
assert data.extra.ticket_waves == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_guard_runs_before_capacity_validation():
|
|
||||||
"""Validation must see the carried-over waves.
|
|
||||||
|
|
||||||
Without the ordering, a client omitting both the waves and a real
|
|
||||||
capacity would be rejected for a zero-capacity primary wave that only
|
|
||||||
existed because its waves had just been dropped.
|
|
||||||
"""
|
|
||||||
from ..views_api import _validate_wave_capacity
|
|
||||||
|
|
||||||
stored = _stored_event()
|
|
||||||
data = _incoming({"email_notifications": False})
|
|
||||||
data.amount_tickets = 0
|
|
||||||
|
|
||||||
_validate_wave_capacity(_apply_guard(data, stored), stored)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("key", ["promo_codes", "ticket_waves"])
|
|
||||||
def test_both_organiser_owned_extra_fields_are_guarded(key):
|
|
||||||
"""Regression net: `extra` holds organiser state a client need not know
|
|
||||||
about, and every such field needs the same carry-over."""
|
|
||||||
stored = _stored_event()
|
|
||||||
data = _incoming({"email_notifications": False})
|
|
||||||
if "promo_codes" not in data.extra.__fields_set__:
|
|
||||||
data.extra.promo_codes = stored.extra.promo_codes
|
|
||||||
_apply_guard(data, stored)
|
|
||||||
|
|
||||||
assert getattr(data.extra, key), f"{key} was dropped on edit"
|
|
||||||
14
views_api.py
14
views_api.py
|
|
@ -416,20 +416,6 @@ async def api_event_update(
|
||||||
if event.wallet != wallet.wallet.id:
|
if event.wallet != wallet.wallet.id:
|
||||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
|
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
|
||||||
|
|
||||||
# Carry the stored waves over unless the request names the key. `extra` is
|
|
||||||
# replaced wholesale below, so a client that rebuilds the envelope instead
|
|
||||||
# of round-tripping it would otherwise destroy every wave: the list lands
|
|
||||||
# empty, `ensure_ticket_waves` synthesizes one primary wave from the
|
|
||||||
# event-level `amount_tickets`, and a multi-wave event silently collapses
|
|
||||||
# to a single tier carrying whatever numbers that client happened to send.
|
|
||||||
# Same hazard and same fix as `promo_codes` below — organiser-managed
|
|
||||||
# state living in `extra` that a client need not know about. Must run
|
|
||||||
# BEFORE `_validate_wave_capacity`, so validation sees the waves the event
|
|
||||||
# will actually end up with. An explicit `[]` still resets, as it does for
|
|
||||||
# promo codes.
|
|
||||||
if "ticket_waves" not in data.extra.__fields_set__:
|
|
||||||
data.extra.ticket_waves = event.extra.ticket_waves
|
|
||||||
|
|
||||||
_validate_wave_capacity(data, event)
|
_validate_wave_capacity(data, event)
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue