File: Data.Validation\TimeSpanAttributeTests.cs
Web Access
Project: src\test\Shared\Shared.Tests.csproj (Shared.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Xunit;
 
namespace Microsoft.Shared.Data.Validation.Test;
 
public class TimeSpanAttributeTests
{
    public class TestOptions
    {
        [TimeSpan(0, 10)]
        public TimeSpan? Time { get; set; }
 
        [TimeSpan(0)]
        public TimeSpan? Time2 { get; set; }
    }
 
    [Fact]
    public void Basic()
    {
        var options = new TestOptions();
        var context = new ValidationContext(options);
        var results = new List<ValidationResult>();
 
        // Assertions on null values.
        options.Time = null;
        options.Time2 = null;
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        // Assertions on values bounded by minimum and maximum.
        options.Time = TimeSpan.FromTicks(-1);
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time), results[0].MemberNames);
        Assert.Contains(nameof(options.Time), results[0].ErrorMessage);
 
        options.Time = TimeSpan.FromTicks(TimeSpan.FromMilliseconds(10).Ticks + 1);
        results.Clear();
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time), results[0].MemberNames);
        Assert.Contains(nameof(options.Time), results[0].ErrorMessage);
 
        options.Time = TimeSpan.FromTicks(0);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        options.Time = TimeSpan.FromMilliseconds(10);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        // Assertions on values bounded by minimum only without upper bound.
        options.Time = null;
        options.Time2 = TimeSpan.FromTicks(-1);
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time2), results[0].MemberNames);
        Assert.Contains(nameof(options.Time2), results[0].ErrorMessage);
 
        options.Time2 = TimeSpan.FromTicks(0);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        options.Time2 = TimeSpan.FromMilliseconds(int.MaxValue);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
    }
 
    public class TestOptionsExclusive
    {
        [TimeSpan(0, 10, Exclusive = true)]
        public TimeSpan? Time { get; set; }
 
        [TimeSpan(0, Exclusive = true)]
        public TimeSpan? Time2 { get; set; }
    }
 
    [Fact]
    public void BasicWithExclusive()
    {
        var options = new TestOptionsExclusive();
        var context = new ValidationContext(options);
        var results = new List<ValidationResult>();
 
        // Assertions on null values.
        options.Time = null;
        options.Time2 = null;
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        // Assertions on values bounded by minimum and maximum.
        options.Time = TimeSpan.FromTicks(0);
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time), results[0].MemberNames);
        Assert.Contains(nameof(options.Time), results[0].ErrorMessage);
 
        options.Time = TimeSpan.FromMilliseconds(10);
        results.Clear();
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time), results[0].MemberNames);
        Assert.Contains(nameof(options.Time), results[0].ErrorMessage);
 
        options.Time = TimeSpan.FromMilliseconds(5);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        // Assertions on values bounded by minimum only without upper bound.
        options.Time = null;
        options.Time2 = TimeSpan.FromTicks(-1);
        results.Clear();
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time2), results[0].MemberNames);
        Assert.Contains(nameof(options.Time2), results[0].ErrorMessage);
 
        options.Time2 = TimeSpan.FromTicks(0);
        results.Clear();
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time2), results[0].MemberNames);
        Assert.Contains(nameof(options.Time2), results[0].ErrorMessage);
 
        options.Time2 = TimeSpan.FromMilliseconds(int.MaxValue);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
    }
 
    public class TestOptionsString
    {
        [TimeSpan("00:00:00", "00:00:00.01")]
        public TimeSpan? Time { get; set; }
 
        [TimeSpan("00:00:00")]
        public TimeSpan? Time2 { get; set; }
    }
 
    [Fact]
    public void BasicString()
    {
        var options = new TestOptionsString();
        var context = new ValidationContext(options);
        var results = new List<ValidationResult>();
 
        // Assertions on null values.
        options.Time = null;
        options.Time2 = null;
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        // Assertions on values bounded by minimum and maximum.
        options.Time = TimeSpan.FromTicks(-1);
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time), results[0].MemberNames);
        Assert.Contains(nameof(options.Time), results[0].ErrorMessage);
 
        options.Time = TimeSpan.FromTicks(TimeSpan.FromMilliseconds(10).Ticks + 1);
        results.Clear();
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time), results[0].MemberNames);
        Assert.Contains(nameof(options.Time), results[0].ErrorMessage);
 
        options.Time = TimeSpan.FromTicks(0);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        options.Time = TimeSpan.FromMilliseconds(10);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        // Assertions on values bounded by minimum only without upper bound.
        options.Time = null;
        options.Time2 = TimeSpan.FromTicks(-1);
        Assert.False(Validator.TryValidateObject(options, context, results, true));
        Assert.Single(results);
        Assert.Contains(nameof(options.Time2), results[0].MemberNames);
        Assert.Contains(nameof(options.Time2), results[0].ErrorMessage);
 
        options.Time2 = TimeSpan.FromTicks(0);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
 
        options.Time2 = TimeSpan.FromMilliseconds(int.MaxValue);
        results.Clear();
        Assert.True(Validator.TryValidateObject(options, context, results, true));
        Assert.Empty(results);
    }
 
    public class BadOptions0
    {
        [TimeSpan(0, 10)]
        public int Time { get; set; }
    }
 
    public class BadOptions1
    {
        [TimeSpan(10, 9)]
        public TimeSpan Time { get; set; }
    }
 
    public class BadOptions3
    {
        [TimeSpan(10, 10)]
        public TimeSpan Time { get; set; }
    }
 
    [Fact]
    public void BadAttributeUse()
    {
        var options0 = new BadOptions0();
        var context = new ValidationContext(options0);
        var results = new List<ValidationResult>();
        Assert.Throws<InvalidOperationException>(() => _ = Validator.TryValidateObject(options0, context, results, true));
 
        var options1 = new BadOptions1();
        context = new ValidationContext(options1);
        results = [];
        Assert.Throws<InvalidOperationException>(() => _ = Validator.TryValidateObject(options1, context, results, true));
 
        var options3 = new BadOptions3();
        context = new ValidationContext(options3);
        results = [];
        Assert.Throws<InvalidOperationException>(() => _ = Validator.TryValidateObject(options3, context, results, true));
    }
 
    [Fact]
    public void NakedContext()
    {
        var value = TimeSpan.FromTicks(-1);
        var context = new ValidationContext(value);
        var attr = new TimeSpanAttribute(0, 10);
 
        var result = attr.GetValidationResult(value, context);
 
        Assert.NotEqual(ValidationResult.Success, result);
        Assert.Empty(result!.MemberNames);
    }
}