| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- """Recalculate Costs prices recorded usage at the spools' current prices (#3414).
- A usage row stores what its grams cost at completion, and Recalculate added
- those up, so a spool price corrected afterwards changed nothing: "0 archives
- were updated". The rows now take the spool's current price first, and the run
- each row was charged in moves by the same difference, as statistics read it.
- """
- from datetime import datetime, timedelta
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from httpx import AsyncClient
- from sqlalchemy import select
- from backend.app.models.archive import PrintArchive
- from backend.app.models.print_log import PrintLogEntry
- from backend.app.models.settings import Settings
- from backend.app.models.spool import Spool
- from backend.app.models.spool_usage_history import SpoolUsageHistory
- pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
- START = datetime(2026, 10, 28, 10, 0, 0)
- async def _spool(db, cost_per_kg):
- spool = Spool(material="PLA", label_weight=1000, core_weight=250, weight_used=0, cost_per_kg=cost_per_kg)
- db.add(spool)
- await db.commit()
- await db.refresh(spool)
- return spool
- async def _print(archive_factory, printer, *, grams, cost, started=START):
- return await archive_factory(
- printer.id,
- status="completed",
- cost=cost,
- filament_used_grams=grams,
- started_at=started,
- completed_at=started + timedelta(hours=1),
- )
- def _usage(archive, printer, *, grams, cost, spool_id=None, spoolman_spool_id=None, at=START, status="completed"):
- return SpoolUsageHistory(
- spool_id=spool_id,
- spoolman_spool_id=spoolman_spool_id,
- printer_id=printer.id,
- archive_id=archive.id if archive is not None else None,
- weight_used=grams,
- percent_used=5,
- status=status,
- cost=cost,
- created_at=at + timedelta(hours=1, seconds=5),
- )
- async def _recalculate(client: AsyncClient, **kwargs):
- response = await client.post("/api/v1/archives/recalculate-costs", **kwargs)
- assert response.status_code == 200, response.text
- return response.json()
- async def _fresh(db, model, row_id):
- return (
- await db.execute(select(model).where(model.id == row_id).execution_options(populate_existing=True))
- ).scalar_one()
- async def _runs(db, archive_id):
- query = select(PrintLogEntry).where(PrintLogEntry.archive_id == archive_id).order_by(PrintLogEntry.id)
- return list((await db.execute(query.execution_options(populate_existing=True))).scalars().all())
- async def test_a_spools_new_price_reaches_its_usage_the_archive_and_the_run(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """The reported case: a print off a priced spool, the spool's price raised
- tenfold afterwards."""
- db_session.add(Settings(key="default_filament_cost", value="1400"))
- printer = await printer_factory()
- spool = await _spool(db_session, 1400.0)
- archive = await _print(archive_factory, printer, grams=45.6, cost=63.84)
- usage = _usage(archive, printer, grams=45.6, cost=63.84, spool_id=spool.id)
- db_session.add(usage)
- spool.cost_per_kg = 14000.0
- await db_session.commit()
- body = await _recalculate(async_client)
- assert body["updated"] == 1
- assert body["usage_repriced"] == 1
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 638.4
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 638.4
- assert [run.cost for run in await _runs(db_session, archive.id)] == [638.4]
- async def test_an_unchanged_price_changes_nothing_and_a_second_run_neither(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="default_filament_cost", value="1400"))
- printer = await printer_factory()
- spool = await _spool(db_session, 1400.0)
- archive = await _print(archive_factory, printer, grams=45.6, cost=63.84)
- usage = _usage(archive, printer, grams=45.6, cost=63.84, spool_id=spool.id)
- db_session.add(usage)
- await db_session.commit()
- first = await _recalculate(async_client)
- second = await _recalculate(async_client)
- assert (first["usage_repriced"], first["updated"]) == (0, 0)
- assert (second["usage_repriced"], second["updated"]) == (0, 0)
- assert [run.cost for run in await _runs(db_session, archive.id)] == [63.84]
- async def test_a_small_prints_price_change_of_a_cent_is_taken(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- archive = await _print(archive_factory, printer, grams=5.0, cost=0.1)
- usage = _usage(archive, printer, grams=5.0, cost=0.1, spool_id=spool.id)
- db_session.add(usage)
- spool.cost_per_kg = 22.0
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 0.11
- assert [run.cost for run in await _runs(db_session, archive.id)] == [0.11]
- async def test_a_reprint_off_an_unchanged_spool_keeps_its_cost(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """The archive's new cost averages both prints; the reprint's own spool
- priced it and did not change."""
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool_a = await _spool(db_session, 20.0)
- spool_b = await _spool(db_session, 20.0)
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- later = START + timedelta(days=1)
- db_session.add(
- PrintLogEntry(
- archive_id=archive.id,
- printer_id=printer.id,
- status="completed",
- started_at=later,
- completed_at=later + timedelta(hours=1),
- filament_used_grams=100.0,
- cost=2.0,
- )
- )
- db_session.add(_usage(archive, printer, grams=100.0, cost=2.0, spool_id=spool_a.id))
- db_session.add(_usage(archive, printer, grams=100.0, cost=2.0, spool_id=spool_b.id, at=later))
- spool_a.cost_per_kg = 40.0
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 3.0
- assert [run.cost for run in await _runs(db_session, archive.id)] == [4.0, 2.0]
- async def test_a_spoolman_print_takes_a_new_default_and_spool_price_together(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """80 g off the spool, 20 g unpriced at the default: completion gave the
- run the archive's whole figure, and it follows the archive's new one."""
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- db_session.add(Settings(key="default_filament_cost", value="50"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.1)
- usage = _usage(archive, printer, grams=80.0, cost=1.6, spoolman_spool_id=41)
- db_session.add(usage)
- await db_session.commit()
- with _spoolman([{"id": 41, "price": 30.0, "initial_weight": 1000, "filament": {"weight": 1000}}]):
- await _recalculate(async_client)
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 3.4
- assert [run.cost for run in await _runs(db_session, archive.id)] == [3.4]
- async def test_spoolman_spool_given_a_price_reaches_every_run(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """Unpriced, completion covered each run's grams at the default rate."""
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- db_session.add(Settings(key="default_filament_cost", value="25"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.5)
- later = START + timedelta(days=1)
- db_session.add(
- PrintLogEntry(
- archive_id=archive.id,
- printer_id=printer.id,
- status="failed",
- started_at=later,
- completed_at=later + timedelta(hours=1),
- cost=1.0,
- )
- )
- db_session.add(_usage(archive, printer, grams=100.0, cost=None, spoolman_spool_id=41))
- db_session.add(_usage(archive, printer, grams=40.0, cost=None, spoolman_spool_id=41, at=later, status="failed"))
- await db_session.commit()
- with _spoolman([{"id": 41, "price": 50.0, "initial_weight": 1000, "filament": {"weight": 1000}}]):
- await _recalculate(async_client)
- assert [run.cost for run in await _runs(db_session, archive.id)] == [5.0, 2.0]
- async def test_usage_recorded_outside_any_run_moves_none(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """A row stamped two hours late (local time on an older install) must not
- move the reprint that started after the print it belongs to."""
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- later = START + timedelta(hours=1, minutes=10)
- db_session.add(
- PrintLogEntry(
- archive_id=archive.id,
- printer_id=printer.id,
- status="completed",
- started_at=later,
- completed_at=later + timedelta(minutes=30),
- cost=1.9,
- )
- )
- usage = _usage(archive, printer, grams=100.0, cost=2.0, spool_id=spool.id)
- usage.created_at = START + timedelta(hours=3)
- db_session.add(usage)
- spool.cost_per_kg = 40.0
- await db_session.commit()
- await _recalculate(async_client)
- assert [run.cost for run in await _runs(db_session, archive.id)] == [2.0, 1.9]
- async def test_a_run_without_an_end_holds_usage_until_the_next_run(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- (first,) = await _runs(db_session, archive.id)
- first.completed_at = None
- first.cost = 1.0
- later = START + timedelta(hours=2)
- db_session.add(
- PrintLogEntry(
- archive_id=archive.id,
- printer_id=printer.id,
- status="completed",
- started_at=later,
- completed_at=later + timedelta(hours=1),
- cost=1.9,
- )
- )
- db_session.add(_usage(archive, printer, grams=50.0, cost=1.0, spool_id=spool.id))
- spool.cost_per_kg = 40.0
- await db_session.commit()
- await _recalculate(async_client)
- assert [run.cost for run in await _runs(db_session, archive.id)] == [2.0, 1.9]
- async def test_the_run_of_a_deleted_archive_follows_its_usage(async_client: AsyncClient, db_session, printer_factory):
- """A printer deleted with its archives leaves both without an archive."""
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- run = PrintLogEntry(
- printer_id=printer.id, status="completed", started_at=START, completed_at=START + timedelta(hours=1), cost=2.0
- )
- db_session.add(run)
- db_session.add(_usage(None, printer, grams=100.0, cost=2.0, spool_id=spool.id))
- spool.cost_per_kg = 40.0
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, PrintLogEntry, run.id)).cost == 4.0
- async def test_a_spool_without_a_price_follows_the_default_cost(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="default_filament_cost", value="50"))
- printer = await printer_factory()
- spool = await _spool(db_session, None)
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.5)
- usage = _usage(archive, printer, grams=100.0, cost=2.5, spool_id=spool.id)
- db_session.add(usage)
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 5.0
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 5.0
- assert [run.cost for run in await _runs(db_session, archive.id)] == [5.0]
- async def test_a_deleted_spools_usage_keeps_what_it_cost(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="default_filament_cost", value="50"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=3.0)
- usage = _usage(archive, printer, grams=100.0, cost=3.0)
- db_session.add(usage)
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 3.0
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 3.0
- async def test_a_deleted_spool_still_named_by_its_usage_keeps_what_it_cost(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """Usage left pointing at a spool id that no longer exists, which only
- SQLite allows: PostgreSQL's foreign key clears it with the spool."""
- if db_session.bind.dialect.name != "sqlite":
- pytest.skip("PostgreSQL refuses a usage row of a missing spool")
- db_session.add(Settings(key="default_filament_cost", value="50"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=3.0)
- usage = _usage(archive, printer, grams=100.0, cost=3.0, spool_id=987654)
- db_session.add(usage)
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 3.0
- async def test_a_run_its_usage_moved_is_not_given_the_archives_cost(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """The run had only its spool's 50 g (1.00), the archive 50 g more at the
- default (2.00). Repriced, the run is 2.00 -- the archive's old figure, which
- must not hand it the archive's new 3.00."""
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- (run,) = await _runs(db_session, archive.id)
- run.cost = 1.0
- db_session.add(_usage(archive, printer, grams=50.0, cost=1.0, spool_id=spool.id))
- spool.cost_per_kg = 40.0
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 3.0
- assert [run.cost for run in await _runs(db_session, archive.id)] == [2.0]
- async def test_each_run_moves_by_its_own_usage(async_client: AsyncClient, db_session, printer_factory, archive_factory):
- """A reprint that failed halfway: each run gets its own row's difference,
- the default-rate grams of the full run stay as they were."""
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- # 100 g: 80 g off the spool at 20/kg (1.60) + 20 g at the default (0.40).
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- later = START + timedelta(days=2)
- db_session.add(
- PrintLogEntry(
- archive_id=archive.id,
- printer_id=printer.id,
- status="failed",
- started_at=later,
- completed_at=later + timedelta(hours=1),
- cost=0.8,
- )
- )
- first = _usage(archive, printer, grams=80.0, cost=1.6, spool_id=spool.id)
- second = _usage(archive, printer, grams=40.0, cost=0.8, spool_id=spool.id, at=later, status="failed")
- db_session.add_all([first, second])
- spool.cost_per_kg = 40.0
- await db_session.commit()
- await _recalculate(async_client)
- assert [run.cost for run in await _runs(db_session, archive.id)] == [3.6, 1.6]
- assert (await _fresh(db_session, SpoolUsageHistory, first.id)).cost == 3.2
- assert (await _fresh(db_session, SpoolUsageHistory, second.id)).cost == 1.6
- async def test_a_run_on_another_printer_is_not_charged_this_ones_usage(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="default_filament_cost", value="20"))
- here = await printer_factory(name="Here")
- there = await printer_factory(name="There")
- spool = await _spool(db_session, 20.0)
- archive = await _print(archive_factory, here, grams=100.0, cost=2.0)
- db_session.add(
- PrintLogEntry(
- archive_id=archive.id,
- printer_id=there.id,
- status="completed",
- # Started after this printer's run, before its usage was recorded.
- started_at=START + timedelta(minutes=1),
- cost=1.9,
- )
- )
- db_session.add(_usage(archive, here, grams=100.0, cost=2.0, spool_id=spool.id))
- spool.cost_per_kg = 30.0
- await db_session.commit()
- await _recalculate(async_client)
- assert sorted((run.printer_id, run.cost) for run in await _runs(db_session, archive.id)) == sorted(
- [(here.id, 3.0), (there.id, 1.9)]
- )
- def _spoolman(spools):
- client = MagicMock()
- client.get_all_spools = AsyncMock(return_value=spools)
- return patch("backend.app.services.usage_repricing._spoolman_client", AsyncMock(return_value=client))
- async def test_a_spoolman_spools_new_price_reaches_its_usage(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """Feature parity: a Spoolman charge is repriced from the spool's price in
- Spoolman, as a built-in one from the inventory's."""
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- db_session.add(Settings(key="default_filament_cost", value="25"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- usage = _usage(archive, printer, grams=100.0, cost=2.0, spoolman_spool_id=41)
- db_session.add(usage)
- await db_session.commit()
- with _spoolman([{"id": 41, "price": 30.0, "initial_weight": 1000, "filament": {"weight": 1000}}]):
- body = await _recalculate(async_client)
- assert body["usage_repriced"] == 1
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 3.0
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 3.0
- assert [run.cost for run in await _runs(db_session, archive.id)] == [3.0]
- async def test_unreachable_spoolman_keeps_its_usage_and_says_so(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- usage = _usage(archive, printer, grams=100.0, cost=2.0, spoolman_spool_id=41)
- db_session.add(usage)
- await db_session.commit()
- with patch("backend.app.services.usage_repricing._spoolman_client", AsyncMock(return_value=None)):
- body = await _recalculate(async_client)
- assert body["spoolman_unreachable"] == 1
- assert "Spoolman not reachable" in body["message"]
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 2.0
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 2.0
- async def test_a_spool_gone_from_spoolman_keeps_its_usage(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- usage = _usage(archive, printer, grams=100.0, cost=2.0, spoolman_spool_id=41)
- db_session.add(usage)
- await db_session.commit()
- with _spoolman([{"id": 7, "price": 30.0, "initial_weight": 1000}]):
- body = await _recalculate(async_client)
- assert body["spoolman_unreachable"] == 0
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 2.0
- async def test_a_caller_limited_to_some_printers_reprices_only_their_usage(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- from backend.tests.integration.test_bulk_archive_routes_scope import _member
- db_session.add(Settings(key="default_filament_cost", value="20"))
- a, b = await printer_factory(name="A"), await printer_factory(name="B")
- spool = await _spool(db_session, 20.0)
- on_a = await _print(archive_factory, a, grams=100.0, cost=2.0)
- on_b = await _print(archive_factory, b, grams=100.0, cost=2.0)
- usage_a = _usage(on_a, a, grams=100.0, cost=2.0, spool_id=spool.id)
- usage_b = _usage(on_b, b, grams=100.0, cost=2.0, spool_id=spool.id)
- unlinked = _usage(None, b, grams=100.0, cost=2.0, spool_id=spool.id)
- db_session.add_all([usage_a, usage_b, unlinked])
- spool.cost_per_kg = 30.0
- await db_session.commit()
- member = await _member(async_client, [a.id])
- await _recalculate(async_client, headers=member)
- assert (await _fresh(db_session, SpoolUsageHistory, usage_a.id)).cost == 3.0
- assert (await _fresh(db_session, SpoolUsageHistory, usage_b.id)).cost == 2.0
- assert (await _fresh(db_session, SpoolUsageHistory, unlinked.id)).cost == 2.0
- async def test_usage_of_no_archive_is_repriced_for_an_unlimited_caller(
- async_client: AsyncClient, db_session, printer_factory
- ):
- """It shows on the spool's usage list."""
- db_session.add(Settings(key="default_filament_cost", value="20"))
- printer = await printer_factory()
- spool = await _spool(db_session, 20.0)
- unlinked = _usage(None, printer, grams=100.0, cost=2.0, spool_id=spool.id)
- db_session.add(unlinked)
- spool.cost_per_kg = 30.0
- await db_session.commit()
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, unlinked.id)).cost == 3.0
- async def test_a_spoolman_spool_given_a_price_prices_its_print(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """Unpriced at completion, the print took the archive's estimate; with the
- spool priced now, the run follows the archive's new cost."""
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- db_session.add(Settings(key="default_filament_cost", value="25"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=3.0)
- usage = _usage(archive, printer, grams=100.0, cost=None, spoolman_spool_id=41)
- db_session.add(usage)
- await db_session.commit()
- with _spoolman([{"id": 41, "price": 40.0, "initial_weight": 1000, "filament": {"weight": 1000}}]):
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost == 4.0
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 4.0
- assert [run.cost for run in await _runs(db_session, archive.id)] == [4.0]
- async def test_a_spoolman_spool_losing_its_price_keeps_its_print_and_run_together(
- async_client: AsyncClient, db_session, printer_factory, archive_factory
- ):
- """The archive keeps a Spoolman-priced cost it can't rebuild (#2591); its
- run must not be moved away from it."""
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- db_session.add(Settings(key="default_filament_cost", value="25"))
- printer = await printer_factory()
- archive = await _print(archive_factory, printer, grams=100.0, cost=2.0)
- usage = _usage(archive, printer, grams=100.0, cost=2.0, spoolman_spool_id=41)
- db_session.add(usage)
- await db_session.commit()
- with _spoolman([{"id": 41, "initial_weight": 1000, "filament": {"weight": 1000}}]):
- await _recalculate(async_client)
- assert (await _fresh(db_session, SpoolUsageHistory, usage.id)).cost is None
- assert (await _fresh(db_session, PrintArchive, archive.id)).cost == 2.0
- assert [run.cost for run in await _runs(db_session, archive.id)] == [2.0]
|