77using System . ClientModel . Primitives ;
88using System . Collections . Generic ;
99using System . Reflection ;
10+ using System . Runtime . InteropServices ;
1011using System . Threading . Tasks ;
1112
1213namespace System . ClientModel . Tests . Pipeline ;
@@ -181,11 +182,13 @@ public void GenerateUserAgentString_ProducesValidUserAgent()
181182 Assembly assembly = Assembly . GetExecutingAssembly ( ) ;
182183
183184 // Test without application ID
184- string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly ) ;
185- Assert . IsNotNull ( userAgent ) ;
186- Assert . IsNotEmpty ( userAgent ) ;
185+ var policy = new UserAgentPolicy ( assembly ) ;
186+ Assert . IsNotNull ( policy ) ;
187+ Assert . IsNull ( policy . ApplicationId ) ;
188+ Assert . AreEqual ( assembly , policy . Assembly ) ;
187189
188190 // Should contain assembly name and version
191+ var userAgent = policy . UserAgentValue ;
189192 string assemblyName = assembly . GetName ( ) . Name ! ;
190193 Assert . That ( userAgent , Does . Contain ( assemblyName ) ) ;
191194
@@ -200,9 +203,11 @@ public void GenerateUserAgentString_WithApplicationId_ProducesValidUserAgent()
200203 Assembly assembly = Assembly . GetExecutingAssembly ( ) ;
201204 string applicationId = "TestApp/1.0" ;
202205
203- string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly , applicationId ) ;
204- Assert . IsNotNull ( userAgent ) ;
205- Assert . IsNotEmpty ( userAgent ) ;
206+ var policy = new UserAgentPolicy ( assembly , applicationId ) ;
207+ Assert . IsNotNull ( policy ) ;
208+ Assert . AreEqual ( applicationId , policy . ApplicationId ) ;
209+ Assert . AreEqual ( assembly , policy . Assembly ) ;
210+ var userAgent = policy . UserAgentValue ;
206211
207212 // Should start with application ID
208213 Assert . That ( userAgent , Does . StartWith ( applicationId ) ) ;
@@ -211,4 +216,150 @@ public void GenerateUserAgentString_WithApplicationId_ProducesValidUserAgent()
211216 string assemblyName = assembly . GetName ( ) . Name ! ;
212217 Assert . That ( userAgent , Does . Contain ( assemblyName ) ) ;
213218 }
214- }
219+
220+ [ Test ]
221+ [ TestCase ( "ValidParens (2023-)" , "ValidParens (2023-)" ) ]
222+ [ TestCase ( "(ValidParens (2023-))" , "(ValidParens (2023-))" ) ]
223+ [ TestCase ( "ProperlyEscapedParens \\ (2023-\\ )" , "ProperlyEscapedParens \\ (2023-\\ )" ) ]
224+ [ TestCase ( "UnescapedOnlyParens (2023-)" , "UnescapedOnlyParens (2023-)" ) ]
225+ [ TestCase ( "UnmatchedOpenParen (2023-" , "UnmatchedOpenParen \\ (2023-" ) ]
226+ [ TestCase ( "UnEscapedParenWithValidParens (()" , "UnEscapedParenWithValidParens \\ (\\ (\\ )" ) ]
227+ [ TestCase ( "UnEscapedInvalidParen (" , "UnEscapedInvalidParen \\ (" ) ]
228+ [ TestCase ( "UnEscapedParenWithValidParens2 ())" , "UnEscapedParenWithValidParens2 \\ (\\ )\\ )" ) ]
229+ [ TestCase ( "InvalidParen )" , "InvalidParen \\ )" ) ]
230+ [ TestCase ( "(InvalidParen " , "\\ (InvalidParen " ) ]
231+ [ TestCase ( "UnescapedParenInText MyO)SDescription " , "UnescapedParenInText MyO\\ )SDescription " ) ]
232+ [ TestCase ( "UnescapedParenInText MyO(SDescription " , "UnescapedParenInText MyO\\ (SDescription " ) ]
233+ public void ValidatesProperParenthesisMatching ( string input , string output )
234+ {
235+ var mockRuntimeInformation = new MockRuntimeInformation
236+ {
237+ OSDescriptionMock = input ,
238+ FrameworkDescriptionMock = RuntimeInformation . FrameworkDescription
239+ } ;
240+ var assembly = Assembly . GetExecutingAssembly ( ) ;
241+ AssemblyInformationalVersionAttribute ? versionAttribute = assembly . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ;
242+ string version = versionAttribute ! . InformationalVersion ;
243+ int hashSeparator = version . IndexOf ( '+' ) ;
244+ if ( hashSeparator != - 1 )
245+ {
246+ version = version . Substring ( 0 , hashSeparator ) ;
247+ }
248+
249+ string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly , null , mockRuntimeInformation ) ;
250+ string assemblyName = assembly . GetName ( ) . Name ! ;
251+
252+ Assert . AreEqual (
253+ $ "{ assemblyName } /{ version } ({ mockRuntimeInformation . FrameworkDescription } ; { output } )",
254+ userAgent ) ;
255+ }
256+
257+ [ Test ]
258+ [ TestCase ( "Win64; x64" , "Win64; x64" ) ]
259+ [ TestCase ( "Intel Mac OS X 10_15_7" , "Intel Mac OS X 10_15_7" ) ]
260+ [ TestCase ( "Android 10; SM-G973F" , "Android 10; SM-G973F" ) ]
261+ [ TestCase ( "Win64; x64; Xbox; Xbox One" , "Win64; x64; Xbox; Xbox One" ) ]
262+ public void AsciiDoesNotEncode ( string input , string output )
263+ {
264+ var mockRuntimeInformation = new MockRuntimeInformation
265+ {
266+ OSDescriptionMock = input ,
267+ FrameworkDescriptionMock = RuntimeInformation . FrameworkDescription
268+ } ;
269+ var assembly = Assembly . GetExecutingAssembly ( ) ;
270+ AssemblyInformationalVersionAttribute ? versionAttribute = assembly . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ;
271+ string version = versionAttribute ! . InformationalVersion ;
272+ int hashSeparator = version . IndexOf ( '+' ) ;
273+ if ( hashSeparator != - 1 )
274+ {
275+ version = version . Substring ( 0 , hashSeparator ) ;
276+ }
277+
278+ string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly , null , mockRuntimeInformation ) ;
279+ string assemblyName = assembly . GetName ( ) . Name ! ;
280+
281+ Assert . AreEqual (
282+ $ "{ assemblyName } /{ version } ({ mockRuntimeInformation . FrameworkDescription } ; { output } )",
283+ userAgent ) ;
284+ }
285+
286+ [ Test ]
287+ [ TestCase ( "»-Browser¢sample" , "%C2%BB-Browser%C2%A2sample" ) ]
288+ [ TestCase ( "NixOS 24.11 (Vicuña)" , "NixOS+24.11+(Vicu%C3%B1a)" ) ]
289+ public void NonAsciiCharactersAreUrlEncoded ( string input , string output )
290+ {
291+ var mockRuntimeInformation = new MockRuntimeInformation
292+ {
293+ OSDescriptionMock = input ,
294+ FrameworkDescriptionMock = RuntimeInformation . FrameworkDescription
295+ } ;
296+ var assembly = Assembly . GetExecutingAssembly ( ) ;
297+ AssemblyInformationalVersionAttribute ? versionAttribute = assembly . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ;
298+ string version = versionAttribute ! . InformationalVersion ;
299+ int hashSeparator = version . IndexOf ( '+' ) ;
300+ if ( hashSeparator != - 1 )
301+ {
302+ version = version . Substring ( 0 , hashSeparator ) ;
303+ }
304+
305+ string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly , null , mockRuntimeInformation ) ;
306+ string assemblyName = assembly . GetName ( ) . Name ! ;
307+
308+ Assert . AreEqual (
309+ $ "{ assemblyName } /{ version } ({ mockRuntimeInformation . FrameworkDescription } ; { output } )",
310+ userAgent ) ;
311+ }
312+
313+ [ Test ]
314+ public void GenerateUserAgentString_WithCustomRuntimeInfo_ProducesValidUserAgent ( )
315+ {
316+ var assembly = Assembly . GetExecutingAssembly ( ) ;
317+ var mockRuntimeInfo = new MockRuntimeInformation
318+ {
319+ OSDescriptionMock = "Test OS" ,
320+ FrameworkDescriptionMock = "Test Framework"
321+ } ;
322+
323+ string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly , null , mockRuntimeInfo ) ;
324+
325+ // Get expected values
326+ string assemblyName = assembly . GetName ( ) . Name ! ;
327+ AssemblyInformationalVersionAttribute ? versionAttribute = assembly . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ;
328+ string version = versionAttribute ! . InformationalVersion ;
329+ int hashSeparator = version . IndexOf ( '+' ) ;
330+ if ( hashSeparator != - 1 )
331+ {
332+ version = version . Substring ( 0 , hashSeparator ) ;
333+ }
334+
335+ string expectedUserAgent = $ "{ assemblyName } /{ version } ({ mockRuntimeInfo . FrameworkDescriptionMock } ; { mockRuntimeInfo . OSDescriptionMock } )";
336+ Assert . AreEqual ( expectedUserAgent , userAgent ) ;
337+ }
338+
339+ [ Test ]
340+ public void GenerateUserAgentString_WithCustomRuntimeInfoAndApplicationId_ProducesValidUserAgent ( )
341+ {
342+ var assembly = Assembly . GetExecutingAssembly ( ) ;
343+ string applicationId = "TestApp/1.0" ;
344+ var mockRuntimeInfo = new MockRuntimeInformation
345+ {
346+ OSDescriptionMock = "Test OS" ,
347+ FrameworkDescriptionMock = "Test Framework"
348+ } ;
349+
350+ string userAgent = UserAgentPolicy . GenerateUserAgentString ( assembly , applicationId , mockRuntimeInfo ) ;
351+
352+ // Get expected values
353+ string assemblyName = assembly . GetName ( ) . Name ! ;
354+ AssemblyInformationalVersionAttribute ? versionAttribute = assembly . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ;
355+ string version = versionAttribute ! . InformationalVersion ;
356+ int hashSeparator = version . IndexOf ( '+' ) ;
357+ if ( hashSeparator != - 1 )
358+ {
359+ version = version . Substring ( 0 , hashSeparator ) ;
360+ }
361+
362+ string expectedUserAgent = $ "{ applicationId } { assemblyName } /{ version } ({ mockRuntimeInfo . FrameworkDescriptionMock } ; { mockRuntimeInfo . OSDescriptionMock } )";
363+ Assert . AreEqual ( expectedUserAgent , userAgent ) ;
364+ }
365+ }
0 commit comments