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