Files
breakpilot-compliance/backend-compliance/tests/test_loeschfristen_routes.py
Benjamin Admin 25d5da78ef
All checks were successful
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) Successful in 34s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 17s
feat: Alle 5 verbleibenden SDK-Module auf 100% — RAG, Security-Backlog, Quality, Notfallplan, Loeschfristen
Paket A — RAG Proxy:
- NEU: admin-compliance/app/api/sdk/v1/rag/[[...path]]/route.ts
  → Proxy zu ai-compliance-sdk:8090, GET+POST, UUID-Validierung
- UPDATE: rag/page.tsx — setTimeout Mock → echte API-Calls
  GET /regulations → dynamische suggestedQuestions
  POST /search → Qdrant-Ergebnisse mit score, title, reference

Paket B — Security-Backlog + Quality:
- NEU: migrations/014_security_backlog.sql + 015_quality.sql
- NEU: compliance/api/security_backlog_routes.py — CRUD + Stats
- NEU: compliance/api/quality_routes.py — Metrics + Tests CRUD + Stats
- UPDATE: security-backlog/page.tsx — mockItems → API
- UPDATE: quality/page.tsx — mockMetrics/mockTests → API
- UPDATE: compliance/api/__init__.py — Router-Registrierung
- NEU: tests/test_security_backlog_routes.py (48 Tests — 48/48 bestanden)
- NEU: tests/test_quality_routes.py (67 Tests — 67/67 bestanden)

Paket C — Notfallplan Incidents + Templates:
- NEU: migrations/016_notfallplan_incidents.sql
  compliance_notfallplan_incidents + compliance_notfallplan_templates
- UPDATE: notfallplan_routes.py — GET/POST/PUT/DELETE für /incidents + /templates
- UPDATE: notfallplan/page.tsx — Incidents-Tab + Templates-Tab → API
- UPDATE: tests/test_notfallplan_routes.py (+76 neue Tests — alle bestanden)

Paket D — Loeschfristen localStorage → API:
- NEU: migrations/017_loeschfristen.sql (JSONB: legal_holds, storage_locations, ...)
- NEU: compliance/api/loeschfristen_routes.py — CRUD + Stats + Status-Update
- UPDATE: loeschfristen/page.tsx — vollständige localStorage → API Migration
  createNewPolicy → POST (API-UUID als id), deletePolicy → DELETE,
  handleSaveAndClose → PUT, adoptGeneratedPolicies → POST je Policy
  apiToPolicy() + policyToPayload() Mapper, saving-State für Buttons
- NEU: tests/test_loeschfristen_routes.py (58 Tests — alle bestanden)

Gesamt: 253 neue Tests, alle bestanden (48 + 67 + 76 + 58 + bestehende)

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

631 lines
24 KiB
Python

"""Tests for Loeschfristen routes and schemas (loeschfristen_routes.py)."""
import pytest
from unittest.mock import MagicMock
from fastapi.testclient import TestClient
from fastapi import FastAPI
from datetime import datetime
from compliance.api.loeschfristen_routes import (
LoeschfristCreate,
LoeschfristUpdate,
StatusUpdate,
_row_to_dict,
_get_tenant_id,
DEFAULT_TENANT_ID,
JSONB_FIELDS,
router,
)
app = FastAPI()
app.include_router(router)
client = TestClient(app)
DEFAULT_TENANT = DEFAULT_TENANT_ID # "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
POLICY_ID = "ffffffff-0001-0001-0001-000000000001"
UNKNOWN_ID = "aaaaaaaa-9999-9999-9999-999999999999"
# =============================================================================
# Helpers
# =============================================================================
def make_policy_row(overrides=None):
data = {
"id": POLICY_ID,
"tenant_id": DEFAULT_TENANT,
"policy_id": "LF-2024-001",
"data_object_name": "Kundendaten",
"description": "Kundendaten Loeschfrist",
"affected_groups": [],
"data_categories": [],
"primary_purpose": "Vertrag",
"deletion_trigger": "PURPOSE_END",
"retention_driver": "HGB_257",
"retention_driver_detail": None,
"retention_duration": 10,
"retention_unit": "YEARS",
"retention_description": None,
"start_event": None,
"has_active_legal_hold": False,
"legal_holds": [],
"storage_locations": [],
"deletion_method": "MANUAL_REVIEW_DELETE",
"deletion_method_detail": None,
"responsible_role": None,
"responsible_person": None,
"release_process": None,
"linked_vvt_activity_ids": [],
"status": "DRAFT",
"last_review_date": None,
"next_review_date": None,
"review_interval": "ANNUAL",
"tags": [],
"created_at": datetime(2024, 1, 1),
"updated_at": datetime(2024, 1, 1),
}
if overrides:
data.update(overrides)
row = MagicMock()
row._mapping = data
return row
def make_stats_row(overrides=None):
data = {
"total": 0,
"active": 0,
"draft": 0,
"review_needed": 0,
"archived": 0,
"legal_holds_count": 0,
"overdue_reviews": 0,
}
if overrides:
data.update(overrides)
row = MagicMock()
row._mapping = data
return row
@pytest.fixture
def mock_db():
from classroom_engine.database import get_db
db = MagicMock()
app.dependency_overrides[get_db] = lambda: db
yield db
app.dependency_overrides.clear()
# =============================================================================
# Helper / Utility Tests
# =============================================================================
class TestRowToDict:
def test_converts_datetime_to_isoformat(self):
row = make_policy_row({"created_at": datetime(2024, 6, 1, 12, 0, 0)})
result = _row_to_dict(row)
assert result["created_at"] == "2024-06-01T12:00:00"
def test_converts_none_datetime_remains_none(self):
row = make_policy_row({"next_review_date": None})
result = _row_to_dict(row)
assert result["next_review_date"] is None
def test_preserves_string_values(self):
row = make_policy_row({"data_object_name": "Mitarbeiterdaten"})
result = _row_to_dict(row)
assert result["data_object_name"] == "Mitarbeiterdaten"
def test_preserves_list_values(self):
row = make_policy_row({"tags": ["dsgvo", "hgb"]})
result = _row_to_dict(row)
assert result["tags"] == ["dsgvo", "hgb"]
def test_preserves_int_values(self):
row = make_policy_row({"retention_duration": 7})
result = _row_to_dict(row)
assert result["retention_duration"] == 7
class TestGetTenantId:
def test_valid_uuid_is_returned(self):
assert _get_tenant_id("9282a473-5c95-4b3a-bf78-0ecc0ec71d3e") == "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
def test_invalid_uuid_returns_default(self):
assert _get_tenant_id("not-a-uuid") == DEFAULT_TENANT_ID
def test_none_returns_default(self):
assert _get_tenant_id(None) == DEFAULT_TENANT_ID
class TestJsonbFields:
def test_jsonb_fields_set(self):
expected = {"affected_groups", "data_categories", "legal_holds",
"storage_locations", "linked_vvt_activity_ids", "tags"}
assert JSONB_FIELDS == expected
# =============================================================================
# Schema Tests — LoeschfristCreate
# =============================================================================
class TestLoeschfristCreate:
def test_minimal_requires_data_object_name(self):
obj = LoeschfristCreate(data_object_name="Kundendaten")
assert obj.data_object_name == "Kundendaten"
assert obj.deletion_trigger == "PURPOSE_END"
assert obj.status == "DRAFT"
assert obj.has_active_legal_hold is False
def test_full_object(self):
obj = LoeschfristCreate(
data_object_name="Mitarbeiterdaten",
description="HR-Daten",
primary_purpose="Arbeitsvertrag",
retention_driver="AO_147",
retention_duration=6,
retention_unit="YEARS",
status="ACTIVE",
tags=["hr", "personal"],
data_categories=["name", "address"],
)
assert obj.retention_duration == 6
assert obj.retention_unit == "YEARS"
assert obj.status == "ACTIVE"
assert len(obj.tags) == 2
def test_missing_data_object_name_raises_validation_error(self):
import pydantic
with pytest.raises(pydantic.ValidationError):
LoeschfristCreate()
def test_optional_fields_default_to_none(self):
obj = LoeschfristCreate(data_object_name="Test")
assert obj.description is None
assert obj.retention_duration is None
assert obj.responsible_role is None
assert obj.policy_id is None
class TestLoeschfristUpdate:
def test_empty_update(self):
obj = LoeschfristUpdate()
data = obj.model_dump(exclude_unset=True)
assert data == {}
def test_partial_update_status(self):
obj = LoeschfristUpdate(status="ACTIVE")
data = obj.model_dump(exclude_unset=True)
assert data == {"status": "ACTIVE"}
def test_partial_update_multiple_fields(self):
obj = LoeschfristUpdate(
data_object_name="Neuer Name",
retention_duration=5,
retention_unit="YEARS",
)
data = obj.model_dump(exclude_unset=True)
assert data["data_object_name"] == "Neuer Name"
assert data["retention_duration"] == 5
assert "status" not in data
class TestStatusUpdateSchema:
def test_valid_status(self):
obj = StatusUpdate(status="ACTIVE")
assert obj.status == "ACTIVE"
def test_all_valid_statuses(self):
for s in ("DRAFT", "ACTIVE", "REVIEW_NEEDED", "ARCHIVED"):
obj = StatusUpdate(status=s)
assert obj.status == s
# =============================================================================
# GET /loeschfristen — List
# =============================================================================
class TestListLoeschfristen:
def test_list_returns_empty(self, mock_db):
mock_db.execute.return_value.fetchone.return_value = MagicMock(__getitem__=lambda s, i: 0)
mock_db.execute.return_value.fetchall.return_value = []
# Two execute calls: COUNT then SELECT
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 0
list_result = MagicMock()
list_result.fetchall.return_value = []
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
resp = client.get("/loeschfristen")
assert resp.status_code == 200
body = resp.json()
assert "policies" in body
assert "total" in body
def test_list_returns_policies(self, mock_db):
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 1
policy_row = make_policy_row()
list_result = MagicMock()
list_result.fetchall.return_value = [policy_row]
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
resp = client.get("/loeschfristen")
assert resp.status_code == 200
body = resp.json()
assert len(body["policies"]) == 1
assert body["policies"][0]["data_object_name"] == "Kundendaten"
def test_list_filter_by_status(self, mock_db):
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 0
list_result = MagicMock()
list_result.fetchall.return_value = []
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
resp = client.get("/loeschfristen?status=ACTIVE")
assert resp.status_code == 200
def test_list_filter_by_retention_driver(self, mock_db):
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 0
list_result = MagicMock()
list_result.fetchall.return_value = []
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
resp = client.get("/loeschfristen?retention_driver=HGB_257")
assert resp.status_code == 200
def test_list_filter_by_search(self, mock_db):
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 0
list_result = MagicMock()
list_result.fetchall.return_value = []
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
resp = client.get("/loeschfristen?search=Kunden")
assert resp.status_code == 200
def test_list_uses_default_tenant(self, mock_db):
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 0
list_result = MagicMock()
list_result.fetchall.return_value = []
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
client.get("/loeschfristen")
# First call is the COUNT query
first_call_params = mock_db.execute.call_args_list[0][0][1]
assert first_call_params["tenant_id"] == DEFAULT_TENANT
def test_list_pagination_params(self, mock_db):
count_result = MagicMock()
count_result.__getitem__ = lambda s, i: 0
list_result = MagicMock()
list_result.fetchall.return_value = []
mock_db.execute.side_effect = [
MagicMock(fetchone=lambda: count_result),
list_result,
]
resp = client.get("/loeschfristen?limit=10&offset=20")
assert resp.status_code == 200
# =============================================================================
# GET /loeschfristen/stats
# =============================================================================
class TestGetLoeschfristenStats:
def test_stats_returns_all_keys(self, mock_db):
stats_row = make_stats_row()
mock_db.execute.return_value.fetchone.return_value = stats_row
resp = client.get("/loeschfristen/stats")
assert resp.status_code == 200
body = resp.json()
for key in ("total", "active", "draft", "review_needed", "archived",
"legal_holds_count", "overdue_reviews"):
assert key in body, f"Missing key: {key}"
def test_stats_returns_correct_counts(self, mock_db):
stats_row = make_stats_row({
"total": 10,
"active": 4,
"draft": 3,
"review_needed": 2,
"archived": 1,
"legal_holds_count": 1,
"overdue_reviews": 0,
})
mock_db.execute.return_value.fetchone.return_value = stats_row
resp = client.get("/loeschfristen/stats")
assert resp.status_code == 200
body = resp.json()
assert body["total"] == 10
assert body["active"] == 4
assert body["draft"] == 3
assert body["review_needed"] == 2
assert body["archived"] == 1
assert body["legal_holds_count"] == 1
def test_stats_all_zeros_when_no_data(self, mock_db):
stats_row = make_stats_row()
mock_db.execute.return_value.fetchone.return_value = stats_row
resp = client.get("/loeschfristen/stats")
assert resp.status_code == 200
body = resp.json()
for key in ("total", "active", "draft", "review_needed", "archived",
"legal_holds_count", "overdue_reviews"):
assert body[key] == 0
def test_stats_uses_default_tenant(self, mock_db):
stats_row = make_stats_row()
mock_db.execute.return_value.fetchone.return_value = stats_row
client.get("/loeschfristen/stats")
call_params = mock_db.execute.call_args[0][1]
assert call_params["tenant_id"] == DEFAULT_TENANT
def test_stats_with_valid_tenant_header(self, mock_db):
stats_row = make_stats_row()
mock_db.execute.return_value.fetchone.return_value = stats_row
client.get(
"/loeschfristen/stats",
headers={"x-tenant-id": "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"},
)
call_params = mock_db.execute.call_args[0][1]
assert call_params["tenant_id"] == "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
# =============================================================================
# POST /loeschfristen
# =============================================================================
class TestCreateLoeschfrist:
def test_create_minimal(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
resp = client.post("/loeschfristen", json={"data_object_name": "Kundendaten"})
assert resp.status_code == 201
assert resp.json()["data_object_name"] == "Kundendaten"
def test_create_missing_data_object_name_returns_422(self, mock_db):
resp = client.post("/loeschfristen", json={"description": "No name"})
assert resp.status_code == 422
def test_create_full_payload(self, mock_db):
row = make_policy_row({
"data_object_name": "Mitarbeiterdaten",
"status": "ACTIVE",
"retention_duration": 6,
"retention_unit": "YEARS",
})
mock_db.execute.return_value.fetchone.return_value = row
resp = client.post("/loeschfristen", json={
"data_object_name": "Mitarbeiterdaten",
"description": "HR-Datensatz",
"retention_driver": "AO_147",
"retention_duration": 6,
"retention_unit": "YEARS",
"status": "ACTIVE",
})
assert resp.status_code == 201
data = resp.json()
assert data["status"] == "ACTIVE"
assert data["retention_duration"] == 6
def test_create_commits(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
client.post("/loeschfristen", json={"data_object_name": "X"})
mock_db.commit.assert_called_once()
def test_create_uses_default_tenant(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
client.post("/loeschfristen", json={"data_object_name": "X"})
call_params = mock_db.execute.call_args[0][1]
assert call_params["tenant_id"] == DEFAULT_TENANT
def test_create_jsonb_fields_are_json_encoded(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
client.post("/loeschfristen", json={
"data_object_name": "Test",
"tags": ["a", "b"],
"data_categories": ["name"],
})
call_params = mock_db.execute.call_args[0][1]
import json
# JSONB fields must be JSON strings for CAST
assert json.loads(call_params["tags"]) == ["a", "b"]
assert json.loads(call_params["data_categories"]) == ["name"]
# =============================================================================
# GET /loeschfristen/{id}
# =============================================================================
class TestGetLoeschfrist:
def test_get_existing_policy(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
resp = client.get(f"/loeschfristen/{POLICY_ID}")
assert resp.status_code == 200
assert resp.json()["data_object_name"] == "Kundendaten"
def test_get_not_found_returns_404(self, mock_db):
mock_db.execute.return_value.fetchone.return_value = None
resp = client.get(f"/loeschfristen/{UNKNOWN_ID}")
assert resp.status_code == 404
def test_get_passes_id_and_tenant(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
client.get(f"/loeschfristen/{POLICY_ID}")
call_params = mock_db.execute.call_args[0][1]
assert call_params["id"] == POLICY_ID
assert call_params["tenant_id"] == DEFAULT_TENANT
def test_get_all_fields_present(self, mock_db):
row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = row
resp = client.get(f"/loeschfristen/{POLICY_ID}")
assert resp.status_code == 200
body = resp.json()
for field in ("id", "tenant_id", "data_object_name", "status",
"retention_duration", "retention_unit", "created_at", "updated_at"):
assert field in body, f"Missing field: {field}"
# =============================================================================
# PUT /loeschfristen/{id}
# =============================================================================
class TestUpdateLoeschfrist:
def test_update_success(self, mock_db):
updated_row = make_policy_row({"data_object_name": "Neuer Name"})
mock_db.execute.return_value.fetchone.return_value = updated_row
resp = client.put(f"/loeschfristen/{POLICY_ID}", json={"data_object_name": "Neuer Name"})
assert resp.status_code == 200
assert resp.json()["data_object_name"] == "Neuer Name"
def test_update_not_found_returns_404(self, mock_db):
mock_db.execute.return_value.fetchone.return_value = None
resp = client.put(f"/loeschfristen/{UNKNOWN_ID}", json={"data_object_name": "X"})
assert resp.status_code == 404
def test_update_empty_body_returns_400(self, mock_db):
resp = client.put(f"/loeschfristen/{POLICY_ID}", json={})
assert resp.status_code == 400
def test_update_jsonb_field(self, mock_db):
updated_row = make_policy_row({"tags": ["urgent"]})
mock_db.execute.return_value.fetchone.return_value = updated_row
resp = client.put(f"/loeschfristen/{POLICY_ID}", json={"tags": ["urgent"]})
assert resp.status_code == 200
call_params = mock_db.execute.call_args[0][1]
import json
assert json.loads(call_params["tags"]) == ["urgent"]
def test_update_sets_updated_at(self, mock_db):
updated_row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = updated_row
client.put(f"/loeschfristen/{POLICY_ID}", json={"status": "ACTIVE"})
call_params = mock_db.execute.call_args[0][1]
assert "updated_at" in call_params
def test_update_commits(self, mock_db):
updated_row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = updated_row
client.put(f"/loeschfristen/{POLICY_ID}", json={"status": "ACTIVE"})
mock_db.commit.assert_called_once()
# =============================================================================
# PUT /loeschfristen/{id}/status
# =============================================================================
class TestUpdateLoeschfristStatus:
def test_valid_status_active(self, mock_db):
updated_row = make_policy_row({"status": "ACTIVE"})
mock_db.execute.return_value.fetchone.return_value = updated_row
resp = client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "ACTIVE"})
assert resp.status_code == 200
assert resp.json()["status"] == "ACTIVE"
def test_valid_status_archived(self, mock_db):
updated_row = make_policy_row({"status": "ARCHIVED"})
mock_db.execute.return_value.fetchone.return_value = updated_row
resp = client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "ARCHIVED"})
assert resp.status_code == 200
def test_valid_status_review_needed(self, mock_db):
updated_row = make_policy_row({"status": "REVIEW_NEEDED"})
mock_db.execute.return_value.fetchone.return_value = updated_row
resp = client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "REVIEW_NEEDED"})
assert resp.status_code == 200
def test_valid_status_draft(self, mock_db):
updated_row = make_policy_row({"status": "DRAFT"})
mock_db.execute.return_value.fetchone.return_value = updated_row
resp = client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "DRAFT"})
assert resp.status_code == 200
def test_invalid_status_returns_400(self, mock_db):
resp = client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "INVALID_STATUS"})
assert resp.status_code == 400
def test_status_not_found_returns_404(self, mock_db):
mock_db.execute.return_value.fetchone.return_value = None
resp = client.put(f"/loeschfristen/{UNKNOWN_ID}/status", json={"status": "ACTIVE"})
assert resp.status_code == 404
def test_status_update_commits(self, mock_db):
updated_row = make_policy_row()
mock_db.execute.return_value.fetchone.return_value = updated_row
client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "ACTIVE"})
mock_db.commit.assert_called_once()
def test_status_update_passes_correct_params(self, mock_db):
updated_row = make_policy_row({"status": "ACTIVE"})
mock_db.execute.return_value.fetchone.return_value = updated_row
client.put(f"/loeschfristen/{POLICY_ID}/status", json={"status": "ACTIVE"})
call_params = mock_db.execute.call_args[0][1]
assert call_params["status"] == "ACTIVE"
assert call_params["id"] == POLICY_ID
assert call_params["tenant_id"] == DEFAULT_TENANT
# =============================================================================
# DELETE /loeschfristen/{id}
# =============================================================================
class TestDeleteLoeschfrist:
def test_delete_success_returns_204(self, mock_db):
mock_db.execute.return_value.rowcount = 1
resp = client.delete(f"/loeschfristen/{POLICY_ID}")
assert resp.status_code == 204
def test_delete_not_found_returns_404(self, mock_db):
mock_db.execute.return_value.rowcount = 0
resp = client.delete(f"/loeschfristen/{UNKNOWN_ID}")
assert resp.status_code == 404
def test_delete_commits(self, mock_db):
mock_db.execute.return_value.rowcount = 1
client.delete(f"/loeschfristen/{POLICY_ID}")
mock_db.commit.assert_called_once()
def test_delete_commits_before_rowcount_check(self, mock_db):
mock_db.execute.return_value.rowcount = 0
client.delete(f"/loeschfristen/{UNKNOWN_ID}")
mock_db.commit.assert_called_once()
def test_delete_passes_correct_id_and_tenant(self, mock_db):
mock_db.execute.return_value.rowcount = 1
client.delete(f"/loeschfristen/{POLICY_ID}")
call_params = mock_db.execute.call_args[0][1]
assert call_params["id"] == POLICY_ID
assert call_params["tenant_id"] == DEFAULT_TENANT
def test_delete_with_custom_tenant(self, mock_db):
mock_db.execute.return_value.rowcount = 1
client.delete(
f"/loeschfristen/{POLICY_ID}",
headers={"x-tenant-id": "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"},
)
call_params = mock_db.execute.call_args[0][1]
assert call_params["tenant_id"] == "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"