1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+
4+ #pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
5+
6+ using System . ComponentModel . DataAnnotations ;
7+ using System . Globalization ;
8+ using System . Text . Json ;
9+ using System . Text . Json . Serialization ;
10+
11+ namespace Microsoft . AspNetCore . Http . Validation ;
12+
13+ public class ValidateContextTests
14+ {
15+ [ Fact ]
16+ public void AddValidationError_FormatsCamelCaseKeys_WithSerializerOptions ( )
17+ {
18+ // Arrange
19+ var context = CreateValidateContext ( ) ;
20+ context . SerializerOptions = new JsonSerializerOptions
21+ {
22+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase
23+ } ;
24+
25+ // Act
26+ context . AddValidationError ( "PropertyName" , [ "Error" ] ) ;
27+
28+ // Assert
29+ Assert . NotNull ( context . ValidationErrors ) ;
30+ Assert . True ( context . ValidationErrors . ContainsKey ( "propertyName" ) ) ;
31+ }
32+
33+ [ Fact ]
34+ public void AddValidationError_FormatsSimpleKeys_WithSerializerOptions ( )
35+ {
36+ // Arrange
37+ var context = CreateValidateContext ( ) ;
38+ context . SerializerOptions = new JsonSerializerOptions
39+ {
40+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase
41+ } ;
42+
43+ // Act
44+ context . AddValidationError ( "ThisIsAProperty" , [ "Error" ] ) ;
45+
46+ // Assert
47+ Assert . NotNull ( context . ValidationErrors ) ;
48+ Assert . True ( context . ValidationErrors . ContainsKey ( "thisIsAProperty" ) ) ;
49+ }
50+
51+ [ Fact ]
52+ public void FormatComplexKey_FormatsNestedProperties_WithDots ( )
53+ {
54+ // Arrange
55+ var context = CreateValidateContext ( ) ;
56+ context . SerializerOptions = new JsonSerializerOptions
57+ {
58+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase
59+ } ;
60+
61+ // Act
62+ context . AddValidationError ( "Customer.Address.Street" , [ "Error" ] ) ;
63+
64+ // Assert
65+ Assert . NotNull ( context . ValidationErrors ) ;
66+ Assert . True ( context . ValidationErrors . ContainsKey ( "customer.address.street" ) ) ;
67+ }
68+
69+ [ Fact ]
70+ public void FormatComplexKey_PreservesArrayIndices ( )
71+ {
72+ // Arrange
73+ var context = CreateValidateContext ( ) ;
74+ context . SerializerOptions = new JsonSerializerOptions
75+ {
76+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase
77+ } ;
78+
79+ // Act
80+ context . AddValidationError ( "Items[0].ProductName" , [ "Error" ] ) ;
81+
82+ // Assert
83+ Assert . NotNull ( context . ValidationErrors ) ;
84+ Assert . True ( context . ValidationErrors . ContainsKey ( "items[0].productName" ) ) ;
85+ Assert . False ( context . ValidationErrors . ContainsKey ( "items[0].ProductName" ) ) ;
86+ }
87+
88+ [ Fact ]
89+ public void FormatComplexKey_HandlesMultipleArrayIndices ( )
90+ {
91+ // Arrange
92+ var context = CreateValidateContext ( ) ;
93+ context . SerializerOptions = new JsonSerializerOptions
94+ {
95+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase
96+ } ;
97+
98+ // Act
99+ context . AddValidationError ( "Orders[0].Items[1].ProductName" , [ "Error" ] ) ;
100+
101+ // Assert
102+ Assert . NotNull ( context . ValidationErrors ) ;
103+ Assert . True ( context . ValidationErrors . ContainsKey ( "orders[0].items[1].productName" ) ) ;
104+ }
105+
106+ [ Fact ]
107+ public void FormatComplexKey_HandlesNestedArraysWithoutProperties ( )
108+ {
109+ // Arrange
110+ var context = CreateValidateContext ( ) ;
111+ context . SerializerOptions = new JsonSerializerOptions
112+ {
113+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase
114+ } ;
115+
116+ // Act
117+ context . AddValidationError ( "Matrix[0][1]" , [ "Error" ] ) ;
118+
119+ // Assert
120+ Assert . NotNull ( context . ValidationErrors ) ;
121+ Assert . True ( context . ValidationErrors . ContainsKey ( "matrix[0][1]" ) ) ;
122+ }
123+
124+ [ Fact ]
125+ public void FormatKey_ReturnsOriginalKey_WhenSerializerOptionsIsNull ( )
126+ {
127+ // Arrange
128+ var context = CreateValidateContext ( ) ;
129+ context . SerializerOptions = null ;
130+
131+ // Act
132+ context . AddValidationError ( "PropertyName" , [ "Error" ] ) ;
133+
134+ // Assert
135+ Assert . NotNull ( context . ValidationErrors ) ;
136+ Assert . True ( context . ValidationErrors . ContainsKey ( "PropertyName" ) ) ;
137+ }
138+
139+ [ Fact ]
140+ public void FormatKey_ReturnsOriginalKey_WhenPropertyNamingPolicyIsNull ( )
141+ {
142+ // Arrange
143+ var context = CreateValidateContext ( ) ;
144+ context . SerializerOptions = new JsonSerializerOptions
145+ {
146+ PropertyNamingPolicy = null
147+ } ;
148+
149+ // Act
150+ context . AddValidationError ( "PropertyName" , [ "Error" ] ) ;
151+
152+ // Assert
153+ Assert . NotNull ( context . ValidationErrors ) ;
154+ Assert . True ( context . ValidationErrors . ContainsKey ( "PropertyName" ) ) ;
155+ }
156+
157+ [ Fact ]
158+ public void FormatKey_AppliesKebabCaseNamingPolicy ( )
159+ {
160+ // Arrange
161+ var context = CreateValidateContext ( ) ;
162+ context . SerializerOptions = new JsonSerializerOptions
163+ {
164+ PropertyNamingPolicy = new KebabCaseNamingPolicy ( )
165+ } ;
166+
167+ // Act
168+ context . AddValidationError ( "ProductName" , [ "Error" ] ) ;
169+ context . AddValidationError ( "OrderItems[0].ProductName" , [ "Error" ] ) ;
170+
171+ // Assert
172+ Assert . NotNull ( context . ValidationErrors ) ;
173+ Assert . True ( context . ValidationErrors . ContainsKey ( "product-name" ) ) ;
174+ Assert . True ( context . ValidationErrors . ContainsKey ( "order-items[0].product-name" ) ) ;
175+ }
176+
177+ private static ValidateContext CreateValidateContext ( )
178+ {
179+ var serviceProvider = new EmptyServiceProvider ( ) ;
180+ var options = new ValidationOptions ( ) ;
181+ var validationContext = new ValidationContext ( new object ( ) , serviceProvider , null ) ;
182+
183+ return new ValidateContext
184+ {
185+ ValidationContext = validationContext ,
186+ ValidationOptions = options
187+ } ;
188+ }
189+
190+ private class KebabCaseNamingPolicy : JsonNamingPolicy
191+ {
192+ public override string ConvertName ( string name )
193+ {
194+ if ( string . IsNullOrEmpty ( name ) )
195+ {
196+ return name ;
197+ }
198+
199+ var result = string . Empty ;
200+
201+ for ( int i = 0 ; i < name . Length ; i ++ )
202+ {
203+ if ( i > 0 && char . IsUpper ( name [ i ] ) )
204+ {
205+ result += "-" ;
206+ }
207+
208+ result += char . ToLower ( name [ i ] , CultureInfo . InvariantCulture ) ;
209+ }
210+
211+ return result ;
212+ }
213+ }
214+
215+ private class EmptyServiceProvider : IServiceProvider
216+ {
217+ public object ? GetService ( Type serviceType ) => null ;
218+ }
219+ }
0 commit comments