145 lines
4.4 KiB
Python
145 lines
4.4 KiB
Python
"""Tests des écarts d'inventaire calculés depuis parts_filtered.csv."""
|
|
|
|
import csv
|
|
from pathlib import Path
|
|
|
|
from lib.rebrickable.inventory_reconciliation import (
|
|
compute_inventory_gaps,
|
|
index_sets_by_num,
|
|
write_inventory_gaps_csv,
|
|
write_inventory_gaps_markdown,
|
|
)
|
|
|
|
|
|
def write_csv(path: Path, headers: list[str], rows: list[list[str]]) -> None:
|
|
"""Écrit un CSV simple pour les besoins des tests."""
|
|
with path.open("w", newline="") as csv_file:
|
|
writer = csv.writer(csv_file)
|
|
writer.writerow(headers)
|
|
writer.writerows(rows)
|
|
|
|
|
|
def test_compute_inventory_gaps_excludes_spares(tmp_path: Path) -> None:
|
|
"""Ignore les pièces de rechange et ne conserve que les sets en écart."""
|
|
sets_path = tmp_path / "sets_enriched.csv"
|
|
parts_path = tmp_path / "parts_filtered.csv"
|
|
write_csv(
|
|
sets_path,
|
|
["set_num", "set_id", "num_parts", "in_collection"],
|
|
[
|
|
["1000-1", "1000", "4", "true"],
|
|
["2000-1", "2000", "3", "false"],
|
|
["3000-1", "3000", "1", "true"],
|
|
],
|
|
)
|
|
write_csv(
|
|
parts_path,
|
|
["part_num", "color_rgb", "is_translucent", "set_num", "set_id", "quantity_in_set", "is_spare"],
|
|
[
|
|
["A", "AAAAAA", "false", "1000-1", "1000", "2", "false"],
|
|
["B", "BBBBBB", "false", "1000-1", "1000", "2", "false"],
|
|
["S", "SSSSSS", "false", "1000-1", "1000", "5", "true"],
|
|
["C", "CCCCCC", "false", "2000-1", "2000", "2", "false"],
|
|
["D", "DDDDDD", "false", "3000-1", "3000", "1", "false"],
|
|
],
|
|
)
|
|
|
|
gaps = compute_inventory_gaps(sets_path, parts_path)
|
|
|
|
assert gaps == [
|
|
{
|
|
"set_num": "1000-1",
|
|
"set_id": "1000",
|
|
"expected_parts": 4,
|
|
"inventory_parts": 9,
|
|
"inventory_parts_non_spare": 4,
|
|
"delta": 5,
|
|
"delta_non_spare": 0,
|
|
"in_collection": "true",
|
|
},
|
|
{
|
|
"set_num": "2000-1",
|
|
"set_id": "2000",
|
|
"expected_parts": 3,
|
|
"inventory_parts": 2,
|
|
"inventory_parts_non_spare": 2,
|
|
"delta": 1,
|
|
"delta_non_spare": 1,
|
|
"in_collection": "false",
|
|
}
|
|
]
|
|
|
|
|
|
def test_write_inventory_gaps_csv(tmp_path: Path) -> None:
|
|
"""Sérialise le rapport d'écarts dans un CSV dédié."""
|
|
destination_path = tmp_path / "inventory_gaps.csv"
|
|
rows = [
|
|
{
|
|
"set_num": "2000-1",
|
|
"set_id": "2000",
|
|
"expected_parts": 3,
|
|
"inventory_parts": 2,
|
|
"inventory_parts_non_spare": 2,
|
|
"delta": 1,
|
|
"delta_non_spare": 1,
|
|
"in_collection": "false",
|
|
}
|
|
]
|
|
|
|
write_inventory_gaps_csv(destination_path, rows)
|
|
|
|
with destination_path.open() as csv_file:
|
|
written_rows = list(csv.DictReader(csv_file))
|
|
|
|
assert written_rows == [
|
|
{
|
|
"set_num": "2000-1",
|
|
"set_id": "2000",
|
|
"expected_parts": "3",
|
|
"inventory_parts": "2",
|
|
"inventory_parts_non_spare": "2",
|
|
"delta": "1",
|
|
"delta_non_spare": "1",
|
|
"in_collection": "false",
|
|
}
|
|
]
|
|
|
|
|
|
def test_write_inventory_gaps_markdown(tmp_path: Path) -> None:
|
|
"""Produit un tableau Markdown listant les sets en écart."""
|
|
destination_path = tmp_path / "inventory_gaps.md"
|
|
gaps = [
|
|
{
|
|
"set_num": "2000-1",
|
|
"set_id": "2000",
|
|
"expected_parts": 3,
|
|
"inventory_parts": 2,
|
|
"inventory_parts_non_spare": 2,
|
|
"delta": 1,
|
|
"delta_non_spare": 1,
|
|
"in_collection": "false",
|
|
}
|
|
]
|
|
sets = [
|
|
{
|
|
"set_num": "2000-1",
|
|
"set_id": "2000",
|
|
"num_parts": "3",
|
|
"name": "Test Set",
|
|
"year": "2020",
|
|
"rebrickable_url": "https://rebrickable.com/sets/2000-1",
|
|
"in_collection": "false",
|
|
}
|
|
]
|
|
|
|
write_inventory_gaps_markdown(destination_path, gaps, index_sets_by_num(sets))
|
|
|
|
with destination_path.open() as markdown_file:
|
|
content = markdown_file.read().splitlines()
|
|
|
|
assert content[0].startswith("| set_id | name |")
|
|
assert (
|
|
"| [2000](https://rebrickable.com/sets/2000-1) | Test Set | 2020 | 1 | 1 | 3 | 2 | 2 | false | [PDF](https://www.lego.com/service/buildinginstructions/2000) |"
|
|
in content
|
|
)
|