From e47f13bb8b3161a5da892db4f550d7e23bc90d4f Mon Sep 17 00:00:00 2001 From: Harsh Raj Singhania Date: Tue, 13 Oct 2026 11:09:04 +0530 Subject: [PATCH 1/2] test: add unit tests verifying inlineRule is forwarded to search and multi-search --- tests/test_inline_rule.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/test_inline_rule.py diff --git a/tests/test_inline_rule.py b/tests/test_inline_rule.py new file mode 100644 index 00000000..612b8f60 --- /dev/null +++ b/tests/test_inline_rule.py @@ -0,0 +1,61 @@ +"""Unit tests for inlineRule support on search and multi-search. + +These tests verify that the parameter is correctly forwarded in the request +body. Full integration requires Meilisearch Enterprise + experimental feature. +""" + +from unittest.mock import MagicMock + +from meilisearch.client import Client +from meilisearch.index import Index + + +def test_search_passes_inline_rule(): + """inlineRule is included in the search request body.""" + config = MagicMock() + config.paths.index = "indexes" + config.paths.search = "search" + index = Index(config, "movies") + index.http = MagicMock() + index.http.post.return_value = {"hits": []} + + inline_rule = { + "uid": "user-likes-batman", + "actions": {"scale": [{"filter": "hero = batman", "weight": 1.2}]}, + } + index.search("super hero", opt_params={"inlineRule": inline_rule}) + + index.http.post.assert_called_once() + call_args = index.http.post.call_args + if call_args.kwargs and "body" in call_args.kwargs: + body = call_args.kwargs["body"] + else: + body = call_args.args[1] if len(call_args.args) > 1 else call_args[1].get("body") + assert body["q"] == "super hero" + assert body["inlineRule"] == inline_rule + + +def test_multi_search_passes_inline_rule(): + """inlineRule is included in multi-search query objects.""" + client = Client("http://localhost:7700", "masterKey") + client.http = MagicMock() + client.http.post.return_value = {"results": []} + + queries = [ + {"indexUid": "products", "q": "fashion"}, + { + "indexUid": "products", + "q": "fashion", + "inlineRule": {"uid": "special-sales"}, + }, + ] + client.multi_search(queries) + + client.http.post.assert_called_once() + call_args = client.http.post.call_args + if call_args.kwargs and "body" in call_args.kwargs: + body = call_args.kwargs["body"] + else: + body = call_args.args[1] if len(call_args.args) > 1 else call_args[1].get("body") + assert body["queries"] == queries + assert body["queries"][1]["inlineRule"]["uid"] == "special-sales" From eec73dcb02c59f06e0a9643b2ed0837ca9ad6262 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 14 Oct 2026 11:05:23 +0800 Subject: [PATCH 2/2] Replace inlineRule request mocks with Enterprise integration tests. Document the parameter and the Meili-Include-Metadata header on search and multi-search. --- meilisearch/client.py | 4 + meilisearch/index.py | 7 ++ tests/index/test_inline_rule_meilisearch.py | 116 ++++++++++++++++++++ tests/test_inline_rule.py | 61 ---------- 4 files changed, 127 insertions(+), 61 deletions(-) create mode 100644 tests/index/test_inline_rule_meilisearch.py delete mode 100644 tests/test_inline_rule.py diff --git a/meilisearch/client.py b/meilisearch/client.py index 32281a82..99646339 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -259,6 +259,10 @@ def multi_search( https://www.meilisearch.com/docs/reference/api/network Each query may include showPerformanceDetails to request a performance trace in the response (results[].performanceDetails, returned as raw data). + Each query may include inlineRule to preview a dynamic search rule + (Meilisearch Enterprise, dynamicSearchRules). Create the client with + custom_headers={"Meili-Include-Metadata": "true"} to include + metadata.inlineRuleConditionOutcomes on queries that send inlineRule. federation: (optional): Dictionary containing offset, limit, and optionally showPerformanceDetails for federated search (top-level performanceDetails in the response). diff --git a/meilisearch/index.py b/meilisearch/index.py index dd37b63d..a7a066e1 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -367,12 +367,19 @@ def search(self, query: str, opt_params: Mapping[str, Any] | None = None) -> dic - offset: Number of documents to skip - showPerformanceDetails: If true, the response includes a performanceDetails object (raw data; fields may change in future API versions) + - inlineRule: Dynamic search rule applied only to this query. + Requires Meilisearch Enterprise and the dynamicSearchRules + experimental feature. Create the client with + custom_headers={"Meili-Include-Metadata": "true"} to include + metadata.inlineRuleConditionOutcomes in the response. Returns ------- results: Dictionary with hits, offset, limit, processingTime and initial query. When showPerformanceDetails is true, may include a performanceDetails object. + When Meili-Include-Metadata is true, may include metadata, with + inlineRuleConditionOutcomes when the query sends inlineRule. Raises ------ diff --git a/tests/index/test_inline_rule_meilisearch.py b/tests/index/test_inline_rule_meilisearch.py new file mode 100644 index 00000000..db987006 --- /dev/null +++ b/tests/index/test_inline_rule_meilisearch.py @@ -0,0 +1,116 @@ +"""Tests for inline dynamic search rule preview on search and multi-search.""" + +import pytest + +import meilisearch +from tests import common + +pytestmark = pytest.mark.usefixtures("enable_dynamic_search_rules", "inline_rule_index") + +INDEX_UID = "inline_rule_preview" +TASK_TIMEOUT_MS = 30_000 + +DOCUMENTS = [ + { + "id": "1", + "title": "super hero", + "hero": "superman", + "color": "blue", + "category": "shirt", + "stock_intent": "keep", + }, + { + "id": "2", + "title": "super hero", + "hero": "batman", + "color": "yellow", + "category": "pants", + "stock_intent": "destock", + }, +] + + +@pytest.fixture +def inline_rule_index(client): + """Index with the attributes used by inline rule preview.""" + task = client.create_index(INDEX_UID, {"primaryKey": "id"}) + client.wait_for_task(task.task_uid, timeout_in_ms=TASK_TIMEOUT_MS) + index = client.index(INDEX_UID) + settings_task = index.update_filterable_attributes( + ["color", "category", "stock_intent", "hero"] + ) + index.wait_for_task(settings_task.task_uid, timeout_in_ms=TASK_TIMEOUT_MS) + documents_task = index.add_documents(DOCUMENTS) + index.wait_for_task(documents_task.task_uid, timeout_in_ms=TASK_TIMEOUT_MS) + return index + + +@pytest.fixture +def metadata_client(): + """Client that asks Meilisearch to include search metadata.""" + return meilisearch.Client( + common.BASE_URL, + common.MASTER_KEY, + custom_headers={"Meili-Include-Metadata": "true"}, + ) + + +def test_search_with_inline_rule_pins_document_and_returns_condition_outcomes(metadata_client): + """A satisfied inline pin rule reorders hits and reports condition outcomes.""" + response = metadata_client.index(INDEX_UID).search( + "super hero", + { + "inlineRule": { + "uid": "user-likes-batman", + "conditions": {"query": {"words": "hero"}}, + "actions": {"pin": [{"id": "2", "position": 0}]}, + } + }, + ) + + assert response["hits"][0]["id"] == "2" + metadata = response["metadata"] + assert isinstance(metadata["queryUid"], str) + assert metadata["indexUid"] == INDEX_UID + assert metadata["primaryKey"] == "id" + assert metadata["inlineRuleConditionOutcomes"] == { + "satisfiesActiveCondition": True, + "satisfiesQueryWordsCondition": "satisfied", + } + + +def test_multi_search_with_inline_rule_returns_condition_outcomes(metadata_client): + """Only the query that sends inlineRule includes condition outcomes.""" + query = { + "indexUid": INDEX_UID, + "q": "super hero", + "filter": "color = blue AND category = shirt", + } + response = metadata_client.multi_search( + [ + query, + { + **query, + "inlineRule": { + "uid": "special-sales", + "conditions": { + "query": {"words": "pants"}, + "filter": { + "values": { + "color": "yellow", + "stock_intent": "destock", + } + }, + }, + }, + }, + ] + ) + + assert response["results"][0]["hits"] == response["results"][1]["hits"] + assert "inlineRuleConditionOutcomes" not in response["results"][0].get("metadata", {}) + assert response["results"][1]["metadata"]["inlineRuleConditionOutcomes"] == { + "satisfiesActiveCondition": True, + "satisfiesQueryWordsCondition": {"missingWord": {"word": "pants"}}, + "satisfiesFilterCondition": {"unmetConstraintOnField": {"field": "color"}}, + } diff --git a/tests/test_inline_rule.py b/tests/test_inline_rule.py deleted file mode 100644 index 612b8f60..00000000 --- a/tests/test_inline_rule.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Unit tests for inlineRule support on search and multi-search. - -These tests verify that the parameter is correctly forwarded in the request -body. Full integration requires Meilisearch Enterprise + experimental feature. -""" - -from unittest.mock import MagicMock - -from meilisearch.client import Client -from meilisearch.index import Index - - -def test_search_passes_inline_rule(): - """inlineRule is included in the search request body.""" - config = MagicMock() - config.paths.index = "indexes" - config.paths.search = "search" - index = Index(config, "movies") - index.http = MagicMock() - index.http.post.return_value = {"hits": []} - - inline_rule = { - "uid": "user-likes-batman", - "actions": {"scale": [{"filter": "hero = batman", "weight": 1.2}]}, - } - index.search("super hero", opt_params={"inlineRule": inline_rule}) - - index.http.post.assert_called_once() - call_args = index.http.post.call_args - if call_args.kwargs and "body" in call_args.kwargs: - body = call_args.kwargs["body"] - else: - body = call_args.args[1] if len(call_args.args) > 1 else call_args[1].get("body") - assert body["q"] == "super hero" - assert body["inlineRule"] == inline_rule - - -def test_multi_search_passes_inline_rule(): - """inlineRule is included in multi-search query objects.""" - client = Client("http://localhost:7700", "masterKey") - client.http = MagicMock() - client.http.post.return_value = {"results": []} - - queries = [ - {"indexUid": "products", "q": "fashion"}, - { - "indexUid": "products", - "q": "fashion", - "inlineRule": {"uid": "special-sales"}, - }, - ] - client.multi_search(queries) - - client.http.post.assert_called_once() - call_args = client.http.post.call_args - if call_args.kwargs and "body" in call_args.kwargs: - body = call_args.kwargs["body"] - else: - body = call_args.args[1] if len(call_args.args) > 1 else call_args[1].get("body") - assert body["queries"] == queries - assert body["queries"][1]["inlineRule"]["uid"] == "special-sales"