|
1 | | -"""Tests for experimental features API.""" |
| 1 | +# pylint: disable=redefined-outer-name |
| 2 | +"""Tests for indexingFragments and searchFragments in embedders (multimodal feature). |
2 | 3 |
|
| 4 | +These tests validate CONFIGURATION ONLY, not AI functionality. |
| 5 | +They only ensure fragments can be configured and stored in Meilisearch. |
| 6 | +No AI calls or document indexing/searching occurs. |
| 7 | +""" |
3 | 8 |
|
4 | | -def test_get_experimental_features(client): |
5 | | - """Test getting experimental features.""" |
6 | | - response = client.get_experimental_features() |
7 | | - assert isinstance(response, dict) |
8 | | - assert "multimodal" in response or "vectorStoreSetting" in response |
| 9 | +import pytest |
9 | 10 |
|
| 11 | +DUMMY_URL = "http://localhost:8000/embed" |
| 12 | +TEST_MODEL = "test-model" |
| 13 | +MULTIMODAL_MODEL = "multimodal" |
10 | 14 |
|
11 | | -def test_update_experimental_features(client): |
12 | | - """Test updating experimental features.""" |
13 | | - # Enable multimodal |
14 | | - response = client.update_experimental_features({"multimodal": True}) |
15 | | - assert isinstance(response, dict) |
16 | | - assert response.get("multimodal") is True |
17 | 15 |
|
18 | | - # Disable multimodal |
19 | | - response = client.update_experimental_features({"multimodal": False}) |
20 | | - assert isinstance(response, dict) |
21 | | - assert response.get("multimodal") is False |
| 16 | +def apply_embedders(index, config): |
| 17 | + """Helper to update embedders and wait for task completion.""" |
| 18 | + response = index.update_embedders(config) |
| 19 | + update = index.wait_for_task(response.task_uid) |
| 20 | + assert update.status == "succeeded" |
| 21 | + return index.get_embedders() |
22 | 22 |
|
23 | 23 |
|
24 | | -def test_enable_multimodal(client): |
25 | | - """Test enabling multimodal experimental feature.""" |
26 | | - response = client.enable_multimodal() |
27 | | - assert isinstance(response, dict) |
28 | | - assert response.get("multimodal") is True |
| 24 | +def test_rest_embedder_with_fragments(empty_index, multimodal_enabled): |
| 25 | + """Tests that REST embedder can be configured with indexingFragments and searchFragments.""" |
| 26 | + index = empty_index() |
29 | 27 |
|
30 | | - # Verify it's enabled |
31 | | - features = client.get_experimental_features() |
32 | | - assert features.get("multimodal") is True |
| 28 | + config = { |
| 29 | + "rest_fragments": { |
| 30 | + "source": "rest", |
| 31 | + "url": DUMMY_URL, |
| 32 | + "apiKey": "test-key", |
| 33 | + "dimensions": 512, |
| 34 | + "indexingFragments": {"text": {"value": "{{doc.title}} - {{doc.description}}"}}, |
| 35 | + "searchFragments": {"text": {"value": "{{fragment}}"}}, |
| 36 | + "request": {"input": ["{{fragment}}"], "model": TEST_MODEL}, |
| 37 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 38 | + "headers": {"Authorization": "Bearer test-key"}, |
| 39 | + } |
| 40 | + } |
33 | 41 |
|
| 42 | + embedders = apply_embedders(index, config) |
34 | 43 |
|
35 | | -def test_disable_multimodal(client): |
36 | | - """Test disabling multimodal experimental feature.""" |
37 | | - # First enable it |
38 | | - client.enable_multimodal() |
| 44 | + e = embedders.embedders["rest_fragments"] |
| 45 | + assert e.source == "rest" |
| 46 | + assert e.url == DUMMY_URL |
| 47 | + assert e.dimensions == 512 |
| 48 | + assert e.indexing_fragments is not None |
| 49 | + assert e.search_fragments is not None |
39 | 50 |
|
40 | | - # Then disable it |
41 | | - response = client.disable_multimodal() |
42 | | - assert isinstance(response, dict) |
43 | | - assert response.get("multimodal") is False |
44 | 51 |
|
45 | | - # Verify it's disabled |
46 | | - features = client.get_experimental_features() |
47 | | - assert features.get("multimodal") is False |
| 52 | +def test_rest_embedder_with_multiple_fragments(empty_index, multimodal_enabled): |
| 53 | + """Tests REST embedder with multiple fragment types.""" |
| 54 | + index = empty_index() |
48 | 55 |
|
| 56 | + config = { |
| 57 | + "multi_fragments": { |
| 58 | + "source": "rest", |
| 59 | + "url": DUMMY_URL, |
| 60 | + "dimensions": 1024, |
| 61 | + "indexingFragments": { |
| 62 | + "text": {"value": "{{doc.title}}"}, |
| 63 | + "description": {"value": "{{doc.overview}}"} |
| 64 | + }, |
| 65 | + "searchFragments": { |
| 66 | + "text": {"value": "{{fragment}}"}, |
| 67 | + "description": {"value": "{{fragment}}"} |
| 68 | + }, |
| 69 | + "request": {"input": ["{{fragment}}"], "model": TEST_MODEL}, |
| 70 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 71 | + } |
| 72 | + } |
49 | 73 |
|
50 | | -def test_update_multiple_experimental_features(client): |
51 | | - """Test updating multiple experimental features at once.""" |
52 | | - response = client.update_experimental_features({"multimodal": True, "vectorStoreSetting": True}) |
53 | | - assert isinstance(response, dict) |
54 | | - # At least one should be accepted (depending on Meilisearch version) |
55 | | - assert "multimodal" in response or "vectorStoreSetting" in response |
| 74 | + embedders = apply_embedders(index, config) |
| 75 | + |
| 76 | + e = embedders.embedders["multi_fragments"] |
| 77 | + assert e.source == "rest" |
| 78 | + assert len(e.indexing_fragments) >= 1 |
| 79 | + assert len(e.search_fragments) >= 1 |
| 80 | + |
| 81 | + |
| 82 | +def test_fragments_without_document_template(empty_index, multimodal_enabled): |
| 83 | + """Tests fragments can be used without documentTemplate.""" |
| 84 | + index = empty_index() |
| 85 | + |
| 86 | + config = { |
| 87 | + "fragments_only": { |
| 88 | + "source": "rest", |
| 89 | + "url": DUMMY_URL, |
| 90 | + "dimensions": 512, |
| 91 | + "indexingFragments": {"text": {"value": "{{doc.content}}"}}, |
| 92 | + "searchFragments": {"text": {"value": "{{fragment}}"}}, |
| 93 | + "request": {"input": ["{{fragment}}"], "model": TEST_MODEL}, |
| 94 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + embedders = apply_embedders(index, config) |
| 99 | + e = embedders.embedders["fragments_only"] |
| 100 | + assert e.document_template is None |
| 101 | + assert e.indexing_fragments is not None |
| 102 | + assert e.search_fragments is not None |
| 103 | + |
| 104 | + |
| 105 | +def test_fragments_require_multimodal_feature(empty_index): |
| 106 | + """Tests fragments require multimodal feature enabled.""" |
| 107 | + index = empty_index() |
| 108 | + |
| 109 | + config = { |
| 110 | + "test": { |
| 111 | + "source": "rest", |
| 112 | + "url": DUMMY_URL, |
| 113 | + "dimensions": 512, |
| 114 | + "indexingFragments": {"text": {"value": "{{doc.title}}"}}, |
| 115 | + "searchFragments": {"text": {"value": "{{fragment}}"}}, |
| 116 | + "request": {"input": ["{{fragment}}"], "model": TEST_MODEL}, |
| 117 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + # May succeed or fail depending on server config; both are acceptable |
| 122 | + try: |
| 123 | + embedders = apply_embedders(index, config) |
| 124 | + assert embedders.embedders["test"].indexing_fragments is not None |
| 125 | + except Exception: |
| 126 | + pass |
| 127 | + |
| 128 | + |
| 129 | +def test_update_fragments_separately(empty_index, multimodal_enabled): |
| 130 | + """Tests updating indexingFragments and searchFragments separately.""" |
| 131 | + index = empty_index() |
| 132 | + |
| 133 | + initial_config = { |
| 134 | + "updatable": { |
| 135 | + "source": "rest", |
| 136 | + "url": DUMMY_URL, |
| 137 | + "dimensions": 512, |
| 138 | + "indexingFragments": {"text": {"value": "{{doc.title}}"}}, |
| 139 | + "searchFragments": {"text": {"value": "{{fragment}}"}}, |
| 140 | + "request": {"input": ["{{fragment}}"], "model": TEST_MODEL}, |
| 141 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + apply_embedders(index, initial_config) |
| 146 | + |
| 147 | + updated_config = { |
| 148 | + "updatable": { |
| 149 | + "source": "rest", |
| 150 | + "url": DUMMY_URL, |
| 151 | + "dimensions": 512, |
| 152 | + "indexingFragments": {"text": {"value": "{{doc.title}} - {{doc.description}}"}}, |
| 153 | + "searchFragments": {"text": {"value": "{{fragment}}"}}, |
| 154 | + "request": {"input": ["{{fragment}}"], "model": TEST_MODEL}, |
| 155 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + embedders = apply_embedders(index, updated_config) |
| 160 | + assert embedders.embedders["updatable"].indexing_fragments is not None |
| 161 | + |
| 162 | + |
| 163 | +def test_profile_picture_and_title_fragments(empty_index, multimodal_enabled): |
| 164 | + """Tests real-world use case: user profiles with picture and title.""" |
| 165 | + index = empty_index() |
| 166 | + |
| 167 | + config = { |
| 168 | + "user_profile": { |
| 169 | + "source": "rest", |
| 170 | + "url": DUMMY_URL, |
| 171 | + "dimensions": 768, |
| 172 | + "indexingFragments": { |
| 173 | + "user_name": {"value": "{{doc.name}}"}, |
| 174 | + "avatar": {"value": "{{doc.profile_picture_url}}"}, |
| 175 | + "biography": {"value": "{{doc.bio}}"}, |
| 176 | + }, |
| 177 | + "searchFragments": { |
| 178 | + "user_name": {"value": "{{fragment}}"}, |
| 179 | + "avatar": {"value": "{{fragment}}"}, |
| 180 | + "biography": {"value": "{{fragment}}"}, |
| 181 | + }, |
| 182 | + "request": {"input": ["{{fragment}}"], "model": MULTIMODAL_MODEL}, |
| 183 | + "response": {"data": [{"embedding": "{{embedding}}"}]}, |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + embedders = apply_embedders(index, config) |
| 188 | + e = embedders.embedders["user_profile"] |
| 189 | + |
| 190 | + assert e.source == "rest" |
| 191 | + expected_keys = {"user_name", "avatar", "biography"} |
| 192 | + assert set(e.indexing_fragments.keys()) == expected_keys |
| 193 | + assert set(e.search_fragments.keys()) == expected_keys |
0 commit comments