99using StackExchange . Redis ;
1010using Xunit ;
1111using Xunit . Abstractions ;
12+ using SkipException = Xunit . Sdk . SkipException ;
1213
1314namespace NRedisStack . Tests . Search ;
1415
@@ -17,6 +18,8 @@ public class HybridSearchIntegrationTests(EndpointsFixture endpointsFixture, ITe
1718{
1819 private readonly struct Api ( SearchCommands ft , string index , IDatabase db )
1920 {
21+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
22+ public bool IsNull => Index is null ;
2023 public string Index { get ; } = index ;
2124 public SearchCommands FT { get ; } = ft ;
2225 public IDatabase DB { get ; } = db ;
@@ -43,11 +46,11 @@ private async Task<Api> CreateIndexAsync(string endpointId, [CallerMemberName] s
4346 . AddTextField ( "text1" , 1.0 , missingIndex : true )
4447 . AddTagField ( "tag1" , missingIndex : true )
4548 . AddNumericField ( "numeric1" , missingIndex : true )
46- . AddVectorField ( "vector1" , Schema . VectorField . VectorAlgo . FLAT , vectorAttrs , missingIndex : true ) ;
49+ . AddVectorField ( "vector1" , Schema . VectorField . VectorAlgo . HNSW , vectorAttrs , missingIndex : true ) ;
4750
4851 var ftCreateParams = FTCreateParams . CreateParams ( ) ;
4952 Assert . True ( await ft . CreateAsync ( index , ftCreateParams , sc ) ) ;
50-
53+
5154 if ( populate )
5255 {
5356#if NET
@@ -75,7 +78,8 @@ private async Task<Api> CreateIndexAsync(string endpointId, [CallerMemberName] s
7578
7679 await last ;
7780#else
78- throw new SkipException ( "FP16 not supported" ) ;
81+ // throw SkipException.ForSkip("FP16 not supported");
82+ return default ;
7983#endif
8084 }
8185
@@ -104,6 +108,7 @@ public async Task TestSetup(string endpointId)
104108 public async Task TestSearch ( string endpointId )
105109 {
106110 var api = await CreateIndexAsync ( endpointId , populate : true ) ;
111+ if ( api . IsNull ) return ;
107112
108113 var hash = ( await api . DB . HashGetAllAsync ( $ "{ api . Index } _entry2") ) . ToDictionary ( k => k . Name , v => v . Value ) ;
109114 var vec = ( byte [ ] ) hash [ "vector1" ] ! ;
@@ -165,16 +170,16 @@ public enum Scenario
165170 ParamVsim ,
166171 ParamPreFilter ,
167172 ParamMultiPreFilter ,
173+ VectorWithRangeAndEpsilon ,
174+ VectorWithNearestMaxCandidates ,
168175
169176 [ NotYetImplemented ] ExplainScore ,
170177 [ NotYetImplemented ] LinearWithScore ,
171178 [ NotYetImplemented ] RrfWithScore ,
172179 [ NotYetImplemented ] PostFilterByTag ,
173180 [ NotYetImplemented ] SearchWithComplexScorer ,
174181 [ NotYetImplemented ] VectorWithRangeAndDistanceAlias ,
175- [ NotYetImplemented ] VectorWithRangeAndEpsilon ,
176182 [ NotYetImplemented ] VectorWithNearestDistAlias ,
177- [ NotYetImplemented ] VectorWithNearestMaxCandidates ,
178183 [ NotYetImplemented ] ParamPostFilter ,
179184 [ NotYetImplemented ] ParamMultiPostFilter ,
180185 }
@@ -185,22 +190,30 @@ private sealed class NotYetImplementedAttribute : Attribute
185190
186191 private static class EnumCache < T >
187192 {
188- public static IEnumerable < T > Values { get ; } = (
193+ public static IEnumerable < T > AllValues { get ; } = (
194+ from field in typeof ( T ) . GetFields ( BindingFlags . Public | BindingFlags . Static )
195+ let val = field . GetRawConstantValue ( )
196+ where val is not null
197+ select ( T ) val ) . ToArray ( ) ;
198+
199+ public static IEnumerable < T > BadValues { get ; } = (
189200 from field in typeof ( T ) . GetFields ( BindingFlags . Public | BindingFlags . Static )
190- where ! Attribute . IsDefined ( field , typeof ( NotYetImplementedAttribute ) )
201+ where Attribute . IsDefined ( field , typeof ( NotYetImplementedAttribute ) )
191202 let val = field . GetRawConstantValue ( )
192203 where val is not null
193204 select ( T ) val ) . ToArray ( ) ;
205+
206+
194207 }
195208
196209 private static IEnumerable < object [ ] > CrossJoin < T > ( Func < IEnumerable < object [ ] > > environments )
197210 where T : unmanaged, Enum
198211 {
199212 foreach ( var arr in environments ( ) )
200213 {
201- foreach ( T scenario in EnumCache < T > . Values )
214+ foreach ( T scenario in EnumCache < T > . AllValues )
202215 {
203- yield return [ ..arr , scenario ] ;
216+ yield return [ .. arr , scenario ] ;
204217 }
205218 }
206219 }
@@ -212,7 +225,13 @@ public static IEnumerable<object[]> AllEnvironments_Scenarios() =>
212225 [ MemberData ( nameof ( AllEnvironments_Scenarios ) ) ]
213226 public async Task TestSearchScenarios ( string endpointId , Scenario scenario )
214227 {
228+ if ( EnumCache < Scenario > . BadValues . Contains ( scenario ) )
229+ {
230+ // throw SkipException.ForSkip("Not expected to work right now");
231+ return ;
232+ }
215233 var api = await CreateIndexAsync ( endpointId , populate : true ) ;
234+ if ( api . IsNull ) return ;
216235
217236 var hash = ( await api . DB . HashGetAllAsync ( $ "{ api . Index } _entry2") ) . ToDictionary ( k => k . Name , v => v . Value ) ;
218237 var vec = ( byte [ ] ) hash [ "vector1" ] ! ;
@@ -252,7 +271,7 @@ public async Task TestSearchScenarios(string endpointId, Scenario scenario)
252271 filter : "@numeric1!=0" ) ) ,
253272 Scenario . NoSort => query . NoSort ( ) ,
254273 Scenario . ExplainScore => query . ExplainScore ( ) ,
255- Scenario . Apply => query . ReturnFields ( [ ..fields , "@numeric1" ] )
274+ Scenario . Apply => query . ReturnFields ( [ .. fields , "@numeric1" ] )
256275 . Apply ( new ( "@numeric1 * 2" , "x2" ) , new ( "@x2 * 3" ) ) , // non-aliased, comes back as the expression
257276 Scenario . LinearNoScore => query . Combine ( HybridSearchQuery . Combiner . Linear ( 0.4 , 0.6 ) ) ,
258277 Scenario . LinearWithScore => query . Combine ( HybridSearchQuery . Combiner . Linear ( ) , "lin_score" ) ,
@@ -262,7 +281,7 @@ public async Task TestSearchScenarios(string endpointId, Scenario scenario)
262281 Scenario . PreFilterByNumeric => query . VectorSearch ( new ( "@vector1" , VectorData . Raw ( vec ) ,
263282 filter : "@numeric1!=0" ) ) ,
264283 Scenario . PostFilterByTag => query . Filter ( "@tag1:{foo}" ) ,
265- Scenario . PostFilterByNumber => query . ReturnFields ( [ ..fields , "@numeric1" ] ) . Filter ( "@numeric1!=0" ) ,
284+ Scenario . PostFilterByNumber => query . ReturnFields ( [ .. fields , "@numeric1" ] ) . Filter ( "@numeric1!=0" ) ,
266285 Scenario . LimitFirstPage => query . Limit ( 0 , 2 ) ,
267286 Scenario . LimitSecondPage => query . Limit ( 2 , 2 ) ,
268287 Scenario . LimitEmptyPage => query . Limit ( 0 , 0 ) ,
@@ -280,10 +299,10 @@ public async Task TestSearchScenarios(string endpointId, Scenario scenario)
280299 Scenario . ParamSearch => query . Search ( "$q" ) ,
281300 Scenario . ParamPreFilter =>
282301 query . VectorSearch ( new ( "@vector1" , VectorData . Raw ( vec ) , filter : "@numeric1!=$n" ) ) ,
283- Scenario . ParamPostFilter => query . ReturnFields ( [ ..fields , "@numeric1" ] ) . Filter ( "@numeric1!=$n" ) ,
302+ Scenario . ParamPostFilter => query . ReturnFields ( [ .. fields , "@numeric1" ] ) . Filter ( "@numeric1!=$n" ) ,
284303 Scenario . ParamMultiPreFilter => query . VectorSearch ( new ( "@vector1" , VectorData . Raw ( vec ) ,
285304 filter : "@numeric1!=$n | @tag1:{$t}" ) ) ,
286- Scenario . ParamMultiPostFilter => query . ReturnFields ( [ ..fields , "@numeric1" ] )
305+ Scenario . ParamMultiPostFilter => query . ReturnFields ( [ .. fields , "@numeric1" ] )
287306 . Filter ( "@numeric1!=$n | @tag1:{$t}" ) ,
288307 _ => throw new ArgumentOutOfRangeException ( scenario . ToString ( ) ) ,
289308 } ;
@@ -292,7 +311,7 @@ public async Task TestSearchScenarios(string endpointId, Scenario scenario)
292311 {
293312 Scenario . ParamPostFilter or Scenario . ParamPreFilter => new Dictionary < string , object > ( ) { [ "n" ] = 42 } ,
294313 Scenario . ParamMultiPostFilter or Scenario . ParamMultiPreFilter => new Dictionary < string , object > ( )
295- { [ "n" ] = 42 , [ "t" ] = "foo" } ,
314+ { [ "n" ] = 42 , [ "t" ] = "foo" } ,
296315 Scenario . ParamSearch => new Dictionary < string , object > ( ) { [ "q" ] = text } ,
297316 Scenario . ParamVsim => new Dictionary < string , object > ( ) { [ "v" ] = VectorData . Raw ( vec ) } ,
298317 _ => null ,
0 commit comments