aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/modules/vector-sets/tests/with.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/modules/vector-sets/tests/with.py')
-rw-r--r--examples/redis-unstable/modules/vector-sets/tests/with.py214
1 files changed, 0 insertions, 214 deletions
diff --git a/examples/redis-unstable/modules/vector-sets/tests/with.py b/examples/redis-unstable/modules/vector-sets/tests/with.py
deleted file mode 100644
index d14a23f..0000000
--- a/examples/redis-unstable/modules/vector-sets/tests/with.py
+++ /dev/null
@@ -1,214 +0,0 @@
1from test import TestCase, generate_random_vector
2import struct
3import json
4import random
5
6class VSIMWithAttribs(TestCase):
7 def getname(self):
8 return "VSIM WITHATTRIBS/WITHSCORES functionality testing"
9
10 def setup(self):
11 super().setup()
12 self.dim = 8
13 self.count = 20
14
15 # Create vectors with attributes
16 for i in range(self.count):
17 vec = generate_random_vector(self.dim)
18 vec_bytes = struct.pack(f'{self.dim}f', *vec)
19
20 # Item name
21 name = f"{self.test_key}:item:{i}"
22
23 # Add to Redis
24 self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, name)
25
26 # Create and add attribute
27 if i % 5 == 0:
28 # Every 5th item has no attribute (for testing NULL responses)
29 continue
30
31 category = random.choice(["electronics", "furniture", "clothing"])
32 price = random.randint(50, 1000)
33 attrs = {"category": category, "price": price, "id": i}
34
35 self.redis.execute_command('VSETATTR', self.test_key, name, json.dumps(attrs))
36
37 def is_numeric(self, value):
38 """Check if a value can be converted to float"""
39 try:
40 if isinstance(value, (int, float)):
41 return True
42 if isinstance(value, bytes):
43 float(value.decode('utf-8'))
44 return True
45 if isinstance(value, str):
46 float(value)
47 return True
48 return False
49 except (ValueError, TypeError):
50 return False
51
52 def test(self):
53 # Create query vector
54 query_vec = generate_random_vector(self.dim)
55
56 # Test 1: VSIM with no additional options (should be same for RESP2 and RESP3)
57 cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim]
58 cmd_args.extend([str(x) for x in query_vec])
59 cmd_args.extend(['COUNT', 5])
60
61 results_resp2 = self.redis.execute_command(*cmd_args)
62 results_resp3 = self.redis3.execute_command(*cmd_args)
63
64 # Both should return simple arrays of item names
65 assert len(results_resp2) == 5, f"RESP2: Expected 5 results, got {len(results_resp2)}"
66 assert len(results_resp3) == 5, f"RESP3: Expected 5 results, got {len(results_resp3)}"
67 assert all(isinstance(item, bytes) for item in results_resp2), "RESP2: Results should be byte strings"
68 assert all(isinstance(item, bytes) for item in results_resp3), "RESP3: Results should be byte strings"
69
70 # Test 2: VSIM with WITHSCORES only
71 cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim]
72 cmd_args.extend([str(x) for x in query_vec])
73 cmd_args.extend(['COUNT', 5, 'WITHSCORES'])
74
75 results_resp2 = self.redis.execute_command(*cmd_args)
76 results_resp3 = self.redis3.execute_command(*cmd_args)
77
78 # RESP2: Should be a flat array alternating item, score
79 assert len(results_resp2) == 10, f"RESP2: Expected 10 elements (5 items × 2), got {len(results_resp2)}"
80 for i in range(0, len(results_resp2), 2):
81 assert isinstance(results_resp2[i], bytes), f"RESP2: Item at {i} should be bytes"
82 assert self.is_numeric(results_resp2[i+1]), f"RESP2: Score at {i+1} should be numeric"
83 score = float(results_resp2[i+1]) if isinstance(results_resp2[i+1], bytes) else results_resp2[i+1]
84 assert 0 <= score <= 1, f"RESP2: Score {score} should be between 0 and 1"
85
86 # RESP3: Should be a dict/map with items as keys and scores as DIRECT values (not arrays)
87 assert isinstance(results_resp3, dict), f"RESP3: Expected dict, got {type(results_resp3)}"
88 assert len(results_resp3) == 5, f"RESP3: Expected 5 entries, got {len(results_resp3)}"
89 for item, score in results_resp3.items():
90 assert isinstance(item, bytes), f"RESP3: Key should be bytes"
91 # Score should be a direct value, NOT an array
92 assert not isinstance(score, list), f"RESP3: With single WITH option, value should not be array"
93 assert self.is_numeric(score), f"RESP3: Score should be numeric, got {type(score)}"
94 score_val = float(score) if isinstance(score, bytes) else score
95 assert 0 <= score_val <= 1, f"RESP3: Score {score_val} should be between 0 and 1"
96
97 # Test 3: VSIM with WITHATTRIBS only
98 cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim]
99 cmd_args.extend([str(x) for x in query_vec])
100 cmd_args.extend(['COUNT', 5, 'WITHATTRIBS'])
101
102 results_resp2 = self.redis.execute_command(*cmd_args)
103 results_resp3 = self.redis3.execute_command(*cmd_args)
104
105 # RESP2: Should be a flat array alternating item, attribute
106 assert len(results_resp2) == 10, f"RESP2: Expected 10 elements (5 items × 2), got {len(results_resp2)}"
107 for i in range(0, len(results_resp2), 2):
108 assert isinstance(results_resp2[i], bytes), f"RESP2: Item at {i} should be bytes"
109 attr = results_resp2[i+1]
110 assert attr is None or isinstance(attr, bytes), f"RESP2: Attribute at {i+1} should be None or bytes"
111 if attr is not None:
112 # Verify it's valid JSON
113 json.loads(attr)
114
115 # RESP3: Should be a dict/map with items as keys and attributes as DIRECT values (not arrays)
116 assert isinstance(results_resp3, dict), f"RESP3: Expected dict, got {type(results_resp3)}"
117 assert len(results_resp3) == 5, f"RESP3: Expected 5 entries, got {len(results_resp3)}"
118 for item, attr in results_resp3.items():
119 assert isinstance(item, bytes), f"RESP3: Key should be bytes"
120 # Attribute should be a direct value, NOT an array
121 assert not isinstance(attr, list), f"RESP3: With single WITH option, value should not be array"
122 assert attr is None or isinstance(attr, bytes), f"RESP3: Attribute should be None or bytes"
123 if attr is not None:
124 # Verify it's valid JSON
125 json.loads(attr)
126
127 # Test 4: VSIM with both WITHSCORES and WITHATTRIBS
128 cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim]
129 cmd_args.extend([str(x) for x in query_vec])
130 cmd_args.extend(['COUNT', 5, 'WITHSCORES', 'WITHATTRIBS'])
131
132 results_resp2 = self.redis.execute_command(*cmd_args)
133 results_resp3 = self.redis3.execute_command(*cmd_args)
134
135 # RESP2: Should be a flat array with pattern: item, score, attribute
136 assert len(results_resp2) == 15, f"RESP2: Expected 15 elements (5 items × 3), got {len(results_resp2)}"
137 for i in range(0, len(results_resp2), 3):
138 assert isinstance(results_resp2[i], bytes), f"RESP2: Item at {i} should be bytes"
139 assert self.is_numeric(results_resp2[i+1]), f"RESP2: Score at {i+1} should be numeric"
140 score = float(results_resp2[i+1]) if isinstance(results_resp2[i+1], bytes) else results_resp2[i+1]
141 assert 0 <= score <= 1, f"RESP2: Score {score} should be between 0 and 1"
142 attr = results_resp2[i+2]
143 assert attr is None or isinstance(attr, bytes), f"RESP2: Attribute at {i+2} should be None or bytes"
144
145 # RESP3: Should be a dict where each value is a 2-element array [score, attribute]
146 assert isinstance(results_resp3, dict), f"RESP3: Expected dict, got {type(results_resp3)}"
147 assert len(results_resp3) == 5, f"RESP3: Expected 5 entries, got {len(results_resp3)}"
148 for item, value in results_resp3.items():
149 assert isinstance(item, bytes), f"RESP3: Key should be bytes"
150 # With BOTH options, value MUST be an array
151 assert isinstance(value, list), f"RESP3: With both WITH options, value should be a list, got {type(value)}"
152 assert len(value) == 2, f"RESP3: Value should have 2 elements [score, attr], got {len(value)}"
153
154 score, attr = value
155 assert self.is_numeric(score), f"RESP3: Score should be numeric"
156 score_val = float(score) if isinstance(score, bytes) else score
157 assert 0 <= score_val <= 1, f"RESP3: Score {score_val} should be between 0 and 1"
158 assert attr is None or isinstance(attr, bytes), f"RESP3: Attribute should be None or bytes"
159
160 # Test 5: Verify consistency - same items returned in same order
161 cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim]
162 cmd_args.extend([str(x) for x in query_vec])
163 cmd_args.extend(['COUNT', 5, 'WITHSCORES', 'WITHATTRIBS'])
164
165 results_resp2 = self.redis.execute_command(*cmd_args)
166 results_resp3 = self.redis3.execute_command(*cmd_args)
167
168 # Extract items from RESP2 (every 3rd element starting from 0)
169 items_resp2 = [results_resp2[i] for i in range(0, len(results_resp2), 3)]
170
171 # Extract items from RESP3 (keys of the dict)
172 items_resp3 = list(results_resp3.keys())
173
174 # Verify same items returned
175 assert set(items_resp2) == set(items_resp3), "RESP2 and RESP3 should return the same items"
176
177 # Build a mapping from items to scores and attributes for comparison
178 data_resp2 = {}
179 for i in range(0, len(results_resp2), 3):
180 item = results_resp2[i]
181 score = float(results_resp2[i+1]) if isinstance(results_resp2[i+1], bytes) else results_resp2[i+1]
182 attr = results_resp2[i+2]
183 data_resp2[item] = (score, attr)
184
185 data_resp3 = {}
186 for item, value in results_resp3.items():
187 score = float(value[0]) if isinstance(value[0], bytes) else value[0]
188 attr = value[1]
189 data_resp3[item] = (score, attr)
190
191 # Verify scores and attributes match for each item
192 for item in data_resp2:
193 score_resp2, attr_resp2 = data_resp2[item]
194 score_resp3, attr_resp3 = data_resp3[item]
195
196 assert abs(score_resp2 - score_resp3) < 0.0001, \
197 f"Scores for {item} don't match: RESP2={score_resp2}, RESP3={score_resp3}"
198 assert attr_resp2 == attr_resp3, \
199 f"Attributes for {item} don't match: RESP2={attr_resp2}, RESP3={attr_resp3}"
200
201 # Test 6: Test ordering of WITHSCORES and WITHATTRIBS doesn't matter
202 cmd_args1 = ['VSIM', self.test_key, 'VALUES', self.dim]
203 cmd_args1.extend([str(x) for x in query_vec])
204 cmd_args1.extend(['COUNT', 3, 'WITHSCORES', 'WITHATTRIBS'])
205
206 cmd_args2 = ['VSIM', self.test_key, 'VALUES', self.dim]
207 cmd_args2.extend([str(x) for x in query_vec])
208 cmd_args2.extend(['COUNT', 3, 'WITHATTRIBS', 'WITHSCORES']) # Reversed order
209
210 results1_resp3 = self.redis3.execute_command(*cmd_args1)
211 results2_resp3 = self.redis3.execute_command(*cmd_args2)
212
213 # Both should return the same structure
214 assert results1_resp3 == results2_resp3, "Order of WITH options shouldn't matter"