"""Tests for AI Quality Metrics and Tests routes (quality_routes.py). Covers: - Schema validation (MetricCreate, MetricUpdate, TestCreate, TestUpdate) - Helper functions (_row_to_dict, _get_tenant_id) - HTTP endpoints via FastAPI TestClient with mocked DB session """ import pytest from unittest.mock import MagicMock from datetime import datetime from fastapi import FastAPI from fastapi.testclient import TestClient from compliance.api.quality_routes import ( router, MetricCreate, MetricUpdate, TestCreate, TestUpdate, ) from compliance.api.db_utils import row_to_dict as _row_to_dict DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" from classroom_engine.database import get_db # ============================================================================= # TestClient Setup # ============================================================================= app = FastAPI() app.include_router(router) client = TestClient(app) DEFAULT_TENANT = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" OTHER_TENANT = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff" METRIC_ID = "bbbbbbbb-0001-0001-0001-000000000001" TEST_ID = "cccccccc-0001-0001-0001-000000000001" def make_metric_row(overrides=None): data = { "id": METRIC_ID, "tenant_id": DEFAULT_TENANT, "name": "Test Metric", "category": "accuracy", "score": 85.0, "threshold": 80.0, "trend": "stable", "ai_system": None, "last_measured": datetime(2024, 1, 1), "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_test_row(overrides=None): data = { "id": TEST_ID, "tenant_id": DEFAULT_TENANT, "name": "Test Run", "status": "passed", "duration": "1.2s", "ai_system": None, "details": None, "last_run": datetime(2024, 1, 1), "created_at": datetime(2024, 1, 1), "updated_at": datetime(2024, 1, 1), } if overrides: data.update(overrides) row = MagicMock() row._mapping = data return row @pytest.fixture def mock_db(): db = MagicMock() def override_get_db(): yield db app.dependency_overrides[get_db] = override_get_db yield db app.dependency_overrides.clear() # ============================================================================= # Schema Tests — MetricCreate # ============================================================================= class TestMetricCreate: def test_minimal_valid(self): req = MetricCreate(name="Accuracy Score") assert req.name == "Accuracy Score" assert req.category == "accuracy" assert req.score == 0.0 assert req.threshold == 80.0 assert req.trend == "stable" assert req.ai_system is None assert req.last_measured is None def test_full_values(self): ts = datetime(2026, 3, 1) req = MetricCreate( name="Fairness Score", category="fairness", score=92.5, threshold=85.0, trend="improving", ai_system="RecSys-v2", last_measured=ts, ) assert req.name == "Fairness Score" assert req.category == "fairness" assert req.score == 92.5 assert req.trend == "improving" assert req.ai_system == "RecSys-v2" assert req.last_measured == ts def test_serialization_excludes_none(self): req = MetricCreate(name="Drift Score", score=75.0) data = req.model_dump(exclude_none=True) assert data["name"] == "Drift Score" assert data["score"] == 75.0 assert "ai_system" not in data assert "last_measured" not in data def test_default_trend_stable(self): req = MetricCreate(name="Test") assert req.trend == "stable" def test_default_score_zero(self): req = MetricCreate(name="Test") assert req.score == 0.0 # ============================================================================= # Schema Tests — MetricUpdate # ============================================================================= class TestMetricUpdate: def test_empty_update(self): req = MetricUpdate() data = req.model_dump(exclude_unset=True) assert data == {} def test_partial_score_update(self): req = MetricUpdate(score=95.0) data = req.model_dump(exclude_unset=True) assert data == {"score": 95.0} def test_trend_and_threshold_update(self): req = MetricUpdate(trend="declining", threshold=90.0) data = req.model_dump(exclude_unset=True) assert data["trend"] == "declining" assert data["threshold"] == 90.0 assert "name" not in data def test_full_update(self): req = MetricUpdate( name="New Name", category="robustness", score=88.0, threshold=85.0, trend="improving", ai_system="ModelB", ) data = req.model_dump(exclude_unset=True) assert len(data) == 6 assert data["category"] == "robustness" # ============================================================================= # Schema Tests — TestCreate # ============================================================================= class TestTestCreate: def test_minimal_valid(self): req = TestCreate(name="Bias Detection Test") assert req.name == "Bias Detection Test" assert req.status == "pending" assert req.duration is None assert req.ai_system is None assert req.details is None assert req.last_run is None def test_full_values(self): ts = datetime(2026, 3, 1, 12, 0, 0) req = TestCreate( name="Accuracy Test Suite", status="passed", duration="3.45s", ai_system="ClassifierV3", details="All 500 samples passed", last_run=ts, ) assert req.status == "passed" assert req.duration == "3.45s" assert req.ai_system == "ClassifierV3" assert req.details == "All 500 samples passed" assert req.last_run == ts def test_failed_status(self): req = TestCreate(name="Fairness Check", status="failed") assert req.status == "failed" def test_serialization_excludes_none(self): req = TestCreate(name="Quick Test", status="passed") data = req.model_dump(exclude_none=True) assert "duration" not in data assert "ai_system" not in data # ============================================================================= # Schema Tests — TestUpdate # ============================================================================= class TestTestUpdate: def test_empty_update(self): req = TestUpdate() data = req.model_dump(exclude_unset=True) assert data == {} def test_status_update(self): req = TestUpdate(status="failed") data = req.model_dump(exclude_unset=True) assert data == {"status": "failed"} def test_duration_and_details_update(self): req = TestUpdate(duration="10.5s", details="Timeout on 3 samples") data = req.model_dump(exclude_unset=True) assert data["duration"] == "10.5s" assert data["details"] == "Timeout on 3 samples" assert "name" not in data # ============================================================================= # Helper Tests — _row_to_dict # ============================================================================= class TestRowToDict: def test_basic_metric_conversion(self): row = make_metric_row() result = _row_to_dict(row) assert result["id"] == METRIC_ID assert result["name"] == "Test Metric" assert result["score"] == 85.0 assert result["threshold"] == 80.0 def test_datetime_serialized(self): ts = datetime(2024, 6, 15, 9, 0, 0) row = make_metric_row({"created_at": ts, "last_measured": ts}) result = _row_to_dict(row) assert result["created_at"] == ts.isoformat() assert result["last_measured"] == ts.isoformat() def test_none_values_preserved(self): row = make_metric_row() result = _row_to_dict(row) assert result["ai_system"] is None def test_uuid_converted_to_string(self): import uuid uid = uuid.UUID(DEFAULT_TENANT) row = MagicMock() row._mapping = {"id": uid, "tenant_id": uid} result = _row_to_dict(row) assert result["id"] == str(uid) def test_numeric_fields_unchanged(self): row = MagicMock() row._mapping = {"score": 92.5, "threshold": 80.0, "count": 10} result = _row_to_dict(row) assert result["score"] == 92.5 assert result["threshold"] == 80.0 assert result["count"] == 10 # ============================================================================= # HTTP Tests — GET /quality/stats # ============================================================================= class TestGetQualityStats: def test_stats_all_zeros(self, mock_db): metrics_row = MagicMock() metrics_row.total_metrics = 0 metrics_row.avg_score = 0 metrics_row.metrics_above_threshold = 0 tests_row = MagicMock() tests_row.passed = 0 tests_row.failed = 0 tests_row.warning = 0 tests_row.total = 0 mock_db.execute.return_value.fetchone.side_effect = [metrics_row, tests_row] response = client.get("/quality/stats") assert response.status_code == 200 data = response.json() assert data["total_metrics"] == 0 assert data["avg_score"] == 0.0 assert data["metrics_above_threshold"] == 0 assert data["passed"] == 0 assert data["failed"] == 0 assert data["warning"] == 0 assert data["total_tests"] == 0 def test_stats_with_data(self, mock_db): metrics_row = MagicMock() metrics_row.total_metrics = 5 metrics_row.avg_score = 87.4 metrics_row.metrics_above_threshold = 4 tests_row = MagicMock() tests_row.passed = 10 tests_row.failed = 2 tests_row.warning = 1 tests_row.total = 13 mock_db.execute.return_value.fetchone.side_effect = [metrics_row, tests_row] response = client.get("/quality/stats") assert response.status_code == 200 data = response.json() assert data["total_metrics"] == 5 assert data["avg_score"] == 87.4 assert data["metrics_above_threshold"] == 4 assert data["passed"] == 10 assert data["failed"] == 2 assert data["warning"] == 1 assert data["total_tests"] == 13 def test_stats_none_values_become_zero(self, mock_db): metrics_row = MagicMock() metrics_row.total_metrics = None metrics_row.avg_score = None metrics_row.metrics_above_threshold = None tests_row = MagicMock() tests_row.passed = None tests_row.failed = None tests_row.warning = None tests_row.total = None mock_db.execute.return_value.fetchone.side_effect = [metrics_row, tests_row] response = client.get("/quality/stats") assert response.status_code == 200 data = response.json() assert data["total_metrics"] == 0 assert data["avg_score"] == 0.0 assert data["total_tests"] == 0 def test_stats_with_tenant_header(self, mock_db): metrics_row = MagicMock() metrics_row.total_metrics = 2 metrics_row.avg_score = 90.0 metrics_row.metrics_above_threshold = 2 tests_row = MagicMock() tests_row.passed = 5 tests_row.failed = 0 tests_row.warning = 0 tests_row.total = 5 mock_db.execute.return_value.fetchone.side_effect = [metrics_row, tests_row] response = client.get( "/quality/stats", headers={"X-Tenant-Id": OTHER_TENANT}, ) assert response.status_code == 200 assert response.json()["total_metrics"] == 2 # ============================================================================= # HTTP Tests — GET /quality/metrics # ============================================================================= class TestListMetrics: def test_empty_list(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 0 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [] mock_db.execute.return_value = execute_result response = client.get("/quality/metrics") assert response.status_code == 200 data = response.json() assert data["metrics"] == [] assert data["total"] == 0 def test_list_with_data(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 2 m1 = make_metric_row() m2 = make_metric_row({"id": "bbbbbbbb-0002-0002-0002-000000000002", "name": "Second Metric"}) execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [m1, m2] mock_db.execute.return_value = execute_result response = client.get("/quality/metrics") assert response.status_code == 200 data = response.json() assert len(data["metrics"]) == 2 assert data["total"] == 2 def test_filter_by_category(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 1 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [make_metric_row({"category": "fairness"})] mock_db.execute.return_value = execute_result response = client.get("/quality/metrics?category=fairness") assert response.status_code == 200 data = response.json() assert data["metrics"][0]["category"] == "fairness" def test_filter_by_ai_system(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 1 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [make_metric_row({"ai_system": "ModelAlpha"})] mock_db.execute.return_value = execute_result response = client.get("/quality/metrics?ai_system=ModelAlpha") assert response.status_code == 200 data = response.json() assert data["metrics"][0]["ai_system"] == "ModelAlpha" def test_pagination(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 20 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [make_metric_row()] mock_db.execute.return_value = execute_result response = client.get("/quality/metrics?limit=1&offset=10") assert response.status_code == 200 data = response.json() assert data["total"] == 20 assert len(data["metrics"]) == 1 # ============================================================================= # HTTP Tests — POST /quality/metrics # ============================================================================= class TestCreateMetric: def test_create_success(self, mock_db): created_row = make_metric_row({"name": "New Metric"}) mock_db.execute.return_value.fetchone.return_value = created_row response = client.post( "/quality/metrics", json={"name": "New Metric"}, ) assert response.status_code == 201 data = response.json() assert data["name"] == "New Metric" mock_db.commit.assert_called_once() def test_create_full_metric(self, mock_db): created_row = make_metric_row({ "name": "Robustness Score", "category": "robustness", "score": 78.5, "threshold": 75.0, "trend": "improving", }) mock_db.execute.return_value.fetchone.return_value = created_row response = client.post("/quality/metrics", json={ "name": "Robustness Score", "category": "robustness", "score": 78.5, "threshold": 75.0, "trend": "improving", }) assert response.status_code == 201 data = response.json() assert data["category"] == "robustness" assert data["score"] == 78.5 def test_create_missing_name_fails(self, mock_db): response = client.post("/quality/metrics", json={"score": 90.0}) assert response.status_code == 422 def test_create_with_tenant_header(self, mock_db): created_row = make_metric_row({"tenant_id": OTHER_TENANT}) mock_db.execute.return_value.fetchone.return_value = created_row response = client.post( "/quality/metrics", json={"name": "Tenant B metric"}, headers={"X-Tenant-Id": OTHER_TENANT}, ) assert response.status_code == 201 assert mock_db.commit.called # ============================================================================= # HTTP Tests — PUT /quality/metrics/{id} # ============================================================================= class TestUpdateMetric: def test_update_success(self, mock_db): updated_row = make_metric_row({"score": 95.0, "trend": "improving"}) mock_db.execute.return_value.fetchone.return_value = updated_row response = client.put( f"/quality/metrics/{METRIC_ID}", json={"score": 95.0, "trend": "improving"}, ) assert response.status_code == 200 data = response.json() assert data["score"] == 95.0 assert data["trend"] == "improving" mock_db.commit.assert_called_once() def test_update_not_found_returns_404(self, mock_db): mock_db.execute.return_value.fetchone.return_value = None response = client.put( "/quality/metrics/nonexistent-id", json={"score": 50.0}, ) assert response.status_code == 404 assert "not found" in response.json()["detail"].lower() def test_update_no_fields_returns_400(self, mock_db): response = client.put(f"/quality/metrics/{METRIC_ID}", json={}) assert response.status_code == 400 def test_partial_update_category(self, mock_db): updated_row = make_metric_row({"category": "explainability"}) mock_db.execute.return_value.fetchone.return_value = updated_row response = client.put( f"/quality/metrics/{METRIC_ID}", json={"category": "explainability"}, ) assert response.status_code == 200 assert response.json()["category"] == "explainability" # ============================================================================= # HTTP Tests — DELETE /quality/metrics/{id} # ============================================================================= class TestDeleteMetric: def test_delete_success_204(self, mock_db): delete_result = MagicMock() delete_result.rowcount = 1 mock_db.execute.return_value = delete_result response = client.delete(f"/quality/metrics/{METRIC_ID}") assert response.status_code == 204 mock_db.commit.assert_called_once() def test_delete_not_found_returns_404(self, mock_db): delete_result = MagicMock() delete_result.rowcount = 0 mock_db.execute.return_value = delete_result response = client.delete("/quality/metrics/nonexistent-id") assert response.status_code == 404 assert "not found" in response.json()["detail"].lower() def test_delete_with_tenant_header(self, mock_db): delete_result = MagicMock() delete_result.rowcount = 1 mock_db.execute.return_value = delete_result response = client.delete( f"/quality/metrics/{METRIC_ID}", headers={"X-Tenant-Id": OTHER_TENANT}, ) assert response.status_code == 204 # ============================================================================= # HTTP Tests — GET /quality/tests # ============================================================================= class TestListQualityTests: def test_empty_list(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 0 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [] mock_db.execute.return_value = execute_result response = client.get("/quality/tests") assert response.status_code == 200 data = response.json() assert data["tests"] == [] assert data["total"] == 0 def test_list_with_data(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 2 t1 = make_test_row() t2 = make_test_row({"id": "cccccccc-0002-0002-0002-000000000002", "name": "Second Test"}) execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [t1, t2] mock_db.execute.return_value = execute_result response = client.get("/quality/tests") assert response.status_code == 200 data = response.json() assert len(data["tests"]) == 2 assert data["total"] == 2 def test_filter_by_status(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 1 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [make_test_row({"status": "failed"})] mock_db.execute.return_value = execute_result response = client.get("/quality/tests?status=failed") assert response.status_code == 200 data = response.json() assert data["tests"][0]["status"] == "failed" def test_filter_by_ai_system(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 1 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [make_test_row({"ai_system": "ModelBeta"})] mock_db.execute.return_value = execute_result response = client.get("/quality/tests?ai_system=ModelBeta") assert response.status_code == 200 data = response.json() assert data["tests"][0]["ai_system"] == "ModelBeta" def test_pagination(self, mock_db): count_row = MagicMock() count_row.__getitem__ = lambda self, i: 50 execute_result = MagicMock() execute_result.fetchone.return_value = count_row execute_result.fetchall.return_value = [make_test_row()] mock_db.execute.return_value = execute_result response = client.get("/quality/tests?limit=1&offset=5") assert response.status_code == 200 assert response.json()["total"] == 50 # ============================================================================= # HTTP Tests — POST /quality/tests # ============================================================================= class TestCreateQualityTest: def test_create_success(self, mock_db): created_row = make_test_row({"name": "New Test Run"}) mock_db.execute.return_value.fetchone.return_value = created_row response = client.post( "/quality/tests", json={"name": "New Test Run"}, ) assert response.status_code == 201 data = response.json() assert data["name"] == "New Test Run" mock_db.commit.assert_called_once() def test_create_full_test(self, mock_db): created_row = make_test_row({ "name": "Full Test Suite", "status": "passed", "duration": "5.0s", "ai_system": "Classifier", "details": "All assertions passed", }) mock_db.execute.return_value.fetchone.return_value = created_row response = client.post("/quality/tests", json={ "name": "Full Test Suite", "status": "passed", "duration": "5.0s", "ai_system": "Classifier", "details": "All assertions passed", }) assert response.status_code == 201 data = response.json() assert data["status"] == "passed" assert data["duration"] == "5.0s" def test_create_missing_name_fails(self, mock_db): response = client.post("/quality/tests", json={"status": "passed"}) assert response.status_code == 422 def test_create_failed_status(self, mock_db): created_row = make_test_row({"status": "failed"}) mock_db.execute.return_value.fetchone.return_value = created_row response = client.post( "/quality/tests", json={"name": "Failing test", "status": "failed"}, ) assert response.status_code == 201 assert response.json()["status"] == "failed" # ============================================================================= # HTTP Tests — PUT /quality/tests/{id} # ============================================================================= class TestUpdateQualityTest: def test_update_success(self, mock_db): updated_row = make_test_row({"status": "failed", "details": "Assertion error on line 42"}) mock_db.execute.return_value.fetchone.return_value = updated_row response = client.put( f"/quality/tests/{TEST_ID}", json={"status": "failed", "details": "Assertion error on line 42"}, ) assert response.status_code == 200 data = response.json() assert data["status"] == "failed" assert data["details"] == "Assertion error on line 42" mock_db.commit.assert_called_once() def test_update_not_found_returns_404(self, mock_db): mock_db.execute.return_value.fetchone.return_value = None response = client.put( "/quality/tests/nonexistent-id", json={"status": "passed"}, ) assert response.status_code == 404 assert "not found" in response.json()["detail"].lower() def test_update_no_fields_returns_400(self, mock_db): response = client.put(f"/quality/tests/{TEST_ID}", json={}) assert response.status_code == 400 def test_update_duration_only(self, mock_db): updated_row = make_test_row({"duration": "2.8s"}) mock_db.execute.return_value.fetchone.return_value = updated_row response = client.put( f"/quality/tests/{TEST_ID}", json={"duration": "2.8s"}, ) assert response.status_code == 200 assert response.json()["duration"] == "2.8s" def test_update_with_tenant_header(self, mock_db): updated_row = make_test_row({"tenant_id": OTHER_TENANT, "status": "warning"}) mock_db.execute.return_value.fetchone.return_value = updated_row response = client.put( f"/quality/tests/{TEST_ID}", json={"status": "warning"}, headers={"X-Tenant-Id": OTHER_TENANT}, ) assert response.status_code == 200 assert response.json()["status"] == "warning" # ============================================================================= # HTTP Tests — DELETE /quality/tests/{id} # ============================================================================= class TestDeleteQualityTest: def test_delete_success_204(self, mock_db): delete_result = MagicMock() delete_result.rowcount = 1 mock_db.execute.return_value = delete_result response = client.delete(f"/quality/tests/{TEST_ID}") assert response.status_code == 204 mock_db.commit.assert_called_once() def test_delete_not_found_returns_404(self, mock_db): delete_result = MagicMock() delete_result.rowcount = 0 mock_db.execute.return_value = delete_result response = client.delete("/quality/tests/nonexistent-id") assert response.status_code == 404 assert "not found" in response.json()["detail"].lower() def test_delete_with_tenant_header(self, mock_db): delete_result = MagicMock() delete_result.rowcount = 1 mock_db.execute.return_value = delete_result response = client.delete( f"/quality/tests/{TEST_ID}", headers={"X-Tenant-Id": OTHER_TENANT}, ) assert response.status_code == 204 assert mock_db.commit.called # ============================================================================= # Tenant Isolation Tests # ============================================================================= class TestTenantIsolation: def test_metrics_tenant_isolation(self, mock_db): """Tenant A sees 3 metrics, Tenant B sees 0.""" def side_effect(query, params): result = MagicMock() tid = params.get("tenant_id", "") if tid == DEFAULT_TENANT: count_row = MagicMock() count_row.__getitem__ = lambda self, i: 3 result.fetchone.return_value = count_row result.fetchall.return_value = [ make_metric_row(), make_metric_row({"id": "bbbbbbbb-0002-0002-0002-000000000002"}), make_metric_row({"id": "bbbbbbbb-0003-0003-0003-000000000003"}), ] else: count_row = MagicMock() count_row.__getitem__ = lambda self, i: 0 result.fetchone.return_value = count_row result.fetchall.return_value = [] return result mock_db.execute.side_effect = side_effect resp_a = client.get( "/quality/metrics", headers={"X-Tenant-Id": DEFAULT_TENANT}, ) assert resp_a.json()["total"] == 3 resp_b = client.get( "/quality/metrics", headers={"X-Tenant-Id": OTHER_TENANT}, ) assert resp_b.json()["total"] == 0 def test_tests_tenant_isolation(self, mock_db): """Tenant A sees 2 tests, Tenant B sees 0.""" def side_effect(query, params): result = MagicMock() tid = params.get("tenant_id", "") if tid == DEFAULT_TENANT: count_row = MagicMock() count_row.__getitem__ = lambda self, i: 2 result.fetchone.return_value = count_row result.fetchall.return_value = [make_test_row(), make_test_row({"id": "cccccccc-0002-0002-0002-000000000002"})] else: count_row = MagicMock() count_row.__getitem__ = lambda self, i: 0 result.fetchone.return_value = count_row result.fetchall.return_value = [] return result mock_db.execute.side_effect = side_effect resp_a = client.get("/quality/tests", headers={"X-Tenant-Id": DEFAULT_TENANT}) assert resp_a.json()["total"] == 2 resp_b = client.get("/quality/tests", headers={"X-Tenant-Id": OTHER_TENANT}) assert resp_b.json()["total"] == 0 def test_invalid_tenant_header_returns_400(self, mock_db): response = client.get( "/quality/metrics", headers={"X-Tenant-Id": "bad-uuid"}, ) assert response.status_code == 400 def test_delete_wrong_tenant_returns_404(self, mock_db): """Deleting a metric that belongs to a different tenant returns 404.""" delete_result = MagicMock() delete_result.rowcount = 0 mock_db.execute.return_value = delete_result response = client.delete( f"/quality/metrics/{METRIC_ID}", headers={"X-Tenant-Id": OTHER_TENANT}, ) assert response.status_code == 404