@@ -37,6 +37,34 @@ private static void SkipClusterPre8(string endpointId)
3737 && EndpointsFixture . RedisVersion . Minor < 4 , "Ignoring cluster tests for FT.SEARCH pre Redis 8.4" ) ;
3838 }
3939
40+ // Legacy cluster servers reject alias commands wholesale with CROSSSLOT; the first
41+ // FT.ALIASADD acts as an environment probe, so skip the test when it fails that way.
42+ private static void AliasAddOrSkipOnCrossSlot ( SearchCommands ft , string alias , string index )
43+ {
44+ try
45+ {
46+ Assert . True ( ft . AliasAdd ( alias , index ) ) ;
47+ }
48+ catch ( RedisServerException rse )
49+ {
50+ Assert . SkipWhen ( rse . Message . StartsWith ( "CROSSSLOT" ) , "legacy failure" ) ;
51+ throw ;
52+ }
53+ }
54+
55+ private static async Task AliasAddOrSkipOnCrossSlotAsync ( SearchCommands ft , string alias , string index )
56+ {
57+ try
58+ {
59+ Assert . True ( await ft . AliasAddAsync ( alias , index ) ) ;
60+ }
61+ catch ( RedisServerException rse )
62+ {
63+ Assert . SkipWhen ( rse . Message . StartsWith ( "CROSSSLOT" ) , "legacy failure" ) ;
64+ throw ;
65+ }
66+ }
67+
4068 private void AddDocument ( IDatabase db , Document doc )
4169 {
4270 var hash = doc . GetProperties ( )
@@ -568,15 +596,7 @@ public void TestAlias(string endpointId)
568596 doc . Add ( "field1" , "value" ) ;
569597 AddDocument ( db , "doc1" , doc ) ;
570598
571- try
572- {
573- Assert . True ( ft . AliasAdd ( "ALIAS1" , index ) ) ;
574- }
575- catch ( RedisServerException rse )
576- {
577- Assert . SkipWhen ( rse . Message . StartsWith ( "CROSSSLOT" ) , "legacy failure" ) ;
578- throw ;
579- }
599+ AliasAddOrSkipOnCrossSlot ( ft , "ALIAS1" , index ) ;
580600
581601 SearchResult res1 = ft . Search ( "ALIAS1" , new Query ( "*" ) . ReturnFields ( "field1" ) ) ;
582602 Assert . Equal ( 1 , res1 . TotalResults ) ;
@@ -607,15 +627,7 @@ public async Task TestAliasAsync(string endpointId)
607627 doc . Add ( "field1" , "value" ) ;
608628 AddDocument ( db , "doc1" , doc ) ;
609629
610- try
611- {
612- Assert . True ( await ft . AliasAddAsync ( "ALIAS1" , index ) ) ;
613- }
614- catch ( RedisServerException rse )
615- {
616- Assert . SkipWhen ( rse . Message . StartsWith ( "CROSSSLOT" ) , "legacy failure" ) ;
617- throw ;
618- }
630+ await AliasAddOrSkipOnCrossSlotAsync ( ft , "ALIAS1" , index ) ;
619631
620632 SearchResult res1 = ft . Search ( "ALIAS1" , new Query ( "*" ) . ReturnFields ( "field1" ) ) ;
621633 Assert . Equal ( 1 , res1 . TotalResults ) ;
@@ -631,6 +643,58 @@ public async Task TestAliasAsync(string endpointId)
631643 await Assert . ThrowsAsync < RedisServerException > ( async ( ) => await ft . AliasDelAsync ( "ALIAS2" ) ) ;
632644 }
633645
646+ [ SkipIfRedisTheory ( Comparison . LessThan , "8.10.0" ) ]
647+ [ MemberData ( nameof ( EndpointsFixture . Env . AllEnvironments ) , MemberType = typeof ( EndpointsFixture . Env ) ) ]
648+ public void TestAliasList ( string endpointId )
649+ {
650+ IDatabase db = GetCleanDatabase ( endpointId ) ;
651+ var ft = db . FT ( ) ;
652+ Assert . True ( ft . Create ( index , FTCreateParams . CreateParams ( ) , new Schema ( ) . AddTextField ( "field1" ) ) ) ;
653+
654+ // Ordering is not part of the contract, so compare sorted.
655+ string [ ] Aliases ( ) => ft . AliasList ( index ) . Select ( a => a . ToString ( ) ) . OrderBy ( a => a ) . ToArray ( ) ;
656+
657+ // An existing index with no aliases returns an empty collection, not an error.
658+ Assert . Empty ( ft . AliasList ( index ) ) ;
659+
660+ AliasAddOrSkipOnCrossSlot ( ft , "ALIAS1" , index ) ;
661+ Assert . Equal ( new [ ] { "ALIAS1" } , Aliases ( ) ) ;
662+
663+ Assert . True ( ft . AliasAdd ( "ALIAS2" , index ) ) ;
664+ Assert . Equal ( new [ ] { "ALIAS1" , "ALIAS2" } , Aliases ( ) ) ;
665+
666+ Assert . True ( ft . AliasDel ( "ALIAS1" ) ) ;
667+ Assert . Equal ( new [ ] { "ALIAS2" } , Aliases ( ) ) ;
668+
669+ // A non-existent index propagates the server error unchanged.
670+ Assert . Throws < RedisServerException > ( ( ) => ft . AliasList ( "nonexisting-index" ) ) ;
671+ }
672+
673+ [ SkipIfRedisTheory ( Comparison . LessThan , "8.10.0" ) ]
674+ [ MemberData ( nameof ( EndpointsFixture . Env . AllEnvironments ) , MemberType = typeof ( EndpointsFixture . Env ) ) ]
675+ public async Task TestAliasListAsync ( string endpointId )
676+ {
677+ IDatabase db = GetCleanDatabase ( endpointId ) ;
678+ var ft = db . FT ( ) ;
679+ Assert . True ( ft . Create ( index , FTCreateParams . CreateParams ( ) , new Schema ( ) . AddTextField ( "field1" ) ) ) ;
680+
681+ // Ordering is not part of the contract, so compare sorted.
682+ async Task < string [ ] > Aliases ( ) => ( await ft . AliasListAsync ( index ) ) . Select ( a => a . ToString ( ) ) . OrderBy ( a => a ) . ToArray ( ) ;
683+
684+ Assert . Empty ( await ft . AliasListAsync ( index ) ) ;
685+
686+ await AliasAddOrSkipOnCrossSlotAsync ( ft , "ALIAS1" , index ) ;
687+ Assert . Equal ( new [ ] { "ALIAS1" } , await Aliases ( ) ) ;
688+
689+ Assert . True ( await ft . AliasAddAsync ( "ALIAS2" , index ) ) ;
690+ Assert . Equal ( new [ ] { "ALIAS1" , "ALIAS2" } , await Aliases ( ) ) ;
691+
692+ Assert . True ( await ft . AliasDelAsync ( "ALIAS1" ) ) ;
693+ Assert . Equal ( new [ ] { "ALIAS2" } , await Aliases ( ) ) ;
694+
695+ await Assert . ThrowsAsync < RedisServerException > ( async ( ) => await ft . AliasListAsync ( "nonexisting-index" ) ) ;
696+ }
697+
634698 [ Theory ]
635699 [ MemberData ( nameof ( EndpointsFixture . Env . AllEnvironments ) , MemberType = typeof ( EndpointsFixture . Env ) ) ]
636700 public void TestApplyAndFilterAggregations ( string endpointId )
0 commit comments