@@ -263,4 +263,102 @@ public void OtlpExporterOptions_ApplyDefaultsTest()
263263 Assert . NotEqual ( defaultOptionsWithData . TimeoutMilliseconds , targetOptionsWithData . TimeoutMilliseconds ) ;
264264 Assert . NotEqual ( defaultOptionsWithData . HttpClientFactory , targetOptionsWithData . HttpClientFactory ) ;
265265 }
266+
267+ [ Fact ]
268+ public void UserAgentProductIdentifier_Default_IsEmpty ( )
269+ {
270+ var options = new OtlpExporterOptions ( ) ;
271+
272+ Assert . Equal ( string . Empty , options . UserAgentProductIdentifier ) ;
273+ }
274+
275+ [ Fact ]
276+ public void UserAgentProductIdentifier_DefaultUserAgent_ContainsExporterInfo ( )
277+ {
278+ var options = new OtlpExporterOptions ( ) ;
279+
280+ var userAgentHeader = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) ;
281+
282+ Assert . NotNull ( userAgentHeader . Key ) ;
283+ Assert . StartsWith ( "OTel-OTLP-Exporter-Dotnet/" , userAgentHeader . Value , StringComparison . OrdinalIgnoreCase ) ;
284+ }
285+
286+ [ Fact ]
287+ public void UserAgentProductIdentifier_WithProductIdentifier_IsPrepended ( )
288+ {
289+ var options = new OtlpExporterOptions
290+ {
291+ UserAgentProductIdentifier = "MyDistribution/1.2.3" ,
292+ } ;
293+
294+ Assert . Equal ( "MyDistribution/1.2.3" , options . UserAgentProductIdentifier ) ;
295+
296+ var userAgentHeader = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) ;
297+
298+ Assert . NotNull ( userAgentHeader . Key ) ;
299+ Assert . StartsWith ( "MyDistribution/1.2.3 OTel-OTLP-Exporter-Dotnet/" , userAgentHeader . Value , StringComparison . OrdinalIgnoreCase ) ;
300+ }
301+
302+ [ Fact ]
303+ public void UserAgentProductIdentifier_UpdatesStandardHeaders ( )
304+ {
305+ var options = new OtlpExporterOptions ( ) ;
306+
307+ var initialUserAgent = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) . Value ;
308+ Assert . StartsWith ( "OTel-OTLP-Exporter-Dotnet/" , initialUserAgent , StringComparison . OrdinalIgnoreCase ) ;
309+
310+ options . UserAgentProductIdentifier = "MyProduct/1.0.0" ;
311+
312+ var updatedUserAgent = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) . Value ;
313+ Assert . StartsWith ( "MyProduct/1.0.0 OTel-OTLP-Exporter-Dotnet/" , updatedUserAgent , StringComparison . OrdinalIgnoreCase ) ;
314+ Assert . NotEqual ( initialUserAgent , updatedUserAgent ) ;
315+ }
316+
317+ [ Fact ]
318+ public void UserAgentProductIdentifier_Rfc7231Compliance_SpaceSeparatedTokens ( )
319+ {
320+ var options = new OtlpExporterOptions
321+ {
322+ UserAgentProductIdentifier = "MyProduct/1.0.0" ,
323+ } ;
324+
325+ var userAgentHeader = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) . Value ;
326+
327+ // Should have two product tokens separated by a space
328+ var tokens = userAgentHeader . Split ( ' ' ) ;
329+ Assert . Equal ( 2 , tokens . Length ) ;
330+ Assert . Equal ( "MyProduct/1.0.0" , tokens [ 0 ] ) ;
331+ Assert . StartsWith ( "OTel-OTLP-Exporter-Dotnet/" , tokens [ 1 ] , StringComparison . OrdinalIgnoreCase ) ;
332+ }
333+
334+ [ Theory ]
335+ [ InlineData ( "" ) ]
336+ [ InlineData ( " " ) ]
337+ [ InlineData ( " " ) ]
338+ public void UserAgentProductIdentifier_EmptyOrWhitespace_UsesDefaultUserAgent ( string identifier )
339+ {
340+ var options = new OtlpExporterOptions
341+ {
342+ UserAgentProductIdentifier = identifier ,
343+ } ;
344+
345+ var userAgentHeader = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) . Value ;
346+
347+ // Should only contain the default exporter identifier, no leading space
348+ Assert . StartsWith ( "OTel-OTLP-Exporter-Dotnet/" , userAgentHeader , StringComparison . OrdinalIgnoreCase ) ;
349+ Assert . DoesNotContain ( " " , userAgentHeader , StringComparison . OrdinalIgnoreCase ) ; // No double spaces
350+ }
351+
352+ [ Fact ]
353+ public void UserAgentProductIdentifier_MultipleProducts_CorrectFormat ( )
354+ {
355+ var options = new OtlpExporterOptions
356+ {
357+ UserAgentProductIdentifier = "MySDK/2.0.0 MyDistribution/1.0.0" ,
358+ } ;
359+
360+ var userAgentHeader = OtlpExporterOptions . StandardHeaders . FirstOrDefault ( h => h . Key == "User-Agent" ) . Value ;
361+
362+ Assert . StartsWith ( "MySDK/2.0.0 MyDistribution/1.0.0 OTel-OTLP-Exporter-Dotnet/" , userAgentHeader , StringComparison . OrdinalIgnoreCase ) ;
363+ }
266364}
0 commit comments