- ruff profile in pyproject (E/W/F/I/B/RUF); clean src/ + tests/ - remove dead code: threaded_len/s/d/coat/first_rec_for_size, self.cat in gui - 59 public re-exports added to __all__ (sorted) - renamed l->ln (E741), zip(strict=True), split long lines, [*list, x] (RUF005) - CI: quality job (ruff), test runs tests/ -k 'not gen_all' w/ coverage - README rewritten (commands, API modules, tests/lint/coverage, CI) - docs/tasks.md: P4 recorded - 136 passed, 3 deselected (regen); ruff: all checks passed
229 lines
7.3 KiB
Python
229 lines
7.3 KiB
Python
"""Тесты для fastengen — генератор крепёжа с STEP-библиотекой и каталогом."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
from contextlib import contextmanager
|
||
|
||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||
|
||
import pytest
|
||
|
||
from fastengen import PortableCatalog, get_bolt, get_nut, get_washer
|
||
from fastengen.standard import (
|
||
BOLT_ALLOWED_LENGTHS,
|
||
BOLT_SIZES,
|
||
NUT_SIZES,
|
||
WASHER_SIZES,
|
||
)
|
||
|
||
# =====Стандарты ======
|
||
|
||
|
||
def test_bolt_sizes_present():
|
||
assert "M6" in BOLT_SIZES and "M24" in BOLT_SIZES
|
||
assert BOLT_SIZES["M10"]["d"] == 10.0
|
||
|
||
|
||
def test_nut_washer_sizes():
|
||
assert len(NUT_SIZES) == 9
|
||
assert len(WASHER_SIZES) == 9
|
||
|
||
|
||
def test_invalid_bolt_size():
|
||
with pytest.raises(ValueError):
|
||
get_bolt("M99", 40)
|
||
|
||
|
||
def test_invalid_length():
|
||
with pytest.raises(ValueError):
|
||
get_bolt("M10", 33) # 33不在 BOLT_ALLOWED_LENGTHS
|
||
|
||
|
||
def test_valid_lengths():
|
||
assert all(ln in BOLT_ALLOWED_LENGTHS for ln in (20, 25, 120))
|
||
|
||
|
||
# =====Генерация STEP ======
|
||
|
||
|
||
@pytest.mark.parametrize("size,length", [("M6", 20), ("M10", 40), ("M16", 80), ("M24", 120)])
|
||
def test_generate_bolt_step(size, length, tmp_path):
|
||
spec = get_bolt(size, length)
|
||
out = str(tmp_path / f"bolt_{size}_{length}.step")
|
||
PortableCatalog(str(tmp_path / "cat.json"), models_dir=str(tmp_path)).add_bolt(spec)
|
||
assert os.path.exists(out)
|
||
assert os.path.getsize(out) > 1000 # не пустой STEP
|
||
|
||
|
||
@pytest.mark.parametrize("size", list(NUT_SIZES))
|
||
def test_generate_nut_step(size, tmp_path):
|
||
spec = get_nut(size)
|
||
out = str(tmp_path / f"nut_{size}.step")
|
||
PortableCatalog(str(tmp_path / "cat.json"), models_dir=str(tmp_path)).add_nut(spec)
|
||
assert os.path.exists(out)
|
||
assert os.path.getsize(out) > 1000
|
||
|
||
|
||
@pytest.mark.parametrize("size", list(WASHER_SIZES))
|
||
def test_generate_washer_step(size, tmp_path):
|
||
spec = get_washer(size)
|
||
out = str(tmp_path / f"washer_{size}.step")
|
||
PortableCatalog(str(tmp_path / "cat.json"), models_dir=str(tmp_path)).add_washer(spec)
|
||
assert os.path.exists(out)
|
||
assert os.path.getsize(out) > 1000
|
||
|
||
|
||
# =====Каталог ======
|
||
|
||
|
||
def test_catalog_roundtrip(tmp_path):
|
||
cat_path = str(tmp_path / "catalog.json")
|
||
models_dir = str(tmp_path / "out")
|
||
cat = PortableCatalog(cat_path, models_dir=models_dir)
|
||
|
||
# добавляем
|
||
bolt = cat.add_bolt(get_bolt("M12", 45))
|
||
cat.add_nut(get_nut("M12"))
|
||
cat.add_washer(get_washer("M12"))
|
||
cat.save()
|
||
|
||
# размер каталога
|
||
assert len(cat) == 3
|
||
assert "bolt_M12_45" in cat
|
||
assert cat["bolt_M12_45"].designation == (
|
||
"Болт М12×45 8.8 ГОСТ 1759.0-87 ГОСТ 7805-70 Zn (225) ГОСТ 1759.2-76"
|
||
)
|
||
assert bolt.model_file.endswith(".step")
|
||
assert bolt.attributes["диаметр"] == "12"
|
||
|
||
# загрузка заново
|
||
cat2 = PortableCatalog(cat_path, models_dir=models_dir)
|
||
assert len(cat2) == 3
|
||
assert cat2["nut_M12"].type == "nut"
|
||
assert cat2["washer_M12"].standard == "ГОСТ 11371-78"
|
||
|
||
|
||
def test_gen_all(tmp_path):
|
||
cat_path = str(tmp_path / "catalog.json")
|
||
models_dir = str(tmp_path / "out")
|
||
cat = PortableCatalog(cat_path, models_dir=models_dir)
|
||
recs = cat.add_all()
|
||
# 167 болтов + 9 гаек + 9 шайб = 185
|
||
assert len(cat) == 167 + 9 + 9
|
||
assert len(recs) == 185
|
||
assert cat_path.endswith("catalog.json")
|
||
# все модели существуют
|
||
for r in cat:
|
||
assert os.path.exists(os.path.join(models_dir, r.model_file))
|
||
|
||
|
||
# =====CLI ======
|
||
|
||
|
||
def test_cli_bolt_gen(tmp_path):
|
||
from fastengen.cli import main
|
||
|
||
with chdir(tmp_path):
|
||
assert main(["bolt-gen", "M10", "40", "--dir", "out"]) == 0
|
||
assert os.path.exists(str(tmp_path / "out" / "bolt_M10_40.step"))
|
||
assert os.path.exists(str(tmp_path / "catalog.json"))
|
||
|
||
|
||
def test_cli_gen_all(tmp_path):
|
||
from fastengen.cli import main
|
||
|
||
with chdir(tmp_path):
|
||
assert main(["gen-all", "--dir", "out"]) == 0
|
||
cat_path = tmp_path / "catalog.json"
|
||
assert cat_path.exists()
|
||
|
||
|
||
def test_cli_show_and_list(tmp_path):
|
||
from fastengen.cli import main
|
||
|
||
with chdir(tmp_path):
|
||
main(["gen-all", "--dir", "out"])
|
||
assert main(["list"]) == 0
|
||
assert main(["show", "bolt_M10_40"]) == 0
|
||
|
||
|
||
def test_cli_single_gen_saves_to_catalog(tmp_path):
|
||
"""Одинарные *-gen команды должны сохранять запись в каталог (fix list/show)."""
|
||
from fastengen.cli import main
|
||
|
||
with chdir(tmp_path):
|
||
assert main(["nut-gen", "M12", "--dir", "out", "--catalog", "cat.json"]) == 0
|
||
assert main(["washer-gen", "M12", "--dir", "out", "--catalog", "cat.json"]) == 0
|
||
assert main(["bolt-set-gen", "M10", "40", "--dir", "out", "--catalog", "cat.json"]) == 0
|
||
assert main(["washer-l-gen", "M12", "--dir", "out", "--catalog", "cat.json"]) == 0
|
||
# каталог теперь не пустой и показывает записи
|
||
assert main(["list", "--catalog", "cat.json"]) == 0
|
||
assert main(["show", "nut_M12", "--catalog", "cat.json"]) == 0
|
||
assert main(["show", "boltset_M10_40", "--catalog", "cat.json"]) == 0
|
||
assert main(["show", "washer_M12_large", "--catalog", "cat.json"]) == 0
|
||
|
||
|
||
def test_gen_all_extended(tmp_path):
|
||
"""add_all_extended генерирует базовые + все расшиленные семейства без исключений."""
|
||
from fastengen.cli import main
|
||
|
||
with chdir(tmp_path):
|
||
assert main(["gen-all-extended", "--dir", "out", "--catalog", "cat.json"]) == 0
|
||
# каталог должен содержать записи всех семейств
|
||
assert main(["list", "--catalog", "cat.json"]) == 0
|
||
|
||
|
||
def test_add_all_extended_covers_all_types(tmp_path):
|
||
"""Каждый новый тип генерирует валидную STEP-модель (по одной на тип)."""
|
||
import os
|
||
|
||
from fastengen import PortableCatalog
|
||
from fastengen.standard import (
|
||
get_bolt_high,
|
||
get_nut_baran,
|
||
get_nut_high,
|
||
get_nut_specing,
|
||
get_screw_potay,
|
||
get_self_tacker_gost,
|
||
get_stud_long,
|
||
get_washer_grever,
|
||
get_washer_l,
|
||
get_washer_stop,
|
||
)
|
||
|
||
cat = PortableCatalog(str(tmp_path / "cat.json"), models_dir=str(tmp_path / "out"))
|
||
cat.add_bolt_high(get_bolt_high("M12", 45))
|
||
cat.add_screw_potay(get_screw_potay("M10", 40))
|
||
cat.add_nut_high(get_nut_high("M12"))
|
||
cat.add_nut_baran(get_nut_baran("M12"))
|
||
cat.add_nut_specing(get_nut_specing("M12"))
|
||
cat.add_washer_grever(get_washer_grever("M12"))
|
||
cat.add_washer_stop(get_washer_stop("M12"))
|
||
cat.add_stud_long(get_stud_long("M12", 80))
|
||
cat.add_self_tacker_gost(get_self_tacker_gost("4.2", 30))
|
||
cat.add_washer_l(get_washer_l("M12"))
|
||
|
||
for r in cat:
|
||
pth = os.path.join(str(tmp_path / "out"), r.model_file)
|
||
assert os.path.exists(pth) and os.path.getsize(pth) > 1000, r.model_file
|
||
assert len(cat) == 10
|
||
|
||
|
||
def test_cli_error(tmp_path):
|
||
from fastengen.cli import main
|
||
|
||
with chdir(tmp_path):
|
||
assert main(["bolt-gen", "M99", "40"]) == 2 # invalid size
|
||
|
||
|
||
@contextmanager
|
||
def chdir(d):
|
||
cwd = os.getcwd()
|
||
os.chdir(d)
|
||
try:
|
||
yield
|
||
finally:
|
||
os.chdir(cwd)
|