diff --git a/backend/src/Controllers/OrganisationController.cs b/backend/src/Controllers/OrganisationController.cs index 5a94b5a..81ec2f4 100644 --- a/backend/src/Controllers/OrganisationController.cs +++ b/backend/src/Controllers/OrganisationController.cs @@ -26,6 +26,7 @@ CancellationToken cancellationToken error switch { GetOrganisationByIdError.NotFound => NotFound(), + GetOrganisationByIdError.NotAllowed => Forbid(), _ => throw new UnreachableException( "Unhandled GetOrganisationByIdError variant." ), @@ -60,6 +61,7 @@ CancellationToken cancellationToken error switch { UpdateOrganisationDetailsError.NotFound => NotFound(), + UpdateOrganisationDetailsError.NotAllowed => Forbid(), _ => throw new UnreachableException( "Unhandled UpdateOrganisationDetailsError variant." ), diff --git a/backend/src/Controllers/Utilities/UkpsClaimTypes.cs b/backend/src/Controllers/Utilities/UkpsClaimTypes.cs new file mode 100644 index 0000000..e740192 --- /dev/null +++ b/backend/src/Controllers/Utilities/UkpsClaimTypes.cs @@ -0,0 +1,7 @@ +namespace UKPS.Api.Controllers.Utilities; + +public static class UkpsClaimTypes +{ + public const string OrganisationId = "organisation_id"; + public const string UserRole = "user_role"; +} diff --git a/backend/src/Controllers/Utilities/WebApiCurrentUserInfoService.cs b/backend/src/Controllers/Utilities/WebApiCurrentUserInfoService.cs new file mode 100644 index 0000000..42da0ac --- /dev/null +++ b/backend/src/Controllers/Utilities/WebApiCurrentUserInfoService.cs @@ -0,0 +1,44 @@ +using System.Globalization; +using System.Security.Claims; +using UKPS.Api.Enums; +using UKPS.Api.Services.Interfaces; + +namespace UKPS.Api.Controllers.Utilities; + +internal class WebApiCurrentUserInfoService : ICurrentUserInfoService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + private ClaimsPrincipal Principal => + _httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(); + + public WebApiCurrentUserInfoService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public CurrentUser GetCurrentUserInfo() + { + return new CurrentUser { OrganisationId = FindOrganisationId(), UserRole = FindUserRole() }; + } + + private int FindOrganisationId() + { + string? organisationIdClaim = Principal.FindFirstValue(UkpsClaimTypes.OrganisationId); + return int.TryParse(organisationIdClaim, CultureInfo.InvariantCulture, out var orgId) + ? orgId + : throw new InvalidOperationException( + $"Invalid {UkpsClaimTypes.OrganisationId} claim value." + ); + } + + private UserRole FindUserRole() + { + string? userRoleClaim = Principal.FindFirstValue(UkpsClaimTypes.UserRole); + return Enum.TryParse(userRoleClaim, out var role) && Enum.IsDefined(role) + ? role + : throw new InvalidOperationException( + $"Invalid {UkpsClaimTypes.UserRole} claim value." + ); + } +} diff --git a/backend/src/Program.cs b/backend/src/Program.cs index 4196f5d..96678de 100644 --- a/backend/src/Program.cs +++ b/backend/src/Program.cs @@ -3,8 +3,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi; using Scalar.AspNetCore; +using UKPS.Api.Controllers.Utilities; using UKPS.Api.Data; using UKPS.Api.Services; +using UKPS.Api.Services.Interfaces; var builder = WebApplication.CreateBuilder(args); @@ -18,6 +20,8 @@ ); // Add services to the container. +builder.Services.AddHttpContextAccessor(); +builder.Services.AddScoped(); builder.Services.AddUkpsServices(); builder.Services.ConfigureHttpJsonOptions(options => diff --git a/backend/src/Services/DependencyInjectionManager.cs b/backend/src/Services/DependencyInjectionManager.cs index 322a148..fb34b82 100644 --- a/backend/src/Services/DependencyInjectionManager.cs +++ b/backend/src/Services/DependencyInjectionManager.cs @@ -7,7 +7,6 @@ internal static class DependencyInjectionManager { public static IServiceCollection AddUkpsServices(this IServiceCollection services) { - services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); diff --git a/backend/tests/Controllers/OrganisationControllerTests.cs b/backend/tests/Controllers/OrganisationControllerTests.cs index 4416926..9e2bf4a 100644 --- a/backend/tests/Controllers/OrganisationControllerTests.cs +++ b/backend/tests/Controllers/OrganisationControllerTests.cs @@ -86,6 +86,25 @@ public async Task GetOrganisationById_OrganisationDoesNotExist_ReturnsNotFound() result.Result.ShouldBeOfType(); } + [Fact] + public async Task GetOrganisationById_ActionNotAllowed_ReturnsForbidden() + { + var sampleOrganisationId = 1; + _organisationServiceMock + .GetOrganisationById(sampleOrganisationId, TestContext.Current.CancellationToken) + .Returns( + Result.Err( + new GetOrganisationByIdError.NotAllowed(sampleOrganisationId) + ) + ); + ActionResult result = await _controller.GetOrganisationById( + 1, + TestContext.Current.CancellationToken + ); + + result.Result.ShouldBeOfType(); + } + [Fact] public async Task GetOrganisationById_IdProvided_PassesIdToService() { @@ -120,6 +139,30 @@ public async Task UpdateOrganisationDetails_OrganisationExists_ReturnsOk() ok.Value.ShouldBe(expected); } + [Fact] + public async Task UpdateOrganisation_ActionNotAllowed_ReturnsForbidden() + { + var sampleOrganisationId = 1; + _organisationServiceMock + .UpdateOrganisationDetails( + sampleOrganisationId, + Arg.Any(), + TestContext.Current.CancellationToken + ) + .Returns( + Result.Err( + new UpdateOrganisationDetailsError.NotAllowed(sampleOrganisationId) + ) + ); + ActionResult result = await _controller.UpdateOrganisationDetails( + sampleOrganisationId, + CreateUpdateOrganisationDetailsDto(), + TestContext.Current.CancellationToken + ); + + result.Result.ShouldBeOfType(); + } + [Fact] public async Task UpdateOrganisationDetails_OrganisationDoesNotExist_ReturnsNotFound() { diff --git a/backend/tests/Controllers/Utilities/WebApiCurrentUserInfoServiceTests.cs b/backend/tests/Controllers/Utilities/WebApiCurrentUserInfoServiceTests.cs new file mode 100644 index 0000000..dacef65 --- /dev/null +++ b/backend/tests/Controllers/Utilities/WebApiCurrentUserInfoServiceTests.cs @@ -0,0 +1,109 @@ +using System.Security.Claims; +using Bogus; +using Microsoft.AspNetCore.Http; +using NSubstitute; +using Shouldly; +using UKPS.Api.Controllers.Utilities; +using UKPS.Api.Enums; +using UKPS.Api.Services.Interfaces; + +namespace UKPS.Api.Tests.Controllers.Utilities; + +public class WebApiCurrentUserInfoServiceTests +{ + [Fact] + public void GetCurrentUserInfo_ShouldReturnCurrentUser_WhenClaimsAreValid() + { + Randomizer.Seed = new Random(0); + var faker = new Faker(); + foreach (var userRole in Enum.GetValues()) + { + var organisationId = faker.Random.Int(min: 0); + var service = CreateService( + new Claim(UkpsClaimTypes.OrganisationId, $"{organisationId}"), + new Claim(UkpsClaimTypes.UserRole, userRole.ToString()) + ); + + CurrentUser result = service.GetCurrentUserInfo(); + + result.OrganisationId.ShouldBe(organisationId); + result.UserRole.ShouldBe(userRole); + } + } + + [Fact] + public void GetCurrentUserInfo_ShouldThrow_WhenOrganisationIdClaimIsMissing() + { + var service = CreateService( + new Claim(UkpsClaimTypes.UserRole, UserRole.Champion.ToString()) + ); + + var exception = Should.Throw(() => service.GetCurrentUserInfo()); + + exception.Message.ShouldBe($"Invalid {UkpsClaimTypes.OrganisationId} claim value."); + } + + [Theory] + [InlineData("")] + [InlineData("abc")] + [InlineData("1.5")] + public void GetCurrentUserInfo_ShouldThrow_WhenOrganisationIdClaimIsInvalid(string claimValue) + { + var service = CreateService( + new Claim(UkpsClaimTypes.OrganisationId, claimValue), + new Claim(UkpsClaimTypes.UserRole, UserRole.Super.ToString()) + ); + + var exception = Should.Throw(() => service.GetCurrentUserInfo()); + + exception.Message.ShouldBe($"Invalid {UkpsClaimTypes.OrganisationId} claim value."); + } + + [Fact] + public void GetCurrentUserInfo_ShouldThrow_WhenUserRoleClaimIsMissing() + { + var service = CreateService(new Claim(UkpsClaimTypes.OrganisationId, "123")); + + var exception = Should.Throw(() => service.GetCurrentUserInfo()); + + exception.Message.ShouldBe($"Invalid {UkpsClaimTypes.UserRole} claim value."); + } + + [Theory] + [InlineData("123")] + public void GetCurrentUserInfo_ShouldThrow_WhenUserRoleClaimIsInvalid(string claimValue) + { + var service = CreateService( + new Claim(UkpsClaimTypes.OrganisationId, "123"), + new Claim(UkpsClaimTypes.UserRole, claimValue) + ); + + var exception = Should.Throw(() => service.GetCurrentUserInfo()); + + exception.Message.ShouldBe($"Invalid {UkpsClaimTypes.UserRole} claim value."); + } + + [Fact] + public void GetCurrentUserInfo_ShouldThrow_WhenHttpContextIsNull() + { + var accessor = Substitute.For(); + accessor.HttpContext.Returns((HttpContext?)null); + + var service = new WebApiCurrentUserInfoService(accessor); + + Should.Throw(() => service.GetCurrentUserInfo()); + } + + private static WebApiCurrentUserInfoService CreateService(params Claim[] claims) + { + var httpContext = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(claims, "Test")), + }; + + var accessor = Substitute.For(); + accessor.HttpContext.Returns(httpContext); + + return new WebApiCurrentUserInfoService(accessor); + } +} diff --git a/backend/tests/Fixtures/ApiFactory.cs b/backend/tests/Fixtures/ApiFactory.cs index c7941c8..36d15cb 100644 --- a/backend/tests/Fixtures/ApiFactory.cs +++ b/backend/tests/Fixtures/ApiFactory.cs @@ -1,14 +1,31 @@ +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; namespace UKPS.Api.Tests.Fixtures; public sealed class ApiFactory(string connectionString) : WebApplicationFactory { + public TestAuthenticationOptions AuthOptions { get; } = new TestAuthenticationOptions(); + protected override void ConfigureWebHost(IWebHostBuilder builder) { ArgumentNullException.ThrowIfNull(builder); + builder.ConfigureTestServices(services => + { + services.AddSingleton(AuthOptions); + + services + .AddAuthentication(TestAuthHandler.AuthenticationScheme) + .AddScheme( + TestAuthHandler.AuthenticationScheme, + _ => { } + ); + }); + builder.UseSetting("ConnectionStrings:DefaultConnection", connectionString); } } diff --git a/backend/tests/Fixtures/TestAuthHandler.cs b/backend/tests/Fixtures/TestAuthHandler.cs new file mode 100644 index 0000000..23bf45d --- /dev/null +++ b/backend/tests/Fixtures/TestAuthHandler.cs @@ -0,0 +1,33 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace UKPS.Api.Tests.Fixtures; + +public class TestAuthHandler : AuthenticationHandler +{ + public const string AuthenticationScheme = "TestScheme"; + private readonly TestAuthenticationOptions _authOptions; + + public TestAuthHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + TestAuthenticationOptions authOptions + ) + : base(options, logger, encoder) + { + _authOptions = authOptions; + } + + protected override Task HandleAuthenticateAsync() + { + var identity = new ClaimsIdentity(_authOptions.Claims); + + var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), AuthenticationScheme); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } +} diff --git a/backend/tests/Fixtures/TestAuthenticationOptions.cs b/backend/tests/Fixtures/TestAuthenticationOptions.cs new file mode 100644 index 0000000..631bcd8 --- /dev/null +++ b/backend/tests/Fixtures/TestAuthenticationOptions.cs @@ -0,0 +1,16 @@ +using System.Security.Claims; +using UKPS.Api.Controllers.Utilities; +using UKPS.Api.Enums; + +namespace UKPS.Api.Tests.Fixtures; + +public class TestAuthenticationOptions +{ + public static IReadOnlyCollection DefaultClaims { get; } = + [ + new Claim(UkpsClaimTypes.UserRole, UserRole.Super.ToString()), + new Claim(UkpsClaimTypes.OrganisationId, $"{1}"), + ]; + + public ICollection Claims { get; } = DefaultClaims.ToList(); +} diff --git a/backend/tests/Services/OrganisationMembershipServiceTests.cs b/backend/tests/Services/OrganisationMembershipServiceTests.cs index 48d8bbc..2791e78 100644 --- a/backend/tests/Services/OrganisationMembershipServiceTests.cs +++ b/backend/tests/Services/OrganisationMembershipServiceTests.cs @@ -11,6 +11,7 @@ using UKPS.Api.Tests.Utilities.AssertionHelpers; using UKPS.Api.Tests.Utilities.Data; using UKPS.Api.Tests.Utilities.Data.Fakers; +using UKPS.Api.Tests.Utilities.Harnesses; namespace UKPS.Api.Tests.Services; diff --git a/backend/tests/Services/OrganisationServiceTests.cs b/backend/tests/Services/OrganisationServiceTests.cs index 8d11c4e..def6681 100644 --- a/backend/tests/Services/OrganisationServiceTests.cs +++ b/backend/tests/Services/OrganisationServiceTests.cs @@ -1,7 +1,6 @@ using Bogus; using Microsoft.EntityFrameworkCore; using Shouldly; -using UKPS.Api.Common; using UKPS.Api.Data; using UKPS.Api.DTOs; using UKPS.Api.Entities.Identity; @@ -11,61 +10,26 @@ using UKPS.Api.Tests.Fixtures; using UKPS.Api.Tests.Utilities.AssertionHelpers; using UKPS.Api.Tests.Utilities.Data.Fakers; +using UKPS.Api.Tests.Utilities.Harnesses; +using GetOrganisationResult = UKPS.Api.Common.Result< + UKPS.Api.DTOs.OrganisationDetailsDto, + UKPS.Api.Services.Errors.GetOrganisationByIdError +>; +using UpdateOrganisationResult = UKPS.Api.Common.Result< + UKPS.Api.DTOs.OrganisationDetailsDto, + UKPS.Api.Services.Errors.UpdateOrganisationDetailsError +>; namespace UKPS.Api.Tests.Services; [Collection(DatabaseCollection.Name)] -public class OrganisationServiceTests : DatabaseTestBase +public class OrganisationServiceTests(PostgresFixture fixture) : DatabaseTestBase(fixture) { - private readonly IOrganisationService _service; - private readonly OrganisationFaker _organisationFaker = new(); + internal IServiceTestHarness ServiceTestHarness => + new ServiceTestHarness(Context); + private IOrganisationService Service => ServiceTestHarness.Service; - public OrganisationServiceTests(PostgresFixture fixture) - : base(fixture) - { - _service = new ServiceTestHarness(Context).Service; - } - - [Theory] - [InlineData(true, UserRole.Super, true)] - [InlineData(false, UserRole.Super, true)] - [InlineData(true, UserRole.Champion, true)] - [InlineData(false, UserRole.Champion, false)] - [InlineData(true, UserRole.Standard, true)] - [InlineData(false, UserRole.Standard, false)] - public async Task GetOrganisationById_AuthorisesBasedOnUserRoleAndOrganisation( - bool organisationIdMatches, - UserRole userRole, - bool expectedAuthorised - ) - { - Organisation organisation = await AddEntity( - _organisationFaker.Generate(), - TestContext.Current.CancellationToken - ); - int otherOrganisationId = 999_999; - int usersOrganisationId = organisationIdMatches ? organisation.Id : otherOrganisationId; - - var testHarness = new ServiceTestHarness(Context).UpdateCurrentUser( - x => x with { OrganisationId = usersOrganisationId, UserRole = userRole } - ); - var service = testHarness.Service; - - Result result = - await service.GetOrganisationById( - organisation.Id, - TestContext.Current.CancellationToken - ); - - if (expectedAuthorised) - { - result.ShouldBeSuccess(); - } - else - { - result.ShouldBeError().ShouldBeOfType(); - } - } + internal readonly OrganisationFaker _organisationFaker = new(); [Fact] public async Task GetOrganisationById_OrganisationExists_ReturnsDto() @@ -90,8 +54,10 @@ public async Task GetOrganisationById_OrganisationExists_ReturnsDto() await Context.Organisations.SingleAsync(TestContext.Current.CancellationToken) ).Id; - Result result = - await _service.GetOrganisationById(id, TestContext.Current.CancellationToken); + GetOrganisationResult result = await Service.GetOrganisationById( + id, + TestContext.Current.CancellationToken + ); var dto = result.ShouldBeSuccess(); dto.ShouldNotBeNull(); @@ -107,6 +73,50 @@ await Context.Organisations.SingleAsync(TestContext.Current.CancellationToken) dto.CreatedAt.ShouldBe(new DateTime(2026, 6, 19, 12, 50, 1, DateTimeKind.Utc)); } + [Theory] + [InlineData(true, UserRole.Super, true)] + [InlineData(false, UserRole.Super, true)] + [InlineData(true, UserRole.Champion, true)] + [InlineData(false, UserRole.Champion, false)] + [InlineData(true, UserRole.Standard, true)] + [InlineData(false, UserRole.Standard, false)] + public async Task GetOrganisationById_AuthorisesBasedOnUserRoleAndOrganisation( + bool organisationIdMatches, + UserRole userRole, + bool expectedAuthorised + ) + { + Organisation organisation = await AddEntity( + _organisationFaker.Generate(), + TestContext.Current.CancellationToken + ); + int otherOrganisationId = 999_999; + int usersOrganisationId = organisationIdMatches ? organisation.Id : otherOrganisationId; + + var testHarness = ServiceTestHarness.UpdateCurrentUser(x => + x with + { + OrganisationId = usersOrganisationId, + UserRole = userRole, + } + ); + var service = testHarness.Service; + + GetOrganisationResult result = await service.GetOrganisationById( + organisation.Id, + TestContext.Current.CancellationToken + ); + + if (expectedAuthorised) + { + result.ShouldBeSuccess(); + } + else + { + result.ShouldBeError().ShouldBeOfType(); + } + } + [Fact] public async Task GetOrganisationById_OrganisationDoesNotExist_ReturnsNotFoundError() { @@ -116,8 +126,10 @@ public async Task GetOrganisationById_OrganisationDoesNotExist_ReturnsNotFoundEr await Context.Organisations.SingleAsync(TestContext.Current.CancellationToken) ).Id; - Result result = - await _service.GetOrganisationById(seededId + 1, TestContext.Current.CancellationToken); + GetOrganisationResult result = await Service.GetOrganisationById( + seededId + 1, + TestContext.Current.CancellationToken + ); GetOrganisationByIdError.NotFound notFound = result .ShouldBeError() @@ -136,12 +148,11 @@ public async Task UpdateOrganisationDetails_OrganisationExists_UpdatesEditableFi await Context.Organisations.SingleAsync(TestContext.Current.CancellationToken) ).Id; - Result result = - await _service.UpdateOrganisationDetails( - id, - CreateUpdateDto(), - TestContext.Current.CancellationToken - ); + UpdateOrganisationResult result = await Service.UpdateOrganisationDetails( + id, + CreateUpdateDto(), + TestContext.Current.CancellationToken + ); var dto = result.ShouldBeSuccess(); AssertUpdatedDetails(dto, createdAt, lastActive); @@ -173,18 +184,22 @@ bool expectedAuthorised ); int otherOrganisationId = 999_999; int usersOrganisationId = organisationIdMatches ? organisation.Id : otherOrganisationId; - var testHarness = new ServiceTestHarness(Context).UpdateCurrentUser( - x => x with { OrganisationId = usersOrganisationId, UserRole = userRole } + + var testHarness = ServiceTestHarness.UpdateCurrentUser(x => + x with + { + OrganisationId = usersOrganisationId, + UserRole = userRole, + } ); var service = testHarness.Service; var updateCommand = new UpdateOrganisationDetailsDtoFaker().Generate(); - Result result = - await service.UpdateOrganisationDetails( - organisation.Id, - updateCommand, - TestContext.Current.CancellationToken - ); + UpdateOrganisationResult result = await service.UpdateOrganisationDetails( + organisation.Id, + updateCommand, + TestContext.Current.CancellationToken + ); if (expectedAuthorised) { @@ -199,18 +214,17 @@ await service.UpdateOrganisationDetails( [Fact] public async Task UpdateOrganisationDetails_OrganisationDoesNotExist_ReturnsNotFoundError() { - Result result = - await _service.UpdateOrganisationDetails( - 99, - new UpdateOrganisationDetailsDto - { - OrganisationName = "New Pharma Ltd", - HeadOfficeAddress = "10 Downing Street\nLondon\nSW1A 2AA", - HeadOfficeEmail = "new@example.com", - HeadOfficeTelephone = "020 1111 1111", - }, - TestContext.Current.CancellationToken - ); + UpdateOrganisationResult result = await Service.UpdateOrganisationDetails( + 99, + new UpdateOrganisationDetailsDto + { + OrganisationName = "New Pharma Ltd", + HeadOfficeAddress = "10 Downing Street\nLondon\nSW1A 2AA", + HeadOfficeEmail = "new@example.com", + HeadOfficeTelephone = "020 1111 1111", + }, + TestContext.Current.CancellationToken + ); UpdateOrganisationDetailsError.NotFound notFound = result .ShouldBeError() @@ -290,7 +304,8 @@ DateTime lastActive saved.CreatedAt.ShouldBe(createdAt); } - private sealed class UpdateOrganisationDetailsDtoFaker : Faker + protected internal sealed class UpdateOrganisationDetailsDtoFaker + : Faker { public UpdateOrganisationDetailsDtoFaker() { diff --git a/backend/tests/Services/UserServiceTests.cs b/backend/tests/Services/UserServiceTests.cs index c627e32..8e01ca2 100644 --- a/backend/tests/Services/UserServiceTests.cs +++ b/backend/tests/Services/UserServiceTests.cs @@ -9,6 +9,7 @@ using UKPS.Api.Tests.Utilities.AssertionHelpers; using UKPS.Api.Tests.Utilities.Data; using UKPS.Api.Tests.Utilities.Data.Fakers; +using UKPS.Api.Tests.Utilities.Harnesses; namespace UKPS.Api.Tests.Services; diff --git a/backend/tests/Utilities/Harnesses/AuthorisationTestConstants.cs b/backend/tests/Utilities/Harnesses/AuthorisationTestConstants.cs new file mode 100644 index 0000000..86cb9d8 --- /dev/null +++ b/backend/tests/Utilities/Harnesses/AuthorisationTestConstants.cs @@ -0,0 +1,10 @@ +using UKPS.Api.Enums; +using UKPS.Api.Services.Interfaces; + +namespace UKPS.Api.Tests.Utilities.Harnesses; + +internal static class AuthorisationTestConstants +{ + public static CurrentUser DefaultCurrentUser { get; } = + new CurrentUser { OrganisationId = 1, UserRole = UserRole.Super }; +} diff --git a/backend/tests/Utilities/Harnesses/IServiceTestHarness.cs b/backend/tests/Utilities/Harnesses/IServiceTestHarness.cs new file mode 100644 index 0000000..732362b --- /dev/null +++ b/backend/tests/Utilities/Harnesses/IServiceTestHarness.cs @@ -0,0 +1,12 @@ +using UKPS.Api.Data; +using UKPS.Api.Services.Interfaces; + +namespace UKPS.Api.Tests.Utilities.Harnesses; + +internal interface IServiceTestHarness + where TService : notnull +{ + TService Service { get; } + AppDbContext Context { get; } + IServiceTestHarness UpdateCurrentUser(Func update); +} diff --git a/backend/tests/Services/ServiceTestHarness.cs b/backend/tests/Utilities/Harnesses/ServiceTestHarness.cs similarity index 75% rename from backend/tests/Services/ServiceTestHarness.cs rename to backend/tests/Utilities/Harnesses/ServiceTestHarness.cs index 01e1ea2..34a1467 100644 --- a/backend/tests/Services/ServiceTestHarness.cs +++ b/backend/tests/Utilities/Harnesses/ServiceTestHarness.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.DependencyInjection; using NSubstitute; using UKPS.Api.Data; -using UKPS.Api.Enums; using UKPS.Api.Services; using UKPS.Api.Services.Interfaces; -namespace UKPS.Api.Tests.Services; +namespace UKPS.Api.Tests.Utilities.Harnesses; -internal sealed class ServiceTestHarness +internal sealed class ServiceTestHarness : IServiceTestHarness where TService : notnull { public TService Service => _serviceProvider.GetRequiredService(); @@ -15,11 +14,7 @@ internal sealed class ServiceTestHarness private readonly ICurrentUserInfoService _mockCurrentUserInfoService; private readonly ServiceProvider _serviceProvider; - private CurrentUser _currentUser = new CurrentUser - { - OrganisationId = 1, - UserRole = UserRole.Super, - }; + private CurrentUser _currentUser = AuthorisationTestConstants.DefaultCurrentUser; public ServiceTestHarness(AppDbContext context) { @@ -33,7 +28,7 @@ public ServiceTestHarness(AppDbContext context) .BuildServiceProvider(); } - public ServiceTestHarness UpdateCurrentUser(Func update) + public IServiceTestHarness UpdateCurrentUser(Func update) { _currentUser = update(_currentUser); _mockCurrentUserInfoService.GetCurrentUserInfo().Returns(_currentUser);