Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AgeValueGenerator generates from DOB when available #82

Merged
merged 1 commit into from
Apr 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ModelBuilder.UnitTests/Scenarios/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using ModelBuilder.Data;
using ModelBuilder.TypeCreators;
using ModelBuilder.UnitTests.Models;
using ModelBuilder.UnitTests.ValueGenerators;
using ModelBuilder.ValueGenerators;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
Expand Down Expand Up @@ -411,6 +412,17 @@ public void CreateReturnsString()
actual.Should().NotBeNullOrWhiteSpace();
}

[Fact]
public void CreatesAgeFromDob()
{
var actual = Model.Create<AgeFromDob>();

var span = DateTime.Now.Subtract(actual.DateOfBirth);
var years = Convert.ToInt32(Math.Floor(span.TotalDays / 365));

actual.Age.Should().Be(years);
}

[Fact]
public void CreatesCircularReferenceWithInstanceFromBuildChain()
{
Expand Down
10 changes: 10 additions & 0 deletions ModelBuilder.UnitTests/ValueGenerators/AgeFromDob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ModelBuilder.UnitTests.ValueGenerators
{
using System;

public class AgeFromDob
{
public int Age { get; set; }
public DateTime DateOfBirth { get; set; }
}
}
100 changes: 100 additions & 0 deletions ModelBuilder.UnitTests/ValueGenerators/AgeValueGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,62 @@

public class AgeValueGeneratorTests
{
[Theory]
[InlineData(5, 0)]
[InlineData(364, 0)]
[InlineData(365, 1)]
[InlineData(400, 1)]
[InlineData(365 * 99 + 10, 99)]
public void CanAssignAgeFromDob(int daysOld, int expectedYears)
{
var dob = DateTime.Now.AddDays(-daysOld);

var model = new AgeFromDob
{
DateOfBirth = dob
};

var executeStrategy = Substitute.For<IExecuteStrategy>();

var buildChain = new BuildHistory();

buildChain.Push(model);

executeStrategy.BuildChain.Returns(buildChain);

var sut = new Wrapper {MinAge = 15, MaxAge = 30};

var actual = (int) sut.RunGenerate(typeof(int), "age", executeStrategy);

actual.Should().Be(expectedYears);
}

[Fact]
public void GenerateReturnsRandomValueWhenDobInFuture()
{
var dob = DateTime.Now.AddDays(1);

var model = new AgeFromDob
{
DateOfBirth = dob
};

var executeStrategy = Substitute.For<IExecuteStrategy>();

var buildChain = new BuildHistory();

buildChain.Push(model);

executeStrategy.BuildChain.Returns(buildChain);

var sut = new Wrapper {MinAge = 15, MaxAge = 30};

var actual = (int) sut.RunGenerate(typeof(int), "age", executeStrategy);

actual.Should().BeGreaterOrEqualTo(15);
actual.Should().BeLessOrEqualTo(30);
}

[Theory]
[ClassData(typeof(NumericTypeRangeDataSource))]
public void GenerateReturnsValuesBetweenMinAndMaxTest(Type type, bool typeSupported, double min, double max)
Expand Down Expand Up @@ -54,6 +110,28 @@ public void GenerateReturnsValuesBetweenMinAndMaxTest(Type type, bool typeSuppor
}
}

[Fact]
public void GenerateThrowsExceptionWithNullExecuteStrategy()
{
var sut = new Wrapper();

Action action = () => sut.RunGenerate(typeof(string), "Age", null);

action.Should().Throw<ArgumentNullException>();
}

[Fact]
public void GenerateThrowsExceptionWithNullType()
{
var executeStrategy = Substitute.For<IExecuteStrategy>();

var sut = new Wrapper();

Action action = () => sut.RunGenerate(null, "Age", executeStrategy);

action.Should().Throw<ArgumentNullException>();
}

[Theory]
[ClassData(typeof(NumericTypeDataSource))]
public void IsMatchEvaluatesRequestedTypeTest(Type type, bool typeSupported)
Expand Down Expand Up @@ -124,6 +202,28 @@ public void IsMatchReturnsWhetherTypeIsSupported(Type type, bool typeSupported)
actual.Should().BeTrue();
}

[Fact]
public void IsMatchThrowsExceptionWithNullExecuteStrategy()
{
var sut = new Wrapper();

Action action = () => sut.RunIsMatch(typeof(string), "Age", null);

action.Should().Throw<ArgumentNullException>();
}

[Fact]
public void IsMatchThrowsExceptionWithNullType()
{
var buildChain = Substitute.For<IBuildChain>();

var sut = new Wrapper();

Action action = () => sut.RunIsMatch(null, "Age", buildChain);

action.Should().Throw<ArgumentNullException>();
}

[Fact]
public void MaxAgeDefaultsTo100()
{
Expand Down
26 changes: 14 additions & 12 deletions ModelBuilder/DefaultConfigurationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,23 @@ public void Configure(IBuildConfiguration configuration)

private static void AddExecuteOrderRules(IBuildConfiguration configuration)
{
// Populate personal properties in a specific order for scenarios where a value generator may use the values in order to set other values
configuration.AddExecuteOrderRule(PropertyExpression.Gender, 9600);
configuration.AddExecuteOrderRule(PropertyExpression.FirstName, 9580);
configuration.AddExecuteOrderRule(PropertyExpression.LastName, 9560);
configuration.AddExecuteOrderRule(PropertyExpression.Domain, 9550);
configuration.AddExecuteOrderRule(PropertyExpression.Email, 9540);
configuration.AddExecuteOrderRule(PropertyExpression.Country, 9400);
configuration.AddExecuteOrderRule(PropertyExpression.State, 9390);
configuration.AddExecuteOrderRule(PropertyExpression.City, 9380);
configuration.AddExecuteOrderRule(PropertyExpression.PostCode, 9370);
configuration.AddExecuteOrderRule(PropertyExpression.TimeZone, 9360);
configuration.AddExecuteOrderRule(PropertyExpression.DateOfBirth, 9340);
configuration.AddExecuteOrderRule(PropertyExpression.Age, 9320);

configuration.AddExecuteOrderRule(x => x.PropertyType.IsEnum, 4000);
configuration.AddExecuteOrderRule(x => x.PropertyType.IsValueType, 3000);

// Populate personal properties in a specific order for scenarios where a value generator may use the values in order to set other values
configuration.AddExecuteOrderRule(PropertyExpression.Gender, 2600);
configuration.AddExecuteOrderRule(PropertyExpression.FirstName, 2580);
configuration.AddExecuteOrderRule(PropertyExpression.LastName, 2560);
configuration.AddExecuteOrderRule(PropertyExpression.Domain, 2550);
configuration.AddExecuteOrderRule(PropertyExpression.Email, 2540);
configuration.AddExecuteOrderRule(PropertyExpression.Country, 2400);
configuration.AddExecuteOrderRule(PropertyExpression.State, 2390);
configuration.AddExecuteOrderRule(PropertyExpression.City, 2380);
configuration.AddExecuteOrderRule(PropertyExpression.PostCode, 2370);
configuration.AddExecuteOrderRule(PropertyExpression.TimeZone, 2360);

// Populate strings before other reference types
configuration.AddExecuteOrderRule(x => x.PropertyType == typeof(string), 2000);
configuration.AddExecuteOrderRule(x => x.PropertyType.IsClass, 1000);
Expand Down
10 changes: 10 additions & 0 deletions ModelBuilder/PropertyExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
/// </summary>
public static class PropertyExpression
{
/// <summary>
/// Defines the expression for matching age properties.
/// </summary>
public static readonly Regex Age = new Regex("Age", RegexOptions.IgnoreCase);

/// <summary>
/// Defines the expression for matching city properties.
/// </summary>
Expand All @@ -18,6 +23,11 @@ public static class PropertyExpression
/// </summary>
public static readonly Regex Country = new Regex("Country", RegexOptions.IgnoreCase);

/// <summary>
/// Defines the expression for matching DOB properties.
/// </summary>
public static readonly Regex DateOfBirth = new Regex("dob|dateofbirth|born", RegexOptions.IgnoreCase);

/// <summary>
/// Defines the expression for matching domain properties.
/// </summary>
Expand Down
95 changes: 78 additions & 17 deletions ModelBuilder/ValueGenerators/AgeValueGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,81 @@
/// The <see cref="AgeValueGenerator" />
/// class is used to generate numbers that should represent a persons age.
/// </summary>
public class AgeValueGenerator : NumericValueGenerator
public class AgeValueGenerator : RelativeValueGenerator
{
/// <summary>
/// Initializes a new instance of the <see cref="AddressValueGenerator" /> class.
/// </summary>
public AgeValueGenerator() : base(PropertyExpression.Age)
{
}

/// <inheritdoc />
protected override object Generate(IExecuteStrategy executeStrategy, Type type, string referenceName)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}

if (executeStrategy == null)
{
throw new ArgumentNullException(nameof(executeStrategy));
}

var generateType = type;

if (generateType.IsNullable())
{
// Allow for a 10% the chance that this might be null
var range = Generator.NextValue(0, 100000);

if (range < 10000)
{
return null;
}

// Hijack the type to generator so we can continue with the normal code pointed at the correct type to generate
generateType = type.GetGenericArguments()[0];
}

var context = executeStrategy?.BuildChain?.Last;

if (context == null)
{
return Generator.NextValue(generateType, MinAge, MaxAge);
}

// Check if there is a DOB value
var dob = GetValue<DateTime>(PropertyExpression.DateOfBirth, context);

if (dob == default)
{
return Generator.NextValue(generateType, MinAge, MaxAge);
}

// Calculate the age from this DOB
var totalDays = DateTime.Now.Subtract(dob).TotalDays;

if (totalDays > 0)
{
return Convert.ChangeType(Math.Floor(totalDays / 365), type);
}

return Generator.NextValue(generateType, MinAge, MaxAge);
}

/// <inheritdoc />
protected override bool IsMatch(IBuildChain buildChain, Type type, string referenceName)
{
var baseSupported = base.IsMatch(buildChain, type, referenceName);
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}

if (baseSupported == false)
if (buildChain == null)
{
return false;
throw new ArgumentNullException(nameof(buildChain));
}

if (string.IsNullOrEmpty(referenceName))
Expand All @@ -24,24 +89,20 @@ protected override bool IsMatch(IBuildChain buildChain, Type type, string refere
return false;
}

if (referenceName.IndexOf("age", StringComparison.OrdinalIgnoreCase) > -1)
if (PropertyExpression.Age.IsMatch(referenceName) == false)
{
return true;
return false;
}

return false;
}
if (type.IsNullable())
{
// Get the internal type
var internalType = type.GetGenericArguments()[0];

/// <inheritdoc />
protected override object GetMaximum(Type type, string referenceName, object context)
{
return MaxAge;
}
return Generator.IsSupported(internalType);
}

/// <inheritdoc />
protected override object GetMinimum(Type type, string referenceName, object context)
{
return MinAge;
return Generator.IsSupported(type);
}

/// <summary>
Expand Down
5 changes: 1 addition & 4 deletions ModelBuilder/ValueGenerators/DateOfBirthValueGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
namespace ModelBuilder.ValueGenerators
{
using System;
using System.Text.RegularExpressions;

/// <summary>
/// The <see cref="DateOfBirthValueGenerator" />
/// class is used to generate random date of birth values.
/// </summary>
public class DateOfBirthValueGenerator : ValueGeneratorMatcher
{
private static readonly Regex _matchNameExpression = new Regex("dob|dateofbirth|born", RegexOptions.IgnoreCase);

/// <summary>
/// Initializes a new instance of the <see cref="DateTimeValueGenerator" /> class.
/// </summary>
public DateOfBirthValueGenerator() : base(
_matchNameExpression,
PropertyExpression.DateOfBirth,
typeof(DateTime),
typeof(DateTime?),
typeof(DateTimeOffset),
Expand Down