Initial commit with inital CMS

This commit is contained in:
2026-06-15 17:00:16 +02:00
commit 95d986790e
132 changed files with 7624 additions and 0 deletions
@@ -0,0 +1,76 @@
using System.Text.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NSubstitute;
using SlpModularCms.Core.Exceptions;
using Xunit;
namespace SlpModularCms.Core.Tests.Exceptions;
public class GlobalExceptionHandlerTests
{
private readonly ILogger<GlobalExceptionHandler> _logger;
private readonly IHostEnvironment _env;
private readonly GlobalExceptionHandler _handler;
public GlobalExceptionHandlerTests()
{
_logger = Substitute.For<ILogger<GlobalExceptionHandler>>();
_env = Substitute.For<IHostEnvironment>();
_handler = new GlobalExceptionHandler(_logger, _env);
}
[Fact]
public async Task TryHandleAsync_ShouldReturnInternalServerError_ForGenericException()
{
// Arrange
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
var exception = new Exception("Test error");
var cancellationToken = CancellationToken.None;
_env.EnvironmentName.Returns("Production");
// Act
var result = await _handler.TryHandleAsync(context, exception, cancellationToken);
// Assert
result.Should().BeTrue();
context.Response.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
context.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Response.Body);
var body = await reader.ReadToEndAsync();
var response = JsonSerializer.Deserialize<ApiErrorResponse>(body, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
response.Should().NotBeNull();
response!.Message.Should().Be("Er is een interne serverfout opgetreden.");
response.Detail.Should().BeNull();
response.TraceId.Should().NotBeNullOrEmpty();
}
[Fact]
public async Task TryHandleAsync_ShouldIncludeDetails_WhenInDevelopment()
{
// Arrange
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
var exception = new Exception("Test error");
var cancellationToken = CancellationToken.None;
_env.EnvironmentName.Returns("Development");
// Act
await _handler.TryHandleAsync(context, exception, cancellationToken);
// Assert
context.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Response.Body);
var body = await reader.ReadToEndAsync();
var response = JsonSerializer.Deserialize<ApiErrorResponse>(body, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
response!.Detail.Should().Contain("Test error");
}
}
@@ -0,0 +1,52 @@
using System.Security.Claims;
using FluentAssertions;
using Microsoft.AspNetCore.Authorization;
using SlpModularCms.Core.Identity.Authorization;
using Xunit;
namespace SlpModularCms.Core.Tests.Identity;
public class HierarchicalRoleHandlerTests
{
private readonly HierarchicalRoleHandler _handler;
public HierarchicalRoleHandlerTests()
{
_handler = new HierarchicalRoleHandler();
}
[Theory]
[InlineData("Owner", "Owner", true)]
[InlineData("Owner", "Administrator", true)]
[InlineData("Owner", "User", true)]
[InlineData("Administrator", "Administrator", true)]
[InlineData("Administrator", "User", true)]
[InlineData("Administrator", "Owner", false)]
[InlineData("User", "User", true)]
[InlineData("User", "Administrator", false)]
[InlineData("User", "Owner", false)]
public async Task HandleAsync_ShouldValidateHierarchyCorrectly(string userRole, string requiredRole, bool shouldSucceed)
{
// Arrange
var requirement = new HierarchicalRoleRequirement(requiredRole);
var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Role, userRole)
}));
var context = new AuthorizationHandlerContext(new[] { requirement }, user, null);
// Act
await _handler.HandleAsync(context);
// Assert
if (shouldSucceed)
{
context.HasSucceeded.Should().BeTrue();
}
else
{
context.HasSucceeded.Should().BeFalse();
}
}
}
@@ -0,0 +1,97 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using SlpModularCms.Core.Data;
using SlpModularCms.Core.Identity.Entities;
using SlpModularCms.Core.Identity.Services;
using SlpModularCms.Core.Exceptions;
using Xunit;
using FluentAssertions;
namespace SlpModularCms.Core.Tests.Identity;
public class InvitationServiceTests
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly InvitationService _service;
public InvitationServiceTests()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new ApplicationDbContext(options);
var store = Substitute.For<IUserStore<ApplicationUser>>();
_userManager = Substitute.For<UserManager<ApplicationUser>>(store, null, null, null, null, null, null, null, null);
_service = new InvitationService(_context, _userManager);
}
[Fact]
public async Task CreateInvitationAsync_ShouldCreateRecordInDb()
{
// Arrange
var email = "test@example.com";
var role = "User";
// Act
var token = await _service.CreateInvitationAsync(email, role);
// Assert
token.Should().NotBeNullOrEmpty();
var invitation = await _context.Invitations.FirstOrDefaultAsync(i => i.Token == token);
invitation.Should().NotBeNull();
invitation!.Email.Should().Be(email);
invitation.Role.Should().Be(role);
invitation.IsAccepted.Should().BeFalse();
}
[Fact]
public async Task ValidateInvitationAsync_ShouldReturnTrue_ForValidToken()
{
// Arrange
var token = await _service.CreateInvitationAsync("test@example.com", "User");
// Act
var isValid = await _service.ValidateInvitationAsync(token);
// Assert
isValid.Should().BeTrue();
}
[Fact]
public async Task CompleteInvitationAsync_ShouldCreateUserAndMarkAsAccepted()
{
// Arrange
var email = "test@example.com";
var role = "User";
var password = "SafePassword123!";
var token = await _service.CreateInvitationAsync(email, role);
_userManager.CreateAsync(Arg.Any<ApplicationUser>(), password)
.Returns(IdentityResult.Success);
_userManager.AddToRoleAsync(Arg.Any<ApplicationUser>(), role)
.Returns(IdentityResult.Success);
// Act
await _service.CompleteInvitationAsync(token, password);
// Assert
var invitation = await _context.Invitations.FirstOrDefaultAsync(i => i.Token == token);
invitation!.IsAccepted.Should().BeTrue();
await _userManager.Received(1).CreateAsync(Arg.Is<ApplicationUser>(u => u.Email == email), password);
await _userManager.Received(1).AddToRoleAsync(Arg.Any<ApplicationUser>(), role);
}
[Fact]
public async Task CompleteInvitationAsync_ShouldThrow_WhenTokenInvalid()
{
// Act
var act = () => _service.CompleteInvitationAsync("invalid-token", "password");
// Assert
await act.Should().ThrowAsync<UnauthorizedException>();
}
}
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="FluentAssertions" Version="8.10.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SlpModularCms.Core\SlpModularCms.Core.csproj" />
</ItemGroup>
</Project>