├── GitVersion.yml ├── ModelBuilder.UnitTests ├── Models │ ├── Empty.cs │ ├── SingleEnum.cs │ ├── IItem.cs │ ├── MultipleGenericArguments.cs │ ├── SpecificCompany.cs │ ├── SlimModel.cs │ ├── Gender.cs │ ├── PropertyModel.cs │ ├── IInterfaceCollection.cs │ ├── AbstractCollection.cs │ ├── NullableItem.cs │ ├── NullablePropertyModel.cs │ ├── Office.cs │ ├── ICustomCollection.cs │ ├── SmallFlags.cs │ ├── WithStatic.cs │ ├── RelativeNullableInt.cs │ ├── Entity.cs │ ├── PropertySetFailure.cs │ ├── SelfReferrer.cs │ ├── Item.cs │ ├── CultureData.cs │ ├── IInternalItem.cs │ ├── ParameterModel.cs │ ├── ITestItem.cs │ ├── IPublicOverInternal.cs │ ├── SelfReferenceInstance.cs │ ├── NamesWithoutGender.cs │ ├── SimpleEnum.cs │ ├── ReadOnlyModel.cs │ ├── Names.cs │ ├── Company.cs │ ├── EmailParts.cs │ ├── InternalItem.cs │ ├── IGenericContainer.cs │ ├── SimpleConstructor.cs │ ├── PublicPickedItem.cs │ ├── StructConstructorException.cs │ ├── InternalNotPickedItem.cs │ ├── Singleton.cs │ ├── NoValues.cs │ ├── FactoryItem.cs │ ├── NotFactoryItem.cs │ ├── FactoryWithValue.cs │ ├── Derived.cs │ ├── EntityWithInternalConstructor.cs │ ├── GenericContainer.cs │ ├── Simple.cs │ ├── ICantCreate.cs │ ├── CustomCollection.cs │ ├── PersonWithoutGender.cs │ ├── Clone.cs │ ├── Optionals.cs │ ├── Copy.cs │ ├── Address.cs │ ├── Numbers.cs │ ├── Location.cs │ ├── OrderedConstructorParameters.cs │ ├── PropertyScopes.cs │ ├── EnumerableParent.cs │ ├── WithConstructorParameters.cs │ ├── Other.cs │ ├── WithValueParameters.cs │ ├── ReadOnlyParent.cs │ ├── PropertySetters.cs │ ├── NumericPropertyModel.cs │ ├── WithMixedValueParameters.cs │ ├── WithInterfaceAndAbstractParameters.cs │ ├── PopulateOrderItem.cs │ ├── StructModel.cs │ ├── Person.cs │ ├── BigEnum.cs │ └── BigValues.cs ├── IEntityResolver.cs ├── InheritedGenericCollection.cs ├── ValueGenerators │ ├── AgeFromDob.cs │ ├── TimeZoneInfoValueGeneratorTests.cs │ ├── StringValueGeneratorTests.cs │ └── LastNameValueGeneratorTests.cs ├── DummyTypeMappingRule.cs ├── NullTypeBuildExecuteStrategy.cs ├── DummyIgnoreRule.cs ├── ModelExtensions.cs ├── IsExternalInit.cs ├── BuildConfigurationFactory.cs ├── OutputBuildLog.cs ├── Scenarios │ ├── PropertyTests.cs │ ├── TypeMappingTests.cs │ ├── RegressionTests.cs │ ├── NonConstructorCreationTests.cs │ ├── NumericGenerationTests.cs │ ├── NullableTests.cs │ └── RelativeDataGenerationTests.cs ├── DummyExecuteOrderRule.cs ├── TestConfigurationModule.cs ├── DummyValueGenerator.cs ├── IncrementingArrayTypeCreator.cs ├── NullExecuteStrategy.cs ├── BuildHistoryItemTests.cs ├── NumericTypeDataSource.cs ├── DummyTypeCreator.cs ├── DataSet.cs ├── DummyCreationRule.cs ├── DummyExecuteStrategy.cs ├── DummyPostBuildAction.cs ├── TestDataTests.cs ├── Data │ └── LocationTests.cs ├── ModelBuilder.UnitTests.csproj ├── IncrementingEnumerableTypeCreator.cs ├── TypeMappingRuleTests.cs ├── BuildConfigurationExtensionsTests.WriteLog.cs ├── IgnoreRules │ └── RegexIgnoreRuleTests.cs ├── BuildConfigurationTests.cs ├── ExpressionExtensionTests.cs └── BuildExceptionTests.cs ├── .github └── dependabot.yml ├── ModelBuilder ├── ValueGenerators │ ├── MailinatorEmailValueGenerator.cs │ ├── StringValueGenerator.cs │ ├── LastNameValueGenerator.cs │ ├── DomainNameValueGenerator.cs │ ├── TimeZoneInfoValueGenerator.cs │ ├── CountryValueGenerator.cs │ ├── CompanyValueGenerator.cs │ ├── CharValueGenerator.cs │ ├── FirstNameValueGenerator.cs │ ├── MIddleNameValueGenerator.cs │ ├── SuburbValueGenerator.cs │ ├── GenderValueGenerator.cs │ ├── GuidValueGenerator.cs │ ├── CultureValueGenerator.cs │ ├── BooleanValueGenerator.cs │ ├── RegexTypeNameValueGenerator.cs │ ├── CountValueGenerator.cs │ ├── UriValueGenerator.cs │ ├── StateValueGenerator.cs │ ├── PhoneValueGenerator.cs │ ├── IPAddressValueGenerator.cs │ ├── DateTimeValueGenerator.cs │ ├── AddressValueGenerator.cs │ ├── CityValueGenerator.cs │ ├── DateOfBirthValueGenerator.cs │ └── PostCodeValueGenerator.cs ├── IConfigurationModule.cs ├── BuildActions │ └── BuildRequirement.cs ├── ITypeResolver.cs ├── INullableBuilder.cs ├── CacheLevel.cs ├── IConstructorResolver.cs ├── IgnoreRules │ ├── IIgnoreRule.cs │ ├── PredicateIgnoreRule.cs │ ├── ExpressionIgnoreRule.cs │ └── RegexIgnoreRule.cs ├── IParameterResolver.cs ├── IBuildChain.cs ├── IExecuteStrategyT.cs ├── BuildHistoryItem.cs ├── IBuildHistory.cs ├── ExecuteOrderRules │ ├── IExecuteOrderRule.cs │ ├── PropertyPredicateExecuteOrderRule.cs │ ├── ParameterPredicateExecuteOrderRule.cs │ ├── RegexExecuteOrderRule.cs │ └── ExpressionExecuteOrderRule.cs ├── IPropertyResolver.cs ├── IRandomGenerator.cs ├── TypeMappingRule.cs ├── BuildConfiguration.cs ├── IExecuteStrategy.cs ├── IBuildProcessor.cs ├── TypeCreators │ └── StructTypeCreator.cs ├── Data │ └── Location.cs ├── BuildConfigurationExtensions.WriteLog.cs ├── DefaultExecuteStrategyT.cs ├── CreationRules │ └── ICreationRule.cs └── IBuildConfiguration.cs ├── Solution Items ├── CustomDictionary.xml └── UnitTest.ruleset ├── LICENSE.md └── .gitattributes /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDeployment 2 | branches: 3 | master: 4 | tag: beta 5 | hotfix: 6 | tag: useBranchName -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Empty.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class Empty 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SingleEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public enum SingleEnum 4 | { 5 | First = 1 6 | } 7 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/IItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public interface IItem 4 | { 5 | void DoSomething(); 6 | } 7 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/MultipleGenericArguments.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class MultipleGenericArguments 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SpecificCompany.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class SpecificCompany : Company 4 | { 5 | public string? Email { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SlimModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class SlimModel 6 | { 7 | public Guid Value { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Gender.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public enum Gender 4 | { 5 | Unknown = 0, 6 | 7 | Male, 8 | 9 | Female 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PropertyModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class PropertyModel where T : notnull 4 | { 5 | public T Value { get; set; } = default!; 6 | } 7 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/IInterfaceCollection.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections.Generic; 4 | 5 | public interface IInterfaceCollection : IList 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/AbstractCollection.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections.ObjectModel; 4 | 5 | public abstract class AbstractCollection : Collection 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/NullableItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class NullableItem 4 | { 5 | public string? Name { get; set; } 6 | public int? Number { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/NullablePropertyModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class NullablePropertyModel where T : class 4 | { 5 | public T Value { get; set; } = default!; 6 | } 7 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Office.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class Office 4 | { 5 | public Address? Address { get; set; } 6 | 7 | public string? Phone { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/ICustomCollection.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections.Generic; 4 | 5 | public interface ICustomCollection : IEnumerable where T : class 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SmallFlags.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | [Flags] 6 | public enum SmallFlags 7 | { 8 | First = 1, 9 | 10 | Second = 2 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/WithStatic.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class WithStatic 4 | { 5 | public static string? Second { get; set; } 6 | 7 | public string? First { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/IEntityResolver.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using ModelBuilder.UnitTests.Models; 5 | 6 | public interface IEntityResolver 7 | { 8 | Entity ResolveById(Guid id); 9 | } 10 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/RelativeNullableInt.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class RelativeNullableInt 4 | { 5 | public int? YearLastUsed { get; set; } 6 | 7 | public int? YearStarted { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public abstract class Entity 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public bool IsActive { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/InheritedGenericCollection.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System.Collections.ObjectModel; 4 | using ModelBuilder.UnitTests.Models; 5 | 6 | public class InheritedGenericCollection : Collection 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PropertySetFailure.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | internal class PropertySetFailure 6 | { 7 | public string Name { get => "Name"; set => throw new NotImplementedException(); } 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SelfReferrer.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class SelfReferrer 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public SelfReferrer? Self { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ValueGenerators/AgeFromDob.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.ValueGenerators 2 | { 3 | using System; 4 | 5 | public class AgeFromDob 6 | { 7 | public int Age { get; set; } 8 | public DateTime DateOfBirth { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Item.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class Item : IItem 6 | { 7 | public void DoSomething() 8 | { 9 | throw new NotImplementedException(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/CultureData.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Globalization; 4 | 5 | public class CultureData 6 | { 7 | public CultureInfo? Culture { get; set; } 8 | 9 | public string? CultureName { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/IInternalItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public interface IInternalItem 6 | { 7 | public string First { get; } 8 | public bool Second { get; } 9 | public Uri Third { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/ParameterModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class ParameterModel 4 | { 5 | public ParameterModel(T value) 6 | { 7 | Value = value; 8 | } 9 | 10 | public T Value { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/ITestItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public interface ITestItem 4 | { 5 | string? FirstName { get; } 6 | } 7 | 8 | public class TestItem : ITestItem 9 | { 10 | public string? FirstName { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyTypeMappingRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System.IO; 4 | 5 | public class DummyTypeMappingRule : TypeMappingRule 6 | { 7 | public DummyTypeMappingRule() : base(typeof(Stream), typeof(MemoryStream)) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/IPublicOverInternal.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public interface IPublicOverInternal 6 | { 7 | public string First { get; } 8 | public bool Second { get; } 9 | public Uri Third { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SelfReferenceInstance.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | internal class SelfReferenceInstance 4 | { 5 | private SelfReferenceInstance? _value; 6 | 7 | public SelfReferenceInstance Instance { get => _value ?? this; set => _value = value; } 8 | } 9 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/NamesWithoutGender.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class NamesWithoutGender 4 | { 5 | public string? FirstName { get; set; } 6 | 7 | public string? LastName { get; set; } 8 | public string? MiddleName { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SimpleEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public enum SimpleEnum 4 | { 5 | First = 1, 6 | 7 | Second, 8 | 9 | Third, 10 | 11 | Fourth, 12 | 13 | Fifth, 14 | 15 | Sixth, 16 | 17 | Seventh 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/ReadOnlyModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class ReadOnlyModel 6 | { 7 | public ReadOnlyModel(Guid value) 8 | { 9 | Value = value; 10 | } 11 | 12 | public Guid Value { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Names.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class Names 4 | { 5 | public string? FirstName { get; set; } 6 | 7 | public Gender Gender { get; set; } 8 | public string? LastName { get; set; } 9 | public string? MiddleName { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Company.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections.Generic; 4 | 5 | public class Company 6 | { 7 | public string? Address { get; set; } 8 | 9 | public string? Name { get; set; } 10 | 11 | public IEnumerable? Staff { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/EmailParts.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class EmailParts 4 | { 5 | public string? Domain { get; set; } 6 | 7 | public string? Email { get; set; } 8 | 9 | public string? FirstName { get; set; } 10 | 11 | public string? LastName { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/InternalItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | internal class InternalItem : IInternalItem 6 | { 7 | public string First { get; set; } = string.Empty; 8 | public bool Second { get; set; } 9 | public Uri Third { get; set; } = new Uri("about:blank"); 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/IGenericContainer.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public interface IGenericContainer where T : class, new() 4 | { 5 | int Age { get; set; } 6 | string Email { get; set; } 7 | string FirstName { get; set; } 8 | string LastName { get; set; } 9 | T Value { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/SimpleConstructor.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class SimpleConstructor 4 | { 5 | public SimpleConstructor(SlimModel? model) 6 | { 7 | Model = model; 8 | } 9 | 10 | public int Age { get; set; } 11 | 12 | public SlimModel? Model { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/NullTypeBuildExecuteStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | 5 | public class NullTypeBuildExecuteStrategy : DefaultExecuteStrategy 6 | { 7 | protected override object Build(Type type, params object?[]? args) 8 | { 9 | return base.Build((Type)null!, args); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PublicPickedItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class PublicPickedItem : IPublicOverInternal 6 | { 7 | public string First { get; set; } = string.Empty; 8 | public bool Second { get; set; } 9 | public Uri Third { get; set; } = new Uri("about:blank"); 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/StructConstructorException.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | internal struct StructConstructorException 6 | { 7 | public StructConstructorException() 8 | { 9 | throw new InvalidOperationException("This is an exception from the constructor."); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/InternalNotPickedItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | internal class InternalNotPickedItem : IPublicOverInternal 6 | { 7 | public string First { get; set; } = string.Empty; 8 | public bool Second { get; set; } 9 | public Uri Third { get; set; } = new Uri("about:blank"); 10 | } 11 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Singleton.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class Singleton 6 | { 7 | private Singleton() 8 | { 9 | Value = Guid.NewGuid().ToString(); 10 | } 11 | 12 | public static Singleton Instance { get; } = new Singleton(); 13 | 14 | public string Value { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyIgnoreRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using ModelBuilder.IgnoreRules; 6 | 7 | public class DummyIgnoreRule : IIgnoreRule 8 | { 9 | public bool IsMatch(PropertyInfo property) 10 | { 11 | return false; 12 | } 13 | 14 | public Guid Value { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/NoValues.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | [Flags] 7 | [SuppressMessage( 8 | "Microsoft.Design", 9 | "CA1028", 10 | Justification = "This base type is used specifically for testing scenarios for that data type.")] 11 | public enum NoValues 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ModelExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | public static class ModelExtensions 8 | { 9 | public static PropertyInfo GetProperty(this T instance, Expression> expression) 10 | { 11 | return expression.GetProperty(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/FactoryItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class FactoryItem 6 | { 7 | private FactoryItem() 8 | { 9 | Value = Guid.NewGuid(); 10 | } 11 | 12 | public static FactoryItem Create() 13 | { 14 | return new FactoryItem(); 15 | } 16 | 17 | public Guid Value { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | // the following namespace/class is required to be able to use init setters with framework versions lower than 5.0: 2 | // https://www.mking.net/blog/error-cs0518-isexternalinit-not-defined 3 | 4 | namespace System.Runtime.CompilerServices 5 | { 6 | using System.ComponentModel; 7 | 8 | [EditorBrowsable(EditorBrowsableState.Never)] 9 | internal static class IsExternalInit 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "nuget" # See documentation for possible values 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "daily" 10 | 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | # Check for updates to GitHub Actions every weekday 15 | interval: "daily" -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/NotFactoryItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class NotFactoryItem 6 | { 7 | private NotFactoryItem() 8 | { 9 | Value = Guid.NewGuid(); 10 | } 11 | 12 | public static NotFactoryItem Create(NotFactoryItem item) 13 | { 14 | return item; 15 | } 16 | 17 | public Guid Value { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/FactoryWithValue.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class FactoryWithValue 6 | { 7 | private FactoryWithValue(Guid value) 8 | { 9 | Value = value; 10 | } 11 | 12 | public static FactoryWithValue Create(Guid value) 13 | { 14 | return new FactoryWithValue(value); 15 | } 16 | 17 | public Guid Value { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Derived.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 6 | public class Derived 7 | { 8 | public Derived(string first, Company second) 9 | { 10 | } 11 | 12 | public Derived(string first, SpecificCompany second, string third) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/EntityWithInternalConstructor.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class EntityWithInternalConstructor 6 | { 7 | internal EntityWithInternalConstructor(Guid entityId, string entityName) 8 | { 9 | EntityId = entityId; 10 | EntityName = entityName; 11 | } 12 | 13 | public Guid EntityId { get; } 14 | 15 | public string EntityName { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/GenericContainer.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class GenericContainer : IGenericContainer where T : class, new() 4 | { 5 | public int Age { get; set; } 6 | 7 | public string Email { get; set; } = string.Empty; 8 | public string FirstName { get; set; } = string.Empty; 9 | 10 | public string LastName { get; set; } = string.Empty; 11 | 12 | public T Value { get; set; } = new T(); 13 | } 14 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/MailinatorEmailValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | /// 4 | /// The 5 | /// class is used to generate email addresses that always point to the mailinator.com domain. 6 | /// 7 | public class MailinatorEmailValueGenerator : EmailValueGenerator 8 | { 9 | /// 10 | protected override string Domain => "mailinator.com"; 11 | } 12 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/BuildConfigurationFactory.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | public static class BuildConfigurationFactory 4 | { 5 | public static IBuildConfiguration CreateEmpty() 6 | { 7 | return new BuildConfiguration 8 | { 9 | ConstructorResolver = new DefaultConstructorResolver(CacheLevel.PerInstance), 10 | PropertyResolver = new DefaultPropertyResolver(CacheLevel.PerInstance) 11 | }; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/OutputBuildLog.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using Xunit.Abstractions; 4 | 5 | public class OutputBuildLog : DefaultBuildLog 6 | { 7 | private readonly ITestOutputHelper _output; 8 | 9 | public OutputBuildLog(ITestOutputHelper output) 10 | { 11 | _output = output; 12 | } 13 | 14 | protected override void WriteMessage(string message) 15 | { 16 | _output.WriteLine(message); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Simple.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class Simple 6 | { 7 | public int Age => DateTime.UtcNow.Subtract(DOB).Days / 365; 8 | 9 | public DateTime DOB { get; set; } 10 | 11 | public string? FirstName { get; set; } 12 | 13 | public Guid Id { get; set; } 14 | 15 | public bool IsActive { get; set; } 16 | 17 | public string? LastName { get; set; } 18 | 19 | public double Priority { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/PropertyTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using FluentAssertions; 4 | using ModelBuilder.UnitTests.Models; 5 | using Xunit; 6 | 7 | public class PropertyTests 8 | { 9 | [Fact] 10 | public void DoesNotPopulatePropertyContainingConstructorValue() 11 | { 12 | var model = Model.Create(); 13 | 14 | var actual = Model.Create(model); 15 | 16 | actual.Model.Should().Be(model); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/ICantCreate.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public interface ICantCreate 4 | { 5 | string? FirstName { get; set; } 6 | } 7 | 8 | public interface IStillCantCreate : ICantCreate 9 | { 10 | string? LastName { get; set; } 11 | } 12 | 13 | internal class CantCreate : ICantCreate 14 | { 15 | public string? FirstName { get; set; } 16 | } 17 | 18 | public abstract class StillCantCreate : ICantCreate 19 | { 20 | public string? FirstName { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /ModelBuilder/IConfigurationModule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | /// 4 | /// The 5 | /// interface is used to configure a . 6 | /// 7 | public interface IConfigurationModule 8 | { 9 | /// 10 | /// Configures the specified build configuration. 11 | /// 12 | /// The build configuration to update. 13 | void Configure(IBuildConfiguration configuration); 14 | } 15 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/CustomCollection.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class CustomCollection : ICustomCollection where T : class 7 | { 8 | private readonly List _store = new List(); 9 | 10 | public IEnumerator GetEnumerator() 11 | { 12 | return _store.GetEnumerator(); 13 | } 14 | 15 | IEnumerator IEnumerable.GetEnumerator() 16 | { 17 | return GetEnumerator(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ModelBuilder/BuildActions/BuildRequirement.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.BuildActions 2 | { 3 | /// 4 | /// The 5 | /// enum identifies a required build capability. 6 | /// 7 | public enum BuildRequirement 8 | { 9 | /// 10 | /// Identifies that a creation build process is required. 11 | /// 12 | Create, 13 | 14 | /// 15 | /// Identifies that a populate build process is required. 16 | /// 17 | Populate 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyExecuteOrderRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using ModelBuilder.ExecuteOrderRules; 6 | 7 | public class DummyExecuteOrderRule : IExecuteOrderRule 8 | { 9 | public bool IsMatch(ParameterInfo parameter) 10 | { 11 | return false; 12 | } 13 | 14 | public bool IsMatch(PropertyInfo property) 15 | { 16 | return false; 17 | } 18 | 19 | public int Priority { get; } = 100; 20 | 21 | public Guid Value { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PersonWithoutGender.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class PersonWithoutGender 6 | { 7 | public DateTime DOB { get; set; } 8 | 9 | public string? FirstName { get; set; } 10 | 11 | public string? LastName { get; set; } 12 | 13 | public string? Mobile { get; set; } 14 | 15 | public SimpleEnum Order { get; set; } 16 | 17 | public string? PersonalEmail { get; set; } 18 | 19 | public string? Phone { get; set; } 20 | 21 | public int Priority { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Clone.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 6 | public class Clone 7 | { 8 | public Clone(Clone source) 9 | { 10 | } 11 | 12 | public Clone(Clone source, string value) 13 | { 14 | } 15 | 16 | private Clone() 17 | { 18 | } 19 | 20 | public static Clone Create() 21 | { 22 | return new Clone(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Optionals.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 6 | public class Optionals 7 | { 8 | public Optionals(string first) 9 | { 10 | Value = first; 11 | } 12 | 13 | public Optionals(string first, int second, string third, int fourth = 0, byte fifth = 4) 14 | { 15 | Value = first; 16 | } 17 | 18 | public string Value { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/TestConfigurationModule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | public class TestConfigurationModule : IConfigurationModule 4 | { 5 | public void Configure(IBuildConfiguration configuration) 6 | { 7 | configuration.Add(new DummyExecuteOrderRule()); 8 | configuration.Add(new DummyIgnoreRule()); 9 | configuration.Add(new DummyPostBuildAction()); 10 | configuration.Add(new DummyCreationRule()); 11 | configuration.Add(new DummyTypeCreator()); 12 | configuration.Add(new DummyValueGenerator()); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Copy.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 6 | public class Copy 7 | { 8 | public Copy(Copy source) 9 | { 10 | } 11 | 12 | private Copy() 13 | { 14 | } 15 | 16 | public Copy Create() 17 | { 18 | return new Copy 19 | { 20 | Value = Value 21 | }; 22 | } 23 | 24 | public string? Value { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using ModelBuilder.ValueGenerators; 5 | 6 | public class DummyValueGenerator : ValueGeneratorBase 7 | { 8 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | protected override bool IsMatch(IBuildChain buildChain, Type type, string? referenceName) 14 | { 15 | return false; 16 | } 17 | 18 | public Guid Value { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Address.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class Address 4 | { 5 | public string? AddressLine1 { get; set; } 6 | 7 | public string? AddressLine2 { get; set; } 8 | 9 | public string? AddressLine3 { get; set; } 10 | 11 | public string? City { get; set; } 12 | 13 | public char Code { get; set; } 14 | 15 | public string? Country { get; set; } 16 | 17 | public string? Postcode { get; set; } 18 | 19 | public string? State { get; set; } 20 | 21 | public string? Suburb { get; set; } 22 | 23 | public string? TimeZone { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Numbers.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class Numbers 4 | { 5 | public sbyte Eighth { get; set; } 6 | 7 | public decimal Eleventh { get; set; } 8 | 9 | public short Fifth { get; set; } 10 | 11 | public int First { get; set; } 12 | 13 | public ulong Fourth { get; set; } 14 | 15 | public double Nineth { get; set; } 16 | 17 | public uint Second { get; set; } 18 | 19 | public byte Seventh { get; set; } 20 | 21 | public ushort Sixth { get; set; } 22 | 23 | public float Tenth { get; set; } 24 | 25 | public long Third { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /ModelBuilder/ITypeResolver.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// interface defines the members for resolving a type to build. 8 | /// 9 | public interface ITypeResolver 10 | { 11 | /// 12 | /// Gets the type to build based on the specified type. 13 | /// 14 | /// 15 | /// The type being requested. 16 | /// The type to build. 17 | Type GetBuildType(IBuildConfiguration configuration, Type requestedType); 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Location.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | public class Location 7 | { 8 | public Uri? First { get; set; } 9 | 10 | [SuppressMessage( 11 | "Microsoft.Design", 12 | "CA1056", 13 | Justification = "The code is written in this way to validate a test scenario.")] 14 | public string? SecondUrl { get; set; } 15 | 16 | [SuppressMessage( 17 | "Microsoft.Design", 18 | "CA1056", 19 | Justification = "The code is written in this way to validate a test scenario.")] 20 | public string? UriThird { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/OrderedConstructorParameters.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class OrderedConstructorParameters 4 | { 5 | public OrderedConstructorParameters(string email, string domain, string lastName, string firstName, 6 | Gender gender) 7 | { 8 | Email = email; 9 | Domain = domain; 10 | LastName = lastName; 11 | FirstName = firstName; 12 | Gender = gender; 13 | } 14 | 15 | public string Domain { get; } 16 | 17 | public string Email { get; } 18 | public string FirstName { get; } 19 | 20 | public Gender Gender { get; } 21 | 22 | public string LastName { get; } 23 | } 24 | } -------------------------------------------------------------------------------- /ModelBuilder/INullableBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | /// 4 | /// The 5 | /// interface defines the members that control whether a null value can be built when a type is requested. 6 | /// 7 | public interface INullableBuilder 8 | { 9 | /// 10 | /// Gets or sets whether this type can return a null value. 11 | /// 12 | bool AllowNull { get; set; } 13 | 14 | /// 15 | /// Gets or sets the percentage change that a null could be return when is true. 16 | /// 17 | int NullPercentageChance { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/TypeMappingTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using FluentAssertions; 4 | using ModelBuilder.UnitTests.Models; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | public class TypeMappingTests 9 | { 10 | private readonly ITestOutputHelper _output; 11 | 12 | public TypeMappingTests(ITestOutputHelper output) 13 | { 14 | _output = output; 15 | } 16 | 17 | [Fact] 18 | public void TypeMappingChangesReturnType() 19 | { 20 | var actual = Model.Mapping().WriteLog(_output.WriteLine).Create(); 21 | 22 | actual.Should().BeOfType(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PropertyScopes.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | public class PropertyScope 7 | { 8 | public PropertyScope() 9 | { 10 | PrivateSet = Guid.Empty; 11 | GlobalValue = Guid.Empty; 12 | } 13 | 14 | public static Guid GlobalValue { get; set; } 15 | 16 | [SuppressMessage( 17 | "Microsoft.Design", 18 | "CA1822", 19 | Justification = "The code is written in this way to validate a test scenario.")] 20 | public Guid CannotSetValue => Guid.Empty; 21 | 22 | public Guid PrivateSet { get; } 23 | 24 | public Guid Public { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/EnumerableParent.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | [SuppressMessage( 8 | "Microsoft.Usage", 9 | "CA2227", 10 | Justification = "This base type is used specifically for testing scenarios for that data type.")] 11 | public class EnumerableParent 12 | { 13 | public Collection? Collection { get; set; } 14 | 15 | public IEnumerable? Enumerable { get; set; } 16 | 17 | public ICollection? InterfaceCollection { get; set; } 18 | 19 | public IList? InterfaceList { get; set; } 20 | 21 | public List? List { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /ModelBuilder/CacheLevel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | /// 4 | /// The 5 | /// enum defines the level of caching to use. 6 | /// 7 | public enum CacheLevel 8 | { 9 | /// 10 | /// Identifies that no items are stored in a cache. 11 | /// 12 | None = 0, 13 | 14 | /// 15 | /// Identifies that items are stored in a per instance cache. 16 | /// 17 | /// This typically means that a cache is valid per Create/Populate call. 18 | PerInstance, 19 | 20 | /// 21 | /// Identifies that items are stored in a cache that lives across Create/Populate calls. 22 | /// 23 | Global 24 | } 25 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/StringValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate random values. 8 | /// 9 | public class StringValueGenerator : ValueGeneratorMatcher 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public StringValueGenerator() : base(typeof(string)) 15 | { 16 | } 17 | 18 | /// 19 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 20 | { 21 | return Guid.NewGuid().ToString(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /ModelBuilder/IConstructorResolver.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// interface defines the members for resolving constructors. 9 | /// 10 | public interface IConstructorResolver 11 | { 12 | /// 13 | /// Returns the for the type, matching on the specified arguments. 14 | /// 15 | /// The type to evaluate. 16 | /// The optional argument list for the constructor. 17 | /// The constructor matching the type and arguments; or null if no constructor is found. 18 | ConstructorInfo? Resolve(Type type, params object?[]? args); 19 | } 20 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/WithConstructorParameters.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class WithConstructorParameters 6 | { 7 | public WithConstructorParameters(Company first, Guid id, int? refNumber, int number, bool value) 8 | { 9 | First = first; 10 | Id = id; 11 | RefNumber = refNumber; 12 | Number = number; 13 | Value = value; 14 | } 15 | 16 | public Person? Customer { get; set; } 17 | 18 | public Company? First { get; set; } 19 | 20 | public Guid Id { get; set; } 21 | 22 | public int Number { get; set; } 23 | 24 | public int OtherNumber { get; set; } 25 | 26 | public int? RefNumber { get; set; } 27 | 28 | public Company? Second { get; set; } 29 | 30 | public bool Value { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/IncrementingArrayTypeCreator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Globalization; 5 | using ModelBuilder.TypeCreators; 6 | 7 | public class IncrementingArrayTypeCreator : ArrayTypeCreator 8 | { 9 | protected override object? CreateChildItem(Type type, IExecuteStrategy executeStrategy, object? previousItem) 10 | { 11 | if (previousItem == null!) 12 | { 13 | return base.CreateChildItem(type, executeStrategy, null!); 14 | } 15 | 16 | // Use a double as the base type then convert later 17 | var value = Convert.ToDouble(previousItem, CultureInfo.InvariantCulture); 18 | 19 | value++; 20 | 21 | var converted = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); 22 | 23 | return converted; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/RegressionTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using FluentAssertions; 4 | using ModelBuilder.UnitTests.Models; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | public class RegressionTests 9 | { 10 | private readonly ITestOutputHelper _output; 11 | 12 | public RegressionTests(ITestOutputHelper output) 13 | { 14 | _output = output; 15 | } 16 | 17 | [Fact] 18 | public void CreateShouldSkipSelfReferencingPropertiesOnPopulateException() 19 | { 20 | // Fixes https://github.com/roryprimrose/ModelBuilder/issues/347 21 | var action = () => Model.WriteLog(_output.WriteLine).Create(); 22 | 23 | var actual = action.Should().NotThrow().Subject; 24 | 25 | actual.Instance.Should().BeSameAs(actual); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /ModelBuilder/IgnoreRules/IIgnoreRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.IgnoreRules 2 | { 3 | using System.Reflection; 4 | 5 | /// 6 | /// The 7 | /// interface defines how a rule can identify a property that should be ignored by and 8 | /// not populated with a value. 9 | /// 10 | public interface IIgnoreRule 11 | { 12 | /// 13 | /// Returns whether the rule matches the specified property. 14 | /// 15 | /// The property to evaluate. 16 | /// true if the property matches and the rule; otherwise false. 17 | /// If the property matches the rule, the should not populate the property. 18 | bool IsMatch(PropertyInfo propertyInfo); 19 | } 20 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Other.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | 7 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 8 | public class Other 9 | { 10 | public Other(Other source) 11 | { 12 | } 13 | 14 | public Other(Other source, string value) 15 | { 16 | } 17 | 18 | public Other(Guid id, string value, int priority) 19 | { 20 | } 21 | 22 | public Other(Guid id, string value, int priority, Stream data) 23 | { 24 | } 25 | 26 | private Other() 27 | { 28 | } 29 | 30 | public Other Create() 31 | { 32 | return new Other 33 | { 34 | Value = Value 35 | }; 36 | } 37 | 38 | public string? Value { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/LastNameValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using ModelBuilder.Data; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random last name values. 9 | /// 10 | public class LastNameValueGenerator : RelativeValueGenerator 11 | { 12 | /// 13 | /// Initializes a new instance of the . 14 | /// 15 | public LastNameValueGenerator() : base(NameExpression.LastName, typeof(string)) 16 | { 17 | } 18 | 19 | /// 20 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 21 | { 22 | return TestData.LastNames.Next(); 23 | } 24 | 25 | /// 26 | public override int Priority { get; } = 1000; 27 | } 28 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/NullExecuteStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | public class NullExecuteStrategy : IExecuteStrategy 7 | { 8 | public object Create(Type type, params object?[]? args) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public object?[]? CreateParameters(MethodBase method) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public void Initialize(IBuildConfiguration configuration) 19 | { 20 | } 21 | 22 | public object Populate(object instance) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | 27 | public IBuildChain BuildChain { get; } = new BuildHistory(); 28 | 29 | public IBuildConfiguration Configuration { get; } = new BuildConfiguration(); 30 | 31 | public IBuildLog Log { get; } = new DefaultBuildLog(); 32 | } 33 | } -------------------------------------------------------------------------------- /ModelBuilder/IParameterResolver.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// interface defines the members for getting parameters that need to be created. 9 | /// 10 | public interface IParameterResolver 11 | { 12 | /// 13 | /// Gets the parameters on that are to be populated in the order identified by 14 | /// . 15 | /// 16 | /// The build configuration. 17 | /// The method used to create an object. 18 | /// The set of parameters to populate in the order they are to be populated. 19 | IEnumerable GetOrderedParameters(IBuildConfiguration configuration, MethodBase method); 20 | } 21 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/DomainNameValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using ModelBuilder.Data; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random domain name values. 9 | /// 10 | public class DomainNameValueGenerator : ValueGeneratorMatcher 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public DomainNameValueGenerator() : base(NameExpression.Domain, typeof(string)) 16 | { 17 | } 18 | 19 | /// 20 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 21 | { 22 | return TestData.Domains.Next(); 23 | } 24 | 25 | /// 26 | public override int Priority { get; } = 1000; 27 | } 28 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/WithValueParameters.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class WithValueParameters 6 | { 7 | public WithValueParameters( 8 | string firstName, 9 | string lastName, 10 | DateTime dob, 11 | bool isActive, 12 | Guid id, 13 | int priority) 14 | { 15 | FirstName = firstName; 16 | LastName = lastName; 17 | DOB = dob; 18 | IsActive = isActive; 19 | Id = id; 20 | Priority = priority; 21 | } 22 | 23 | public int Age => DateTime.UtcNow.Subtract(DOB).Days / 365; 24 | 25 | public DateTime DOB { get; set; } 26 | 27 | public string FirstName { get; set; } 28 | 29 | public Guid Id { get; set; } 30 | 31 | public bool IsActive { get; set; } 32 | 33 | public string LastName { get; set; } 34 | 35 | public int Priority { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/TimeZoneInfoValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate random values. 8 | /// 9 | public class TimeZoneInfoValueGenerator : ValueGeneratorMatcher 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public TimeZoneInfoValueGenerator() : base(typeof(TimeZoneInfo)) 15 | { 16 | } 17 | 18 | /// 19 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 20 | { 21 | var zones = TimeZoneInfo.GetSystemTimeZones(); 22 | var zoneIndex = Generator.NextValue(0, zones.Count - 1); 23 | 24 | return zones[zoneIndex]; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/CountryValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using ModelBuilder.Data; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random country values. 9 | /// 10 | public class CountryValueGenerator : ValueGeneratorMatcher 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public CountryValueGenerator() : base(NameExpression.Country, typeof(string)) 16 | { 17 | } 18 | 19 | /// 20 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 21 | { 22 | var location = TestData.Locations.Next(); 23 | 24 | return location.Country; 25 | } 26 | 27 | /// 28 | public override int Priority { get; } = 1000; 29 | } 30 | } -------------------------------------------------------------------------------- /ModelBuilder/IBuildChain.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | /// 7 | /// The 8 | /// interface defines the properties that allow inspection of build chain values. 9 | /// 10 | [SuppressMessage( 11 | "Code.Quality", 12 | "CA1710", 13 | Justification = "The build chain is enumerable, but does not have the characteristics of a Collection.")] 14 | public interface IBuildChain : IEnumerable 15 | { 16 | /// 17 | /// Gets the number of items in the build chain. 18 | /// 19 | int Count { get; } 20 | 21 | /// 22 | /// Get the first item added to the build chain. 23 | /// 24 | object? First { get; } 25 | 26 | /// 27 | /// Gets the last item added to the build chain. 28 | /// 29 | object? Last { get; } 30 | } 31 | } -------------------------------------------------------------------------------- /Solution Items/CustomDictionary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | ITypeCreator 15 | IValueGenerator 16 | Mailinator 17 | 18 | 19 | 20 | 21 | postcode 22 | 23 | 24 | 25 | 26 | EHLO 27 | 28 | 29 | -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/BuildHistoryItemTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | public class BuildHistoryItemTests 8 | { 9 | [Fact] 10 | public void CapabilitiesReturnsEmptyByDefault() 11 | { 12 | var value = Guid.NewGuid().ToString(); 13 | 14 | var actual = new BuildHistoryItem(value); 15 | 16 | actual.Capabilities.Should().BeEmpty(); 17 | } 18 | 19 | [Fact] 20 | public void ThrowsExceptionWithNullInstance() 21 | { 22 | // ReSharper disable once ObjectCreationAsStatement 23 | Action action = () => new BuildHistoryItem(null!); 24 | 25 | action.Should().Throw(); 26 | } 27 | 28 | [Fact] 29 | public void ValueReturnsConstructorValue() 30 | { 31 | var value = Guid.NewGuid().ToString(); 32 | 33 | var actual = new BuildHistoryItem(value); 34 | 35 | actual.Value.Should().Be(value); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/ReadOnlyParent.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | 6 | public class ReadOnlyParent 7 | { 8 | public ReadOnlyParent() 9 | { 10 | Company = new Company(); 11 | RestrictedPeople = EmptySet(); 12 | AssignablePeople = new List(); 13 | People = new Collection(); 14 | ReadOnlyPerson = new Person(); 15 | PrivateValue = 0; 16 | } 17 | 18 | private static IEnumerable EmptySet() 19 | { 20 | yield break; 21 | } 22 | 23 | public IEnumerable AssignablePeople { get; } 24 | 25 | public Company Company { get; } 26 | 27 | public ICollection People { get; } 28 | 29 | public int PrivateValue { get; } 30 | 31 | public Person ReadOnlyPerson { get; } 32 | 33 | public IEnumerable RestrictedPeople { get; } 34 | 35 | public Person? Unassigned { get; } 36 | } 37 | } -------------------------------------------------------------------------------- /ModelBuilder/IExecuteStrategyT.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | /// 4 | /// The 5 | /// interface defines the members for creating and populating instances. 6 | /// 7 | /// The type to create or populate. 8 | public interface IExecuteStrategy : IExecuteStrategy where T : notnull 9 | { 10 | /// 11 | /// Creates a new instance of using any specified arguments. 12 | /// 13 | /// The constructor arguments of the type. 14 | /// A new instance of 15 | T Create(params object?[]? args); 16 | 17 | /// 18 | /// Populates values onto settable properties of the specified instance. 19 | /// 20 | /// The instance to populate. 21 | /// A new instance of 22 | T Populate(T instance); 23 | } 24 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Rory Primrose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/NumericTypeDataSource.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | 8 | [SuppressMessage( 9 | "Code.Quality", 10 | "CA1710", 11 | Justification = "The data source not have the characteristics of a Collection.")] 12 | public class NumericTypeDataSource : IEnumerable 13 | { 14 | private readonly List _data = BuildValues(); 15 | 16 | public IEnumerator GetEnumerator() 17 | { 18 | return _data.GetEnumerator(); 19 | } 20 | 21 | IEnumerator IEnumerable.GetEnumerator() 22 | { 23 | return GetEnumerator(); 24 | } 25 | 26 | private static List BuildValues() 27 | { 28 | var source = new NumericTypeRangeDataSource(); 29 | 30 | return source.Select( 31 | x => new[] 32 | { 33 | x[0], x[1] 34 | }).ToList(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /ModelBuilder/BuildHistoryItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using ModelBuilder.BuildActions; 6 | 7 | /// 8 | /// The 9 | /// class is used to track items built and related build capabilities. 10 | /// 11 | public class BuildHistoryItem 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The value created. 17 | public BuildHistoryItem(object value) 18 | { 19 | Value = value ?? throw new ArgumentNullException(nameof(value)); 20 | } 21 | 22 | /// 23 | /// The cache of build capabilities by requested types. 24 | /// 25 | public Dictionary Capabilities { get; } = new Dictionary(); 26 | 27 | /// 28 | /// Gets the value created. 29 | /// 30 | public object Value { get; } 31 | } 32 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/CompanyValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | using ModelBuilder.Data; 6 | 7 | /// 8 | /// The 9 | /// class is used to generate random company name values. 10 | /// 11 | public class CompanyValueGenerator : ValueGeneratorMatcher 12 | { 13 | private static readonly Regex _matchNameExpression = new Regex("Company", RegexOptions.IgnoreCase); 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public CompanyValueGenerator() : base(_matchNameExpression, typeof(string)) 19 | { 20 | } 21 | 22 | /// 23 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 24 | { 25 | return TestData.Companies.Next(); 26 | } 27 | 28 | /// 29 | public override int Priority { get; } = 1000; 30 | } 31 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyTypeCreator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using ModelBuilder.TypeCreators; 5 | 6 | public class DummyTypeCreator : TypeCreatorBase 7 | { 8 | protected override bool CanCreate(IBuildConfiguration configuration, 9 | IBuildChain buildChain, Type type, string? referenceName) 10 | { 11 | return false; 12 | } 13 | 14 | protected override bool CanPopulate(IBuildConfiguration configuration, 15 | IBuildChain buildChain, Type type, string? referenceName) 16 | { 17 | return false; 18 | } 19 | 20 | protected override object? CreateInstance(IExecuteStrategy executeStrategy, 21 | Type type, 22 | string? referenceName, 23 | params object?[]? args) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | protected override object PopulateInstance(IExecuteStrategy executeStrategy, object instance) 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | public Guid Value { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PropertySetters.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class PropertySetters 6 | { 7 | internal float _backingField; 8 | public float BackingFieldMethod() => _backingField; 9 | 10 | public ConsoleColor AutoInit { get; init; } 11 | 12 | public DateTimeOffset AutoInternal { get; internal set; } 13 | 14 | public int AutoPrivate { get; } 15 | 16 | public PropertySetters? AutoPrivateInternal { get; private protected set; } 17 | 18 | public decimal AutoProtected { get; protected set; } 19 | 20 | public Uri? AutoProtectedInternal { get; protected internal set; } 21 | public Guid AutoPublic { get; set; } 22 | 23 | public string? AutoReadonly { get; } = string.Empty; 24 | public float BackingField { get => _backingField; set => _backingField = value; } 25 | 26 | public float PrivateBackingField { get => _backingField; private set => _backingField = value; } 27 | 28 | public float PublicBackingField { get => _backingField; set => _backingField = value; } 29 | 30 | public char? Readonly => default; 31 | } 32 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/CharValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Globalization; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random values. 9 | /// 10 | /// This generator creates char values in the printable ASCII range of 33-126. 11 | public class CharValueGenerator : ValueGeneratorMatcher 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public CharValueGenerator() : base(typeof(char)) 17 | { 18 | } 19 | 20 | /// 21 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 22 | { 23 | // We want to use printable ASCII characters which are in the range 33-126 24 | var internalValue = base.Generator.NextValue(typeof(byte), 33, 126); 25 | 26 | return Convert.ToChar(internalValue, CultureInfo.CurrentCulture); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Solution Items/UnitTest.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DataSet.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | public static class DataSet 8 | { 9 | public static IEnumerable GetNullableParameters(Type type) 10 | { 11 | return from x in type.GetConstructors().Single().GetParameters() 12 | where x.ParameterType.IsNullable() 13 | select new object[] { x }; 14 | } 15 | 16 | public static IEnumerable GetNullableProperties(Type type) 17 | { 18 | return from x in type.GetProperties() 19 | where x.PropertyType.IsNullable() 20 | select new object[] { x }; 21 | } 22 | 23 | public static IEnumerable GetParameters(Type type) 24 | { 25 | return from x in type.GetConstructors().Single().GetParameters() 26 | select new object[] { x }; 27 | } 28 | 29 | public static IEnumerable GetProperties(Type type) 30 | { 31 | return from x in type.GetProperties() 32 | select new object[] { x }; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyCreationRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using ModelBuilder.CreationRules; 6 | 7 | public class DummyCreationRule : ICreationRule 8 | { 9 | public object? Create(IExecuteStrategy executeStrategy, Type type) 10 | { 11 | throw new NotImplementedException(); 12 | } 13 | 14 | public object? Create(IExecuteStrategy executeStrategy, PropertyInfo propertyInfo) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | 19 | public object? Create(IExecuteStrategy executeStrategy, ParameterInfo parameterInfo) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | public bool IsMatch(Type type) 25 | { 26 | return false; 27 | } 28 | 29 | public bool IsMatch(PropertyInfo propertyInfo) 30 | { 31 | return false; 32 | } 33 | 34 | public bool IsMatch(ParameterInfo parameterInfo) 35 | { 36 | return false; 37 | } 38 | 39 | public int Priority { get; } 40 | 41 | public Guid Value { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/FirstNameValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using ModelBuilder.Data; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random first name values. 9 | /// 10 | public class FirstNameValueGenerator : RelativeValueGenerator 11 | { 12 | /// 13 | /// Initializes a new instance of the . 14 | /// 15 | public FirstNameValueGenerator() : base(NameExpression.FirstName, typeof(string)) 16 | { 17 | } 18 | 19 | /// 20 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 21 | { 22 | if (IsMale(executeStrategy)) 23 | { 24 | // Use a male first name 25 | return TestData.MaleNames.Next(); 26 | } 27 | 28 | // Use a female name 29 | return TestData.FemaleNames.Next(); 30 | } 31 | 32 | /// 33 | public override int Priority { get; } = 1000; 34 | } 35 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/MIddleNameValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using ModelBuilder.Data; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random middle name values. 9 | /// 10 | public class MiddleNameValueGenerator : RelativeValueGenerator 11 | { 12 | /// 13 | /// Initializes a new instance of the . 14 | /// 15 | public MiddleNameValueGenerator() : base(NameExpression.MiddleName, typeof(string)) 16 | { 17 | } 18 | 19 | /// 20 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 21 | { 22 | if (IsMale(executeStrategy)) 23 | { 24 | // Use a male first name 25 | return TestData.MaleNames.Next(); 26 | } 27 | 28 | // Use a female name 29 | return TestData.FemaleNames.Next(); 30 | } 31 | 32 | /// 33 | public override int Priority { get; } = 1000; 34 | } 35 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/SuburbValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | using ModelBuilder.Data; 6 | 7 | /// 8 | /// The 9 | /// class is used to generate random suburb values. 10 | /// 11 | public class SuburbValueGenerator : ValueGeneratorMatcher 12 | { 13 | private static readonly Regex _matchNameExpression = new Regex("Suburb", RegexOptions.IgnoreCase); 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public SuburbValueGenerator() : base(_matchNameExpression, typeof(string)) 19 | { 20 | } 21 | 22 | /// 23 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 24 | { 25 | // Some suburbs are named after people so we will just take a random last name 26 | return TestData.LastNames.Next(); 27 | } 28 | 29 | /// 30 | public override int Priority { get; } = 1000; 31 | } 32 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/GenderValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random gender values. 9 | /// 10 | public class GenderValueGenerator : ValueGeneratorMatcher 11 | { 12 | private static readonly Regex _matchNameExpression = new Regex("Gender|Sex", RegexOptions.IgnoreCase); 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public GenderValueGenerator() : base(_matchNameExpression, typeof(string)) 18 | { 19 | } 20 | 21 | /// 22 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 23 | { 24 | var index = Generator.NextValue(0, 1); 25 | 26 | if (index == 0) 27 | { 28 | return "Male"; 29 | } 30 | 31 | return "Female"; 32 | } 33 | 34 | /// 35 | public override int Priority { get; } = 1000; 36 | } 37 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyExecuteStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | public class DummyExecuteStrategy : IExecuteStrategy 7 | { 8 | public string Create(params object?[]? args) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public object Create(Type type, params object?[]? args) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public object?[] CreateParameters(MethodBase method) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public void Initialize(IBuildConfiguration configuration) 24 | { 25 | } 26 | 27 | public string Populate(string instance) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public object Populate(object instance) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | public IBuildChain BuildChain { get; } = new BuildHistory(); 38 | 39 | public IBuildConfiguration Configuration { get; } = new BuildConfiguration(); 40 | 41 | public IBuildLog Log { get; } = new DefaultBuildLog(); 42 | } 43 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/NumericPropertyModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | public class NumericPropertyModel 4 | { 5 | public byte PropByte { get; set; } 6 | public decimal PropDecimal { get; set; } 7 | public double PropDouble { get; set; } 8 | public float PropFloat { get; set; } 9 | public int PropInt { get; set; } 10 | public long PropLong { get; set; } 11 | public byte? PropNullableByte { get; set; } 12 | public decimal? PropNullableDecimal { get; set; } 13 | public double? PropNullableDouble { get; set; } 14 | public float? PropNullableFloat { get; set; } 15 | public int? PropNullableInt { get; set; } 16 | public long? PropNullableLong { get; set; } 17 | public sbyte? PropNullableSbyte { get; set; } 18 | public short? PropNullableShort { get; set; } 19 | public uint? PropNullableUint { get; set; } 20 | public ulong? PropNullableUlong { get; set; } 21 | public ushort? PropNullableUshort { get; set; } 22 | public sbyte PropSbyte { get; set; } 23 | public short PropShort { get; set; } 24 | public uint PropUint { get; set; } 25 | public ulong PropUlong { get; set; } 26 | public ushort PropUshort { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/NonConstructorCreationTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using System; 4 | using FluentAssertions; 5 | using ModelBuilder.UnitTests.Models; 6 | using Xunit; 7 | 8 | public class NonConstructorCreationTests 9 | { 10 | [Fact] 11 | public void CanCreateViaSingletonProperty() 12 | { 13 | var actual = Model.Create(); 14 | 15 | actual.Value.Should().NotBeNullOrWhiteSpace(); 16 | } 17 | 18 | [Fact] 19 | public void CanCreateViaStaticFactoryMethodWithCreatedParameters() 20 | { 21 | var actual = Model.Create(); 22 | 23 | actual.Value.Should().NotBeEmpty(); 24 | } 25 | 26 | [Fact] 27 | public void CanCreateViaStaticFactoryMethodWithoutParameters() 28 | { 29 | var actual = Model.Create(); 30 | 31 | actual.Value.Should().NotBeEmpty(); 32 | } 33 | 34 | [Fact] 35 | public void CanCreateViaStaticFactoryMethodWithProvidedParameters() 36 | { 37 | var value = Guid.NewGuid(); 38 | 39 | var actual = Model.Create(value); 40 | 41 | actual.Value.Should().Be(value); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/DummyPostBuildAction.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using ModelBuilder.UnitTests.Models; 6 | 7 | public class DummyPostBuildAction : IPostBuildAction 8 | { 9 | public void Execute(IBuildChain buildChain, object instance, Type type) 10 | { 11 | } 12 | 13 | public void Execute(IBuildChain buildChain, object instance, ParameterInfo parameterInfo) 14 | { 15 | } 16 | 17 | public void Execute(IBuildChain buildChain, object instance, PropertyInfo propertyInfo) 18 | { 19 | } 20 | 21 | public bool IsMatch(IBuildChain buildChain, Type type) 22 | { 23 | if (type == typeof(Company)) 24 | { 25 | return true; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | public bool IsMatch(IBuildChain buildChain, ParameterInfo parameterInfo) 32 | { 33 | return IsMatch(buildChain, parameterInfo.ParameterType); 34 | } 35 | 36 | public bool IsMatch(IBuildChain buildChain, PropertyInfo propertyInfo) 37 | { 38 | return IsMatch(buildChain, propertyInfo.PropertyType); 39 | } 40 | 41 | public int Priority { get; } 42 | 43 | public Guid Value { get; set; } 44 | } 45 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/WithMixedValueParameters.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 7 | public class WithMixedValueParameters 8 | { 9 | public WithMixedValueParameters() 10 | { 11 | } 12 | 13 | public WithMixedValueParameters(string someIgnoredValue) 14 | { 15 | FirstName = Guid.NewGuid().ToString(); 16 | } 17 | 18 | public WithMixedValueParameters(string firstName, string lastName) 19 | { 20 | FirstName = firstName; 21 | LastName = lastName; 22 | } 23 | 24 | public WithMixedValueParameters(string firstName, string lastName, DateTime dob, bool isActive, Guid id) 25 | { 26 | FirstName = firstName; 27 | LastName = lastName; 28 | DOB = dob; 29 | IsActive = isActive; 30 | Id = id; 31 | } 32 | 33 | public int Age => DateTime.UtcNow.Subtract(DOB).Days / 365; 34 | 35 | public DateTime DOB { get; set; } 36 | 37 | public string? FirstName { get; set; } 38 | 39 | public Guid Id { get; set; } 40 | 41 | public bool IsActive { get; set; } 42 | 43 | public string? LastName { get; set; } 44 | } 45 | } -------------------------------------------------------------------------------- /ModelBuilder/IgnoreRules/PredicateIgnoreRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.IgnoreRules 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// class is used to match the rule to a using a predicate. 9 | /// 10 | public class PredicateIgnoreRule : IIgnoreRule 11 | { 12 | private readonly Predicate _predicate; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The predicate to evaluate. 18 | /// The parameter is null. 19 | public PredicateIgnoreRule(Predicate predicate) 20 | { 21 | _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); 22 | } 23 | 24 | /// 25 | /// The parameter is null. 26 | public bool IsMatch(PropertyInfo propertyInfo) 27 | { 28 | propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); 29 | 30 | return _predicate(propertyInfo); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/WithInterfaceAndAbstractParameters.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | [SuppressMessage("Microsoft.Usage", "CA1801", Justification = "Constructors are called indirectly.")] 7 | public class WithInterfaceAndAbstractParameters 8 | { 9 | public WithInterfaceAndAbstractParameters(IEntityResolver resolver) 10 | { 11 | } 12 | 13 | public WithInterfaceAndAbstractParameters(IEntityResolver resolver, Entity entity) 14 | { 15 | } 16 | 17 | public WithInterfaceAndAbstractParameters( 18 | string firstName, 19 | string lastName, 20 | DateTime dob, 21 | bool isActive, 22 | Guid id) 23 | { 24 | FirstName = firstName; 25 | LastName = lastName; 26 | DOB = dob; 27 | IsActive = isActive; 28 | Id = id; 29 | } 30 | 31 | internal WithInterfaceAndAbstractParameters() 32 | { 33 | } 34 | 35 | public int Age => DateTime.UtcNow.Subtract(DOB).Days / 365; 36 | 37 | public DateTime DOB { get; set; } 38 | 39 | public string? FirstName { get; set; } 40 | 41 | public Guid Id { get; set; } 42 | 43 | public bool IsActive { get; set; } 44 | 45 | public string? LastName { get; set; } 46 | } 47 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/GuidValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate values. 8 | /// 9 | public class GuidValueGenerator : ValueGeneratorMatcher, INullableBuilder 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public GuidValueGenerator() : base(typeof(Guid), typeof(Guid?)) 15 | { 16 | } 17 | 18 | /// 19 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 20 | { 21 | if (type == typeof(Guid) 22 | || AllowNull == false) 23 | { 24 | return Guid.NewGuid(); 25 | } 26 | 27 | // Allow for a % the chance that this might be null 28 | var range = Generator.NextValue(0, 100000); 29 | 30 | if (range < NullPercentageChance * 1000) 31 | { 32 | return null; 33 | } 34 | 35 | return Guid.NewGuid(); 36 | } 37 | 38 | /// 39 | public bool AllowNull { get; set; } = false; 40 | 41 | /// 42 | public int NullPercentageChance { get; set; } = 10; 43 | } 44 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/TestDataTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using FluentAssertions; 4 | using ModelBuilder.Data; 5 | using Xunit; 6 | 7 | public class TestDataTests 8 | { 9 | [Fact] 10 | public void CompaniesReturnsTestData() 11 | { 12 | var sut = TestData.Companies; 13 | 14 | sut.Should().NotBeEmpty(); 15 | } 16 | 17 | [Fact] 18 | public void DomainsReturnsTestData() 19 | { 20 | var sut = TestData.Domains; 21 | 22 | sut.Should().NotBeEmpty(); 23 | } 24 | 25 | [Fact] 26 | public void FemaleNamesReturnsTestData() 27 | { 28 | var sut = TestData.FemaleNames; 29 | 30 | sut.Should().NotBeEmpty(); 31 | } 32 | 33 | [Fact] 34 | public void LastNamesReturnsTestData() 35 | { 36 | var sut = TestData.LastNames; 37 | 38 | sut.Should().NotBeEmpty(); 39 | } 40 | 41 | [Fact] 42 | public void LocationsReturnsTestData() 43 | { 44 | var sut = TestData.Locations; 45 | 46 | sut.Should().NotBeEmpty(); 47 | } 48 | 49 | [Fact] 50 | public void MaleNamesReturnsTestData() 51 | { 52 | var sut = TestData.MaleNames; 53 | 54 | sut.Should().NotBeEmpty(); 55 | } 56 | 57 | [Fact] 58 | public void TimeZonesReturnsTestData() 59 | { 60 | var sut = TestData.TimeZones; 61 | 62 | sut.Should().NotBeEmpty(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/CultureValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | 8 | /// 9 | /// The 10 | /// class is used to generate random culture values. 11 | /// 12 | public class CultureValueGenerator : ValueGeneratorMatcher 13 | { 14 | private static readonly Regex _matchNameExpression = new Regex("Culture", RegexOptions.IgnoreCase); 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public CultureValueGenerator() : base(_matchNameExpression, typeof(string), typeof(CultureInfo)) 20 | { 21 | } 22 | 23 | /// 24 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 25 | { 26 | var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures) 27 | .Where(x => string.IsNullOrWhiteSpace(x.Name) == false).ToList(); 28 | var index = Generator.NextValue(0, cultures.Count - 1); 29 | var culture = cultures[index]; 30 | 31 | if (type == typeof(string)) 32 | { 33 | return culture.Name; 34 | } 35 | 36 | return culture; 37 | } 38 | 39 | /// 40 | public override int Priority { get; } = 1000; 41 | } 42 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Data/LocationTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Data 2 | { 3 | using System; 4 | using FluentAssertions; 5 | using ModelBuilder.Data; 6 | using Xunit; 7 | 8 | public class LocationTests 9 | { 10 | [Fact] 11 | public void ParseCorrectlyAssignsValuesToProperties() 12 | { 13 | var country = Guid.NewGuid().ToString(); 14 | var state = Guid.NewGuid().ToString(); 15 | var city = Guid.NewGuid().ToString(); 16 | var postcode = Guid.NewGuid().ToString(); 17 | var streetName = Guid.NewGuid().ToString(); 18 | var streetSuffix = Guid.NewGuid().ToString(); 19 | var phone = Guid.NewGuid().ToString(); 20 | var data = $@"{country},{state},{city},{postcode},{streetName},{streetSuffix},{phone}"; 21 | 22 | var actual = Location.Parse(data); 23 | 24 | actual.Country.Should().Be(country); 25 | actual.State.Should().Be(state); 26 | actual.City.Should().Be(city); 27 | actual.PostCode.Should().Be(postcode); 28 | actual.StreetName.Should().Be(streetName); 29 | actual.StreetSuffix.Should().Be(streetSuffix); 30 | actual.Phone.Should().Be(phone); 31 | } 32 | 33 | [Theory] 34 | [InlineData(null)] 35 | [InlineData("")] 36 | [InlineData(" ")] 37 | public void ParseThrowsExceptionWithInvalidData(string? data) 38 | { 39 | Action action = () => Location.Parse(data!); 40 | 41 | action.Should().Throw(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /ModelBuilder/IBuildHistory.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using ModelBuilder.BuildActions; 6 | 7 | /// 8 | /// The 9 | /// interface defines the members for adding and removing items from the build chain. 10 | /// 11 | [SuppressMessage( 12 | "Code.Quality", 13 | "CA1710", 14 | Justification = "The history is enumerable, but does not have the characteristics of a Collection.")] 15 | public interface IBuildHistory : IBuildChain 16 | { 17 | /// 18 | /// Adds a build capability for the specified type. 19 | /// 20 | /// The type being created. 21 | /// The build capability. 22 | void AddCapability(Type type, IBuildCapability capability); 23 | 24 | /// 25 | /// Gets the build capability for the specified type. 26 | /// 27 | /// The type to build. 28 | /// The build capability or null if no capability exists for the type. 29 | IBuildCapability? GetCapability(Type type); 30 | 31 | /// 32 | /// Removes the last item added to the build chain. 33 | /// 34 | void Pop(); 35 | 36 | /// 37 | /// Tracks the specified item in the build chain. 38 | /// 39 | /// The item to track. 40 | void Push(object instance); 41 | } 42 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/PopulateOrderItem.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public class PopulateOrderItem 6 | { 7 | private Person? _w; 8 | private string? _x; 9 | private int _y = int.MinValue; 10 | 11 | public Person? W 12 | { 13 | get { return _w; } 14 | set 15 | { 16 | if (_x == null!) 17 | { 18 | throw new InvalidOperationException( 19 | "Execution order was not run as expected because the string was not assigned before the class"); 20 | } 21 | 22 | _w = value; 23 | } 24 | } 25 | 26 | public string? X 27 | { 28 | get { return _x; } 29 | set 30 | { 31 | if (_y == int.MinValue) 32 | { 33 | throw new InvalidOperationException( 34 | "Execution order was not run as expected because the int was not assigned before the string"); 35 | } 36 | 37 | _x = value; 38 | } 39 | } 40 | 41 | public int Y 42 | { 43 | get { return _y; } 44 | set 45 | { 46 | if (Z == (SimpleEnum)int.MinValue) 47 | { 48 | throw new InvalidOperationException( 49 | "Execution order was not run as expected because the enum was not assigned before the int"); 50 | } 51 | 52 | _y = value; 53 | } 54 | } 55 | 56 | public SimpleEnum Z { get; set; } = (SimpleEnum)int.MinValue; 57 | } 58 | } -------------------------------------------------------------------------------- /ModelBuilder/ExecuteOrderRules/IExecuteOrderRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ExecuteOrderRules 2 | { 3 | using System.Reflection; 4 | 5 | /// 6 | /// The 7 | /// interface defines how a rule can identify the order in which properties should be populated by 8 | /// . 9 | /// 10 | public interface IExecuteOrderRule 11 | { 12 | /// 13 | /// Returns whether the rule matches the specified parameter. 14 | /// 15 | /// The parameter to evaluate. 16 | /// true if the parameter matches and the rule; otherwise false. 17 | /// 18 | /// If the parameter matches the rule, the should use the priority to evaluate 19 | /// ordering. 20 | /// 21 | bool IsMatch(ParameterInfo parameterInfo); 22 | 23 | /// 24 | /// Returns whether the rule matches the specified property. 25 | /// 26 | /// The property to evaluate. 27 | /// true if the property matches and the rule; otherwise false. 28 | /// 29 | /// If the property matches the rule, the should use the priority to evaluate 30 | /// ordering. 31 | /// 32 | bool IsMatch(PropertyInfo propertyInfo); 33 | 34 | /// 35 | /// Gets the priority for this rule. 36 | /// 37 | int Priority { get; } 38 | } 39 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/BooleanValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate random values. 8 | /// 9 | public class BooleanValueGenerator : ValueGeneratorMatcher, INullableBuilder 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public BooleanValueGenerator() : base(typeof(bool), typeof(bool?)) 15 | { 16 | } 17 | 18 | /// 19 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 20 | { 21 | var generateType = type; 22 | 23 | if (generateType.IsNullable()) 24 | { 25 | if (AllowNull) 26 | { 27 | // Allow for a % the chance that this might be null 28 | var range = Generator.NextValue(0, 100000); 29 | 30 | if (range < NullPercentageChance * 1000) 31 | { 32 | return null; 33 | } 34 | } 35 | } 36 | 37 | var nextValue = Generator.NextValue(0, 1); 38 | 39 | if (nextValue == 0) 40 | { 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | /// 48 | public bool AllowNull { get; set; } = false; 49 | 50 | /// 51 | public int NullPercentageChance { get; set; } = 10; 52 | } 53 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/StructModel.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | 5 | public struct StructModel : IEquatable 6 | { 7 | public bool Equals(StructModel other) 8 | { 9 | return Id.Equals(other.Id) && FirstName == other.FirstName && LastName == other.LastName 10 | && Email == other.Email; 11 | } 12 | 13 | public override bool Equals(object? obj) 14 | { 15 | return obj is StructModel other && Equals(other); 16 | } 17 | 18 | public override int GetHashCode() 19 | { 20 | unchecked 21 | { 22 | var hashCode = Id.GetHashCode(); 23 | hashCode = (hashCode * 397) 24 | ^ (FirstName != null ? FirstName.GetHashCode(StringComparison.CurrentCulture) : 0); 25 | hashCode = (hashCode * 397) 26 | ^ (LastName != null ? LastName.GetHashCode(StringComparison.CurrentCulture) : 0); 27 | hashCode = (hashCode * 397) ^ (Email != null ? Email.GetHashCode(StringComparison.CurrentCulture) : 0); 28 | return hashCode; 29 | } 30 | } 31 | 32 | public static bool operator ==(StructModel left, StructModel right) 33 | { 34 | return left.Equals(right); 35 | } 36 | 37 | public static bool operator !=(StructModel left, StructModel right) 38 | { 39 | return !left.Equals(right); 40 | } 41 | 42 | public Guid Id { get; set; } 43 | 44 | public string? FirstName { get; set; } 45 | 46 | public string? LastName { get; set; } 47 | 48 | public string? Email { get; set; } 49 | } 50 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/RegexTypeNameValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate a value for a target type and property/parameter name. 9 | /// 10 | public abstract class RegexTypeNameValueGenerator : ValueGeneratorBase 11 | { 12 | private readonly Regex _nameExpression; 13 | private readonly Type _type; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The regular expression that matches the target name. 19 | /// The type of value to generate. 20 | protected RegexTypeNameValueGenerator(Regex nameExpression, Type type) 21 | { 22 | _nameExpression = nameExpression ?? throw new ArgumentNullException(nameof(nameExpression)); 23 | _type = type ?? throw new ArgumentNullException(nameof(type)); 24 | } 25 | 26 | /// 27 | protected override bool IsMatch(IBuildChain buildChain, Type type, string? referenceName) 28 | { 29 | if (_type.IsAssignableFrom(type) == false) 30 | { 31 | return false; 32 | } 33 | 34 | if (string.IsNullOrWhiteSpace(referenceName)) 35 | { 36 | return false; 37 | } 38 | 39 | if (_nameExpression.IsMatch(referenceName)) 40 | { 41 | return true; 42 | } 43 | 44 | return false; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ModelBuilder/IPropertyResolver.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | /// 8 | /// The 9 | /// interface defines the members for resolving information about properties. 10 | /// 11 | public interface IPropertyResolver 12 | { 13 | /// 14 | /// Gets the properties on that are to be populated in the order identified by 15 | /// . 16 | /// 17 | /// The build configuration. 18 | /// The target type to populate. 19 | /// The set of properties to populate in the order they are to be populated. 20 | IEnumerable GetOrderedProperties(IBuildConfiguration configuration, Type targetType); 21 | 22 | /// 23 | /// Determines whether the property should be populated with a value based on arguments provided. 24 | /// 25 | /// The build configuration. 26 | /// The instance being populated. 27 | /// The property to evaluate. 28 | /// The constructor parameters for the instance. 29 | /// true if the property should be populated; otherwise false. 30 | bool IsIgnored( 31 | IBuildConfiguration configuration, 32 | object instance, 33 | PropertyInfo propertyInfo, 34 | object?[]? args); 35 | } 36 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/Person.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | public class Person : Entity 7 | { 8 | [SuppressMessage( 9 | "Microsoft.Design", 10 | "CA1051", 11 | Justification = "The code is written in this way to validate a test scenario.")] 12 | public int MinAge = 0; 13 | 14 | public Person() 15 | { 16 | } 17 | 18 | public Person(Entity entity) 19 | { 20 | Id = entity.Id; 21 | IsActive = entity.IsActive; 22 | } 23 | 24 | public Person(string firstName, string lastName, DateTime dob, bool isActive, Guid id, int priority) 25 | { 26 | FirstName = firstName; 27 | LastName = lastName; 28 | DOB = dob; 29 | IsActive = isActive; 30 | Id = id; 31 | Priority = priority; 32 | } 33 | 34 | public object? DoSomething() 35 | { 36 | return FirstName; 37 | } 38 | 39 | public Address? Address { get; set; } 40 | 41 | public int Age => DateTime.UtcNow.Subtract(DOB).Days / 365; 42 | 43 | public DateTime DOB { get; set; } 44 | 45 | public string? FirstName { get; set; } 46 | 47 | public Gender Gender { get; set; } 48 | 49 | public string? LastName { get; set; } 50 | 51 | public string? Mobile { get; set; } 52 | 53 | public SimpleEnum Order { get; set; } 54 | 55 | public string? PersonalEmail { get; set; } 56 | 57 | public string? Phone { get; set; } 58 | 59 | public int Priority { get; set; } 60 | 61 | public string? TimeZone { get; set; } 62 | 63 | public string? WorkEmail { get; set; } 64 | } 65 | } -------------------------------------------------------------------------------- /ModelBuilder/IRandomGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// interface defines the members for generating random numeric values. 8 | /// 9 | public interface IRandomGenerator 10 | { 11 | /// 12 | /// Gets the maximum value for the specified type. 13 | /// 14 | /// The type to evaluate. 15 | /// The maximum value for the type. 16 | object GetMax(Type type); 17 | 18 | /// 19 | /// Gets the minimum value for the specified type. 20 | /// 21 | /// The type to evaluate. 22 | /// The minimum value for the type. 23 | object GetMin(Type type); 24 | 25 | /// 26 | /// Gets whether the specified type is supported by the generator. 27 | /// 28 | /// The type to evaluate. 29 | /// true if the type is supported; otherwise false. 30 | bool IsSupported(Type type); 31 | 32 | /// 33 | /// Generates a new random value constrained to the specified minimum and maximum boundaries. 34 | /// 35 | /// The type of number to generate. 36 | /// The minimum value. 37 | /// The maximum value. 38 | /// A new random value. 39 | object NextValue(Type type, object min, object max); 40 | 41 | /// 42 | /// Populates the specified buffer with random bytes. 43 | /// 44 | void NextValue(byte[] buffer); 45 | } 46 | } -------------------------------------------------------------------------------- /ModelBuilder/TypeMappingRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Globalization; 5 | using ModelBuilder.Properties; 6 | 7 | /// 8 | /// The 9 | /// class defines a mapping between two types. 10 | /// 11 | public class TypeMappingRule 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The source type. 17 | /// The target type 18 | /// The parameter is null. 19 | /// The parameter is null. 20 | public TypeMappingRule(Type sourceType, Type targetType) 21 | { 22 | SourceType = sourceType ?? throw new ArgumentNullException(nameof(sourceType)); 23 | TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); 24 | 25 | if (SourceType.IsAssignableFrom(TargetType) == false) 26 | { 27 | var message = string.Format( 28 | CultureInfo.CurrentCulture, 29 | Resources.TypeMappingRule_TypeNotAssignable, 30 | sourceType.FullName, 31 | targetType.FullName); 32 | 33 | throw new ArgumentException(message, nameof(targetType)); 34 | } 35 | } 36 | 37 | /// 38 | /// Gets the source type. 39 | /// 40 | public Type SourceType { get; } 41 | 42 | /// 43 | /// Gets the target type. 44 | /// 45 | public Type TargetType { get; } 46 | } 47 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/CountValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate numeric values for parameters and properties that look like count or length values. 8 | /// 9 | public class CountValueGenerator : NumericValueGenerator 10 | { 11 | /// 12 | protected override object GetMaximum(Type type, string? referenceName, object? context) 13 | { 14 | return MaxCount; 15 | } 16 | 17 | /// 18 | protected override object GetMinimum(Type type, string? referenceName, object? context) 19 | { 20 | return 1; 21 | } 22 | 23 | /// 24 | protected override bool IsMatch(IBuildChain buildChain, Type type, string? referenceName) 25 | { 26 | var baseSupported = base.IsMatch(buildChain, type, referenceName); 27 | 28 | if (baseSupported == false) 29 | { 30 | return false; 31 | } 32 | 33 | if (referenceName == null) 34 | { 35 | return false; 36 | } 37 | 38 | if (referenceName.Equals("count", StringComparison.OrdinalIgnoreCase)) 39 | { 40 | return true; 41 | } 42 | 43 | if (referenceName.Equals("length", StringComparison.OrdinalIgnoreCase)) 44 | { 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | /// 52 | /// Gets or sets the maximum count generated by this instance. 53 | /// 54 | public int MaxCount { get; set; } = 30; 55 | 56 | /// 57 | public override int Priority { get; } = 1000; 58 | } 59 | } -------------------------------------------------------------------------------- /ModelBuilder/IgnoreRules/ExpressionIgnoreRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.IgnoreRules 2 | { 3 | using System; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | /// 8 | /// The 9 | /// class is used to identify a property on a type that should be ignored by and not be 10 | /// populated. 11 | /// 12 | /// The type being evaluated. 13 | public class ExpressionIgnoreRule : IIgnoreRule 14 | { 15 | private readonly Expression> _expression; 16 | 17 | /// 18 | /// Creates a new instance of the class. 19 | /// 20 | /// The expression used to identify a property on a type. 21 | /// The parameter is null. 22 | public ExpressionIgnoreRule(Expression> expression) 23 | { 24 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 25 | } 26 | 27 | /// 28 | /// The parameter is null. 29 | public bool IsMatch(PropertyInfo propertyInfo) 30 | { 31 | propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); 32 | 33 | var expressionProperty = _expression.GetProperty(); 34 | 35 | if (propertyInfo.Name != expressionProperty.Name) 36 | { 37 | return false; 38 | } 39 | 40 | if (propertyInfo.DeclaringType != expressionProperty.DeclaringType) 41 | { 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/UriValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using ModelBuilder.Data; 5 | 6 | /// 7 | /// The 8 | /// class is used to generate random uri values. 9 | /// 10 | public class UriValueGenerator : ValueGeneratorBase 11 | { 12 | /// 13 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 14 | { 15 | var domain = TestData.Domains.Next(); 16 | var value = "https://www." + domain; 17 | 18 | if (type == typeof(Uri)) 19 | { 20 | return new Uri(value); 21 | } 22 | 23 | return value; 24 | } 25 | 26 | /// 27 | /// The parameter is null. 28 | protected override bool IsMatch(IBuildChain buildChain, Type type, string? referenceName) 29 | { 30 | type = type ?? throw new ArgumentNullException(nameof(type)); 31 | 32 | if (type == typeof(Uri)) 33 | { 34 | return true; 35 | } 36 | 37 | if (type != typeof(string)) 38 | { 39 | return false; 40 | } 41 | 42 | if (string.IsNullOrEmpty(referenceName)) 43 | { 44 | return false; 45 | } 46 | 47 | if (referenceName!.IndexOf("url", StringComparison.OrdinalIgnoreCase) > -1) 48 | { 49 | return true; 50 | } 51 | 52 | if (referenceName.IndexOf("uri", StringComparison.OrdinalIgnoreCase) > -1) 53 | { 54 | return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | /// 61 | public override int Priority { get; } = 1000; 62 | } 63 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/StateValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using ModelBuilder.Data; 7 | 8 | /// 9 | /// The 10 | /// class is used to generate state addressing values. 11 | /// 12 | public class StateValueGenerator : RelativeValueGenerator 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public StateValueGenerator() : base(NameExpression.State, typeof(string)) 18 | { 19 | } 20 | 21 | /// 22 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 23 | { 24 | var context = executeStrategy?.BuildChain?.Last; 25 | IEnumerable locations = TestData.Locations; 26 | 27 | if (context != null) 28 | { 29 | var country = GetValue(NameExpression.Country, context); 30 | 31 | if (string.IsNullOrWhiteSpace(country) == false) 32 | { 33 | locations = locations 34 | .Where(x => x.Country.Equals(country, StringComparison.OrdinalIgnoreCase)).ToList(); 35 | } 36 | } 37 | 38 | var availableLocations = locations.ToList(); 39 | 40 | if (availableLocations.Count > 0) 41 | { 42 | var matchingLocation = availableLocations.Next(); 43 | 44 | return matchingLocation.State; 45 | } 46 | 47 | // There was either no country or no match on the country 48 | var location = TestData.Locations.Next(); 49 | 50 | return location.State; 51 | } 52 | 53 | /// 54 | public override int Priority { get; } = 1000; 55 | } 56 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/NumericGenerationTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using FluentAssertions; 4 | using ModelBuilder.TypeCreators; 5 | using ModelBuilder.UnitTests.Models; 6 | using ModelBuilder.ValueGenerators; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | public class NumericGenerationTests 11 | { 12 | private readonly ITestOutputHelper _output; 13 | 14 | public NumericGenerationTests(ITestOutputHelper output) 15 | { 16 | _output = output; 17 | } 18 | 19 | [Fact] 20 | public void CanCreateAllNumberTypes() 21 | { 22 | var actual = Model.WriteLog(_output.WriteLine).Create(); 23 | 24 | actual.First.Should().NotBe(0); 25 | actual.Second.Should().NotBe(0); 26 | actual.Third.Should().NotBe(0); 27 | actual.Fourth.Should().NotBe(0); 28 | actual.Fifth.Should().NotBe(0); 29 | actual.Sixth.Should().NotBe(0); 30 | actual.Seventh.Should().NotBe(0); 31 | actual.Eighth.Should().NotBe(0); 32 | actual.Nineth.Should().NotBe(0); 33 | actual.Tenth.Should().NotBe(0); 34 | } 35 | 36 | [Fact] 37 | public void CanCreateNumericParameters() 38 | { 39 | var config = BuildConfigurationFactory.CreateEmpty().AddTypeCreator() 40 | .AddValueGenerator().WriteLog(_output.WriteLine); 41 | 42 | var actual = config.Create(); 43 | 44 | actual.Should().NotBeNull(); 45 | } 46 | 47 | [Fact] 48 | public void CanCreateNumericProperties() 49 | { 50 | var config = BuildConfigurationFactory.CreateEmpty().AddTypeCreator() 51 | .AddValueGenerator().WriteLog(_output.WriteLine); 52 | 53 | var actual = config.Create(); 54 | 55 | actual.Should().NotBeNull(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/PhoneValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using ModelBuilder.Data; 8 | 9 | /// 10 | /// The 11 | /// class is used to generate phone numbers. 12 | /// 13 | public class PhoneValueGenerator : RelativeValueGenerator 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public PhoneValueGenerator() : base(new Regex("Phone|Cell|Mobile|Fax", RegexOptions.IgnoreCase), typeof(string)) 19 | { 20 | } 21 | 22 | /// 23 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 24 | { 25 | var context = executeStrategy?.BuildChain?.Last; 26 | IEnumerable locations = TestData.Locations; 27 | 28 | if (context != null) 29 | { 30 | var country = GetValue(NameExpression.Country, context); 31 | 32 | if (string.IsNullOrWhiteSpace(country) == false) 33 | { 34 | locations = locations 35 | .Where(x => x.Country.Equals(country, StringComparison.OrdinalIgnoreCase)).ToList(); 36 | } 37 | } 38 | 39 | var availableLocations = locations.ToList(); 40 | 41 | if (availableLocations.Count > 0) 42 | { 43 | var matchingLocation = availableLocations.Next(); 44 | 45 | return matchingLocation.Phone; 46 | } 47 | 48 | // There was either no country or no match on the country 49 | var location = TestData.Locations.Next(); 50 | 51 | return location.Phone; 52 | } 53 | 54 | /// 55 | public override int Priority { get; } = 1000; 56 | } 57 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ModelBuilder.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | ..\Solution Items\UnitTest.ruleset 6 | en-US 7 | latest 8 | enable 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | 34 | 35 | BuildConfigurationExtensionsTests.cs 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ModelBuilder/BuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System.Collections.Generic; 4 | using ModelBuilder.CreationRules; 5 | using ModelBuilder.ExecuteOrderRules; 6 | using ModelBuilder.IgnoreRules; 7 | using ModelBuilder.TypeCreators; 8 | using ModelBuilder.ValueGenerators; 9 | 10 | /// 11 | /// The 12 | /// class is used to contain all the configuration required to create values. 13 | /// 14 | public class BuildConfiguration : IBuildConfiguration 15 | { 16 | /// 17 | public IConstructorResolver ConstructorResolver { get; set; } = 18 | new DefaultConstructorResolver(CacheLevel.PerInstance); 19 | 20 | /// 21 | public ICollection CreationRules { get; } = new List(); 22 | 23 | /// 24 | public ICollection ExecuteOrderRules { get; } = new List(); 25 | 26 | /// 27 | public ICollection IgnoreRules { get; } = new List(); 28 | 29 | /// 30 | public IParameterResolver ParameterResolver { get; set; } = 31 | new DefaultParameterResolver(CacheLevel.PerInstance); 32 | 33 | /// 34 | public ICollection PostBuildActions { get; } = new List(); 35 | 36 | /// 37 | public IPropertyResolver PropertyResolver { get; set; } = new DefaultPropertyResolver(CacheLevel.PerInstance); 38 | 39 | /// 40 | public ICollection TypeCreators { get; } = new List(); 41 | 42 | /// 43 | public ICollection TypeMappingRules { get; } = new List(); 44 | 45 | /// 46 | public ITypeResolver TypeResolver { get; set; } = new DefaultTypeResolver(); 47 | 48 | /// 49 | public ICollection ValueGenerators { get; } = new List(); 50 | } 51 | } -------------------------------------------------------------------------------- /ModelBuilder/ExecuteOrderRules/PropertyPredicateExecuteOrderRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ExecuteOrderRules 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// class is used to match the rule to a using a predicate. 9 | /// 10 | public class PropertyPredicateExecuteOrderRule : IExecuteOrderRule 11 | { 12 | private readonly Predicate _predicate; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The predicate to evaluate. 18 | /// The execution order priority to apply to the property. 19 | /// The parameter is null. 20 | public PropertyPredicateExecuteOrderRule(Predicate predicate, int priority) 21 | { 22 | _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); 23 | 24 | Priority = priority; 25 | } 26 | 27 | /// 28 | /// The parameter is null. 29 | public bool IsMatch(ParameterInfo parameterInfo) 30 | { 31 | return false; 32 | } 33 | 34 | /// 35 | /// The parameter is null. 36 | public bool IsMatch(PropertyInfo propertyInfo) 37 | { 38 | propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); 39 | 40 | return _predicate(propertyInfo); 41 | } 42 | 43 | /// 44 | public override string ToString() 45 | { 46 | return _predicate.ToString() ?? ""; 47 | } 48 | 49 | /// 50 | public int Priority { get; } 51 | } 52 | } -------------------------------------------------------------------------------- /ModelBuilder/ExecuteOrderRules/ParameterPredicateExecuteOrderRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ExecuteOrderRules 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// class is used to match the rule to a using a predicate. 9 | /// 10 | public class ParameterPredicateExecuteOrderRule : IExecuteOrderRule 11 | { 12 | private readonly Predicate _predicate; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The predicate to evaluate. 18 | /// The execution order priority to apply to the parameter. 19 | /// The parameter is null. 20 | public ParameterPredicateExecuteOrderRule(Predicate predicate, int priority) 21 | { 22 | _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); 23 | 24 | Priority = priority; 25 | } 26 | 27 | /// 28 | /// The parameter is null. 29 | public bool IsMatch(ParameterInfo parameterInfo) 30 | { 31 | parameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo)); 32 | 33 | return _predicate(parameterInfo); 34 | } 35 | 36 | /// 37 | /// The parameter is null. 38 | public bool IsMatch(PropertyInfo propertyInfo) 39 | { 40 | return false; 41 | } 42 | 43 | /// 44 | public override string ToString() 45 | { 46 | return _predicate.ToString() ?? ""; 47 | } 48 | 49 | /// 50 | public int Priority { get; } 51 | } 52 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/IPAddressValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Net; 6 | 7 | /// 8 | /// The 9 | /// class is used to generate IP Address values. 10 | /// 11 | public class IPAddressValueGenerator : ValueGeneratorBase 12 | { 13 | /// 14 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 15 | { 16 | var buffer = new byte[4]; 17 | 18 | Generator.NextValue(buffer); 19 | 20 | if (type == typeof(IPAddress)) 21 | { 22 | return new IPAddress(buffer); 23 | } 24 | 25 | const string addressFormat = "{0}.{1}.{2}.{3}"; 26 | 27 | var address = string.Format( 28 | CultureInfo.InvariantCulture, 29 | addressFormat, 30 | buffer[0], 31 | buffer[1], 32 | buffer[2], 33 | buffer[3]); 34 | 35 | return address; 36 | } 37 | 38 | /// 39 | /// The parameter is null. 40 | protected override bool IsMatch(IBuildChain buildChain, Type type, string? referenceName) 41 | { 42 | type = type ?? throw new ArgumentNullException(nameof(type)); 43 | 44 | if (type == typeof(IPAddress)) 45 | { 46 | return true; 47 | } 48 | 49 | if (type != typeof(string)) 50 | { 51 | return false; 52 | } 53 | 54 | if (referenceName == null) 55 | { 56 | return false; 57 | } 58 | 59 | if (referenceName.IndexOf("ipaddress", StringComparison.OrdinalIgnoreCase) > -1) 60 | { 61 | return true; 62 | } 63 | 64 | return false; 65 | } 66 | 67 | /// 68 | public override int Priority { get; } = 1000; 69 | } 70 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/IncrementingEnumerableTypeCreator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Globalization; 5 | using ModelBuilder.TypeCreators; 6 | 7 | public class IncrementingEnumerableTypeCreator : EnumerableTypeCreator 8 | { 9 | protected override bool CanCreate(IBuildConfiguration configuration, 10 | IBuildChain buildChain, Type type, string? referenceName) 11 | { 12 | if (base.CanCreate(configuration, buildChain, type, referenceName) == false) 13 | { 14 | return false; 15 | } 16 | 17 | var baseType = type.GenericTypeArguments[0]; 18 | 19 | if (baseType.IsNullable()) 20 | { 21 | return false; 22 | } 23 | 24 | var generator = new RandomGenerator(); 25 | 26 | return generator.IsSupported(baseType); 27 | } 28 | 29 | protected override bool CanPopulate(IBuildConfiguration configuration, 30 | IBuildChain buildChain, Type type, string? referenceName) 31 | { 32 | if (base.CanPopulate(configuration, buildChain, type, referenceName) == false) 33 | { 34 | return false; 35 | } 36 | 37 | var baseType = type.GenericTypeArguments[0]; 38 | 39 | if (baseType.IsNullable()) 40 | { 41 | return false; 42 | } 43 | 44 | var generator = new RandomGenerator(); 45 | 46 | return generator.IsSupported(baseType); 47 | } 48 | 49 | protected override object? CreateChildItem(Type type, IExecuteStrategy executeStrategy, object? previousItem) 50 | { 51 | if (previousItem == null!) 52 | { 53 | return base.CreateChildItem(type, executeStrategy, null!); 54 | } 55 | 56 | // Use a double as the base type then convert later 57 | var value = Convert.ToDouble(previousItem, CultureInfo.InvariantCulture); 58 | 59 | value++; 60 | 61 | var converted = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); 62 | 63 | return converted; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/NullableTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using FluentAssertions; 4 | using FluentAssertions.Execution; 5 | using ModelBuilder.UnitTests.Models; 6 | using ModelBuilder.ValueGenerators; 7 | using Xunit; 8 | 9 | public class NullableTests 10 | { 11 | [Fact] 12 | public void CanConfigureNullValueGeneration() 13 | { 14 | var config = Model.UsingDefaultConfiguration() 15 | .UpdateValueGenerator(x => x.AllowNull = true); 16 | 17 | for (var index = 0; index < 10000; index++) 18 | { 19 | var actual = config.Create()!; 20 | 21 | if (actual.PropNullableInt == null) 22 | { 23 | // We are happy with this 24 | return; 25 | } 26 | } 27 | 28 | throw new AssertionFailedException("Null value was expected to be generated but none were"); 29 | } 30 | 31 | [Fact] 32 | public void DoesNotGenerateNullNumericValuesByDefault() 33 | { 34 | var config = Model.UsingDefaultConfiguration() 35 | .UpdateValueGenerator(x => x.AllowNull = false); 36 | 37 | for (var index = 0; index < 100; index++) 38 | { 39 | var actual = config.Create()!; 40 | 41 | actual.PropNullableByte.Should().NotBeNull(); 42 | actual.PropNullableDecimal.Should().NotBeNull(); 43 | actual.PropNullableDouble.Should().NotBeNull(); 44 | actual.PropNullableFloat.Should().NotBeNull(); 45 | actual.PropNullableInt.Should().NotBeNull(); 46 | actual.PropNullableLong.Should().NotBeNull(); 47 | actual.PropNullableSbyte.Should().NotBeNull(); 48 | actual.PropNullableShort.Should().NotBeNull(); 49 | actual.PropNullableUint.Should().NotBeNull(); 50 | actual.PropNullableUlong.Should().NotBeNull(); 51 | actual.PropNullableUshort.Should().NotBeNull(); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /ModelBuilder/IExecuteStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// interface defines the dependencies used to create and populate instances. 9 | /// 10 | public interface IExecuteStrategy 11 | { 12 | /// 13 | /// Creates a new instance of the specified type with optional constructor arguments. 14 | /// 15 | /// The type of object to create. 16 | /// The constructor arguments of the type. 17 | /// A new instance of the specified type. 18 | object Create(Type type, params object?[]? args); 19 | 20 | /// 21 | /// Creates a set of parameters for the specified method. 22 | /// 23 | /// The method that defines the parameters. 24 | /// An array of parameter values. 25 | object?[]? CreateParameters(MethodBase method); 26 | 27 | /// 28 | /// Initializes the execute strategy with a build configuration. 29 | /// 30 | /// The build configuration. 31 | void Initialize(IBuildConfiguration configuration); 32 | 33 | /// 34 | /// Populates values onto settable properties of the specified instance. 35 | /// 36 | /// The instance to populate. 37 | /// The populated object. 38 | object Populate(object instance); 39 | 40 | /// 41 | /// Gets the build chain for objects up to the current build execution. 42 | /// 43 | IBuildChain BuildChain { get; } 44 | 45 | /// 46 | /// Gets the build configuration. 47 | /// 48 | IBuildConfiguration Configuration { get; } 49 | 50 | /// 51 | /// Gets the build log for items created by this strategy. 52 | /// 53 | IBuildLog Log { get; } 54 | } 55 | } -------------------------------------------------------------------------------- /ModelBuilder/IgnoreRules/RegexIgnoreRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.IgnoreRules 2 | { 3 | using System; 4 | using System.Reflection; 5 | using System.Text.RegularExpressions; 6 | 7 | /// 8 | /// The 9 | /// class is used to match a property on a type using a 10 | /// on the property name for whether the property should be ignored by 11 | /// and not be populated. 12 | /// 13 | public class RegexIgnoreRule : IIgnoreRule 14 | { 15 | private readonly Regex _expression; 16 | 17 | /// 18 | /// Creates a new instance of the class. 19 | /// 20 | /// The expression used to match on property name. 21 | /// The parameter is null. 22 | public RegexIgnoreRule(string expression) 23 | { 24 | if (string.IsNullOrEmpty(expression)) 25 | { 26 | throw new ArgumentNullException(nameof(expression)); 27 | } 28 | 29 | _expression = new Regex(expression); 30 | } 31 | 32 | /// 33 | /// Creates a new instance of the class. 34 | /// 35 | /// The expression used to match on property name. 36 | /// The parameter is null. 37 | public RegexIgnoreRule(Regex expression) 38 | { 39 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 40 | } 41 | 42 | /// 43 | /// The parameter is null. 44 | public bool IsMatch(PropertyInfo propertyInfo) 45 | { 46 | propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); 47 | 48 | return _expression.IsMatch(propertyInfo.Name); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /ModelBuilder/IBuildProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using System.Reflection; 5 | using ModelBuilder.BuildActions; 6 | 7 | /// 8 | /// The 9 | /// interface defines the members for building a new value. 10 | /// 11 | public interface IBuildProcessor 12 | { 13 | /// 14 | /// Gets the build capability for the specified type. 15 | /// 16 | /// The execute strategy. 17 | /// The build capability. 18 | /// The type to evaluate. 19 | /// A indicating instance creation support via a . 20 | IBuildCapability GetBuildCapability(IExecuteStrategy executeStrategy, 21 | BuildRequirement buildRequirement, Type type); 22 | 23 | /// 24 | /// Gets the build capability for the specified parameter. 25 | /// 26 | /// The execute strategy. 27 | /// The build capability. 28 | /// The parameter to evaluate. 29 | /// A indicating instance creation support via a . 30 | IBuildCapability GetBuildCapability(IExecuteStrategy executeStrategy, 31 | BuildRequirement buildRequirement, 32 | ParameterInfo parameterInfo); 33 | 34 | /// 35 | /// Gets the build capability for the specified property. 36 | /// 37 | /// The execute strategy. 38 | /// The build capability. 39 | /// The property to evaluate. 40 | /// A indicating instance creation support via a . 41 | IBuildCapability GetBuildCapability(IExecuteStrategy executeStrategy, 42 | BuildRequirement buildRequirement, 43 | PropertyInfo propertyInfo); 44 | } 45 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/TypeMappingRuleTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.IO; 5 | using FluentAssertions; 6 | using ModelBuilder.UnitTests.Models; 7 | using Xunit; 8 | 9 | public class TypeMappingRuleTests 10 | { 11 | [Fact] 12 | public void ReturnsConstructorParametersAsProperties() 13 | { 14 | var sourceType = typeof(Stream); 15 | var targetType = typeof(MemoryStream); 16 | 17 | var sut = new TypeMappingRule(sourceType, targetType); 18 | 19 | sut.SourceType.Should().Be(sourceType); 20 | sut.TargetType.Should().Be(targetType); 21 | } 22 | 23 | [Fact] 24 | public void ThrowsExceptionWhenCreatedWithNullSourceType() 25 | { 26 | var targetType = typeof(MemoryStream); 27 | 28 | // ReSharper disable once ObjectCreationAsStatement 29 | Action action = () => new TypeMappingRule(null!, targetType); 30 | 31 | action.Should().Throw(); 32 | } 33 | 34 | [Fact] 35 | public void ThrowsExceptionWhenCreatedWithNullTargetType() 36 | { 37 | var sourceType = typeof(Stream); 38 | 39 | // ReSharper disable once ObjectCreationAsStatement 40 | Action action = () => new TypeMappingRule(sourceType, null!); 41 | 42 | action.Should().Throw(); 43 | } 44 | 45 | [Fact] 46 | public void ThrowsExceptionWhenOrderOfTargetTypeAndSourceTypeAreBackward() 47 | { 48 | var sourceType = typeof(MemoryStream); 49 | var targetType = typeof(Stream); 50 | 51 | // ReSharper disable once ObjectCreationAsStatement 52 | Action action = () => new TypeMappingRule(sourceType, targetType); 53 | 54 | action.Should().Throw(); 55 | } 56 | 57 | [Fact] 58 | public void ThrowsExceptionWhenTargetTypeNotAssignableToSourceType() 59 | { 60 | var sourceType = typeof(Stream); 61 | var targetType = typeof(Person); 62 | 63 | // ReSharper disable once ObjectCreationAsStatement 64 | Action action = () => new TypeMappingRule(sourceType, targetType); 65 | 66 | action.Should().Throw(); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /ModelBuilder/ExecuteOrderRules/RegexExecuteOrderRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ExecuteOrderRules 2 | { 3 | using System; 4 | using System.Reflection; 5 | using System.Text.RegularExpressions; 6 | 7 | /// 8 | /// The 9 | /// class is used to match a property on a type using a 10 | /// on the property name to determine the priority order in populating the property by . 11 | /// 12 | public class RegexExecuteOrderRule : IExecuteOrderRule 13 | { 14 | private readonly Regex _expression; 15 | 16 | /// 17 | /// Creates a new instance of the class. 18 | /// 19 | /// The expression used to match on property name. 20 | /// The execution order priority to apply to the property. 21 | /// The parameter is null. 22 | public RegexExecuteOrderRule(Regex expression, int priority) 23 | { 24 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 25 | Priority = priority; 26 | } 27 | 28 | /// 29 | /// The is null. 30 | public bool IsMatch(ParameterInfo parameterInfo) 31 | { 32 | parameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo)); 33 | 34 | return _expression.IsMatch(parameterInfo.Name!); 35 | } 36 | 37 | /// 38 | /// The is null. 39 | public bool IsMatch(PropertyInfo propertyInfo) 40 | { 41 | propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); 42 | 43 | return _expression.IsMatch(propertyInfo.Name); 44 | } 45 | 46 | /// 47 | public override string ToString() 48 | { 49 | return _expression.ToString(); 50 | } 51 | 52 | /// 53 | public int Priority { get; } 54 | } 55 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/DateTimeValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate random date time values. 8 | /// 9 | public class DateTimeValueGenerator : ValueGeneratorMatcher, INullableBuilder 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public DateTimeValueGenerator() : base( 15 | typeof(DateTime), 16 | typeof(DateTime?), 17 | typeof(DateTimeOffset), 18 | typeof(DateTimeOffset?), 19 | typeof(TimeSpan), 20 | typeof(TimeSpan?)) 21 | { 22 | } 23 | 24 | /// 25 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 26 | { 27 | var generateType = type; 28 | 29 | if (generateType.IsNullable()) 30 | { 31 | if (AllowNull) 32 | { 33 | // Allow for a % the chance that this might be null 34 | var range = Generator.NextValue(0, 100000); 35 | 36 | if (range < NullPercentageChance * 1000) 37 | { 38 | return null; 39 | } 40 | } 41 | 42 | // Hijack the type to generator so we can continue with the normal code pointed at the correct type to generate 43 | generateType = type.GetGenericArguments()[0]; 44 | } 45 | 46 | var tenYears = TimeSpan.FromDays(3650); 47 | var shift = Generator.NextValue(0, tenYears.TotalSeconds); 48 | 49 | if (generateType == typeof(DateTime)) 50 | { 51 | return DateTime.UtcNow.AddSeconds(shift); 52 | } 53 | 54 | if (generateType == typeof(TimeSpan)) 55 | { 56 | return TimeSpan.FromSeconds(shift); 57 | } 58 | 59 | return DateTimeOffset.UtcNow.AddSeconds(shift); 60 | } 61 | 62 | /// 63 | public bool AllowNull { get; set; } = false; 64 | 65 | /// 66 | public int NullPercentageChance { get; set; } = 10; 67 | } 68 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Scenarios/RelativeDataGenerationTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Scenarios 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | using FluentAssertions; 5 | using ModelBuilder.Data; 6 | using ModelBuilder.UnitTests.Models; 7 | using ModelBuilder.ValueGenerators; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | public class RelativeDataGenerationTests 12 | { 13 | private readonly ITestOutputHelper _output; 14 | 15 | public RelativeDataGenerationTests(ITestOutputHelper output) 16 | { 17 | _output = output; 18 | } 19 | 20 | [Fact] 21 | [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", 22 | Justification = "Emails are lower-case by convention")] 23 | public void AssignsPropertyValuesRelativeToOtherPropertyValues() 24 | { 25 | var actual = Model.WriteLog(_output.WriteLine).Create(); 26 | 27 | var firstName = EmailValueGenerator.SpecialCharacters.Replace(actual.FirstName!, string.Empty); 28 | var lastName = EmailValueGenerator.SpecialCharacters.Replace(actual.LastName!, string.Empty); 29 | 30 | var expected = (firstName + "." + lastName + "@").ToLowerInvariant(); 31 | 32 | actual.PersonalEmail.Should() 33 | .StartWith(expected); 34 | } 35 | 36 | [Fact] 37 | [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", 38 | Justification = "Emails are lower-case by convention")] 39 | public void CreatesParameterValuesRelativeToOtherParameterValues() 40 | { 41 | var actual = Model.WriteLog(_output.WriteLine).Create(); 42 | 43 | if (actual.Gender == Gender.Female) 44 | { 45 | TestData.FemaleNames.Should().Contain(actual.FirstName); 46 | } 47 | else if (actual.Gender == Gender.Male) 48 | { 49 | TestData.MaleNames.Should().Contain(actual.FirstName); 50 | } 51 | 52 | var firstName = EmailValueGenerator.SpecialCharacters.Replace(actual.FirstName, string.Empty); 53 | var lastName = EmailValueGenerator.SpecialCharacters.Replace(actual.LastName, string.Empty); 54 | 55 | var expectedEmail = (firstName + "." + lastName + "@" + actual.Domain).ToLowerInvariant(); 56 | 57 | actual.Email.Should().StartWith(expectedEmail); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /ModelBuilder/TypeCreators/StructTypeCreator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.TypeCreators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to create an instance of a struct. 8 | /// 9 | public class StructTypeCreator : TypeCreatorBase 10 | { 11 | /// 12 | protected override bool CanCreate(IBuildConfiguration configuration, IBuildChain buildChain, Type type, 13 | string? referenceName) 14 | { 15 | type = type ?? throw new ArgumentNullException(nameof(type)); 16 | 17 | if (type.IsValueType == false) 18 | { 19 | return false; 20 | } 21 | 22 | if (type.IsEnum) 23 | { 24 | return false; 25 | } 26 | 27 | return true; 28 | } 29 | 30 | /// 31 | protected override object? CreateInstance(IExecuteStrategy executeStrategy, Type type, string? referenceName, 32 | params object?[]? args) 33 | { 34 | if (args?.Length > 0) 35 | { 36 | // We have arguments supplied so we will assume that they may the resolve type 37 | return Activator.CreateInstance(type, args); 38 | } 39 | 40 | // Use constructor detection to figure out how to create this instance 41 | var constructorResolver = executeStrategy.Configuration.ConstructorResolver; 42 | 43 | // We aren't provided with arguments so we need to resolve the most appropriate constructor 44 | var constructor = constructorResolver.Resolve(type); 45 | 46 | if (constructor == null) 47 | { 48 | // Structs return null for a default constructor 49 | return Activator.CreateInstance(type); 50 | } 51 | 52 | // Create the arguments for the constructor we have found 53 | var builtArgs = executeStrategy.CreateParameters(constructor); 54 | 55 | return constructor.Invoke(builtArgs); 56 | } 57 | 58 | /// 59 | protected override object PopulateInstance(IExecuteStrategy executeStrategy, object instance) 60 | { 61 | // There is no out of the box population and this is left up to the execution strategy 62 | return instance; 63 | } 64 | 65 | /// 66 | public override int Priority => 1000; 67 | } 68 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/BuildConfigurationExtensionsTests.WriteLog.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using FluentAssertions; 5 | using ModelBuilder.UnitTests.Models; 6 | using NSubstitute; 7 | using Xunit; 8 | 9 | public partial class BuildConfigurationExtensionsTests 10 | { 11 | [Fact] 12 | public void WriteLogForExecuteStrategyReturnsExecuteStrategyWithLogging() 13 | { 14 | var configuration = Substitute.For(); 15 | 16 | var actual = configuration.WriteLog(_output.WriteLine); 17 | 18 | actual.Should().BeAssignableTo(); 19 | actual.Should().NotBeOfType(); 20 | } 21 | 22 | [Fact] 23 | public void WriteLogForExecuteStrategyThrowsExceptionWithNullAction() 24 | { 25 | var configuration = Substitute.For(); 26 | 27 | Action action = () => configuration.WriteLog(null!); 28 | 29 | action.Should().Throw(); 30 | } 31 | 32 | [Fact] 33 | public void WriteLogForExecuteStrategyThrowsExceptionWithNullConfiguration() 34 | { 35 | Action action = () => BuildConfigurationExtensions.WriteLog(null!, _output.WriteLine); 36 | 37 | action.Should().Throw(); 38 | } 39 | 40 | [Fact] 41 | public void WriteLogForExecuteStrategyTReturnsExecuteStrategyWithLogging() 42 | { 43 | var configuration = Substitute.For(); 44 | 45 | var actual = configuration.WriteLog(_output.WriteLine); 46 | 47 | actual.Should().BeAssignableTo>(); 48 | actual.Should().NotBeOfType>(); 49 | } 50 | 51 | [Fact] 52 | public void WriteLogForExecuteStrategyTThrowsExceptionWithNullAction() 53 | { 54 | var configuration = Substitute.For(); 55 | 56 | Action action = () => configuration.WriteLog(null!); 57 | 58 | action.Should().Throw(); 59 | } 60 | 61 | [Fact] 62 | public void WriteLogForExecuteStrategyTThrowsExceptionWithNullConfiguration() 63 | { 64 | Action action = () => BuildConfigurationExtensions.WriteLog(null!, _output.WriteLine); 65 | 66 | action.Should().Throw(); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ValueGenerators/TimeZoneInfoValueGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.ValueGenerators 2 | { 3 | using System; 4 | using System.IO; 5 | using FluentAssertions; 6 | using ModelBuilder.ValueGenerators; 7 | using NSubstitute; 8 | using Xunit; 9 | 10 | public class TimeZoneInfoValueGeneratorTests 11 | { 12 | [Fact] 13 | public void GenerateReturnsRandomTimeZoneInfoValue() 14 | { 15 | var buildChain = new BuildHistory(); 16 | var executeStrategy = Substitute.For(); 17 | 18 | executeStrategy.BuildChain.Returns(buildChain); 19 | 20 | var sut = new Wrapper(); 21 | 22 | var first = (TimeZoneInfo)sut.RunGenerate(typeof(TimeZoneInfo), null!, executeStrategy); 23 | 24 | var second = first; 25 | 26 | for (var index = 0; index < 1000; index++) 27 | { 28 | second = (TimeZoneInfo)sut.RunGenerate(typeof(TimeZoneInfo), null!, executeStrategy); 29 | 30 | if (first.Equals(second) == false) 31 | { 32 | break; 33 | } 34 | } 35 | 36 | first.Should().NotBe(second); 37 | } 38 | 39 | [Theory] 40 | [InlineData(typeof(string), false)] 41 | [InlineData(typeof(Stream), false)] 42 | [InlineData(typeof(TimeSpan), false)] 43 | [InlineData(typeof(TimeSpan?), false)] 44 | [InlineData(typeof(DateTimeOffset), false)] 45 | [InlineData(typeof(DateTimeOffset?), false)] 46 | [InlineData(typeof(DateTime), false)] 47 | [InlineData(typeof(DateTime?), false)] 48 | [InlineData(typeof(TimeZoneInfo), true)] 49 | public void IsMatchTest(Type type, bool expected) 50 | { 51 | var buildChain = Substitute.For(); 52 | 53 | var sut = new Wrapper(); 54 | 55 | var actual = sut.RunIsMatch(type, null!, buildChain); 56 | 57 | actual.Should().Be(expected); 58 | } 59 | 60 | private class Wrapper : TimeZoneInfoValueGenerator 61 | { 62 | public object RunGenerate(Type type, string referenceName, IExecuteStrategy executeStrategy) 63 | { 64 | return Generate(executeStrategy, type, referenceName)!; 65 | } 66 | 67 | public bool RunIsMatch(Type type, string referenceName, IBuildChain buildChain) 68 | { 69 | return IsMatch(buildChain, type, referenceName); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/BigEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | [SuppressMessage( 6 | "Microsoft.Design", 7 | "CA1028", 8 | Justification = "This base type is used specifically for testing scenarios for that data type.")] 9 | public enum BigEnum : ulong 10 | { 11 | Flag1 = 0x00000001, 12 | Flag2 = 0x00000002, 13 | Flag3 = 0x00000004, 14 | Flag4 = 0x00000008, 15 | Flag5 = 0x00000010, 16 | Flag6 = 0x00000020, 17 | Flag7 = 0x00000040, 18 | Flag8 = 0x00000080, 19 | Flag9 = 0x00000100, 20 | Flag10 = 0x00000200, 21 | Flag11 = 0x00000400, 22 | Flag12 = 0x00000800, 23 | Flag13 = 0x00001000, 24 | Flag14 = 0x00002000, 25 | Flag15 = 0x00004000, 26 | Flag16 = 0x00008000, 27 | Flag17 = 0x00010000, 28 | Flag18 = 0x00020000, 29 | Flag19 = 0x00040000, 30 | Flag20 = 0x00080000, 31 | Flag21 = 0x00100000, 32 | Flag22 = 0x00200000, 33 | Flag23 = 0x00400000, 34 | Flag24 = 0x00800000, 35 | Flag25 = 0x01000000, 36 | Flag26 = 0x02000000, 37 | Flag27 = 0x04000000, 38 | Flag28 = 0x08000000, 39 | Flag29 = 0x10000000, 40 | Flag30 = 0x20000000, 41 | Flag31 = 0x40000000, 42 | Flag32 = 0x80000000, 43 | Flag33 = 0x100000000, 44 | Flag34 = 0x200000000, 45 | Flag35 = 0x400000000, 46 | Flag36 = 0x800000000, 47 | Flag37 = 0x1000000000, 48 | Flag38 = 0x2000000000, 49 | Flag39 = 0x4000000000, 50 | Flag40 = 0x8000000000, 51 | Flag41 = 0x10000000000, 52 | Flag42 = 0x20000000000, 53 | Flag43 = 0x40000000000, 54 | Flag44 = 0x80000000000, 55 | Flag45 = 0x100000000000, 56 | Flag46 = 0x200000000000, 57 | Flag47 = 0x400000000000, 58 | Flag48 = 0x800000000000, 59 | Flag49 = 0x1000000000000, 60 | Flag50 = 0x2000000000000, 61 | Flag51 = 0x4000000000000, 62 | Flag52 = 0x8000000000000, 63 | Flag53 = 0x10000000000000, 64 | Flag54 = 0x20000000000000, 65 | Flag55 = 0x40000000000000, 66 | Flag56 = 0x80000000000000, 67 | Flag57 = 0x100000000000000, 68 | Flag58 = 0x200000000000000, 69 | Flag59 = 0x400000000000000, 70 | Flag60 = 0x800000000000000, 71 | Flag61 = 0x1000000000000000, 72 | Flag62 = 0x2000000000000000, 73 | Flag63 = 0x4000000000000000, 74 | Flag64 = 0x8000000000000000 75 | } 76 | } -------------------------------------------------------------------------------- /ModelBuilder/Data/Location.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.Data 2 | { 3 | using System; 4 | using ModelBuilder.Properties; 5 | 6 | /// 7 | /// The 8 | /// class defines location information. 9 | /// 10 | public class Location 11 | { 12 | /// 13 | /// Parses a new location from the specified CSV data. 14 | /// 15 | /// The CSV data. 16 | /// The location. 17 | public static Location Parse(string csvData) 18 | { 19 | if (string.IsNullOrWhiteSpace(csvData)) 20 | { 21 | throw new ArgumentException(Resources.ArgumentException_NullOrWhiteSpace, nameof(csvData)); 22 | } 23 | 24 | // This data is expected to be in the following CSV format 25 | // Country,State,City,PostCode,StreetName,StreetSuffix,Phone 26 | var parts = csvData.Split(','); 27 | 28 | var location = new Location 29 | { 30 | Country = parts[0], 31 | State = parts[1], 32 | City = parts[2], 33 | PostCode = parts[3], 34 | StreetName = parts[4], 35 | StreetSuffix = parts[5], 36 | Phone = parts[6] 37 | }; 38 | 39 | return location; 40 | } 41 | 42 | /// 43 | /// Gets or sets the city. 44 | /// 45 | public string City { get; set; } = string.Empty; 46 | 47 | /// 48 | /// Gets or sets the country. 49 | /// 50 | public string Country { get; set; } = string.Empty; 51 | 52 | /// 53 | /// Gets or sets the phone. 54 | /// 55 | public string Phone { get; set; } = string.Empty; 56 | 57 | /// 58 | /// Gets or sets the post code. 59 | /// 60 | public string PostCode { get; set; } = string.Empty; 61 | 62 | /// 63 | /// Gets or sets the state. 64 | /// 65 | public string State { get; set; } = string.Empty; 66 | 67 | /// 68 | /// Gets or sets the street name. 69 | /// 70 | public string StreetName { get; set; } = string.Empty; 71 | 72 | /// 73 | /// Gets or sets the street suffix. 74 | /// 75 | public string StreetSuffix { get; set; } = string.Empty; 76 | } 77 | } -------------------------------------------------------------------------------- /ModelBuilder/BuildConfigurationExtensions.WriteLog.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class provides extension methods for the interface. 8 | /// 9 | public static partial class BuildConfigurationExtensions 10 | { 11 | /// 12 | /// Writes the log entry using the specified action after the execute strategy is invoked. 13 | /// 14 | /// The type of instance to create and populate. 15 | /// The configuration. 16 | /// The logging action to call. 17 | /// The execute strategy to invoke. 18 | /// The parameter is null. 19 | /// The parameter is null. 20 | public static IExecuteStrategy WriteLog(this IBuildConfiguration configuration, Action action) 21 | where T : notnull 22 | { 23 | configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 24 | 25 | action = action ?? throw new ArgumentNullException(nameof(action)); 26 | 27 | return configuration.UsingExecuteStrategy>().WriteLog(action); 28 | } 29 | 30 | /// 31 | /// Writes the log entry using the specified action after the execute strategy is invoked. 32 | /// 33 | /// The configuration. 34 | /// The logging action to call. 35 | /// The execute strategy to invoke. 36 | /// The parameter is null. 37 | /// The parameter is null. 38 | public static IExecuteStrategy WriteLog(this IBuildConfiguration configuration, Action action) 39 | { 40 | configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 41 | 42 | action = action ?? throw new ArgumentNullException(nameof(action)); 43 | 44 | return configuration.UsingExecuteStrategy().WriteLog(action); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/Models/BigValues.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.Models 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | [Flags] 7 | [SuppressMessage( 8 | "Microsoft.Design", 9 | "CA1028", 10 | Justification = "This base type is used specifically for testing scenarios for that data type.")] 11 | public enum BigValues : ulong 12 | { 13 | Flag1 = 0x00000001, 14 | Flag2 = 0x00000002, 15 | Flag3 = 0x00000004, 16 | Flag4 = 0x00000008, 17 | Flag5 = 0x00000010, 18 | Flag6 = 0x00000020, 19 | Flag7 = 0x00000040, 20 | Flag8 = 0x00000080, 21 | Flag9 = 0x00000100, 22 | Flag10 = 0x00000200, 23 | Flag11 = 0x00000400, 24 | Flag12 = 0x00000800, 25 | Flag13 = 0x00001000, 26 | Flag14 = 0x00002000, 27 | Flag15 = 0x00004000, 28 | Flag16 = 0x00008000, 29 | Flag17 = 0x00010000, 30 | Flag18 = 0x00020000, 31 | Flag19 = 0x00040000, 32 | Flag20 = 0x00080000, 33 | Flag21 = 0x00100000, 34 | Flag22 = 0x00200000, 35 | Flag23 = 0x00400000, 36 | Flag24 = 0x00800000, 37 | Flag25 = 0x01000000, 38 | Flag26 = 0x02000000, 39 | Flag27 = 0x04000000, 40 | Flag28 = 0x08000000, 41 | Flag29 = 0x10000000, 42 | Flag30 = 0x20000000, 43 | Flag31 = 0x40000000, 44 | Flag32 = 0x80000000, 45 | Flag33 = 0x100000000, 46 | Flag34 = 0x200000000, 47 | Flag35 = 0x400000000, 48 | Flag36 = 0x800000000, 49 | Flag37 = 0x1000000000, 50 | Flag38 = 0x2000000000, 51 | Flag39 = 0x4000000000, 52 | Flag40 = 0x8000000000, 53 | Flag41 = 0x10000000000, 54 | Flag42 = 0x20000000000, 55 | Flag43 = 0x40000000000, 56 | Flag44 = 0x80000000000, 57 | Flag45 = 0x100000000000, 58 | Flag46 = 0x200000000000, 59 | Flag47 = 0x400000000000, 60 | Flag48 = 0x800000000000, 61 | Flag49 = 0x1000000000000, 62 | Flag50 = 0x2000000000000, 63 | Flag51 = 0x4000000000000, 64 | Flag52 = 0x8000000000000, 65 | Flag53 = 0x10000000000000, 66 | Flag54 = 0x20000000000000, 67 | Flag55 = 0x40000000000000, 68 | Flag56 = 0x80000000000000, 69 | Flag57 = 0x100000000000000, 70 | Flag58 = 0x200000000000000, 71 | Flag59 = 0x400000000000000, 72 | Flag60 = 0x800000000000000, 73 | Flag61 = 0x1000000000000000, 74 | Flag62 = 0x2000000000000000, 75 | Flag63 = 0x4000000000000000, 76 | Flag64 = 0x8000000000000000 77 | } 78 | } -------------------------------------------------------------------------------- /ModelBuilder/DefaultExecuteStrategyT.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System; 4 | using ModelBuilder.TypeCreators; 5 | using ModelBuilder.ValueGenerators; 6 | 7 | /// 8 | /// The 9 | /// class is used to create and populate instances. 10 | /// 11 | /// The type of instance to create and populate. 12 | public class DefaultExecuteStrategy : DefaultExecuteStrategy, IExecuteStrategy where T : notnull 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public DefaultExecuteStrategy() 18 | { 19 | } 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The build history tracker. 25 | /// The build log. 26 | /// The build processor. 27 | public DefaultExecuteStrategy(IBuildHistory buildHistory, IBuildLog buildLog, IBuildProcessor buildProcessor) : 28 | base(buildHistory, buildLog, buildProcessor) 29 | { 30 | } 31 | 32 | /// 33 | /// 34 | /// No or was found to 35 | /// generate a requested type. 36 | /// 37 | /// Failed to generate a requested type. 38 | public virtual T Create(params object?[]? args) 39 | { 40 | var requestedType = typeof(T); 41 | 42 | var instance = Build(requestedType, args); 43 | 44 | return (T)instance; 45 | } 46 | 47 | /// 48 | /// The parameter is null. 49 | /// 50 | /// No or was found to 51 | /// generate a requested type. 52 | /// 53 | /// Failed to generate a requested type. 54 | public virtual T Populate(T instance) 55 | { 56 | instance = instance ?? throw new ArgumentNullException(nameof(instance)); 57 | 58 | return (T)Populate((object)instance); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/IgnoreRules/RegexIgnoreRuleTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.IgnoreRules 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | using FluentAssertions; 6 | using ModelBuilder.IgnoreRules; 7 | using ModelBuilder.UnitTests.Models; 8 | using Xunit; 9 | 10 | public class RegexIgnoreRuleTests 11 | { 12 | [Fact] 13 | public void IsMatchReturnsFalseWhenPropertyDoesNotMatch() 14 | { 15 | var property = typeof(Person).GetProperty(nameof(Person.FirstName))!; 16 | 17 | var sut = new RegexIgnoreRule(NameExpression.LastName); 18 | 19 | var actual = sut.IsMatch(property); 20 | 21 | actual.Should().BeFalse(); 22 | } 23 | 24 | [Fact] 25 | public void IsMatchReturnsTrueWhenPropertyMatches() 26 | { 27 | var property = typeof(Person).GetProperty(nameof(Person.FirstName))!; 28 | 29 | var sut = new RegexIgnoreRule(NameExpression.FirstName); 30 | 31 | var actual = sut.IsMatch(property); 32 | 33 | actual.Should().BeTrue(); 34 | } 35 | 36 | [Theory] 37 | [InlineData("First", true)] 38 | [InlineData("Last", false)] 39 | public void IsMatchReturnsWhenPropertyMatchesExpression(string expression, bool expected) 40 | { 41 | var property = typeof(Person).GetProperty(nameof(Person.FirstName))!; 42 | 43 | var sut = new RegexIgnoreRule(expression); 44 | 45 | var actual = sut.IsMatch(property); 46 | 47 | actual.Should().Be(expected); 48 | } 49 | 50 | [Fact] 51 | public void IsMatchThrowsExceptionWithNullProperty() 52 | { 53 | var sut = new RegexIgnoreRule(NameExpression.FirstName); 54 | 55 | Action action = () => sut.IsMatch(null!); 56 | 57 | action.Should().Throw(); 58 | } 59 | 60 | [Theory] 61 | [InlineData(null)] 62 | [InlineData("")] 63 | public void ThrowsExceptionWhenCreatedWithInvalidExpression(string? expression) 64 | { 65 | // ReSharper disable once ObjectCreationAsStatement 66 | Action action = () => new RegexIgnoreRule(expression!); 67 | 68 | action.Should().Throw(); 69 | } 70 | 71 | [Fact] 72 | public void ThrowsExceptionWhenCreatedWithNullExpression() 73 | { 74 | // ReSharper disable once ObjectCreationAsStatement 75 | Action action = () => new RegexIgnoreRule((Regex)null!); 76 | 77 | action.Should().Throw(); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/AddressValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Text.RegularExpressions; 6 | using ModelBuilder.Data; 7 | 8 | /// 9 | /// The 10 | /// class is used to generate postal addressing values. 11 | /// 12 | public class AddressValueGenerator : RegexTypeNameValueGenerator 13 | { 14 | private static readonly Regex _matchNameExpression = 15 | new Regex("(?\\d+)", 19 | RegexOptions.IgnoreCase); 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public AddressValueGenerator() : base(_matchNameExpression, typeof(string)) 25 | { 26 | } 27 | 28 | /// 29 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 30 | { 31 | if (referenceName != null) 32 | { 33 | var multipleMatch = _multipleAddressExpression.Match(referenceName); 34 | 35 | if (multipleMatch.Success) 36 | { 37 | // Get the number from the match 38 | var number = int.Parse(multipleMatch.Groups["Number"].Value, CultureInfo.InvariantCulture); 39 | 40 | if (number == 1) 41 | { 42 | var floor = Generator.NextValue(1, 15); 43 | var unitIndex = Generator.NextValue(0, 15); 44 | var unit = (char)(65 + unitIndex); 45 | 46 | // Return a Unit Xy, Floor X style value 47 | return "Unit " + floor + unit + ", Floor " + floor; 48 | } 49 | 50 | if (number > 2) 51 | { 52 | // This generator will only populate the first two address lines 53 | return string.Empty; 54 | } 55 | } 56 | } 57 | 58 | var addressNumber = Generator.NextValue(1, 1500); 59 | var location = TestData.Locations.Next(); 60 | 61 | return addressNumber + " " + location.StreetName + " " + location.StreetSuffix; 62 | } 63 | 64 | /// 65 | public override int Priority { get; } = 900; 66 | } 67 | } -------------------------------------------------------------------------------- /ModelBuilder/ExecuteOrderRules/ExpressionExecuteOrderRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ExecuteOrderRules 2 | { 3 | using System; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | /// 8 | /// The 9 | /// class is used to identify a property to determine the priority order in populating the property by 10 | /// . 11 | /// 12 | /// The type being evaluated. 13 | public class ExpressionExecuteOrderRule : IExecuteOrderRule 14 | { 15 | private readonly Expression> _expression; 16 | 17 | /// 18 | /// Creates a new instance of the class. 19 | /// 20 | /// The expression used to identify a property on a type. 21 | /// The execution order priority to apply to the property. 22 | /// The parameter is null. 23 | public ExpressionExecuteOrderRule(Expression> expression, int priority) 24 | { 25 | _expression = expression ?? throw new ArgumentNullException(nameof(expression)); 26 | Priority = priority; 27 | } 28 | 29 | /// 30 | /// The parameter is null. 31 | public bool IsMatch(ParameterInfo parameterInfo) 32 | { 33 | return false; 34 | } 35 | 36 | /// 37 | /// The parameter is null. 38 | public bool IsMatch(PropertyInfo propertyInfo) 39 | { 40 | propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); 41 | 42 | var expressionProperty = _expression.GetProperty(); 43 | 44 | if (propertyInfo.Name != expressionProperty.Name) 45 | { 46 | return false; 47 | } 48 | 49 | if (propertyInfo.DeclaringType != expressionProperty.DeclaringType) 50 | { 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | 57 | /// 58 | public override string ToString() 59 | { 60 | return _expression.ToString(); 61 | } 62 | 63 | /// 64 | public int Priority { get; } 65 | } 66 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/CityValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using ModelBuilder.Data; 8 | 9 | /// 10 | /// The 11 | /// class is used to generate random city values. 12 | /// 13 | public class CityValueGenerator : RelativeValueGenerator 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public CityValueGenerator() : base(NameExpression.City, typeof(string)) 19 | { 20 | } 21 | 22 | /// 23 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 24 | { 25 | var context = executeStrategy?.BuildChain?.Last; 26 | IEnumerable locations = TestData.Locations; 27 | 28 | if (context != null) 29 | { 30 | locations = FilterLocations( 31 | locations, 32 | NameExpression.Country, 33 | (item, value) => item.Country.Equals(value, StringComparison.OrdinalIgnoreCase), 34 | context); 35 | 36 | locations = FilterLocations( 37 | locations, 38 | NameExpression.State, 39 | (item, value) => item.State.Equals(value, StringComparison.OrdinalIgnoreCase), 40 | context); 41 | } 42 | 43 | var availableLocations = locations.ToList(); 44 | 45 | if (availableLocations.Count > 0) 46 | { 47 | var matchingLocation = availableLocations.Next(); 48 | 49 | return matchingLocation.City; 50 | } 51 | 52 | // There was either no country or no match on the country 53 | var location = TestData.Locations.Next(); 54 | 55 | return location.City; 56 | } 57 | 58 | private IEnumerable FilterLocations( 59 | IEnumerable locations, 60 | Regex getExpression, 61 | Func evaluator, 62 | object context) 63 | { 64 | var matchValue = GetValue(getExpression, context); 65 | 66 | if (string.IsNullOrWhiteSpace(matchValue)) 67 | { 68 | return locations; 69 | } 70 | 71 | return locations.Where(x => evaluator(x, matchValue)); 72 | } 73 | 74 | /// 75 | public override int Priority { get; } = 1000; 76 | } 77 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/DateOfBirthValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | 5 | /// 6 | /// The 7 | /// class is used to generate random date of birth values. 8 | /// 9 | public class DateOfBirthValueGenerator : ValueGeneratorMatcher, INullableBuilder 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public DateOfBirthValueGenerator() : base( 15 | NameExpression.DateOfBirth, 16 | typeof(DateTime), 17 | typeof(DateTime?), 18 | typeof(DateTimeOffset), 19 | typeof(DateTimeOffset?)) 20 | { 21 | } 22 | 23 | /// 24 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 25 | { 26 | var generateType = type; 27 | 28 | if (generateType.IsNullable()) 29 | { 30 | if (AllowNull) 31 | { 32 | // Allow for a % the chance that this might be null 33 | var range = Generator.NextValue(0, 100000); 34 | 35 | if (range < NullPercentageChance * 1000) 36 | { 37 | return null; 38 | } 39 | } 40 | 41 | // Hijack the type to generator so we can continue with the normal code pointed at the correct type to generate 42 | generateType = type.GetGenericArguments()[0]; 43 | } 44 | 45 | var years = Generator.NextValue(0, 98); 46 | var months = Generator.NextValue(0, 12); 47 | var days = Generator.NextValue(0, 31); 48 | var hours = Generator.NextValue(0, 24); 49 | var minutes = Generator.NextValue(0, 60); 50 | 51 | if (generateType == typeof(DateTime)) 52 | { 53 | var point = DateTime.UtcNow; 54 | 55 | point = point.AddYears(-years).AddMonths(-months).AddDays(-days).AddHours(-hours).AddMinutes(-minutes); 56 | 57 | return point; 58 | } 59 | 60 | var offsetPoint = DateTimeOffset.UtcNow; 61 | 62 | offsetPoint = offsetPoint.AddYears(-years).AddMonths(-months).AddDays(-days).AddHours(-hours) 63 | .AddMinutes(-minutes); 64 | 65 | return offsetPoint; 66 | } 67 | 68 | /// 69 | public bool AllowNull { get; set; } = false; 70 | 71 | /// 72 | public int NullPercentageChance { get; set; } = 10; 73 | 74 | /// 75 | public override int Priority { get; } = 1000; 76 | } 77 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ValueGenerators/StringValueGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.ValueGenerators 2 | { 3 | using System; 4 | using FluentAssertions; 5 | using ModelBuilder.ValueGenerators; 6 | using NSubstitute; 7 | using Xunit; 8 | 9 | public class StringValueGeneratorTests 10 | { 11 | [Fact] 12 | public void GenerateReturnsRandomValue() 13 | { 14 | var buildChain = new BuildHistory(); 15 | var executeStrategy = Substitute.For(); 16 | 17 | executeStrategy.BuildChain.Returns(buildChain); 18 | 19 | var sut = new Wrapper(); 20 | 21 | var first = (string)sut.RunGenerate(typeof(string), null!, executeStrategy); 22 | 23 | var second = first; 24 | 25 | for (var index = 0; index < 1000; index++) 26 | { 27 | second = (string)sut.RunGenerate(typeof(string), null!, executeStrategy); 28 | 29 | if (string.Equals(first, second, StringComparison.OrdinalIgnoreCase) == false) 30 | { 31 | break; 32 | } 33 | } 34 | 35 | first.Should().NotBeNull(); 36 | second.Should().NotBeNull(); 37 | first.Should().NotBe(second); 38 | } 39 | 40 | [Fact] 41 | public void GenerateReturnsStringValue() 42 | { 43 | var buildChain = new BuildHistory(); 44 | var executeStrategy = Substitute.For(); 45 | 46 | executeStrategy.BuildChain.Returns(buildChain); 47 | 48 | var sut = new Wrapper(); 49 | 50 | var actual = sut.RunGenerate(typeof(string), null!, executeStrategy); 51 | 52 | actual.Should().BeOfType(); 53 | actual.As().Should().NotBeNullOrWhiteSpace(); 54 | } 55 | 56 | [Theory] 57 | [InlineData(typeof(bool), false)] 58 | [InlineData(typeof(string), true)] 59 | public void IsMatchReturnsWhetherTypeIsSupportedTest(Type type, bool supported) 60 | { 61 | var buildChain = Substitute.For(); 62 | 63 | var sut = new Wrapper(); 64 | 65 | var actual = sut.RunIsMatch(type, null!, buildChain); 66 | 67 | actual.Should().Be(supported); 68 | } 69 | 70 | private class Wrapper : StringValueGenerator 71 | { 72 | public object RunGenerate(Type type, string? referenceName, IExecuteStrategy executeStrategy) 73 | { 74 | return Generate(executeStrategy, type, referenceName)!; 75 | } 76 | 77 | public bool RunIsMatch(Type type, string referenceName, IBuildChain buildChain) 78 | { 79 | return IsMatch(buildChain, type, referenceName); 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /ModelBuilder/CreationRules/ICreationRule.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.CreationRules 2 | { 3 | using System; 4 | using System.Reflection; 5 | 6 | /// 7 | /// The 8 | /// interface defines the members for generating values for simple scenarios. 9 | /// 10 | public interface ICreationRule 11 | { 12 | /// 13 | /// Creates a new value of the specified type. 14 | /// 15 | /// The execution strategy. 16 | /// The type of value to create. 17 | /// A new value of the type. 18 | object? Create(IExecuteStrategy executeStrategy, Type type); 19 | 20 | /// 21 | /// Creates a new value of the specified type. 22 | /// 23 | /// The execution strategy. 24 | /// The property to create the value for. 25 | /// A new value of the type. 26 | object? Create(IExecuteStrategy executeStrategy, PropertyInfo propertyInfo); 27 | 28 | /// 29 | /// Creates a new value of the specified type. 30 | /// 31 | /// The execution strategy. 32 | /// The parameter to create the value for. 33 | /// A new value of the type. 34 | object? Create(IExecuteStrategy executeStrategy, ParameterInfo parameterInfo); 35 | 36 | /// 37 | /// Returns whether the specified type matches this rule. 38 | /// 39 | /// The type to evaluate. 40 | /// true if the type matches this rule; otherwise false. 41 | bool IsMatch(Type type); 42 | 43 | /// 44 | /// Returns whether the specified property matches this rule. 45 | /// 46 | /// The property to generate the value for. 47 | /// true if the property matches this rule; otherwise false. 48 | bool IsMatch(PropertyInfo propertyInfo); 49 | 50 | /// 51 | /// Returns whether the specified parameter matches this rule. 52 | /// 53 | /// The parameter to generate the value for. 54 | /// true if the parameter matches this rule; otherwise false. 55 | bool IsMatch(ParameterInfo parameterInfo); 56 | 57 | /// 58 | /// Gets the priority for this rule. 59 | /// 60 | int Priority { get; } 61 | } 62 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/BuildConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using FluentAssertions; 4 | using NSubstitute; 5 | using Xunit; 6 | 7 | public class BuildConfigurationTests 8 | { 9 | [Fact] 10 | public void CanSetConstructorResolver() 11 | { 12 | var expected = Substitute.For(); 13 | 14 | var sut = new BuildConfiguration { ConstructorResolver = expected }; 15 | 16 | var actual = sut.ConstructorResolver; 17 | 18 | actual.Should().Be(expected); 19 | } 20 | 21 | [Fact] 22 | public void CanSetParameterResolver() 23 | { 24 | var expected = Substitute.For(); 25 | 26 | var sut = new BuildConfiguration { ParameterResolver = expected }; 27 | 28 | var actual = sut.ParameterResolver; 29 | 30 | actual.Should().Be(expected); 31 | } 32 | 33 | [Fact] 34 | public void CanSetPropertyResolver() 35 | { 36 | var expected = Substitute.For(); 37 | 38 | var sut = new BuildConfiguration { PropertyResolver = expected }; 39 | 40 | var actual = sut.PropertyResolver; 41 | 42 | actual.Should().Be(expected); 43 | } 44 | 45 | [Fact] 46 | public void CanSetTypeResolver() 47 | { 48 | var expected = Substitute.For(); 49 | 50 | var sut = new BuildConfiguration { TypeResolver = expected }; 51 | 52 | var actual = sut.TypeResolver; 53 | 54 | actual.Should().Be(expected); 55 | } 56 | 57 | [Fact] 58 | public void CreatedWithDefaultValues() 59 | { 60 | var sut = new BuildConfiguration(); 61 | 62 | sut.TypeMappingRules.Should().BeEmpty(); 63 | sut.IgnoreRules.Should().BeEmpty(); 64 | sut.TypeCreators.Should().BeEmpty(); 65 | sut.ValueGenerators.Should().BeEmpty(); 66 | sut.ExecuteOrderRules.Should().BeEmpty(); 67 | sut.CreationRules.Should().BeEmpty(); 68 | sut.PostBuildActions.Should().BeEmpty(); 69 | sut.ConstructorResolver.Should().BeOfType(); 70 | sut.ConstructorResolver.As().CacheLevel.Should().Be(CacheLevel.PerInstance); 71 | sut.ParameterResolver.Should().BeOfType(); 72 | sut.ParameterResolver.As().CacheLevel.Should().Be(CacheLevel.PerInstance); 73 | sut.PropertyResolver.Should().BeOfType(); 74 | sut.PropertyResolver.As().CacheLevel.Should().Be(CacheLevel.PerInstance); 75 | sut.TypeResolver.Should().BeOfType(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ExpressionExtensionTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using FluentAssertions; 8 | using ModelBuilder.UnitTests.Models; 9 | using Xunit; 10 | 11 | public class ExpressionExtensionTests 12 | { 13 | [Fact] 14 | public void GetPropertyReturnsPropertyInfoOfExpression() 15 | { 16 | var actual = Wrapper(x => x.Priority); 17 | 18 | actual.Name.Should().Be(nameof(Person.Priority)); 19 | actual.PropertyType.Should().Be(); 20 | } 21 | 22 | [Fact] 23 | public void GetPropertyReturnsPropertyInfoOfExpressionForNullableType() 24 | { 25 | var actual = Wrapper(x => x.FirstName); 26 | 27 | actual.Name.Should().Be(nameof(Person.FirstName)); 28 | actual.PropertyType.Should().Be(); 29 | } 30 | 31 | [Fact] 32 | public void GetPropertyThrowsExceptionForStaticProperty() 33 | { 34 | Action action = () => Wrapper(x => WithStatic.Second); 35 | 36 | action.Should().Throw(); 37 | } 38 | 39 | [Fact] 40 | public void GetPropertyThrowsExceptionForStaticReadOnlyProperty() 41 | { 42 | Action action = () => Wrapper(x => StaticGetter.Value); 43 | 44 | action.Should().Throw(); 45 | } 46 | 47 | [Fact] 48 | public void GetPropertyThrowsExceptionWhenPropertyNotOnTargetType() 49 | { 50 | Action action = () => Wrapper(x => x.Priority.ToString(CultureInfo.InvariantCulture).Length); 51 | 52 | action.Should().Throw(); 53 | } 54 | 55 | [Fact] 56 | public void GetPropertyThrowsExceptionWithFieldExpression() 57 | { 58 | Action action = () => Wrapper(x => x.MinAge); 59 | 60 | action.Should().Throw(); 61 | } 62 | 63 | [Fact] 64 | public void GetPropertyThrowsExceptionWithMethodExpression() 65 | { 66 | Action action = () => Wrapper(x => x.DoSomething()!); 67 | 68 | action.Should().Throw(); 69 | } 70 | 71 | [Fact] 72 | public void GetPropertyThrowsExceptionWithNullExpression() 73 | { 74 | Action action = () => Wrapper(null!); 75 | 76 | action.Should().Throw(); 77 | } 78 | 79 | private static PropertyInfo Wrapper(Expression> expression) 80 | { 81 | return expression.GetProperty(); 82 | } 83 | 84 | private class StaticGetter 85 | { 86 | public static string Value => Guid.NewGuid().ToString(); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /ModelBuilder/IBuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder 2 | { 3 | using System.Collections.Generic; 4 | using ModelBuilder.CreationRules; 5 | using ModelBuilder.ExecuteOrderRules; 6 | using ModelBuilder.IgnoreRules; 7 | using ModelBuilder.TypeCreators; 8 | using ModelBuilder.ValueGenerators; 9 | 10 | /// 11 | /// The 12 | /// interface defines the configuration used to create and populate instances. 13 | /// 14 | public interface IBuildConfiguration 15 | { 16 | /// 17 | /// Gets or sets the constructor resolver used to create an instance of a type. 18 | /// 19 | IConstructorResolver ConstructorResolver { get; set; } 20 | 21 | /// 22 | /// Gets the creation rules used to quickly generate values without invoking or 23 | /// instances. 24 | /// 25 | ICollection CreationRules { get; } 26 | 27 | /// 28 | /// Gets the execute order rules used to determine the order that properties are populated. 29 | /// 30 | ICollection ExecuteOrderRules { get; } 31 | 32 | /// 33 | /// Gets the ignore rules used to skip over property population. 34 | /// 35 | ICollection IgnoreRules { get; } 36 | 37 | /// 38 | /// Gets or sets the parameter resolver used to create an instance of a type. 39 | /// 40 | IParameterResolver ParameterResolver { get; set; } 41 | 42 | /// 43 | /// Gets the post build actions used to modify instances after they have been created or populated. 44 | /// 45 | ICollection PostBuildActions { get; } 46 | 47 | /// 48 | /// Gets or sets the property resolver used to populate an instance of a type. 49 | /// 50 | IPropertyResolver PropertyResolver { get; set; } 51 | 52 | /// 53 | /// Gets the type creators used to create instances. 54 | /// 55 | ICollection TypeCreators { get; } 56 | 57 | /// 58 | /// Gets the rules used to map between types before attempting to create a value of the source type. 59 | /// 60 | ICollection TypeMappingRules { get; } 61 | 62 | /// 63 | /// Gets or sets the type resolver used to determine the build type. 64 | /// 65 | ITypeResolver TypeResolver { get; set; } 66 | 67 | /// 68 | /// Gets the value generators used to generate flat values. 69 | /// 70 | ICollection ValueGenerators { get; } 71 | } 72 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/BuildExceptionTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests 2 | { 3 | using System; 4 | using FluentAssertions; 5 | using ModelBuilder.UnitTests.Models; 6 | using Xunit; 7 | 8 | public class BuildExceptionTests 9 | { 10 | [Fact] 11 | public void CanCreatesWithDefaultValues() 12 | { 13 | var sut = new BuildException(); 14 | 15 | sut.BuildLog.Should().BeNull(); 16 | sut.Message.Should().NotBeNullOrWhiteSpace(); 17 | sut.InnerException.Should().BeNull(); 18 | sut.TargetType.Should().BeNull(); 19 | sut.Context.Should().BeNull(); 20 | } 21 | 22 | [Fact] 23 | public void CanCreateWithBuildInformation() 24 | { 25 | var message = Guid.NewGuid().ToString(); 26 | var targetType = typeof(Person); 27 | var referenceName = Guid.NewGuid().ToString(); 28 | var context = new Company(); 29 | var buildLog = Guid.NewGuid().ToString(); 30 | 31 | var sut = new BuildException(message, targetType, referenceName, context, buildLog); 32 | 33 | sut.Message.Should().Be(message); 34 | sut.TargetType.Should().Be(targetType); 35 | sut.ReferenceName.Should().Be(referenceName); 36 | sut.Context.Should().Be(context); 37 | sut.BuildLog.Should().Be(buildLog); 38 | sut.InnerException.Should().BeNull(); 39 | } 40 | 41 | [Fact] 42 | public void CanCreateWithBuildInformationAndInnerException() 43 | { 44 | var message = Guid.NewGuid().ToString(); 45 | var targetType = typeof(Person); 46 | var referenceName = Guid.NewGuid().ToString(); 47 | var context = new Company(); 48 | var buildLog = Guid.NewGuid().ToString(); 49 | var inner = new TimeoutException(); 50 | 51 | var sut = new BuildException(message, targetType, referenceName, context, buildLog, inner); 52 | 53 | sut.Message.Should().Be(message); 54 | sut.TargetType.Should().Be(targetType); 55 | sut.ReferenceName.Should().Be(referenceName); 56 | sut.Context.Should().Be(context); 57 | sut.BuildLog.Should().Be(buildLog); 58 | sut.InnerException.Should().Be(inner); 59 | } 60 | 61 | [Fact] 62 | public void CanCreateWithMessage() 63 | { 64 | var message = Guid.NewGuid().ToString(); 65 | 66 | var sut = new BuildException(message); 67 | 68 | sut.Message.Should().Be(message); 69 | } 70 | 71 | [Fact] 72 | public void CanCreateWithMessageAndException() 73 | { 74 | var message = Guid.NewGuid().ToString(); 75 | var inner = new TimeoutException(); 76 | 77 | var sut = new BuildException(message, inner); 78 | 79 | sut.Message.Should().Be(message); 80 | sut.InnerException.Should().Be(inner); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /ModelBuilder.UnitTests/ValueGenerators/LastNameValueGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.UnitTests.ValueGenerators 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using FluentAssertions; 7 | using ModelBuilder.Data; 8 | using ModelBuilder.UnitTests.Models; 9 | using ModelBuilder.ValueGenerators; 10 | using NSubstitute; 11 | using Xunit; 12 | 13 | public class LastNameValueGeneratorTests 14 | { 15 | [Fact] 16 | public void GeneratorReturnsName() 17 | { 18 | var person = new PersonWithoutGender(); 19 | var buildChain = new BuildHistory(); 20 | var executeStrategy = Substitute.For(); 21 | 22 | executeStrategy.BuildChain.Returns(buildChain); 23 | 24 | buildChain.Push(person); 25 | 26 | var sut = new Wrapper(); 27 | 28 | var actual = (string)sut.RunGenerate(typeof(string), "LastName", executeStrategy); 29 | 30 | TestData.LastNames.Any(x => x == actual).Should().BeTrue(); 31 | } 32 | 33 | [Theory] 34 | [InlineData(typeof(Stream), "lastname", false)] 35 | [InlineData(typeof(string), null, false)] 36 | [InlineData(typeof(string), "", false)] 37 | [InlineData(typeof(string), "Stuff", false)] 38 | [InlineData(typeof(string), "LastName", true)] 39 | [InlineData(typeof(string), "lastname", true)] 40 | [InlineData(typeof(string), "LASTNAME", true)] 41 | [InlineData(typeof(string), "Last_Name", true)] 42 | [InlineData(typeof(string), "last_name", true)] 43 | [InlineData(typeof(string), "LAST_NAME", true)] 44 | [InlineData(typeof(string), "Surname", true)] 45 | [InlineData(typeof(string), "surname", true)] 46 | [InlineData(typeof(string), "SURNAME", true)] 47 | public void IsMatchReturnsWhetherTypeAndNameAreSupportedTest(Type type, string? referenceName, bool expected) 48 | { 49 | var person = new Person(); 50 | var buildChain = new BuildHistory(); 51 | 52 | buildChain.Push(person); 53 | 54 | var sut = new Wrapper(); 55 | 56 | var actual = sut.RunIsMatch(type, referenceName!, buildChain); 57 | 58 | actual.Should().Be(expected); 59 | } 60 | 61 | [Fact] 62 | public void PriorityReturnsHigherPriorityThanStringValidator() 63 | { 64 | var sut = new LastNameValueGenerator(); 65 | var other = new StringValueGenerator(); 66 | 67 | sut.Priority.Should().BeGreaterThan(other.Priority); 68 | } 69 | 70 | private class Wrapper : LastNameValueGenerator 71 | { 72 | public object RunGenerate(Type type, string referenceName, IExecuteStrategy executeStrategy) 73 | { 74 | return Generate(executeStrategy, type, referenceName)!; 75 | } 76 | 77 | public bool RunIsMatch(Type type, string referenceName, IBuildChain buildChain) 78 | { 79 | return IsMatch(buildChain, type, referenceName); 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /ModelBuilder/ValueGenerators/PostCodeValueGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace ModelBuilder.ValueGenerators 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using ModelBuilder.Data; 8 | 9 | /// 10 | /// The 11 | /// class is used to generate random post code values. 12 | /// 13 | public class PostCodeValueGenerator : RelativeValueGenerator 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public PostCodeValueGenerator() : base(NameExpression.PostCode, typeof(string)) 19 | { 20 | } 21 | 22 | /// 23 | protected override object? Generate(IExecuteStrategy executeStrategy, Type type, string? referenceName) 24 | { 25 | var context = executeStrategy?.BuildChain?.Last; 26 | 27 | IEnumerable locations = TestData.Locations; 28 | 29 | if (context != null) 30 | { 31 | // Filter down the locations based on the context 32 | locations = FilterLocations( 33 | locations, 34 | NameExpression.Country, 35 | (item, value) => item.Country.Equals(value, StringComparison.OrdinalIgnoreCase), 36 | context); 37 | 38 | locations = FilterLocations( 39 | locations, 40 | NameExpression.State, 41 | (item, value) => item.State.Equals(value, StringComparison.OrdinalIgnoreCase), 42 | context); 43 | 44 | locations = FilterLocations( 45 | locations, 46 | NameExpression.City, 47 | (item, value) => item.City.Equals(value, StringComparison.OrdinalIgnoreCase), 48 | context); 49 | } 50 | 51 | var availableLocations = locations.ToList(); 52 | 53 | if (availableLocations.Count > 0) 54 | { 55 | var matchingLocation = availableLocations.Next(); 56 | 57 | return matchingLocation.PostCode; 58 | } 59 | 60 | // There was either no country or no match on the country 61 | var location = TestData.Locations.Next(); 62 | 63 | return location.PostCode; 64 | } 65 | 66 | private IEnumerable FilterLocations( 67 | IEnumerable locations, 68 | Regex getExpression, 69 | Func evaluator, 70 | object context) 71 | { 72 | var matchValue = GetValue(getExpression, context); 73 | 74 | if (string.IsNullOrWhiteSpace(matchValue)) 75 | { 76 | return locations; 77 | } 78 | 79 | return locations.Where(x => evaluator(x, matchValue)); 80 | } 81 | 82 | /// 83 | public override int Priority { get; } = 1000; 84 | } 85 | } --------------------------------------------------------------------------------