1+ using System ;
2+ using System . Collections . Generic ;
13using System . Text ;
24using FluentAssertions ;
35using QsNet . Enums ;
@@ -108,4 +110,111 @@ public void CopyWith_WithModifications_ShouldReturnModifiedOptions()
108110 newOptions . ParseLists . Should ( ) . BeTrue ( ) ;
109111 newOptions . StrictNullHandling . Should ( ) . BeFalse ( ) ;
110112 }
113+
114+ [ Fact ]
115+ public void DecodeKey_ShouldThrow_When_DecodeDotInKeysTrue_And_AllowDotsFalse ( )
116+ {
117+ var options = new DecodeOptions
118+ {
119+ AllowDots = false ,
120+ DecodeDotInKeys = true
121+ } ;
122+
123+ Action act = ( ) => options . DecodeKey ( "a%2Eb" , Encoding . UTF8 ) ;
124+ act . Should ( ) . Throw < ArgumentException > ( )
125+ . Where ( e => e . Message . Contains ( "decodeDotInKeys" , StringComparison . OrdinalIgnoreCase )
126+ && e . Message . Contains ( "allowDots" , StringComparison . OrdinalIgnoreCase ) ) ;
127+ }
128+
129+ [ Fact ]
130+ public void DecodeKey_DecodesPercentSequences_LikeValues ( )
131+ {
132+ var options = new DecodeOptions
133+ {
134+ AllowDots = true ,
135+ DecodeDotInKeys = false
136+ } ;
137+
138+ options . DecodeKey ( "a%2Eb" , Encoding . UTF8 ) . Should ( ) . Be ( "a.b" ) ;
139+ options . DecodeKey ( "a%2eb" , Encoding . UTF8 ) . Should ( ) . Be ( "a.b" ) ;
140+ }
141+
142+ [ Fact ]
143+ public void DecodeValue_DecodesPercentSequences_Normally ( )
144+ {
145+ var options = new DecodeOptions ( ) ;
146+ options . DecodeValue ( "%2E" , Encoding . UTF8 ) . Should ( ) . Be ( "." ) ;
147+ }
148+
149+ [ Fact ]
150+ public void DecoderWithKind_IsUsed_For_Key_And_Value ( )
151+ {
152+ var calls = new List < ( string ? s , DecodeKind kind ) > ( ) ;
153+ var options = new DecodeOptions
154+ {
155+ DecoderWithKind = ( s , enc , kind ) =>
156+ {
157+ calls . Add ( ( s , kind ) ) ;
158+ return s ;
159+ }
160+ } ;
161+
162+ options . DecodeKey ( "x" , Encoding . UTF8 ) . Should ( ) . Be ( "x" ) ;
163+ options . DecodeValue ( "y" , Encoding . UTF8 ) . Should ( ) . Be ( "y" ) ;
164+
165+ calls . Should ( ) . HaveCount ( 2 ) ;
166+ calls [ 0 ] . kind . Should ( ) . Be ( DecodeKind . Key ) ;
167+ calls [ 0 ] . s . Should ( ) . Be ( "x" ) ;
168+ calls [ 1 ] . kind . Should ( ) . Be ( DecodeKind . Value ) ;
169+ calls [ 1 ] . s . Should ( ) . Be ( "y" ) ;
170+ }
171+
172+ [ Fact ]
173+ public void DecoderWithKind_NullReturn_IsHonored_NoFallback ( )
174+ {
175+ var options = new DecodeOptions
176+ {
177+ DecoderWithKind = ( s , enc , kind ) => null
178+ } ;
179+
180+ options . DecodeValue ( "foo" , Encoding . UTF8 ) . Should ( ) . BeNull ( ) ;
181+ options . DecodeKey ( "bar" , Encoding . UTF8 ) . Should ( ) . BeNull ( ) ;
182+ }
183+
184+ [ Fact ]
185+ public void LegacyDecoder_IsUsed_When_NoKindAwareDecoder_IsProvided ( )
186+ {
187+ var options = new DecodeOptions
188+ {
189+ Decoder = ( s , enc ) => s is null ? null : s . ToUpperInvariant ( )
190+ } ;
191+
192+ options . DecodeValue ( "abc" , Encoding . UTF8 ) . Should ( ) . Be ( "ABC" ) ;
193+ // For keys, legacy decoder is also used when no kind-aware decoder is set
194+ options . DecodeKey ( "a%2Eb" , Encoding . UTF8 ) . Should ( ) . Be ( "A%2EB" ) ;
195+ }
196+
197+ [ Fact ]
198+ public void CopyWith_PreservesAndOverrides_Decoders ( )
199+ {
200+ var original = new DecodeOptions
201+ {
202+ Decoder = ( s , enc ) => s == null ? null : $ "L:{ s } ",
203+ DecoderWithKind = ( s , enc , k ) => s == null ? null : $ "K:{ k } :{ s } "
204+ } ;
205+
206+ // Copy without overrides preserves both decoders
207+ var copy = original . CopyWith ( ) ;
208+ copy . DecodeValue ( "v" , Encoding . UTF8 ) . Should ( ) . Be ( "K:Value:v" ) ;
209+ copy . DecodeKey ( "k" , Encoding . UTF8 ) . Should ( ) . Be ( "K:Key:k" ) ;
210+
211+ // Override only the legacy decoder; kind-aware remains
212+ var copy2 = original . CopyWith ( decoder : ( s , enc ) => s == null ? null : $ "L2:{ s } ") ;
213+ copy2 . DecodeValue ( "v" , Encoding . UTF8 ) . Should ( ) . Be ( "K:Value:v" ) ; // still kind-aware takes precedence
214+
215+ // Override kind-aware decoder
216+ var copy3 = original . CopyWith ( decoderWithKind : ( s , enc , k ) => s == null ? null : $ "K2:{ k } :{ s } ") ;
217+ copy3 . DecodeValue ( "v" , Encoding . UTF8 ) . Should ( ) . Be ( "K2:Value:v" ) ;
218+ copy3 . DecodeKey ( "k" , Encoding . UTF8 ) . Should ( ) . Be ( "K2:Key:k" ) ;
219+ }
111220}
0 commit comments