perf(core): batch tenant summary providers
This commit is contained in:
237
tests/test_tenant_summary_batching.py
Normal file
237
tests/test_tenant_summary_batching.py
Normal file
@@ -0,0 +1,237 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from collections.abc import Mapping, Sequence
|
||||
from unittest.mock import patch
|
||||
|
||||
from govoplan_core.core.modules import ModuleManifest
|
||||
from govoplan_core.core.registry import PlatformRegistry
|
||||
from govoplan_core.tenancy.service import tenant_counts_many
|
||||
|
||||
|
||||
EXPECTED_EMPTY_COUNTS = {
|
||||
"users": 0,
|
||||
"active_users": 0,
|
||||
"groups": 0,
|
||||
"campaigns": 0,
|
||||
"files": 0,
|
||||
"api_keys": 0,
|
||||
"active_api_keys": 0,
|
||||
}
|
||||
|
||||
|
||||
class _BatchAccessAdministration:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[tuple[str, ...]] = []
|
||||
|
||||
def tenant_counts_many(
|
||||
self,
|
||||
session: object,
|
||||
tenant_ids: Sequence[str],
|
||||
) -> Mapping[str, Mapping[str, int]]:
|
||||
del session
|
||||
ids = tuple(tenant_ids)
|
||||
self.calls.append(ids)
|
||||
return {
|
||||
tenant_id: {
|
||||
"users": index + 1,
|
||||
"active_users": index + 1,
|
||||
"groups": 1,
|
||||
"api_keys": 0,
|
||||
"active_api_keys": 0,
|
||||
}
|
||||
for index, tenant_id in enumerate(ids)
|
||||
}
|
||||
|
||||
|
||||
class TenantSummaryBatchingTests(unittest.TestCase):
|
||||
def test_absent_providers_return_complete_zero_summaries(self) -> None:
|
||||
registry = PlatformRegistry()
|
||||
with (
|
||||
patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
|
||||
patch("govoplan_core.tenancy.service._access_administration", return_value=None),
|
||||
):
|
||||
result = tenant_counts_many(object(), ["tenant-1", "tenant-2"])
|
||||
|
||||
self.assertEqual(
|
||||
{
|
||||
"tenant-1": EXPECTED_EMPTY_COUNTS,
|
||||
"tenant-2": EXPECTED_EMPTY_COUNTS,
|
||||
},
|
||||
result,
|
||||
)
|
||||
|
||||
def test_legacy_single_tenant_provider_remains_supported(self) -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
|
||||
del session
|
||||
calls.append(tenant_id)
|
||||
return {"campaigns": 1}
|
||||
|
||||
registry = PlatformRegistry()
|
||||
registry.register(
|
||||
ModuleManifest(
|
||||
id="campaigns",
|
||||
name="Campaigns",
|
||||
version="test",
|
||||
tenant_summary_providers=(single_provider,),
|
||||
)
|
||||
)
|
||||
with (
|
||||
patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
|
||||
patch("govoplan_core.tenancy.service._access_administration", return_value=None),
|
||||
):
|
||||
result = tenant_counts_many(
|
||||
object(),
|
||||
["tenant-1", "tenant-2"],
|
||||
module_ids=("campaigns",),
|
||||
)
|
||||
|
||||
self.assertEqual(["tenant-1", "tenant-2"], calls)
|
||||
self.assertEqual(1, result["tenant-1"]["campaigns"])
|
||||
self.assertEqual(1, result["tenant-2"]["campaigns"])
|
||||
|
||||
def test_batch_provider_handles_single_item_and_partial_results(self) -> None:
|
||||
calls: list[tuple[str, ...]] = []
|
||||
|
||||
def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
|
||||
raise AssertionError(f"single provider should not run for {tenant_id}")
|
||||
|
||||
def batch_provider(
|
||||
session: object,
|
||||
tenant_ids: Sequence[str],
|
||||
) -> Mapping[str, Mapping[str, int]]:
|
||||
del session
|
||||
calls.append(tuple(tenant_ids))
|
||||
return {tenant_ids[0]: {"files": 7}}
|
||||
|
||||
registry = PlatformRegistry()
|
||||
registry.register(
|
||||
ModuleManifest(
|
||||
id="files",
|
||||
name="Files",
|
||||
version="test",
|
||||
tenant_summary_providers=(single_provider,),
|
||||
tenant_summary_batch_providers=(batch_provider,),
|
||||
)
|
||||
)
|
||||
with (
|
||||
patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
|
||||
patch("govoplan_core.tenancy.service._access_administration", return_value=None),
|
||||
):
|
||||
single = tenant_counts_many(object(), ["tenant-1"], module_ids=("files",))
|
||||
partial = tenant_counts_many(
|
||||
object(),
|
||||
["tenant-1", "tenant-2"],
|
||||
module_ids=("files",),
|
||||
)
|
||||
|
||||
self.assertEqual([("tenant-1",), ("tenant-1", "tenant-2")], calls)
|
||||
self.assertEqual(7, single["tenant-1"]["files"])
|
||||
self.assertEqual(7, partial["tenant-1"]["files"])
|
||||
self.assertEqual(0, partial["tenant-2"]["files"])
|
||||
|
||||
def test_batch_provider_failures_remain_visible(self) -> None:
|
||||
def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
|
||||
return {}
|
||||
|
||||
def failing_provider(
|
||||
session: object,
|
||||
tenant_ids: Sequence[str],
|
||||
) -> Mapping[str, Mapping[str, int]]:
|
||||
raise RuntimeError("summary unavailable")
|
||||
|
||||
registry = PlatformRegistry()
|
||||
registry.register(
|
||||
ModuleManifest(
|
||||
id="files",
|
||||
name="Files",
|
||||
version="test",
|
||||
tenant_summary_providers=(single_provider,),
|
||||
tenant_summary_batch_providers=(failing_provider,),
|
||||
)
|
||||
)
|
||||
with (
|
||||
patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
|
||||
patch("govoplan_core.tenancy.service._access_administration", return_value=None),
|
||||
self.assertRaisesRegex(RuntimeError, "summary unavailable"),
|
||||
):
|
||||
tenant_counts_many(object(), ["tenant-1"], module_ids=("files",))
|
||||
|
||||
def test_provider_calls_are_bounded_for_large_pages(self) -> None:
|
||||
provider_calls = {"campaigns": 0, "files": 0}
|
||||
|
||||
def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
|
||||
raise AssertionError(f"single provider should not run for {tenant_id}")
|
||||
|
||||
def batch_provider(module_id: str):
|
||||
def provide(
|
||||
session: object,
|
||||
tenant_ids: Sequence[str],
|
||||
) -> Mapping[str, Mapping[str, int]]:
|
||||
del session
|
||||
provider_calls[module_id] += 1
|
||||
return {tenant_id: {} for tenant_id in tenant_ids}
|
||||
|
||||
return provide
|
||||
|
||||
registry = PlatformRegistry()
|
||||
for module_id in ("campaigns", "files"):
|
||||
registry.register(
|
||||
ModuleManifest(
|
||||
id=module_id,
|
||||
name=module_id.title(),
|
||||
version="test",
|
||||
tenant_summary_providers=(single_provider,),
|
||||
tenant_summary_batch_providers=(batch_provider(module_id),),
|
||||
)
|
||||
)
|
||||
access = _BatchAccessAdministration()
|
||||
tenant_ids = [f"tenant-{index}" for index in range(100)]
|
||||
with (
|
||||
patch("govoplan_core.tenancy.service.get_registry", return_value=registry),
|
||||
patch("govoplan_core.tenancy.service._access_administration", return_value=access),
|
||||
):
|
||||
result = tenant_counts_many(
|
||||
object(),
|
||||
tenant_ids,
|
||||
module_ids=("campaigns", "files"),
|
||||
)
|
||||
|
||||
self.assertEqual({"campaigns": 1, "files": 1}, provider_calls)
|
||||
self.assertEqual([tuple(tenant_ids)], access.calls)
|
||||
self.assertEqual(100, len(result))
|
||||
self.assertEqual(100, result["tenant-99"]["users"])
|
||||
|
||||
def test_registry_replacement_carries_batch_providers(self) -> None:
|
||||
def single_provider(session: object, tenant_id: str) -> Mapping[str, int]:
|
||||
return {}
|
||||
|
||||
def batch_provider(
|
||||
session: object,
|
||||
tenant_ids: Sequence[str],
|
||||
) -> Mapping[str, Mapping[str, int]]:
|
||||
return {}
|
||||
|
||||
registry = PlatformRegistry()
|
||||
registry.replace(
|
||||
[
|
||||
ModuleManifest(
|
||||
id="files",
|
||||
name="Files",
|
||||
version="test",
|
||||
tenant_summary_providers=(single_provider,),
|
||||
tenant_summary_batch_providers=(batch_provider,),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
self.assertIs(
|
||||
batch_provider,
|
||||
registry.tenant_summary_batch_providers()["files"],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user