Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce OrchardCore.Testing APIs #13018

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bff07e3
Add OC.Testing project
hishamco Dec 24, 2022
8e77805
Add UseCultureAttribute
hishamco Dec 24, 2022
aa81d46
Add overload that accepts CultureInfo
hishamco Dec 24, 2022
49faca3
Use UseCultureAttribute
hishamco Dec 24, 2022
e4a039c
Replace xunit.extensibility.core with xunit package
hishamco Dec 24, 2022
a247ce1
Move stubs to OC.Testing
hishamco Dec 24, 2022
c7dfd50
Fix broken test
hishamco Dec 24, 2022
bdd6714
Add OrchardCoreMock.CreateSmtpService()
hishamco Dec 24, 2022
2d178b6
Add OrchardCoreWorkflowMocks
hishamco Dec 24, 2022
753b2bf
Move PermissionHandlerHelper to OC.Testing.Security
hishamco Dec 24, 2022
03c771c
Move AutorouteEntriesStub to OC.Testing
hishamco Dec 25, 2022
12069e8
Move FileVersionProviderStub to OC.Testing
hishamco Dec 25, 2022
55ee921
Move UsersMockHelpers to OrchardCore.SecurityMocks
hishamco Dec 25, 2022
2886364
Move HttpContentExtensions & HttpRequestExtensions to OC.Testing
hishamco Dec 31, 2022
336b574
Move TablePrefixGenerator to OC.Testing
hishamco Dec 31, 2022
78bc2b6
Move secutity classes to OC.Testing
hishamco Dec 31, 2022
116e7df
Move TestRecipeHarvester to OC.Testing
hishamco Dec 31, 2022
12872ad
Split SiteContextExtensions into another file
hishamco Dec 31, 2022
7ce18aa
Remove unnecessary TestRecipeHarvester
hishamco Dec 31, 2022
fee6883
Move OrchardCoreTestFixture into OC.Testing
hishamco Dec 31, 2022
b07c23c
Move ModuleNameProvider into OC.Testing
hishamco Dec 31, 2022
ead2bbf
Refactoring
hishamco Dec 31, 2022
674ed8a
Merge branch 'main' into hishamco/testing
hishamco Dec 31, 2022
bae8077
Merge branch 'main' into hishamco/testing
hishamco Jan 6, 2023
6025dbb
Fix merge issue
hishamco Jan 6, 2023
db53d7b
Move common namespaces into global usings
hishamco Jan 6, 2023
3ce3591
Add readonly modifier
hishamco Jan 6, 2023
137b5de
TestRecipeHarvester -> RecipeHarvesterStub
hishamco Jan 6, 2023
9a0f2a5
Move SuccessAsync() to AuthorizationHandlerContextExtensions
hishamco Jan 13, 2023
b1d4286
Replace RolesMockHelper wtih OrchardCoreMock.CreateRoleManager()
hishamco Jan 13, 2023
b89d4d1
CreateUserManagerMock -> CreateUserManager
hishamco Jan 13, 2023
fcf7c12
OrchardCoreMock.CreateSignInManager() should returns mocked object
hishamco Jan 13, 2023
0cc8fab
Merge branch 'main' into hishamco/testing
hishamco Jan 13, 2023
797fe4d
Merge branch 'main' into hishamco/testing
hishamco Feb 24, 2023
af0d321
Merge branch 'main' into hishamco/testing
hishamco Feb 25, 2023
262b7ea
Address feedback
hishamco Feb 25, 2023
9b145ae
Revert changes in OC.Cms.Web
hishamco Feb 25, 2023
c604fec
Add docs
hishamco Feb 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 0 additions & 4 deletions src/OrchardCore.Cms.Web/OrchardCore.Cms.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
<Nullable>enable</Nullable>
<ImplicitUsings Condition="'$(RazorRuntimeCompilation)' == 'false'">enable</ImplicitUsings>
</PropertyGroup>
<!-- For Unit Tests-->
<ItemGroup>
<InternalsVisibleTo Include="OrchardCore.Tests" />
</ItemGroup>

<!-- Watcher include and excludes -->
<ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/OrchardCore.Cms.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@
app.UseOrchardCore();

app.Run();

public partial class Program
hishamco marked this conversation as resolved.
Show resolved Hide resolved
{

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
29 changes: 29 additions & 0 deletions src/OrchardCore/OrchardCore.Testing/Apis/ISiteContext.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
9 changes: 9 additions & 0 deletions src/OrchardCore/OrchardCore.Testing/Apis/ISiteContextOfT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using OrchardCore.Testing.Infrastructure;

namespace OrchardCore.Testing.Apis
{
public interface ISiteContext<TSiteStartup> : ISiteContext where TSiteStartup : class
{
static OrchardCoreTestFixture<TSiteStartup> Site { get; }
}
}
Original file line number Diff line number Diff line change
@@ -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<PermissionRequirement>
public class PermissionContextAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly PermissionsContext _permissionsContext;

Expand All @@ -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;
Expand Down Expand Up @@ -88,20 +94,4 @@ private void GetGrantingNamesInternal(Permission permission, HashSet<string> sta
}
}
}

public class PermissionsContext
{
public IEnumerable<Permission> AuthorizedPermissions { get; set; } = Enumerable.Empty<Permission>();

public bool UsePermissionsContext { get; set; } = false;
}

internal class StubIdentity : IIdentity
{
public string AuthenticationType => "TEST TEST";

public bool IsAuthenticated => true;

public string Name => "Mr Robot";
}
}
Original file line number Diff line number Diff line change
@@ -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<Permission>();
UsePermissionsContext = false;
}
public IEnumerable<Permission> AuthorizedPermissions { get; set; }

public bool UsePermissionsContext { get; set; }
}
}
149 changes: 149 additions & 0 deletions src/OrchardCore/OrchardCore.Testing/Apis/SiteContextBase.cs
Original file line number Diff line number Diff line change
@@ -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<TSiteStartup> : ISiteContext<TSiteStartup> where TSiteStartup : class
{
static SiteContextBase()
{
Site = new OrchardCoreTestFixture<TSiteStartup>();
ShellHost = Site.Services.GetRequiredService<IShellHost>();
ShellSettingsManager = Site.Services.GetRequiredService<IShellSettingsManager>();
DefaultTenantClient = Site.CreateDefaultClient();
}

public SiteContextBase()
{
Options = new SiteContextOptions();
}

public static OrchardCoreTestFixture<TSiteStartup> 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<IShellFeaturesManager>();
var recipeHarvesters = scope.ServiceProvider.GetRequiredService<IEnumerable<IRecipeHarvester>>();
var recipeExecutor = scope.ServiceProvider.GetRequiredService<IRecipeExecutor>();

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<string, object>(), CancellationToken.None);
});
}

public void Dispose() => Client?.Dispose();

private async Task<HttpResponseMessage> 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();
}
}
}
Loading