perf(organizations): cache and instrument API responses

This commit is contained in:
2026-05-14 16:14:45 +02:00
parent 6d1ec2e55c
commit 5fdd23ecc0
13 changed files with 603 additions and 33 deletions

View File

@@ -1,18 +1,22 @@
"""Tests for core middleware"""
import json
import logging
from io import StringIO
from apps.core.middleware import (
ApiCsrfExemptMiddleware,
ApiSlashlessRouteMiddleware,
OrganizationApiMetricsMiddleware,
RequestIDMiddleware,
RequestLoggingMiddleware,
get_request_id,
)
from django.conf import settings
from django.http import HttpResponse
from django.test import RequestFactory
from django.urls import reverse
from organizations.models import Organization
from rest_framework.test import APITestCase
@@ -126,3 +130,70 @@ class ApiSlashlessRouteMiddlewareTest(APITestCase):
self.middleware.process_request(request)
self.assertEqual(request.path_info, "/admin/login")
class OrganizationApiMetricsMiddlewareTest(APITestCase):
def setUp(self):
self.factory = RequestFactory()
def test_middleware_is_enabled_for_organization_endpoint_metrics(self):
self.assertIn(
"apps.core.middleware.OrganizationApiMetricsMiddleware",
settings.MIDDLEWARE,
)
def test_logs_organization_endpoint_metrics_without_query_values(self):
Organization.objects.create(name='ООО "Метрика"', inn="7707083893")
def get_response(request):
Organization.objects.count()
response = HttpResponse("ok", status=200)
response["X-Cache"] = "MISS"
return response
middleware = OrganizationApiMetricsMiddleware(get_response)
request = self.factory.get(
"/api/v2/organizations/",
{"page": "1", "page_size": "20", "search": "7707083893"},
)
request.request_id = "metrics-request"
request.user = type("AnonymousUser", (), {"is_authenticated": False})()
with self.assertLogs("organizations.api.metrics", level="INFO") as captured:
response = middleware(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(captured.output), 1)
_, payload = captured.output[0].split("organization_api_metrics ", 1)
metrics = json.loads(payload)
self.assertEqual(metrics["request_id"], "metrics-request")
self.assertEqual(metrics["method"], "GET")
self.assertEqual(metrics["path"], "/api/v2/organizations/")
self.assertEqual(metrics["status_code"], 200)
self.assertEqual(metrics["cache"], "MISS")
self.assertEqual(metrics["query_keys"], ["page", "page_size", "search"])
self.assertGreaterEqual(metrics["db_query_count"], 1)
self.assertGreater(metrics["duration_ms"], 0)
self.assertGreater(metrics["response_size_bytes"], 0)
self.assertNotIn("7707083893", captured.output[0])
def test_ignores_non_organization_api_paths(self):
middleware = OrganizationApiMetricsMiddleware(
lambda request: HttpResponse("ok", status=200)
)
request = self.factory.get("/api/v2/sources/")
request.request_id = "metrics-request"
request.user = type("AnonymousUser", (), {"is_authenticated": False})()
logger = logging.getLogger("organizations.api.metrics")
stream = StringIO()
handler = logging.StreamHandler(stream)
logger.addHandler(handler)
try:
response = middleware(request)
finally:
logger.removeHandler(handler)
self.assertEqual(response.status_code, 200)
self.assertEqual(stream.getvalue(), "")