v0.1-sprint0: Esqueleto completo AR-ShipDesign
- Estructura completa de carpetas (236 módulos stub + implementados) - pyproject.toml, requirements, .gitignore, LICENSE (propietario) - core/project.py: serialización .arsd (ZIP con JSON) - core/units.py: conversiones SI <-> imperial completas - ui/main_window.py: layout DELFTship-style con todos los paneles - Árbol de proyecto (dock izquierda) - Tabs de módulos (centro) - Panel de propiedades (dock derecha) - Panel hidrostáticos en vivo (inferior, fijo) - ui/i18n: español e inglés - ui/themes: tema claro y oscuro - utils/logger.py, settings.py, validation.py - data/liquids.json: 15 líquidos navales - data/stability_criteria.json: IMO IS Code 2008, A.749(18), USCG - tests/test_startup.py: 12 tests, todos PASSED - Módulo scantling/ ISO 12215 (stubs Sprint 2.5) - Módulo fabrication/molds/ para moldes FRP (stubs Sprint 13B) - Módulo fabrication/ para CNC plasma/router/laser (stubs Sprint 13)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Configuración global de pytest para AR-ShipDesign.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Asegurar que el proyecto raíz está en sys.path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Tests de Sprint 0 — Validación del esqueleto del proyecto.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
|
||||
def test_version_importable():
|
||||
"""La versión del paquete debe ser importable."""
|
||||
from arshipdesign import __version__
|
||||
assert __version__ == "0.1.0"
|
||||
|
||||
|
||||
def test_units_conversions():
|
||||
"""Conversiones de unidades deben ser correctas."""
|
||||
from arshipdesign.core.units import (
|
||||
m_to_ft, ft_to_m,
|
||||
kn_to_ms, ms_to_kn,
|
||||
kg_to_ton_metric,
|
||||
w_to_kw,
|
||||
c_to_f,
|
||||
)
|
||||
# Longitud
|
||||
assert abs(m_to_ft(1.0) - 3.280839895) < 1e-6
|
||||
assert abs(ft_to_m(3.280839895) - 1.0) < 1e-6
|
||||
|
||||
# Velocidad
|
||||
assert abs(kn_to_ms(1.0) - 0.514444) < 1e-4
|
||||
assert abs(ms_to_kn(0.514444) - 1.0) < 1e-3
|
||||
|
||||
# Masa
|
||||
assert abs(kg_to_ton_metric(1000.0) - 1.0) < 1e-9
|
||||
|
||||
# Potencia
|
||||
assert abs(w_to_kw(1000.0) - 1.0) < 1e-9
|
||||
|
||||
# Temperatura
|
||||
assert abs(c_to_f(0.0) - 32.0) < 1e-6
|
||||
assert abs(c_to_f(100.0) - 212.0) < 1e-6
|
||||
|
||||
|
||||
def test_units_roundtrip():
|
||||
"""Las conversiones deben ser reversibles (roundtrip)."""
|
||||
from arshipdesign.core.units import m_to_ft, ft_to_m, kn_to_ms, ms_to_kn
|
||||
|
||||
for val in [1.0, 5.5, 100.0, 0.01]:
|
||||
assert abs(ft_to_m(m_to_ft(val)) - val) < 1e-9
|
||||
assert abs(ms_to_kn(kn_to_ms(val)) - val) < 1e-9
|
||||
|
||||
|
||||
def test_project_new():
|
||||
"""Crear un proyecto nuevo debe generar metadatos correctos."""
|
||||
from arshipdesign.core.project import Project
|
||||
|
||||
p = Project.new("Test Boat", author="Test Author")
|
||||
assert p.name == "Test Boat"
|
||||
assert p.metadata.author == "Test Author"
|
||||
assert p.is_modified is True
|
||||
assert p.path is None
|
||||
|
||||
|
||||
def test_project_save_load(tmp_path):
|
||||
"""Guardar y cargar un proyecto debe preservar los datos."""
|
||||
from arshipdesign.core.project import Project
|
||||
|
||||
project = Project.new("Mi Velero de Prueba", author="Álvaro")
|
||||
save_path = tmp_path / "test_project.arsd"
|
||||
|
||||
project.save(save_path)
|
||||
assert save_path.exists()
|
||||
assert not project.is_modified
|
||||
|
||||
# Recargar
|
||||
loaded = Project.load(save_path)
|
||||
assert loaded.name == "Mi Velero de Prueba"
|
||||
assert loaded.metadata.author == "Álvaro"
|
||||
assert loaded.metadata.format_version == "1.0"
|
||||
assert not loaded.is_modified
|
||||
|
||||
|
||||
def test_project_save_creates_valid_zip(tmp_path):
|
||||
"""El archivo .arsd debe ser un ZIP válido con manifest.json."""
|
||||
import zipfile
|
||||
from arshipdesign.core.project import Project
|
||||
|
||||
p = Project.new("Zip Test")
|
||||
path = tmp_path / "ziptest.arsd"
|
||||
p.save(path)
|
||||
|
||||
assert zipfile.is_zipfile(path)
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
assert "manifest.json" in zf.namelist()
|
||||
assert "ship.json" in zf.namelist()
|
||||
|
||||
|
||||
def test_project_load_nonexistent():
|
||||
"""Cargar un archivo inexistente debe lanzar FileNotFoundError."""
|
||||
from arshipdesign.core.project import Project
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
Project.load(Path("/ruta/que/no/existe.arsd"))
|
||||
|
||||
|
||||
def test_project_load_wrong_extension(tmp_path):
|
||||
"""Cargar un archivo con extensión incorrecta debe lanzar ValueError."""
|
||||
from arshipdesign.core.project import Project
|
||||
|
||||
fake = tmp_path / "archivo.txt"
|
||||
fake.write_text("hola")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
Project.load(fake)
|
||||
|
||||
|
||||
def test_i18n_files_valid():
|
||||
"""Los archivos de i18n deben ser JSON válido con claves básicas."""
|
||||
base = Path(__file__).parent.parent / "arshipdesign" / "ui" / "i18n"
|
||||
for lang in ("es", "en"):
|
||||
f = base / f"{lang}.json"
|
||||
assert f.exists(), f"Falta el archivo i18n/{lang}.json"
|
||||
data = json.loads(f.read_text(encoding="utf-8"))
|
||||
assert "app_title" in data
|
||||
assert "menu_file" in data
|
||||
assert "hydro_draft" in data
|
||||
|
||||
|
||||
def test_liquids_json_valid():
|
||||
"""El archivo de líquidos debe tener las propiedades requeridas."""
|
||||
liq_path = Path(__file__).parent.parent / "data" / "liquids.json"
|
||||
assert liq_path.exists()
|
||||
liquids = json.loads(liq_path.read_text(encoding="utf-8"))
|
||||
|
||||
required_codes = {"FW", "SW", "MGO", "HFO"}
|
||||
for code in required_codes:
|
||||
assert code in liquids, f"Líquido {code} faltante"
|
||||
assert "density_kg_m3" in liquids[code]
|
||||
assert liquids[code]["density_kg_m3"] > 0
|
||||
|
||||
|
||||
def test_stability_criteria_json_valid():
|
||||
"""El archivo de criterios de estabilidad debe tener IMO IS Code 2008."""
|
||||
crit_path = Path(__file__).parent.parent / "data" / "stability_criteria.json"
|
||||
assert crit_path.exists()
|
||||
criteria = json.loads(crit_path.read_text(encoding="utf-8"))
|
||||
assert "IMO_IS_Code_2008" in criteria
|
||||
imo = criteria["IMO_IS_Code_2008"]["criteria"]
|
||||
assert "area_0_30" in imo
|
||||
assert imo["area_0_30"]["min"] == 0.055
|
||||
|
||||
|
||||
def test_directory_structure():
|
||||
"""Los módulos principales deben existir como paquetes Python."""
|
||||
root = Path(__file__).parent.parent / "arshipdesign"
|
||||
required_packages = [
|
||||
"core", "geometry", "hydrostatics", "stability",
|
||||
"scantling", "resistance", "propulsion",
|
||||
"fabrication", "fabrication/molds",
|
||||
"sailing", "seakeeping", "systems",
|
||||
"ui", "ui/widgets", "ui/dialogs", "utils",
|
||||
"reports", "parametric", "tanks", "engines", "io",
|
||||
]
|
||||
for pkg in required_packages:
|
||||
init_file = root / pkg / "__init__.py"
|
||||
assert init_file.exists(), f"Falta {pkg}/__init__.py"
|
||||
Reference in New Issue
Block a user