From 26750c1988104ca0462029867ba90d97646b3c86 Mon Sep 17 00:00:00 2001 From: Vchen7629 Date: Tue, 9 Jun 2026 23:27:51 -0700 Subject: [PATCH 1/9] fix: compute active_source_count for knowledge filters from indexed documents --- src/services/knowledge_filter_service.py | 45 ++++++++++++++++++++++++ src/utils/opensearch_queries.py | 20 ++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/services/knowledge_filter_service.py b/src/services/knowledge_filter_service.py index 065e97159..b73ab79af 100644 --- a/src/services/knowledge_filter_service.py +++ b/src/services/knowledge_filter_service.py @@ -102,6 +102,51 @@ async def search_knowledge_filters( knowledge_filter["score"] = hit.get("_score") filters.append(knowledge_filter) + # compute active_source_count field: how many of each filter's configured + # data sources still have indexed documents. Filters scoped to "*" are skipped + # We use the admin client to check existence in the documents index so the count is the + # same for every viewer of a shared filter, not DLS-scoped per user + try: + import json + import logging + + from config.settings import clients, get_index_name + from utils.opensearch_queries import build_existing_filenames_agg_body + + data_sources_by_filter = [] + all_filenames = set() + for knowledge_filter in filters: + data_sources = json.loads(knowledge_filter.get("query_data") or "{}").get("filters", {}).get( + "data_sources" + ) + if not data_sources or data_sources == ["*"]: + data_sources_by_filter.append(None) + continue + data_sources_by_filter.append(data_sources) + all_filenames.update(data_sources) + + existing_filenames = set() + if all_filenames and clients.opensearch is not None: + existence_result = await clients.opensearch.search( + index=get_index_name(), + body=build_existing_filenames_agg_body(list(all_filenames)), + ) + existing_filenames = { + bucket["key"] + for bucket in existence_result["aggregations"]["filenames"]["buckets"] + } + + for knowledge_filter, data_sources in zip(filters, data_sources_by_filter): + if data_sources: + knowledge_filter["active_source_count"] = sum( + 1 for source in data_sources if source in existing_filenames + ) + + except Exception: + logging.getLogger(__name__).warning( + "active_source_count computation failed", exc_info=True + ) + return {"success": True, "filters": filters} except Exception as e: diff --git a/src/utils/opensearch_queries.py b/src/utils/opensearch_queries.py index eadafd376..07391efff 100644 --- a/src/utils/opensearch_queries.py +++ b/src/utils/opensearch_queries.py @@ -30,16 +30,28 @@ def build_filename_search_body(filename: str, size: int = 1, source: Union[bool, size: Number of results to return (default: 1) source: Whether to include source fields, or list of specific fields to include (default: False) + Returns: + A dict containing the complete OpenSearch search body + """ + return {"query": build_filename_query(filename), "size": size, "_source": source} + + +def build_existing_filenames_agg_body(filenames: list[str]) -> dict: + """ + build a search body that for checking which of the given filenames currently have one indexed chunk + + Args: + filenames: Filenames to check for existance + Returns: A dict containing the complete OpenSearch search body """ return { - "query": build_filename_query(filename), - "size": size, - "_source": source + "query": {"terms": {"filename": filenames}}, + "size": 0, + "aggs": {"filenames": {"terms": {"field": "filename", "size": len(filenames)}}}, } - def build_filename_delete_body(filename: str) -> dict: """ Build a delete-by-query body for removing all documents with a filename. From ed407e3cc95c1a53f04bab9c588eb6160629c8d1 Mon Sep 17 00:00:00 2001 From: Vchen7629 Date: Tue, 9 Jun 2026 23:28:24 -0700 Subject: [PATCH 2/9] test: added unit tests for search_knowledge_filters changes --- tests/unit/test_knowledge_filter_service.py | 189 ++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/unit/test_knowledge_filter_service.py diff --git a/tests/unit/test_knowledge_filter_service.py b/tests/unit/test_knowledge_filter_service.py new file mode 100644 index 000000000..75b991bd7 --- /dev/null +++ b/tests/unit/test_knowledge_filter_service.py @@ -0,0 +1,189 @@ +from types import SimpleNamespace + +import json +import pytest + +from services.knowledge_filter_service import ( + KNOWLEDGE_FILTERS_INDEX_NAME, + KnowledgeFilterService, +) + +class _Indices: + async def refresh(self, index): + return {"acknowledged": True, "index": index} + + +def _filter(filter_id, data_sources=None, query_data=None): + if query_data is None: + query_data = json.dumps({"filters": {"data_sources": data_sources}}) if data_sources else "{}" + return {"id": filter_id, "name": filter_id, "query_data": query_data} + + +def _setup_search(monkeypatch, filters, existing_filenames): + """Service whose user client returns `filters` from search, and whose + admin client's existence-check aggregation returns `existing_filenames`. + """ + + async def user_search(*, index, body): + return {"hits": {"hits": [{"_source": f, "_score": 1.0} for f in filters]}} + + admin_client = SimpleNamespace(search_calls=[]) + + async def admin_search(*, index, body): + admin_client.search_calls.append(body) + return { + "aggregations": { + "filenames": {"buckets": [{"key": name} for name in existing_filenames]} + } + } + + admin_client.search = admin_search + + class SessionManager: + def get_user_opensearch_client(self, user_id, jwt_token): + return SimpleNamespace(search=user_search) + + monkeypatch.setattr("config.settings.clients", SimpleNamespace(opensearch=admin_client)) + monkeypatch.setattr("config.settings.get_index_name", lambda: "documents") + + return KnowledgeFilterService(SessionManager()), admin_client + + +@pytest.mark.asyncio +async def test_knowledge_filter_writes_use_admin_client_after_user_visibility_check( + monkeypatch, +): + user_client = SimpleNamespace(get_calls=[], write_calls=[]) + admin_client = SimpleNamespace( + index_calls=[], + update_calls=[], + delete_calls=[], + indices=_Indices(), + ) + + filter_doc = { + "id": "filter-1", + "name": "Test filter", + "owner": "user-1", + "query_data": "{}", + } + stored_doc = dict(filter_doc) + + async def get(*, index, id): + user_client.get_calls.append({"index": index, "id": id}) + return {"found": True, "_source": dict(stored_doc)} + + async def user_index(**kwargs): + user_client.write_calls.append(("index", kwargs)) + + async def user_update(**kwargs): + user_client.write_calls.append(("update", kwargs)) + + async def user_delete(**kwargs): + user_client.write_calls.append(("delete", kwargs)) + + async def admin_index(**kwargs): + admin_client.index_calls.append(kwargs) + stored_doc.update(kwargs["body"]) + return {"result": "created"} + + async def admin_update(**kwargs): + admin_client.update_calls.append(kwargs) + stored_doc.update(kwargs["body"]["doc"]) + return {"result": "updated"} + + async def admin_delete(**kwargs): + admin_client.delete_calls.append(kwargs) + return {"result": "deleted"} + + user_client.get = get + user_client.index = user_index + user_client.update = user_update + user_client.delete = user_delete + admin_client.index = admin_index + admin_client.update = admin_update + admin_client.delete = admin_delete + + class SessionManager: + def get_user_opensearch_client(self, user_id, jwt_token): + assert user_id == "user-1" + assert jwt_token == "Bearer user-token" + return user_client + + monkeypatch.setattr( + "config.settings.clients", + SimpleNamespace(opensearch=admin_client), + ) + + service = KnowledgeFilterService(SessionManager()) + + created = await service.create_knowledge_filter( + filter_doc, user_id="user-1", jwt_token="Bearer user-token" + ) + updated = await service.update_knowledge_filter( + "filter-1", + {"description": "Updated"}, + user_id="user-1", + jwt_token="Bearer user-token", + ) + deleted = await service.delete_knowledge_filter( + "filter-1", user_id="user-1", jwt_token="Bearer user-token" + ) + + assert created["success"] is True + assert updated["success"] is True + assert deleted["success"] is True + assert admin_client.index_calls[0]["index"] == KNOWLEDGE_FILTERS_INDEX_NAME + assert admin_client.update_calls[0]["index"] == KNOWLEDGE_FILTERS_INDEX_NAME + assert admin_client.delete_calls[0]["index"] == KNOWLEDGE_FILTERS_INDEX_NAME + assert user_client.write_calls == [] + assert user_client.get_calls == [ + {"index": KNOWLEDGE_FILTERS_INDEX_NAME, "id": "filter-1"}, + {"index": KNOWLEDGE_FILTERS_INDEX_NAME, "id": "filter-1"}, + {"index": KNOWLEDGE_FILTERS_INDEX_NAME, "id": "filter-1"}, + ] + +@pytest.mark.asyncio +async def test_search_knowledge_filters_active_source_count_zero_when_document_deleted( + monkeypatch, +): + filters = [_filter("filter-1", data_sources=["README.md"])] + service, admin_client = _setup_search(monkeypatch, filters, existing_filenames=set()) + + result = await service.search_knowledge_filters("", user_id="user-1", jwt_token="token") + + assert result["success"] is True + assert result["filters"][0]["active_source_count"] == 0 + assert len(admin_client.search_calls) == 1 + + +@pytest.mark.asyncio +async def test_search_knowledge_filters_malformed_query_data_fails_silently(monkeypatch): + filters = [ + _filter("filter-1", query_data="not json"), + _filter("filter-2", data_sources=["a.md"]), + ] + service, _ = _setup_search(monkeypatch, filters, existing_filenames={"a.md"}) + + result = await service.search_knowledge_filters("", user_id="user-1", jwt_token="token") + + assert result["success"] is True + assert len(result["filters"]) == 2 + for knowledge_filter in result["filters"]: + assert "active_source_count" not in knowledge_filter + + +@pytest.mark.asyncio +async def test_search_knowledge_filters_dedups_shared_filenames_in_one_query(monkeypatch): + filters = [ + _filter("filter-1", data_sources=["shared.pdf"]), + _filter("filter-2", data_sources=["shared.pdf"]), + ] + service, admin_client = _setup_search(monkeypatch, filters, existing_filenames={"shared.pdf"}) + + result = await service.search_knowledge_filters("", user_id="user-1", jwt_token="token") + + assert len(admin_client.search_calls) == 1 + assert admin_client.search_calls[0]["query"]["terms"]["filename"] == ["shared.pdf"] + assert result["filters"][0]["active_source_count"] == 1 + assert result["filters"][1]["active_source_count"] == 1 From f61c0cd35f8e7f130d2d0f60958820a113d71b1d Mon Sep 17 00:00:00 2001 From: Vchen7629 Date: Tue, 9 Jun 2026 23:29:55 -0700 Subject: [PATCH 3/9] fix: use active_source_count from backend for knowledge filter list source count --- frontend/app/api/queries/useGetFiltersSearchQuery.ts | 1 + frontend/components/knowledge-filter-list.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/app/api/queries/useGetFiltersSearchQuery.ts b/frontend/app/api/queries/useGetFiltersSearchQuery.ts index 26c110f53..f85f948fc 100644 --- a/frontend/app/api/queries/useGetFiltersSearchQuery.ts +++ b/frontend/app/api/queries/useGetFiltersSearchQuery.ts @@ -12,6 +12,7 @@ export interface KnowledgeFilter { owner: string; created_at: string; updated_at: string; + active_source_count?: number; } export const useGetFiltersSearchQuery = ( diff --git a/frontend/components/knowledge-filter-list.tsx b/frontend/components/knowledge-filter-list.tsx index 455cf399d..923d70519 100644 --- a/frontend/components/knowledge-filter-list.tsx +++ b/frontend/components/knowledge-filter-list.tsx @@ -155,7 +155,8 @@ export function KnowledgeFilterList({ const dataSources = parseQueryData(filter.query_data) .filters.data_sources; if (dataSources[0] === "*") return "All sources"; - const count = dataSources.length; + const count = + filter.active_source_count ?? dataSources.length; return `${count} ${ count === 1 ? "source" : "sources" }`; From 3200d33be2606e0f64c70fe9ce967feb361e6791 Mon Sep 17 00:00:00 2001 From: Vchen7629 Date: Tue, 9 Jun 2026 23:30:23 -0700 Subject: [PATCH 4/9] fix: show accurate source count in knowledge filter panel dropdown --- frontend/components/knowledge-filter-panel.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/components/knowledge-filter-panel.tsx b/frontend/components/knowledge-filter-panel.tsx index 03957e997..149bbc7bb 100644 --- a/frontend/components/knowledge-filter-panel.tsx +++ b/frontend/components/knowledge-filter-panel.tsx @@ -356,7 +356,13 @@ export function KnowledgeFilterPanel() {
+ sourceOptions.some((option) => option.value === source), + ) + } onValueChange={(values) => handleFilterChange("data_sources", values) } From a5850f2c4eee784d30c2ae2e5be8ce9dc2286636 Mon Sep 17 00:00:00 2001 From: vchen7629 Date: Wed, 10 Jun 2026 10:45:56 -0700 Subject: [PATCH 5/9] fix: checked grammar issues in docstring --- src/utils/opensearch_queries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/opensearch_queries.py b/src/utils/opensearch_queries.py index 07391efff..d6761f75e 100644 --- a/src/utils/opensearch_queries.py +++ b/src/utils/opensearch_queries.py @@ -38,10 +38,10 @@ def build_filename_search_body(filename: str, size: int = 1, source: Union[bool, def build_existing_filenames_agg_body(filenames: list[str]) -> dict: """ - build a search body that for checking which of the given filenames currently have one indexed chunk + build a search body for checking which of the given filenames currently have indexed chunks Args: - filenames: Filenames to check for existance + filenames: Filenames to check for existence Returns: A dict containing the complete OpenSearch search body From 592678a7d1053a7c58a804754a6e0e779532ec49 Mon Sep 17 00:00:00 2001 From: vchen7629 Date: Wed, 10 Jun 2026 10:47:23 -0700 Subject: [PATCH 6/9] fix: wrapped data_sources in try catch to prevent one malformed filter from crashing rest --- src/services/knowledge_filter_service.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/services/knowledge_filter_service.py b/src/services/knowledge_filter_service.py index b73ab79af..63c133a20 100644 --- a/src/services/knowledge_filter_service.py +++ b/src/services/knowledge_filter_service.py @@ -108,17 +108,22 @@ async def search_knowledge_filters( # same for every viewer of a shared filter, not DLS-scoped per user try: import json - import logging from config.settings import clients, get_index_name from utils.opensearch_queries import build_existing_filenames_agg_body + from utils.logging_config import get_logger data_sources_by_filter = [] all_filenames = set() for knowledge_filter in filters: - data_sources = json.loads(knowledge_filter.get("query_data") or "{}").get("filters", {}).get( - "data_sources" - ) + try: + data_sources = json.loads(knowledge_filter.get("query_data") or "{}").get("filters", {}).get( + "data_sources" + ) + except Exception: + data_sources_by_filter.append(None) + continue + if not data_sources or data_sources == ["*"]: data_sources_by_filter.append(None) continue @@ -143,7 +148,7 @@ async def search_knowledge_filters( ) except Exception: - logging.getLogger(__name__).warning( + get_logger(__name__).warning( "active_source_count computation failed", exc_info=True ) From 6343fc5ecdcd30806b80df14cc9d7404497387d0 Mon Sep 17 00:00:00 2001 From: vchen7629 Date: Wed, 10 Jun 2026 10:47:51 -0700 Subject: [PATCH 7/9] test: updated search_knowledge_filters test to catch malformed filter --- tests/unit/test_knowledge_filter_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_knowledge_filter_service.py b/tests/unit/test_knowledge_filter_service.py index 75b991bd7..3eb600abc 100644 --- a/tests/unit/test_knowledge_filter_service.py +++ b/tests/unit/test_knowledge_filter_service.py @@ -169,8 +169,8 @@ async def test_search_knowledge_filters_malformed_query_data_fails_silently(monk assert result["success"] is True assert len(result["filters"]) == 2 - for knowledge_filter in result["filters"]: - assert "active_source_count" not in knowledge_filter + assert "active_source_count" not in result["filters"][0] # malformed filter + assert result["filters"][1]["active_source_count"] == 1 # valid filter @pytest.mark.asyncio From eb8d87af7dd906fcb3e2df185c353cace89eaa5f Mon Sep 17 00:00:00 2001 From: Vchen7629 Date: Wed, 10 Jun 2026 21:52:10 -0700 Subject: [PATCH 8/9] refactor: extracted attach active source count logic into a seperate private helper to keep search_knowledge_filters method focused --- src/services/knowledge_filter_service.py | 115 +++++++++++++---------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/src/services/knowledge_filter_service.py b/src/services/knowledge_filter_service.py index 63c133a20..a1a827a08 100644 --- a/src/services/knowledge_filter_service.py +++ b/src/services/knowledge_filter_service.py @@ -7,6 +7,71 @@ class KnowledgeFilterService: def __init__(self, session_manager=None): self.session_manager = session_manager + def _user_client(self, user_id: str = None, jwt_token: str = None): + return self.session_manager.get_user_opensearch_client(user_id, jwt_token) + + def _write_client(self, user_id: str = None, jwt_token: str = None): + # OpenSearch rejects write requests on indices protected by filter-level + # DLS. The app enforces ownership/visibility with the scoped client, then + # performs trusted writes with the admin client. + from config.settings import clients + + if clients.opensearch is None: + raise RuntimeError("Backend OpenSearch write client is unavailable") + return clients.opensearch + + async def _attach_active_source_counts(self, filters: list[dict[str, Any]]) -> None: + """Annotate each filter with active_source_count (mutates filters in place). + + Counts how many of each filter's configured data sources still have indexed + documents, via a single batched terms aggregation against the documents index + using the admin client (so the count is the same for every viewer of a shared + filter, not DLS-scoped per user). Filters scoped to "*" are skipped. + """ + try: + import json + + from config.settings import clients, get_index_name + from utils.logging_config import get_logger + from utils.opensearch_queries import build_existing_filenames_agg_body + + data_sources_by_filter = [] + all_filenames = set() + for knowledge_filter in filters: + try: + data_sources = json.loads(knowledge_filter.get("query_data") or "{}").get("filters", {}).get( + "data_sources" + ) + except Exception: + data_sources_by_filter.append(None) + continue + + if not data_sources or data_sources == ["*"]: + data_sources_by_filter.append(None) + continue + data_sources_by_filter.append(data_sources) + all_filenames.update(data_sources) + + if not all_filenames or clients.opensearch is None: + return + + existence_result = await clients.opensearch.search( + index=get_index_name(), + body=build_existing_filenames_agg_body(list(all_filenames)), + ) + existing_filenames = { + bucket["key"] + for bucket in existence_result["aggregations"]["filenames"]["buckets"] + } + + for knowledge_filter, data_sources in zip(filters, data_sources_by_filter): + if data_sources: + knowledge_filter["active_source_count"] = sum( + 1 for source in data_sources if source in existing_filenames + ) + except Exception: + get_logger(__name__).warning("active_source_count computation failed", exc_info=True) + async def create_knowledge_filter( self, filter_doc: Dict[str, Any], user_id: str = None, jwt_token: str = None ) -> Dict[str, Any]: @@ -102,55 +167,7 @@ async def search_knowledge_filters( knowledge_filter["score"] = hit.get("_score") filters.append(knowledge_filter) - # compute active_source_count field: how many of each filter's configured - # data sources still have indexed documents. Filters scoped to "*" are skipped - # We use the admin client to check existence in the documents index so the count is the - # same for every viewer of a shared filter, not DLS-scoped per user - try: - import json - - from config.settings import clients, get_index_name - from utils.opensearch_queries import build_existing_filenames_agg_body - from utils.logging_config import get_logger - - data_sources_by_filter = [] - all_filenames = set() - for knowledge_filter in filters: - try: - data_sources = json.loads(knowledge_filter.get("query_data") or "{}").get("filters", {}).get( - "data_sources" - ) - except Exception: - data_sources_by_filter.append(None) - continue - - if not data_sources or data_sources == ["*"]: - data_sources_by_filter.append(None) - continue - data_sources_by_filter.append(data_sources) - all_filenames.update(data_sources) - - existing_filenames = set() - if all_filenames and clients.opensearch is not None: - existence_result = await clients.opensearch.search( - index=get_index_name(), - body=build_existing_filenames_agg_body(list(all_filenames)), - ) - existing_filenames = { - bucket["key"] - for bucket in existence_result["aggregations"]["filenames"]["buckets"] - } - - for knowledge_filter, data_sources in zip(filters, data_sources_by_filter): - if data_sources: - knowledge_filter["active_source_count"] = sum( - 1 for source in data_sources if source in existing_filenames - ) - - except Exception: - get_logger(__name__).warning( - "active_source_count computation failed", exc_info=True - ) + await self._attach_active_source_counts(filters) return {"success": True, "filters": filters} From 3126e8ddde5298f3b9151ea911726d218b955723 Mon Sep 17 00:00:00 2001 From: vchen7629 Date: Mon, 22 Jun 2026 10:32:58 -0700 Subject: [PATCH 9/9] test: removed unused test in test_knowledge_filter_service.py --- tests/unit/test_knowledge_filter_service.py | 94 --------------------- 1 file changed, 94 deletions(-) diff --git a/tests/unit/test_knowledge_filter_service.py b/tests/unit/test_knowledge_filter_service.py index 3eb600abc..1181e1b3a 100644 --- a/tests/unit/test_knowledge_filter_service.py +++ b/tests/unit/test_knowledge_filter_service.py @@ -49,100 +49,6 @@ def get_user_opensearch_client(self, user_id, jwt_token): return KnowledgeFilterService(SessionManager()), admin_client -@pytest.mark.asyncio -async def test_knowledge_filter_writes_use_admin_client_after_user_visibility_check( - monkeypatch, -): - user_client = SimpleNamespace(get_calls=[], write_calls=[]) - admin_client = SimpleNamespace( - index_calls=[], - update_calls=[], - delete_calls=[], - indices=_Indices(), - ) - - filter_doc = { - "id": "filter-1", - "name": "Test filter", - "owner": "user-1", - "query_data": "{}", - } - stored_doc = dict(filter_doc) - - async def get(*, index, id): - user_client.get_calls.append({"index": index, "id": id}) - return {"found": True, "_source": dict(stored_doc)} - - async def user_index(**kwargs): - user_client.write_calls.append(("index", kwargs)) - - async def user_update(**kwargs): - user_client.write_calls.append(("update", kwargs)) - - async def user_delete(**kwargs): - user_client.write_calls.append(("delete", kwargs)) - - async def admin_index(**kwargs): - admin_client.index_calls.append(kwargs) - stored_doc.update(kwargs["body"]) - return {"result": "created"} - - async def admin_update(**kwargs): - admin_client.update_calls.append(kwargs) - stored_doc.update(kwargs["body"]["doc"]) - return {"result": "updated"} - - async def admin_delete(**kwargs): - admin_client.delete_calls.append(kwargs) - return {"result": "deleted"} - - user_client.get = get - user_client.index = user_index - user_client.update = user_update - user_client.delete = user_delete - admin_client.index = admin_index - admin_client.update = admin_update - admin_client.delete = admin_delete - - class SessionManager: - def get_user_opensearch_client(self, user_id, jwt_token): - assert user_id == "user-1" - assert jwt_token == "Bearer user-token" - return user_client - - monkeypatch.setattr( - "config.settings.clients", - SimpleNamespace(opensearch=admin_client), - ) - - service = KnowledgeFilterService(SessionManager()) - - created = await service.create_knowledge_filter( - filter_doc, user_id="user-1", jwt_token="Bearer user-token" - ) - updated = await service.update_knowledge_filter( - "filter-1", - {"description": "Updated"}, - user_id="user-1", - jwt_token="Bearer user-token", - ) - deleted = await service.delete_knowledge_filter( - "filter-1", user_id="user-1", jwt_token="Bearer user-token" - ) - - assert created["success"] is True - assert updated["success"] is True - assert deleted["success"] is True - assert admin_client.index_calls[0]["index"] == KNOWLEDGE_FILTERS_INDEX_NAME - assert admin_client.update_calls[0]["index"] == KNOWLEDGE_FILTERS_INDEX_NAME - assert admin_client.delete_calls[0]["index"] == KNOWLEDGE_FILTERS_INDEX_NAME - assert user_client.write_calls == [] - assert user_client.get_calls == [ - {"index": KNOWLEDGE_FILTERS_INDEX_NAME, "id": "filter-1"}, - {"index": KNOWLEDGE_FILTERS_INDEX_NAME, "id": "filter-1"}, - {"index": KNOWLEDGE_FILTERS_INDEX_NAME, "id": "filter-1"}, - ] - @pytest.mark.asyncio async def test_search_knowledge_filters_active_source_count_zero_when_document_deleted( monkeypatch,