Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2416] Scope ZGW clients cache keys by the underlying service endpoint #1237

Merged
merged 1 commit into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions src/open_inwoner/openzaak/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def fetch_cases(
return []

@cache_result(
"cases:{user_bsn}:{max_requests}:{identificatie}",
"{self.base_url}:cases:{user_bsn}:{max_requests}:{identificatie}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_cases_by_bsn(
Expand Down Expand Up @@ -114,7 +114,7 @@ def fetch_cases_by_bsn(
return cases

@cache_result(
"cases:{kvk_or_rsin}:{vestigingsnummer}:{max_requests}:{zaak_identificatie}",
"{self.base_url}:cases:{kvk_or_rsin}:{vestigingsnummer}:{max_requests}:{zaak_identificatie}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_cases_by_kvk_or_rsin(
Expand Down Expand Up @@ -174,7 +174,10 @@ def fetch_cases_by_kvk_or_rsin(

return cases

@cache_result("single_case:{case_uuid}", timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT)
@cache_result(
"{self.base_url}:single_case:{case_uuid}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_single_case(self, case_uuid: str) -> Zaak | None:
try:
response = self.get(f"zaken/{case_uuid}", headers=CRS_HEADERS)
Expand All @@ -200,7 +203,8 @@ def fetch_case_by_url_no_cache(self, case_url: str) -> Zaak | None:
return case

@cache_result(
"single_case_information_object:{url}", timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT
"{self.base_url}:single_case_information_object:{url}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_single_case_information_object(
self, url: str
Expand Down Expand Up @@ -246,11 +250,14 @@ def fetch_status_history_no_cache(self, case_url: str) -> list[Status]:

return statuses

@cache_result("status_history:{case_url}", timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT)
@cache_result(
"{self.base_url}:status_history:{case_url}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_status_history(self, case_url: str) -> list[Status]:
return self.fetch_status_history_no_cache(case_url)

@cache_result("status:{status_url}", timeout=60 * 60)
@cache_result("{self.base_url}:status:{status_url}", timeout=60 * 60)
def fetch_single_status(self, status_url: str) -> Status | None:
try:
response = self.get(url=status_url)
Expand All @@ -264,7 +271,7 @@ def fetch_single_status(self, status_url: str) -> Status | None:
return status

@cache_result(
"case_roles:{case_url}:{role_desc_generic}",
"{self.base_url}:case_roles:{case_url}:{role_desc_generic}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_case_roles(
Expand Down Expand Up @@ -385,7 +392,8 @@ def fetch_case_information_objects_for_case_and_info(
return case_info_objects

@cache_result(
"single_result:{result_url}", timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT
"{self.base_url}:single_result:{result_url}",
timeout=settings.CACHE_ZGW_ZAKEN_TIMEOUT,
)
def fetch_single_result(self, result_url: str) -> Resultaat | None:
try:
Expand Down Expand Up @@ -453,7 +461,8 @@ def fetch_result_types_no_cache(self, case_type_url: str) -> list[ResultaatType]
return result_types

@cache_result(
"status_type:{status_type_url}", timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT
"{self.base_url}:status_type:{status_type_url}",
timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT,
)
def fetch_single_status_type(self, status_type_url: str) -> StatusType | None:
try:
Expand All @@ -468,7 +477,7 @@ def fetch_single_status_type(self, status_type_url: str) -> StatusType | None:
return status_type

@cache_result(
"resultaat_type:{resultaat_type_url}",
"{self.base_url}:resultaat_type:{resultaat_type_url}",
timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT,
)
def fetch_single_resultaat_type(
Expand Down Expand Up @@ -522,7 +531,8 @@ def fetch_case_types_by_identification_no_cache(
return zaak_types

@cache_result(
"case_type:{case_type_url}", timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT
"{self.base_url}:case_type:{case_type_url}",
timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT,
)
def fetch_single_case_type(self, case_type_url: str) -> ZaakType | None:
try:
Expand Down Expand Up @@ -553,7 +563,7 @@ def fetch_catalogs_no_cache(self) -> list[Catalogus]:
return catalogs

@cache_result(
"information_object_type:{information_object_type_url}",
"{self.base_url}:information_object_type:{information_object_type_url}",
timeout=settings.CACHE_ZGW_CATALOGI_TIMEOUT,
)
def fetch_single_information_object_type(
Expand Down
76 changes: 53 additions & 23 deletions src/open_inwoner/openzaak/tests/test_cases_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,17 @@ def test_case_types_are_cached(self, m):
self._setUpMocks(m)

# Cache is empty before the request
self.assertIsNone(cache.get(f"case_type:{self.zaaktype['url']}"))
self.assertIsNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.zaaktype['url']}")
)

self.client.force_login(user=self.user)
self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

# Case type is cached after the request
self.assertIsNotNone(cache.get(f"case_type:{self.zaaktype['url']}"))
self.assertIsNotNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.zaaktype['url']}")
)

def test_cached_case_types_are_deleted_after_one_day(self, m):
self._setUpMocks(m)
Expand All @@ -213,30 +217,42 @@ def test_cached_case_types_are_deleted_after_one_day(self, m):

# After one day the results should be deleted
frozen_time.tick(delta=datetime.timedelta(days=1))
self.assertIsNone(cache.get(f"case_type:{self.zaaktype['url']}"))
self.assertIsNone(
cache.get(f"{ZAKEN_ROOT}:case_type:{self.zaaktype['url']}")
)

def test_cached_case_types_in_combination_with_new_ones(self, m):
self._setUpMocks(m)

with freeze_time("2022-01-01 12:00") as frozen_time:
# First attempt
self.assertIsNone(cache.get(f"case_type:{self.zaaktype['url']}"))
self.assertIsNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.zaaktype['url']}")
)

self.client.force_login(user=self.user)
self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

self.assertIsNotNone(cache.get(f"case_type:{self.zaaktype['url']}"))
self.assertIsNotNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.zaaktype['url']}")
)

# Second attempt with new case and case type
self._setUpNewMock(m)
# Wait 3 minutes for the list cases cache to expire
frozen_time.tick(delta=datetime.timedelta(minutes=3))
self.assertIsNone(cache.get(f"case_type:{self.new_zaaktype['url']}"))
self.assertIsNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.new_zaaktype['url']}")
)

self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

self.assertIsNotNone(cache.get(f"case_type:{self.zaaktype['url']}"))
self.assertIsNotNone(cache.get(f"case_type:{self.new_zaaktype['url']}"))
self.assertIsNotNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.zaaktype['url']}")
)
self.assertIsNotNone(
cache.get(f"{CATALOGI_ROOT}:case_type:{self.new_zaaktype['url']}")
)

def test_cached_status_types_are_deleted_after_one_day(self, m):
self._setUpMocks(m)
Expand All @@ -248,25 +264,29 @@ def test_cached_status_types_are_deleted_after_one_day(self, m):
# After one day the results should be deleted
frozen_time.tick(delta=datetime.timedelta(hours=24))
self.assertIsNone(
cache.get(f"status_types_for_case_type:{self.zaaktype['url']}")
cache.get(
f"{ZAKEN_ROOT}:status_types_for_case_type:{self.zaaktype['url']}"
)
)
self.assertIsNone(
cache.get(f"status_types_for_case_type:{self.zaaktype['url']}")
cache.get(
f"{ZAKEN_ROOT}:status_types_for_case_type:{self.zaaktype['url']}"
)
)

def test_statuses_are_cached(self, m):
self._setUpMocks(m)

# Cache is empty before the request
self.assertIsNone(cache.get(f"status:{self.status1['url']}"))
self.assertIsNone(cache.get(f"status:{self.status2['url']}"))
self.assertIsNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status1['url']}"))
self.assertIsNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status2['url']}"))

self.client.force_login(user=self.user)
self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

# Status is cached after the request
self.assertIsNotNone(cache.get(f"status:{self.status1['url']}"))
self.assertIsNotNone(cache.get(f"status:{self.status2['url']}"))
self.assertIsNotNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status1['url']}"))
self.assertIsNotNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status2['url']}"))

def test_cached_statuses_are_deleted_after_one_hour(self, m):
self._setUpMocks(m)
Expand All @@ -277,22 +297,26 @@ def test_cached_statuses_are_deleted_after_one_hour(self, m):

# After one hour the results should be deleted
frozen_time.tick(delta=datetime.timedelta(hours=1))
self.assertIsNone(cache.get(f"status:{self.status1['url']}"))
self.assertIsNone(cache.get(f"status:{self.status2['url']}"))
self.assertIsNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status1['url']}"))
self.assertIsNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status2['url']}"))

def test_cached_statuses_in_combination_with_new_ones(self, m):
self._setUpMocks(m)

with freeze_time("2022-01-01 12:00") as frozen_time:
# First attempt
self.assertIsNone(cache.get(f"status:{self.status1['url']}"))
self.assertIsNone(cache.get(f"status:{self.status2['url']}"))
self.assertIsNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status1['url']}"))
self.assertIsNone(cache.get(f"{ZAKEN_ROOT}:status:{self.status2['url']}"))

self.client.force_login(user=self.user)
self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

self.assertIsNotNone(cache.get(f"status:{self.status1['url']}"))
self.assertIsNotNone(cache.get(f"status:{self.status2['url']}"))
self.assertIsNotNone(
cache.get(f"{ZAKEN_ROOT}:status:{self.status1['url']}")
)
self.assertIsNotNone(
cache.get(f"{ZAKEN_ROOT}:status:{self.status2['url']}")
)

# Second attempt with new case and status type
self._setUpNewMock(m)
Expand All @@ -302,6 +326,12 @@ def test_cached_statuses_in_combination_with_new_ones(self, m):

self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

self.assertIsNotNone(cache.get(f"status:{self.new_status['url']}"))
self.assertIsNotNone(cache.get(f"status:{self.status1['url']}"))
self.assertIsNotNone(cache.get(f"status:{self.status2['url']}"))
self.assertIsNotNone(
cache.get(f"{ZAKEN_ROOT}:status:{self.new_status['url']}")
)
self.assertIsNotNone(
cache.get(f"{ZAKEN_ROOT}:status:{self.status1['url']}")
)
self.assertIsNotNone(
cache.get(f"{ZAKEN_ROOT}:status:{self.status2['url']}")
)
Loading