"""Test crítico Sprint 0: roundtrip de persistencia .vmsproj. Criterio de aceptación de la Parte 6: `load_project(save_project(p))` produce un Project equivalente campo por campo. """ from __future__ import annotations import sqlite3 from pathlib import Path import pytest from vmssailor.core import Project from vmssailor.core.persistence import load_project, save_project from vmssailor.core.persistence.vmsproj_reader import VmsProjError from vmssailor.tools.generate_test_project import build_demo_project def _projects_equivalent(a: Project, b: Project) -> bool: return a.model_dump(mode="json", exclude={"updated_at"}) == b.model_dump( mode="json", exclude={"updated_at"} ) def test_roundtrip_minimal(sample_project: Project, tmp_path: Path) -> None: out = tmp_path / "minimal.vmsproj" save_project(sample_project, out) assert out.exists() loaded = load_project(out) assert _projects_equivalent(sample_project, loaded) def test_roundtrip_full_demo(tmp_path: Path) -> None: project = build_demo_project() out = tmp_path / "demo.vmsproj" save_project(project, out) loaded = load_project(out) assert _projects_equivalent(project, loaded), ( "Roundtrip falló — el proyecto releído no coincide con el original." ) # Stats deben coincidir 1:1 assert project.stats() == loaded.stats() def test_vmsproj_is_real_sqlite(sample_project: Project, tmp_path: Path) -> None: out = tmp_path / "x.vmsproj" save_project(sample_project, out) # Cualquier herramienta SQLite debe abrirlo conn = sqlite3.connect(str(out)) try: version = conn.execute("SELECT MAX(version) FROM schema_version").fetchone()[0] assert version == 1 # Meta esperada meta_rows = dict(conn.execute("SELECT key, value FROM meta")) assert meta_rows["file_format"] == "vmsproj" finally: conn.close() def test_load_nonexistent_raises(tmp_path: Path) -> None: with pytest.raises(VmsProjError): load_project(tmp_path / "does_not_exist.vmsproj") def test_load_invalid_db_raises(tmp_path: Path) -> None: bad = tmp_path / "bad.vmsproj" bad.write_bytes(b"not a sqlite database at all") with pytest.raises(Exception): # noqa: B017 -- sqlite3 levanta varias clases distintas load_project(bad) def test_save_overwrites_existing(sample_project: Project, tmp_path: Path) -> None: out = tmp_path / "ow.vmsproj" save_project(sample_project, out) first_size = out.stat().st_size # Modifico el proyecto y vuelvo a guardar sample_project.notes = "Cambiado en memoria" save_project(sample_project, out) loaded = load_project(out) assert loaded.notes == "Cambiado en memoria" # El archivo sigue siendo válido assert out.stat().st_size > 0 _ = first_size # no relevante para el assert principal def test_save_creates_parent_dirs(sample_project: Project, tmp_path: Path) -> None: out = tmp_path / "deep" / "nested" / "out.vmsproj" save_project(sample_project, out) assert out.exists()