test_one_print_cost_b5.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """An archive's cost is the cost of one print of it.
  2. Completion stores the first run's cost on the archive and every run's own cost
  3. on its Print History entry (#1378). "Recalculate costs" and "Rescan" used to
  4. sum the spool usage of every run, so an archive printed three times was priced
  5. three times over; editing its grams repriced nothing.
  6. """
  7. import pytest
  8. from httpx import AsyncClient
  9. from sqlalchemy import select
  10. from backend.app.models.print_log import PrintLogEntry
  11. from backend.app.models.spool_usage_history import SpoolUsageHistory
  12. async def _archive(archive_factory, printer_factory, db_session, *, grams, cost=None, with_run=True):
  13. printer = await printer_factory()
  14. archive = await archive_factory(
  15. printer.id,
  16. print_name="Bracket",
  17. status="completed",
  18. cost=cost,
  19. filament_used_grams=grams,
  20. filament_type="PLA",
  21. with_run=with_run,
  22. )
  23. return archive.id
  24. async def _usage(db_session, archive_id, *rows):
  25. for grams, cost in rows:
  26. db_session.add(SpoolUsageHistory(archive_id=archive_id, weight_used=grams, cost=cost, print_name="Bracket"))
  27. await db_session.commit()
  28. async def _cost(async_client, archive_id):
  29. response = await async_client.get(f"/api/v1/archives/{archive_id}")
  30. assert response.status_code == 200
  31. return response.json()["cost"]
  32. @pytest.mark.asyncio
  33. @pytest.mark.integration
  34. async def test_recalculate_prices_one_print_of_an_archive_printed_twice(
  35. async_client: AsyncClient, archive_factory, printer_factory, db_session
  36. ):
  37. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=100.0)
  38. await _usage(db_session, archive_id, (100.0, 3.0), (100.0, 3.0))
  39. assert (await async_client.post("/api/v1/archives/recalculate-costs")).status_code == 200
  40. assert await _cost(async_client, archive_id) == 3.0
  41. @pytest.mark.asyncio
  42. @pytest.mark.integration
  43. async def test_recalculate_still_prices_untracked_grams_at_the_default(
  44. async_client: AsyncClient, archive_factory, printer_factory, db_session
  45. ):
  46. """One run, half the grams on an inventory spool (#1344): 50 g at the
  47. spool's 2.00 plus 50 g at the 25/kg default."""
  48. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=100.0)
  49. await _usage(db_session, archive_id, (50.0, 2.0))
  50. assert (await async_client.post("/api/v1/archives/recalculate-costs")).status_code == 200
  51. assert await _cost(async_client, archive_id) == 3.25
  52. @pytest.mark.asyncio
  53. @pytest.mark.integration
  54. async def test_editing_grams_prices_the_archive_and_its_run(
  55. async_client: AsyncClient, archive_factory, printer_factory, db_session
  56. ):
  57. """A print archived without its 3MF has neither grams nor cost (#1820)."""
  58. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=None)
  59. response = await async_client.patch(f"/api/v1/archives/{archive_id}", json={"filament_used_grams": 120})
  60. assert response.status_code == 200
  61. assert response.json()["cost"] == 3.0
  62. run = await db_session.scalar(select(PrintLogEntry).where(PrintLogEntry.archive_id == archive_id))
  63. await db_session.refresh(run)
  64. assert (run.filament_used_grams, run.cost) == (120, 3.0)
  65. @pytest.mark.asyncio
  66. @pytest.mark.integration
  67. async def test_a_cost_given_with_the_grams_is_kept(
  68. async_client: AsyncClient, archive_factory, printer_factory, db_session
  69. ):
  70. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=100.0, cost=2.5)
  71. response = await async_client.patch(
  72. f"/api/v1/archives/{archive_id}", json={"filament_used_grams": 200, "cost": 9.99}
  73. )
  74. assert response.json()["cost"] == 9.99
  75. @pytest.mark.asyncio
  76. @pytest.mark.integration
  77. async def test_a_run_that_measured_its_own_grams_keeps_its_cost(
  78. async_client: AsyncClient, archive_factory, printer_factory, db_session
  79. ):
  80. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=100.0, cost=2.5)
  81. run = await db_session.scalar(select(PrintLogEntry).where(PrintLogEntry.archive_id == archive_id))
  82. run.filament_used_grams, run.cost = 37.0, 1.11
  83. await db_session.commit()
  84. response = await async_client.patch(f"/api/v1/archives/{archive_id}", json={"filament_used_grams": 200})
  85. assert response.json()["cost"] == 5.0
  86. await db_session.refresh(run)
  87. assert (run.filament_used_grams, run.cost) == (37.0, 1.11)
  88. async def _runs(db_session, archive_id, *grams):
  89. for g in grams:
  90. db_session.add(PrintLogEntry(archive_id=archive_id, status="completed", filament_used_grams=g))
  91. await db_session.commit()
  92. @pytest.mark.asyncio
  93. @pytest.mark.integration
  94. async def test_recalculate_keeps_the_untracked_share_of_every_run(
  95. async_client: AsyncClient, archive_factory, printer_factory, db_session
  96. ):
  97. """Printed three times, half of each 100 g run on a 60/kg spool: one print
  98. is 50 g at 0.06 plus 50 g at the 25/kg default, as completion priced it."""
  99. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=100.0)
  100. await _runs(db_session, archive_id, 100.0, 100.0)
  101. await _usage(db_session, archive_id, (50.0, 3.0), (50.0, 3.0), (50.0, 3.0))
  102. assert (await async_client.post("/api/v1/archives/recalculate-costs")).status_code == 200
  103. assert await _cost(async_client, archive_id) == 4.25
  104. @pytest.mark.asyncio
  105. @pytest.mark.integration
  106. async def test_recalculate_prices_unpriced_usage_at_the_default(
  107. async_client: AsyncClient, archive_factory, printer_factory, db_session
  108. ):
  109. """A usage row recorded without a price is not free filament."""
  110. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=100.0)
  111. await _usage(db_session, archive_id, (50.0, 2.0), (50.0, None))
  112. assert (await async_client.post("/api/v1/archives/recalculate-costs")).status_code == 200
  113. assert await _cost(async_client, archive_id) == 3.25
  114. @pytest.mark.asyncio
  115. @pytest.mark.integration
  116. async def test_recalculate_without_grams_prices_an_average_run(
  117. async_client: AsyncClient, archive_factory, printer_factory, db_session
  118. ):
  119. """Archived without its 3MF, so no grams: the spool usage of its two runs
  120. still prices one of them."""
  121. archive_id = await _archive(archive_factory, printer_factory, db_session, grams=None)
  122. await _runs(db_session, archive_id, None)
  123. await _usage(db_session, archive_id, (40.0, 2.0), (40.0, 2.0))
  124. assert (await async_client.post("/api/v1/archives/recalculate-costs")).status_code == 200
  125. assert await _cost(async_client, archive_id) == 2.0