Skip to content

Commit

Permalink
Updated AgeValueGenerator to create value from DOB when available. (#82)
Browse files Browse the repository at this point in the history
Updated DefaultConfigurationModule to calculate DOB before Age.

Closes #68
  • Loading branch information
roryprimrose authored Apr 5, 2020
1 parent b5321db commit 9b6b76d
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 33 deletions.
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

0 comments on commit 9b6b76d

Please sign in to comment.