"""Tests for Document Versioning (Phase 3). Verifies: - versioning_utils: create_version_snapshot, list_versions, get_version - VERSION_TABLES mapping is correct - Version endpoints are registered on all 5 route files """ import pytest import json from unittest.mock import MagicMock, patch from datetime import datetime from compliance.api.versioning_utils import ( VERSION_TABLES, create_version_snapshot, list_versions, get_version, ) TENANT = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" DOC_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" # ============================================================================= # VERSION_TABLES mapping # ============================================================================= class TestVersionTablesMapping: def test_all_5_doc_types(self): assert "dsfa" in VERSION_TABLES assert "vvt_activity" in VERSION_TABLES assert "tom" in VERSION_TABLES assert "loeschfristen" in VERSION_TABLES assert "obligation" in VERSION_TABLES assert len(VERSION_TABLES) == 5 def test_dsfa_mapping(self): table, fk, source = VERSION_TABLES["dsfa"] assert table == "compliance_dsfa_versions" assert fk == "dsfa_id" assert source == "compliance_dsfas" def test_vvt_mapping(self): table, fk, source = VERSION_TABLES["vvt_activity"] assert table == "compliance_vvt_activity_versions" assert fk == "activity_id" assert source == "compliance_vvt_activities" def test_tom_mapping(self): table, fk, source = VERSION_TABLES["tom"] assert table == "compliance_tom_versions" assert fk == "measure_id" assert source == "compliance_tom_measures" def test_loeschfristen_mapping(self): table, fk, source = VERSION_TABLES["loeschfristen"] assert table == "compliance_loeschfristen_versions" assert fk == "policy_id" assert source == "compliance_loeschfristen" def test_obligation_mapping(self): table, fk, source = VERSION_TABLES["obligation"] assert table == "compliance_obligation_versions" assert fk == "obligation_id" assert source == "compliance_obligations" # ============================================================================= # create_version_snapshot # ============================================================================= class TestCreateVersionSnapshot: def test_invalid_doc_type_raises(self): db = MagicMock() with pytest.raises(ValueError, match="Unknown document type"): create_version_snapshot(db, "invalid_type", DOC_ID, TENANT, {"data": 1}) def test_creates_version_with_correct_params(self): db = MagicMock() # Mock: MAX(version_number) returns 0 (first version) max_result = MagicMock() max_result.scalar.return_value = 0 # Mock: INSERT RETURNING insert_result = MagicMock() insert_result.fetchone.return_value = ( "new-uuid", 1, datetime(2026, 3, 7, 12, 0, 0), ) # Mock: UPDATE (returns nothing) update_result = MagicMock() db.execute.side_effect = [max_result, insert_result, update_result] result = create_version_snapshot( db, "dsfa", DOC_ID, TENANT, snapshot={"title": "Test DSFA"}, change_summary="Initial version", created_by="test-user", ) assert result["version_number"] == 1 assert result["id"] == "new-uuid" assert db.execute.call_count == 3 def test_increments_version_number(self): db = MagicMock() # MAX returns 2 (existing 2 versions) max_result = MagicMock() max_result.scalar.return_value = 2 insert_result = MagicMock() insert_result.fetchone.return_value = ("uuid-3", 3, datetime(2026, 3, 7)) update_result = MagicMock() db.execute.side_effect = [max_result, insert_result, update_result] result = create_version_snapshot( db, "vvt_activity", DOC_ID, TENANT, snapshot={"name": "Activity"}, ) assert result["version_number"] == 3 # ============================================================================= # list_versions # ============================================================================= class TestListVersions: def test_invalid_doc_type_raises(self): db = MagicMock() with pytest.raises(ValueError, match="Unknown document type"): list_versions(db, "invalid", DOC_ID, TENANT) def test_returns_formatted_list(self): db = MagicMock() mock_result = MagicMock() mock_result.fetchall.return_value = [ ("uuid-1", 1, "draft", "Initial", [], "system", None, None, datetime(2026, 3, 1)), ("uuid-2", 2, "approved", "Updated measures", ["section3"], "admin", "dpo", datetime(2026, 3, 5), datetime(2026, 3, 2)), ] db.execute.return_value = mock_result result = list_versions(db, "tom", DOC_ID, TENANT) assert len(result) == 2 assert result[0]["version_number"] == 1 assert result[0]["status"] == "draft" assert result[1]["version_number"] == 2 assert result[1]["approved_by"] == "dpo" def test_empty_list(self): db = MagicMock() mock_result = MagicMock() mock_result.fetchall.return_value = [] db.execute.return_value = mock_result result = list_versions(db, "obligation", DOC_ID, TENANT) assert result == [] # ============================================================================= # get_version # ============================================================================= class TestGetVersion: def test_invalid_doc_type_raises(self): db = MagicMock() with pytest.raises(ValueError, match="Unknown document type"): get_version(db, "invalid", DOC_ID, 1, TENANT) def test_returns_version_with_snapshot(self): db = MagicMock() mock_result = MagicMock() mock_result.fetchone.return_value = ( "uuid-1", 1, "draft", {"title": "Test", "status": "draft"}, "Initial version", ["section1"], "system", None, None, datetime(2026, 3, 1), ) db.execute.return_value = mock_result result = get_version(db, "loeschfristen", DOC_ID, 1, TENANT) assert result is not None assert result["version_number"] == 1 assert result["snapshot"]["title"] == "Test" assert result["change_summary"] == "Initial version" def test_returns_none_for_missing(self): db = MagicMock() mock_result = MagicMock() mock_result.fetchone.return_value = None db.execute.return_value = mock_result result = get_version(db, "dsfa", DOC_ID, 99, TENANT) assert result is None # ============================================================================= # Route Registration Tests # ============================================================================= class TestVersionEndpointsRegistered: """Verify all 5 route files have version endpoints.""" def _has_route(self, router, suffix): return any(r.path.endswith(suffix) for r in router.routes) def test_dsfa_has_version_routes(self): from compliance.api.dsfa_routes import router assert self._has_route(router, "/versions") assert self._has_route(router, "/versions/{version_number}") def test_vvt_has_version_routes(self): from compliance.api.vvt_routes import router assert self._has_route(router, "/versions") assert self._has_route(router, "/versions/{version_number}") def test_tom_has_version_routes(self): from compliance.api.tom_routes import router assert self._has_route(router, "/versions") assert self._has_route(router, "/versions/{version_number}") def test_loeschfristen_has_version_routes(self): from compliance.api.loeschfristen_routes import router assert self._has_route(router, "/versions") assert self._has_route(router, "/versions/{version_number}") def test_obligation_has_version_routes(self): from compliance.api.obligation_routes import router assert self._has_route(router, "/versions") assert self._has_route(router, "/versions/{version_number}")