Files
boltgen/tests/test_specs.py
OpenClaw Coder 811ec2c141
CI / test (3.10) (push) Waiting to run
CI / test (3.11) (push) Waiting to run
CI / test (3.12) (push) Waiting to run
boltgen: fix stale test_models.py reference in test_specs docstring
2026-11-10 10:11:01 +10:00

201 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Standalone-тесты boltgen — ПСЕВДОНЕЗАВИСИМЫЕ от cadquery.
Проверяют логику спецификаций (standard.py) и схему каталога/product_params,
не импортируя model.py (который тянет cadquery). Отдельно — проверка сгенерированных
STEP-файлов лежит в test_geometry.py, который падает «skip» при отсутствии cadquery.
Нужны для того, чтобы `pytest` проходился на хостах без cadquery (тесты спецификаций),
а тяжёлый экспорт модели — только когда cadquery доступен.
"""
from __future__ import annotations
import math
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
import pytest # noqa: F401
from boltgen.standard import ( # noqa: E402
BOLT_ALLOWED_LENGTHS,
BOLT_SIZES,
NUT_SIZES,
WASHER_SIZES,
BoltSpec,
NutSpec,
WasherSpec,
bolt_designation,
get_bolt,
get_nut,
get_washer,
iter_bolts,
iter_nuts,
iter_washers,
model_filename,
product_params,
washer_designation,
)
# =====Стандартные размерные данные ======
def test_size_tables_present_and_consistent():
for kind in (BOLT_SIZES, NUT_SIZES, WASHER_SIZES):
assert len(kind) >= 7
# диаметры совпадают между таблицами болт/гайка/шайба по размеру
for s in BOLT_SIZES:
assert BOLT_SIZES[s]["d"] == NUT_SIZES[s]["d"]
assert s in WASHER_SIZES
def test_bolt_geometry_monotonic():
# размер под ключ и высота головки растут с размером
sizes = sorted(BOLT_SIZES, key=lambda s: int(s[1:]))
prev_s = prev_k = 0.0
for s in sizes:
cur_s = BOLT_SIZES[s]["S"]
cur_k = BOLT_SIZES[s]["k"]
assert cur_s > prev_s or cur_s == prev_s
assert cur_k >= prev_k
prev_s, prev_k = cur_s, cur_k
def test_nut_washer_geometry_reasonable():
for s, n in NUT_SIZES.items():
# высота гайки меньше размера под ключ
assert n["k"] < n["S"]
for s, w in WASHER_SIZES.items():
# внутренний диаметр < внешнего, толщина положительная
assert w["d"] < w["D"]
assert w["S"] > 0
def test_m6_short_lengths():
# M6 по ГОСТ 7805-70 только до 60
lengths = [spec.length for spec in iter_bolts() if spec.size == "M6"]
assert lengths == sorted(lengths)
assert all(l <= 60 for l in lengths)
assert all(l in BOLT_ALLOWED_LENGTHS for l in lengths)
# =====Спецификации и валидация ======
def test_get_bolt_roundtrip():
spec = get_bolt("M12", 45)
assert isinstance(spec, BoltSpec)
assert spec.size == "M12"
assert spec.length == 45
def test_get_bolt_invalid_size():
with pytest.raises(ValueError):
get_bolt("M99", 40)
def test_get_bolt_invalid_length():
with pytest.raises(ValueError):
get_bolt("M10", 33) # 33 не в BOLT_ALLOWED_LENGTHS
def test_get_nut_washer_invalid():
with pytest.raises(ValueError):
get_nut("M99")
with pytest.raises(ValueError):
get_washer("M99")
# =====Итераторы (полные семейства) ======
def test_iter_counts():
bolt_count = sum(1 for _ in iter_bolts())
nut_count = sum(1 for _ in iter_nuts())
washer_count = sum(1 for _ in iter_washers())
# M6×7 + M8,M10,M12,M16,M20,M24,M27,M30 × 20 = 7 + 160 = 167 болтов + 9 гаек + 9 шайб
assert bolt_count == 167
assert nut_count == 9
assert washer_count == 9
assert bolt_count + nut_count + washer_count == 185
def test_iter_unique_bolt_ids():
ids = {(b.size, b.length) for b in iter_bolts()}
assert len(ids) == sum(1 for _ in iter_bolts()) # без дубликатov
# =====Дизигнации и model_filename ======
def test_bolt_designation_format():
# bolt_designation/nut_designation/washer_designation — базовое наименование;
# составное обозначение по ГОСТ 1759.0-87 (класс, стандарты, покрытие) собирает _*_params.
assert bolt_designation("M12", 45) == "Болт М12×45"
assert bolt_designation("M10", 20) == "Болт М10×20"
def test_washer_designation_format():
# шайба по номиналу резьбы: M12 -> 13
assert washer_designation("M12") == "Шайба 13"
def test_model_filename_types():
assert model_filename(get_bolt("M12", 45)) == "bolt_M12_45.step"
assert model_filename(get_nut("M12")) == "nut_M12.step"
assert model_filename(get_washer("M12")) == "washer_M12.step"
# =====product_params / схема каталога ======
def test_bolt_product_params():
p = product_params(get_bolt("M12", 45))
assert p["type"] == "bolt"
assert p["category"] == "Крепёж"
assert p["standard"] == "ГОСТ 7805-70"
assert p["material"] == "Сталь 8.8"
assert p["attributes"]["диаметр"] == "12"
assert p["attributes"]["длина"] == 45
assert p["attributes"]["размер_под_ключ"] == 18.0
def test_nut_product_params():
p = product_params(get_nut("M12"))
assert p["type"] == "nut"
assert p["standard"] == "ГОСТ 5915-70"
assert p["material"] == "Сталь 8"
def test_washer_product_params():
p = product_params(get_washer("M12"))
assert p["type"] == "washer"
assert p["standard"] == "ГОСТ 11371-78"
assert p["attributes"]["диаметр_наружный"] == 24.0
def test_all_bolt_params_have_required_keys():
required = {"designation", "type", "category", "standard", "material", "attributes"}
for spec in iter_bolts():
p = product_params(spec)
assert required.issubset(p.keys())
assert p["designation"].startswith("Болт ")
# =====Критические гостовские допуски ======
def test_bolt_key_size_upper_bound():
# размер под ключ не превышает диаметр + ~25 (защита от дурака в данных)
for s, b in BOLT_SIZES.items():
assert b["S"] <= float(s[1:]) + 25
def test_washer_hole_vs_outer_clearance_ratio():
# внутреннее отверстие заметно меньше внешнего (плоская шайба)
for s, w in WASHER_SIZES.items():
ratio = w["d"] / w["D"]
assert 0.3 < ratio < 0.75