@@ -27,7 +27,7 @@ public void HttpClientShouldBeCreatedIfNotProvider()
2727 [ Fact ]
2828 public void BasicAuthHeaderShouldBeCorrect ( )
2929 {
30- var credentials = new LokiCredentials { Login = "Billy" , Password = "Herrington" } ;
30+ var credentials = new LokiCredentials { Login = "Billy" , Password = "Herrington" } ;
3131 using var client = new TestLokiHttpClient ( ) ;
3232
3333 client . SetCredentials ( credentials ) ;
@@ -48,16 +48,96 @@ public void AuthorizationHeaderShouldNotBeSetWithoutCredentials()
4848 client . Client . DefaultRequestHeaders . Authorization . ShouldBeNull ( ) ;
4949 }
5050
51- [ Fact ]
52- public void TenantHeaderShouldBeCorrect ( )
51+ [ Theory ]
52+ [ InlineData ( "tenant123" , true ) ] // only alphanumeric
53+ [ InlineData ( "tenant-123" , true ) ] // allowed hyphen
54+ [ InlineData ( "tenant..123" , false ) ] // double period not allowed
55+ [ InlineData ( "." , false ) ] // single period not allowed
56+ [ InlineData ( "tenant!_*.123'()" , true ) ] // allowed special characters
57+ [ InlineData ( "tenant-123..." , false ) ] // ends with multiple periods
58+ [ InlineData ( "tenant123456...test" , false ) ] // ends with period
59+ [ InlineData ( "tenant1234567890!@" , false ) ] // '@' is not allowed
60+ [ InlineData ( "a" , true ) ] // minimal length
61+ [ InlineData ( "tenant_with_underscores" , true ) ] // underscores
62+ [ InlineData ( "tenant.." , false ) ] // ends with double period
63+ [ InlineData ( "..tenant" , false ) ] // starts with double period
64+ [ InlineData ( "tenant-.-test" , true ) ] // single periods inside are ok
65+ public void TenantHeaderShouldBeCorrect ( string tenantId , bool isValid )
66+ {
67+ using var client = new TestLokiHttpClient ( ) ;
68+
69+ if ( isValid )
70+ {
71+ // Act
72+ client . SetTenant ( tenantId ) ;
73+
74+ // Assert header is correctly set
75+ var tenantHeaders = client . Client . DefaultRequestHeaders
76+ . GetValues ( "X-Scope-OrgID" )
77+ . ToList ( ) ;
78+
79+ tenantHeaders . ShouldBeEquivalentTo ( new List < string > { tenantId } ) ;
80+ }
81+ else
82+ {
83+ // Act & Assert: invalid tenant IDs throw ArgumentException
84+ Should . Throw < ArgumentException > ( ( ) => client . SetTenant ( tenantId ) ) ;
85+ }
86+ }
87+
88+ // Allowed special characters
89+ [ Theory ]
90+ [ InlineData ( '!' , true ) ]
91+ [ InlineData ( '.' , true ) ]
92+ [ InlineData ( '_' , true ) ]
93+ [ InlineData ( '*' , true ) ]
94+ [ InlineData ( '\' ' , true ) ]
95+ [ InlineData ( '(' , true ) ]
96+ [ InlineData ( ')' , true ) ]
97+ [ InlineData ( '-' , true ) ]
98+
99+ // Disallowed special characters
100+ [ InlineData ( '@' , false ) ]
101+ [ InlineData ( '#' , false ) ]
102+ [ InlineData ( '&' , false ) ]
103+ [ InlineData ( '$' , false ) ]
104+ [ InlineData ( '%' , false ) ]
105+ [ InlineData ( '^' , false ) ]
106+ [ InlineData ( '=' , false ) ]
107+ [ InlineData ( '+' , false ) ]
108+ [ InlineData ( '[' , false ) ]
109+ [ InlineData ( ']' , false ) ]
110+ [ InlineData ( '{' , false ) ]
111+ [ InlineData ( '}' , false ) ]
112+ [ InlineData ( '<' , false ) ]
113+ [ InlineData ( '>' , false ) ]
114+ [ InlineData ( '?' , false ) ]
115+ [ InlineData ( '/' , false ) ]
116+ [ InlineData ( '\\ ' , false ) ]
117+ [ InlineData ( '|' , false ) ]
118+ [ InlineData ( '~' , false ) ]
119+ [ InlineData ( '"' , false ) ]
120+ public void TenantSpecialCharacterShouldValidateCorrectly ( char specialChar , bool isValid )
53121 {
54- var tenantId = "lokitenant" ;
55122 using var client = new TestLokiHttpClient ( ) ;
123+ string tenantId = "tenant" + specialChar + "123" ;
56124
57- client . SetTenant ( tenantId ) ;
125+ if ( isValid )
126+ {
127+ // Should succeed
128+ client . SetTenant ( tenantId ) ;
58129
59- var tenantHeaders = client . Client . DefaultRequestHeaders . GetValues ( "X-Scope-OrgID" ) . ToList ( ) ;
60- tenantHeaders . ShouldBeEquivalentTo ( new List < string > { "lokitenant" } ) ;
130+ var tenantHeaders = client . Client . DefaultRequestHeaders
131+ . GetValues ( "X-Scope-OrgID" )
132+ . ToList ( ) ;
133+
134+ tenantHeaders . ShouldBeEquivalentTo ( new List < string > { tenantId } ) ;
135+ }
136+ else
137+ {
138+ // Should throw
139+ Should . Throw < ArgumentException > ( ( ) => client . SetTenant ( tenantId ) ) ;
140+ }
61141 }
62142
63143 [ Fact ]
@@ -78,4 +158,110 @@ public void TenantHeaderShouldThrowAnExceptionOnTenantIdAgainstRule()
78158
79159 Should . Throw < ArgumentException > ( ( ) => client . SetTenant ( tenantId ) ) ;
80160 }
81- }
161+
162+ [ Theory ]
163+ [ InlineData ( "Custom-Header" , "HeaderValue" , true ) ]
164+ [ InlineData ( "X-Test" , "12345" , true ) ]
165+ [ InlineData ( "X-Correlation-ID" , "abcd-1234" , true ) ]
166+ [ InlineData ( "X-Feature-Flag" , "enabled" , true ) ]
167+ [ InlineData ( "" , "value" , false ) ]
168+ [ InlineData ( " " , "value" , false ) ]
169+ [ InlineData ( null , "value" , false ) ]
170+ [ InlineData ( "Invalid Header" , "value" , false ) ]
171+ [ InlineData ( "X-Test" , "" , false ) ]
172+ [ InlineData ( "X-Test" , null , false ) ]
173+ public void SetDefaultHeadersShouldValidateCorrectly ( string ? headerKey , string ? headerValue , bool isValid )
174+ {
175+ using var httpClient = new HttpClient ( ) ;
176+ var client = new TestLokiHttpClient ( httpClient ) ;
177+
178+ if ( isValid )
179+ {
180+ var headersToSet = new Dictionary < string , string >
181+ {
182+ { headerKey ! , headerValue ! }
183+ } ;
184+
185+ client . SetDefaultHeaders ( headersToSet ) ;
186+
187+ httpClient . DefaultRequestHeaders . Contains ( headerKey ! ) . ShouldBeTrue ( ) ;
188+ httpClient . DefaultRequestHeaders
189+ . GetValues ( headerKey ! )
190+ . ShouldBe ( new [ ] { headerValue } ) ;
191+ }
192+ else
193+ {
194+ Should . Throw < ArgumentException > ( ( ) =>
195+ {
196+ var headersToSet = new Dictionary < string , string >
197+ {
198+ { headerKey ! , headerValue ! }
199+ } ;
200+ client . SetDefaultHeaders ( headersToSet ) ;
201+ } ) ;
202+ }
203+ }
204+
205+ [ Theory ]
206+ [ InlineData ( '!' , true ) ]
207+ [ InlineData ( '#' , true ) ]
208+ [ InlineData ( '$' , true ) ]
209+ [ InlineData ( '%' , true ) ]
210+ [ InlineData ( '&' , true ) ]
211+ [ InlineData ( '\' ' , true ) ]
212+ [ InlineData ( '*' , true ) ]
213+ [ InlineData ( '+' , true ) ]
214+ [ InlineData ( '-' , true ) ]
215+ [ InlineData ( '.' , true ) ]
216+ [ InlineData ( '^' , true ) ]
217+ [ InlineData ( '_' , true ) ]
218+ [ InlineData ( '`' , true ) ]
219+ [ InlineData ( '|' , true ) ]
220+ [ InlineData ( '~' , true ) ]
221+ [ InlineData ( 'A' , true ) ]
222+ [ InlineData ( 'z' , true ) ]
223+ [ InlineData ( ' ' , false ) ]
224+ [ InlineData ( '(' , false ) ]
225+ [ InlineData ( ')' , false ) ]
226+ [ InlineData ( '<' , false ) ]
227+ [ InlineData ( '>' , false ) ]
228+ [ InlineData ( '@' , false ) ]
229+ [ InlineData ( ',' , false ) ]
230+ [ InlineData ( ';' , false ) ]
231+ [ InlineData ( ':' , false ) ]
232+ [ InlineData ( '"' , false ) ]
233+ [ InlineData ( '/' , false ) ]
234+ [ InlineData ( '[' , false ) ]
235+ [ InlineData ( ']' , false ) ]
236+ [ InlineData ( '?' , false ) ]
237+ [ InlineData ( '=' , false ) ]
238+ [ InlineData ( '{' , false ) ]
239+ [ InlineData ( '}' , false ) ]
240+ [ InlineData ( '\\ ' , false ) ]
241+ [ InlineData ( '\t ' , false ) ]
242+ public void DefaultHeaderCharactersShouldValidateCorrectly ( char character , bool isValid ) // Valid token characters according to RFC 7230
243+ {
244+ using var httpClient = new HttpClient ( ) ;
245+ var client = new TestLokiHttpClient ( httpClient ) ;
246+
247+ string headerKey = "X-Test" + character ;
248+ var headersToSet = new Dictionary < string , string >
249+ {
250+ { headerKey , "value" }
251+ } ;
252+
253+ if ( isValid )
254+ {
255+ // Should succeed
256+ client . SetDefaultHeaders ( headersToSet ) ;
257+
258+ httpClient . DefaultRequestHeaders . Contains ( headerKey ) . ShouldBeTrue ( ) ;
259+ httpClient . DefaultRequestHeaders . GetValues ( headerKey ) . ShouldHaveSingleItem ( ) . ShouldBe ( "value" ) ;
260+ }
261+ else
262+ {
263+ // Should throw exception
264+ Should . Throw < ArgumentException > ( ( ) => client . SetDefaultHeaders ( headersToSet ) ) ;
265+ }
266+ }
267+ }
0 commit comments