1+ using System ;
2+ using System . Linq ;
3+ using System . Threading . Tasks ;
4+ using FluentAssertions ;
5+ using Moq ;
6+ using Xunit ;
7+
8+ namespace HashidsNet . test
9+ {
10+ public class GeneralTests
11+ {
12+ private const string salt = "this is my salt" ;
13+ private readonly Hashids _hashids = new Hashids ( salt ) ;
14+
15+ [ Fact ]
16+ public async Task EncodingIsThreadSafe ( )
17+ {
18+ var hashids = new Hashids ( ) ;
19+ const int threadCount = 6 ;
20+ const int numberCount = 1000001 ;
21+
22+ var tasks = Enumerable . Range ( 1 , threadCount ) . Select ( t => Task . Run ( ( ) =>
23+ {
24+ for ( var n = 1 ; n < numberCount ; n ++ )
25+ {
26+ var s = hashids . Encode ( n ) ;
27+ hashids . Decode ( s ) . Should ( ) . Equal ( n ) ;
28+ }
29+ } ) ) . ToArray ( ) ;
30+
31+ await Task . WhenAll ( tasks ) ;
32+ }
33+
34+ [ Fact ]
35+ public void DefaultSaltIsBlank ( )
36+ {
37+ // default salt of empty string "" should result in this encoded value
38+ new Hashids ( ) . Encode ( 1 , 2 , 3 ) . Should ( ) . Be ( "o2fXhV" ) ;
39+ }
40+
41+ [ Fact ]
42+ public void SingleInt_Encodes ( )
43+ {
44+ _hashids . Encode ( 1 ) . Should ( ) . Be ( "NV" ) ;
45+ _hashids . Encode ( 22 ) . Should ( ) . Be ( "K4" ) ;
46+ _hashids . Encode ( 333 ) . Should ( ) . Be ( "OqM" ) ;
47+ _hashids . Encode ( 9999 ) . Should ( ) . Be ( "kQVg" ) ;
48+ _hashids . Encode ( 123000 ) . Should ( ) . Be ( "58LzD" ) ;
49+ _hashids . Encode ( 456000000 ) . Should ( ) . Be ( "5gn6mQP" ) ;
50+ _hashids . Encode ( 987654321 ) . Should ( ) . Be ( "oyjYvry" ) ;
51+ }
52+
53+ [ Fact ]
54+ public void SingleInt_Decodes ( )
55+ {
56+ _hashids . Decode ( "NkK9" ) . Should ( ) . Equal ( new [ ] { 12345 } ) ;
57+ _hashids . Decode ( "5O8yp5P" ) . Should ( ) . Equal ( new [ ] { 666555444 } ) ;
58+ _hashids . Decode ( "Wzo" ) . Should ( ) . Equal ( new [ ] { 1337 } ) ;
59+ _hashids . Decode ( "DbE" ) . Should ( ) . Equal ( new [ ] { 808 } ) ;
60+ _hashids . Decode ( "yj8" ) . Should ( ) . Equal ( new [ ] { 303 } ) ;
61+ }
62+
63+ [ Fact ]
64+ public void SingleLong_Encodes ( )
65+ {
66+ _hashids . EncodeLong ( 1L ) . Should ( ) . Be ( "NV" ) ;
67+ _hashids . EncodeLong ( 2147483648L ) . Should ( ) . Be ( "21OjjRK" ) ;
68+ _hashids . EncodeLong ( 4294967296L ) . Should ( ) . Be ( "D54yen6" ) ;
69+ _hashids . EncodeLong ( 666555444333222L ) . Should ( ) . Be ( "KVO9yy1oO5j" ) ;
70+ _hashids . EncodeLong ( 12345678901112L ) . Should ( ) . Be ( "4bNP1L26r" ) ;
71+ _hashids . EncodeLong ( Int64 . MaxValue ) . Should ( ) . Be ( "jvNx4BjM5KYjv" ) ;
72+ }
73+
74+ [ Fact ]
75+ public void SingleLong_Decode ( )
76+ {
77+ _hashids . DecodeLong ( "NV" ) . Should ( ) . Equal ( new [ ] { 1L } ) ;
78+ _hashids . DecodeLong ( "21OjjRK" ) . Should ( ) . Equal ( new [ ] { 2147483648L } ) ;
79+ _hashids . DecodeLong ( "D54yen6" ) . Should ( ) . Equal ( new [ ] { 4294967296L } ) ;
80+ _hashids . DecodeLong ( "KVO9yy1oO5j" ) . Should ( ) . Equal ( new [ ] { 666555444333222L } ) ;
81+ _hashids . DecodeLong ( "4bNP1L26r" ) . Should ( ) . Equal ( new [ ] { 12345678901112L } ) ;
82+ _hashids . DecodeLong ( "jvNx4BjM5KYjv" ) . Should ( ) . Equal ( new [ ] { Int64 . MaxValue } ) ;
83+ }
84+
85+ [ Fact ]
86+ public void ListOfInt_Encodes ( )
87+ {
88+ _hashids . Encode ( 1 , 2 , 3 ) . Should ( ) . Be ( "laHquq" ) ;
89+ _hashids . Encode ( 2 , 4 , 6 ) . Should ( ) . Be ( "44uotN" ) ;
90+ _hashids . Encode ( 99 , 25 ) . Should ( ) . Be ( "97Jun" ) ;
91+ _hashids . Encode ( 1337 , 42 , 314 ) . Should ( ) . Be ( "7xKhrUxm" ) ;
92+ _hashids . Encode ( 683 , 94108 , 123 , 5 ) . Should ( ) . Be ( "aBMswoO2UB3Sj" ) ;
93+ _hashids . Encode ( 547 , 31 , 241271 , 311 , 31397 , 1129 , 71129 ) . Should ( ) . Be ( "3RoSDhelEyhxRsyWpCx5t1ZK" ) ;
94+ _hashids . Encode ( 21979508 , 35563591 , 57543099 , 93106690 , 150649789 ) . Should ( ) . Be ( "p2xkL3CK33JjcrrZ8vsw4YRZueZX9k" ) ;
95+ }
96+
97+ [ Fact ]
98+ public void ListOfInt_Decodes ( )
99+ {
100+ _hashids . Decode ( "1gRYUwKxBgiVuX" ) . Should ( ) . Equal ( new [ ] { 66655 , 5444333 , 2 , 22 } ) ;
101+ _hashids . Decode ( "aBMswoO2UB3Sj" ) . Should ( ) . Equal ( new [ ] { 683 , 94108 , 123 , 5 } ) ;
102+ _hashids . Decode ( "jYhp" ) . Should ( ) . Equal ( new [ ] { 3 , 4 } ) ;
103+ _hashids . Decode ( "k9Ib" ) . Should ( ) . Equal ( new [ ] { 6 , 5 } ) ;
104+ _hashids . Decode ( "EMhN" ) . Should ( ) . Equal ( new [ ] { 31 , 41 } ) ;
105+ _hashids . Decode ( "glSgV" ) . Should ( ) . Equal ( new [ ] { 13 , 89 } ) ;
106+ }
107+
108+ [ Fact ]
109+ public void ListOfInt_Roundtrip ( )
110+ {
111+ var input = new [ ] { 12345 , 67890 , int . MaxValue } ;
112+ var decodedValue = _hashids . Decode ( _hashids . Encode ( input ) ) ;
113+ decodedValue . Should ( ) . BeEquivalentTo ( input ) ;
114+ }
115+
116+ [ Fact ]
117+ public void ListOfLong_Encodes ( )
118+ {
119+ _hashids . EncodeLong ( 666555444333222L , 12345678901112L ) . Should ( ) . Be ( "mPVbjj7yVMzCJL215n69" ) ;
120+ }
121+
122+ [ Fact ]
123+ public void ListOfLong_Decodes ( )
124+ {
125+ _hashids . DecodeLong ( "mPVbjj7yVMzCJL215n69" ) . Should ( ) . Equal ( new [ ] { 666555444333222L , 12345678901112L } ) ;
126+ }
127+
128+ [ Fact ]
129+ public void ListOfLong_Roundtrip ( )
130+ {
131+ var input = new [ ] { 1L , 1234567890123456789 , long . MaxValue } ;
132+ var decodedValue = _hashids . DecodeLong ( _hashids . EncodeLong ( input ) ) ;
133+ decodedValue . Should ( ) . BeEquivalentTo ( input ) ;
134+ }
135+
136+ [ Fact ]
137+ public void HexString_Encode ( )
138+ {
139+ _hashids . EncodeHex ( "FA" ) . Should ( ) . Be ( "lzY" ) ;
140+ _hashids . EncodeHex ( "26dd" ) . Should ( ) . Be ( "MemE" ) ;
141+ _hashids . EncodeHex ( "FF1A" ) . Should ( ) . Be ( "eBMrb" ) ;
142+ _hashids . EncodeHex ( "12abC" ) . Should ( ) . Be ( "D9NPE" ) ;
143+ _hashids . EncodeHex ( "185b0" ) . Should ( ) . Be ( "9OyNW" ) ;
144+ _hashids . EncodeHex ( "17b8d" ) . Should ( ) . Be ( "MRWNE" ) ;
145+ _hashids . EncodeHex ( "1d7f21dd38" ) . Should ( ) . Be ( "4o6Z7KqxE" ) ;
146+ _hashids . EncodeHex ( "20015111d" ) . Should ( ) . Be ( "ooweQVNB" ) ;
147+ }
148+
149+ [ Fact ]
150+ public void HexString_Decode ( )
151+ {
152+ _hashids . DecodeHex ( "lzY" ) . Should ( ) . Be ( "FA" ) ;
153+ _hashids . DecodeHex ( "eBMrb" ) . Should ( ) . Be ( "FF1A" ) ;
154+ _hashids . DecodeHex ( "D9NPE" ) . Should ( ) . Be ( "12ABC" ) ;
155+ }
156+
157+ [ Fact ]
158+ public void HexString_Roundtrip ( )
159+ {
160+ var hashids = new Hashids ( "this is my salt" ) ;
161+
162+ var encoded = hashids . EncodeHex ( "DEADBEEF" ) ;
163+ encoded . Should ( ) . Be ( "kRNrpKlJ" ) ;
164+
165+ var decoded = hashids . DecodeHex ( encoded ) ;
166+ decoded . Should ( ) . Be ( "DEADBEEF" ) ;
167+
168+ var input2 = "1234567890ABCDEF" ;
169+ var decoded2 = hashids . DecodeHex ( hashids . EncodeHex ( input2 ) ) ;
170+ decoded2 . Should ( ) . Be ( input2 ) ;
171+ }
172+
173+ [ Fact ]
174+ public void NumbersIncludingZero_AtStart_Roundtrip ( )
175+ {
176+ var input = new [ ] { 0 , 1 , 2 } ;
177+ var decodedValue = _hashids . Decode ( _hashids . Encode ( input ) ) ;
178+ decodedValue . Should ( ) . Equal ( input ) ;
179+ }
180+
181+ [ Fact ]
182+ public void NumbersIncludingZero_AtEnd_Roundtrip ( )
183+ {
184+ var input = new [ ] { 1 , 2 , 0 } ;
185+ var decodedValue = _hashids . Decode ( _hashids . Encode ( input ) ) ;
186+ decodedValue . Should ( ) . Equal ( input ) ;
187+ }
188+
189+ [ Fact ]
190+ public void ListOfIdenticalNumbers_DoNotProducePattern ( )
191+ {
192+ _hashids . Encode ( 5 , 5 , 5 , 5 ) . Should ( ) . Be ( "1Wc8cwcE" ) ;
193+ }
194+
195+ [ Fact ]
196+ public void ListOfIncrementingNumbers_DoNotProducePattern ( )
197+ {
198+ _hashids . Encode ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) . Should ( ) . Be ( "kRHnurhptKcjIDTWC3sx" ) ;
199+ }
200+
201+ [ Fact ]
202+ public void IncrementingNumbers_DoNotProduceSimilarPattern ( )
203+ {
204+ _hashids . Encode ( 1 ) . Should ( ) . Be ( "NV" ) ;
205+ _hashids . Encode ( 2 ) . Should ( ) . Be ( "6m" ) ;
206+ _hashids . Encode ( 3 ) . Should ( ) . Be ( "yD" ) ;
207+ _hashids . Encode ( 4 ) . Should ( ) . Be ( "2l" ) ;
208+ _hashids . Encode ( 5 ) . Should ( ) . Be ( "rD" ) ;
209+ }
210+
211+ [ Fact ]
212+ public void NoNumbers_ReturnsEmptyString ( )
213+ {
214+ _hashids . Encode ( ) . Should ( ) . Be ( string . Empty ) ;
215+ _hashids . EncodeLong ( ) . Should ( ) . Be ( string . Empty ) ;
216+ }
217+
218+ [ Fact ]
219+ public void DecodeInvalidHexString_ReturnsEmptyString ( )
220+ {
221+ _hashids . EncodeHex ( "XYZ123" ) . Should ( ) . Be ( string . Empty ) ;
222+ }
223+
224+ [ Fact ]
225+ public void ListWithNegativeNumbers_ReturnsEmptyString ( )
226+ {
227+ _hashids . Encode ( 1 , int . MaxValue , - 3 ) . Should ( ) . Be ( string . Empty ) ;
228+ _hashids . EncodeLong ( 1 , long . MaxValue , - 4 ) . Should ( ) . Be ( string . Empty ) ;
229+ }
230+
231+ [ Fact ]
232+ public void DifferentSalt_ReturnsEmptyList ( )
233+ {
234+ _hashids . Decode ( "NkK9" ) . Should ( ) . Equal ( new [ ] { 12345 } ) ;
235+ new Hashids ( "different salt" ) . Decode ( "NkK9" ) . Should ( ) . Equal ( new int [ 0 ] ) ;
236+ }
237+
238+ [ Fact ]
239+ public void HashMinLength_EncodesHashWithAtLeastThatLength ( )
240+ {
241+ var hashLength = 18 ;
242+ var hashids = new Hashids ( salt , hashLength ) ;
243+ hashids . Encode ( 1 ) . Length . Should ( ) . BeGreaterOrEqualTo ( hashLength ) ;
244+ hashids . Encode ( 4140 , 21147 , 115975 , 678570 , 4213597 , 27644437 ) . Length . Should ( ) . BeGreaterOrEqualTo ( hashLength ) ;
245+ }
246+
247+ [ Fact ]
248+ public void HashMinLength_Decodes ( )
249+ {
250+ var hashids = new Hashids ( salt , 8 ) ;
251+ hashids . Decode ( "gB0NV05e" ) . Should ( ) . Equal ( new [ ] { 1 } ) ;
252+ hashids . Decode ( "mxi8XH87" ) . Should ( ) . Equal ( new [ ] { 25 , 100 , 950 } ) ;
253+ hashids . Decode ( "KQcmkIW8hX" ) . Should ( ) . Equal ( new [ ] { 5 , 200 , 195 , 1 } ) ;
254+ }
255+
256+ [ Fact ]
257+ public void AlphabetContainsLessThan4UniqueChars_ThrowsArgumentException ( )
258+ {
259+ Action invocation = ( ) => new Hashids ( alphabet : "aadsss" ) ;
260+ invocation . Should ( ) . Throw < ArgumentException > ( ) ;
261+ }
262+
263+ [ Fact ]
264+ public void AlphabetWithDashes_Roundtrip ( )
265+ {
266+ var hashids = new Hashids ( alphabet : "abcdefghijklmnopqrstuvwxyz1234567890_-" ) ;
267+ var input = new long [ ] { 1 , 2 , 3 } ;
268+ var decodedValue = hashids . DecodeLong ( hashids . EncodeLong ( input ) ) ;
269+ decodedValue . Should ( ) . BeEquivalentTo ( input ) ;
270+ }
271+
272+ [ Fact ]
273+ public void AlphabetLessThanMinLength_ThrowsArgumentException ( )
274+ {
275+ var tooShortAlphabet = Hashids . DEFAULT_ALPHABET . Substring ( 0 , Hashids . MIN_ALPHABET_LENGTH - 1 ) ;
276+ Action invocation = ( ) => new Hashids ( alphabet : tooShortAlphabet ) ;
277+ invocation . Should ( ) . Throw < ArgumentException > ( ) ;
278+ }
279+
280+ [ Fact ]
281+ public void AlphabetAtLeastMinLength_ShouldNotThrowException ( )
282+ {
283+ var minLengthAlphabet = Hashids . DEFAULT_ALPHABET . Substring ( 0 , Hashids . MIN_ALPHABET_LENGTH ) ;
284+ var largerLengthAlphabet = Hashids . DEFAULT_ALPHABET . Substring ( 0 , Hashids . MIN_ALPHABET_LENGTH + 1 ) ;
285+ var results1 = new Hashids ( alphabet : minLengthAlphabet ) ;
286+ var results2 = new Hashids ( alphabet : largerLengthAlphabet ) ;
287+ }
288+
289+ [ Fact ]
290+ public void NullAlphabet_ThrowsNullArgumentException ( )
291+ {
292+ Action invocation = ( ) => new Hashids ( alphabet : null ) ;
293+ invocation . Should ( ) . Throw < ArgumentNullException > ( ) ;
294+ }
295+
296+ [ Fact ]
297+ public void CustomAlphabet_Roundtrip ( )
298+ {
299+ var hashids = new Hashids ( salt , 0 , "ABCDEFGhijklmn34567890-:" ) ;
300+ var input = new [ ] { 1 , 2 , 3 , 4 , 5 } ;
301+ hashids . Encode ( input ) . Should ( ) . Be ( "6nhmFDikA0" ) ;
302+ hashids . Decode ( hashids . Encode ( input ) ) . Should ( ) . BeEquivalentTo ( input ) ;
303+ }
304+
305+ [ Fact ]
306+ public void CustomAlphabet2_Roundtrip ( )
307+ {
308+ var hashids = new Hashids ( salt , 0 , "ABCDEFGHIJKMNOPQRSTUVWXYZ23456789" ) ;
309+ var input = new [ ] { 1 , 2 , 3 , 4 , 5 } ;
310+ hashids . Encode ( input ) . Should ( ) . Be ( "44HYIRU3TO" ) ;
311+ hashids . Decode ( hashids . Encode ( input ) ) . Should ( ) . BeEquivalentTo ( input ) ;
312+ }
313+
314+ [ Fact ]
315+ public void SaltIsLongerThanAlphabet_Roundtrip ( )
316+ {
317+ var longSalt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" ;
318+ var hashids = new Hashids ( salt : longSalt ) ;
319+ var input = new [ ] { 1 , 2 , 0 } ;
320+ var decodedValue = hashids . Decode ( hashids . Encode ( input ) ) ;
321+ decodedValue . Should ( ) . Equal ( input ) ;
322+ }
323+
324+ [ Fact ]
325+ public void GuardCharacterOnly_DecodesToEmptyArray ( )
326+ {
327+ // no salt creates guard characters: "abde"
328+ var hashids = new Hashids ( "" ) ;
329+ var decodedValue = hashids . Decode ( "a" ) ;
330+ decodedValue . Should ( ) . Equal ( Array . Empty < int > ( ) ) ;
331+ }
332+
333+ [ Fact ]
334+ public void PublicMethodsCanBeMocked ( )
335+ {
336+ var mock = new Mock < Hashids > ( ) ;
337+ mock . Setup ( hashids => hashids . Encode ( It . IsAny < int [ ] > ( ) ) ) . Returns ( "It works" ) ;
338+ mock . Object . Encode ( new [ ] { 1 } ) . Should ( ) . Be ( "It works" ) ;
339+ }
340+ }
341+ }
0 commit comments