diff --git a/README.md b/README.md index f91999e..3408120 100644 --- a/README.md +++ b/README.md @@ -64,20 +64,23 @@ The library is available as a package on NuGet: [NetArchTest.eNhancedEdition](ht [TestClass] public class SampleApp_ModuleAlpha_Tests { - static readonly Assembly AssemblyUnderTest = typeof(SampleApp.ModuleAlpha.TestUtils).Assembly; + static readonly Assembly AssemblyUnderTest = typeof(TestUtils).Assembly; + [TestMethod] public void PersistenceIsNotAccessibleFromOutsideOfModuleExceptOfDbContext() { var result = Types.InAssembly(AssemblyUnderTest) .That() - .ResideInNamespace("SampleApp.ModuleAlpha.Persistence") + .ResideInNamespace("SampleApp.ModuleAlpha.Persistence") .And() .DoNotHaveNameEndingWith("DbContext") .Should() .NotBePublic() .GetResult(); + Assert.IsTrue(result.IsSuccessful); } + [TestMethod] public void DomainIsIndependent() { @@ -89,11 +92,32 @@ public class SampleApp_ModuleAlpha_Tests "System", "SampleApp.ModuleAlpha.Domain", "SampleApp.SharedKernel.Domain", - "SampleApp.BuildingBlocks.Domain" + "SampleApp.BuildingBlocks.Domain" ) .GetResult(); + Assert.IsTrue(result.IsSuccessful, "Domain has lost its independence!"); } + +} + +[TestClass] +public class SampleApp_ModuleOmega_Tests +{ + static readonly Assembly AssemblyUnderTest = typeof(TestUtils).Assembly; + + [TestMethod] + public void RequestHandlersShouldBeSealed() + { + var result = Types.InAssembly(AssemblyUnderTest) + .That() + .ImplementInterface(typeof(IRequestHandler<,>)) + .Should() + .BeSealed() + .GetResult(); + + Assert.IsTrue(result.IsSuccessful); + } } ``` diff --git a/documentation/readme.nt b/documentation/readme.nt index 72663ee..cb543f3 100644 --- a/documentation/readme.nt +++ b/documentation/readme.nt @@ -64,8 +64,11 @@ The library is available as a package on NuGet: [NetArchTest.eNhancedEdition](ht ### Examples ```csharp -{{ sample = data.Classes | Symbols.WhereNameMatches "SampleApp_ModuleAlpha_Tests" | Array.First - sample.SourceCode +{{ sample1 = data.Classes | Symbols.WhereNameMatches "SampleApp_ModuleAlpha_Tests" | Array.First + sample1.SourceCode}} + +{{ sample2 = data.Classes | Symbols.WhereNameMatches "SampleApp_ModuleOmega_Tests" | Array.First + sample2.SourceCode }} ``` diff --git a/samples/NetArchTest.SampleTests/UnitTest1.cs b/samples/NetArchTest.SampleTests/SampleApp_ModuleAlpha_Tests.cs similarity index 89% rename from samples/NetArchTest.SampleTests/UnitTest1.cs rename to samples/NetArchTest.SampleTests/SampleApp_ModuleAlpha_Tests.cs index 0c461dd..019eb34 100644 --- a/samples/NetArchTest.SampleTests/UnitTest1.cs +++ b/samples/NetArchTest.SampleTests/SampleApp_ModuleAlpha_Tests.cs @@ -1,25 +1,28 @@ using System.Reflection; +using MediatR; using Microsoft.VisualStudio.TestTools.UnitTesting; using NetArchTest.Rules; +using SampleApp.ModuleAlpha; namespace NetArchTest.SampleTests { [TestClass] public class SampleApp_ModuleAlpha_Tests { - static readonly Assembly AssemblyUnderTest = typeof(SampleApp.ModuleAlpha.TestUtils).Assembly; + static readonly Assembly AssemblyUnderTest = typeof(TestUtils).Assembly; [TestMethod] public void PersistenceIsNotAccessibleFromOutsideOfModuleExceptOfDbContext() { var result = Types.InAssembly(AssemblyUnderTest) .That() - .ResideInNamespace("SampleApp.ModuleAlpha.Persistence") + .ResideInNamespace("SampleApp.ModuleAlpha.Persistence") .And() .DoNotHaveNameEndingWith("DbContext") .Should() .NotBePublic() .GetResult(); + Assert.IsTrue(result.IsSuccessful); } @@ -34,10 +37,12 @@ public void DomainIsIndependent() "System", "SampleApp.ModuleAlpha.Domain", "SampleApp.SharedKernel.Domain", - "SampleApp.BuildingBlocks.Domain" + "SampleApp.BuildingBlocks.Domain" ) .GetResult(); + Assert.IsTrue(result.IsSuccessful, "Domain has lost its independence!"); } + } } \ No newline at end of file diff --git a/samples/NetArchTest.SampleTests/SampleApp_ModuleOmega_Tests.cs b/samples/NetArchTest.SampleTests/SampleApp_ModuleOmega_Tests.cs new file mode 100644 index 0000000..70b4b8e --- /dev/null +++ b/samples/NetArchTest.SampleTests/SampleApp_ModuleOmega_Tests.cs @@ -0,0 +1,28 @@ +using System.Reflection; +using MediatR; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetArchTest.Rules; +using SampleApp.ModuleOmega; + + +namespace NetArchTest.SampleTests +{ + [TestClass] + public class SampleApp_ModuleOmega_Tests + { + static readonly Assembly AssemblyUnderTest = typeof(TestUtils).Assembly; + + [TestMethod] + public void RequestHandlersShouldBeSealed() + { + var result = Types.InAssembly(AssemblyUnderTest) + .That() + .ImplementInterface(typeof(IRequestHandler<,>)) + .Should() + .BeSealed() + .GetResult(); + + Assert.IsTrue(result.IsSuccessful); + } + } +} \ No newline at end of file diff --git a/samples/SampleApp.BuildingBlocks/Persistence/GenericRepository.cs b/samples/SampleApp.BuildingBlocks/Persistence/GenericRepository.cs new file mode 100644 index 0000000..a29b439 --- /dev/null +++ b/samples/SampleApp.BuildingBlocks/Persistence/GenericRepository.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace SampleApp.BuildingBlocks.Persistence +{ + public abstract class GenericRepository where TEntity : class + where TContext : DbContext + { + protected readonly TContext context; + + + public GenericRepository(TContext context) + { + this.context = context; + } + + + public void Add(TEntity entity) + { + context.Set().Add(entity); + } + public TEntity GetById(long id) + { + return context.Set().Find(id); + } + } +} diff --git a/samples/SampleApp.BuildingBlocks/SampleApp.BuildingBlocks.csproj b/samples/SampleApp.BuildingBlocks/SampleApp.BuildingBlocks.csproj index 55916dd..629aeaa 100644 --- a/samples/SampleApp.BuildingBlocks/SampleApp.BuildingBlocks.csproj +++ b/samples/SampleApp.BuildingBlocks/SampleApp.BuildingBlocks.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/samples/SampleApp.ModuleAlpha/App/Users/UsersService.cs b/samples/SampleApp.ModuleAlpha/App/Users/UsersService.cs index 9528b35..9040c52 100644 --- a/samples/SampleApp.ModuleAlpha/App/Users/UsersService.cs +++ b/samples/SampleApp.ModuleAlpha/App/Users/UsersService.cs @@ -3,7 +3,7 @@ namespace SampleApp.ModuleAlpha.App.Users { - internal class UsersService + internal sealed class UsersService { public async Task CreateUser(CreateUser createUser) diff --git a/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/QuestionOnListDTO.cs b/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/QuestionOnListDTO.cs new file mode 100644 index 0000000..e4a0f77 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/QuestionOnListDTO.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SampleApp.ModuleOmega.App.RequestHandlers.Questions +{ + internal class QuestionOnListDTO + { + } +} diff --git a/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/ReadQuestionsHandler.cs b/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/ReadQuestionsHandler.cs new file mode 100644 index 0000000..c14c555 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/ReadQuestionsHandler.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediatR; +using SampleApp.ModuleOmega.Persistence; + +namespace SampleApp.ModuleOmega.App.RequestHandlers.Questions +{ + internal sealed class ReadQuestionsHandler : IRequestHandler> + { + private readonly ReadOnlyTestCreationDbContext context; + + public ReadQuestionsHandler(ReadOnlyTestCreationDbContext context) + { + this.context = context; + } + + + public async Task> Handle(ReadQuestionsQuery query, CancellationToken cancellationToken) + { + + + return null; + } + } +} diff --git a/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/ReadQuestionsQuery.cs b/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/ReadQuestionsQuery.cs new file mode 100644 index 0000000..6fab6a8 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/App/RequestHandlers/Questions/ReadQuestionsQuery.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediatR; + +namespace SampleApp.ModuleOmega.App.RequestHandlers.Questions +{ + internal class ReadQuestionsQuery : IRequest> + { + } +} diff --git a/samples/SampleApp.ModuleOmega/Domain/ITestCreationUoW.cs b/samples/SampleApp.ModuleOmega/Domain/ITestCreationUoW.cs new file mode 100644 index 0000000..e3c35d5 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Domain/ITestCreationUoW.cs @@ -0,0 +1,11 @@ +using SampleApp.ModuleOmega.Domain.Questions; + +namespace SampleApp.ModuleOmega.Domain +{ + internal interface ITestCreationUoW + { + IQuestionRepository Questions { get; } + + Task Save(); + } +} diff --git a/samples/SampleApp.ModuleOmega/Domain/Questions/Answer.cs b/samples/SampleApp.ModuleOmega/Domain/Questions/Answer.cs new file mode 100644 index 0000000..af219ee --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Domain/Questions/Answer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SampleApp.ModuleOmega.Domain.Questions +{ + internal sealed class Answer + { + + } +} diff --git a/samples/SampleApp.ModuleOmega/Domain/Questions/IQuestionRepository.cs b/samples/SampleApp.ModuleOmega/Domain/Questions/IQuestionRepository.cs new file mode 100644 index 0000000..29be3c4 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Domain/Questions/IQuestionRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SampleApp.ModuleOmega.Domain.Questions +{ + internal interface IQuestionRepository + { + Question GetByIdWithAnswers(long id); + } +} diff --git a/samples/SampleApp.ModuleOmega/Domain/Questions/Question.cs b/samples/SampleApp.ModuleOmega/Domain/Questions/Question.cs new file mode 100644 index 0000000..21305f5 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Domain/Questions/Question.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SampleApp.ModuleOmega.Domain.Questions +{ + internal sealed class Question + { + private readonly List _answers = new List(); + } +} diff --git a/samples/SampleApp.ModuleOmega/Persistence/Questions/QuestionRepository.cs b/samples/SampleApp.ModuleOmega/Persistence/Questions/QuestionRepository.cs new file mode 100644 index 0000000..ac4aa09 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Persistence/Questions/QuestionRepository.cs @@ -0,0 +1,19 @@ +using SampleApp.BuildingBlocks.Persistence; +using SampleApp.ModuleOmega.Domain.Questions; + +namespace SampleApp.ModuleOmega.Persistence.Questions +{ + internal sealed class QuestionRepository : GenericRepository, IQuestionRepository + { + public QuestionRepository(TestCreationDbContext context) : base(context) + { + + } + + + public Question GetByIdWithAnswers(long id) + { + return null; + } + } +} diff --git a/samples/SampleApp.ModuleOmega/Persistence/ReadOnlyTestCreationDbContext.cs b/samples/SampleApp.ModuleOmega/Persistence/ReadOnlyTestCreationDbContext.cs new file mode 100644 index 0000000..3aff959 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Persistence/ReadOnlyTestCreationDbContext.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace SampleApp.ModuleOmega.Persistence +{ + internal sealed class ReadOnlyTestCreationDbContext : TestCreationDbContext + { + + + public ReadOnlyTestCreationDbContext(DbContextOptions options) : base(options) + { + ChangeTracker.LazyLoadingEnabled = false; + ChangeTracker.AutoDetectChangesEnabled = false; + ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/samples/SampleApp.ModuleOmega/Persistence/TestCreationDbContext.cs b/samples/SampleApp.ModuleOmega/Persistence/TestCreationDbContext.cs new file mode 100644 index 0000000..4c4f3fd --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Persistence/TestCreationDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using SampleApp.ModuleOmega.Domain.Questions; + +namespace SampleApp.ModuleOmega.Persistence +{ + internal class TestCreationDbContext : DbContext + { + public DbSet Questions { get; protected set; } + + + public TestCreationDbContext(DbContextOptions options) : base(options) + { + ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + } + } +} diff --git a/samples/SampleApp.ModuleOmega/Persistence/TestCreationUoW.cs b/samples/SampleApp.ModuleOmega/Persistence/TestCreationUoW.cs new file mode 100644 index 0000000..c22a257 --- /dev/null +++ b/samples/SampleApp.ModuleOmega/Persistence/TestCreationUoW.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore; +using SampleApp.ModuleOmega.Domain; +using SampleApp.ModuleOmega.Domain.Questions; + +namespace SampleApp.ModuleOmega.Persistence +{ + internal sealed class TestCreationUoW : ITestCreationUoW, IDisposable + { + private readonly TestCreationDbContext context; + private readonly Lazy questions; + + + public IQuestionRepository Questions { get => questions.Value; } + + + public Task Save() + { + return context.SaveChangesAsync(); + } + + + + + + public void Dispose() + { + context.Dispose(); + } + } +} diff --git a/samples/SampleApp.ModuleOmega/SampleApp.ModuleOmega.csproj b/samples/SampleApp.ModuleOmega/SampleApp.ModuleOmega.csproj index 8c106bc..462c0df 100644 --- a/samples/SampleApp.ModuleOmega/SampleApp.ModuleOmega.csproj +++ b/samples/SampleApp.ModuleOmega/SampleApp.ModuleOmega.csproj @@ -7,14 +7,16 @@ - - - + + + + + diff --git a/samples/SampleApp.ModuleOmega/TestUtils.cs b/samples/SampleApp.ModuleOmega/TestUtils.cs new file mode 100644 index 0000000..77c414e --- /dev/null +++ b/samples/SampleApp.ModuleOmega/TestUtils.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +[assembly: InternalsVisibleTo("NetArchTest.SampleTests")] +namespace SampleApp.ModuleOmega +{ + internal class TestUtils + { + } +} diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs index 94e12bc..6e68f2b 100644 --- a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs @@ -135,14 +135,14 @@ public static string GetNamespace(this TypeDefinition typeDefinition) { if (typeDefinition.IsNested) { - return typeDefinition.DeclaringType.GetFullName(); + return typeDefinition.DeclaringType.FullName; } return typeDefinition.Namespace; } - public static string GetName(this TypeDefinition typeDefinition) + public static string GetNameWithoutGenericPart(this TypeDefinition typeDefinition) { if (typeDefinition.HasGenericParameters == false) { @@ -150,14 +150,7 @@ public static string GetName(this TypeDefinition typeDefinition) } return typeDefinition.Name.RemoveGenericPart(); } - public static string GetFullName(this TypeDefinition typeDefinition) - { - if (typeDefinition.HasGenericParameters == false) - { - return typeDefinition.FullName; - } - return typeDefinition.FullName.RemoveGenericPart(); - } + diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs index d2ab05e..a7881d5 100644 --- a/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeReferenceExtensions.cs @@ -25,5 +25,21 @@ public static string GetNamespace(this TypeReference typeReference) } return typeReference.Namespace; } + + + public static string GetFullNameWithoutGenericParameters(this TypeReference typeReference) + { + if (typeReference is GenericInstanceType genericInstanceType && genericInstanceType.HasGenericArguments) + { + return typeReference.GetNamespace() + "." + typeReference.Name; + } + + if (typeReference.HasGenericParameters == true ) + { + return typeReference.GetNamespace() + "." + typeReference.Name; + } + + return typeReference.FullName; + } } } \ No newline at end of file diff --git a/sources/NetArchTest/Extensions/System/StringExtensions.cs b/sources/NetArchTest/Extensions/System/StringExtensions.cs index a9629c3..256dcbb 100644 --- a/sources/NetArchTest/Extensions/System/StringExtensions.cs +++ b/sources/NetArchTest/Extensions/System/StringExtensions.cs @@ -11,7 +11,7 @@ public static string RemoveGenericPart(this string name) if (string.IsNullOrEmpty(name)) return name; - int index = name.IndexOf('`'); + int index = name.LastIndexOf('`'); if (index > 0) { return name.Substring(0, index); diff --git a/sources/NetArchTest/Functions/FunctionDelegates.cs b/sources/NetArchTest/Functions/FunctionDelegates.cs index 0ad9c97..4486efa 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates.cs @@ -57,7 +57,7 @@ internal static IEnumerable Inherit(IEnumerable input, Type internal static IEnumerable BeInherited(FunctionSequenceExecutionContext context, IEnumerable input, bool condition) { - var InheritedTypes = new HashSet(context.AllTypes.Select(x => x.Definition.BaseType?.FullName).Where(x => x is not null)); + var InheritedTypes = new HashSet(context.AllTypes.Select(x => x.Definition.BaseType?.GetFullNameWithoutGenericParameters()).Where(x => x is not null)); if (condition) { return input.Where(c => InheritedTypes.Contains(c.Definition.FullName)); @@ -84,7 +84,14 @@ internal static IEnumerable ImplementInterface(IEnumerable i return input.Where(c => !Implements(c.Definition, typeInterface)); } - static bool Implements(TypeDefinition c, Type typeInterface) => c.Interfaces.Any(t => t.InterfaceType.FullName.Equals(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase)); + static bool Implements(TypeDefinition c, Type typeInterface) + { + if (typeInterface.IsGenericType) + { + return c.Interfaces.Any(t => t.InterfaceType.FullName.StartsWith(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase)); + } + return c.Interfaces.Any(t => t.InterfaceType.FullName.Equals(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase)); + } } internal static IEnumerable MeetCustomRule(IEnumerable input, ICustomRule rule, bool condition) diff --git a/sources/NetArchTest/Functions/FunctionDelegates_Names.cs b/sources/NetArchTest/Functions/FunctionDelegates_Names.cs index 2b9a5db..cdd1e0f 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates_Names.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates_Names.cs @@ -32,11 +32,11 @@ internal static IEnumerable HaveName(FunctionSequenceExecutionContext var plainNames = names.Select(x => x.RemoveGenericPart()).ToArray(); if (condition) { - return input.Where(c => HasName(c.Definition.GetName(), plainNames, context.UserOptions.Comparer)); + return input.Where(c => HasName(c.Definition.GetNameWithoutGenericPart(), plainNames, context.UserOptions.Comparer)); } else { - return input.Where(c => !HasName(c.Definition.GetName(), plainNames, context.UserOptions.Comparer)); + return input.Where(c => !HasName(c.Definition.GetNameWithoutGenericPart(), plainNames, context.UserOptions.Comparer)); } static bool HasName(string typeName, string[] lookigFor, StringComparison comparer) => lookigFor.Any(x => typeName.Equals(x, comparer)); @@ -47,11 +47,11 @@ internal static IEnumerable HaveNameMatching(IEnumerable inp Regex r = new Regex(pattern, RegexOptions.IgnoreCase); if (condition) { - return input.Where(c => r.Match(c.Definition.GetName()).Success); + return input.Where(c => r.Match(c.Definition.GetNameWithoutGenericPart()).Success); } else { - return input.Where(c => !r.Match(c.Definition.GetName()).Success); + return input.Where(c => !r.Match(c.Definition.GetNameWithoutGenericPart()).Success); } } @@ -59,11 +59,11 @@ internal static IEnumerable HaveNameStartingWith(FunctionSequenceExecu { if (condition) { - return input.Where(c => StartsWith(c.Definition.GetName(), prefixes, context.UserOptions.Comparer)); + return input.Where(c => StartsWith(c.Definition.GetNameWithoutGenericPart(), prefixes, context.UserOptions.Comparer)); } else { - return input.Where(c => !StartsWith(c.Definition.GetName(), prefixes, context.UserOptions.Comparer)); + return input.Where(c => !StartsWith(c.Definition.GetNameWithoutGenericPart(), prefixes, context.UserOptions.Comparer)); } static bool StartsWith(string typeName, string[] lookigFor, StringComparison comparer) => lookigFor.Any(x => typeName.StartsWith(x, comparer)); @@ -73,11 +73,11 @@ internal static IEnumerable HaveNameEndingWith(FunctionSequenceExecuti { if (condition) { - return input.Where(c => EndsWith(c.Definition.GetName(), suffixes, context.UserOptions.Comparer)); + return input.Where(c => EndsWith(c.Definition.GetNameWithoutGenericPart(), suffixes, context.UserOptions.Comparer)); } else { - return input.Where(c => !EndsWith(c.Definition.GetName(), suffixes, context.UserOptions.Comparer)); + return input.Where(c => !EndsWith(c.Definition.GetNameWithoutGenericPart(), suffixes, context.UserOptions.Comparer)); } static bool EndsWith(string typeName, string[] lookigFor, StringComparison comparer) => lookigFor.Any(x => typeName.EndsWith(x, comparer)); diff --git a/sources/NetArchTest/Functions/FunctionDelegates_Special.cs b/sources/NetArchTest/Functions/FunctionDelegates_Special.cs index 66c3ac6..a57cf4f 100644 --- a/sources/NetArchTest/Functions/FunctionDelegates_Special.cs +++ b/sources/NetArchTest/Functions/FunctionDelegates_Special.cs @@ -75,11 +75,11 @@ internal static IEnumerable HaveFileNameMatchingTypeName(FunctionSeque { if (condition) { - return input.Where(c => IsMatching(c.SourceFilePath, c.Definition.GetName(), context.UserOptions.Comparer)); + return input.Where(c => IsMatching(c.SourceFilePath, c.Definition.GetNameWithoutGenericPart(), context.UserOptions.Comparer)); } else { - return input.Where(c => !IsMatching(c.SourceFilePath, c.Definition.GetName(), context.UserOptions.Comparer)); + return input.Where(c => !IsMatching(c.SourceFilePath, c.Definition.GetNameWithoutGenericPart(), context.UserOptions.Comparer)); } static bool IsMatching(string sourceFilePath, string typeName, StringComparison comparer) @@ -115,7 +115,7 @@ static bool IsMatching(string sourceFilePath, string @namespace, StringCompariso internal static IEnumerable HaveMatchingTypeWithName(FunctionSequenceExecutionContext context, IEnumerable input, Func getMatchingTypeName, bool condition) { - var exisitingTypes = new HashSet(context.AllTypes.Select(x => x.Definition.GetName())); + var exisitingTypes = new HashSet(context.AllTypes.Select(x => x.Definition.GetNameWithoutGenericPart())); if (condition) { return input.Where(c => exisitingTypes.Contains(getMatchingTypeName(c.Definition))); diff --git a/tests/NetArchTest.Rules.UnitTests/ConditionTests.cs b/tests/NetArchTest.Rules.UnitTests/ConditionTests.cs index 8c1d7cb..63d9a43 100644 --- a/tests/NetArchTest.Rules.UnitTests/ConditionTests.cs +++ b/tests/NetArchTest.Rules.UnitTests/ConditionTests.cs @@ -125,7 +125,7 @@ public void NotImplementInterface() .That() .ResideInNamespace("NetArchTest.TestStructure.Interfaces") .And() - .DoNotHaveNameStartingWith("Implements") + .HaveNameStartingWith("DoesNotImplement") .Should() .NotImplementInterface(typeof(IExample)).GetResult(); diff --git a/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs b/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs index fadf3c5..473e280 100644 --- a/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs +++ b/tests/NetArchTest.Rules.UnitTests/PredicateTests.cs @@ -90,7 +90,7 @@ public void Inherit() .Inherit(typeof(BaseClass)).GetReflectionTypes(); Assert.Equal(2, result.Count()); - Assert.Contains(typeof(DerivedClass), result); + Assert.Contains(typeof(DerivedClass<>), result); Assert.Contains(typeof(DerivedDerivedClass), result); } @@ -135,8 +135,8 @@ public void AreInheritedByAnyType() .AreInheritedByAnyType().GetReflectionTypes(); Assert.Equal(2, result.Count()); - Assert.Contains(typeof(BaseClass), result); - Assert.Contains(typeof(DerivedClass), result); + Assert.Contains(typeof(BaseClass), result); + Assert.Contains(typeof(DerivedClass<>), result); } [Fact(DisplayName = "AreNotInheritedByAnyType")] @@ -165,8 +165,25 @@ public void ImplementInterface() .And() .ImplementInterface(typeof(IExample)).GetReflectionTypes(); - Assert.Single(result); + Assert.Equal(3, result.Count()); Assert.Contains(typeof(ImplementsExampleInterface), result); + Assert.Contains(typeof(IGenericExample<>), result); + Assert.Contains(typeof(ImplementsGenericExampleInterface), result); + } + + [Fact(DisplayName = "ImplementInterface_OpenGeneric")] + public void ImplementInterface_OpenGeneric() + { + var result = Types + .InAssembly(Assembly.GetAssembly(typeof(ClassA1))) + .That() + .ResideInNamespace("NetArchTest.TestStructure.Interfaces") + .And() + .ImplementInterface(typeof(IGenericExample<>)) + .GetReflectionTypes(); + + Assert.Equal(1, result.Count()); + Assert.Contains(typeof(ImplementsGenericExampleInterface), result); } [Fact(DisplayName = "DoNotImplementInterface")] diff --git a/tests/NetArchTest.TestStructure/Inheritance/DerivedClass.cs b/tests/NetArchTest.TestStructure/Inheritance/DerivedClass.cs index 0006171..6030430 100644 --- a/tests/NetArchTest.TestStructure/Inheritance/DerivedClass.cs +++ b/tests/NetArchTest.TestStructure/Inheritance/DerivedClass.cs @@ -1,6 +1,6 @@ namespace NetArchTest.TestStructure.Inheritance { - public class DerivedClass : BaseClass + public class DerivedClass : BaseClass { } } diff --git a/tests/NetArchTest.TestStructure/Inheritance/DerivedDerivedClass.cs b/tests/NetArchTest.TestStructure/Inheritance/DerivedDerivedClass.cs index 00b8b50..39feaf3 100644 --- a/tests/NetArchTest.TestStructure/Inheritance/DerivedDerivedClass.cs +++ b/tests/NetArchTest.TestStructure/Inheritance/DerivedDerivedClass.cs @@ -1,6 +1,6 @@ namespace NetArchTest.TestStructure.Inheritance { - public class DerivedDerivedClass : DerivedClass + public class DerivedDerivedClass : DerivedClass { } } diff --git a/tests/NetArchTest.TestStructure/Interfaces/IExample.cs b/tests/NetArchTest.TestStructure/Interfaces/IExample.cs index 9ec2987..5d9e055 100644 --- a/tests/NetArchTest.TestStructure/Interfaces/IExample.cs +++ b/tests/NetArchTest.TestStructure/Interfaces/IExample.cs @@ -3,4 +3,10 @@ public interface IExample { } + + + + public interface IGenericExample : IExample + { + } } diff --git a/tests/NetArchTest.TestStructure/Interfaces/ImplementsExampleInterface.cs b/tests/NetArchTest.TestStructure/Interfaces/ImplementsExampleInterface.cs index bc5772f..33ccce5 100644 --- a/tests/NetArchTest.TestStructure/Interfaces/ImplementsExampleInterface.cs +++ b/tests/NetArchTest.TestStructure/Interfaces/ImplementsExampleInterface.cs @@ -3,4 +3,9 @@ public class ImplementsExampleInterface : IExample { } + + + public class ImplementsGenericExampleInterface : IGenericExample + { + } }