|
|
@@ -0,0 +1,66 @@
|
|
|
+"""Reservations no print can use any more are released at start.
|
|
|
+
|
|
|
+Before b2ed37b6c, billing a print without a user (authentication off, or a
|
|
|
+user deleted while it printed) stopped before releasing the print's budget
|
|
|
+reservation, so it held that much of the cost centre's budget for good. A
|
|
|
+reservation whose queue entry has finished or is gone can never be charged.
|
|
|
+"""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import pytest
|
|
|
+from sqlalchemy import select
|
|
|
+
|
|
|
+from backend.app.core.database import _release_stuck_budget_reservations
|
|
|
+from backend.app.models.finance import BudgetReservation, CostCenter
|
|
|
+from backend.app.models.print_queue import PrintQueueItem
|
|
|
+
|
|
|
+pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
|
|
+
|
|
|
+CASES = {
|
|
|
+ # queue entry status (None: entry deleted) -> reservation released?
|
|
|
+ "completed": True,
|
|
|
+ "failed": True,
|
|
|
+ "cancelled": True,
|
|
|
+ None: True,
|
|
|
+ "printing": False,
|
|
|
+ "pending": False,
|
|
|
+ "skipped": False, # can go back to pending
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+async def test_reservations_of_finished_or_deleted_entries_are_released(db_session, printer_factory, archive_factory):
|
|
|
+ printer = await printer_factory()
|
|
|
+ archive = await archive_factory(printer.id)
|
|
|
+ center = CostCenter(name="Lab", total_budget=100)
|
|
|
+ db_session.add(center)
|
|
|
+ await db_session.flush()
|
|
|
+ reservation_ids = {}
|
|
|
+ for status in CASES:
|
|
|
+ if status is None:
|
|
|
+ source_id = 999_999
|
|
|
+ else:
|
|
|
+ item = PrintQueueItem(printer_id=printer.id, archive_id=archive.id, status=status)
|
|
|
+ db_session.add(item)
|
|
|
+ await db_session.flush()
|
|
|
+ source_id = item.id
|
|
|
+ reservation = BudgetReservation(
|
|
|
+ cost_center_id=center.id, amount=5, status="active", source_type="print_queue", source_id=source_id
|
|
|
+ )
|
|
|
+ db_session.add(reservation)
|
|
|
+ await db_session.flush()
|
|
|
+ reservation_ids[status] = reservation.id
|
|
|
+ other = BudgetReservation(cost_center_id=center.id, amount=5, status="active", source_type="manual", source_id=1)
|
|
|
+ db_session.add(other)
|
|
|
+ await db_session.commit()
|
|
|
+ other_id = other.id
|
|
|
+
|
|
|
+ await _release_stuck_budget_reservations(await db_session.connection())
|
|
|
+ await _release_stuck_budget_reservations(await db_session.connection()) # a restart changes nothing
|
|
|
+ await db_session.commit()
|
|
|
+
|
|
|
+ db_session.expire_all()
|
|
|
+ rows = dict((await db_session.execute(select(BudgetReservation.id, BudgetReservation.status))).all())
|
|
|
+ released = {status: rows[rid] == "released" for status, rid in reservation_ids.items()}
|
|
|
+ assert released == CASES
|
|
|
+ assert rows[other_id] == "active"
|