Files
breakpilot-compliance/backend-compliance/tests/test_project_routes.py
Benjamin Admin 0affa4eb66
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 33s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s
feat(sdk): Multi-Projekt-Architektur — mehrere Projekte pro Tenant
Jeder Tenant kann jetzt mehrere Compliance-Projekte anlegen (z.B. verschiedene
Produkte, Tochterunternehmen). CompanyProfile ist pro Projekt kopierbar und
danach unabhaengig editierbar. Multi-Tab-Support via separater BroadcastChannel
und localStorage Keys pro Projekt.

- Migration 039: compliance_projects Tabelle, sdk_states.project_id
- Backend: FastAPI CRUD-Routes fuer Projekte mit Tenant-Isolation
- Frontend: ProjectSelector UI, SDKProvider mit projectId, URL ?project=
- State API: UPSERT auf (tenant_id, project_id) mit Abwaertskompatibilitaet
- Tests: pytest fuer Model-Validierung, Row-Konvertierung, Tenant-Isolation
- Docs: MKDocs Seite, CLAUDE.md, Backend README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:53:50 +01:00

190 lines
6.3 KiB
Python

"""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