otko/tests/unit/test_persistence.py
smillmorel d01a5957b7
Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
feat: consolidate units to Metric/Imperial with unit-aware dialogs
- core/units: two dominant systems (Metric m/kN, Imperial ft/kip) with
  display conversion helpers, legacy 4-system migration in ProjectMeta
- dialogs/docks: unit-aware material, section, case, load, grid and
  results labels; diagram renderer unit labels; render controls update
- docs: add consistent_units.md; regen examples/*.osmodel artifacts
- tests: update persistence/phase8/project/unit-labels for new systems
2026-09-08 16:33:30 -04:00

99 lines
3.1 KiB
Python

"""Unit tests for the persistence service — JSON round-trip."""
from __future__ import annotations
from pathlib import Path
import pytest
from pydantic import ValidationError
from otko.core import (
Concrete02,
ElasticBeamColumn,
ElasticSection,
LinearTimeSeries,
NodalLoad,
Node,
PlainLoadPattern,
Project,
ProjectMeta,
Steel01,
TrussElement,
UnitSystem,
)
from otko.services import PROJECT_FILE_SUFFIX, load_project, save_project
def _sample_project() -> Project:
return Project(
meta=ProjectMeta(name="Sample", author="Ozan", units=UnitSystem.METRIC),
ndm=3, ndf=6,
nodes=[
Node(id=1, coords=(0, 0, 0), restraint=(True,) * 6),
Node(id=2, coords=(0, 0, 3.0)),
],
materials=[
Steel01(id=1, Fy=420e6, E0=200e9, b=0.01),
Concrete02(
id=2, fpc=-30e6, epsc0=-0.002, fpcu=-15e6, epsU=-0.005,
ft=3e6, Ets=2e9, **{"lambda": 0.1},
),
],
sections=[ElasticSection(id=1, E=200e9, A=0.01, Iz=8.33e-6, Iy=8.33e-6, G=80e9, J=1e-6)],
elements=[
ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1, geom_transf="PDelta"),
TrussElement(id=2, nodes=(1, 2), area=1e-3, material_id=1),
],
time_series=[LinearTimeSeries(id=1, factor=1.0)],
load_patterns=[
PlainLoadPattern(
id=1, time_series_id=1,
nodal_loads=[NodalLoad(node_id=2, forces=(0, 0, -10e3, 0, 0, 0))],
)
],
)
def test_round_trip_preserves_everything(tmp_path: Path) -> None:
original = _sample_project()
target = save_project(original, tmp_path / "model")
assert target.suffix == PROJECT_FILE_SUFFIX
assert target.exists()
restored = load_project(target)
assert restored.model_dump(by_alias=True) == original.model_dump(by_alias=True)
def test_save_appends_suffix_when_missing(tmp_path: Path) -> None:
p = Project()
out = save_project(p, tmp_path / "no_extension")
assert out.suffix == PROJECT_FILE_SUFFIX
def test_save_keeps_existing_suffix(tmp_path: Path) -> None:
p = Project()
out = save_project(p, tmp_path / "with_ext.osmodel")
assert out.name == "with_ext.osmodel"
def test_load_missing_file_raises() -> None:
with pytest.raises(FileNotFoundError):
load_project("/no/such/path/x.osmodel")
def test_load_corrupt_file_raises_validation_error(tmp_path: Path) -> None:
bad = tmp_path / "bad.osmodel"
bad.write_text('{"ndm": 2, "ndf": 6}', encoding="utf-8") # invalid combo
with pytest.raises(ValidationError):
load_project(bad)
def test_polymorphic_collection_dispatches_correctly(tmp_path: Path) -> None:
"""Materials list must restore as Steel01 + Concrete02 — not generic Entity."""
original = _sample_project()
out = save_project(original, tmp_path / "x")
restored = load_project(out)
assert isinstance(restored.materials[0], Steel01)
assert isinstance(restored.materials[1], Concrete02)
assert isinstance(restored.elements[0], ElasticBeamColumn)
assert isinstance(restored.elements[1], TrussElement)