chore: merge origin/main into mintlify/af82bda4 and resolve conflicts
This commit is contained in:
@@ -249,9 +249,23 @@ REDIS_HOST = os.environ.get("REDIS_HOST", "localhost")
|
||||
REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379"))
|
||||
|
||||
|
||||
def _redis_search_available(host, port, timeout=2):
|
||||
"""RediSearch (the FT.* command family) is an optional module, not part of vanilla Redis."""
|
||||
if not _tcp_reachable(host, port, timeout=timeout):
|
||||
return False
|
||||
try:
|
||||
import redis
|
||||
|
||||
client = redis.Redis(host=host, port=port, socket_connect_timeout=timeout, socket_timeout=timeout)
|
||||
modules = {m["name"].lower() for m in client.module_list()}
|
||||
return "search" in modules
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not _tcp_reachable(REDIS_HOST, REDIS_PORT),
|
||||
reason=f"Redis not reachable at {REDIS_HOST}:{REDIS_PORT}",
|
||||
not _redis_search_available(REDIS_HOST, REDIS_PORT),
|
||||
reason=f"RediSearch module not available at {REDIS_HOST}:{REDIS_PORT} (requires Redis Stack, not vanilla Redis)",
|
||||
)
|
||||
class TestRedisThreshold:
|
||||
def test_threshold_filtering(self):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) 2026, Oracle and/or its affiliates.
|
||||
import os
|
||||
import uuid
|
||||
from contextlib import nullcontext
|
||||
@@ -267,6 +268,16 @@ def test_index_parameters_reject_non_string_keys():
|
||||
)
|
||||
|
||||
|
||||
def test_index_accuracy_rejects_zero():
|
||||
with pytest.raises(ValueError, match="index_accuracy.*between 1 and 100"):
|
||||
OracleAIVectorSearchConfig(
|
||||
collection_name=_unique_collection_name(),
|
||||
embedding_model_dims=DIM,
|
||||
client=object(),
|
||||
index_accuracy=0,
|
||||
)
|
||||
|
||||
|
||||
def test_index_parameters_canonicalize_int_subclasses():
|
||||
class FormattedInt(int):
|
||||
def __format__(self, format_spec):
|
||||
@@ -331,6 +342,27 @@ def test_config_rejects_none_for_non_optional_fields(field, value):
|
||||
OracleAIVectorSearchConfig(client=object(), **{field: value})
|
||||
|
||||
|
||||
def test_init_closes_owned_client_when_post_connect_setup_fails(monkeypatch):
|
||||
fake_connection = MagicMock(spec=oracledb.Connection)
|
||||
fake_connection.thin = True
|
||||
fake_connection.version = "23.4.0.0"
|
||||
|
||||
monkeypatch.setattr(oracledb, "connect", MagicMock(return_value=fake_connection))
|
||||
monkeypatch.setattr(OracleAIVectorSearch, "create_col", MagicMock(side_effect=RuntimeError("boom")))
|
||||
monkeypatch.setattr(OracleAIVectorSearch, "__del__", lambda self: None)
|
||||
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
OracleAIVectorSearch(
|
||||
collection_name=_unique_collection_name(),
|
||||
embedding_model_dims=DIM,
|
||||
connection_params={"user": "u", "password": "p", "dsn": "d"},
|
||||
use_connection_pool=False,
|
||||
do_create_index=False,
|
||||
)
|
||||
|
||||
fake_connection.close.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("metric", "distance", "expected_score"),
|
||||
[
|
||||
@@ -1049,7 +1081,7 @@ def test_documentation():
|
||||
},
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
results = m.search("What movie to watch?", user_id="alice", limit=2)["results"]
|
||||
results = m.search("What movie to watch?", filters={"user_id": "alice"}, top_k=2)["results"]
|
||||
assert len(results) == 2
|
||||
assert all(res["user_id"] == "alice" for res in results)
|
||||
m.reset()
|
||||
|
||||
@@ -2508,3 +2508,20 @@ class TestBuildFilterConditions(unittest.TestCase):
|
||||
def test_numeric_scalar_becomes_string(self):
|
||||
conditions, params = _build_filter_conditions({"priority": 42})
|
||||
self.assertEqual(params, ["priority", "42"])
|
||||
|
||||
def test_in_rejects_string_value(self):
|
||||
"""Passing a string to 'in' would iterate characters and produce a misleading ANY() clause."""
|
||||
with self.assertRaises(ValueError, msg="Expected ValueError for non-list 'in' value"):
|
||||
_build_filter_conditions({"user_id": {"in": "alice"}})
|
||||
|
||||
def test_in_rejects_dict_value(self):
|
||||
with self.assertRaises(ValueError):
|
||||
_build_filter_conditions({"user_id": {"in": {"$gt": 0}}})
|
||||
|
||||
def test_nin_rejects_string_value(self):
|
||||
with self.assertRaises(ValueError):
|
||||
_build_filter_conditions({"user_id": {"nin": "alice"}})
|
||||
|
||||
def test_in_accepts_list_value(self):
|
||||
conditions, params = _build_filter_conditions({"user_id": {"in": ["alice", "bob"]}})
|
||||
self.assertEqual(params, ["user_id", ["alice", "bob"]])
|
||||
|
||||
@@ -239,9 +239,23 @@ REDIS_HOST = os.environ.get("REDIS_HOST", "localhost")
|
||||
REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379"))
|
||||
|
||||
|
||||
def _redis_search_available(host, port, timeout=2):
|
||||
"""RediSearch (the FT.* command family) is an optional module, not part of vanilla Redis."""
|
||||
if not _tcp_reachable(host, port, timeout=timeout):
|
||||
return False
|
||||
try:
|
||||
import redis
|
||||
|
||||
client = redis.Redis(host=host, port=port, socket_connect_timeout=timeout, socket_timeout=timeout)
|
||||
modules = {m["name"].lower() for m in client.module_list()}
|
||||
return "search" in modules
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not _tcp_reachable(REDIS_HOST, REDIS_PORT),
|
||||
reason=f"Redis not reachable at {REDIS_HOST}:{REDIS_PORT}",
|
||||
not _redis_search_available(REDIS_HOST, REDIS_PORT),
|
||||
reason=f"RediSearch module not available at {REDIS_HOST}:{REDIS_PORT} (requires Redis Stack, not vanilla Redis)",
|
||||
)
|
||||
class TestRedis:
|
||||
"""Redis returns cosine distance [0,2]. Conversion: score = max(0, 1 - dist)."""
|
||||
|
||||
Reference in New Issue
Block a user