"""Tests for Compliance Project routes (project_routes.py).""" import json import pytest from unittest.mock import MagicMock, patch, PropertyMock from datetime import datetime, timezone from compliance.api.project_routes import ( CreateProjectRequest, UpdateProjectRequest, _row_to_response, ) class TestCreateProjectRequest: """Tests for request model validation.""" def test_default_values(self): req = CreateProjectRequest(name="Test Project") assert req.name == "Test Project" assert req.description == "" assert req.customer_type == "new" assert req.copy_from_project_id is None def test_full_values(self): req = CreateProjectRequest( name="KI-Produkt X", description="DSGVO-Compliance fuer Produkt X", customer_type="existing", copy_from_project_id="uuid-123", ) assert req.name == "KI-Produkt X" assert req.description == "DSGVO-Compliance fuer Produkt X" assert req.customer_type == "existing" assert req.copy_from_project_id == "uuid-123" def test_name_required(self): with pytest.raises(Exception): CreateProjectRequest() def test_serialization(self): req = CreateProjectRequest(name="Test") data = req.model_dump() assert data["name"] == "Test" assert data["customer_type"] == "new" class TestUpdateProjectRequest: """Tests for update model.""" def test_empty_update(self): req = UpdateProjectRequest() assert req.name is None assert req.description is None def test_partial_update(self): req = UpdateProjectRequest(name="New Name") assert req.name == "New Name" assert req.description is None class TestRowToResponse: """Tests for DB row to response conversion.""" def _make_row(self, **overrides): now = datetime.now(timezone.utc) defaults = { "id": "uuid-project-1", "tenant_id": "uuid-tenant-1", "name": "Test Project", "description": "A test project", "customer_type": "new", "status": "active", "project_version": 1, "completion_percentage": 0, "created_at": now, "updated_at": now, } defaults.update(overrides) mock = MagicMock() for key, value in defaults.items(): setattr(mock, key, value) return mock def test_basic_conversion(self): row = self._make_row() result = _row_to_response(row) assert result["id"] == "uuid-project-1" assert result["tenant_id"] == "uuid-tenant-1" assert result["name"] == "Test Project" assert result["description"] == "A test project" assert result["customer_type"] == "new" assert result["status"] == "active" assert result["project_version"] == 1 assert result["completion_percentage"] == 0 def test_null_description(self): row = self._make_row(description=None) result = _row_to_response(row) assert result["description"] == "" def test_null_customer_type(self): row = self._make_row(customer_type=None) result = _row_to_response(row) assert result["customer_type"] == "new" def test_null_status(self): row = self._make_row(status=None) result = _row_to_response(row) assert result["status"] == "active" def test_created_at_iso(self): dt = datetime(2026, 3, 9, 12, 0, 0, tzinfo=timezone.utc) row = self._make_row(created_at=dt) result = _row_to_response(row) assert "2026-03-09" in result["created_at"] def test_archived_project(self): row = self._make_row(status="archived", completion_percentage=75) result = _row_to_response(row) assert result["status"] == "archived" assert result["completion_percentage"] == 75 class TestCreateProjectCopiesProfile: """Tests that creating a project with copy_from_project_id works.""" def test_copy_request_model(self): req = CreateProjectRequest( name="Tochter GmbH", customer_type="existing", copy_from_project_id="source-uuid-123", ) assert req.copy_from_project_id == "source-uuid-123" def test_no_copy_request_model(self): req = CreateProjectRequest(name="Brand New") assert req.copy_from_project_id is None class TestTenantIsolation: """Tests verifying tenant isolation is enforced in query patterns.""" def test_list_query_includes_tenant_filter(self): """Verify that our SQL queries always filter by tenant_id.""" import inspect from compliance.api.project_routes import list_projects source = inspect.getsource(list_projects) assert "tenant_id" in source assert "WHERE" in source def test_get_query_includes_tenant_filter(self): import inspect from compliance.api.project_routes import get_project source = inspect.getsource(get_project) assert "tenant_id" in source assert "project_id" in source def test_archive_query_includes_tenant_filter(self): import inspect from compliance.api.project_routes import archive_project source = inspect.getsource(archive_project) assert "tenant_id" in source def test_update_query_includes_tenant_filter(self): import inspect from compliance.api.project_routes import update_project source = inspect.getsource(update_project) assert "tenant_id" in source class TestStateIsolation: """Tests verifying state isolation between projects.""" def test_create_project_creates_sdk_state(self): """Verify create_project function inserts into sdk_states.""" import inspect from compliance.api.project_routes import create_project source = inspect.getsource(create_project) assert "sdk_states" in source assert "project_id" in source def test_create_project_copies_company_profile(self): """Verify create_project copies companyProfile when copy_from specified.""" import inspect from compliance.api.project_routes import create_project source = inspect.getsource(create_project) assert "copy_from_project_id" in source assert "companyProfile" in source