| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- """Numbers restored from a GitHub backup are checked like numbers typed in.
- Archive and spool-usage restore copied grams, costs, energy and print time
- straight from the backup: a hand-edited or damaged backup could put negative or
- absurd values (or text) into the filament and cost totals, which the archive
- editor itself refuses. Each such field now has to be a finite number of at
- least 0 (grams at most 100000, as the editor allows); anything else is left
- empty, said once per category.
- """
- import math
- import pytest
- from sqlalchemy import select
- from backend.app.models.archive import PrintArchive
- from backend.app.models.spool_usage_history import SpoolUsageHistory
- from backend.app.services.github_restore import _CategoryTally
- from backend.tests.unit.test_github_restore import TestRestoreArchives, _codes, _service
- pytestmark = pytest.mark.asyncio
- def _entry(**numbers):
- return TestRestoreArchives()._archive_entry(**numbers)
- async def _restore_archive(db_session, entry):
- tally = _CategoryTally()
- await _service()._restore_archives(db_session, {"archives": [entry]}, False, tally, {})
- await db_session.commit()
- return (await db_session.execute(select(PrintArchive))).scalar_one(), tally
- async def test_good_numbers_come_through(db_session):
- row, tally = await _restore_archive(
- db_session,
- _entry(
- filament_used_grams=45.2,
- cost=1.13,
- energy_kwh=0.4,
- energy_cost=0.12,
- wear_cost=0.05,
- print_time_seconds=3600,
- ),
- )
- assert (
- row.filament_used_grams,
- row.cost,
- row.energy_kwh,
- row.energy_cost,
- row.wear_cost,
- row.print_time_seconds,
- ) == (
- 45.2,
- 1.13,
- 0.4,
- 0.12,
- 0.05,
- 3600,
- )
- assert "archivesNumbersDropped" not in _codes(tally)
- @pytest.mark.parametrize(
- "bad",
- [
- {"filament_used_grams": -5},
- {"filament_used_grams": 1e9},
- {"cost": "12"},
- {"energy_kwh": math.inf},
- {"energy_cost": float("nan")},
- {"wear_cost": True},
- {"print_time_seconds": -1},
- ],
- ids=["negative-grams", "absurd-grams", "text-cost", "infinite-energy", "nan-cost", "bool-wear", "negative-time"],
- )
- async def test_a_bad_number_is_left_empty_and_said_once(db_session, bad):
- row, tally = await _restore_archive(db_session, _entry(**bad))
- (field,) = bad
- assert getattr(row, field) is None
- assert _codes(tally).count("archivesNumbersDropped") == 1
- async def test_a_bad_quantity_becomes_one(db_session):
- row, _ = await _restore_archive(db_session, _entry(quantity="many"))
- assert row.quantity == 1
- async def test_usage_numbers_are_checked_too(db_session):
- tally = _CategoryTally()
- usage = {
- "usage_history": [
- {
- "spool_id": None,
- "weight_used": -3,
- "percent_used": 500,
- "cost": "abc",
- "created_at": "2026-02-01 09:00:00",
- }
- ]
- }
- await _service()._restore_spools(db_session, {"spools": []}, usage, False, tally, {})
- await db_session.commit()
- row = (await db_session.execute(select(SpoolUsageHistory))).scalar_one()
- assert (row.weight_used, row.cost) == (0, None)
- assert "usageNumbersDropped" in _codes(tally)
- async def test_a_quantity_of_zero_is_kept(db_session):
- """A ruined plate (#3051): counting it as a print again would be wrong."""
- row, _ = await _restore_archive(db_session, _entry(quantity=0))
- assert row.quantity == 0
- async def test_a_huge_integer_is_left_empty_not_a_crash(db_session):
- row, tally = await _restore_archive(db_session, _entry(print_time_seconds=10**400, cost=10**400))
- assert (row.print_time_seconds, row.cost) == (None, None)
- assert "archivesNumbersDropped" in _codes(tally)
- async def test_an_integer_too_large_for_the_column_is_left_empty(db_session):
- row, _ = await _restore_archive(db_session, _entry(print_time_seconds=2**31))
- assert row.print_time_seconds is None
|