diff --git a/source/Octopus.Client.Tests/Conventions/ClientConventions.cs b/source/Octopus.Client.Tests/Conventions/ClientConventions.cs index e207c262b..9aaa70482 100644 --- a/source/Octopus.Client.Tests/Conventions/ClientConventions.cs +++ b/source/Octopus.Client.Tests/Conventions/ClientConventions.cs @@ -344,8 +344,113 @@ public void MostSyncRepositoriesThatGetResourcesShouldImplementIPaginate() } #endif + [Test] + public void AsyncRepositoriesThatImplementCreateShouldAlsoImplementModify() + { + var ignored = new [] + { + typeof(IDeploymentRepository).GetTypeInfo(), + typeof(ITaskRepository).GetTypeInfo() + }; + + var createsResources = AsyncRepositoryInterfaceTypes + .Except(ignored) + .Where(t => t.AsType().IsAssignableToGenericType(typeof(ICreate<>))) + .ToArray(); + + var alsoImplementsModify = createsResources + .Where(t => t.GetInterfaces().Any(i => i.IsClosedTypeOf(typeof(IModify<>)))) + .ToArray(); + + var missingModify = createsResources.Except(alsoImplementsModify).ToArray(); + + if (missingModify.Any()) + { + Assert.Fail($"Repositories that implement ICreate should usually implement IModify.{Environment.NewLine}{missingModify.Select(t => t.Name).NewLineSeperate()}"); + } + } + +#if SYNC_CLIENT + [Test] + public void SyncRepositoriesThatImplementCreateShouldAlsoImplementModify() + { + var ignored = new[] + { + typeof(Sync.IDeploymentRepository).GetTypeInfo(), + typeof(Sync.ITaskRepository).GetTypeInfo() + }; + + var createsResources = SyncRepositoryInterfaceTypes + .Except(ignored) + .Where(t => t.AsType().IsAssignableToGenericType(typeof(ICreate<>))) + .ToArray(); + + var alsoImplementsModify = createsResources + .Where(t => t.GetInterfaces().Any(i => i.IsClosedTypeOf(typeof(IModify<>)))) + .ToArray(); + + var missingModify = createsResources.Except(alsoImplementsModify).ToArray(); + + if (missingModify.Any()) + { + Assert.Fail($"Repositories that implement ICreate should usually implement IModify.{Environment.NewLine}{missingModify.Select(t => t.Name).NewLineSeperate()}"); + } + } +#endif + [Test] + public void AsyncRepositoriesThatImplementCreateShouldAlsoImplementDelete() + { + var ignored = new[] + { + typeof(IDeploymentRepository).GetTypeInfo(), + typeof(ITaskRepository).GetTypeInfo() + }; + + var createsResources = AsyncRepositoryInterfaceTypes + .Except(ignored) + .Where(t => t.AsType().IsAssignableToGenericType(typeof(ICreate<>))) + .ToArray(); + + var alsoImplementsDelete = createsResources + .Where(t => t.GetInterfaces().Any(i => i.IsClosedTypeOf(typeof(IDelete<>)))) + .ToArray(); + + var missingDelete = createsResources.Except(alsoImplementsDelete).ToArray(); + + if (missingDelete.Any()) + { + Assert.Fail($"Repositories that implement ICreate should usually implement IDelete.{Environment.NewLine}{missingDelete.Select(t => t.Name).NewLineSeperate()}"); + } + } + +#if SYNC_CLIENT + [Test] + public void SyncRepositoriesThatImplementCreateShouldAlsoImplementDelete() + { + var ignored = new[] + { + typeof(Sync.IDeploymentRepository).GetTypeInfo(), + typeof(Sync.ITaskRepository).GetTypeInfo() + }; + + var createsResources = SyncRepositoryInterfaceTypes + .Except(ignored) + .Where(t => t.AsType().IsAssignableToGenericType(typeof(ICreate<>))) + .ToArray(); + + var alsoImplementsDelete = createsResources + .Where(t => t.GetInterfaces().Any(i => i.IsClosedTypeOf(typeof(IDelete<>)))) + .ToArray(); + + var missingDelete = createsResources.Except(alsoImplementsDelete).ToArray(); + if (missingDelete.Any()) + { + Assert.Fail($"Repositories that implement ICreate should usually implement IDelete.{Environment.NewLine}{missingDelete.Select(t => t.Name).NewLineSeperate()}"); + } + } +#endif #if HAS_BEST_CONVENTIONAL diff --git a/source/Octopus.Client.Tests/Conventions/RepositorySymetryConventions.cs b/source/Octopus.Client.Tests/Conventions/RepositorySymmetryConventions.cs similarity index 71% rename from source/Octopus.Client.Tests/Conventions/RepositorySymetryConventions.cs rename to source/Octopus.Client.Tests/Conventions/RepositorySymmetryConventions.cs index 073616c9d..b8e47eb02 100644 --- a/source/Octopus.Client.Tests/Conventions/RepositorySymetryConventions.cs +++ b/source/Octopus.Client.Tests/Conventions/RepositorySymmetryConventions.cs @@ -9,7 +9,7 @@ namespace Octopus.Client.Tests.Conventions { - public class RepositorySymetryConventions + public class RepositorySymmetryConventions { [Test] public void IOctopusAsyncRepositoryExposesTheSamePropertiesAsIOctopusRepository() @@ -102,6 +102,45 @@ public void AllSyncRepositoriesShouldHaveEquivalentSignaturesToTheAsyncRepositor } + [TestCaseSource(nameof(AsyncRepositories))] + public void AllAsyncRepositoriesShouldImplementEquivalentInterfacesToTheSyncRepositories(Type asyncRepository) + { + var syncRepository = typeof(IOctopusAsyncRepository).Assembly + .GetExportedTypes() + .FirstOrDefault(t => t.Name == asyncRepository.Name && !t.Namespace.EndsWith("Async")); + + if (syncRepository == null) + Assert.Fail("Sync repository not found"); + + var asyncInterfaces = asyncRepository.GetInterfaces().Select(i => i.ToString().Replace(i.Namespace, string.Empty).TrimStart('.')).ToArray(); + var syncInterfaces = syncRepository.GetInterfaces().Select(i => i.ToString().Replace(i.Namespace, string.Empty).TrimStart('.')).ToArray(); + + var missing = syncInterfaces.Except(asyncInterfaces).ToArray(); + + if (missing.Any()) + Assert.Fail($"The following interfaces are implemented on the sync {syncRepository.Name} but not on the async one:\r\n{missing.NewLineSeperate()}"); + } + + [TestCaseSource(nameof(SyncRepositories))] + public void AllSyncRepositoriesShouldImplementEquivalentInterfacesToTheAsyncRepositories(Type syncRepository) + { + var asyncRepository = typeof(IOctopusAsyncRepository).Assembly + .GetExportedTypes() + .FirstOrDefault(t => t.Name == syncRepository.Name && t.Namespace.EndsWith("Async")); + + if (asyncRepository == null) + Assert.Fail("Async repository not found"); + + var asyncInterfaces = asyncRepository.GetInterfaces().Select(i => i.ToString().Replace(i.Namespace, string.Empty).TrimStart('.')).ToArray(); + var syncInterfaces = syncRepository.GetInterfaces().Select(i => i.ToString().Replace(i.Namespace, string.Empty).TrimStart('.')).ToArray(); + + var missing = asyncInterfaces.Except(syncInterfaces).ToArray(); + + if (missing.Any()) + Assert.Fail($"The following interfaces are implemented on the async {asyncRepository.Name} but not on the sync one:\r\n{missing.NewLineSeperate()}"); + + } + bool IsEquivalentReturnType(Type asyncType, Type syncType) { if (asyncType.Name == syncType.Name) diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt index 70098d786..3008bf5ca 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETCore.approved.txt @@ -3927,6 +3927,7 @@ Octopus.Client.Repositories.Async Octopus.Client.Repositories.Async.IGet Octopus.Client.Repositories.Async.ICreate Octopus.Client.Repositories.Async.IModify + Octopus.Client.Repositories.Async.IDelete { } interface IVariableSetRepository diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt index 329fc104d..0fc3b1e4d 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt @@ -4332,6 +4332,7 @@ Octopus.Client.Repositories Octopus.Client.Repositories.IGet Octopus.Client.Repositories.ICreate Octopus.Client.Repositories.IModify + Octopus.Client.Repositories.IDelete { } interface IVariableSetRepository @@ -4778,6 +4779,7 @@ Octopus.Client.Repositories.Async Octopus.Client.Repositories.Async.IGet Octopus.Client.Repositories.Async.ICreate Octopus.Client.Repositories.Async.IModify + Octopus.Client.Repositories.Async.IDelete { } interface IVariableSetRepository diff --git a/source/Octopus.Client/Repositories/Async/UserRolesRepository.cs b/source/Octopus.Client/Repositories/Async/UserRolesRepository.cs index 81dcea167..7c6fad8c2 100644 --- a/source/Octopus.Client/Repositories/Async/UserRolesRepository.cs +++ b/source/Octopus.Client/Repositories/Async/UserRolesRepository.cs @@ -3,7 +3,7 @@ namespace Octopus.Client.Repositories.Async { - public interface IUserRolesRepository : IFindByName, IGet, ICreate, IModify + public interface IUserRolesRepository : IFindByName, IGet, ICreate, IModify, IDelete { } diff --git a/source/Octopus.Client/Repositories/UserRolesRepository.cs b/source/Octopus.Client/Repositories/UserRolesRepository.cs index 49f1cb8b2..19f760d4a 100644 --- a/source/Octopus.Client/Repositories/UserRolesRepository.cs +++ b/source/Octopus.Client/Repositories/UserRolesRepository.cs @@ -3,7 +3,7 @@ namespace Octopus.Client.Repositories { - public interface IUserRolesRepository : IFindByName, IGet, ICreate, IModify + public interface IUserRolesRepository : IFindByName, IGet, ICreate, IModify, IDelete { }