test_archive_edit_cost_and_reprint.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. """Editing an archive: its cost reaches the run, a reprint's run is not touched.
  2. * A hand-set cost was saved on the archive only. Statistics, project totals
  3. and batch sums read the run's log entry, so they kept the old figure (COST-5).
  4. Like the grams, it reaches the run that inherited it, never a run that
  5. measured its own.
  6. * While the archive is being reprinted, its newest log entry is the previous
  7. run's; an edit then rewrote that run's outcome (FG/I9).
  8. * A negative cost was accepted and subtracted from every total (COST-6).
  9. """
  10. import pytest
  11. from sqlalchemy import select
  12. from backend.app.models.print_log import PrintLogEntry
  13. pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
  14. async def _entry(db_session, archive_id):
  15. query = select(PrintLogEntry).where(PrintLogEntry.archive_id == archive_id)
  16. return (await db_session.scalars(query.execution_options(populate_existing=True))).one()
  17. class TestAHandSetCost:
  18. async def test_reaches_the_run_that_inherited_it(self, async_client, archive_factory, printer_factory, db_session):
  19. printer = await printer_factory()
  20. archive = await archive_factory(printer.id, cost=1.50)
  21. response = await async_client.patch(f"/api/v1/archives/{archive.id}", json={"cost": 5.00})
  22. assert response.status_code == 200, response.text
  23. assert (await _entry(db_session, archive.id)).cost == 5.00
  24. async def test_leaves_a_run_that_measured_its_own(self, async_client, archive_factory, printer_factory, db_session):
  25. printer = await printer_factory()
  26. archive = await archive_factory(printer.id, cost=1.50)
  27. entry = await _entry(db_session, archive.id)
  28. entry.cost = 1.23 # the tracked spools' price for this run
  29. await db_session.commit()
  30. await async_client.patch(f"/api/v1/archives/{archive.id}", json={"cost": 5.00})
  31. assert (await _entry(db_session, archive.id)).cost == 1.23
  32. async def test_a_negative_cost_is_refused(self, async_client, archive_factory, printer_factory, db_session):
  33. printer = await printer_factory()
  34. archive = await archive_factory(printer.id, cost=1.50)
  35. response = await async_client.patch(f"/api/v1/archives/{archive.id}", json={"cost": -100})
  36. assert response.status_code == 422
  37. assert (await _entry(db_session, archive.id)).cost == 1.50
  38. class TestAnEditDuringAReprint:
  39. async def test_leaves_the_previous_run_alone(self, async_client, archive_factory, printer_factory, db_session):
  40. from backend.app.models.archive import PrintArchive
  41. printer = await printer_factory()
  42. archive = await archive_factory(printer.id, status="completed", filament_used_grams=10.0, cost=1.50)
  43. # The reprint is running: the archive row is its, the entry the last run's
  44. row = await db_session.get(PrintArchive, archive.id)
  45. row.status = "printing"
  46. await db_session.commit()
  47. response = await async_client.patch(
  48. f"/api/v1/archives/{archive.id}",
  49. json={"failure_reason": "spaghetti", "user_verdict": "reject", "filament_used_grams": 12.0, "cost": 9.0},
  50. )
  51. assert response.status_code == 200, response.text
  52. entry = await _entry(db_session, archive.id)
  53. assert (entry.status, entry.failure_reason, entry.user_verdict, entry.filament_used_grams, entry.cost) == (
  54. "completed",
  55. None,
  56. None,
  57. 10.0,
  58. 1.50,
  59. )
  60. class TestWhichRunsTakeIt:
  61. async def test_not_a_failed_run_that_printed_only_part(
  62. self, async_client, archive_factory, printer_factory, db_session
  63. ):
  64. """A failed run without tracked spools has no cost on purpose."""
  65. printer = await printer_factory()
  66. archive = await archive_factory(printer.id, cost=1.50, status="failed")
  67. entry = await _entry(db_session, archive.id)
  68. entry.cost = None
  69. await db_session.commit()
  70. await async_client.patch(f"/api/v1/archives/{archive.id}", json={"cost": 5.00})
  71. assert (await _entry(db_session, archive.id)).cost is None
  72. async def test_after_recalculate_costs_too(self, async_client, archive_factory, printer_factory, db_session):
  73. """Recalculate gives the run the new figure as well, so a later hand
  74. edit still finds the run carrying the archive's cost."""
  75. printer = await printer_factory()
  76. archive = await archive_factory(printer.id, cost=1.50, filament_used_grams=100.0, filament_type="PLA")
  77. recalculated = await async_client.post("/api/v1/archives/recalculate-costs")
  78. assert recalculated.json()["updated"] == 1
  79. recalculated_cost = (await _entry(db_session, archive.id)).cost
  80. assert recalculated_cost not in (None, 1.50)
  81. await async_client.patch(f"/api/v1/archives/{archive.id}", json={"cost": 2.00})
  82. assert (await _entry(db_session, archive.id)).cost == 2.00
  83. async def test_an_infinite_cost_is_refused(self, async_client, archive_factory, printer_factory):
  84. printer = await printer_factory()
  85. archive = await archive_factory(printer.id, cost=1.50)
  86. response = await async_client.patch(
  87. f"/api/v1/archives/{archive.id}",
  88. content=b'{"cost": Infinity}',
  89. headers={"Content-Type": "application/json"},
  90. )
  91. assert response.status_code == 422