feat(cache): add targeted server-side response caching

This commit is contained in:
2026-04-30 16:13:12 +03:30
parent 08e1793765
commit 054bb5a582
8 changed files with 432 additions and 16 deletions

View File

@@ -1,6 +1,8 @@
from types import SimpleNamespace
from django.core.cache import cache
from django.test import TestCase
from rest_framework.test import APITestCase
from apps.users.models import User
from apps.workspaces.api.permissions import (
@@ -144,3 +146,46 @@ class WorkspacePermissionTests(TestCase):
object(),
)
)
class WorkspaceMembershipCacheTests(APITestCase):
@classmethod
def setUpTestData(cls):
cls.owner = User.objects.create_user(mobile="09127770031", password="secret123")
cls.member = User.objects.create_user(mobile="09127770032", password="secret123")
cls.workspace = Workspace.objects.create(name="Membership Cache", owner=cls.owner)
cls.membership = WorkspaceMembership.objects.create(
workspace=cls.workspace,
user=cls.member,
role=WorkspaceMembership.Role.MEMBER,
is_active=True,
)
def setUp(self):
cache.clear()
self.client.force_authenticate(user=self.owner)
def test_membership_list_cache_invalidates_after_membership_save(self):
params = {"workspace": str(self.workspace.id)}
first_response = self.client.get("/api/workspace-memberships/", params)
self.assertEqual(first_response.status_code, 200)
target = next(item for item in first_response.data["items"] if item["id"] == str(self.membership.id))
self.assertEqual(target["role"], WorkspaceMembership.Role.MEMBER)
WorkspaceMembership.objects.filter(id=self.membership.id).update(role=WorkspaceMembership.Role.GUEST)
cached_response = self.client.get("/api/workspace-memberships/", params)
self.assertEqual(cached_response.status_code, 200)
target = next(item for item in cached_response.data["items"] if item["id"] == str(self.membership.id))
self.assertEqual(target["role"], WorkspaceMembership.Role.MEMBER)
self.membership.refresh_from_db()
self.membership.is_active = False
self.membership.save(update_fields=["is_active"])
fresh_response = self.client.get("/api/workspace-memberships/", params)
self.assertEqual(fresh_response.status_code, 200)
target = next(item for item in fresh_response.data["items"] if item["id"] == str(self.membership.id))
self.assertEqual(target["role"], WorkspaceMembership.Role.GUEST)
self.assertFalse(target["is_active"])

View File

@@ -1,5 +1,6 @@
from decimal import Decimal
from django.core.cache import cache
from django.test import TestCase
from rest_framework.test import APITestCase
@@ -53,6 +54,9 @@ class WorkspaceRateTests(APITestCase):
symbol="EUR",
)
def setUp(self):
cache.clear()
def test_resolve_rate_uses_workspace_user_rate(self):
WorkspaceUserRate.objects.create(
workspace=self.workspace,
@@ -122,6 +126,72 @@ class WorkspaceRateTests(APITestCase):
self.assertEqual(response.status_code, 403)
def test_workspace_user_rates_cache_invalidates_after_rate_save(self):
rate = WorkspaceUserRate.objects.create(
workspace=self.workspace,
user=self.member,
hourly_rate=Decimal("30.00"),
currency="USD",
effective_from=self.workspace.created_at,
is_active=True,
)
self.client.force_authenticate(user=self.admin)
first_response = self.client.get(
"/api/workspace-user-rates/",
{"workspace": str(self.workspace.id)},
)
self.assertEqual(first_response.status_code, 200)
self.assertEqual(first_response.data["items"][0]["hourly_rate"], "30.00")
WorkspaceUserRate.objects.filter(id=rate.id).update(hourly_rate=Decimal("45.00"))
cached_response = self.client.get(
"/api/workspace-user-rates/",
{"workspace": str(self.workspace.id)},
)
self.assertEqual(cached_response.status_code, 200)
self.assertEqual(cached_response.data["items"][0]["hourly_rate"], "30.00")
rate.refresh_from_db()
rate.currency = "EUR"
rate.save(update_fields=["currency"])
fresh_response = self.client.get(
"/api/workspace-user-rates/",
{"workspace": str(self.workspace.id)},
)
self.assertEqual(fresh_response.status_code, 200)
self.assertEqual(fresh_response.data["items"][0]["hourly_rate"], "45.00")
self.assertEqual(fresh_response.data["items"][0]["currency"], "EUR")
def test_price_unit_cache_invalidates_after_price_unit_create(self):
self.client.force_authenticate(user=self.owner)
first_response = self.client.get("/api/price-units/")
self.assertEqual(first_response.status_code, 200)
self.assertEqual(first_response.data[0]["name"], "Euro")
self.assertEqual(len(first_response.data), 2)
PriceUnit.objects.filter(code="EUR").update(name="Updated Euro")
cached_response = self.client.get("/api/price-units/")
self.assertEqual(cached_response.status_code, 200)
self.assertEqual(cached_response.data[0]["name"], "Euro")
PriceUnit.objects.create(
code="GBP",
name="British Pound",
local_name="Pound",
symbol="£",
)
fresh_response = self.client.get("/api/price-units/")
self.assertEqual(fresh_response.status_code, 200)
self.assertEqual(len(fresh_response.data), 3)
euro_row = next(item for item in fresh_response.data if item["code"] == "EUR")
self.assertEqual(euro_row["name"], "Updated Euro")
class WorkspaceRateServiceTests(TestCase):
@classmethod