diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdfced8..e1138fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,12 +10,8 @@ env: jobs: test: - runs-on: ${{ matrix.os }} + runs-on: windows-latest name: Build & Test - strategy: - fail-fast: false - matrix: - os: [ ubuntu-latest, windows-latest ] steps: - uses: actions/checkout@v4 - name: Setup .NET Core @@ -29,10 +25,5 @@ jobs: - name: Install Playwright browsers & dependencies run: pwsh test/OrchardCoreContrib.Testing.UI.Tests/bin/Release/net8.0/playwright.ps1 install --with-deps - name: Test - run: | - if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then - xvfb-run dotnet test test/OrchardCoreContrib.Testing.UI.Tests -c Release --no-restore --verbosity normal - else + run: dotnet test test/OrchardCoreContrib.Testing.UI.Tests -c Release --no-restore --verbosity normal - fi - shell: bash diff --git a/.gitignore b/.gitignore index 8a30d25..3b81dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +src/OrchardCoreContrib.Testing.Web/Localization diff --git a/OrchardCoreContrib.Testing.sln b/OrchardCoreContrib.Testing.sln index 5d6aa80..c2560c1 100644 --- a/OrchardCoreContrib.Testing.sln +++ b/OrchardCoreContrib.Testing.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34701.34 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{20F306B8-D63F-4A61-AF6E-64B4E0918E30}" EndProject @@ -11,6 +11,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCoreContrib.Testing. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCoreContrib.Testing.UI.Tests", "test\OrchardCoreContrib.Testing.UI.Tests\OrchardCoreContrib.Testing.UI.Tests.csproj", "{985F5AD6-8C18-4E63-A35C-A5673F237A4D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCoreContrib.Testing", "src\OrchardCoreContrib.Testing\OrchardCoreContrib.Testing.csproj", "{60B9E226-55FF-4B5C-BFFE-57A14DE6CC58}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCoreContrib.Testing.Web", "src\OrchardCoreContrib.Testing.Web\OrchardCoreContrib.Testing.Web.csproj", "{A0FE9046-5B92-4C73-AB51-33CE15F14917}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCoreContrib.Testing.Tests", "test\OrchardCoreContrib.Testing.Tests\OrchardCoreContrib.Testing.Tests.csproj", "{F0C2E52D-4412-4A18-B0F7-FD99F85F192C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,6 +31,18 @@ Global {985F5AD6-8C18-4E63-A35C-A5673F237A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU {985F5AD6-8C18-4E63-A35C-A5673F237A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU {985F5AD6-8C18-4E63-A35C-A5673F237A4D}.Release|Any CPU.Build.0 = Release|Any CPU + {60B9E226-55FF-4B5C-BFFE-57A14DE6CC58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60B9E226-55FF-4B5C-BFFE-57A14DE6CC58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60B9E226-55FF-4B5C-BFFE-57A14DE6CC58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60B9E226-55FF-4B5C-BFFE-57A14DE6CC58}.Release|Any CPU.Build.0 = Release|Any CPU + {A0FE9046-5B92-4C73-AB51-33CE15F14917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0FE9046-5B92-4C73-AB51-33CE15F14917}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0FE9046-5B92-4C73-AB51-33CE15F14917}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0FE9046-5B92-4C73-AB51-33CE15F14917}.Release|Any CPU.Build.0 = Release|Any CPU + {F0C2E52D-4412-4A18-B0F7-FD99F85F192C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0C2E52D-4412-4A18-B0F7-FD99F85F192C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0C2E52D-4412-4A18-B0F7-FD99F85F192C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0C2E52D-4412-4A18-B0F7-FD99F85F192C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -32,6 +50,9 @@ Global GlobalSection(NestedProjects) = preSolution {A9E5A40B-78F8-4F02-9E73-C74F395B5BBD} = {20F306B8-D63F-4A61-AF6E-64B4E0918E30} {985F5AD6-8C18-4E63-A35C-A5673F237A4D} = {27507B03-7D7C-492D-92A4-FF5812B9D7DB} + {60B9E226-55FF-4B5C-BFFE-57A14DE6CC58} = {20F306B8-D63F-4A61-AF6E-64B4E0918E30} + {A0FE9046-5B92-4C73-AB51-33CE15F14917} = {20F306B8-D63F-4A61-AF6E-64B4E0918E30} + {F0C2E52D-4412-4A18-B0F7-FD99F85F192C} = {27507B03-7D7C-492D-92A4-FF5812B9D7DB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DD153137-BF4D-4977-A4D7-4C448D23479A} diff --git a/src/OrchardCoreContrib.Testing.Web/NLog.config b/src/OrchardCoreContrib.Testing.Web/NLog.config new file mode 100644 index 0000000..410f7bf --- /dev/null +++ b/src/OrchardCoreContrib.Testing.Web/NLog.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OrchardCoreContrib.Testing.Web/OrchardCoreContrib.Testing.Web.csproj b/src/OrchardCoreContrib.Testing.Web/OrchardCoreContrib.Testing.Web.csproj new file mode 100644 index 0000000..31bf943 --- /dev/null +++ b/src/OrchardCoreContrib.Testing.Web/OrchardCoreContrib.Testing.Web.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + InProcess + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/src/OrchardCoreContrib.Testing.Web/Program.cs b/src/OrchardCoreContrib.Testing.Web/Program.cs new file mode 100644 index 0000000..6de8170 --- /dev/null +++ b/src/OrchardCoreContrib.Testing.Web/Program.cs @@ -0,0 +1,35 @@ +using OrchardCore.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Host.UseNLogHost(); + +builder.Services + .AddOrchardCms() +// // Orchard Specific Pipeline +// .ConfigureServices( services => { +// }) +// .Configure( (app, routes, services) => { +// }) +; + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseOrchardCore(); + +app.Run(); + +namespace OrchardCoreContrib.Testing.Web +{ + public partial class Program; +} diff --git a/src/OrchardCoreContrib.Testing.Web/Properties/launchSettings.json b/src/OrchardCoreContrib.Testing.Web/Properties/launchSettings.json new file mode 100644 index 0000000..87329d2 --- /dev/null +++ b/src/OrchardCoreContrib.Testing.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8080", + "sslPort": 44300 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "OrchardCoreContrib.Testing.Web": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/OrchardCoreContrib.Testing.Web/appsettings.json b/src/OrchardCoreContrib.Testing.Web/appsettings.json new file mode 100644 index 0000000..6d27889 --- /dev/null +++ b/src/OrchardCoreContrib.Testing.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, +} diff --git a/src/OrchardCoreContrib.Testing.Web/wwwroot/.placeholder b/src/OrchardCoreContrib.Testing.Web/wwwroot/.placeholder new file mode 100644 index 0000000..46b134b --- /dev/null +++ b/src/OrchardCoreContrib.Testing.Web/wwwroot/.placeholder @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/src/OrchardCoreContrib.Testing/ISiteContext.cs b/src/OrchardCoreContrib.Testing/ISiteContext.cs new file mode 100644 index 0000000..5aba3c2 --- /dev/null +++ b/src/OrchardCoreContrib.Testing/ISiteContext.cs @@ -0,0 +1,20 @@ +using OrchardCore.Environment.Shell; + +namespace OrchardCoreContrib.Testing; + +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; } + + Task InitializeAsync(); +} diff --git a/src/OrchardCoreContrib.Testing/ISiteContextOfT.cs b/src/OrchardCoreContrib.Testing/ISiteContextOfT.cs new file mode 100644 index 0000000..fe30b25 --- /dev/null +++ b/src/OrchardCoreContrib.Testing/ISiteContextOfT.cs @@ -0,0 +1,6 @@ +namespace OrchardCoreContrib.Testing; + +public interface ISiteContext : ISiteContext where TSiteStartup : class +{ + static OrchardCoreWebApplicationFactory Site { get; } +} diff --git a/src/OrchardCoreContrib.Testing/ModuleNamesProvider.cs b/src/OrchardCoreContrib.Testing/ModuleNamesProvider.cs new file mode 100644 index 0000000..c3183ad --- /dev/null +++ b/src/OrchardCoreContrib.Testing/ModuleNamesProvider.cs @@ -0,0 +1,21 @@ +using OrchardCore.Modules; +using OrchardCore.Modules.Manifest; +using System.Reflection; + +namespace OrchardCoreContrib.Testing; + +public sealed class ModuleNamesProvider : IModuleNamesProvider +{ + private readonly IEnumerable _moduleNames; + + public ModuleNamesProvider(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + + _moduleNames = Assembly.Load(new AssemblyName(assembly.GetName().Name)) + .GetCustomAttributes() + .Select(m => m.Name); + } + + public IEnumerable GetModuleNames() => _moduleNames; +} diff --git a/src/OrchardCoreContrib.Testing/OrchardCoreContrib.Testing.csproj b/src/OrchardCoreContrib.Testing/OrchardCoreContrib.Testing.csproj new file mode 100644 index 0000000..4fb5f7f --- /dev/null +++ b/src/OrchardCoreContrib.Testing/OrchardCoreContrib.Testing.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + + + + + + + + + + diff --git a/src/OrchardCoreContrib.Testing/OrchardCoreWebApplicationFactory.cs b/src/OrchardCoreContrib.Testing/OrchardCoreWebApplicationFactory.cs new file mode 100644 index 0000000..0bfe02f --- /dev/null +++ b/src/OrchardCoreContrib.Testing/OrchardCoreWebApplicationFactory.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; + +namespace OrchardCoreContrib.Testing; + +public class OrchardCoreWebApplicationFactory : WebApplicationFactory where TEntryPoint : 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(TEntryPoint).Assembly, []); + + protected override IHostBuilder CreateHostBuilder() + => Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); +} diff --git a/src/OrchardCoreContrib.Testing/Security/PermissionContextAuthorizationHandler.cs b/src/OrchardCoreContrib.Testing/Security/PermissionContextAuthorizationHandler.cs new file mode 100644 index 0000000..63cb97f --- /dev/null +++ b/src/OrchardCoreContrib.Testing/Security/PermissionContextAuthorizationHandler.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using OrchardCore.Security; +using OrchardCore.Security.Permissions; + +namespace OrchardCoreContrib.Testing.Security; + +public sealed class PermissionContextAuthorizationHandler : AuthorizationHandler +{ + private readonly PermissionsContext _permissionsContext; + + public PermissionContextAuthorizationHandler(IHttpContextAccessor httpContextAccessor, IDictionary permissionsContexts) + { + _permissionsContext = new PermissionsContext(); + + if (httpContextAccessor.HttpContext is null) + { + return; + } + + var request = httpContextAccessor.HttpContext.Request; + + if (request?.Headers.ContainsKey(nameof(PermissionsContext)) == true && + permissionsContexts.TryGetValue(request.Headers[nameof(PermissionsContext)], out var permissionsContext)) + { + _permissionsContext = permissionsContext; + } + } + + public PermissionContextAuthorizationHandler(PermissionsContext permissionsContext) + { + _permissionsContext = permissionsContext; + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) + { + var permissions = (_permissionsContext.AuthorizedPermissions ?? []).ToList(); + + if (!_permissionsContext.UsePermissionsContext) + { + context.Succeed(requirement); + } + else if (permissions.Contains(requirement.Permission)) + { + context.Succeed(requirement); + } + else + { + var grantingNames = new HashSet(StringComparer.OrdinalIgnoreCase); + + GetGrantingNamesInternal(requirement.Permission, grantingNames); + + if (permissions.Any(p => grantingNames.Contains(p.Name))) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + } + + return Task.CompletedTask; + } + + private static void GetGrantingNamesInternal(Permission permission, HashSet stack) + { + stack.Add(permission.Name); + + if (permission.ImpliedBy != null && permission.ImpliedBy.Any()) + { + foreach (var impliedBy in permission.ImpliedBy) + { + if (impliedBy == null || stack.Contains(impliedBy.Name)) + { + continue; + } + + GetGrantingNamesInternal(impliedBy, stack); + } + } + } +} diff --git a/src/OrchardCoreContrib.Testing/Security/PermissionsContext.cs b/src/OrchardCoreContrib.Testing/Security/PermissionsContext.cs new file mode 100644 index 0000000..d58ddbc --- /dev/null +++ b/src/OrchardCoreContrib.Testing/Security/PermissionsContext.cs @@ -0,0 +1,15 @@ +using OrchardCore.Security.Permissions; + +namespace OrchardCoreContrib.Testing.Security; + +public class PermissionsContext +{ + public PermissionsContext() + { + AuthorizedPermissions = []; + UsePermissionsContext = false; + } + public IEnumerable AuthorizedPermissions { get; set; } + + public bool UsePermissionsContext { get; set; } +} diff --git a/src/OrchardCoreContrib.Testing/SiteContextBase.cs b/src/OrchardCoreContrib.Testing/SiteContextBase.cs new file mode 100644 index 0000000..456bb37 --- /dev/null +++ b/src/OrchardCoreContrib.Testing/SiteContextBase.cs @@ -0,0 +1,125 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.BackgroundTasks; +using OrchardCore.Environment.Shell; +using OrchardCore.Environment.Shell.Scope; +using OrchardCore.Tenants.ViewModels; +using OrchardCoreContrib.Testing.Security; +using System.Net.Http.Json; + +namespace OrchardCoreContrib.Testing; + +public abstract class SiteContextBase : ISiteContext where TEntryPoint : class +{ + static SiteContextBase() + { + Site = new OrchardCoreWebApplicationFactory(); + ShellHost = Site.Services.GetRequiredService(); + ShellSettingsManager = Site.Services.GetRequiredService(); + HttpContextAccessor = Site.Services.GetRequiredService(); + DefaultTenantClient = Site.CreateDefaultClient(); + } + + public SiteContextBase() + { + Options = new SiteContextOptions(); + } + + public static OrchardCoreWebApplicationFactory 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 SiteContextOptions Options { init; get; } + + public HttpClient Client { get; private set; } + + public string TenantName { get; private set; } + + public virtual async Task InitializeAsync() + { + var tenantName = Guid.NewGuid().ToString("n"); + + var response = await CreateSiteAsync(tenantName); + + var content = await response.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 is not null) + { + var permissionContextKey = Guid.NewGuid().ToString(); + + SiteContextOptions.PermissionsContexts.TryAdd(permissionContextKey, Options.PermissionsContext); + + Client.DefaultRequestHeaders.Add(nameof(PermissionsContext), permissionContextKey); + } + } + + public void Dispose() => Client?.Dispose(); + + private async Task CreateSiteAsync(string tenantName) + { + var model = 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", model); + + result.EnsureSuccessStatusCode(); + + return result; + } + + private async Task SetupSiteAsync(string tenantName) + { + var model = new SetupApiViewModel + { + SiteName = Options.SiteName, + DatabaseProvider = Options.DatabaseProvider, + TablePrefix = Options.TablePrefix, + ConnectionString = Options.ConnectionString, + RecipeName = Options.RecipeName, + UserName = Options.Username, + Password = Options.Password, + Name = tenantName, + Email = Options.Email + }; + + var result = await DefaultTenantClient.PostAsJsonAsync("api/tenants/setup", model); + + result.EnsureSuccessStatusCode(); + } + + public async Task UsingTenantScopeAsync(Func execute, bool activateShell = true) + { + var shellScope = await ShellHost.GetScopeAsync(TenantName); + + HttpContextAccessor.HttpContext = shellScope.ShellContext.CreateHttpContext(); + + await shellScope.UsingAsync(execute, activateShell); + + HttpContextAccessor.HttpContext = null; + } +} diff --git a/src/OrchardCoreContrib.Testing/SiteContextOptionDefaults.cs b/src/OrchardCoreContrib.Testing/SiteContextOptionDefaults.cs new file mode 100644 index 0000000..4d73bbb --- /dev/null +++ b/src/OrchardCoreContrib.Testing/SiteContextOptionDefaults.cs @@ -0,0 +1,16 @@ +namespace OrchardCoreContrib.Testing; + +internal sealed class SiteContextOptionDefaults +{ + public const string RecipeName = "Blog"; + + public const string DatabaseProvider = "Sqlite"; + + public const string SiteName = "Orchard Core Contrib"; + + public const string Username = "admin"; + + public const string Password = "P@ssw0rd"; + + public const string Email = "admin@orchardcorecontrib.com"; +} diff --git a/src/OrchardCoreContrib.Testing/SiteContextOptions.cs b/src/OrchardCoreContrib.Testing/SiteContextOptions.cs new file mode 100644 index 0000000..8c3b133 --- /dev/null +++ b/src/OrchardCoreContrib.Testing/SiteContextOptions.cs @@ -0,0 +1,32 @@ +using OrchardCoreContrib.Testing.Security; +using System.Collections.Concurrent; + +namespace OrchardCoreContrib.Testing; + +public class SiteContextOptions +{ + static SiteContextOptions() + { + PermissionsContexts = new(); + } + + public static ConcurrentDictionary PermissionsContexts { get; set; } + + public string SiteName { get; set; } = SiteContextOptionDefaults.SiteName; + + public string Username { get; set; } = SiteContextOptionDefaults.Username; + + public string Password { get; set; } = SiteContextOptionDefaults.Password; + + public string Email { get; set; } = SiteContextOptionDefaults.Email; + + public string RecipeName { get; set; } = SiteContextOptionDefaults.RecipeName; + + public string DatabaseProvider { get; set; } = SiteContextOptionDefaults.DatabaseProvider; + + public string ConnectionString { get; set; } + + public string TablePrefix { get; set; } + + public PermissionsContext PermissionsContext { get; set; } +} diff --git a/test/OrchardCoreContrib.Testing.Tests/BlogSiteContext.cs b/test/OrchardCoreContrib.Testing.Tests/BlogSiteContext.cs new file mode 100644 index 0000000..e62aba4 --- /dev/null +++ b/test/OrchardCoreContrib.Testing.Tests/BlogSiteContext.cs @@ -0,0 +1,6 @@ +namespace OrchardCoreContrib.Testing.Tests; + +public class BlogSiteContext : SiteContextBase +{ + public BlogSiteContext() => Options.RecipeName = "Blog"; +} diff --git a/test/OrchardCoreContrib.Testing.Tests/OrchardCoreApplicationTests.cs b/test/OrchardCoreContrib.Testing.Tests/OrchardCoreApplicationTests.cs new file mode 100644 index 0000000..cafba5a --- /dev/null +++ b/test/OrchardCoreContrib.Testing.Tests/OrchardCoreApplicationTests.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.ContentManagement; +using System.Net; + +namespace OrchardCoreContrib.Testing.Tests; + +public class OrchardCoreApplicationTests +{ + [Fact] + public async Task IndexPage_ShouldContainsBlogInItsContent() + { + // Arrange + var context = new BlogSiteContext(); + + await context.InitializeAsync(); + + // Act + var response = await context.Client.GetAsync("/"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Blog", content); + } + + [Fact] + public async Task Tenant_ShouldAccessServiceFromDIContainer() + { + // Arrange + var context = new BlogSiteContext(); + + await context.InitializeAsync(); + + // Act & Assert + await context.UsingTenantScopeAsync(async scope => + { + var siteService = scope.ServiceProvider.GetRequiredService(); + + Assert.NotNull(siteService); + }); + } +} \ No newline at end of file diff --git a/test/OrchardCoreContrib.Testing.Tests/OrchardCoreContrib.Testing.Tests.csproj b/test/OrchardCoreContrib.Testing.Tests/OrchardCoreContrib.Testing.Tests.csproj new file mode 100644 index 0000000..0315bc7 --- /dev/null +++ b/test/OrchardCoreContrib.Testing.Tests/OrchardCoreContrib.Testing.Tests.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + false + true + + + + + + + + + + + + + + + + + + + diff --git a/test/OrchardCoreContrib.Testing.Tests/OrchardCoreStartup.cs b/test/OrchardCoreContrib.Testing.Tests/OrchardCoreStartup.cs new file mode 100644 index 0000000..86b560b --- /dev/null +++ b/test/OrchardCoreContrib.Testing.Tests/OrchardCoreStartup.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Modules; +using OrchardCoreContrib.Testing.Security; + +namespace OrchardCoreContrib.Testing.Tests; + +public class OrchardCoreStartup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddOrchardCms(builder => builder + .AddSetupFeatures("OrchardCore.Tenants") + .ConfigureServices(serviceCollection => + { + serviceCollection.AddScoped(sp => + new PermissionContextAuthorizationHandler(sp.GetRequiredService(), SiteContextOptions.PermissionsContexts)); + }) + .Configure(appBuilder => appBuilder.UseAuthorization())); + + services.AddSingleton(new ModuleNamesProvider(typeof(Web.Program).Assembly)); + } + + public void Configure(IApplicationBuilder app) => app.UseOrchardCore(); +} \ No newline at end of file diff --git a/test/OrchardCoreContrib.Testing.Tests/SiteContextTests.cs b/test/OrchardCoreContrib.Testing.Tests/SiteContextTests.cs new file mode 100644 index 0000000..7f566ba --- /dev/null +++ b/test/OrchardCoreContrib.Testing.Tests/SiteContextTests.cs @@ -0,0 +1,122 @@ +namespace OrchardCoreContrib.Testing.Tests; + +public class SiteContextTests +{ + [Fact] + public async Task Site_ShouldBeSameForAllSites() + { + // Arrange & Act + var _ = new BlogSiteContext(); + var site1 = BlogSiteContext.Site; + + var __ = new BlogSiteContext(); + var site2 = BlogSiteContext.Site; + + // Assert + Assert.Same(site1, site2); + } + + [Fact] + public async Task ShellHost_ShouldBeSameForAllSites() + { + // Arrange & Act + var _ = new BlogSiteContext(); + var shellHost1 = BlogSiteContext.ShellHost; + + var __ = new BlogSiteContext(); + var shellHost2 = BlogSiteContext.ShellHost; + + // Assert + Assert.Same(shellHost1, shellHost2); + } + + [Fact] + public async Task ShellSettingsManager_ShouldBeSameForAllSites() + { + // Arrange & Act + var _ = new BlogSiteContext(); + var shellSettingsManager1 = BlogSiteContext.ShellSettingsManager; + + var __ = new BlogSiteContext(); + var shellSettingsManager2 = BlogSiteContext.ShellSettingsManager; + + // Assert + Assert.Same(shellSettingsManager1, shellSettingsManager2); + } + + [Fact] + public async Task HttpContextAccessor_ShouldBeSameForAllSites() + { + // Arrange & Act + var _ = new BlogSiteContext(); + var httpContextAccessor1 = BlogSiteContext.HttpContextAccessor; + + var __ = new BlogSiteContext(); + var httpContextAccessor2 = BlogSiteContext.HttpContextAccessor; + + // Assert + Assert.Same(httpContextAccessor1, httpContextAccessor2); + } + + [Fact] + public async Task Options_ShouldBeSetOnConstructor() + { + // Arrange + BlogSiteContext siteContext; + + // Act + siteContext = new BlogSiteContext(); + + // Assert + Assert.NotNull(siteContext.Options); + } + + [Fact] + public async Task Client_ShouldBeSetAfterCallingInitializeAsync() + { + // Arrange + var siteContext = new BlogSiteContext(); + + Assert.Null(siteContext.Client); + + // Act + await siteContext.InitializeAsync(); + + // Assert + Assert.NotNull(siteContext.Client); + } + + [Fact] + public async Task TenantName_ShouldBeSetAfterCallingInitializeAsync() + { + // Arrange + var siteContext = new BlogSiteContext(); + + Assert.Null(siteContext.TenantName); + + // Act + await siteContext.InitializeAsync(); + + // Assert + Assert.NotNull(siteContext.TenantName); + } + + [Fact] + public async Task CallingInitializeAsyncMultipleTimes_ShouldChangeTenantName() + { + // Arrange + var siteContext = new BlogSiteContext(); + + // Act + await siteContext.InitializeAsync(); + + var tenant1 = siteContext.TenantName; + + await siteContext.InitializeAsync(); + + var tenant2 = siteContext.TenantName; + + // Assert + Assert.NotEqual(tenant1, tenant2); + } +}