diff --git a/OrchardCore.sln b/OrchardCore.sln index 775951b05c4..700338d4282 100644 --- a/OrchardCore.sln +++ b/OrchardCore.sln @@ -480,7 +480,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Media.AmazonS3" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.ArchiveLater", "src\OrchardCore.Modules\OrchardCore.ArchiveLater\OrchardCore.ArchiveLater.csproj", "{190C4BEB-C506-4F7F-BDCA-93F3C1C221BC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Features.Core", "src\OrchardCore\OrchardCore.Features.Core\OrchardCore.Features.Core.csproj", "{122EC0DA-A593-4038-BD21-2D4A7061F348}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Features.Core", "src\OrchardCore\OrchardCore.Features.Core\OrchardCore.Features.Core.csproj", "{122EC0DA-A593-4038-BD21-2D4A7061F348}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Search", "src\OrchardCore.Modules\OrchardCore.Search\OrchardCore.Search.csproj", "{7BDF280B-70B7-4AFC-A6F7-B5759DCA2A2C}" EndProject @@ -489,6 +489,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Notifications.Core", "src\OrchardCore\OrchardCore.Notifications.Core\OrchardCore.Notifications.Core.csproj", "{2A6E7DF9-E417-42F8-94F7-0060E252E4D6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Notifications", "src\OrchardCore.Modules\OrchardCore.Notifications\OrchardCore.Notifications.csproj", "{19594A96-A033-4820-820B-C6186D00D507}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Testing", "src\OrchardCore\OrchardCore.Testing\OrchardCore.Testing.csproj", "{C3DA562E-7B0B-455E-87FF-FF3E1D31A400}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Testing.Tests", "test\OrchardCore.Testing.Tests\OrchardCore.Testing.Tests.csproj", "{841FF342-25ED-487E-8EA4-65C015545F3E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1278,10 +1281,6 @@ Global {FF1C550C-6D30-499A-AF11-68DE7C8B6869}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF1C550C-6D30-499A-AF11-68DE7C8B6869}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF1C550C-6D30-499A-AF11-68DE7C8B6869}.Release|Any CPU.Build.0 = Release|Any CPU - {A6563050-EE6D-4E7C-81AA-C383DB3ED124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6563050-EE6D-4E7C-81AA-C383DB3ED124}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6563050-EE6D-4E7C-81AA-C383DB3ED124}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6563050-EE6D-4E7C-81AA-C383DB3ED124}.Release|Any CPU.Build.0 = Release|Any CPU {190C4BEB-C506-4F7F-BDCA-93F3C1C221BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {190C4BEB-C506-4F7F-BDCA-93F3C1C221BC}.Debug|Any CPU.Build.0 = Debug|Any CPU {190C4BEB-C506-4F7F-BDCA-93F3C1C221BC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1306,6 +1305,14 @@ Global {19594A96-A033-4820-820B-C6186D00D507}.Debug|Any CPU.Build.0 = Debug|Any CPU {19594A96-A033-4820-820B-C6186D00D507}.Release|Any CPU.ActiveCfg = Release|Any CPU {19594A96-A033-4820-820B-C6186D00D507}.Release|Any CPU.Build.0 = Release|Any CPU + {C3DA562E-7B0B-455E-87FF-FF3E1D31A400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3DA562E-7B0B-455E-87FF-FF3E1D31A400}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3DA562E-7B0B-455E-87FF-FF3E1D31A400}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3DA562E-7B0B-455E-87FF-FF3E1D31A400}.Release|Any CPU.Build.0 = Release|Any CPU + {841FF342-25ED-487E-8EA4-65C015545F3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {841FF342-25ED-487E-8EA4-65C015545F3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {841FF342-25ED-487E-8EA4-65C015545F3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {841FF342-25ED-487E-8EA4-65C015545F3E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1533,6 +1540,8 @@ Global {307AA9CB-D62E-4E30-B715-53E3BF535D94} = {F23AC6C2-DE44-4699-999D-3C478EF3D691} {2A6E7DF9-E417-42F8-94F7-0060E252E4D6} = {F23AC6C2-DE44-4699-999D-3C478EF3D691} {19594A96-A033-4820-820B-C6186D00D507} = {A066395F-6F73-45DC-B5A6-B4E306110DCE} + {C3DA562E-7B0B-455E-87FF-FF3E1D31A400} = {F23AC6C2-DE44-4699-999D-3C478EF3D691} + {841FF342-25ED-487E-8EA4-65C015545F3E} = {B8D16C60-99B4-43D5-A3AD-4CD89AF39B25} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46A1D25A-78D1-4476-9CBF-25B75E296341} diff --git a/src/OrchardCore.Cms.Web/OrchardCore.Cms.Web.csproj b/src/OrchardCore.Cms.Web/OrchardCore.Cms.Web.csproj index 62a73532bfc..821099c13dd 100644 --- a/src/OrchardCore.Cms.Web/OrchardCore.Cms.Web.csproj +++ b/src/OrchardCore.Cms.Web/OrchardCore.Cms.Web.csproj @@ -14,6 +14,7 @@ enable enable + diff --git a/src/OrchardCore/OrchardCore.Testing/Apis/Extensions/SiteContextExtensions.cs b/src/OrchardCore/OrchardCore.Testing/Apis/Extensions/SiteContextExtensions.cs new file mode 100644 index 00000000000..205231ee0e4 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Apis/Extensions/SiteContextExtensions.cs @@ -0,0 +1,56 @@ +using System; +using OrchardCore.Testing.Apis.Security; + +namespace OrchardCore.Testing.Apis +{ + public static class SiteContextExtensions + { + public static ISiteContext WithDatabaseProvider(this ISiteContext siteContext, string databaseProvider) + { + if (String.IsNullOrEmpty(databaseProvider)) + { + throw new ArgumentException($"'{nameof(databaseProvider)}' cannot be null or empty.", nameof(databaseProvider)); + } + + siteContext.Options.DatabaseProvider = databaseProvider; + + return siteContext; + } + + public static ISiteContext WithConnectionString(this ISiteContext siteContext, string connectionString) + { + if (String.IsNullOrEmpty(connectionString)) + { + throw new ArgumentException($"'{nameof(connectionString)}' cannot be null or empty.", nameof(connectionString)); + } + + siteContext.Options.ConnectionString = connectionString; + + return siteContext; + } + + public static ISiteContext WithPermissionsContext(this ISiteContext siteContext, PermissionsContext permissionsContext) + { + if (permissionsContext is null) + { + throw new ArgumentNullException(nameof(permissionsContext)); + } + + siteContext.Options.PermissionsContext = permissionsContext; + + return siteContext; + } + + public static ISiteContext WithRecipe(this ISiteContext siteContext, string recipeName) + { + if (String.IsNullOrEmpty(recipeName)) + { + throw new ArgumentException($"'{nameof(recipeName)}' cannot be null or empty.", nameof(recipeName)); + } + + siteContext.Options.RecipeName = recipeName; + + return siteContext; + } + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Apis/ISiteContext.cs b/src/OrchardCore/OrchardCore.Testing/Apis/ISiteContext.cs new file mode 100644 index 00000000000..0681b1406ca --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Apis/ISiteContext.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using OrchardCore.Apis.GraphQL.Client; +using OrchardCore.Environment.Shell; + +namespace OrchardCore.Testing.Apis +{ + public interface ISiteContext : IDisposable + { + static IShellHost ShellHost { get; } + + static IShellSettingsManager ShellSettingsManager { get; } + + static HttpClient DefaultTenantClient { get; } + + SiteContextOptions Options { init; get; } + + HttpClient Client { get; } + + string TenantName { get; } + + OrchardGraphQLClient GraphQLClient { get; } + + Task InitializeAsync(); + + Task RunRecipeAsync(string recipeName, string recipePath); + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Apis/ISiteContextOfT.cs b/src/OrchardCore/OrchardCore.Testing/Apis/ISiteContextOfT.cs new file mode 100644 index 00000000000..7b2f60caddc --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Apis/ISiteContextOfT.cs @@ -0,0 +1,9 @@ +using OrchardCore.Testing.Infrastructure; + +namespace OrchardCore.Testing.Apis +{ + public interface ISiteContext : ISiteContext where TSiteStartup : class + { + static OrchardCoreTestFixture Site { get; } + } +} diff --git a/test/OrchardCore.Tests/Apis/Context/AuthenticationContext.cs b/src/OrchardCore/OrchardCore.Testing/Apis/Security/PermissionContextAuthorizationHandler.cs similarity index 82% rename from test/OrchardCore.Tests/Apis/Context/AuthenticationContext.cs rename to src/OrchardCore/OrchardCore.Testing/Apis/Security/PermissionContextAuthorizationHandler.cs index ff439ccb86c..f75b88ef187 100644 --- a/test/OrchardCore.Tests/Apis/Context/AuthenticationContext.cs +++ b/src/OrchardCore/OrchardCore.Testing/Apis/Security/PermissionContextAuthorizationHandler.cs @@ -1,9 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using OrchardCore.Security; using OrchardCore.Security.Permissions; -namespace OrchardCore.Tests.Apis.Context +namespace OrchardCore.Testing.Apis.Security { - internal class PermissionContextAuthorizationHandler : AuthorizationHandler + public class PermissionContextAuthorizationHandler : AuthorizationHandler { private readonly PermissionsContext _permissionsContext; @@ -26,7 +32,7 @@ public PermissionContextAuthorizationHandler(IHttpContextAccessor httpContextAcc } } - // Used for static graphql test; passes a permissionsContext directly + // Used for static graphql test; passes a permissions Context directly public PermissionContextAuthorizationHandler(PermissionsContext permissionsContext) { _permissionsContext = permissionsContext; @@ -88,20 +94,4 @@ private void GetGrantingNamesInternal(Permission permission, HashSet sta } } } - - public class PermissionsContext - { - public IEnumerable AuthorizedPermissions { get; set; } = Enumerable.Empty(); - - public bool UsePermissionsContext { get; set; } = false; - } - - internal class StubIdentity : IIdentity - { - public string AuthenticationType => "TEST TEST"; - - public bool IsAuthenticated => true; - - public string Name => "Mr Robot"; - } } diff --git a/src/OrchardCore/OrchardCore.Testing/Apis/Security/PermissionsContext.cs b/src/OrchardCore/OrchardCore.Testing/Apis/Security/PermissionsContext.cs new file mode 100644 index 00000000000..717f023ef30 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Apis/Security/PermissionsContext.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +using OrchardCore.Security.Permissions; + +namespace OrchardCore.Testing.Apis.Security +{ + public class PermissionsContext + { + public PermissionsContext() + { + AuthorizedPermissions = Enumerable.Empty(); + UsePermissionsContext = false; + } + public IEnumerable AuthorizedPermissions { get; set; } + + public bool UsePermissionsContext { get; set; } + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Apis/SiteContextBase.cs b/src/OrchardCore/OrchardCore.Testing/Apis/SiteContextBase.cs new file mode 100644 index 00000000000..045f4635227 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Apis/SiteContextBase.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Apis.GraphQL.Client; +using OrchardCore.Environment.Shell; +using OrchardCore.Recipes.Services; +using OrchardCore.Tenants.ViewModels; +using OrchardCore.Testing.Data; +using OrchardCore.Testing.Infrastructure; + +namespace OrchardCore.Testing.Apis +{ + public abstract class SiteContextBase : ISiteContext where TSiteStartup : class + { + static SiteContextBase() + { + Site = new OrchardCoreTestFixture(); + ShellHost = Site.Services.GetRequiredService(); + ShellSettingsManager = Site.Services.GetRequiredService(); + DefaultTenantClient = Site.CreateDefaultClient(); + } + + public SiteContextBase() + { + Options = new SiteContextOptions(); + } + + public static OrchardCoreTestFixture Site { get; } + + public static IShellHost ShellHost { get; private set; } + + public static IShellSettingsManager ShellSettingsManager { get; private set; } + + public static HttpClient DefaultTenantClient { get; } + + public SiteContextOptions Options { init; get; } + + public HttpClient Client { get; private set; } + + public string TenantName { get; private set; } + + public OrchardGraphQLClient GraphQLClient { get; protected set; } + + public virtual async Task InitializeAsync() + { + var tenantName = Guid.NewGuid().ToString("n"); + + if (String.IsNullOrEmpty(Options.TablePrefix)) + { + Options.TablePrefix = await new TablePrefixGenerator().GeneratePrefixAsync(); + } + + var createResult = await CreateSiteAsync(tenantName); + + var content = await createResult.Content.ReadAsStringAsync(); + + await SetupSiteAsync(tenantName); + + lock (Site) + { + var url = new Uri(content.Trim('"')); + url = new Uri(url.Scheme + "://" + url.Authority + url.LocalPath + "/"); + + Client = Site.CreateDefaultClient(url); + + TenantName = tenantName; + } + + if (Options.PermissionsContext != null) + { + var permissionContextKey = Guid.NewGuid().ToString(); + + SiteContextOptions.PermissionsContexts.TryAdd(permissionContextKey, Options.PermissionsContext); + + Client.DefaultRequestHeaders.Add("PermissionsContext", permissionContextKey); + } + + GraphQLClient = new OrchardGraphQLClient(Client); + } + + public async Task RunRecipeAsync(string recipeName, string recipePath) + { + var shellScope = await ShellHost.GetScopeAsync(TenantName); + + await shellScope.UsingServiceScopeAsync(async scope => + { + var shellFeaturesManager = scope.ServiceProvider.GetRequiredService(); + var recipeHarvesters = scope.ServiceProvider.GetRequiredService>(); + var recipeExecutor = scope.ServiceProvider.GetRequiredService(); + + var recipeCollections = await Task.WhenAll(recipeHarvesters + .Select(recipe => recipe.HarvestRecipesAsync())); + + var recipe = recipeCollections + .SelectMany(r => r) + .FirstOrDefault(d => d.RecipeFileInfo.Name == recipeName && d.BasePath == recipePath); + + var executionId = Guid.NewGuid().ToString("n"); + + await recipeExecutor.ExecuteAsync(executionId, recipe, new Dictionary(), CancellationToken.None); + }); + } + + public void Dispose() => Client?.Dispose(); + + private async Task CreateSiteAsync(string tenantName) + { + var createModel = new CreateApiViewModel + { + DatabaseProvider = Options.DatabaseProvider, + TablePrefix = Options.TablePrefix, + ConnectionString = Options.ConnectionString, + RecipeName = Options.RecipeName, + Name = tenantName, + RequestUrlPrefix = tenantName + }; + + var result = await DefaultTenantClient.PostAsJsonAsync("api/tenants/create", createModel); + + result.EnsureSuccessStatusCode(); + + return result; + } + + private async Task SetupSiteAsync(string tenantName) + { + var setupModel = new SetupApiViewModel + { + SiteName = "Orchard Core Site", + DatabaseProvider = Options.DatabaseProvider, + TablePrefix = Options.TablePrefix, + ConnectionString = Options.ConnectionString, + RecipeName = Options.RecipeName, + UserName = "admin", + Password = "P@ssw0rd", + Name = tenantName, + Email = "admin@OrchardCore.net" + }; + + var result = await DefaultTenantClient.PostAsJsonAsync("api/tenants/setup", setupModel); + + result.EnsureSuccessStatusCode(); + } + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Apis/SiteContextOptions.cs b/src/OrchardCore/OrchardCore.Testing/Apis/SiteContextOptions.cs new file mode 100644 index 00000000000..1644031b9ac --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Apis/SiteContextOptions.cs @@ -0,0 +1,24 @@ +using System.Collections.Concurrent; +using OrchardCore.Testing.Apis.Security; + +namespace OrchardCore.Testing.Apis; + +public class SiteContextOptions +{ + static SiteContextOptions() + { + PermissionsContexts = new(); + } + + public static ConcurrentDictionary PermissionsContexts { get; set; } + + public string RecipeName { get; set; } = "Blog"; + + public string DatabaseProvider { get; set; } = "Sqlite"; + + public string ConnectionString { get; set; } + + public string TablePrefix { get; set; } + + public PermissionsContext PermissionsContext { get; set; } +} diff --git a/test/OrchardCore.Tests/Apis/Context/TablePrefixGenerator.cs b/src/OrchardCore/OrchardCore.Testing/Data/TablePrefixGenerator.cs similarity index 62% rename from test/OrchardCore.Tests/Apis/Context/TablePrefixGenerator.cs rename to src/OrchardCore/OrchardCore.Testing/Data/TablePrefixGenerator.cs index 18426d1f32e..8a3ffde0180 100644 --- a/test/OrchardCore.Tests/Apis/Context/TablePrefixGenerator.cs +++ b/src/OrchardCore/OrchardCore.Testing/Data/TablePrefixGenerator.cs @@ -1,4 +1,8 @@ -namespace OrchardCore.Tests.Apis.Context +using System; +using System.Text; +using System.Threading.Tasks; + +namespace OrchardCore.Testing.Data { /// /// This is an internal table prefix generator which uses a timestamp to generate a table prefix @@ -9,9 +13,9 @@ namespace OrchardCore.Tests.Apis.Context /// internal class TablePrefixGenerator { - private static readonly char[] CharList = "abcdefghijklmnopqrstuvwxyz".ToCharArray(); + private static readonly char[] _charList = "abcdefghijklmnopqrstuvwxyz".ToCharArray(); - internal async Task GeneratePrefixAsync() + public async Task GeneratePrefixAsync() { await Task.Delay(1); var ticks = DateTime.Now.Ticks; @@ -19,8 +23,9 @@ internal async Task GeneratePrefixAsync() var result = new StringBuilder(); while (ticks != 0) { - result.Append(CharList[ticks % CharList.Length]); - ticks /= CharList.Length; + result.Append(_charList[ticks % _charList.Length]); + + ticks /= _charList.Length; } return result.ToString(); diff --git a/test/OrchardCore.Tests/Apis/Context/Extensions/HttpContentExtensions.cs b/src/OrchardCore/OrchardCore.Testing/Extensions/HttpContentExtensions.cs similarity index 87% rename from test/OrchardCore.Tests/Apis/Context/Extensions/HttpContentExtensions.cs rename to src/OrchardCore/OrchardCore.Testing/Extensions/HttpContentExtensions.cs index 1cbfc1263c3..673432f1ad3 100644 --- a/test/OrchardCore.Tests/Apis/Context/Extensions/HttpContentExtensions.cs +++ b/src/OrchardCore/OrchardCore.Testing/Extensions/HttpContentExtensions.cs @@ -1,6 +1,11 @@ -namespace OrchardCore.Tests.Apis.Context +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace System.Net.Http { - internal static class HttpContentExtensions + public static class HttpContentExtensions { public static async Task ReadAsAsync(this HttpContent content, JsonConverter jsonConverter) { diff --git a/src/OrchardCore/OrchardCore.Testing/Extensions/HttpRequestExtensions.cs b/src/OrchardCore/OrchardCore.Testing/Extensions/HttpRequestExtensions.cs new file mode 100644 index 00000000000..92da57e30fb --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Extensions/HttpRequestExtensions.cs @@ -0,0 +1,109 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace System.Net.Http +{ + /// + /// The http request extensions. + /// + public static class HttpRequestExtensions + { + private readonly static JsonSerializerSettings JsonSettings = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore + }; + + public static Task PatchAsJsonAsync( + this HttpClient client, + string requestUri, + T value, + JsonSerializerSettings settings = null) + { + var content = new StringContent( + JsonConvert.SerializeObject(value, settings ?? JsonSettings), + Encoding.UTF8, + "application/json"); + + return PatchAsync(client, requestUri, content); + } + + public static Task PatchAsync(this HttpClient client, string requestUri, HttpContent content) + { + var request = new HttpRequestMessage + { + Method = new HttpMethod("PATCH"), + RequestUri = new Uri(client.BaseAddress + requestUri), + Content = content + }; + + request.Headers.ExpectContinue = false; + + return client.SendAsync(request); + } + + public static Task PutAsJsonAsync( + this HttpClient client, + string requestUri, + T value, + JsonSerializerSettings settings = null) + { + var content = new StringContent( + JsonConvert.SerializeObject(value, settings ?? JsonSettings), + Encoding.UTF8, + "application/json"); + + return client.PutAsync(requestUri, content); + } + + public static Task PostAsJsonAsync( + this HttpClient client, + string requestUri, + T value, + JsonSerializerSettings settings = null) + { + var content = new StringContent( + JsonConvert.SerializeObject(value, settings ?? JsonSettings), + Encoding.UTF8, + "application/json"); + + var request = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = content + }; + + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + return client.SendAsync(request); + } + + public static Task PostJsonAsync(this HttpClient client, string requestUri, string json) + { + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var request = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = content + }; + + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + return client.SendAsync(request); + } + + public static Task PostJsonApiAsync(this HttpClient client, string requestUri, string json) + { + var content = new StringContent(json, Encoding.UTF8, "application/vnd.api+json"); + + var request = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = content + }; + + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json")); + + return client.SendAsync(request); + } + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Fakes/FakePermissionHandler.cs b/src/OrchardCore/OrchardCore.Testing/Fakes/FakePermissionHandler.cs new file mode 100644 index 00000000000..256fdb29ffc --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Fakes/FakePermissionHandler.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using OrchardCore.Security; + +namespace OrchardCore.Testing.Fakes; + +internal class FakePermissionHandler : AuthorizationHandler +{ + private readonly HashSet _permissionNames; + + public FakePermissionHandler(string[] permissionNames) + { + _permissionNames = new HashSet(permissionNames, StringComparer.OrdinalIgnoreCase); + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) + { + if (_permissionNames.Contains(requirement.Permission.Name)) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Infrastructure/OrchardCoreTestFixture.cs b/src/OrchardCore/OrchardCore.Testing/Infrastructure/OrchardCoreTestFixture.cs new file mode 100644 index 00000000000..1c7a0aac5fc --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Infrastructure/OrchardCoreTestFixture.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; + +namespace OrchardCore.Testing.Infrastructure; + +public class OrchardCoreTestFixture : WebApplicationFactory where TStartup : class +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + var shellsApplicationDataPath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data"); + + if (Directory.Exists(shellsApplicationDataPath)) + { + Directory.Delete(shellsApplicationDataPath, true); + } + + builder.UseContentRoot(Directory.GetCurrentDirectory()); + } + + protected override IWebHostBuilder CreateWebHostBuilder() + => WebHostBuilderFactory.CreateFromAssemblyEntryPoint(typeof(TStartup).Assembly, Array.Empty()); + + protected override IHostBuilder CreateHostBuilder() + => Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); +} diff --git a/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreEmailMocks.cs b/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreEmailMocks.cs new file mode 100644 index 00000000000..7d16ab2e519 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreEmailMocks.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using OrchardCore.Email; +using OrchardCore.Email.Services; + +namespace OrchardCore.Testing.Mocks; + +public static partial class OrchardCoreMock +{ + public static ISmtpService CreateSmtpService(SmtpSettings settings) + { + var smtpSettings = new Mock>(); + smtpSettings + .Setup(o => o.Value) + .Returns(settings); + + var logger = new Mock>(); + var localizer = new Mock>(); + + return new SmtpService(smtpSettings.Object, logger.Object, localizer.Object); + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreSecurityMocks.cs b/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreSecurityMocks.cs new file mode 100644 index 00000000000..03084233ec2 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreSecurityMocks.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Moq; +using OrchardCore.Users; + +namespace OrchardCore.Testing.Mocks; + +public static partial class OrchardCoreMock +{ + public static Mock> CreateSignInManager(UserManager userManager = null) where TUser : class, IUser + { + var context = new Mock(); + var manager = userManager ?? CreateUserManager().Object; + + var signInManager = new Mock>( + manager, + new HttpContextAccessor { HttpContext = context.Object }, + Mock.Of>(), + null, + null, + null, + null) + { + CallBase = true + }; + + signInManager + .Setup(x => x.SignInAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + return signInManager; + } + + public static Mock> CreateUserManager() where TUser : class + { + var userStore = new Mock>(); + var identityOptions = new IdentityOptions(); + identityOptions.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+"; + identityOptions.User.RequireUniqueEmail = true; + + var userManagerMock = new Mock>( + userStore.Object, + Options.Create(identityOptions), + null, + null, + null, + null, + null, + null, + null); + + userManagerMock.Object.UserValidators.Add(new UserValidator(new IdentityErrorDescriber())); + + userManagerMock.Object.PasswordValidators.Add(new PasswordValidator()); + + return userManagerMock; + } + + public static Mock> CreateRoleManager() where TRole : class + { + var roleStoreMock = new Mock>().Object; + var rolesValidators = new List> + { + new RoleValidator() + }; + + var roleManagerMock = new Mock>( + roleStoreMock, + rolesValidators, + new UpperInvariantLookupNormalizer(), + new IdentityErrorDescriber(), + null); + + return roleManagerMock; + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreWorkflowMocks.cs b/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreWorkflowMocks.cs new file mode 100644 index 00000000000..0b5aecae4a0 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Mocks/OrchardCoreWorkflowMocks.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Moq; +using OrchardCore.Locking.Distributed; +using OrchardCore.Modules; +using OrchardCore.Scripting; +using OrchardCore.Scripting.JavaScript; +using OrchardCore.Workflows.Activities; +using OrchardCore.Workflows.Evaluators; +using OrchardCore.Workflows.Models; +using OrchardCore.Workflows.Services; + +namespace OrchardCore.Testing.Mocks; + +public static partial class OrchardCoreMock +{ + public static WorkflowManager CreateWorkflowManager( + IServiceProvider serviceProvider, + IEnumerable activities, + WorkflowType workflowType) + { + var workflowValueSerializers = new Resolver>(serviceProvider); + var activityLibrary = new Mock(); + var workflowTypeStore = new Mock(); + var workflowStore = new Mock(); + var workflowIdGenerator = new Mock(); + workflowIdGenerator + .Setup(x => x.GenerateUniqueId(It.IsAny())) + .Returns(IdGenerator.GenerateId()); + + var distributedLock = new Mock(); + var workflowManagerLogger = new Mock>(); + var workflowContextLogger = new Mock>(); + var missingActivityLogger = new Mock>(); + var missingActivityLocalizer = new Mock>(); + var clock = new Mock(); + var workflowManager = new WorkflowManager( + activityLibrary.Object, + workflowTypeStore.Object, + workflowStore.Object, + workflowIdGenerator.Object, + workflowValueSerializers, + distributedLock.Object, + workflowManagerLogger.Object, + missingActivityLogger.Object, + missingActivityLocalizer.Object, + clock.Object); + + foreach (var activity in activities) + { + activityLibrary.Setup(x => x.InstantiateActivity(activity.Name)).Returns(activity); + } + + workflowTypeStore.Setup(x => x.GetAsync(workflowType.Id)).Returns(Task.FromResult(workflowType)); + + return workflowManager; + } + + public static IWorkflowScriptEvaluator CreateWorkflowScriptEvaluator(IServiceProvider serviceProvider) + { + var memoryCache = new MemoryCache(new MemoryCacheOptions()); + var javaScriptEngine = new JavaScriptEngine(memoryCache); + var workflowContextHandlers = new Resolver>(serviceProvider); + var globalMethodProviders = new IGlobalMethodProvider[0]; + var scriptingManager = new DefaultScriptingManager(new[] { javaScriptEngine }, globalMethodProviders); + + return new JavaScriptWorkflowScriptEvaluator( + scriptingManager, + workflowContextHandlers.Resolve(), + new Mock>().Object + ); + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/ModuleNamesProvider.cs b/src/OrchardCore/OrchardCore.Testing/ModuleNamesProvider.cs new file mode 100644 index 00000000000..0f3a8b7380c --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/ModuleNamesProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using OrchardCore.Modules; +using OrchardCore.Modules.Manifest; + +namespace OrchardCore.Testing +{ + public class ModuleNamesProvider : IModuleNamesProvider + { + private readonly IEnumerable _moduleNames; + + public ModuleNamesProvider(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + _moduleNames = Assembly.Load(new AssemblyName(assembly.GetName().Name)) + .GetCustomAttributes() + .Select(m => m.Name); + } + + public IEnumerable GetModuleNames() => _moduleNames; + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/OrchardCore.Testing.csproj b/src/OrchardCore/OrchardCore.Testing/OrchardCore.Testing.csproj new file mode 100644 index 00000000000..308a2ff487d --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/OrchardCore.Testing.csproj @@ -0,0 +1,42 @@ + + + + + OrchardCore Testing + + $(OCFrameworkDescription) + + Implementation of OrchardCore testing APIs + + $(PackageTags) OrchardCoreFramework + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OrchardCore/OrchardCore.Testing/Recipes/IRecipeFileProvider.cs b/src/OrchardCore/OrchardCore.Testing/Recipes/IRecipeFileProvider.cs new file mode 100644 index 00000000000..a44ade34d1d --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Recipes/IRecipeFileProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Microsoft.Extensions.FileProviders; + +namespace OrchardCore.Testing.Recipes; + +public interface IRecipeFileProvider +{ + IFileProvider FileProvider { get; } + + IEnumerable GetRecipes(); +} diff --git a/src/OrchardCore/OrchardCore.Testing/Security/Extensions/AuthorizationHandlerContextExtensions.cs b/src/OrchardCore/OrchardCore.Testing/Security/Extensions/AuthorizationHandlerContextExtensions.cs new file mode 100644 index 00000000000..6331556a7c5 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Security/Extensions/AuthorizationHandlerContextExtensions.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using OrchardCore.Testing.Fakes; + +namespace Microsoft.AspNetCore.Authorization; + +public static class AuthorizationHandlerContextExtensions +{ + public static async Task SuccessAsync(this AuthorizationHandlerContext context, params string[] permissionNames) + { + var handler = new FakePermissionHandler(permissionNames); + + await handler.HandleAsync(context); + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Security/PermissionHandlerHelper.cs b/src/OrchardCore/OrchardCore.Testing/Security/PermissionHandlerHelper.cs new file mode 100644 index 00000000000..c0b4a53b41c --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Security/PermissionHandlerHelper.cs @@ -0,0 +1,30 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using OrchardCore.Security.Permissions; +using OrchardCore.Security; + +namespace OrchardCore.Testing.Security; + +public static class PermissionHandlerHelper +{ + public static AuthorizationHandlerContext CreateTestAuthorizationHandlerContext(Permission required, string[] allowed = null, bool authenticated = false) + { + var identity = authenticated + ? new ClaimsIdentity("Testing") + : new ClaimsIdentity(); + + if (allowed != null) + { + foreach (var permissionName in allowed) + { + var permission = new Permission(permissionName); + identity.AddClaim(permission); + } + + } + + var principal = new ClaimsPrincipal(identity); + + return new AuthorizationHandlerContext(new[] { new PermissionRequirement(required) }, principal, null); + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/ShapeBindingsDictionary.cs b/src/OrchardCore/OrchardCore.Testing/ShapeBindingsDictionary.cs new file mode 100644 index 00000000000..23468e20dba --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/ShapeBindingsDictionary.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using OrchardCore.DisplayManagement.Descriptors; + +namespace OrchardCore.Testing; + +public class ShapeBindingsDictionary : Dictionary +{ + public ShapeBindingsDictionary() + : base(StringComparer.OrdinalIgnoreCase) + { + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/AutorouteEntriesStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/AutorouteEntriesStub.cs new file mode 100644 index 00000000000..9f5ee7f727a --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/AutorouteEntriesStub.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using OrchardCore.Autoroute.Core.Services; +using OrchardCore.ContentManagement.Routing; + +namespace OrchardCore.Testing.Stubs; + +public class AutorouteEntriesStub : AutorouteEntries, IAutorouteEntriesStub +{ + public AutorouteEntriesStub() : base(null) + { + } + + public new void AddEntries(IEnumerable entries) => base.AddEntries(entries); + + public new void RemoveEntries(IEnumerable entries) => base.RemoveEntries(entries); + + protected override Task InitializeEntriesAsync() => Task.CompletedTask; +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/FileVersionProviderStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/FileVersionProviderStub.cs new file mode 100644 index 00000000000..72098c31e61 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/FileVersionProviderStub.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace OrchardCore.Testing.Stubs; + +public class FileVersionProviderStub : IFileVersionProvider +{ + public string AddFileVersionToPath(PathString requestPathBase, string path) => path; +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/HostingEnvironmentStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/HostingEnvironmentStub.cs new file mode 100644 index 00000000000..7d284543104 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/HostingEnvironmentStub.cs @@ -0,0 +1,40 @@ +using System.IO; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; + +namespace OrchardCore.Testing.Stubs; + +public class HostingEnvironmentStub : IHostEnvironment +{ + private string _rootPath; + private IFileProvider _contentRootFileProvider; + + public HostingEnvironmentStub() + { + ApplicationName = GetType().Assembly.GetName().Name; + } + + public string EnvironmentName { get; set; } = "Testing"; + + public string ApplicationName { get; set; } + + public string WebRootPath { get; set; } + + public IFileProvider WebRootFileProvider { get; set; } + + public string ContentRootPath + { + get => _rootPath ?? Directory.GetCurrentDirectory(); + set + { + _contentRootFileProvider = new PhysicalFileProvider(value); + _rootPath = value; + } + } + + public IFileProvider ContentRootFileProvider + { + get => _contentRootFileProvider; + set => _contentRootFileProvider = value; + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/IAutorouteEntriesStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/IAutorouteEntriesStub.cs new file mode 100644 index 00000000000..eb73c24f4dc --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/IAutorouteEntriesStub.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using OrchardCore.ContentManagement.Routing; + +namespace OrchardCore.Testing.Stubs; + +public interface IAutorouteEntriesStub : IAutorouteEntries +{ + void AddEntries(IEnumerable entries); + + void RemoveEntries(IEnumerable entries); +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/IdentityStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/IdentityStub.cs new file mode 100644 index 00000000000..e7132acbdc7 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/IdentityStub.cs @@ -0,0 +1,13 @@ +using System.Security.Principal; + +namespace OrchardCore.Testing.Stubs +{ + public class IdentityStub : IIdentity + { + public string AuthenticationType => "Testing"; + + public bool IsAuthenticated => true; + + public string Name => "OrchardCore"; + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/MemoryFileBuilder.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/MemoryFileBuilder.cs new file mode 100644 index 00000000000..e55a07d38cd --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/MemoryFileBuilder.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using OrchardCore.Deployment; + +namespace OrchardCore.Testing.Stubs; +/// +/// Represents In memory file builder that uses a dictionary as virtual file system. +/// +public class MemoryFileBuilder : IFileBuilder +{ + private readonly Dictionary _virtualFiles = new(); + + /// + public async Task SetFileAsync(string subpath, Stream stream) + { + using var memoryStream = new MemoryStream(); + + await stream.CopyToAsync(memoryStream); + + _virtualFiles[subpath] = memoryStream.ToArray(); + } + + /// + /// Read the contents of a file using the specified encoding. + /// + /// The file path. + /// The encoding used to convert the byte array to string. + /// + public string GetFileContents(string subpath, Encoding encoding) => encoding.GetString(_virtualFiles[subpath]); +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/NullExtensionManager.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/NullExtensionManager.cs new file mode 100644 index 00000000000..34850341303 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/NullExtensionManager.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using OrchardCore.Environment.Extensions; +using OrchardCore.Environment.Extensions.Features; + +namespace OrchardCore.Testing.Stubs; + +public class NullExtensionManager : IExtensionManager +{ + public IEnumerable GetDependentFeatures(string featureId) => Enumerable.Empty(); + + public IExtensionInfo GetExtension(string extensionId) => null; + + public IEnumerable GetExtensions() => Enumerable.Empty(); + + public IEnumerable GetFeatureDependencies(string featureId) => Enumerable.Empty(); + + public IEnumerable GetFeatures() => Enumerable.Empty(); + + public IEnumerable GetFeatures(string[] featureIdsToLoad) => Enumerable.Empty(); + + public Task LoadExtensionAsync(IExtensionInfo extensionInfo) => Task.FromResult(new ExtensionEntry()); + + public Task> LoadFeaturesAsync() => Task.FromResult(Enumerable.Empty()); + + public Task> LoadFeaturesAsync(string[] featureIdsToLoad) + => Task.FromResult(Enumerable.Empty()); +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/PoFileLocationProviderStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/PoFileLocationProviderStub.cs new file mode 100644 index 00000000000..e6c30809b84 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/PoFileLocationProviderStub.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using OrchardCore.Localization; + +namespace OrchardCore.Testing.Stubs; + +public class PoFileLocationProviderStub : ILocalizationFileLocationProvider +{ + private readonly IFileProvider _fileProvider; + private readonly string _resourcesPath; + + public PoFileLocationProviderStub(IHostEnvironment hostingEnvironment, IOptions localizationOptions) + { + var rootPath = new DirectoryInfo(hostingEnvironment.ContentRootPath).Parent.Parent.Parent.FullName; + _fileProvider = new PhysicalFileProvider(rootPath); + _resourcesPath = localizationOptions.Value.ResourcesPath; + } + + public IEnumerable GetLocations(string cultureName) + { + var resourcePath = Path.Combine(_resourcesPath, cultureName + ".po"); + + yield return _fileProvider.GetFileInfo(resourcePath); + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/RecipeHarvesterStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/RecipeHarvesterStub.cs new file mode 100644 index 00000000000..3a69990ccf2 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/RecipeHarvesterStub.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using OrchardCore.Recipes.Models; +using OrchardCore.Recipes.Services; +using OrchardCore.Testing.Recipes; + +namespace OrchardCore.Testing.Stubs +{ + public class RecipeHarvesterStub : IRecipeHarvester + { + private readonly IRecipeFileProvider _recipeFileProvider; + private readonly IRecipeReader _recipeReader; + + public RecipeHarvesterStub(IRecipeFileProvider recipeFileProvider, IRecipeReader recipeReader) + { + _recipeFileProvider = recipeFileProvider; + _recipeReader = recipeReader; + } + + public async Task> HarvestRecipesAsync() + { + var recipeFiles = _recipeFileProvider.GetRecipes(); + + var recipeDescriptors = new List(); + foreach (var recipeFile in recipeFiles) + { + if (recipeFile.Exists) + { + var recipeDescriptor = await _recipeReader.GetRecipeDescriptor( + recipeFile.PhysicalPath, + recipeFile, + _recipeFileProvider.FileProvider); + + recipeDescriptors.Add(recipeDescriptor); + } + } + + return recipeDescriptors; + } + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/ShapeBindingResolverStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/ShapeBindingResolverStub.cs new file mode 100644 index 00000000000..a6b5baa2d4d --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/ShapeBindingResolverStub.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using OrchardCore.DisplayManagement; +using OrchardCore.DisplayManagement.Descriptors; + +namespace OrchardCore.Testing.Stubs; + +public class ShapeBindingResolverStub : IShapeBindingResolver +{ + private readonly ShapeBindingsDictionary _shapeBindings; + + public ShapeBindingResolverStub(ShapeBindingsDictionary shapeBindings) + { + _shapeBindings = shapeBindings; + } + + public Task GetShapeBindingAsync(string shapeType) + { + if (_shapeBindings.TryGetValue(shapeType, out var binding)) + { + return Task.FromResult(binding); + } + else + { + return Task.FromResult(null); + } + } +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/ShapeTableManagerStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/ShapeTableManagerStub.cs new file mode 100644 index 00000000000..2081fec7752 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/ShapeTableManagerStub.cs @@ -0,0 +1,15 @@ +using OrchardCore.DisplayManagement.Descriptors; + +namespace OrchardCore.Testing.Stubs; + +public class ShapeTableManagerStub : IShapeTableManager +{ + private readonly ShapeTable _defaultShapeTable; + + public ShapeTableManagerStub(ShapeTable defaultShapeTable) + { + _defaultShapeTable = defaultShapeTable; + } + + public ShapeTable GetShapeTable(string themeId) => _defaultShapeTable; +} diff --git a/src/OrchardCore/OrchardCore.Testing/Stubs/ThemeManagerStub.cs b/src/OrchardCore/OrchardCore.Testing/Stubs/ThemeManagerStub.cs new file mode 100644 index 00000000000..2df9e4ac3e8 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/Stubs/ThemeManagerStub.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using OrchardCore.DisplayManagement.Theming; +using OrchardCore.Environment.Extensions; + +namespace OrchardCore.Testing.Stubs; + +public class ThemeManagerStub : IThemeManager +{ + private readonly IExtensionInfo _extensionInfo; + + public ThemeManagerStub(IExtensionInfo extensionInfo) + { + _extensionInfo = extensionInfo; + } + + public Task GetThemeAsync() => Task.FromResult(_extensionInfo); +} diff --git a/src/OrchardCore/OrchardCore.Testing/UseCultureAttribute.cs b/src/OrchardCore/OrchardCore.Testing/UseCultureAttribute.cs new file mode 100644 index 00000000000..e8a4982730d --- /dev/null +++ b/src/OrchardCore/OrchardCore.Testing/UseCultureAttribute.cs @@ -0,0 +1,83 @@ +using System; +using System.Globalization; +using System.Reflection; +using System.Threading; +using Xunit.Sdk; + +namespace OrchardCore.Testing; + +/// +/// Represents an attribute to be used in test method to replace the +/// and +/// with another culture(s). +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class UseCultureAttribute : BeforeAfterTestAttribute +{ + private readonly Lazy _culture; + private readonly Lazy _uiCulture; + + private CultureInfo _originalCulture; + private CultureInfo _originalUICulture; + + /// + /// Creates an instance of with culture. + /// + /// The name of the culture to replace the current thread culture with. + public UseCultureAttribute(string culture) + : this(culture, culture) + { + } + + /// + /// Creates an instance of with culture and UI culture. + /// + /// >The name of the culture to replace the current thread culture with. + /// >The name of the UI culture to replace the current thread UI culture with. + public UseCultureAttribute(string culture, string uiCulture) + : this(new CultureInfo(culture), new CultureInfo(uiCulture)) + { + } + + /// + /// Creates an instance of with culture and UI culture. + /// + /// >The culture to replace the current thread culture with. + /// >The UI culture to replace the current thread UI culture with. + public UseCultureAttribute(CultureInfo culture, CultureInfo uiCulture) + { + _culture = new Lazy(() => culture); + _uiCulture = new Lazy(() => uiCulture); + } + + /// + /// Gets the culture. + /// + public CultureInfo Culture => _culture.Value; + + /// + /// Gets the UI culture. + /// + public CultureInfo UICulture => _uiCulture.Value; + + /// + public override void Before(MethodInfo methodUnderTest) + { + _originalCulture = Thread.CurrentThread.CurrentCulture; + _originalUICulture = Thread.CurrentThread.CurrentUICulture; + + SetCultures(Culture, UICulture); + } + + /// + public override void After(MethodInfo methodUnderTest) => SetCultures(_originalCulture, _originalUICulture); + + private static void SetCultures(CultureInfo culture, CultureInfo uiCulture) + { + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = uiCulture; + + CultureInfo.CurrentCulture.ClearCachedData(); + CultureInfo.CurrentUICulture.ClearCachedData(); + } +} diff --git a/src/docs/topics/testing/README.md b/src/docs/topics/testing/README.md new file mode 100644 index 00000000000..b1676fdb934 --- /dev/null +++ b/src/docs/topics/testing/README.md @@ -0,0 +1,31 @@ +# Testing with Orchard Core + +When developing an OrchardCore solutions, most of the times you want to unit test the most mandatory parts of your application/module. +That's why we provide a testing infrastructure APIs, that will allow you to get started easily. + +The best way to see how it works, is by looking at `OrchardCore.Tests` project in the source code. + +## Unit Tests + +For unit testing, `OrchardCore.Testing` provides a lot of mocks ans stubs that accelerate the process of testing your application/module. Moreover it contains helpers and utility classes that might help during writing unit tests. + +### UseCultureAttribute + +With the help of `UseCultureAttribute` you scope your method under test (MUT) to run on a specific culture, which is quite useful in many cases. + +```csharp +[Fact] +[UseCulture("ar-YE")] +public void UnitTestName() +{ + // Omitted for brivety +} +``` + +## Integration Tests + +For integration testing, `OrchardCore.Testing` provides some classes that accelerate the process of testing your application/module. + +### SiteContextBase<TSiteStartup> + +You create a `SiteContext` object and configure it by using the `SiteContextOptions` for building a site that using a certain database and recipe. diff --git a/test/OrchardCore.Benchmarks/ShapeFactoryBenchmark.cs b/test/OrchardCore.Benchmarks/ShapeFactoryBenchmark.cs index 3bc865ec6d4..177d17f90b1 100644 --- a/test/OrchardCore.Benchmarks/ShapeFactoryBenchmark.cs +++ b/test/OrchardCore.Benchmarks/ShapeFactoryBenchmark.cs @@ -15,7 +15,7 @@ using OrchardCore.Environment.Extensions.Features; using OrchardCore.Environment.Extensions.Manifests; using OrchardCore.Modules.Manifest; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Benchmark { @@ -38,8 +38,8 @@ static ShapeFactoryBenchmark() var shapeFactory = new DefaultShapeFactory( serviceProvider: new ServiceCollection().BuildServiceProvider(), events: Enumerable.Empty(), - shapeTableManager: new TestShapeTableManager(defaultShapeTable), - themeManager: new MockThemeManager(new ExtensionInfo("path", new ManifestInfo(new ModuleAttribute()), (x, y) => Enumerable.Empty()))); + shapeTableManager: new ShapeTableManagerStub(defaultShapeTable), + themeManager: new ThemeManagerStub(new ExtensionInfo("path", new ManifestInfo(new ModuleAttribute()), (x, y) => Enumerable.Empty()))); _templateContext.AmbientValues["DisplayHelper"] = new DisplayHelper(null, shapeFactory, null); } diff --git a/test/OrchardCore.Tests/Apis/Context/TablePrefixGeneratorTests.cs b/test/OrchardCore.Testing.Tests/Data/TablePrefixGeneratorTests.cs similarity index 92% rename from test/OrchardCore.Tests/Apis/Context/TablePrefixGeneratorTests.cs rename to test/OrchardCore.Testing.Tests/Data/TablePrefixGeneratorTests.cs index a2ce7b8016b..cc4a1ed0799 100644 --- a/test/OrchardCore.Tests/Apis/Context/TablePrefixGeneratorTests.cs +++ b/test/OrchardCore.Testing.Tests/Data/TablePrefixGeneratorTests.cs @@ -1,4 +1,4 @@ -namespace OrchardCore.Tests.Apis.Context +namespace OrchardCore.Testing.Data.Tests { public class TablePrefixGeneratorTests { @@ -11,7 +11,9 @@ public async Task TenantPrefixShouldBeUnique() for (var i = 0; i < 200; i++) { var prefix = await tablePrefixGenerator.GeneratePrefixAsync(); + Assert.DoesNotContain(prefix, prefixes); + prefixes.Add(prefix); } } diff --git a/test/OrchardCore.Testing.Tests/OrchardCore.Testing.Tests.csproj b/test/OrchardCore.Testing.Tests/OrchardCore.Testing.Tests.csproj new file mode 100644 index 00000000000..90d6e0511a3 --- /dev/null +++ b/test/OrchardCore.Testing.Tests/OrchardCore.Testing.Tests.csproj @@ -0,0 +1,28 @@ + + + + + + $(CommonTargetFrameworks) + true + + + + + + + + + + + + + + + + + + + + + diff --git a/test/OrchardCore.Tests/Security/PermissionHandlerTests.cs b/test/OrchardCore.Testing.Tests/Security/PermissionHandlerTests.cs similarity index 97% rename from test/OrchardCore.Tests/Security/PermissionHandlerTests.cs rename to test/OrchardCore.Testing.Tests/Security/PermissionHandlerTests.cs index 1096ce03de9..23f9c1071c1 100644 --- a/test/OrchardCore.Tests/Security/PermissionHandlerTests.cs +++ b/test/OrchardCore.Testing.Tests/Security/PermissionHandlerTests.cs @@ -1,6 +1,8 @@ +using Microsoft.AspNetCore.Authorization; using OrchardCore.Security; using OrchardCore.Security.AuthorizationHandlers; using OrchardCore.Security.Permissions; +using OrchardCore.Testing.Security; namespace OrchardCore.Tests.Security { @@ -13,6 +15,7 @@ public async Task GrantsClaimsPermissions(string required, bool success) { // Arrange var context = PermissionHandlerHelper.CreateTestAuthorizationHandlerContext(new Permission(required), new[] { "Allowed" }, true); + var permissionHandler = CreatePermissionHandler(); // Act @@ -27,6 +30,7 @@ public async Task DontRevokeExistingGrants() { // Arrange var context = PermissionHandlerHelper.CreateTestAuthorizationHandlerContext(new Permission("Required"), new[] { "Other" }, true); + var permissionHandler = CreatePermissionHandler(); await context.SuccessAsync("Required"); @@ -43,6 +47,7 @@ public async Task DontHandleNonAuthenticated() { // Arrange var context = PermissionHandlerHelper.CreateTestAuthorizationHandlerContext(new Permission("Allowed"), new[] { "Allowed" }); + var permissionHandler = CreatePermissionHandler(); // Act @@ -61,6 +66,7 @@ public async Task GrantsInheritedPermissions() var required = new Permission("Required", "Foo", new[] { level1 }); var context = PermissionHandlerHelper.CreateTestAuthorizationHandlerContext(required, new[] { "Implicit2" }, true); + var permissionHandler = CreatePermissionHandler(); // Act @@ -77,6 +83,7 @@ public async Task IsCaseInsensitive() var required = new Permission("required"); var context = PermissionHandlerHelper.CreateTestAuthorizationHandlerContext(required, new[] { "ReQuIrEd" }, true); + var permissionHandler = CreatePermissionHandler(); // Act @@ -89,6 +96,7 @@ public async Task IsCaseInsensitive() private static PermissionHandler CreatePermissionHandler() { var permissionGrantingService = new DefaultPermissionGrantingService(); + return new PermissionHandler(permissionGrantingService); } } diff --git a/test/OrchardCore.Testing.Tests/UseCultureAttributeTests.cs b/test/OrchardCore.Testing.Tests/UseCultureAttributeTests.cs new file mode 100644 index 00000000000..08a3dbbaeaf --- /dev/null +++ b/test/OrchardCore.Testing.Tests/UseCultureAttributeTests.cs @@ -0,0 +1,62 @@ +namespace OrchardCore.Testing.Tests; + +public class UseCultureAttributeTests +{ + [Fact] + public void UsesSuppliedCultureAndUICulture() + { + // Arrange + var culture = "de-DE"; + var uiCulture = "fr-CA"; + + // Act + var usedCulture = new UseCultureAttribute(culture, uiCulture); + + // Assert + Assert.Equal(new CultureInfo(culture), usedCulture.Culture); + Assert.Equal(new CultureInfo(uiCulture), usedCulture.UICulture); + } + + [Fact] + public void UseCulture_BeforeAndAfterTest() + { + // Arrange + var originalCulture = CultureInfo.CurrentCulture; + var originalUICulture = CultureInfo.CurrentUICulture; + var culture = "de-DE"; + var uiCulture = "fr-CA"; + var usedCulture = new UseCultureAttribute(culture, uiCulture); + + // Act + usedCulture.Before(methodUnderTest: null); + + // Assert + Assert.Equal(new CultureInfo(culture), CultureInfo.CurrentCulture); + Assert.Equal(new CultureInfo(uiCulture), CultureInfo.CurrentUICulture); + + // Act + usedCulture.After(methodUnderTest: null); + + // Assert + Assert.Equal(originalCulture, CultureInfo.CurrentCulture); + Assert.Equal(originalUICulture, CultureInfo.CurrentUICulture); + } + + [Fact] + [UseCulture("ar-YE")] + public void UseCultureAttribute_UsesSuppliedCulture() + { + // Assert + Assert.Equal(new CultureInfo("ar-YE"), CultureInfo.CurrentCulture); + Assert.Equal(new CultureInfo("ar-YE"), CultureInfo.CurrentUICulture); + } + + [Fact] + [UseCulture("ar-YE", "ar-SA")] + public void UseCultureAttribute_UsesSuppliedCultureAndUICulture() + { + // Assert + Assert.Equal(new CultureInfo("ar-YE"), CultureInfo.CurrentCulture); + Assert.Equal(new CultureInfo("ar-SA"), CultureInfo.CurrentUICulture); + } +} diff --git a/test/OrchardCore.Testing.Tests/Usings.cs b/test/OrchardCore.Testing.Tests/Usings.cs new file mode 100644 index 00000000000..8e08b5e452b --- /dev/null +++ b/test/OrchardCore.Testing.Tests/Usings.cs @@ -0,0 +1,4 @@ +global using System.Collections.Generic; +global using System.Globalization; +global using System.Threading.Tasks; +global using Xunit; diff --git a/test/OrchardCore.Tests/Apis/ContentManagement/ContentApiController/BlogPostApiControllerTests.cs b/test/OrchardCore.Tests/Apis/ContentManagement/ContentApiController/BlogPostApiControllerTests.cs index 4afd62e8aca..daf6c29c738 100644 --- a/test/OrchardCore.Tests/Apis/ContentManagement/ContentApiController/BlogPostApiControllerTests.cs +++ b/test/OrchardCore.Tests/Apis/ContentManagement/ContentApiController/BlogPostApiControllerTests.cs @@ -2,6 +2,7 @@ using OrchardCore.Autoroute.Models; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Records; +using OrchardCore.Environment.Shell; using OrchardCore.Lists.Models; using OrchardCore.Taxonomies.Fields; using OrchardCore.Tests.Apis.Context; @@ -67,12 +68,13 @@ public async Task ShouldOnlyCreateTwoContentItemRecordsForExistingContentItem() await context.Client.PostAsJsonAsync("api/content", context.BlogPost); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostApiControllerContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => x.ContentType == "BlogPost").ListAsync(); - Assert.Equal(2, blogPosts.Count()); }); } @@ -245,7 +247,9 @@ public async Task ShouldFailValidationWhenAutoroutePathIsNotUnique() Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); Assert.Contains("Your permalink is already in use.", problemDetails.Detail); - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostApiControllerContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingServiceScopeAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -305,7 +309,9 @@ public async Task ShouldGenerateUniqueAutoroutePath() publishedContentItem.ContentItemId }; - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostApiControllerContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingServiceScopeAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var newAutoroutePartIndex = await session diff --git a/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostContentStepIdempotentTests.cs b/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostContentStepIdempotentTests.cs index 94464ddd538..f8841002178 100644 --- a/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostContentStepIdempotentTests.cs +++ b/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostContentStepIdempotentTests.cs @@ -1,5 +1,6 @@ using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Records; +using OrchardCore.Environment.Shell; using OrchardCore.Tests.Apis.Context; using YesSql; using ISession = YesSql.ISession; @@ -28,7 +29,9 @@ public async Task ShouldProduceSameOutcomeForNewContentOnMultipleExecutions() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -68,7 +71,9 @@ public async Task ShouldProduceSameOutcomeForExistingContentItemVersionOnMultipl await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => diff --git a/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostCreateDeploymentPlanTests.cs b/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostCreateDeploymentPlanTests.cs index ab25c62ee47..73046d0bff1 100644 --- a/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostCreateDeploymentPlanTests.cs +++ b/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostCreateDeploymentPlanTests.cs @@ -1,6 +1,7 @@ using OrchardCore.Autoroute.Models; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Records; +using OrchardCore.Environment.Shell; using OrchardCore.Tests.Apis.Context; using YesSql; using ISession = YesSql.ISession; @@ -27,7 +28,9 @@ public async Task ShouldCreateNewPublishedContentItemVersion() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -68,7 +71,9 @@ public async Task ShouldDiscardDraftThenCreateNewPublishedContentItemVersion() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -113,7 +118,9 @@ public async Task ShouldDiscardDraftThenCreateNewDraftContentItemVersion() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -157,7 +164,9 @@ public async Task ShouldCreateNewPublishedContentItem() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPostsCount = await session.Query(x => @@ -202,7 +211,9 @@ public async Task ShouldIgnoreDuplicateContentItems() await context.PostRecipeAsync(firstRecipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPostsCount = await session.Query(x => diff --git a/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostUpdateDeploymentPlanTests.cs b/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostUpdateDeploymentPlanTests.cs index 88253e1c670..ac330c3e34b 100644 --- a/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostUpdateDeploymentPlanTests.cs +++ b/test/OrchardCore.Tests/Apis/ContentManagement/DeploymentPlans/BlogPostUpdateDeploymentPlanTests.cs @@ -1,5 +1,6 @@ using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Records; +using OrchardCore.Environment.Shell; using OrchardCore.Tests.Apis.Context; using YesSql; using ISession = YesSql.ISession; @@ -25,7 +26,9 @@ public async Task ShouldUpdateExistingContentItemVersion() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingServiceScopeAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -58,7 +61,9 @@ public async Task ShouldDiscardDraftThenUpdateExistingContentItemVersion() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingServiceScopeAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => @@ -99,7 +104,9 @@ public async Task ShouldUpdateDraftThenPublishExistingContentItemVersion() await context.PostRecipeAsync(recipe); // Test - await context.UsingTenantScopeAsync(async scope => + var shellScope = await BlogPostDeploymentContext.ShellHost.GetScopeAsync(context.TenantName); + + await shellScope.UsingAsync(async scope => { var session = scope.ServiceProvider.GetRequiredService(); var blogPosts = await session.Query(x => diff --git a/test/OrchardCore.Tests/Apis/Context/AgencyContext.cs b/test/OrchardCore.Tests/Apis/Context/AgencyContext.cs index e0bf460b3f8..726bf47eab9 100644 --- a/test/OrchardCore.Tests/Apis/Context/AgencyContext.cs +++ b/test/OrchardCore.Tests/Apis/Context/AgencyContext.cs @@ -1,3 +1,5 @@ +using OrchardCore.Testing.Apis; + namespace OrchardCore.Tests.Apis.Context { public class AgencyContext : SiteContext diff --git a/test/OrchardCore.Tests/Apis/Context/BlogPostDeploymentContext.cs b/test/OrchardCore.Tests/Apis/Context/BlogPostDeploymentContext.cs index 6e2da039598..740cb615df7 100644 --- a/test/OrchardCore.Tests/Apis/Context/BlogPostDeploymentContext.cs +++ b/test/OrchardCore.Tests/Apis/Context/BlogPostDeploymentContext.cs @@ -1,6 +1,7 @@ using OrchardCore.ContentManagement; using OrchardCore.Deployment.Remote.Services; using OrchardCore.Deployment.Remote.ViewModels; +using OrchardCore.Environment.Shell; namespace OrchardCore.Tests.Apis.Context { @@ -35,7 +36,9 @@ public override async Task InitializeAsync() OriginalBlogPost = await content.Content.ReadAsAsync(); OriginalBlogPostVersionId = OriginalBlogPost.ContentItemVersionId; - await UsingTenantScopeAsync(async scope => + var shellScope = await ShellHost.GetScopeAsync(TenantName); + + await shellScope.UsingAsync(async scope => { var remoteClientService = scope.ServiceProvider.GetRequiredService(); diff --git a/test/OrchardCore.Tests/Apis/Context/Extensions/HttpRequestExtensions.cs b/test/OrchardCore.Tests/Apis/Context/Extensions/HttpRequestExtensions.cs deleted file mode 100644 index fb4ac826424..00000000000 --- a/test/OrchardCore.Tests/Apis/Context/Extensions/HttpRequestExtensions.cs +++ /dev/null @@ -1,193 +0,0 @@ -namespace OrchardCore.Tests.Apis.Context -{ - /// - /// The http request extensions. - /// - internal static class HttpRequestExtensions - { - private readonly static JsonSerializerSettings JsonSettings = new JsonSerializerSettings() - { - NullValueHandling = NullValueHandling.Ignore - }; - - /// - /// The patch as json async. - /// - /// - /// The client. - /// - /// - /// The request uri. - /// - /// - /// The value. - /// - /// - /// The formatter. - /// - /// - /// - /// - /// The . - /// - public static Task PatchAsJsonAsync( - this HttpClient client, - string requestUri, - T value, - JsonSerializerSettings settings = null) - { - var content = new StringContent( - JsonConvert.SerializeObject(value, settings ?? JsonSettings), - Encoding.UTF8, - "application/json"); - - return HttpRequestExtensions.PatchAsync(client, requestUri, content); - } - - /// - /// The patch async. - /// - /// - /// The client. - /// - /// - /// The request uri. - /// - /// - /// The content. - /// - /// - /// The . - /// - public static Task PatchAsync( - this HttpClient client, - string requestUri, - HttpContent content) - { - var request = new HttpRequestMessage - { - Method = new HttpMethod("PATCH"), - RequestUri = new Uri(client.BaseAddress + requestUri), - Content = content - }; - - request.Headers.ExpectContinue = false; - return client.SendAsync(request); - } - - /// - /// The put as json async. - /// - /// - /// The client. - /// - /// - /// The request uri. - /// - /// - /// The value. - /// - /// - /// The formatter. - /// - /// - /// - /// - /// The . - /// - public static Task PutAsJsonAsync( - this HttpClient client, - string requestUri, - T value, - JsonSerializerSettings settings = null) - { - var content = new StringContent( - JsonConvert.SerializeObject(value, settings ?? JsonSettings), - Encoding.UTF8, - "application/json"); - - return client.PutAsync(requestUri, content); - } - - /// - /// PostAsJsonAsync - /// - /// - /// The client. - /// - /// - /// The request uri. - /// - /// - /// The value. - /// - /// - /// The formatter. - /// - /// - /// - /// - /// The . - /// - public static Task PostAsJsonAsync( - this HttpClient client, - string requestUri, - T value, - JsonSerializerSettings settings = null) - { - var content = new StringContent( - JsonConvert.SerializeObject(value, settings ?? JsonSettings), - Encoding.UTF8, - "application/json"); - - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - request.Content = content; - - request.Headers - .Accept - .Add(new MediaTypeWithQualityHeaderValue("application/json")); - - return client.SendAsync(request); - } - - public static Task PostJsonAsync( - this HttpClient client, - string requestUri, - string json) - { - var content = new StringContent( - json, - Encoding.UTF8, - "application/json"); - - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - request.Content = content; - - request.Headers - .Accept - .Add(new MediaTypeWithQualityHeaderValue("application/json")); - - return client.SendAsync(request); - } - - public static Task PostJsonApiAsync( - this HttpClient client, - string requestUri, - string json) - { - var content = new StringContent( - json, - Encoding.UTF8, - "application/vnd.api+json"); - - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - request.Content = content; - - request.Headers - .Accept - .Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json")); - - return client.SendAsync(request); - } - } -} diff --git a/test/OrchardCore.Tests/Apis/Context/MvcTestFixture.cs b/test/OrchardCore.Tests/Apis/Context/MvcTestFixture.cs deleted file mode 100644 index f960ad8645e..00000000000 --- a/test/OrchardCore.Tests/Apis/Context/MvcTestFixture.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace OrchardCore.Tests.Apis.Context -{ - public class OrchardTestFixture : WebApplicationFactory - where TStartup : class - { - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - var shellsApplicationDataPath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data"); - - if (Directory.Exists(shellsApplicationDataPath)) - { - Directory.Delete(shellsApplicationDataPath, true); - } - - builder.UseContentRoot(Directory.GetCurrentDirectory()); - } - - protected override IWebHostBuilder CreateWebHostBuilder() - { - return WebHostBuilderFactory.CreateFromAssemblyEntryPoint( - typeof(Program).Assembly, Array.Empty()); - } - - protected override IHostBuilder CreateHostBuilder() - => Host.CreateDefaultBuilder() - .ConfigureWebHostDefaults(webBuilder => - webBuilder.UseStartup()); - } -} diff --git a/test/OrchardCore.Tests/Apis/Context/RecipeFileProvider.cs b/test/OrchardCore.Tests/Apis/Context/RecipeFileProvider.cs new file mode 100644 index 00000000000..608026af80f --- /dev/null +++ b/test/OrchardCore.Tests/Apis/Context/RecipeFileProvider.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Microsoft.Extensions.FileProviders; +using System.Reflection; +using OrchardCore.Testing.Recipes; + +namespace OrchardCore.Tests.Apis.Context; + +public class RecipeFileProvider : IRecipeFileProvider +{ + public IFileProvider FileProvider => new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + + public IEnumerable GetRecipes() + { + yield return FileProvider.GetFileInfo("Apis/Lucene/Recipes/luceneQueryTest.json"); + } +} diff --git a/test/OrchardCore.Tests/Apis/Context/SiteContext.cs b/test/OrchardCore.Tests/Apis/Context/SiteContext.cs index cc4babc1973..b443b2f421f 100644 --- a/test/OrchardCore.Tests/Apis/Context/SiteContext.cs +++ b/test/OrchardCore.Tests/Apis/Context/SiteContext.cs @@ -1,132 +1,31 @@ using OrchardCore.Apis.GraphQL.Client; -using OrchardCore.BackgroundTasks; using OrchardCore.ContentManagement; using OrchardCore.Environment.Shell; -using OrchardCore.Environment.Shell.Scope; -using OrchardCore.Recipes.Services; using OrchardCore.Search.Lucene; +using OrchardCore.Testing.Apis; namespace OrchardCore.Tests.Apis.Context { - public class SiteContext : IDisposable + public class SiteContext : SiteContextBase { - private static readonly TablePrefixGenerator TablePrefixGenerator = new TablePrefixGenerator(); - public static OrchardTestFixture Site { get; } - public static IShellHost ShellHost { get; private set; } - public static IShellSettingsManager ShellSettingsManager { get; private set; } - public static IHttpContextAccessor HttpContextAccessor { get; } - public static HttpClient DefaultTenantClient { get; } - - public string RecipeName { get; set; } = "Blog"; - public string DatabaseProvider { get; set; } = "Sqlite"; - public string ConnectionString { get; set; } - public PermissionsContext PermissionsContext { get; set; } - - public HttpClient Client { get; private set; } - public string TenantName { get; private set; } - public OrchardGraphQLClient GraphQLClient { get; private set; } - - static SiteContext() + public SiteContext() { - Site = new OrchardTestFixture(); - ShellHost = Site.Services.GetRequiredService(); - ShellSettingsManager = Site.Services.GetRequiredService(); - HttpContextAccessor = Site.Services.GetRequiredService(); - DefaultTenantClient = Site.CreateDefaultClient(); + this.WithRecipe("Blog") + .WithDatabaseProvider("Sqlite"); } - public virtual async Task InitializeAsync() + public override async Task InitializeAsync() { - var tenantName = Guid.NewGuid().ToString("n"); - var tablePrefix = await TablePrefixGenerator.GeneratePrefixAsync(); - - var createModel = new Tenants.Models.TenantApiModel - { - DatabaseProvider = DatabaseProvider, - TablePrefix = tablePrefix, - ConnectionString = ConnectionString, - RecipeName = RecipeName, - Name = tenantName, - RequestUrlPrefix = tenantName, - Schema = null, - }; - - var createResult = await DefaultTenantClient.PostAsJsonAsync("api/tenants/create", createModel); - createResult.EnsureSuccessStatusCode(); - - var content = await createResult.Content.ReadAsStringAsync(); - - var url = new Uri(content.Trim('"')); - url = new Uri(url.Scheme + "://" + url.Authority + url.LocalPath + "/"); - - var setupModel = new Tenants.ViewModels.SetupApiViewModel - { - SiteName = "Test Site", - DatabaseProvider = DatabaseProvider, - TablePrefix = tablePrefix, - ConnectionString = ConnectionString, - RecipeName = RecipeName, - UserName = "admin", - Password = "Password01_", - Name = tenantName, - Email = "Nick@Orchard", - }; - - var setupResult = await DefaultTenantClient.PostAsJsonAsync("api/tenants/setup", setupModel); - setupResult.EnsureSuccessStatusCode(); - - lock (Site) - { - Client = Site.CreateDefaultClient(url); - TenantName = tenantName; - } - - if (PermissionsContext != null) - { - var permissionContextKey = Guid.NewGuid().ToString(); - SiteStartup.PermissionsContexts.TryAdd(permissionContextKey, PermissionsContext); - Client.DefaultRequestHeaders.Add("PermissionsContext", permissionContextKey); - } + await base.InitializeAsync(); GraphQLClient = new OrchardGraphQLClient(Client); } - public async Task UsingTenantScopeAsync(Func execute, bool activateShell = true) + public async Task ResetLuceneIndiciesAsync(string indexName) { - // Ensure that 'HttpContext' is not null before using a 'ShellScope'. var shellScope = await ShellHost.GetScopeAsync(TenantName); - HttpContextAccessor.HttpContext = shellScope.ShellContext.CreateHttpContext(); - await shellScope.UsingAsync(execute, activateShell); - } - public async Task RunRecipeAsync(string recipeName, string recipePath) - { - await UsingTenantScopeAsync(async scope => - { - var shellFeaturesManager = scope.ServiceProvider.GetRequiredService(); - var recipeHarvesters = scope.ServiceProvider.GetRequiredService>(); - var recipeExecutor = scope.ServiceProvider.GetRequiredService(); - - var recipeCollections = await Task.WhenAll( - recipeHarvesters.Select(recipe => recipe.HarvestRecipesAsync())); - - var recipes = recipeCollections.SelectMany(recipeCollection => recipeCollection); - var recipe = recipes - .FirstOrDefault(recipe => recipe.RecipeFileInfo.Name == recipeName && recipe.BasePath == recipePath); - - var executionId = Guid.NewGuid().ToString("n"); - - await recipeExecutor.ExecuteAsync( - executionId, - recipe, - new Dictionary(), - CancellationToken.None); - }); - } - - public async Task ResetLuceneIndiciesAsync(string indexName) - { - await UsingTenantScopeAsync(async scope => + await shellScope.UsingServiceScopeAsync(async scope => { var luceneIndexSettingsService = scope.ServiceProvider.GetRequiredService(); var luceneIndexingService = scope.ServiceProvider.GetRequiredService(); @@ -134,6 +33,7 @@ await UsingTenantScopeAsync(async scope => var luceneIndexSettings = await luceneIndexSettingsService.GetSettingsAsync(indexName); luceneIndexingService.ResetIndexAsync(indexName); + await luceneIndexingService.ProcessContentItemsAsync(indexName); }); } @@ -151,46 +51,12 @@ public async Task CreateContentItem(string contentType, Action(); return response.ContentItemId; } - public Task DeleteContentItem(string contentItemId) - { - return Client.DeleteAsync("api/content/" + contentItemId); - } - - public void Dispose() - { - Client?.Dispose(); - } - } - - public static class SiteContextExtensions - { - public static T WithDatabaseProvider(this T siteContext, string databaseProvider) where T : SiteContext - { - siteContext.DatabaseProvider = databaseProvider; - return siteContext; - } - - public static T WithConnectionString(this T siteContext, string connectionString) where T : SiteContext - { - siteContext.ConnectionString = connectionString; - return siteContext; - } - - public static T WithPermissionsContext(this T siteContext, PermissionsContext permissionsContext) where T : SiteContext - { - siteContext.PermissionsContext = permissionsContext; - return siteContext; - } - - public static T WithRecipe(this T siteContext, string recipeName) where T : SiteContext - { - siteContext.RecipeName = recipeName; - return siteContext; - } + public async Task DeleteContentItem(string contentItemId) => await Client.DeleteAsync("api/content/" + contentItemId); } } diff --git a/test/OrchardCore.Tests/Apis/Context/SiteStartup.cs b/test/OrchardCore.Tests/Apis/Context/SiteStartup.cs index 856de32bef6..040cc915120 100644 --- a/test/OrchardCore.Tests/Apis/Context/SiteStartup.cs +++ b/test/OrchardCore.Tests/Apis/Context/SiteStartup.cs @@ -1,60 +1,34 @@ using OrchardCore.Modules; -using OrchardCore.Modules.Manifest; using OrchardCore.Recipes.Services; +using OrchardCore.Testing; +using OrchardCore.Testing.Apis; +using OrchardCore.Testing.Apis.Security; +using OrchardCore.Testing.Recipes; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.Apis.Context { public class SiteStartup { - public static ConcurrentDictionary PermissionsContexts; - - static SiteStartup() - { - PermissionsContexts = new ConcurrentDictionary(); - } - public void ConfigureServices(IServiceCollection services) { - services.AddOrchardCms(builder => - builder.AddSetupFeatures( - "OrchardCore.Tenants" - ) - .AddTenantFeatures( - "OrchardCore.Apis.GraphQL" - ) - .ConfigureServices(collection => + services.AddOrchardCms(builder => builder + .AddSetupFeatures("OrchardCore.Tenants") + .AddTenantFeatures("OrchardCore.Apis.GraphQL") + .ConfigureServices(serviceCollection => { - collection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); - collection.AddScoped(sp => - { - return new PermissionContextAuthorizationHandler(sp.GetRequiredService(), PermissionsContexts); - }); + serviceCollection.AddScoped(sp => + new PermissionContextAuthorizationHandler(sp.GetRequiredService(), + SiteContextOptions.PermissionsContexts)); }) .Configure(appBuilder => appBuilder.UseAuthorization())); - services.AddSingleton(); + services.AddSingleton(new ModuleNamesProvider(typeof(Program).Assembly)); } - public void Configure(IApplicationBuilder app, IHostEnvironment env, ILoggerFactory loggerFactory) - { - app.UseOrchardCore(); - } - - private class ModuleNamesProvider : IModuleNamesProvider - { - private readonly string[] _moduleNames; - - public ModuleNamesProvider() - { - var assembly = Assembly.Load(new AssemblyName(typeof(Program).Assembly.GetName().Name)); - _moduleNames = assembly.GetCustomAttributes().Select(m => m.Name).ToArray(); - } - - public IEnumerable GetModuleNames() - { - return _moduleNames; - } - } + public void Configure(IApplicationBuilder app, IHostEnvironment env, ILoggerFactory loggerFactory) => app.UseOrchardCore(); } } diff --git a/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs b/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs deleted file mode 100644 index 06f7be64aaa..00000000000 --- a/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs +++ /dev/null @@ -1,44 +0,0 @@ -using OrchardCore.Recipes.Models; -using OrchardCore.Recipes.Services; - -namespace OrchardCore.Tests.Apis.Context -{ - public class TestRecipeHarvester : IRecipeHarvester - { - private readonly IRecipeReader _recipeReader; - - public TestRecipeHarvester(IRecipeReader recipeReader) - { - _recipeReader = recipeReader; - } - - public Task> HarvestRecipesAsync() - => HarvestRecipesAsync(new[] - { - "Apis/Lucene/Recipes/luceneQueryTest.json" - }); - - private async Task> HarvestRecipesAsync(string[] paths) - { - var recipeDescriptors = new List(); - var testAssemblyFileProvider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); - var fileInfos = new List(); - - foreach (var path in paths) - { - // EmbeddedFileProvider doesn't list directory contents. - var fileInfo = testAssemblyFileProvider.GetFileInfo(path); - Assert.True(fileInfo.Exists); - fileInfos.Add(fileInfo); - } - - foreach (var fileInfo in fileInfos) - { - var descriptor = await _recipeReader.GetRecipeDescriptor(fileInfo.PhysicalPath, fileInfo, testAssemblyFileProvider); - recipeDescriptors.Add(descriptor); - } - - return recipeDescriptors; - } - } -} diff --git a/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs index 90e4e786f8f..0fbe209d79b 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs @@ -2,6 +2,8 @@ using OrchardCore.ContentFields.Fields; using OrchardCore.ContentManagement; using OrchardCore.Lists.Models; +using OrchardCore.Testing.Apis; +using OrchardCore.Testing.Apis.Security; using OrchardCore.Tests.Apis.Context; using GraphQLApi = OrchardCore.Apis.GraphQL; diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs index d0ea2b3e2da..948be718f98 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ValidationRules/RequiresPermissionValidationRuleTests.cs @@ -6,7 +6,8 @@ using OrchardCore.Apis.GraphQL; using OrchardCore.Apis.GraphQL.ValidationRules; using OrchardCore.Security.Permissions; -using OrchardCore.Tests.Apis.Context; +using OrchardCore.Testing.Apis.Security; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.Apis.GraphQL.ValidationRules { @@ -123,7 +124,7 @@ private ExecutionOptions BuildExecutionOptions(string query, PermissionsContext Schema = new ValidationSchema(), UserContext = new GraphQLUserContext { - User = new ClaimsPrincipal(new StubIdentity()) + User = new ClaimsPrincipal(new IdentityStub()) }, ValidationRules = DocumentValidator.CoreRules.Concat(serviceProvider.GetServices()) }; diff --git a/test/OrchardCore.Tests/Apis/Lucene/LuceneContext.cs b/test/OrchardCore.Tests/Apis/Lucene/LuceneContext.cs index 534713bc0c5..0f37028b255 100644 --- a/test/OrchardCore.Tests/Apis/Lucene/LuceneContext.cs +++ b/test/OrchardCore.Tests/Apis/Lucene/LuceneContext.cs @@ -1,13 +1,10 @@ +using OrchardCore.Testing.Apis; using OrchardCore.Tests.Apis.Context; namespace OrchardCore.Tests.Apis.Lucene { public class LuceneContext : SiteContext { - static LuceneContext() - { - } - public LuceneContext() { this.WithRecipe("luceneQueryTest"); diff --git a/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs b/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs index 0a2f89b747a..5305ec71545 100644 --- a/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs @@ -7,7 +7,7 @@ using OrchardCore.Environment.Extensions.Manifests; using OrchardCore.Environment.Shell; using OrchardCore.Modules.Manifest; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.DisplayManagement.Decriptors { @@ -119,7 +119,7 @@ public DefaultShapeTableManagerTests() serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(new StubHostingEnvironment()); + serviceCollection.AddSingleton(new HostingEnvironmentStub()); var testFeatureExtensionInfo = new TestModuleExtensionInfo("Testing"); var theme1FeatureExtensionInfo = new TestThemeExtensionInfo("Theme1"); diff --git a/test/OrchardCore.Tests/DisplayManagement/DefaultDisplayManagerTests.cs b/test/OrchardCore.Tests/DisplayManagement/DefaultDisplayManagerTests.cs index ab0a68f4bfc..bd743778bf5 100644 --- a/test/OrchardCore.Tests/DisplayManagement/DefaultDisplayManagerTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/DefaultDisplayManagerTests.cs @@ -5,15 +5,16 @@ using OrchardCore.DisplayManagement.Theming; using OrchardCore.Environment.Extensions; using OrchardCore.Localization; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.DisplayManagement { public class DefaultDisplayManagerTests { - private ShapeTable _defaultShapeTable; - private TestShapeBindingsDictionary _additionalBindings; - private IServiceProvider _serviceProvider; + private readonly ShapeTable _defaultShapeTable; + private readonly ShapeBindingsDictionary _additionalBindings; + private readonly IServiceProvider _serviceProvider; public DefaultDisplayManagerTests() { @@ -22,16 +23,16 @@ public DefaultDisplayManagerTests() new Dictionary(StringComparer.OrdinalIgnoreCase), new Dictionary(StringComparer.OrdinalIgnoreCase) ); - _additionalBindings = new TestShapeBindingsDictionary(); + _additionalBindings = new ShapeBindingsDictionary(); IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); serviceCollection.AddSingleton(); serviceCollection.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); diff --git a/test/OrchardCore.Tests/DisplayManagement/ShapeFactoryTests.cs b/test/OrchardCore.Tests/DisplayManagement/ShapeFactoryTests.cs index 2c7eec68444..7315de417df 100644 --- a/test/OrchardCore.Tests/DisplayManagement/ShapeFactoryTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/ShapeFactoryTests.cs @@ -4,7 +4,7 @@ using OrchardCore.DisplayManagement.Shapes; using OrchardCore.DisplayManagement.Theming; using OrchardCore.Environment.Extensions; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.DisplayManagement { @@ -20,8 +20,8 @@ public ShapeFactoryTests() serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); _shapeTable = new ShapeTable ( diff --git a/test/OrchardCore.Tests/DisplayManagement/ShapeHelperTests.cs b/test/OrchardCore.Tests/DisplayManagement/ShapeHelperTests.cs index 2f16607b4c8..45ae30044d0 100644 --- a/test/OrchardCore.Tests/DisplayManagement/ShapeHelperTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/ShapeHelperTests.cs @@ -3,7 +3,7 @@ using OrchardCore.DisplayManagement.Implementation; using OrchardCore.DisplayManagement.Theming; using OrchardCore.Environment.Extensions; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.DisplayManagement { @@ -17,10 +17,10 @@ public ShapeHelperTests() serviceCollection.AddLogging(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); var defaultShapeTable = new ShapeTable ( diff --git a/test/OrchardCore.Tests/DisplayManagement/ShapeSerializerTests.cs b/test/OrchardCore.Tests/DisplayManagement/ShapeSerializerTests.cs index c3c9c1fc306..c0a274f279b 100644 --- a/test/OrchardCore.Tests/DisplayManagement/ShapeSerializerTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/ShapeSerializerTests.cs @@ -3,7 +3,7 @@ using OrchardCore.DisplayManagement.Implementation; using OrchardCore.DisplayManagement.Theming; using OrchardCore.Environment.Extensions; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.DisplayManagement { @@ -17,10 +17,10 @@ public ShapeSerializerTests() serviceCollection.AddLogging(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); var defaultShapeTable = new ShapeTable ( diff --git a/test/OrchardCore.Tests/Email/EmailTests.cs b/test/OrchardCore.Tests/Email/EmailTests.cs index 1ee39b88db0..48ff8d59892 100644 --- a/test/OrchardCore.Tests/Email/EmailTests.cs +++ b/test/OrchardCore.Tests/Email/EmailTests.cs @@ -1,6 +1,7 @@ using MimeKit; using OrchardCore.Email; using OrchardCore.Email.Services; +using OrchardCore.Testing.Mocks; namespace OrchardCore.Tests.Email { @@ -197,7 +198,7 @@ public async Task SendEmail_WithoutToAndCcAndBccHeaders_ShouldThrowsException() DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory }; - var smtp = CreateSmtpService(settings); + var smtp = OrchardCoreMock.CreateSmtpService(settings); // Act var result = await smtp.SendAsync(message); @@ -221,7 +222,7 @@ public async Task SendOfflineEmailHasNoResponse() DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory }; - var smtp = CreateSmtpService(settings); + var smtp = OrchardCoreMock.CreateSmtpService(settings); // Act var result = await smtp.SendAsync(message); @@ -248,7 +249,7 @@ private async Task SendEmailAsync(MailMessage message, string defaultSen DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory, PickupDirectoryLocation = pickupDirectoryPath }; - var smtp = CreateSmtpService(settings); + var smtp = OrchardCoreMock.CreateSmtpService(settings); var result = await smtp.SendAsync(message); @@ -262,17 +263,5 @@ private async Task SendEmailAsync(MailMessage message, string defaultSen return content; } - - private static ISmtpService CreateSmtpService(SmtpSettings settings) - { - var options = new Mock>(); - options.Setup(o => o.Value).Returns(settings); - - var logger = new Mock>(); - var localizer = new Mock>(); - var smtp = new SmtpService(options.Object, logger.Object, localizer.Object); - - return smtp; - } } } diff --git a/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs b/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs index 21d43f902fe..bcf37151a94 100644 --- a/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs +++ b/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs @@ -3,14 +3,13 @@ using OrchardCore.Environment.Extensions; using OrchardCore.Environment.Extensions.Features; using OrchardCore.Modules; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.Extensions { public class ExtensionManagerTests { - private static IHostEnvironment HostingEnvironment - = new StubHostingEnvironment(); + private static IHostEnvironment HostingEnvironment = new HostingEnvironmentStub(); private static IApplicationContext ApplicationContext = new ModularApplicationContext(HostingEnvironment, new List() diff --git a/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerFactoryTests.cs b/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerFactoryTests.cs index 89bba33a739..65934556a4f 100644 --- a/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerFactoryTests.cs +++ b/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerFactoryTests.cs @@ -1,4 +1,5 @@ using OrchardCore.Localization; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.Localization { @@ -15,7 +16,7 @@ public void ConfigureServices(IServiceCollection services) services.AddMvc(); services.AddLocalization(); services.AddPortableObjectLocalization(options => options.ResourcesPath = "Localization/PoFiles"); - services.Replace(ServiceDescriptor.Singleton()); + services.Replace(ServiceDescriptor.Singleton()); } public void Configure( diff --git a/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs b/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs index ec5b4378ef1..d9b0a09ce25 100644 --- a/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs +++ b/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs @@ -1,5 +1,6 @@ using OrchardCore.Localization; using OrchardCore.Localization.PortableObject; +using OrchardCore.Testing; namespace OrchardCore.Tests.Localization { @@ -15,6 +16,7 @@ public PortableObjectStringLocalizerTests() } [Fact] + [UseCulture("cs")] public void LocalizerReturnsTranslationsFromProvidedDictionary() { SetupDictionary("cs", new[] { @@ -22,16 +24,14 @@ public void LocalizerReturnsTranslationsFromProvidedDictionary() }); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer["ball"]; + var translation = localizer["ball"]; - Assert.Equal("míč", translation); - } + Assert.Equal("míč", translation); } [Fact] + [UseCulture("cs")] public void LocalizerReturnsOriginalTextIfTranslationsDoesntExistInProvidedDictionary() { SetupDictionary("cs", new[] { @@ -39,29 +39,27 @@ public void LocalizerReturnsOriginalTextIfTranslationsDoesntExistInProvidedDicti }); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer["car"]; - Assert.Equal("car", translation); - } + var translation = localizer["car"]; + + Assert.Equal("car", translation); } [Fact] + [UseCulture("cs")] public void LocalizerReturnsOriginalTextIfDictionaryIsEmpty() { SetupDictionary("cs", Array.Empty()); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer["car"]; - Assert.Equal("car", translation); - } + var translation = localizer["car"]; + + Assert.Equal("car", translation); } [Fact] + [UseCulture("cs-CZ")] public void LocalizerFallbacksToParentCultureIfTranslationDoesntExistInSpecificCulture() { SetupDictionary("cs", new[] { @@ -73,15 +71,14 @@ public void LocalizerFallbacksToParentCultureIfTranslationDoesntExistInSpecificC }); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs-cz")) - { - var translation = localizer["ball"]; - Assert.Equal("míč", translation); - } + var translation = localizer["ball"]; + + Assert.Equal("míč", translation); } [Fact] + [UseCulture("cs-CZ")] public void LocalizerReturnsTranslationFromSpecificCultureIfItExists() { SetupDictionary("cs", new[] { @@ -93,15 +90,14 @@ public void LocalizerReturnsTranslationFromSpecificCultureIfItExists() }); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs-CZ")) - { - var translation = localizer["ball"]; - Assert.Equal("balón", translation); - } + var translation = localizer["ball"]; + + Assert.Equal("balón", translation); } [Fact] + [UseCulture("cs")] public void LocalizerReturnsTranslationWithSpecificContext() { SetupDictionary("cs", new[] { @@ -110,15 +106,14 @@ public void LocalizerReturnsTranslationWithSpecificContext() }); var localizer = new PortableObjectStringLocalizer("small", _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer["ball"]; - Assert.Equal("míček", translation); - } + var translation = localizer["ball"]; + + Assert.Equal("míček", translation); } [Fact] + [UseCulture("cs")] public void LocalizerReturnsTranslationWithoutContextIfTranslationWithContextDoesntExist() { SetupDictionary("cs", new[] { @@ -127,15 +122,14 @@ public void LocalizerReturnsTranslationWithoutContextIfTranslationWithContextDoe }); var localizer = new PortableObjectStringLocalizer("small", _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer["ball"]; - Assert.Equal("míč", translation); - } + var translation = localizer["ball"]; + + Assert.Equal("míč", translation); } [Fact] + [UseCulture("cs")] public void LocalizerReturnsFormattedTranslation() { SetupDictionary("cs", new[] { @@ -143,15 +137,14 @@ public void LocalizerReturnsFormattedTranslation() }); var localizer = new PortableObjectStringLocalizer("small", _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer["The page (ID:{0}) was deleted.", 1]; - Assert.Equal("Stránka (ID:1) byla smazána.", translation); - } + var translation = localizer["The page (ID:{0}) was deleted.", 1]; + + Assert.Equal("Stránka (ID:1) byla smazána.", translation); } [Fact] + [UseCulture("cs")] public void HtmlLocalizerDoesNotFormatTwiceIfFormattedTranslationContainsCurlyBraces() { SetupDictionary("cs", new[] { @@ -159,29 +152,27 @@ public void HtmlLocalizerDoesNotFormatTwiceIfFormattedTranslationContainsCurlyBr }); var localizer = new PortableObjectStringLocalizer("small", _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var htmlLocalizer = new PortableObjectHtmlLocalizer(localizer); - var unformatted = htmlLocalizer["The page (ID:{0}) was deleted.", "{1}"]; - var memStream = new MemoryStream(); - var textWriter = new StreamWriter(memStream); - var textReader = new StreamReader(memStream); + var htmlLocalizer = new PortableObjectHtmlLocalizer(localizer); + var unformatted = htmlLocalizer["The page (ID:{0}) was deleted.", "{1}"]; + var memStream = new MemoryStream(); + var textWriter = new StreamWriter(memStream); + var textReader = new StreamReader(memStream); - unformatted.WriteTo(textWriter, HtmlEncoder.Default); + unformatted.WriteTo(textWriter, HtmlEncoder.Default); - textWriter.Flush(); - memStream.Seek(0, SeekOrigin.Begin); - var formatted = textReader.ReadToEnd(); + textWriter.Flush(); + memStream.Seek(0, SeekOrigin.Begin); + var formatted = textReader.ReadToEnd(); - textWriter.Dispose(); - textReader.Dispose(); - memStream.Dispose(); + textWriter.Dispose(); + textReader.Dispose(); + memStream.Dispose(); - Assert.Equal("Stránka (ID:{1}) byla smazána.", formatted); - } + Assert.Equal("Stránka (ID:{1}) byla smazána.", formatted); } [Theory] + [UseCulture("cs")] [InlineData("car", 1)] [InlineData("cars", 2)] public void LocalizerReturnsOriginalTextForPluralIfTranslationDoesntExist(string expected, int count) @@ -191,11 +182,9 @@ public void LocalizerReturnsOriginalTextForPluralIfTranslationDoesntExist(string }); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer.Plural(count, "car", "cars"); - Assert.Equal(expected, translation); - } + + var translation = localizer.Plural(count, "car", "cars"); + Assert.Equal(expected, translation); } [Theory] @@ -220,6 +209,7 @@ public void LocalizerReturnsCorrectTranslationForPluralIfNoPluralFormsSpecified( } [Theory] + [UseCulture("cs")] [InlineData("míč", 1)] [InlineData("2 míče", 2)] [InlineData("5 míčů", 5)] @@ -230,15 +220,14 @@ public void LocalizerReturnsTranslationInCorrectPluralForm(string expected, int }); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("cs")) - { - var translation = localizer.Plural(count, "ball", "{0} balls", count); - Assert.Equal(expected, translation); - } + var translation = localizer.Plural(count, "ball", "{0} balls", count); + + Assert.Equal(expected, translation); } [Theory] + [UseCulture("en")] [InlineData("míč", 1)] [InlineData("2 míče", 2)] [InlineData("5 míčů", 5)] @@ -247,15 +236,14 @@ public void LocalizerReturnsOriginalValuesIfTranslationDoesntExistAndMultiplePlu SetupDictionary("en", Array.Empty()); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("en")) - { - var translation = localizer.Plural(count, new[] { "míč", "{0} míče", "{0} míčů" }, count); - Assert.Equal(expected, translation); - } + var translation = localizer.Plural(count, new[] { "míč", "{0} míče", "{0} míčů" }, count); + + Assert.Equal(expected, translation); } [Theory] + [UseCulture("en")] [InlineData("ball", 1)] [InlineData("2 balls", 2)] public void LocalizerReturnsCorrectPluralFormIfMultiplePluraflFormsAreSpecified(string expected, int count) @@ -266,15 +254,14 @@ public void LocalizerReturnsCorrectPluralFormIfMultiplePluraflFormsAreSpecified( }, PluralizationRule.English); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, true, _logger.Object); - using (CultureScope.Create("en")) - { - var translation = localizer.Plural(count, new[] { "míč", "{0} míče", "{0} míčů" }, count); - Assert.Equal(expected, translation); - } + var translation = localizer.Plural(count, new[] { "míč", "{0} míče", "{0} míčů" }, count); + + Assert.Equal(expected, translation); } [Theory] + [UseCulture("ar-YE")] [InlineData(false, "hello", "hello")] [InlineData(true, "hello", "مرحبا")] public void LocalizerFallBackToParentCultureIfFallBackToParentUICulturesIsTrue(bool fallBackToParentCulture, string resourceKey, string expected) @@ -287,15 +274,14 @@ public void LocalizerFallBackToParentCultureIfFallBackToParentUICulturesIsTrue(b SetupDictionary("ar-YE", Array.Empty(), PluralizationRule.Arabic); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, fallBackToParentCulture, _logger.Object); - using (CultureScope.Create("ar-YE")) - { - var translation = localizer[resourceKey]; - Assert.Equal(expected, translation); - } + var translation = localizer[resourceKey]; + + Assert.Equal(expected, translation); } [Theory] + [UseCulture("ar-YE")] [InlineData(false, new[] { "مدونة", "منتج" })] [InlineData(true, new[] { "مدونة", "منتج", "قائمة", "صفحة", "مقالة" })] public void LocalizerReturnsGetAllStrings(bool includeParentCultures, string[] expected) @@ -315,12 +301,10 @@ public void LocalizerReturnsGetAllStrings(bool includeParentCultures, string[] e }, PluralizationRule.Arabic); var localizer = new PortableObjectStringLocalizer(null, _localizationManager.Object, false, _logger.Object); - using (CultureScope.Create("ar-YE")) - { - var translations = localizer.GetAllStrings(includeParentCultures).Select(l => l.Value).ToArray(); - Assert.Equal(expected.Length, translations.Length); - } + var translations = localizer.GetAllStrings(includeParentCultures).Select(l => l.Value).ToArray(); + + Assert.Equal(expected.Length, translations.Length); } [Fact] diff --git a/test/OrchardCore.Tests/Modules/OrchardCore.OpenId/OpenIdServerDeploymentSourceTests.cs b/test/OrchardCore.Tests/Modules/OrchardCore.OpenId/OpenIdServerDeploymentSourceTests.cs index 070e7ef05d9..68a6b46d61d 100644 --- a/test/OrchardCore.Tests/Modules/OrchardCore.OpenId/OpenIdServerDeploymentSourceTests.cs +++ b/test/OrchardCore.Tests/Modules/OrchardCore.OpenId/OpenIdServerDeploymentSourceTests.cs @@ -4,7 +4,7 @@ using OrchardCore.OpenId.Services; using OrchardCore.OpenId.Settings; using OrchardCore.Recipes.Models; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; using static OrchardCore.OpenId.Settings.OpenIdServerSettings; namespace OrchardCore.Tests.Modules.OrchardCore.OpenId diff --git a/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesMockHelper.cs b/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesMockHelper.cs deleted file mode 100644 index 5600e77ceab..00000000000 --- a/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesMockHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace OrchardCore.Tests.Modules.OrchardCore.Roles -{ - public static class RolesMockHelper - { - public static Mock> MockRoleManager() - where TRole : class - { - var store = new Mock>().Object; - var validators = new List>(); - validators.Add(new RoleValidator()); - - return new Mock>(store, validators, new UpperInvariantLookupNormalizer(), new IdentityErrorDescriber(), null); - } - } -} diff --git a/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesPermissionsHandlerTests.cs b/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesPermissionsHandlerTests.cs index 5f21f4de2ec..be1a13959d7 100644 --- a/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesPermissionsHandlerTests.cs +++ b/test/OrchardCore.Tests/Modules/OrchardCore.Roles/RolesPermissionsHandlerTests.cs @@ -1,7 +1,8 @@ using OrchardCore.Roles; using OrchardCore.Security; using OrchardCore.Security.Permissions; -using OrchardCore.Tests.Security; +using OrchardCore.Testing.Mocks; +using OrchardCore.Testing.Security; namespace OrchardCore.Tests.Modules.OrchardCore.Roles { @@ -119,15 +120,16 @@ public async Task IsCaseIsensitive(string required, bool authenticated) private static RolesPermissionsHandler CreatePermissionHandler(params Role[] roles) { - var roleManager = RolesMockHelper.MockRoleManager(); + var roleManagerMock = OrchardCoreMock.CreateRoleManager(); foreach (var role in roles) { - roleManager.Setup(m => m.FindByNameAsync(role.RoleName)).ReturnsAsync(role); + roleManagerMock.Setup(m => m.FindByNameAsync(role.RoleName)).ReturnsAsync(role); } var permissionGrantingService = new DefaultPermissionGrantingService(); - return new RolesPermissionsHandler(roleManager.Object, permissionGrantingService); + + return new RolesPermissionsHandler(roleManagerMock.Object, permissionGrantingService); } } } diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj index 1fb053a61fd..5f58cb74ca1 100644 --- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj +++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj @@ -48,6 +48,7 @@ + diff --git a/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalProviderFactoryTests.cs b/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalProviderFactoryTests.cs index 31b78fe2553..c4686bbc88a 100644 --- a/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalProviderFactoryTests.cs +++ b/test/OrchardCore.Tests/OrchardCore.Users/OrchardCore.Users.Core/DefaultUserClaimsPrincipalProviderFactoryTests.cs @@ -1,4 +1,5 @@ using OrchardCore.Security; +using OrchardCore.Testing.Mocks; using OrchardCore.Users; using OrchardCore.Users.Models; using OrchardCore.Users.Services; @@ -13,7 +14,7 @@ public class DefaultUserClaimsPrincipalProviderFactoryTests public async Task EnsurePrincipalHasExpectedClaims(bool emailVerified) { //Arrange - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var user = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo", Email = "foo@foo.com" }; userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.UserId); userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName); @@ -24,7 +25,7 @@ public async Task EnsurePrincipalHasExpectedClaims(bool emailVerified) userManager.Setup(m => m.IsEmailConfirmedAsync(user)).ReturnsAsync(emailVerified); } - var roleManager = UsersMockHelper.MockRoleManager().Object; + var roleManager = OrchardCoreMock.CreateRoleManager().Object; var options = new Mock>(); options.Setup(a => a.Value).Returns(new IdentityOptions()); diff --git a/test/OrchardCore.Tests/OrchardCore.Users/RegistrationControllerTests.cs b/test/OrchardCore.Tests/OrchardCore.Users/RegistrationControllerTests.cs index 53fb535a726..a6fd6a34e58 100644 --- a/test/OrchardCore.Tests/OrchardCore.Users/RegistrationControllerTests.cs +++ b/test/OrchardCore.Tests/OrchardCore.Users/RegistrationControllerTests.cs @@ -2,6 +2,7 @@ using OrchardCore.DisplayManagement.Notify; using OrchardCore.Email; using OrchardCore.Settings; +using OrchardCore.Testing.Mocks; using OrchardCore.Users; using OrchardCore.Users.Controllers; using OrchardCore.Users.Events; @@ -98,27 +99,6 @@ public async Task UsersCanRequireEmailConfirmation() Assert.Equal("ConfirmEmailSent", ((RedirectToActionResult)result).ActionName); } - private static Mock> MockSignInManager(UserManager userManager = null) where TUser : class, IUser - { - var context = new Mock(); - var manager = userManager ?? UsersMockHelper.MockUserManager().Object; - - var signInManager = new Mock>( - manager, - new HttpContextAccessor { HttpContext = context.Object }, - Mock.Of>(), - null, - null, - null, - null) - { CallBase = true }; - - signInManager.Setup(x => x.SignInAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask); - - return signInManager; - } - private static RegistrationController SetupRegistrationController() => SetupRegistrationController(new RegistrationSettings { @@ -128,7 +108,7 @@ private static RegistrationController SetupRegistrationController() private static RegistrationController SetupRegistrationController(RegistrationSettings registrationSettings) { var users = new List(); - var mockUserManager = UsersMockHelper.MockUserManager(); + var mockUserManager = OrchardCoreMock.CreateUserManager(); mockUserManager.Setup(um => um.FindByEmailAsync(It.IsAny())) .Returns(e => { @@ -152,6 +132,8 @@ private static RegistrationController SetupRegistrationController(RegistrationSe .Callback>((u, p, e) => users.Add(u)) .ReturnsAsync, IUserService, IUser>((u, p, e) => u); + var signInManagerMock = OrchardCoreMock.CreateSignInManager(mockUserManager.Object); + var urlHelperMock = new Mock(); urlHelperMock.Setup(urlHelper => urlHelper.Action(It.IsAny())); @@ -193,7 +175,7 @@ private static RegistrationController SetupRegistrationController(RegistrationSe .Returns(userService.Object); mockServiceProvider .Setup(x => x.GetService(typeof(SignInManager))) - .Returns(MockSignInManager(mockUserManager.Object).Object); + .Returns(signInManagerMock.Object); mockServiceProvider .Setup(x => x.GetService(typeof(ITempDataDictionaryFactory))) .Returns(Mock.Of()); diff --git a/test/OrchardCore.Tests/OrchardCore.Users/UserValidatorTests.cs b/test/OrchardCore.Tests/OrchardCore.Users/UserValidatorTests.cs index f5531c4471b..809b4092f90 100644 --- a/test/OrchardCore.Tests/OrchardCore.Users/UserValidatorTests.cs +++ b/test/OrchardCore.Tests/OrchardCore.Users/UserValidatorTests.cs @@ -1,3 +1,4 @@ +using OrchardCore.Testing.Mocks; using OrchardCore.Users; using OrchardCore.Users.Models; @@ -9,7 +10,7 @@ public class UserValidatorTests public async Task CanValidateUser() { // Arrange - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var user = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo", Email = "foo@foo.com" }; userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.UserId); userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName); @@ -28,7 +29,7 @@ public async Task ShouldRequireEmail() { // Arrange var describer = new IdentityErrorDescriber(); - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var user = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo" }; userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.UserId); userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName); @@ -48,7 +49,7 @@ public async Task ShouldRequireValidEmail() { // Arrange var describer = new IdentityErrorDescriber(); - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var user = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo", Email = "foo" }; userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.UserId); userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName); @@ -68,7 +69,7 @@ public async Task ShouldRequireUniqueEmail() { // Arrange var describer = new IdentityErrorDescriber(); - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var existingUser = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo", Email = "foo@foo.com" }; userManager.Setup(m => m.GetUserIdAsync(existingUser)).ReturnsAsync(existingUser.UserId); userManager.Setup(m => m.GetUserNameAsync(existingUser)).ReturnsAsync(existingUser.UserName); @@ -95,7 +96,7 @@ public async Task ShouldRequireUserNameIsNotAnEmailAddress() { // Arrange var describer = new IdentityErrorDescriber(); - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var user = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "foo@foo.com", Email = "foo" }; userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.UserId); userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName); @@ -115,7 +116,7 @@ public async Task ShouldRequireUniqueUserName() { // Arrange var describer = new IdentityErrorDescriber(); - var userManager = UsersMockHelper.MockUserManager(); + var userManager = OrchardCoreMock.CreateUserManager(); var existingUser = new User { UserId = Guid.NewGuid().ToString("n"), UserName = "Foo", Email = "bar@bar.com" }; userManager.Setup(m => m.GetUserIdAsync(existingUser)).ReturnsAsync(existingUser.UserId); userManager.Setup(m => m.GetUserNameAsync(existingUser)).ReturnsAsync(existingUser.UserName); diff --git a/test/OrchardCore.Tests/OrchardCore.Users/UsersMockHelper.cs b/test/OrchardCore.Tests/OrchardCore.Users/UsersMockHelper.cs deleted file mode 100644 index f69fab350a6..00000000000 --- a/test/OrchardCore.Tests/OrchardCore.Users/UsersMockHelper.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace OrchardCore.Tests.OrchardCore.Users -{ - public static class UsersMockHelper - { - public static Mock> MockUserManager() where TUser : class - { - var store = new Mock>(); - var identityOptions = new IdentityOptions(); - identityOptions.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+"; - identityOptions.User.RequireUniqueEmail = true; - - var mgr = new Mock>(store.Object, Options.Create(identityOptions), null, null, null, null, null, null, null); - mgr.Object.UserValidators.Add(new UserValidator(new IdentityErrorDescriber())); - mgr.Object.PasswordValidators.Add(new PasswordValidator()); - - return mgr; - } - - public static Mock> MockRoleManager() where TRole : class - { - var store = new Mock>().Object; - var roles = new List>(); - roles.Add(new RoleValidator()); - - return new Mock>(store, roles, new UpperInvariantLookupNormalizer(), new IdentityErrorDescriber(), null); - } - } -} diff --git a/test/OrchardCore.Tests/ResourceManagement/ResourceDefinitionTests.cs b/test/OrchardCore.Tests/ResourceManagement/ResourceDefinitionTests.cs index ba6cbaee378..04960097853 100644 --- a/test/OrchardCore.Tests/ResourceManagement/ResourceDefinitionTests.cs +++ b/test/OrchardCore.Tests/ResourceManagement/ResourceDefinitionTests.cs @@ -1,9 +1,12 @@ using OrchardCore.ResourceManagement; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.ResourceManagement { public class ResourceDefinitionTests { + private readonly static FileVersionProviderStub _fileVersionProviderStub = new(); + private readonly ResourceManifest _resourceManifest; public ResourceDefinitionTests() @@ -24,7 +27,7 @@ public void GetScriptResourceWithUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.js", "https://cdn.tld/foo.debug.js"); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = false }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal($"{applicationPath}/foo.js", tagBuilder.Attributes["src"]); @@ -39,7 +42,7 @@ public void GetScriptResourceWithBasePath(string basePath) .SetBasePath(basePath); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = false }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, String.Empty, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, String.Empty, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal($"{basePath}/foo.js", tagBuilder.Attributes["src"]); @@ -56,7 +59,7 @@ public void GetScriptResourceWithDebugUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.js", "https://cdn.tld/foo.debug.js"); var requireSettings = new RequireSettings { DebugMode = true, CdnMode = false }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal($"{applicationPath}/foo.debug.js", tagBuilder.Attributes["src"]); @@ -73,7 +76,7 @@ public void GetScriptResourceWithCdnUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.js", "https://cdn.tld/foo.debug.js"); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = true }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal("https://cdn.tld/foo.js", tagBuilder.Attributes["src"]); @@ -90,7 +93,7 @@ public void GetScriptResourceWithDebugCdnUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.js", "https://cdn.tld/foo.debug.js"); var requireSettings = new RequireSettings { DebugMode = true, CdnMode = true, CdnBaseUrl = "https://hostcdn.net" }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal("https://cdn.tld/foo.debug.js", tagBuilder.Attributes["src"]); @@ -111,7 +114,7 @@ public void GetLocalScriptResourceWithCdnBaseUrl(string applicationPath, string .SetUrl(url, url); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = true, CdnBaseUrl = "https://hostcdn.net" }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal(expected, tagBuilder.Attributes["src"]); @@ -128,7 +131,7 @@ public void GetScriptResourceWithInlineContent(string applicationPath) var requireSettings = new RequireSettings() .UseCdnBaseUrl("https://hostcdn.net"); - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("script", tagBuilder.TagName); Assert.Equal("console.log('foo');", ReadIHtmlContent(tagBuilder.InnerHtml)); @@ -145,7 +148,7 @@ public void GetStyleResourceWithUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.css", "https://cdn.tld/foo.debug.css"); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = false }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("link", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -164,7 +167,7 @@ public void GetStyleResourceWithDebugUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.css", "https://cdn.tld/foo.debug.css"); var requireSettings = new RequireSettings { DebugMode = true, CdnMode = false }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("link", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -183,7 +186,7 @@ public void GetStyleResourceWithCdnUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.css", "https://cdn.tld/foo.debug.css"); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = true }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("link", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -206,7 +209,7 @@ public void GetLocalStyleResourceWithCdnBaseUrl(string applicationPath, string u .SetUrl(url, url); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = true, CdnBaseUrl = "https://hostcdn.net" }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("link", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -225,7 +228,7 @@ public void GetStyleResourceWithDebugCdnUrl(string applicationPath) .SetCdn("https://cdn.tld/foo.css", "https://cdn.tld/foo.debug.css"); var requireSettings = new RequireSettings { DebugMode = true, CdnMode = true }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("link", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -245,7 +248,7 @@ public void GetStyleResourceWithAttributes(string applicationPath) .SetAttribute("media", "all"); var requireSettings = new RequireSettings { DebugMode = false, CdnMode = false }; - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("link", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -265,7 +268,7 @@ public void GetStyleResourceWithInlineContent(string applicationPath) var requireSettings = new RequireSettings() .UseCdnBaseUrl("https://cdn.net"); - var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, StubFileVersionProvider.Instance); + var tagBuilder = resourceDefinition.GetTagBuilder(requireSettings, applicationPath, _fileVersionProviderStub); Assert.Equal("style", tagBuilder.TagName); Assert.Equal("text/css", tagBuilder.Attributes["type"]); @@ -283,18 +286,5 @@ private static string ReadIHtmlContent(IHtmlContent content) } #endregion - - #region Stubs - private class StubFileVersionProvider : IFileVersionProvider - { - public static StubFileVersionProvider Instance { get; } = new StubFileVersionProvider(); - - public string AddFileVersionToPath(PathString requestPathBase, string path) - { - return path; - } - } - - #endregion } } diff --git a/test/OrchardCore.Tests/ResourceManagement/ResourceManagerTests.cs b/test/OrchardCore.Tests/ResourceManagement/ResourceManagerTests.cs index 39b3962bf17..775f8fe1afb 100644 --- a/test/OrchardCore.Tests/ResourceManagement/ResourceManagerTests.cs +++ b/test/OrchardCore.Tests/ResourceManagement/ResourceManagerTests.cs @@ -3,6 +3,7 @@ using AngleSharp.Html.Dom; using OrchardCore.ResourceManagement; using ResourceLocation = OrchardCore.ResourceManagement.ResourceLocation; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.ResourceManagement { @@ -10,6 +11,8 @@ public class ResourceManagerTests { private const string basePath = "http://host"; + private readonly static FileVersionProviderStub _fileVersionProviderStub = new(); + private readonly IBrowsingContext browsingContext; public ResourceManagerTests() @@ -31,7 +34,7 @@ public void FindResourceFromManifestProviders() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var resourceDefinition = resourceManager.FindResource(new RequireSettings { Type = "foo", Name = "bar2" }); @@ -48,7 +51,7 @@ public void RegisterResouceUrl() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var requireSetting = resourceManager.RegisterUrl("foo", "schema://domain.ext/resource", "schema://domain.ext/resource.debug"); @@ -65,7 +68,7 @@ public void RegisteredResouceUrlIsRequired() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterUrl("foo", "schema://domain.ext/resource", "schema://domain.ext/resource.debug"); @@ -91,7 +94,7 @@ public void RegisteredResouceNameIsRequired() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "bar"); @@ -134,7 +137,7 @@ public void RequireDependencies() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "last-resource"); @@ -183,7 +186,7 @@ public void RequireCircularDependenciesShouldThrowException() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "required"); @@ -209,7 +212,7 @@ public void RequireCircularNestedDependencyShouldThrowException() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "requires-indirect-dependency"); @@ -234,7 +237,7 @@ public void RequireByDependencyResourceThatDependsOnLastPositionedResourceShould var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "resource"); @@ -268,7 +271,7 @@ public void RequireFirstPositionedResourceThatDependsOnByDependencyResourceShoul var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "first-resource"); @@ -305,7 +308,7 @@ public void RequireFirstPositionedResourceWithDependencyToResourcePositionedLast var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "first-resource"); @@ -329,7 +332,7 @@ public void RemoveRequiredResource() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "required"); @@ -362,7 +365,7 @@ public void RemoveRequiredResourceDependency() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterResource("foo", "required"); @@ -385,7 +388,7 @@ public void RegisterHeadScript() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var customScript = ""; @@ -401,7 +404,7 @@ public void RegisterFootScript() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var customScript = ""; @@ -417,7 +420,7 @@ public void RegisterStyle() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var customStyle = ""; @@ -433,7 +436,7 @@ public void RegisterLink() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var linkEntry = new LinkEntry @@ -454,7 +457,7 @@ public void RegisterMeta() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var metaEntry = new MetaEntry(name: "foo", content: "bar"); @@ -471,7 +474,7 @@ public async Task AppendMeta() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var first = new MetaEntry(name: "keywords", content: "bar") @@ -499,7 +502,7 @@ public async Task RenderMeta() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterMeta(new MetaEntry { Charset = "utf-8" }); @@ -526,7 +529,7 @@ public async Task RenderHeadLink() { var resourceManager = new ResourceManager( new OptionsWrapper(new ResourceManagementOptions()), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterUrl("stylesheet", "other.min.css", "other.css"); // Should not be rendered @@ -562,7 +565,7 @@ public async Task RenderStylesheet() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); resourceManager.RegisterLink(new LinkEntry { Rel = "icon", Href = "/favicon.ico" }); // Should not be rendered @@ -619,7 +622,7 @@ public async Task RenderHeadScript() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); // Require resource @@ -671,7 +674,7 @@ public async Task RenderFootScript() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); // Require resource @@ -733,7 +736,7 @@ public async Task RenderHeadAndFootScriptWithSameDependency() var resourceManager = new ResourceManager ( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); // Require resource. @@ -791,7 +794,7 @@ public async Task RenderLocalScript() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var requireSetting = resourceManager.RegisterResource("script", "required"); @@ -831,7 +834,7 @@ public async Task RenderLocalStyle() var resourceManager = new ResourceManager( new OptionsWrapper(options), - StubFileVersionProvider.Instance + _fileVersionProviderStub ); var requireSetting = resourceManager.RegisterResource("stylesheet", "required").AtLocation(ResourceLocation.Inline); @@ -867,33 +870,5 @@ private async Task ParseHtmlAsync(IHtmlContent content) } #endregion - - #region Stubs - private class StubResourceManifestProvider : IConfigureOptions - { - private readonly Action _configureManifestAction; - - public StubResourceManifestProvider(Action configureManifestAction) - { - _configureManifestAction = configureManifestAction; - } - - public void Configure(ResourceManagementOptions options) - { - _configureManifestAction?.Invoke(options); - } - } - - private class StubFileVersionProvider : IFileVersionProvider - { - public static StubFileVersionProvider Instance { get; } = new StubFileVersionProvider(); - - public string AddFileVersionToPath(PathString requestPathBase, string path) - { - return path; - } - } - - #endregion } } diff --git a/test/OrchardCore.Tests/Routing/AutorouteEntriesTests.cs b/test/OrchardCore.Tests/Routing/AutorouteEntriesTests.cs index 8f23e953149..3c1deb21a66 100644 --- a/test/OrchardCore.Tests/Routing/AutorouteEntriesTests.cs +++ b/test/OrchardCore.Tests/Routing/AutorouteEntriesTests.cs @@ -5,6 +5,7 @@ using OrchardCore.Environment.Shell.Models; using OrchardCore.Locking; using OrchardCore.Locking.Distributed; +using OrchardCore.Testing.Stubs; namespace OrchardCore.Tests.Routing { @@ -18,7 +19,7 @@ public async Task ShouldGetContainedEntryByPath() await shellContext.CreateScope().UsingAsync(scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Act var initialEntries = new List() @@ -34,7 +35,7 @@ await shellContext.CreateScope().UsingAsync(scope => await shellContext.CreateScope().UsingAsync(async scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Test (var result, var containedEntry) = await entries.TryGetEntryByPathAsync("/contained-path"); @@ -52,7 +53,7 @@ public async Task ShouldGetEntryByContainedContentItemId() await shellContext.CreateScope().UsingAsync(scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Act var initialEntries = new List() @@ -68,7 +69,7 @@ await shellContext.CreateScope().UsingAsync(scope => await shellContext.CreateScope().UsingAsync(async scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Test (var result, var containedEntry) = await entries.TryGetEntryByContentItemIdAsync("contained"); @@ -86,7 +87,7 @@ public async Task RemovesContainedEntriesWhenContainerRemoved() await shellContext.CreateScope().UsingAsync(scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Act var initialEntries = new List() @@ -104,7 +105,7 @@ await shellContext.CreateScope().UsingAsync(scope => await shellContext.CreateScope().UsingAsync(async scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Test (var result, var containedEntry) = await entries.TryGetEntryByPathAsync("/contained-path"); @@ -121,7 +122,7 @@ public async Task RemovesContainedEntriesWhenDeleted() await shellContext.CreateScope().UsingAsync(scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Act var initialEntries = new List() @@ -146,7 +147,7 @@ await shellContext.CreateScope().UsingAsync(scope => await shellContext.CreateScope().UsingAsync(async scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Test (var result, var containedEntry) = await entries.TryGetEntryByPathAsync("/contained-path2"); @@ -163,7 +164,7 @@ public async Task RemovesOldContainedPaths() await shellContext.CreateScope().UsingAsync(scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Act var initialEntries = new List() @@ -187,7 +188,7 @@ await shellContext.CreateScope().UsingAsync(scope => await shellContext.CreateScope().UsingAsync(async scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Test (var result, var containedEntry) = await entries.TryGetEntryByPathAsync("/contained-path-old"); @@ -204,7 +205,7 @@ public async Task RemovesOldPaths() await shellContext.CreateScope().UsingAsync(scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Act entries.AddEntries(new[] { new AutorouteEntry("container", "container-path", null, null) }); @@ -216,7 +217,7 @@ await shellContext.CreateScope().UsingAsync(scope => await shellContext.CreateScope().UsingAsync(async scope => { - var entries = scope.ServiceProvider.GetRequiredService(); + var entries = scope.ServiceProvider.GetRequiredService(); // Test (var result, var containedEntry) = await entries.TryGetEntryByPathAsync("/container-path"); @@ -237,23 +238,10 @@ private static ShellContext CreateShellContext() private static IServiceProvider CreateServiceProvider() { var services = new ServiceCollection(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); - return services.AddLogging().BuildServiceProvider(); - } - public interface IStubAutorouteEntries : IAutorouteEntries - { - void AddEntries(IEnumerable entries); - void RemoveEntries(IEnumerable entries); - } - - private class StubAutorouteEntries : AutorouteEntries, IStubAutorouteEntries - { - public StubAutorouteEntries() : base(null) { } - public new void AddEntries(IEnumerable entries) => base.AddEntries(entries); - public new void RemoveEntries(IEnumerable entries) => base.RemoveEntries(entries); - protected override Task InitializeEntriesAsync() => Task.CompletedTask; + return services.AddLogging().BuildServiceProvider(); } } } diff --git a/test/OrchardCore.Tests/Security/PermissionHandlerHelper.cs b/test/OrchardCore.Tests/Security/PermissionHandlerHelper.cs deleted file mode 100644 index a37386bb05f..00000000000 --- a/test/OrchardCore.Tests/Security/PermissionHandlerHelper.cs +++ /dev/null @@ -1,56 +0,0 @@ -using OrchardCore.Security; -using OrchardCore.Security.Permissions; - -namespace OrchardCore.Tests.Security -{ - public static class PermissionHandlerHelper - { - public static AuthorizationHandlerContext CreateTestAuthorizationHandlerContext(Permission required, string[] allowed = null, bool authenticated = false) - { - var identity = authenticated ? new ClaimsIdentity("Test") : new ClaimsIdentity(); - - if (allowed != null) - { - foreach (var permissionName in allowed) - { - var permission = new Permission(permissionName); - identity.AddClaim(permission); - } - - } - - var principal = new ClaimsPrincipal(identity); - - return new AuthorizationHandlerContext( - new[] { new PermissionRequirement(required) }, - principal, - null); - } - - public static async Task SuccessAsync(this AuthorizationHandlerContext context, params string[] permissionNames) - { - var handler = new FakePermissionHandler(permissionNames); - await handler.HandleAsync(context); - } - - private class FakePermissionHandler : AuthorizationHandler - { - private readonly HashSet _permissionNames; - - public FakePermissionHandler(string[] permissionNames) - { - _permissionNames = new HashSet(permissionNames, StringComparer.OrdinalIgnoreCase); - } - - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) - { - if (_permissionNames.Contains(requirement.Permission.Name)) - { - context.Succeed(requirement); - } - - return Task.CompletedTask; - } - } - } -} diff --git a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs index 49f3b26258a..0081ffa7387 100644 --- a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs +++ b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs @@ -5,7 +5,7 @@ using OrchardCore.Environment.Shell.Builders.Models; using OrchardCore.Environment.Shell.Descriptor.Models; using OrchardCore.Environment.Shell.Models; -using OrchardCore.Tests.Stubs; +using OrchardCore.Testing.Stubs; using StartupBase = OrchardCore.Modules.StartupBase; namespace OrchardCore.Tests.Shell @@ -37,8 +37,8 @@ public ShellContainerFactoryTests() applicationServices.AddScoped(); _shellContainerFactory = new ShellContainerFactory( - new StubHostingEnvironment(), - new StubExtensionManager(), + new HostingEnvironmentStub(), + new NullExtensionManager(), _applicationServiceProvider = applicationServices.BuildServiceProvider(), applicationServices ); diff --git a/test/OrchardCore.Tests/Stubs/MemoryFileBuilder.cs b/test/OrchardCore.Tests/Stubs/MemoryFileBuilder.cs deleted file mode 100644 index e9698e72ed9..00000000000 --- a/test/OrchardCore.Tests/Stubs/MemoryFileBuilder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using OrchardCore.Deployment; - -namespace OrchardCore.Tests.Stubs -{ - /// - /// In memory file builder that uses a dictionary as virtual file system. - /// Intended for unit testing. - /// - public class MemoryFileBuilder - : IFileBuilder - { - public Dictionary VirtualFiles { get; private set; } = new Dictionary(); - - public async Task SetFileAsync(string subpath, Stream stream) - { - using var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - VirtualFiles[subpath] = ms.ToArray(); - } - - /// - /// Read the contents of a file built with this file builder, using the specified encoding. - /// - /// Path and/or file name - /// Encoding used to convert the byte array to string - /// - public string GetFileContents(string subpath, Encoding encoding) - { - return encoding.GetString(VirtualFiles[subpath]); - } - } -} diff --git a/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs b/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs deleted file mode 100644 index e839c4e02d7..00000000000 --- a/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs +++ /dev/null @@ -1,53 +0,0 @@ -using OrchardCore.Environment.Extensions; -using OrchardCore.Environment.Extensions.Features; - -namespace OrchardCore.Tests.Stubs -{ - public class StubExtensionManager : IExtensionManager - { - public IEnumerable GetDependentFeatures(string featureId) - { - throw new NotImplementedException(); - } - - public IExtensionInfo GetExtension(string extensionId) - { - throw new NotImplementedException(); - } - - public IEnumerable GetExtensions() - { - throw new NotImplementedException(); - } - - public IEnumerable GetFeatureDependencies(string featureId) - { - throw new NotImplementedException(); - } - - public IEnumerable GetFeatures() - { - return Enumerable.Empty(); - } - - public IEnumerable GetFeatures(string[] featureIdsToLoad) - { - throw new NotImplementedException(); - } - - public Task LoadExtensionAsync(IExtensionInfo extensionInfo) - { - throw new NotImplementedException(); - } - - public Task> LoadFeaturesAsync() - { - throw new NotImplementedException(); - } - - public Task> LoadFeaturesAsync(string[] featureIdsToLoad) - { - throw new NotImplementedException(); - } - } -} diff --git a/test/OrchardCore.Tests/Stubs/StubHostingEnvironment.cs b/test/OrchardCore.Tests/Stubs/StubHostingEnvironment.cs deleted file mode 100644 index 1ea1f97456e..00000000000 --- a/test/OrchardCore.Tests/Stubs/StubHostingEnvironment.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace OrchardCore.Tests.Stubs -{ - public class StubHostingEnvironment : IHostEnvironment - { - private string _rootPath; - private IFileProvider _contentRootFileProvider; - - public StubHostingEnvironment() - { - ApplicationName = GetType().Assembly.GetName().Name; - } - - public string EnvironmentName { get; set; } = "Stub"; - - public string ApplicationName { get; set; } - - public string WebRootPath { get; set; } - - public IFileProvider WebRootFileProvider { get; set; } - - public string ContentRootPath - { - get { return _rootPath ?? Directory.GetCurrentDirectory(); } - set - { - _contentRootFileProvider = new PhysicalFileProvider(value); - _rootPath = value; - } - } - - public IFileProvider ContentRootFileProvider - { - get { return _contentRootFileProvider; } - set { _contentRootFileProvider = value; } - } - } -} diff --git a/test/OrchardCore.Tests/Stubs/StubPoFileLocationProvider.cs b/test/OrchardCore.Tests/Stubs/StubPoFileLocationProvider.cs deleted file mode 100644 index 637565fbc58..00000000000 --- a/test/OrchardCore.Tests/Stubs/StubPoFileLocationProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using OrchardCore.Localization; - -namespace OrchardCore.Tests -{ - public class StubPoFileLocationProvider : ILocalizationFileLocationProvider - { - private readonly IFileProvider _fileProvider; - private readonly string _resourcesContainer; - - public StubPoFileLocationProvider(IHostEnvironment hostingEnvironment, IOptions localizationOptions) - { - var rootPath = new DirectoryInfo(hostingEnvironment.ContentRootPath).Parent.Parent.Parent.FullName; - _fileProvider = new PhysicalFileProvider(rootPath); - _resourcesContainer = localizationOptions.Value.ResourcesPath; - } - - public IEnumerable GetLocations(string cultureName) - { - yield return _fileProvider.GetFileInfo(Path.Combine(_resourcesContainer, cultureName + ".po")); - } - } -} diff --git a/test/OrchardCore.Tests/Stubs/TestShapeBindingResolver.cs b/test/OrchardCore.Tests/Stubs/TestShapeBindingResolver.cs deleted file mode 100644 index d3e36ada12b..00000000000 --- a/test/OrchardCore.Tests/Stubs/TestShapeBindingResolver.cs +++ /dev/null @@ -1,33 +0,0 @@ -using OrchardCore.DisplayManagement; -using OrchardCore.DisplayManagement.Descriptors; - -namespace OrchardCore.Tests.Stubs -{ - public class TestShapeBindingsDictionary : Dictionary - { - public TestShapeBindingsDictionary() - : base(StringComparer.OrdinalIgnoreCase) { } - } - - public class TestShapeBindingResolver : IShapeBindingResolver - { - private readonly TestShapeBindingsDictionary _shapeBindings; - - public TestShapeBindingResolver(TestShapeBindingsDictionary shapeBindings) - { - _shapeBindings = shapeBindings; - } - - public Task GetShapeBindingAsync(string shapeType) - { - if (_shapeBindings.TryGetValue(shapeType, out var binding)) - { - return Task.FromResult(binding); - } - else - { - return Task.FromResult(null); - } - } - } -} diff --git a/test/OrchardCore.Tests/Stubs/TestShapeTableManager.cs b/test/OrchardCore.Tests/Stubs/TestShapeTableManager.cs deleted file mode 100644 index 9646c658d37..00000000000 --- a/test/OrchardCore.Tests/Stubs/TestShapeTableManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -using OrchardCore.DisplayManagement.Descriptors; -using OrchardCore.DisplayManagement.Theming; -using OrchardCore.Environment.Extensions; - -namespace OrchardCore.Tests.Stubs -{ - public class TestShapeTableManager : IShapeTableManager - { - private readonly ShapeTable _defaultShapeTable; - - public TestShapeTableManager(ShapeTable defaultShapeTable) - { - _defaultShapeTable = defaultShapeTable; - } - - public ShapeTable GetShapeTable(string themeId) - { - return _defaultShapeTable; - } - } - - public class MockThemeManager : IThemeManager - { - private IExtensionInfo _dec; - public MockThemeManager(IExtensionInfo des) - { - _dec = des; - } - public Task GetThemeAsync() - { - return Task.FromResult(_dec); - } - } -} diff --git a/test/OrchardCore.Tests/Workflows/WorkflowManagerTests.cs b/test/OrchardCore.Tests/Workflows/WorkflowManagerTests.cs index 7f1fddb9cd8..8782f6ecc70 100644 --- a/test/OrchardCore.Tests/Workflows/WorkflowManagerTests.cs +++ b/test/OrchardCore.Tests/Workflows/WorkflowManagerTests.cs @@ -1,11 +1,7 @@ using OrchardCore.DisplayManagement; -using OrchardCore.Locking.Distributed; -using OrchardCore.Modules; -using OrchardCore.Scripting; -using OrchardCore.Scripting.JavaScript; +using OrchardCore.Testing.Mocks; using OrchardCore.Tests.Workflows.Activities; using OrchardCore.Workflows.Activities; -using OrchardCore.Workflows.Evaluators; using OrchardCore.Workflows.Models; using OrchardCore.Workflows.Services; using OrchardCore.Workflows.WorkflowContextProviders; @@ -18,7 +14,7 @@ public class WorkflowManagerTests public async Task CanExecuteSimpleWorkflow() { var serviceProvider = CreateServiceProvider(); - var scriptEvaluator = CreateWorkflowScriptEvaluator(serviceProvider); + var scriptEvaluator = OrchardCoreMock.CreateWorkflowScriptEvaluator(serviceProvider); var localizer = new Mock>(); var stringBuilder = new StringBuilder(); @@ -47,7 +43,7 @@ public async Task CanExecuteSimpleWorkflow() } }; - var workflowManager = CreateWorkflowManager(serviceProvider, new IActivity[] { addTask, writeLineTask, setOutputTask }, workflowType); + var workflowManager = OrchardCoreMock.CreateWorkflowManager(serviceProvider, new IActivity[] { addTask, writeLineTask, setOutputTask }, workflowType); var a = 10d; var b = 22d; var expectedSum = a + b; @@ -71,60 +67,5 @@ private IServiceProvider CreateServiceProvider() return services.BuildServiceProvider(); } - - private IWorkflowScriptEvaluator CreateWorkflowScriptEvaluator(IServiceProvider serviceProvider) - { - var memoryCache = new MemoryCache(new MemoryCacheOptions()); - var javaScriptEngine = new JavaScriptEngine(memoryCache); - var workflowContextHandlers = new Resolver>(serviceProvider); - var globalMethodProviders = new IGlobalMethodProvider[0]; - var scriptingManager = new DefaultScriptingManager(new[] { javaScriptEngine }, globalMethodProviders); - - return new JavaScriptWorkflowScriptEvaluator( - scriptingManager, - workflowContextHandlers.Resolve(), - new Mock>().Object - ); - } - - private WorkflowManager CreateWorkflowManager( - IServiceProvider serviceProvider, - IEnumerable activities, - WorkflowType workflowType - ) - { - var workflowValueSerializers = new Resolver>(serviceProvider); - var activityLibrary = new Mock(); - var workflowTypeStore = new Mock(); - var workflowStore = new Mock(); - var workflowIdGenerator = new Mock(); - workflowIdGenerator.Setup(x => x.GenerateUniqueId(It.IsAny())).Returns(IdGenerator.GenerateId()); - var distributedLock = new Mock(); - var workflowManagerLogger = new Mock>(); - var workflowContextLogger = new Mock>(); - var missingActivityLogger = new Mock>(); - var missingActivityLocalizer = new Mock>(); - var clock = new Mock(); - var workflowManager = new WorkflowManager( - activityLibrary.Object, - workflowTypeStore.Object, - workflowStore.Object, - workflowIdGenerator.Object, - workflowValueSerializers, - distributedLock.Object, - workflowManagerLogger.Object, - missingActivityLogger.Object, - missingActivityLocalizer.Object, - clock.Object); - - foreach (var activity in activities) - { - activityLibrary.Setup(x => x.InstantiateActivity(activity.Name)).Returns(activity); - } - - workflowTypeStore.Setup(x => x.GetAsync(workflowType.Id)).Returns(Task.FromResult(workflowType)); - - return workflowManager; - } } }