import sqlite3 from pathlib import Path import pytest from app.config_io import export_document, import_document, parse_document, serialize_document from app.models import ServiceCreate, ServiceKind from app.store import Store CONFIG = """ version: 1 services: - name: Database kind: tcp host: db.local port: 5432 depends_on: [] - name: API kind: http url: https://api.local depends_on: [Database] """ def test_yaml_import_export_round_trip(tmp_path: Path) -> None: store = Store(str(tmp_path / "servicemap.db")) result = import_document(store, parse_document(CONFIG, "yaml"), "replace") assert result.created == 2 services = store.list_services() assert len(services) == 2 api = store.get_service_by_name("API") database = store.get_service_by_name("Database") assert api is not None and database is not None assert api.depends_on == [database.id] exported = export_document(store) yaml_text = serialize_document(exported, "yaml") reparsed = parse_document(yaml_text, "yaml") assert [service.name for service in reparsed.services] == [service.name for service in exported.services] def test_merge_can_reference_existing_service_not_in_document(tmp_path: Path) -> None: store = Store(str(tmp_path / "servicemap.db")) database = store.create_service( ServiceCreate(name="Database", kind=ServiceKind.TCP, host="db.local", port=5432) ) document = parse_document( """ version: 1 services: - name: API kind: http url: https://api.local depends_on: [Database] """, "yaml", ) result = import_document(store, document, "merge") assert result.created == 1 assert result.updated == 0 api = store.get_service_by_name("API") assert api is not None assert api.depends_on == [database.id] def test_import_rejects_missing_dependency_and_cycles(tmp_path: Path) -> None: store = Store(str(tmp_path / "servicemap.db")) missing = parse_document( """ version: 1 services: - name: API kind: http url: https://api.local depends_on: [Database] """, "yaml", ) with pytest.raises(ValueError, match="unknown dependencies"): import_document(store, missing, "replace") cyclic = """ version: 1 services: - name: A kind: http url: https://a.local depends_on: [B] - name: B kind: http url: https://b.local depends_on: [A] """ with pytest.raises(ValueError, match="cycle"): parse_document(cyclic, "yaml") with pytest.raises(ValueError, match="less than or equal to 1"): parse_document("version: 2\nservices: []", "yaml") def test_replace_import_rolls_back_on_mid_transaction_failure( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: store = Store(str(tmp_path / "servicemap.db")) original = store.create_service( ServiceCreate(name="Original", kind=ServiceKind.HTTP, url="https://original.local") ) document = parse_document( """ version: 1 services: - name: First kind: http url: https://first.local depends_on: [] - name: Second kind: http url: https://second.local depends_on: [] """, "yaml", ) monkeypatch.setattr("app.store.uuid4", lambda: "duplicate-id") with pytest.raises(sqlite3.IntegrityError): import_document(store, document, "replace") remaining = store.list_services() assert [(service.id, service.name) for service in remaining] == [(original.id, "Original")]