Skip to content

Commit ee9c087

Browse files
Copilotcaptainsafia
andcommitted
Update RuntimeValidatableTypeInfoResolverTests to validate actual test data
Co-authored-by: captainsafia <[email protected]>
1 parent 658d475 commit ee9c087

File tree

1 file changed

+280
-7
lines changed

1 file changed

+280
-7
lines changed

src/Validation/test/Microsoft.Extensions.Validation.Tests/RuntimeValidatableTypeInfoResolverTests.cs

Lines changed: 280 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// The .NET Foundation licenses this file to you under the MIT license.
55

66
using System.ComponentModel.DataAnnotations;
7+
using System.Linq;
78
using System.Reflection;
89

910
namespace Microsoft.Extensions.Validation.Tests;
@@ -52,13 +53,61 @@ public void TryGetValidatableTypeInfo_WithEnum_ReturnsFalse()
5253
}
5354

5455
[Fact]
55-
public void TryGetValidatableTypeInfo_WithSimplePocoWithValidationAttributes_ReturnsTrue()
56+
public async Task TryGetValidatableTypeInfo_WithSimplePocoWithValidationAttributes_ReturnsTrue_AndValidatesCorrectly()
5657
{
5758
var result = _resolver.TryGetValidatableTypeInfo(typeof(SimplePocoWithValidation), out var validatableInfo);
5859

5960
Assert.True(result);
6061
Assert.NotNull(validatableInfo);
6162
Assert.IsType<RuntimeValidatableTypeInfoResolver.RuntimeValidatableTypeInfo>(validatableInfo);
63+
64+
// Test validation with invalid data
65+
var invalidPoco = new SimplePocoWithValidation
66+
{
67+
Name = "", // Required but empty
68+
Age = 150 // Out of range (0-100)
69+
};
70+
71+
var validationOptions = new ValidationOptions();
72+
validationOptions.Resolvers.Add(_resolver);
73+
74+
var context = new ValidateContext
75+
{
76+
ValidationOptions = validationOptions,
77+
ValidationContext = new ValidationContext(invalidPoco)
78+
};
79+
80+
await validatableInfo.ValidateAsync(invalidPoco, context, default);
81+
82+
Assert.NotNull(context.ValidationErrors);
83+
Assert.Collection(context.ValidationErrors,
84+
kvp =>
85+
{
86+
Assert.Equal("Name", kvp.Key);
87+
Assert.Equal("The Name field is required.", kvp.Value.First());
88+
},
89+
kvp =>
90+
{
91+
Assert.Equal("Age", kvp.Key);
92+
Assert.Equal("The field Age must be between 0 and 100.", kvp.Value.First());
93+
});
94+
95+
// Test validation with valid data
96+
var validPoco = new SimplePocoWithValidation
97+
{
98+
Name = "John Doe",
99+
Age = 25
100+
};
101+
102+
var validContext = new ValidateContext
103+
{
104+
ValidationOptions = validationOptions,
105+
ValidationContext = new ValidationContext(validPoco)
106+
};
107+
108+
await validatableInfo.ValidateAsync(validPoco, validContext, default);
109+
110+
Assert.Null(validContext.ValidationErrors); // No validation errors for valid data
62111
}
63112

64113
[Fact]
@@ -71,30 +120,217 @@ public void TryGetValidatableTypeInfo_WithSimplePocoWithoutValidation_ReturnsFal
71120
}
72121

73122
[Fact]
74-
public void TryGetValidatableTypeInfo_WithNestedComplexType_ReturnsTrue()
123+
public async Task TryGetValidatableTypeInfo_WithNestedComplexType_ReturnsTrue_AndValidatesCorrectly()
75124
{
76125
var result = _resolver.TryGetValidatableTypeInfo(typeof(PocoWithNestedType), out var validatableInfo);
77126

78127
Assert.True(result);
79128
Assert.NotNull(validatableInfo);
129+
130+
// Test validation with invalid nested data
131+
var invalidPoco = new PocoWithNestedType
132+
{
133+
Name = "", // Required but empty
134+
NestedPoco = new SimplePocoWithValidation
135+
{
136+
Name = "", // Required but empty in nested object
137+
Age = -5 // Out of range (0-100) in nested object
138+
}
139+
};
140+
141+
var validationOptions = new ValidationOptions();
142+
validationOptions.Resolvers.Add(_resolver);
143+
144+
var context = new ValidateContext
145+
{
146+
ValidationOptions = validationOptions,
147+
ValidationContext = new ValidationContext(invalidPoco)
148+
};
149+
150+
await validatableInfo.ValidateAsync(invalidPoco, context, default);
151+
152+
Assert.NotNull(context.ValidationErrors);
153+
Assert.Collection(context.ValidationErrors,
154+
kvp =>
155+
{
156+
Assert.Equal("Name", kvp.Key);
157+
Assert.Equal("The Name field is required.", kvp.Value.First());
158+
},
159+
kvp =>
160+
{
161+
Assert.Equal("NestedPoco.Name", kvp.Key);
162+
Assert.Equal("The Name field is required.", kvp.Value.First());
163+
},
164+
kvp =>
165+
{
166+
Assert.Equal("NestedPoco.Age", kvp.Key);
167+
Assert.Equal("The field Age must be between 0 and 100.", kvp.Value.First());
168+
});
169+
170+
// Test validation with valid nested data
171+
var validPoco = new PocoWithNestedType
172+
{
173+
Name = "John Doe",
174+
NestedPoco = new SimplePocoWithValidation
175+
{
176+
Name = "Jane Smith",
177+
Age = 30
178+
}
179+
};
180+
181+
var validContext = new ValidateContext
182+
{
183+
ValidationOptions = validationOptions,
184+
ValidationContext = new ValidationContext(validPoco)
185+
};
186+
187+
await validatableInfo.ValidateAsync(validPoco, validContext, default);
188+
189+
Assert.Null(validContext.ValidationErrors); // No validation errors for valid data
80190
}
81191

82192
[Fact]
83-
public void TryGetValidatableTypeInfo_WithCyclicReference_DoesNotCauseStackOverflow()
193+
public async Task TryGetValidatableTypeInfo_WithCyclicReference_DoesNotCauseStackOverflow_AndValidatesCorrectly()
84194
{
85195
var result = _resolver.TryGetValidatableTypeInfo(typeof(CyclicTypeA), out var validatableInfo);
86196

87197
Assert.True(result);
88198
Assert.NotNull(validatableInfo);
199+
200+
// Test validation with invalid data in cyclic structure
201+
var cyclicA = new CyclicTypeA
202+
{
203+
Name = "", // Required but empty
204+
TypeB = new CyclicTypeB
205+
{
206+
Value = "", // Required but empty
207+
TypeA = new CyclicTypeA
208+
{
209+
Name = "Valid Name", // This one is valid
210+
TypeB = null // No further nesting
211+
}
212+
}
213+
};
214+
215+
var validationOptions = new ValidationOptions();
216+
validationOptions.Resolvers.Add(_resolver);
217+
218+
var context = new ValidateContext
219+
{
220+
ValidationOptions = validationOptions,
221+
ValidationContext = new ValidationContext(cyclicA)
222+
};
223+
224+
await validatableInfo.ValidateAsync(cyclicA, context, default);
225+
226+
Assert.NotNull(context.ValidationErrors);
227+
Assert.Collection(context.ValidationErrors,
228+
kvp =>
229+
{
230+
Assert.Equal("Name", kvp.Key);
231+
Assert.Equal("The Name field is required.", kvp.Value.First());
232+
},
233+
kvp =>
234+
{
235+
Assert.Equal("TypeB.Value", kvp.Key);
236+
Assert.Equal("The Value field is required.", kvp.Value.First());
237+
});
238+
239+
// Test validation with valid cyclic data
240+
var validCyclicA = new CyclicTypeA
241+
{
242+
Name = "Valid A",
243+
TypeB = new CyclicTypeB
244+
{
245+
Value = "Valid B",
246+
TypeA = new CyclicTypeA
247+
{
248+
Name = "Valid Nested A",
249+
TypeB = null // Stop the cycle
250+
}
251+
}
252+
};
253+
254+
var validContext = new ValidateContext
255+
{
256+
ValidationOptions = validationOptions,
257+
ValidationContext = new ValidationContext(validCyclicA)
258+
};
259+
260+
await validatableInfo.ValidateAsync(validCyclicA, validContext, default);
261+
262+
Assert.Null(validContext.ValidationErrors); // No validation errors for valid data
89263
}
90264

91265
[Fact]
92-
public void TryGetValidatableTypeInfo_WithCollectionOfComplexTypes_ReturnsTrue()
266+
public async Task TryGetValidatableTypeInfo_WithCollectionOfComplexTypes_ReturnsTrue_AndValidatesCorrectly()
93267
{
94268
var result = _resolver.TryGetValidatableTypeInfo(typeof(PocoWithCollection), out var validatableInfo);
95269

96270
Assert.True(result);
97271
Assert.NotNull(validatableInfo);
272+
273+
// Test validation with invalid data in collection
274+
var invalidPoco = new PocoWithCollection
275+
{
276+
Name = "", // Required but empty
277+
Items = new List<SimplePocoWithValidation>
278+
{
279+
new SimplePocoWithValidation { Name = "Valid Item", Age = 25 }, // Valid item
280+
new SimplePocoWithValidation { Name = "", Age = 150 }, // Invalid: empty name and out of range age
281+
new SimplePocoWithValidation { Name = "Another Valid", Age = 30 } // Valid item
282+
}
283+
};
284+
285+
var validationOptions = new ValidationOptions();
286+
validationOptions.Resolvers.Add(_resolver);
287+
288+
var context = new ValidateContext
289+
{
290+
ValidationOptions = validationOptions,
291+
ValidationContext = new ValidationContext(invalidPoco)
292+
};
293+
294+
await validatableInfo.ValidateAsync(invalidPoco, context, default);
295+
296+
Assert.NotNull(context.ValidationErrors);
297+
Assert.Collection(context.ValidationErrors,
298+
kvp =>
299+
{
300+
Assert.Equal("Name", kvp.Key);
301+
Assert.Equal("The Name field is required.", kvp.Value.First());
302+
},
303+
kvp =>
304+
{
305+
Assert.Equal("Items[1].Name", kvp.Key);
306+
Assert.Equal("The Name field is required.", kvp.Value.First());
307+
},
308+
kvp =>
309+
{
310+
Assert.Equal("Items[1].Age", kvp.Key);
311+
Assert.Equal("The field Age must be between 0 and 100.", kvp.Value.First());
312+
});
313+
314+
// Test validation with valid collection data
315+
var validPoco = new PocoWithCollection
316+
{
317+
Name = "Collection Owner",
318+
Items = new List<SimplePocoWithValidation>
319+
{
320+
new SimplePocoWithValidation { Name = "Item 1", Age = 25 },
321+
new SimplePocoWithValidation { Name = "Item 2", Age = 30 }
322+
}
323+
};
324+
325+
var validContext = new ValidateContext
326+
{
327+
ValidationOptions = validationOptions,
328+
ValidationContext = new ValidationContext(validPoco)
329+
};
330+
331+
await validatableInfo.ValidateAsync(validPoco, validContext, default);
332+
333+
Assert.Null(validContext.ValidationErrors); // No validation errors for valid data
98334
}
99335

100336
[Fact]
@@ -112,16 +348,53 @@ public void TryGetValidatableTypeInfo_UsesCaching()
112348
}
113349

114350
[Fact]
115-
public void TryGetValidatableTypeInfo_WithReadOnlyProperty_IgnoresReadOnlyProperty()
351+
public async Task TryGetValidatableTypeInfo_WithReadOnlyProperty_IgnoresReadOnlyProperty_AndValidatesCorrectly()
116352
{
117353
var result = _resolver.TryGetValidatableTypeInfo(typeof(PocoWithReadOnlyProperty), out var validatableInfo);
118354

119355
Assert.True(result);
120356
Assert.NotNull(validatableInfo);
121357

122358
var typeInfo = Assert.IsType<RuntimeValidatableTypeInfoResolver.RuntimeValidatableTypeInfo>(validatableInfo);
123-
// Should only include the writable property with validation
124-
// We can't directly access Members since it's internal, but we can validate through validation behavior
359+
360+
// Test validation with invalid writable property (read-only property should be ignored)
361+
var invalidPoco = new PocoWithReadOnlyProperty
362+
{
363+
Name = "" // Required but empty (ReadOnlyValue should be ignored)
364+
};
365+
366+
var validationOptions = new ValidationOptions();
367+
validationOptions.Resolvers.Add(_resolver);
368+
369+
var context = new ValidateContext
370+
{
371+
ValidationOptions = validationOptions,
372+
ValidationContext = new ValidationContext(invalidPoco)
373+
};
374+
375+
await validatableInfo.ValidateAsync(invalidPoco, context, default);
376+
377+
Assert.NotNull(context.ValidationErrors);
378+
var error = Assert.Single(context.ValidationErrors);
379+
Assert.Equal("Name", error.Key);
380+
Assert.Equal("The Name field is required.", error.Value.First());
381+
// ReadOnlyValue should not generate validation errors even though it has [Required]
382+
383+
// Test validation with valid writable property
384+
var validPoco = new PocoWithReadOnlyProperty
385+
{
386+
Name = "Valid Name" // ReadOnlyValue is always "ReadOnly" and should be ignored
387+
};
388+
389+
var validContext = new ValidateContext
390+
{
391+
ValidationOptions = validationOptions,
392+
ValidationContext = new ValidationContext(validPoco)
393+
};
394+
395+
await validatableInfo.ValidateAsync(validPoco, validContext, default);
396+
397+
Assert.Null(validContext.ValidationErrors); // No validation errors for valid data
125398
}
126399

127400
// Helper method for parameter test

0 commit comments

Comments
 (0)