diff --git a/src/Dfe.SignIn.Core.Interfaces/DataAccess/IUnitOfWork.cs b/src/Dfe.SignIn.Core.Interfaces/DataAccess/IUnitOfWork.cs deleted file mode 100644 index 1e5daf7e..00000000 --- a/src/Dfe.SignIn.Core.Interfaces/DataAccess/IUnitOfWork.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace Dfe.SignIn.Core.Interfaces.DataAccess; - -/// -/// Represents a unit of work that manages database transactions and provides -/// access to repositories. Ensures that a set of operations either fully succeed -/// or are rolled back as a single atomic operation. -/// -public interface IUnitOfWork : IDisposable, IAsyncDisposable -{ - /// - /// Returns a queryable source for the specified entity type. - /// - /// - /// The entity type to be queried. - /// - /// - /// An that can be used to query or modify - /// instances of . - /// - IQueryable Repository() where TEntity : class; - - /// - /// Asynchronously adds the specified entity to the current unit of work. - /// - /// - /// The type of the entity being added. - /// - /// The entity instance to add. - /// The cancellation token. - /// - /// A task that represents the asynchronous operation. - /// - Task AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class; - - /// - /// Marks the specified entity for deletion in the current unit of work. - /// - /// - /// The type of the entity being removed. - /// - /// - /// The entity instance to remove. - /// - void Remove(TEntity entity) where TEntity : class; - - /// - /// Persists all pending changes to the database. - /// - /// An optional cancellation token. - /// - /// The number of state entries written to the database. - /// - Task SaveChangesAsync(CancellationToken cancellationToken = default); - - /// - /// Begins a new database transaction. - /// - /// An optional cancellation token. - Task BeginTransactionAsync(CancellationToken cancellationToken = default); - - /// - /// Commits the currently active database transaction. - /// - /// An optional cancellation token. - Task CommitTransactionAsync(CancellationToken cancellationToken = default); - - /// - /// Rolls back the currently active database transaction. - /// - /// An optional cancellation token. - Task RollbackTransactionAsync(CancellationToken cancellationToken = default); -} - -/// -/// A unit of work for the Directories database. -/// -public interface IUnitOfWorkDirectories : IUnitOfWork { } - -/// -/// A unit of work for the Organisations database. -/// -public interface IUnitOfWorkOrganisations : IUnitOfWork { } - -/// -/// A unit of work for the Audit database. -/// -public interface IUnitOfWorkAudit : IUnitOfWork { } diff --git a/src/Dfe.SignIn.Core.Interfaces/Dfe.SignIn.Core.Interfaces.csproj b/src/Dfe.SignIn.Core.Interfaces/Dfe.SignIn.Core.Interfaces.csproj index f5265b0b..c9f7cdd8 100644 --- a/src/Dfe.SignIn.Core.Interfaces/Dfe.SignIn.Core.Interfaces.csproj +++ b/src/Dfe.SignIn.Core.Interfaces/Dfe.SignIn.Core.Interfaces.csproj @@ -13,4 +13,8 @@ + + + + diff --git a/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationApiConfigurationUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationApiConfigurationUseCase.cs index 7cc24e74..e27d0d6b 100644 --- a/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationApiConfigurationUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationApiConfigurationUseCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Applications; using Dfe.SignIn.Core.Contracts.PublicApi; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Applications; @@ -11,8 +10,8 @@ namespace Dfe.SignIn.Core.UseCases.Applications; /// Use case responsible for obtaining the Public API configuration of an application. /// public sealed class GetApplicationApiConfigurationUseCase( - IInteractionDispatcher interaction, - IUnitOfWorkOrganisations uowOrganisations + DbOrganisationsContext uowOrganisations, + IInteractionDispatcher interaction ) : Interactor { /// @@ -22,7 +21,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var serviceEntity = await uowOrganisations.Repository() + var serviceEntity = await uowOrganisations.Services .Select(x => new { x.ClientId, x.ApiSecret, diff --git a/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationByClientIdUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationByClientIdUseCase.cs index b2d501dc..3909bee2 100644 --- a/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationByClientIdUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationByClientIdUseCase.cs @@ -1,7 +1,6 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Applications; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Applications; @@ -9,9 +8,7 @@ namespace Dfe.SignIn.Core.UseCases.Applications; /// /// Use case responsible for obtaining information about an application. /// -public sealed class GetApplicationByClientIdUseCase( - IUnitOfWorkOrganisations uowOrganisations -) : Interactor +public sealed class GetApplicationByClientIdUseCase(DbOrganisationsContext uowOrganisations) : Interactor { /// public override async Task InvokeAsync( @@ -20,7 +17,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var serviceEntity = await uowOrganisations.Repository() + var serviceEntity = await uowOrganisations.Services .Select(x => new { x.Id, x.ClientId, diff --git a/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationRolesUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationRolesUseCase.cs index cb9bb5b4..654e6ebe 100644 --- a/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationRolesUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Applications/GetApplicationRolesUseCase.cs @@ -1,8 +1,7 @@ using System.Data; using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Applications; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Applications; @@ -10,9 +9,7 @@ namespace Dfe.SignIn.Core.UseCases.Applications; /// /// Use case responsible for obtaining information about an application. /// -public sealed class GetApplicationRolesUseCase( - IUnitOfWorkOrganisations uowOrganisations -) : Interactor +public sealed class GetApplicationRolesUseCase(DbOrganisationsContext uowOrganisations) : Interactor { /// public override async Task InvokeAsync( @@ -21,7 +18,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var roles = await uowOrganisations.Repository() + var roles = await uowOrganisations.Roles .Include(r => r.Parent) .Where(r => r.ApplicationId == context.Request.ApplicationId) .OrderBy(r => r.Name) diff --git a/src/Dfe.SignIn.Core.UseCases/Dfe.SignIn.Core.UseCases.csproj b/src/Dfe.SignIn.Core.UseCases/Dfe.SignIn.Core.UseCases.csproj index 8685f362..def8bab9 100644 --- a/src/Dfe.SignIn.Core.UseCases/Dfe.SignIn.Core.UseCases.csproj +++ b/src/Dfe.SignIn.Core.UseCases/Dfe.SignIn.Core.UseCases.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Dfe.SignIn.Core.UseCases/Organisations/GetOrganisationByIdUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Organisations/GetOrganisationByIdUseCase.cs index 43a022e4..4f2375b5 100644 --- a/src/Dfe.SignIn.Core.UseCases/Organisations/GetOrganisationByIdUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Organisations/GetOrganisationByIdUseCase.cs @@ -1,7 +1,6 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Organisations; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Organisations; @@ -9,9 +8,7 @@ namespace Dfe.SignIn.Core.UseCases.Organisations; /// /// Use case for getting information about an organisation. /// -public sealed class GetOrganisationByIdUseCase( - IUnitOfWorkOrganisations uowOrganisations -) : Interactor +public sealed class GetOrganisationByIdUseCase(DbOrganisationsContext uowOrganisations) : Interactor { /// public override async Task InvokeAsync( @@ -20,7 +17,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var organisationEntity = await uowOrganisations.Repository() + var organisationEntity = await uowOrganisations.Organisations .Where(x => x.Id == context.Request.OrganisationId) .FirstOrDefaultAsync(cancellationToken) ?? throw OrganisationNotFoundException.FromOrganisationId(context.Request.OrganisationId); diff --git a/src/Dfe.SignIn.Core.UseCases/Organisations/GetUsersAtOrganisationUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Organisations/GetUsersAtOrganisationUseCase.cs index 10b87157..e18516d4 100644 --- a/src/Dfe.SignIn.Core.UseCases/Organisations/GetUsersAtOrganisationUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Organisations/GetUsersAtOrganisationUseCase.cs @@ -2,9 +2,9 @@ using System.Linq.Expressions; using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Organisations; -using Dfe.SignIn.Core.Entities.Directories; + using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Organisations; @@ -13,9 +13,7 @@ namespace Dfe.SignIn.Core.UseCases.Organisations; /// Query returns users and their roles for a service and organisation. /// /// -public sealed class GetUsersAtOrganisationUseCase( - IUnitOfWorkOrganisations uowOrganisations -) : Interactor +public sealed class GetUsersAtOrganisationUseCase(DbOrganisationsContext uowOrganisations) : Interactor { /// public override async Task InvokeAsync( @@ -53,18 +51,18 @@ private IQueryable BuildQuery( Expression> organisationFilter) { var organisations = uowOrganisations - .Repository() + .Organisations .Where(organisationFilter); return - from us in uowOrganisations.Repository() + from us in uowOrganisations.UserServices join o in organisations on us.OrganisationId equals o.Id - join u in uowOrganisations.Repository() + join u in uowOrganisations.Users on us.UserId equals u.Sub - join s in uowOrganisations.Repository() + join s in uowOrganisations.Services on us.ServiceId equals s.Id - join usr in uowOrganisations.Repository() + join usr in uowOrganisations.UserServiceRoles on new { oid = us.OrganisationId, sid = us.ServiceId, @@ -77,7 +75,7 @@ join usr in uowOrganisations.Repository() } into usrGroup from usr in usrGroup.DefaultIfEmpty() - join r in uowOrganisations.Repository() + join r in uowOrganisations.Roles on usr.RoleId equals r.Id into roleGroup from r in roleGroup.DefaultIfEmpty() diff --git a/src/Dfe.SignIn.Core.UseCases/SupportTickets/GetApplicationNamesForSupportTicketUseCase.cs b/src/Dfe.SignIn.Core.UseCases/SupportTickets/GetApplicationNamesForSupportTicketUseCase.cs index 8148aa52..2b2f8e4b 100644 --- a/src/Dfe.SignIn.Core.UseCases/SupportTickets/GetApplicationNamesForSupportTicketUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/SupportTickets/GetApplicationNamesForSupportTicketUseCase.cs @@ -1,7 +1,6 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.SupportTickets; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.SupportTickets; @@ -10,9 +9,7 @@ namespace Dfe.SignIn.Core.UseCases.SupportTickets; /// Use case for getting a list of application names that can be chosen from /// when raising a support ticket. /// -public sealed class GetApplicationNamesForSupportTicketUseCase( - IUnitOfWorkOrganisations unitOfWork -) : Interactor +public sealed class GetApplicationNamesForSupportTicketUseCase(DbOrganisationsContext unitOfWork) : Interactor { /// public override async Task InvokeAsync( @@ -21,7 +18,7 @@ public override async Task InvokeAs { context.ThrowIfHasValidationErrors(); - var applications = await unitOfWork.Repository() + var applications = await unitOfWork.Services .Where(x => !x.IsChildService && !x.ServiceParams.Any(sp => sp.ParamName == "helpHidden" && sp.ParamValue == "true")) diff --git a/src/Dfe.SignIn.Core.UseCases/Users/ChangeJobTitleUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/ChangeJobTitleUseCase.cs index df52b39c..91505cf6 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/ChangeJobTitleUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/ChangeJobTitleUseCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Audit; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Directories; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -11,7 +10,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// An interactor to change the job title of a user. /// public sealed class ChangeJobTitleUseCase( - IUnitOfWorkDirectories unitOfWork, + DbDirectoriesContext unitOfWork, IInteractionDispatcher interaction ) : Interactor { @@ -22,7 +21,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var user = await unitOfWork.Repository() + var user = await unitOfWork.Users .Where(x => x.Sub == context.Request.UserId) .FirstOrDefaultAsync(cancellationToken) ?? throw UserNotFoundException.FromUserId(context.Request.UserId); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/ChangeNameUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/ChangeNameUseCase.cs index 4e6d3281..1a99ea05 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/ChangeNameUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/ChangeNameUseCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Audit; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Directories; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -11,7 +10,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// An interactor to change the job title of a user. /// public sealed class ChangeNameUseCase( - IUnitOfWorkDirectories unitOfWork, + DbDirectoriesContext unitOfWork, IInteractionDispatcher interaction ) : Interactor { @@ -22,7 +21,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var user = await unitOfWork.Repository() + var user = await unitOfWork.Users .Where(x => x.Sub == context.Request.UserId) .FirstOrDefaultAsync(cancellationToken) ?? throw UserNotFoundException.FromUserId(context.Request.UserId); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/CreateUserUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/CreateUserUseCase.cs index c342828c..08402b30 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/CreateUserUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/CreateUserUseCase.cs @@ -2,7 +2,7 @@ using Dfe.SignIn.Core.Contracts.Search; using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -20,8 +20,8 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// Review login.dfe.directories for implementation details. /// public sealed class CreateUserUseCase( + DbDirectoriesContext unitOfWork, IInteractionDispatcher interaction, - IUnitOfWorkDirectories unitOfWork, TimeProvider timeProvider ) : Interactor { @@ -32,7 +32,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var user = await unitOfWork.Repository() + var user = await unitOfWork.Users .Where(x => x.Email == context.Request.EmailAddress || x.EntraOid == context.Request.EntraUserId) diff --git a/src/Dfe.SignIn.Core.UseCases/Users/GetOrganisationsAssociatedWithUserUserCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/GetOrganisationsAssociatedWithUserUserCase.cs index 2c31d649..46e4fa18 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/GetOrganisationsAssociatedWithUserUserCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/GetOrganisationsAssociatedWithUserUserCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; using Dfe.SignIn.Core.UseCases.Organisations; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -11,7 +10,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// Use case for getting all of the organisations that are associated with a particular user. /// public sealed class GetOrganisationsAssociatedWithUserUseCase( - IUnitOfWorkOrganisations unitOfWork + DbOrganisationsContext unitOfWork ) : Interactor { @@ -22,7 +21,7 @@ public override async Task InvokeAsy { context.ThrowIfHasValidationErrors(); - var organisations = await unitOfWork.Repository() + var organisations = await unitOfWork.UserOrganisations .Where(x => x.UserId == context.Request.UserId) .Select(x => OrganisationHelpers.OrganisationFromEntity(x.Organisation)) .ToArrayAsync(cancellationToken); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/GetServiceUsersUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/GetServiceUsersUseCase.cs index c58a73bc..69fbe808 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/GetServiceUsersUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/GetServiceUsersUseCase.cs @@ -5,7 +5,7 @@ using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -14,9 +14,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// Get the service users for a given service (client). /// /// -public sealed class GetServiceUsersUseCase( - IUnitOfWorkOrganisations uowOrganisations -) : Interactor +public sealed class GetServiceUsersUseCase(DbOrganisationsContext uowOrganisations) : Interactor { /// public override async Task InvokeAsync( @@ -29,7 +27,7 @@ public override async Task InvokeAsync( var pageSize = context.Request.PageSize; var pageNumber = context.Request.PageNumber; - var query = uowOrganisations.Repository() + var query = uowOrganisations.UserServices .AsNoTracking() .Include(x => x.User) .Include(x => x.Organisation) @@ -88,7 +86,7 @@ public override async Task InvokeAsync( private async Task> GetServiceRolesLookupAsync( Guid appId, List userIds, CancellationToken ct) { - var roles = await uowOrganisations.Repository() + var roles = await uowOrganisations.UserServiceRoles .AsNoTracking() .Include(x => x.Role) .Where(x => x.ServiceId == appId) @@ -109,7 +107,7 @@ public override async Task InvokeAsync( private async Task> GetOrgRolesLookupAsync( List userIds, CancellationToken ct) { - var orgRoles = await uowOrganisations.Repository() + var orgRoles = await uowOrganisations.UserOrganisations .AsNoTracking() .Where(x => userIds.Contains(x.UserId)) .Select(x => new { x.UserId, x.OrganisationId, x.RoleId }) diff --git a/src/Dfe.SignIn.Core.UseCases/Users/GetUserOrganisationIdentifiersUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/GetUserOrganisationIdentifiersUseCase.cs index e9e8fdef..a8de41bc 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/GetUserOrganisationIdentifiersUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/GetUserOrganisationIdentifiersUseCase.cs @@ -1,7 +1,6 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -12,7 +11,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// legacy system integration. /// public sealed class GetUserOrganisationIdentifiersUseCase( - IUnitOfWorkOrganisations uowOrganisations + DbOrganisationsContext unitOfWork ) : Interactor { /// @@ -22,8 +21,8 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var userOrganisation = await uowOrganisations - .Repository() + var userOrganisation = await unitOfWork + .UserOrganisations .Where(x => x.UserId == context.Request.UserId && x.OrganisationId == context.Request.OrganisationId) .FirstOrDefaultAsync(cancellationToken); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/GetUserProfileUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/GetUserProfileUseCase.cs index a29ef45c..0c35e265 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/GetUserProfileUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/GetUserProfileUseCase.cs @@ -1,7 +1,6 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Directories; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -10,7 +9,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// Use case for getting the profile of a user. /// public sealed class GetUserProfileUseCase( - IUnitOfWorkDirectories unitOfWork + DbDirectoriesContext unitOfWork ) : Interactor { /// @@ -20,7 +19,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var user = await unitOfWork.Repository() + var user = await unitOfWork.Users .Where(x => x.Sub == context.Request.UserId) .FirstOrDefaultAsync(cancellationToken) ?? throw UserNotFoundException.FromUserId(context.Request.UserId); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/GetUserServiceAccessDetailsUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/GetUserServiceAccessDetailsUseCase.cs index 9e99e701..19348d68 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/GetUserServiceAccessDetailsUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/GetUserServiceAccessDetailsUseCase.cs @@ -2,8 +2,7 @@ using Dfe.SignIn.Core.Contracts.Access; using Dfe.SignIn.Core.Contracts.Organisations; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -25,8 +24,8 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// /// public sealed class GetUserServiceAccessDetailsUseCase( - IInteractionDispatcher interaction, - IUnitOfWorkOrganisations uowOrganisations + DbOrganisationsContext uowOrganisations, + IInteractionDispatcher interaction ) : Interactor { /// @@ -85,7 +84,7 @@ public override async Task InvokeAsync( private async Task GetUserService(Guid userId, Guid serviceId, Guid organisationId) { - var userService = await uowOrganisations.Repository() + var userService = await uowOrganisations.UserServices .AsNoTracking() .Where(x => x.ServiceId == serviceId) .Where(x => x.UserId == userId) @@ -96,7 +95,7 @@ private async Task GetUserService(Guid userId, Gui return new GetUserServiceAccessResponse { Access = null }; } - var roles = await uowOrganisations.Repository() + var roles = await uowOrganisations.UserServiceRoles .AsNoTracking() .Include(x => x.Role) .Where(x => x.UserId == userId) @@ -104,7 +103,7 @@ private async Task GetUserService(Guid userId, Gui .Where(x => x.OrganisationId == organisationId) .ToListAsync(); - var identifiers = await uowOrganisations.Repository() + var identifiers = await uowOrganisations.UserServiceIdentifiers .AsNoTracking() .Where(x => x.UserId == userId) .Where(x => x.ServiceId == serviceId) diff --git a/src/Dfe.SignIn.Core.UseCases/Users/GetUserStatusUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/GetUserStatusUseCase.cs index d7ceed84..1243c903 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/GetUserStatusUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/GetUserStatusUseCase.cs @@ -2,7 +2,7 @@ using Dfe.SignIn.Base.Framework.Internal; using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -11,7 +11,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// An interactor to determine if a user exists and retrieve their account status. /// public sealed class GetUserStatusUseCase( - IUnitOfWorkDirectories unitOfWork + DbDirectoriesContext unitOfWork ) : Interactor { /// @@ -21,7 +21,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var queryBuilder = unitOfWork.Repository(); + IQueryable queryBuilder = unitOfWork.Users; queryBuilder = context.Request.EntraUserId.HasValue ? queryBuilder.Where(x => x.EntraOid == context.Request.EntraUserId.Value) : queryBuilder.Where(x => x.Email == context.Request.EmailAddress); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/IsOrganisationApproverUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/IsOrganisationApproverUseCase.cs index 8e3755b6..8f8ad6d4 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/IsOrganisationApproverUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/IsOrganisationApproverUseCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Organisations; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -10,9 +9,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// /// Use case for checking if a user is an approver for any of their associated organisations. /// -public sealed class IsOrganisationApproverUseCase( - IUnitOfWorkOrganisations unitOfWork -) +public sealed class IsOrganisationApproverUseCase(DbOrganisationsContext unitOfWork) : Interactor { /// @@ -22,7 +19,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var isApprover = await unitOfWork.Repository() + var isApprover = await unitOfWork.UserOrganisations .Where(x => x.UserId == context.Request.UserId) .AnyAsync(x => x.RoleId == OrganisationRoles.Approver.Id, cancellationToken); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/LinkEntraUserToDsiUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/LinkEntraUserToDsiUseCase.cs index 7b76721b..36b7316b 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/LinkEntraUserToDsiUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/LinkEntraUserToDsiUseCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Audit; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Directories; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -11,7 +10,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// Use case for linking an Entra user to a DfE Sign-in user. /// public sealed class LinkEntraUserToDsiUseCase( - IUnitOfWorkDirectories unitOfWork, + DbDirectoriesContext unitOfWork, IInteractionDispatcher interaction, TimeProvider timeProvider ) : Interactor @@ -23,7 +22,7 @@ public override async Task InvokeAsync( { context.ThrowIfHasValidationErrors(); - var user = await unitOfWork.Repository() + var user = await unitOfWork.Users .Where(x => x.Sub == context.Request.DsiUserId) .SingleOrDefaultAsync(cancellationToken: cancellationToken) ?? throw UserNotFoundException.FromUserId(context.Request.DsiUserId); @@ -34,8 +33,7 @@ public override async Task InvokeAsync( user.Sub, userEntraOid, context.Request.EntraUserId); } - var existingEntraUser = await unitOfWork - .Repository() + var existingEntraUser = await unitOfWork.Users .Where(x => x.EntraOid == context.Request.EntraUserId) .Select(x => new { x.Sub }) .FirstOrDefaultAsync(cancellationToken); diff --git a/src/Dfe.SignIn.Core.UseCases/Users/PendingApprovalCountUseCase.cs b/src/Dfe.SignIn.Core.UseCases/Users/PendingApprovalCountUseCase.cs index dbe8a5cb..846f5ab2 100644 --- a/src/Dfe.SignIn.Core.UseCases/Users/PendingApprovalCountUseCase.cs +++ b/src/Dfe.SignIn.Core.UseCases/Users/PendingApprovalCountUseCase.cs @@ -1,8 +1,7 @@ using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Organisations; using Dfe.SignIn.Core.Contracts.Users; -using Dfe.SignIn.Core.Entities.Organisations; -using Dfe.SignIn.Core.Interfaces.DataAccess; +using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.Users; @@ -12,7 +11,7 @@ namespace Dfe.SignIn.Core.UseCases.Users; /// services for a approval user /// /// -public sealed class PendingApprovalCountUseCase(IUnitOfWorkOrganisations unitOfWork) : Interactor +public sealed class PendingApprovalCountUseCase(DbOrganisationsContext unitOfWork) : Interactor { /// /// Calculates the number of outstanding approvals that the Approval user for the given @@ -25,15 +24,15 @@ public sealed class PendingApprovalCountUseCase(IUnitOfWorkOrganisations unitOfW /// public override async Task InvokeAsync(InteractionContext context, CancellationToken cancellationToken = default) { - var orgIds = await unitOfWork.Repository().Include(x => x.Organisation) + var orgIds = await unitOfWork.UserOrganisations.Include(x => x.Organisation) .Where(x => x.UserId == context.Request.UserId && x.RoleId == OrganisationRoles.Approver.Id) .Select(x => x.OrganisationId) .ToListAsync(cancellationToken); - var pendingServiceNotificationCount = await unitOfWork.Repository() + var pendingServiceNotificationCount = await unitOfWork.UserServiceRequests .CountAsync(x => orgIds.Contains(x.OrganisationId) && !x.ActionedAt.HasValue, cancellationToken); - var pendingOrganisationNotificationCount = await unitOfWork.Repository() + var pendingOrganisationNotificationCount = await unitOfWork.UserOrganisationRequests .CountAsync(x => orgIds.Contains(x.OrganisationId) && !x.ActionedAt.HasValue, cancellationToken); var pendingCount = pendingServiceNotificationCount + pendingOrganisationNotificationCount; diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/Configuration/UnitOfWorkEntityFrameworkExtensions.cs b/src/Dfe.SignIn.Gateways.EntityFramework/Configuration/UnitOfWorkEntityFrameworkExtensions.cs index 8b4d90c9..61ea92e5 100644 --- a/src/Dfe.SignIn.Gateways.EntityFramework/Configuration/UnitOfWorkEntityFrameworkExtensions.cs +++ b/src/Dfe.SignIn.Gateways.EntityFramework/Configuration/UnitOfWorkEntityFrameworkExtensions.cs @@ -1,6 +1,5 @@ using Dfe.SignIn.Base.Framework; -using Dfe.SignIn.Core.Interfaces.DataAccess; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -42,24 +41,22 @@ public static IServiceCollection AddUnitOfWorkEntityFrameworkServices( if (addDirectoriesUnitOfWork || addOrganisationsUnitOfWork || addAuditUnitOfWork) { services.TryAddSingleton(TimeProvider.System); - services.AddScoped(); - services.Decorate(); services.AddScoped(); } - AddUnitOfWork( + AddUnitOfWork( services, section, "Directories", addDirectoriesUnitOfWork); - AddUnitOfWork( + AddUnitOfWork( services, section, "Organisations", addOrganisationsUnitOfWork); - AddUnitOfWork( + AddUnitOfWork( services, section, "Audit", @@ -74,13 +71,6 @@ public static IServiceCollection AddUnitOfWorkEntityFrameworkServices( /// with a SQL Server connection string derived from configuration and attaches a /// to automatically manage CreatedAt and UpdatedAt timestamps. /// - /// - /// The interface type representing the unit of work contract. - /// - /// - /// The concrete implementation type of the unit of work. Must inherit from - /// and implement . - /// /// /// The type of associated with this unit of work. /// @@ -104,14 +94,12 @@ public static IServiceCollection AddUnitOfWorkEntityFrameworkServices( /// (Host, Name, Username, Password) /// is missing for the specified . /// - private static void AddUnitOfWork( + private static void AddUnitOfWork( IServiceCollection services, IConfiguration section, string configKey, bool register) where TDbContext : DbContext - where TUnitOfWorkConcrete : EntityFrameworkUnitOfWork, TUnitOfWorkContract - where TUnitOfWorkContract : class, IUnitOfWork { if (!register) { return; @@ -143,6 +131,5 @@ private static void AddUnitOfWork(); } } diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/EntityFrameworkUnitOfWork.cs b/src/Dfe.SignIn.Gateways.EntityFramework/EntityFrameworkUnitOfWork.cs deleted file mode 100644 index 0adbd242..00000000 --- a/src/Dfe.SignIn.Gateways.EntityFramework/EntityFrameworkUnitOfWork.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Dfe.SignIn.Core.Interfaces.DataAccess; -using Microsoft.EntityFrameworkCore; - -namespace Dfe.SignIn.Gateways.EntityFramework; - -/// -/// Implements the pattern using Entity Framework Core. -/// Manages a shared instance and provides access to typed repositories. -/// -public abstract class EntityFrameworkUnitOfWork : IUnitOfWork -{ - private readonly DbContext dbContext; - private readonly IEntityFrameworkTransactionContext transactionContext; - - /// - /// Creates a new using the provided . - /// - /// The Entity Framework database context to use. - /// The Entity Framework core transaction context. - protected EntityFrameworkUnitOfWork(DbContext dbContext, IEntityFrameworkTransactionContext transactionContext) - { - this.dbContext = dbContext; - this.transactionContext = transactionContext; - } - - /// - /// Returns an representing the underlying - /// Entity Framework Core for the specified entity type. - /// - /// - /// The entity type for which the queryable repository is requested. - /// - /// - /// An that can be used to query and modify - /// entities of type . - /// Changes made to tracked entities are persisted when the unit of work - /// is committed. - /// - public IQueryable Repository() where TEntity : class - { - return this.dbContext.Set(); - } - - /// - /// - /// This method queues an insert operation by adding the entity to the DbContext. - /// No database interaction occurs until is called. - /// - public async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class - { - await this.dbContext.Set().AddAsync(entity, cancellationToken); - } - - /// - /// - /// This method queues a delete operation by marking the entity as deleted in the DbContext. - /// No database interaction occurs until is called. - /// - public void Remove(TEntity entity) where TEntity : class - { - this.dbContext.Set().Remove(entity); - } - - /// - public Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - return this.dbContext.SaveChangesAsync(cancellationToken); - } - - /// - public async Task BeginTransactionAsync(CancellationToken cancellationToken = default) - { - this.transactionContext.InsideTransaction = true; - await this.dbContext.Database.BeginTransactionAsync(cancellationToken); - } - - /// - public Task CommitTransactionAsync(CancellationToken cancellationToken = default) - { - this.transactionContext.InsideTransaction = false; - return this.dbContext.Database.CurrentTransaction?.CommitAsync(cancellationToken) ?? Task.CompletedTask; - } - - /// - public Task RollbackTransactionAsync(CancellationToken cancellationToken = default) - { - this.transactionContext.InsideTransaction = false; - return this.dbContext.Database.CurrentTransaction?.RollbackAsync(cancellationToken) ?? Task.CompletedTask; - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Core dispose logic for EF Core resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) { - this.dbContext?.Dispose(); - } - } - - /// - public async ValueTask DisposeAsync() - { - await this.DisposeAsyncCore(); - GC.SuppressFinalize(this); - } - - /// - /// Core async dispose logic for EF Core resources. - /// - protected virtual async ValueTask DisposeAsyncCore() - { - if (this.dbContext != null) { - await this.dbContext.DisposeAsync(); - } - } -} diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/IEntityFrameworkTransactionContext.cs b/src/Dfe.SignIn.Gateways.EntityFramework/IEntityFrameworkTransactionContext.cs deleted file mode 100644 index a293b6a6..00000000 --- a/src/Dfe.SignIn.Gateways.EntityFramework/IEntityFrameworkTransactionContext.cs +++ /dev/null @@ -1,28 +0,0 @@ - -namespace Dfe.SignIn.Gateways.EntityFramework; - -/// -/// Defines a context for tracking transaction state in Entity Framework operations. -/// -public interface IEntityFrameworkTransactionContext -{ - /// - /// Indicates whether the current context is inside a transaction. - /// - bool InsideTransaction { get; set; } -} - -/// -/// Implementation of -/// that internally tracks nested transaction starts using a counter. -/// -public sealed class EntityFrameworkTransactionContext : IEntityFrameworkTransactionContext -{ - private int counter = 0; - - /// - public bool InsideTransaction { - get => this.counter > 0; - set => this.counter = Math.Max(0, this.counter + (value ? 1 : -1)); - } -} diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/ProtectTransactionInteractionDispatcher.cs b/src/Dfe.SignIn.Gateways.EntityFramework/ProtectTransactionInteractionDispatcher.cs deleted file mode 100644 index 994a5a35..00000000 --- a/src/Dfe.SignIn.Gateways.EntityFramework/ProtectTransactionInteractionDispatcher.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Dfe.SignIn.Base.Framework; - -namespace Dfe.SignIn.Gateways.EntityFramework; - -/// -/// An interaction dispatcher that checks whether the current -/// is inside a transaction, and throws an exception if so. This prevents nested Entity Framework Core -/// transactions, which are not supported. -/// -public sealed class ProtectTransactionInteractionDispatcher( - IInteractionDispatcher inner, - IEntityFrameworkTransactionContext transactionContext -) : IInteractionDispatcher -{ - /// - public InteractionTask DispatchAsync(InteractionContext context) where TRequest : class - { - if (transactionContext.InsideTransaction) { - throw new InvalidOperationException("Cannot dispatch interaction within the context of a transaction."); - } - return inner.DispatchAsync(context); - } -} diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkAudit.cs b/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkAudit.cs deleted file mode 100644 index c044f9d7..00000000 --- a/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkAudit.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Dfe.SignIn.Core.Interfaces.DataAccess; - -namespace Dfe.SignIn.Gateways.EntityFramework; - -/// -/// Represents a Unit of Work specifically for the . -/// Inherits from to provide repository access and transaction -/// management for the audit database. -/// -/// -/// This class implements as a marker interface -/// to allow dependency injection to distinguish it from other UnitOfWork implementations. -/// It is a thin wrapper around and does not add -/// additional logic. -/// -[ExcludeFromCodeCoverage] -public sealed class UnitOfWorkAudit : EntityFrameworkUnitOfWork, IUnitOfWorkAudit -{ - /// - /// Initializes a new instance of the class - /// using the specified . - /// - /// - /// The instance used to perform database operations. - /// - /// - /// The Entity Framework core transaction context. - /// - public UnitOfWorkAudit(DbAuditContext db, IEntityFrameworkTransactionContext transactionContext) : base(db, transactionContext) { } -} diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkDirectories.cs b/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkDirectories.cs deleted file mode 100644 index e04c8f6e..00000000 --- a/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkDirectories.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Dfe.SignIn.Core.Interfaces.DataAccess; - -namespace Dfe.SignIn.Gateways.EntityFramework; - -/// -/// Represents a Unit of Work specifically for the . -/// Inherits from to provide repository access and transaction -/// management for the directories database. -/// -/// -/// This class implements as a marker interface -/// to allow dependency injection to distinguish it from other UnitOfWork implementations. -/// It is a thin wrapper around and does not add -/// additional logic. -/// -[ExcludeFromCodeCoverage] -public sealed class UnitOfWorkDirectories : EntityFrameworkUnitOfWork, IUnitOfWorkDirectories -{ - /// - /// Initializes a new instance of the class - /// using the specified . - /// - /// - /// The instance used to perform database operations. - /// - /// - /// The Entity Framework core transaction context. - /// - public UnitOfWorkDirectories(DbDirectoriesContext db, IEntityFrameworkTransactionContext transactionContext) : base(db, transactionContext) { } -} diff --git a/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkOrganisations.cs b/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkOrganisations.cs deleted file mode 100644 index 47bfb07c..00000000 --- a/src/Dfe.SignIn.Gateways.EntityFramework/UnitOfWorkOrganisations.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Dfe.SignIn.Core.Interfaces.DataAccess; - -namespace Dfe.SignIn.Gateways.EntityFramework; - -/// -/// Represents a Unit of Work specifically for the . -/// Inherits from to provide repository access and transaction -/// management for the organisations database. -/// -/// -/// This class implements as a marker interface -/// to allow dependency injection to distinguish it from other UnitOfWork implementations. -/// It is a thin wrapper around and does not add -/// additional logic. -/// -[ExcludeFromCodeCoverage] -public sealed class UnitOfWorkOrganisations : EntityFrameworkUnitOfWork, IUnitOfWorkOrganisations -{ - /// - /// Initializes a new instance of the class - /// using the specified . - /// - /// - /// The instance used to perform database operations. - /// - /// - /// The Entity Framework core transaction context. - /// - public UnitOfWorkOrganisations(DbOrganisationsContext db, IEntityFrameworkTransactionContext transactionContext) : base(db, transactionContext) { } -} diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationApiConfigurationUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationApiConfigurationUseCaseTests.cs index 7e94afd1..fe8a4303 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationApiConfigurationUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationApiConfigurationUseCaseTests.cs @@ -1,7 +1,11 @@ +using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Applications; using Dfe.SignIn.Core.Contracts.PublicApi; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.Applications; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Moq; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Applications; @@ -12,15 +16,27 @@ public sealed class GetApplicationApiConfigurationUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetApplicationApiConfigurationRequest, GetApplicationApiConfigurationUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.Services.Add(new ServiceEntity { Id = Guid.Parse("b03e0aa5-f2a9-4926-8be0-9b996f97790d"), @@ -31,14 +47,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task Throws_WhenApplicationNotFound() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationApiConfigurationUseCase interactor = new(orgCtx, Mock.Of()); string nonExistentClientId = "non-existent"; @@ -55,7 +72,9 @@ public async Task Throws_WhenApplicationNotFound() public async Task ReturnsApiConfiguration() { var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); + var orgCtx = await SetupFakeDatabaseAsync(); + + autoMocker.Use(orgCtx); // register DbContext autoMocker.MockResponse( new DecryptApiSecretRequest { @@ -66,7 +85,8 @@ public async Task ReturnsApiConfiguration() } ); - var interactor = autoMocker.CreateInstance(); + var interactor = autoMocker + .CreateInstance(); var response = await interactor.InvokeAsync( new GetApplicationApiConfigurationRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationByClientIdUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationByClientIdUseCaseTests.cs index 80c1f77f..cc2cee44 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationByClientIdUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationByClientIdUseCaseTests.cs @@ -1,6 +1,8 @@ using Dfe.SignIn.Core.Contracts.Applications; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.Applications; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Applications; @@ -11,15 +13,27 @@ public sealed class GetApplicationByClientIdUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetApplicationByClientIdRequest, GetApplicationByClientIdUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.Services.Add(new ServiceEntity { Id = Guid.Parse("01e876a1-a06d-4945-bbe0-599a3d73dd11"), @@ -58,14 +72,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task Throws_WhenApplicationNotFound() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationByClientIdUseCase interactor = new(orgCtx); string nonExistentClientId = "non-existent"; @@ -131,10 +146,8 @@ public static IEnumerable GetCasesForReturnsApplicationInformation() [DynamicData(nameof(GetCasesForReturnsApplicationInformation), DynamicDataSourceType.Method)] public async Task ReturnsApplicationInformation(string clientId, GetApplicationByClientIdResponse expectedResponse) { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationByClientIdUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationByClientIdRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationRolesUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationRolesUseCaseTests.cs index 5280113f..4f4aab2b 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationRolesUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Applications/GetApplicationRolesUseCaseTests.cs @@ -1,6 +1,8 @@ using Dfe.SignIn.Core.Contracts.Applications; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.Applications; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Applications; @@ -11,19 +13,31 @@ public sealed class GetApplicationRolesUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetApplicationRolesRequest, GetApplicationRolesUseCase - >(); + >(autoMocker); } private static readonly Guid AppAId = Guid.Parse("01e876a1-a06d-4945-bbe0-599a3d73dd11"); private static readonly Guid AppBId = Guid.Parse("33510512-33f3-491c-86ca-e036726584d0"); private static readonly Guid AppCId = Guid.Parse("f8da743c-eeb7-4be4-8045-54fba2e13419"); - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.Services.Add(new ServiceEntity { Id = AppAId, @@ -102,6 +116,8 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) // AppC has no roles await ctx.SaveChangesAsync(); + + return ctx; } public static IEnumerable GetCasesForReturnsApplicationRoles() @@ -174,10 +190,8 @@ public static IEnumerable GetCasesForReturnsApplicationRoles() [DynamicData(nameof(GetCasesForReturnsApplicationRoles), DynamicDataSourceType.Method)] public async Task ReturnsApplicationRoles(Guid applicationId, GetApplicationRolesResponse expectedResponse) { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationRolesUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationRolesRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetOrganisationByIdUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetOrganisationByIdUseCaseTests.cs index c70c8588..8d81ef54 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetOrganisationByIdUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetOrganisationByIdUseCaseTests.cs @@ -2,6 +2,8 @@ using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.Public; using Dfe.SignIn.Core.UseCases.Organisations; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Organisations; @@ -12,15 +14,27 @@ public sealed class GetOrganisationByIdUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetOrganisationByIdRequest, GetOrganisationByIdUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.Organisations.Add(new OrganisationEntity { Id = Guid.Parse("d289bd61-06c5-4fcf-b0f1-7509bc3570f4"), @@ -30,14 +44,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task Throws_WhenOrganisationNotFound() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetOrganisationByIdUseCase interactor = new(orgCtx); Guid nonExistentUserId = Guid.Parse("6d690a96-c392-4482-b750-733ea472bc96"); @@ -53,9 +68,8 @@ public async Task Throws_WhenOrganisationNotFound() [TestMethod] public async Task ReturnsExpectedProfile() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetOrganisationByIdUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetOrganisationByIdRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetUsersAtOrganisationUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetUsersAtOrganisationUseCaseTests.cs index 39587f61..9b435877 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetUsersAtOrganisationUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Organisations/GetUsersAtOrganisationUseCaseTests.cs @@ -2,7 +2,8 @@ using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.Organisations; -using Moq.AutoMock; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.UnitTests.Organisations; @@ -18,9 +19,13 @@ public sealed class GetUsersAtOrganisationUseCaseTests private const string Ukprn = "12345678"; private const string Upin = "87654321"; - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); // Organisations ctx.Organisations.Add(new OrganisationEntity { @@ -104,15 +109,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task ReturnsUsers_WhenMatchingUkprn() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var useCase = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetUsersAtOrganisationUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUsersAtOrganisationRequestRaw(ClientId, Ukprn) @@ -130,10 +135,8 @@ public async Task ReturnsUsers_WhenMatchingUkprn() [TestMethod] public async Task FallsBackToUpin_WhenUkprnHasNoResults() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var useCase = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetUsersAtOrganisationUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUsersAtOrganisationRequestRaw(ClientId, Upin) @@ -147,10 +150,8 @@ public async Task FallsBackToUpin_WhenUkprnHasNoResults() [TestMethod] public async Task ReturnsEmpty_WhenNoMatchingOrganisation() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var useCase = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetUsersAtOrganisationUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUsersAtOrganisationRequestRaw(ClientId, "99999999") @@ -163,10 +164,8 @@ public async Task ReturnsEmpty_WhenNoMatchingOrganisation() [TestMethod] public async Task FiltersUsers_ByClientId() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var useCase = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetUsersAtOrganisationUseCase useCase = new(orgCtx); // Different clientId -> should return no users var response = await useCase.InvokeAsync( @@ -179,16 +178,18 @@ public async Task FiltersUsers_ByClientId() [TestMethod] public async Task ReturnsUsers_WithNullRole_WhenNoRoleAssigned() { - var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var orgCtx = new DbOrganisationsContext(options); var orgId = Guid.NewGuid(); var serviceId = Guid.NewGuid(); - ctx.Organisations.Add(new OrganisationEntity { Id = orgId, Ukprn = Ukprn, Name = "New organisation", Status = 1 }); + orgCtx.Organisations.Add(new OrganisationEntity { Id = orgId, Ukprn = Ukprn, Name = "New organisation", Status = 1 }); - ctx.Services.Add(new ServiceEntity { + orgCtx.Services.Add(new ServiceEntity { Id = serviceId, ClientId = ClientId, Name = "Service", @@ -205,17 +206,17 @@ public async Task ReturnsUsers_WithNullRole_WhenNoRoleAssigned() Salt = "pinch" }; - ctx.Users.Add(user); + orgCtx.Users.Add(user); - ctx.UserServices.Add(new UserServiceEntity { + orgCtx.UserServices.Add(new UserServiceEntity { OrganisationId = orgId, ServiceId = serviceId, UserId = user.Sub }); - await ctx.SaveChangesAsync(); + await orgCtx.SaveChangesAsync(); - var useCase = autoMocker.CreateInstance(); + GetUsersAtOrganisationUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUsersAtOrganisationRequestRaw(ClientId, Ukprn) diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/SupportTickets/GetApplicationNamesForSupportTicketUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/SupportTickets/GetApplicationNamesForSupportTicketUseCaseTests.cs index c97342c8..b0abeb77 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/SupportTickets/GetApplicationNamesForSupportTicketUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/SupportTickets/GetApplicationNamesForSupportTicketUseCaseTests.cs @@ -1,7 +1,8 @@ using Dfe.SignIn.Core.Contracts.SupportTickets; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.SupportTickets; -using Moq.AutoMock; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.UnitTests.SupportTickets; @@ -9,9 +10,13 @@ namespace Dfe.SignIn.Core.UseCases.UnitTests.SupportTickets; public sealed class GetApplicationNamesForSupportTicketUseCaseTests { - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.Services.Add(new ServiceEntity { Id = Guid.NewGuid(), @@ -98,14 +103,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task Services_WithNoParams_AreReturned() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -117,9 +123,8 @@ public async Task Services_WithNoParams_AreReturned() [TestMethod] public async Task Services_WithUnrelatedParams_AreReturned() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -131,9 +136,8 @@ public async Task Services_WithUnrelatedParams_AreReturned() [TestMethod] public async Task Services_WithHelpHiddenTrue_AreExcluded() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -145,9 +149,8 @@ public async Task Services_WithHelpHiddenTrue_AreExcluded() [TestMethod] public async Task Services_WithHelpHiddenFalse_AreReturned() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -159,9 +162,8 @@ public async Task Services_WithHelpHiddenFalse_AreReturned() [TestMethod] public async Task Services_WithMixedParams_IncludingHelpHiddenTrue_AreExcluded() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -173,9 +175,8 @@ public async Task Services_WithMixedParams_IncludingHelpHiddenTrue_AreExcluded() [TestMethod] public async Task ChildServices_AreExcluded() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -187,9 +188,8 @@ public async Task ChildServices_AreExcluded() [TestMethod] public async Task OnlyExpectedServices_AreReturned() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } @@ -211,9 +211,8 @@ public async Task OnlyExpectedServices_AreReturned() [TestMethod] public async Task OnlyExpectedServices_AreReturned_Ordered() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetApplicationNamesForSupportTicketUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetApplicationNamesForSupportTicketRequest { } diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/TestHelpers/EntityFrameworkTestHelpers.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/TestHelpers/EntityFrameworkTestHelpers.cs index cd0e4edb..5e2e09c5 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/TestHelpers/EntityFrameworkTestHelpers.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/TestHelpers/EntityFrameworkTestHelpers.cs @@ -1,13 +1,11 @@ -using Dfe.SignIn.Core.Interfaces.DataAccess; using Dfe.SignIn.Gateways.EntityFramework; using Microsoft.EntityFrameworkCore; -using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.TestHelpers; public static class EntityFrameworkTestHelpers { - public static DbDirectoriesContext UseInMemoryDirectoriesDb(this AutoMocker autoMocker) + public static DbDirectoriesContext UseInMemoryDirectoriesDb() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) @@ -15,14 +13,10 @@ public static DbDirectoriesContext UseInMemoryDirectoriesDb(this AutoMocker auto var ctx = new DbDirectoriesContext(options); - autoMocker.Use( - new UnitOfWorkDirectories(ctx, new EntityFrameworkTransactionContext()) - ); - return ctx; } - public static DbOrganisationsContext UseInMemoryOrganisationsDb(this AutoMocker autoMocker) + public static DbOrganisationsContext UseInMemoryOrganisationsDb() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) @@ -30,10 +24,6 @@ public static DbOrganisationsContext UseInMemoryOrganisationsDb(this AutoMocker var ctx = new DbOrganisationsContext(options); - autoMocker.Use( - new UnitOfWorkOrganisations(ctx, new EntityFrameworkTransactionContext()) - ); - return ctx; } } diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeJobTitleUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeJobTitleUseCaseTests.cs index 56728feb..acb362a8 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeJobTitleUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeJobTitleUseCaseTests.cs @@ -3,6 +3,7 @@ using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.UseCases.Users; using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -10,21 +11,32 @@ namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; [TestClass] public sealed class ChangeJobTitleUseCaseTests { - private static readonly Guid ExistingChangedUserId = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"); [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var dirCtx = new DbDirectoriesContext(options); + autoMocker.Use(dirCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< ChangeJobTitleRequest, ChangeJobTitleUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryDirectoriesDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbDirectoriesContext(options); ctx.Users.Add(new UserEntity { Sub = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"), @@ -55,13 +67,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocke SetupAsync() { var autoMocker = new AutoMocker(); - var db = await SetupFakeDatabaseAsync(autoMocker); + var dirCtx = await SetupFakeDatabaseAsync(); + autoMocker.Use(dirCtx); + var interactor = autoMocker.CreateInstance(); var capturedAudit = new List(); autoMocker.CaptureRequest(capturedAudit.Add); - return (interactor, db, capturedAudit); + return (interactor, dirCtx, capturedAudit); } [TestMethod] diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeNameUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeNameUseCaseTests.cs index f0a00e4f..22cabc90 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeNameUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/ChangeNameUseCaseTests.cs @@ -3,6 +3,7 @@ using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.UseCases.Users; using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -16,22 +17,31 @@ public sealed class ChangeNameUseCaseTests SetupAsync() { var autoMocker = new AutoMocker(); - var db = await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + autoMocker.Use(dirCtx); + ChangeNameUseCase interactor = autoMocker.CreateInstance(); var capturedAudit = new List(); autoMocker.CaptureRequest(capturedAudit.Add); - return (interactor, db, capturedAudit); + return (interactor, dirCtx, capturedAudit); } [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var dirCtx = new DbDirectoriesContext(options); + autoMocker.Use(dirCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< ChangeNameRequest, ChangeNameUseCase - >(); + >(autoMocker); } [TestMethod] @@ -216,9 +226,13 @@ await interactor.InvokeAsync( Assert.IsEmpty(capturedAudit); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryDirectoriesDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbDirectoriesContext(options); ctx.Users.Add(new UserEntity { Sub = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"), diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/CreateUserUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/CreateUserUseCaseTests.cs index cc573635..6773341f 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/CreateUserUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/CreateUserUseCaseTests.cs @@ -1,8 +1,11 @@ +using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Search; using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.UseCases.Users; using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Moq; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -13,19 +16,31 @@ public sealed class CreateUserUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var dirCtx = new DbDirectoriesContext(options); + autoMocker.Use(dirCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< CreateUserRequest, CreateUserUseCase - >(); + >(autoMocker); } private static readonly Guid UserIdMatchingEmail = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"); private static readonly Guid UserIdMatchingEntraOid = Guid.Parse("74f539fa-5de2-4b15-b983-24ca9612b7cb"); private static readonly Guid UserEntraOid = Guid.Parse("ecf31b2d-03e8-4f00-9035-32d2dd4a9ed3"); - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryDirectoriesDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbDirectoriesContext(options); ctx.Users.Add(new UserEntity { Sub = UserIdMatchingEmail, @@ -58,9 +73,9 @@ private static async Task SetupFakeDatabaseAsync(AutoMocke [TestMethod] public async Task ThrowsWhen_EmailAlreadyExists() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + CreateUserUseCase interactor = + new(dirCtx, Mock.Of(), Mock.Of()); await Assert.ThrowsExactlyAsync(() => interactor.InvokeAsync( @@ -76,9 +91,9 @@ await Assert.ThrowsExactlyAsync(() [TestMethod] public async Task ThrowsWhen_EntraUserIdAlreadyExists() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + CreateUserUseCase interactor = + new(dirCtx, Mock.Of(), Mock.Of()); await Assert.ThrowsExactlyAsync(() => interactor.InvokeAsync( @@ -94,9 +109,9 @@ await Assert.ThrowsExactlyAsync(() [TestMethod] public async Task ShouldCreateNewUser() { - var autoMocker = new AutoMocker(); - var db = await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + CreateUserUseCase interactor = + new(dirCtx, Mock.Of(), Mock.Of()); var user = await interactor.InvokeAsync( new CreateUserRequest { @@ -108,7 +123,7 @@ public async Task ShouldCreateNewUser() Assert.IsNotNull(user); - var matchingUser = db.Users.FirstOrDefault(x => x.Sub == user.UserId); + var matchingUser = dirCtx.Users.FirstOrDefault(x => x.Sub == user.UserId); Assert.IsNotNull(user); Assert.AreEqual("joe.brown@example.com", matchingUser?.Email); @@ -126,10 +141,18 @@ public async Task ShouldCreateNewUser() public async Task UpdatesUserInSearchIndex() { var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); + var dirCtx = await SetupFakeDatabaseAsync(); + var mockTimeProvider = new MockTimeProvider( + new DateTimeOffset(2025, 11, 18, 17, 56, 45, TimeSpan.Zero) + ); + + autoMocker.Use(dirCtx); + autoMocker.Use(mockTimeProvider); UpdateUserInSearchIndexRequest? capturedRequest = null; - autoMocker.CaptureRequest(r => capturedRequest = r); + autoMocker.CaptureRequest( + r => capturedRequest = r + ); var interactor = autoMocker.CreateInstance(); @@ -139,7 +162,8 @@ public async Task UpdatesUserInSearchIndex() FirstName = "joe", LastName = "brown", EntraUserId = Guid.Parse("fa70e11c-f1eb-4bab-9fa0-ff36a9620066") - }); + } + ); Assert.IsNotNull(capturedRequest); Assert.AreEqual(user.UserId, capturedRequest.UserId); diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetOrganisationsAssociatedWithUserUserCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetOrganisationsAssociatedWithUserUserCaseTests.cs index ac4ff534..5264324f 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetOrganisationsAssociatedWithUserUserCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetOrganisationsAssociatedWithUserUserCaseTests.cs @@ -3,6 +3,8 @@ using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.Public; using Dfe.SignIn.Core.UseCases.Users; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -13,15 +15,27 @@ public sealed class GetOrganisationsAssociatedWithUserUserCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetOrganisationsAssociatedWithUserRequest, GetOrganisationsAssociatedWithUserUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.Organisations.Add(new OrganisationEntity { Id = Guid.Parse("d289bd61-06c5-4fcf-b0f1-7509bc3570f4"), @@ -72,14 +86,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task ReturnsExpectedCollection() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetOrganisationsAssociatedWithUserUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync( new GetOrganisationsAssociatedWithUserRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetServiceUsersUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetServiceUsersUseCaseTests.cs index 9c88fd3a..00dc8619 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetServiceUsersUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetServiceUsersUseCaseTests.cs @@ -3,6 +3,8 @@ using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.Users; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -15,10 +17,21 @@ public class GetServiceUsersUseCaseTests private static readonly Guid UserId = Guid.Parse("c1c1c1c1-3333-3333-3333-333333333333"); [TestMethod] - public Task Throws_WhenRequestIsInvalid() => InteractionAssert.ThrowsWhenRequestIsInvalid< + public Task Throws_WhenRequestIsInvalid() + { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetServiceUsersRequest, GetServiceUsersUseCase - >(); + >(autoMocker); + } private static OrganisationEntity CreateOrganisation(string name = "Org 1") => new() { @@ -90,10 +103,14 @@ private static GetServiceUsersRequest CreateRequest(int pageNumber = 1, int page [TestMethod] public async Task ReturnsEmpty_WhenNoServiceUsers() { - var autoMocker = new AutoMocker(); - autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + + GetServiceUsersUseCase interactor = new(orgCtx); - var interactor = autoMocker.CreateInstance(); var response = await interactor.InvokeAsync(new GetServiceUsersRequest { ApplicationId = ServiceId, PageNumber = 1, @@ -109,8 +126,11 @@ public async Task ReturnsEmpty_WhenNoServiceUsers() [TestMethod] public async Task ReturnsSingleUser_WithOrgAndRoles() { - var autoMocker = new AutoMocker(); - var orgCtx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); orgCtx.Organisations.Add(CreateOrganisation()); orgCtx.Users.Add(CreateUser()); @@ -123,7 +143,7 @@ public async Task ReturnsSingleUser_WithOrgAndRoles() await orgCtx.SaveChangesAsync(); - var interactor = autoMocker.CreateInstance(); + GetServiceUsersUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync(CreateRequest()); Assert.AreEqual(1, response.Users.Count); @@ -140,8 +160,11 @@ public async Task ReturnsSingleUser_WithOrgAndRoles() [TestMethod] public async Task Handles_NullRoleForeignKey() { - var autoMocker = new AutoMocker(); - var orgCtx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); orgCtx.Organisations.Add(CreateOrganisation()); orgCtx.Users.Add(CreateUser()); @@ -152,7 +175,7 @@ public async Task Handles_NullRoleForeignKey() await orgCtx.SaveChangesAsync(); - var interactor = autoMocker.CreateInstance(); + GetServiceUsersUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync(CreateRequest()); Assert.AreEqual(1, response.Users.Count); @@ -162,8 +185,11 @@ public async Task Handles_NullRoleForeignKey() [TestMethod] public async Task Handles_MissingUserOrganisationRole() { - var autoMocker = new AutoMocker(); - var orgCtx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); orgCtx.Organisations.Add(CreateOrganisation("Test Org")); orgCtx.Users.Add(CreateUser()); @@ -171,7 +197,7 @@ public async Task Handles_MissingUserOrganisationRole() await orgCtx.SaveChangesAsync(); - var interactor = autoMocker.CreateInstance(); + GetServiceUsersUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync(CreateRequest()); Assert.AreEqual(1, response.Users.Count); @@ -184,8 +210,11 @@ public async Task Handles_MissingUserOrganisationRole() [TestMethod] public async Task Pagination_SkipTake_WithCorrectOrdering() { - var autoMocker = new AutoMocker(); - var orgCtx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); for (int i = 0 ; i < 3 ; i++) { var userId = Guid.Parse($"00000000-0000-0000-0000-00000000000{i + 1}"); @@ -208,7 +237,7 @@ public async Task Pagination_SkipTake_WithCorrectOrdering() orgCtx.Organisations.Add(CreateOrganisation()); await orgCtx.SaveChangesAsync(); - var interactor = autoMocker.CreateInstance(); + GetServiceUsersUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync(CreateRequest(2, 2)); Assert.AreEqual(1, response.Users.Count); @@ -219,8 +248,11 @@ public async Task Pagination_SkipTake_WithCorrectOrdering() [TestMethod] public async Task ReturnsEmpty_WhenAllMappedUsersHaveEmptyEmail() { - var autoMocker = new AutoMocker(); - var orgCtx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); orgCtx.Organisations.Add(CreateOrganisation()); orgCtx.UserServices.Add(CreateUserService()); @@ -228,7 +260,7 @@ public async Task ReturnsEmpty_WhenAllMappedUsersHaveEmptyEmail() await orgCtx.SaveChangesAsync(); - var interactor = autoMocker.CreateInstance(); + GetServiceUsersUseCase interactor = new(orgCtx); var response = await interactor.InvokeAsync(CreateRequest()); Assert.AreEqual(0, response.Users.Count); diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserOrganisationIdentifiersUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserOrganisationIdentifiersUseCaseTests.cs index 927b782d..809d2b9d 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserOrganisationIdentifiersUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserOrganisationIdentifiersUseCaseTests.cs @@ -1,6 +1,8 @@ using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.UseCases.Users; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -14,15 +16,27 @@ public sealed class GetUserOrganisationIdentifiersUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetUserOrganisationIdentifiersRequest, GetUserOrganisationIdentifiersUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); ctx.UserOrganisations.Add(new UserOrganisationEntity { UserId = UserId, @@ -34,14 +48,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task ReturnsIdentifiers_WhenUserOrganisationExists() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var useCase = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetUserOrganisationIdentifiersUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUserOrganisationIdentifiersRequest { @@ -57,9 +72,8 @@ public async Task ReturnsIdentifiers_WhenUserOrganisationExists() [TestMethod] public async Task ReturnsNullIdentifiers_WhenUserOrganisationDoesNotExist() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var useCase = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + GetUserOrganisationIdentifiersUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUserOrganisationIdentifiersRequest { @@ -76,9 +90,13 @@ public async Task ReturnsNullIdentifiers_WhenUserOrganisationDoesNotExist() public async Task ReturnsNullIdentifiers_WhenNumericAndTextIdentifiersAreNotSet() { var autoMocker = new AutoMocker(); - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; - ctx.UserOrganisations.Add(new UserOrganisationEntity { + var orgCtx = new DbOrganisationsContext(options); + + orgCtx.UserOrganisations.Add(new UserOrganisationEntity { UserId = UserId, OrganisationId = OrganisationId, RoleId = 0, @@ -86,9 +104,9 @@ public async Task ReturnsNullIdentifiers_WhenNumericAndTextIdentifiersAreNotSet( NumericIdentifier = null, TextIdentifier = null, }); - await ctx.SaveChangesAsync(); + await orgCtx.SaveChangesAsync(); - var useCase = autoMocker.CreateInstance(); + GetUserOrganisationIdentifiersUseCase useCase = new(orgCtx); var response = await useCase.InvokeAsync( new GetUserOrganisationIdentifiersRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserProfileUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserProfileUseCaseTests.cs index 0680fe46..87a0699c 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserProfileUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserProfileUseCaseTests.cs @@ -1,6 +1,8 @@ using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.UseCases.Users; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -11,15 +13,27 @@ public sealed class GetUserProfileUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var dirCtx = new DbDirectoriesContext(options); + autoMocker.Use(dirCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetUserProfileRequest, GetUserProfileUseCase - >(); + >(autoMocker); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryDirectoriesDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbDirectoriesContext(options); ctx.Users.Add(new UserEntity { Sub = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"), @@ -46,14 +60,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task Throws_WhenUserNotFound() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + GetUserProfileUseCase interactor = new(dirCtx); Guid nonExistentUserId = Guid.Parse("6d690a96-c392-4482-b750-733ea472bc96"); @@ -96,9 +111,8 @@ public static IEnumerable GetCasesForReturnsExpectedProfile() [DynamicData(nameof(GetCasesForReturnsExpectedProfile), DynamicDataSourceType.Method)] public async Task ReturnsExpectedProfile(Guid userId, GetUserProfileResponse expectedResponse) { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + GetUserProfileUseCase interactor = new(dirCtx); var response = await interactor.InvokeAsync( new GetUserProfileRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserServiceAccessDetailsUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserServiceAccessDetailsUseCaseTests.cs index b80e30da..a30b24a2 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserServiceAccessDetailsUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserServiceAccessDetailsUseCaseTests.cs @@ -4,6 +4,8 @@ using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.Public; using Dfe.SignIn.Core.UseCases.Users; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -25,16 +27,28 @@ public sealed class GetUserServiceAccessDetailsUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var orgCtx = new DbOrganisationsContext(options); + autoMocker.Use(orgCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetUserServiceAccessDetailsRequest, GetUserServiceAccessDetailsUseCase - >(); + >(autoMocker); } private static async Task SetupWithAccessAsync() { var autoMocker = new AutoMocker(); - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); // Seed UserService (the access record) ctx.UserServices.Add(new UserServiceEntity { @@ -117,6 +131,8 @@ private static async Task SetupWithAccessAsync() } ); + autoMocker.Use(ctx); // register DbContext + return autoMocker; } @@ -124,7 +140,14 @@ private static async Task SetupWithAccessAsync() public async Task Throws_WhenUserHasNoAccess() { var autoMocker = new AutoMocker(); - autoMocker.UseInMemoryOrganisationsDb(); // empty DB — no UserService row + + // Simulate a scenario where the database is empty and there is no UserService row for the user. + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); + autoMocker.Use(ctx); var useCase = autoMocker.CreateInstance(); diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserStatusUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserStatusUseCaseTests.cs index a86ee44d..f2437dc3 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserStatusUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/GetUserStatusUseCaseTests.cs @@ -2,6 +2,8 @@ using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.UseCases.Users; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -12,19 +14,31 @@ public sealed class GetUserStatusUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var dirCtx = new DbDirectoriesContext(options); + autoMocker.Use(dirCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< GetUserStatusRequest, GetUserStatusUseCase - >(); + >(autoMocker); } private static readonly Guid UserIdMatchingEmail = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"); private static readonly Guid UserIdMatchingEntraOid = Guid.Parse("74f539fa-5de2-4b15-b983-24ca9612b7cb"); private static readonly Guid UserEntraOid = Guid.Parse("ecf31b2d-03e8-4f00-9035-32d2dd4a9ed3"); - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryDirectoriesDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbDirectoriesContext(options); ctx.Users.Add(new UserEntity { Sub = UserIdMatchingEmail, @@ -50,14 +64,15 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) }); await ctx.SaveChangesAsync(); + + return ctx; } [TestMethod] public async Task UserDoesNotExistByEntraOid_ReturnsExpectedObject() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + GetUserStatusUseCase interactor = new(dirCtx); var result = await interactor.InvokeAsync( new GetUserStatusRequest { @@ -76,9 +91,8 @@ public async Task UserDoesNotExistByEntraOid_ReturnsExpectedObject() [TestMethod] public async Task UserDoesNotExistByEmail_ReturnsExpectedObject() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + GetUserStatusUseCase interactor = new(dirCtx); var result = await interactor.InvokeAsync( new GetUserStatusRequest { @@ -97,9 +111,8 @@ public async Task UserDoesNotExistByEmail_ReturnsExpectedObject() [TestMethod] public async Task UserExistByEmail_ReturnsExpectedObject() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + GetUserStatusUseCase interactor = new(dirCtx); var result = await interactor.InvokeAsync( new GetUserStatusRequest { @@ -118,9 +131,8 @@ public async Task UserExistByEmail_ReturnsExpectedObject() [TestMethod] public async Task UserExistByEntraOid_ReturnsExpectedObject() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - var interactor = autoMocker.CreateInstance(); + var dirCtx = await SetupFakeDatabaseAsync(); + GetUserStatusUseCase interactor = new(dirCtx); var result = await interactor.InvokeAsync( new GetUserStatusRequest { diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/IsOrganisationApproverUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/IsOrganisationApproverUseCaseTests.cs index 8c1b3388..1f847f30 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/IsOrganisationApproverUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/IsOrganisationApproverUseCaseTests.cs @@ -3,7 +3,8 @@ using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.Public; using Dfe.SignIn.Core.UseCases.Users; -using Moq.AutoMock; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -14,10 +15,8 @@ public sealed class IsOrganisationApproverUseCaseTests public async Task Approver_IsFalseWhenUserHasNoApproverPermissions() { // Arrange - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + IsOrganisationApproverUseCase interactor = new(orgCtx); Guid nonExistentUserId = Guid.Parse("6d690a96-c392-4482-b750-733ea472bc96"); @@ -32,23 +31,25 @@ public async Task Approver_IsFalseWhenUserHasNoApproverPermissions() public async Task Approver_IsTrueWhenUserHasApproverPermissions() { // Arrange - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + IsOrganisationApproverUseCase interactor = new(orgCtx); - Guid nonExistentUserId = Guid.Parse("1d690a94-c392-4482-b750-711ea472bc96"); + Guid approverUserId = Guid.Parse("1d690a94-c392-4482-b750-711ea472bc96"); // Act - var result = await interactor.InvokeAsync(new IsOrganisationApproverRequest(nonExistentUserId)); + var result = await interactor.InvokeAsync(new IsOrganisationApproverRequest(approverUserId)); // Assert Assert.IsTrue(result.IsApprover); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); var user1Id = Guid.Parse("6d690a96-c392-4482-b750-733ea472bc96"); var user2Id = Guid.Parse("1d690a94-c392-4482-b750-711ea472bc96"); @@ -112,5 +113,7 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) await ctx.SaveChangesAsync(); + return ctx; + } } diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/LinkEntraUserToDsiUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/LinkEntraUserToDsiUseCaseTests.cs index fba9000f..f7c478bd 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/LinkEntraUserToDsiUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/LinkEntraUserToDsiUseCaseTests.cs @@ -1,8 +1,11 @@ +using Dfe.SignIn.Base.Framework; using Dfe.SignIn.Core.Contracts.Audit; using Dfe.SignIn.Core.Contracts.Users; using Dfe.SignIn.Core.Entities.Directories; using Dfe.SignIn.Core.UseCases.Users; using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Moq; using Moq.AutoMock; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -13,10 +16,18 @@ public sealed class LinkEntraUserToDsiUseCaseTests [TestMethod] public Task Throws_WhenRequestIsInvalid() { + var autoMocker = new AutoMocker(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var dirCtx = new DbDirectoriesContext(options); + autoMocker.Use(dirCtx); + return InteractionAssert.ThrowsWhenRequestIsInvalid< LinkEntraUserToDsiRequest, LinkEntraUserToDsiUseCase - >(); + >(autoMocker); } private static readonly Guid UserIdMatchingDsiUserId = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8691"); @@ -24,10 +35,15 @@ public Task Throws_WhenRequestIsInvalid() private static readonly Guid UserEntraOid = Guid.Parse("ecf31b2d-03e8-4f00-9035-32d2dd4a9ed3"); private static readonly Guid EntraOid = Guid.Parse("3ed6826f-6854-4adf-b65f-5b2ace7c8693"); - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker) + private static async Task SetupFakeDatabaseAsync() { - var ctx = autoMocker.UseInMemoryDirectoriesDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbDirectoriesContext(options); + ctx.Users.AddRange(new UserEntity { Sub = UserIdMatchingDsiUserId, IsEntra = false, @@ -62,9 +78,12 @@ private static async Task SetupFakeDatabaseAsync(AutoMocke { var autoMocker = new AutoMocker(); + var db = await SetupFakeDatabaseAsync(); + autoMocker.Use(db); + autoMocker.Use( - new MockTimeProvider(new DateTimeOffset(2025, 11, 18, 17, 56, 45, TimeSpan.Zero))); - var db = await SetupFakeDatabaseAsync(autoMocker); + new MockTimeProvider(new DateTimeOffset(2025, 11, 18, 17, 56, 45, TimeSpan.Zero)) + ); var interactor = autoMocker.CreateInstance(); @@ -96,10 +115,10 @@ await interactor.InvokeAsync( [TestMethod] public async Task Throws_WhenEntraAccountAlreadyLinkedToDifferentUser() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); + var orgCtx = await SetupFakeDatabaseAsync(); + var mockTimeProvider = new MockTimeProvider(new DateTimeOffset(2025, 11, 18, 17, 56, 45, TimeSpan.Zero)); - var interactor = autoMocker.CreateInstance(); + LinkEntraUserToDsiUseCase interactor = new(orgCtx, Mock.Of(), mockTimeProvider); var exception = await Assert.ThrowsExactlyAsync(async () => { await interactor.InvokeAsync( @@ -119,10 +138,10 @@ await interactor.InvokeAsync( [TestMethod] public async Task Throws_WhenUserAlreadyLinkedToAnEntraAccount() { - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); + var orgCtx = await SetupFakeDatabaseAsync(); + var mockTimeProvider = new MockTimeProvider(new DateTimeOffset(2025, 11, 18, 17, 56, 45, TimeSpan.Zero)); - var interactor = autoMocker.CreateInstance(); + LinkEntraUserToDsiUseCase interactor = new(orgCtx, Mock.Of(), mockTimeProvider); var exception = await Assert.ThrowsExactlyAsync(async () => { await interactor.InvokeAsync( diff --git a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/PendingApprovalCountUseCaseTests.cs b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/PendingApprovalCountUseCaseTests.cs index 9af12018..978200fa 100644 --- a/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/PendingApprovalCountUseCaseTests.cs +++ b/tests/Dfe.SignIn.Core.UseCases.UnitTests/Users/PendingApprovalCountUseCaseTests.cs @@ -3,7 +3,8 @@ using Dfe.SignIn.Core.Entities.Organisations; using Dfe.SignIn.Core.Public; using Dfe.SignIn.Core.UseCases.Users; -using Moq.AutoMock; +using Dfe.SignIn.Gateways.EntityFramework; +using Microsoft.EntityFrameworkCore; namespace Dfe.SignIn.Core.UseCases.UnitTests.Users; @@ -16,10 +17,8 @@ public sealed class PendingApprovalCountUseCaseTests public async Task GetPendingApprovalCount_Returns_OneWhenUserRequestAccessToOrganisation() { // Arrange - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker, CreatePendingOrganisationRequest: true); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(CreatePendingOrganisationRequest: true); + PendingApprovalCountUseCase interactor = new(orgCtx); // Act var result = await interactor.InvokeAsync(new GetPendingApprovalCountRequest() { UserId = ApprovalUserId }); @@ -32,10 +31,8 @@ public async Task GetPendingApprovalCount_Returns_OneWhenUserRequestAccessToOrga public async Task GetPendingApprovalCount_Returns_OneWhenUserRequestAccessToService() { // Arrange - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker, CreatePendingOrganisationRequest: false, CreatePendingServiceRequest: true); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(CreatePendingOrganisationRequest: false, CreatePendingServiceRequest: true); + PendingApprovalCountUseCase interactor = new(orgCtx); // Act var result = await interactor.InvokeAsync(new GetPendingApprovalCountRequest() { UserId = ApprovalUserId }); @@ -48,10 +45,8 @@ public async Task GetPendingApprovalCount_Returns_OneWhenUserRequestAccessToServ public async Task GetPendingApprovalCount_Returns_TwoWhenUserRequestsAccessToOrganisationAndService() { // Arrange - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker, CreatePendingOrganisationRequest: true, CreatePendingServiceRequest: true); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(CreatePendingOrganisationRequest: true, CreatePendingServiceRequest: true); + PendingApprovalCountUseCase interactor = new(orgCtx); // Act var result = await interactor.InvokeAsync(new GetPendingApprovalCountRequest() { UserId = ApprovalUserId }); @@ -64,10 +59,8 @@ public async Task GetPendingApprovalCount_Returns_TwoWhenUserRequestsAccessToOrg public async Task GetPendingApprovalCount_Returns_ZeroWhenNoActiveRequests() { // Arrange - var autoMocker = new AutoMocker(); - await SetupFakeDatabaseAsync(autoMocker); - - var interactor = autoMocker.CreateInstance(); + var orgCtx = await SetupFakeDatabaseAsync(); + PendingApprovalCountUseCase interactor = new(orgCtx); // Act var result = await interactor.InvokeAsync(new GetPendingApprovalCountRequest() { UserId = ApprovalUserId }); @@ -76,10 +69,14 @@ public async Task GetPendingApprovalCount_Returns_ZeroWhenNoActiveRequests() Assert.AreEqual(0, result.Count); } - private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker, bool CreatePendingOrganisationRequest = false, + private static async Task SetupFakeDatabaseAsync(bool CreatePendingOrganisationRequest = false, bool CreatePendingServiceRequest = false) { - var ctx = autoMocker.UseInMemoryOrganisationsDb(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + var ctx = new DbOrganisationsContext(options); var standardUser = Guid.Parse("1d690a94-c392-4482-b750-711ea472bc96"); @@ -130,5 +127,6 @@ private static async Task SetupFakeDatabaseAsync(AutoMocker autoMocker, bool Cre await ctx.SaveChangesAsync(); + return ctx; } } diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/Configuration/UnitOfWorkEntityFrameworkExtensionsTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/Configuration/UnitOfWorkEntityFrameworkExtensionsTests.cs index 01414cee..ec88cff7 100644 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/Configuration/UnitOfWorkEntityFrameworkExtensionsTests.cs +++ b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/Configuration/UnitOfWorkEntityFrameworkExtensionsTests.cs @@ -1,5 +1,4 @@ using Dfe.SignIn.Base.Framework; -using Dfe.SignIn.Core.Interfaces.DataAccess; using Dfe.SignIn.Gateways.EntityFramework.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -92,68 +91,6 @@ public void AddUnitOfWorkEntityFrameworkServices_Throws_WhenMissingRequiredConfi Assert.AreEqual($"Section 'Directories:{missingKey}' not found in configuration.", ex.Message); } - [TestMethod] - public void AddUnitOfWorkEntityFrameworkServices_Registers_TransactionDecorator_And_TransactionContext() - { - var services = new ServiceCollection(); - services.AddTransient(); - - services.AddUnitOfWorkEntityFrameworkServices( - this.configMock, - addDirectoriesUnitOfWork: true, - addOrganisationsUnitOfWork: false, - addAuditUnitOfWork: false); - - var provider = services.BuildServiceProvider(); - - var dispatcher = provider.GetRequiredService(); - Assert.IsInstanceOfType(dispatcher, typeof(ProtectTransactionInteractionDispatcher)); - - var transactionContext = provider.GetRequiredService(); - Assert.IsInstanceOfType(transactionContext, typeof(EntityFrameworkTransactionContext)); - } - - [TestMethod] - public void AddUnitOfWorkEntityFrameworkServices_DoesNotRegister_Decorator_IfNoUoWEnabled() - { - var services = new ServiceCollection(); - services.AddTransient(); - - services.AddUnitOfWorkEntityFrameworkServices( - this.configMock, - addDirectoriesUnitOfWork: false, - addOrganisationsUnitOfWork: false, - addAuditUnitOfWork: false); - - var provider = services.BuildServiceProvider(); - - var dispatcher = provider.GetRequiredService(); - Assert.IsInstanceOfType(dispatcher, typeof(FakeDispatcher)); - - Assert.IsNull(provider.GetService()); - } - - [TestMethod] - public void AddUnitOfWorkEntityFrameworkServices_Registers_Directories_UoW_And_Context() - { - var services = new ServiceCollection(); - services.AddTransient(); - - services.AddUnitOfWorkEntityFrameworkServices( - this.configMock, - addDirectoriesUnitOfWork: true, - addOrganisationsUnitOfWork: false, - addAuditUnitOfWork: false); - - var provider = services.BuildServiceProvider(); - - Assert.IsNotNull(provider.GetService()); - - Assert.IsInstanceOfType( - provider.GetRequiredService(), - typeof(UnitOfWorkDirectories)); - } - [TestMethod] public void AddUnitOfWorkEntityFrameworkServices_RegistersExpectedServices() { diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkRepositoryAndUnitOfWork.InMemoryTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkRepositoryAndUnitOfWork.InMemoryTests.cs deleted file mode 100644 index de1cc994..00000000 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkRepositoryAndUnitOfWork.InMemoryTests.cs +++ /dev/null @@ -1,227 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Dfe.SignIn.Gateways.EntityFramework.UnitTests; - -[TestClass] -public sealed class EntityFrameworkRepositoryAndUnitOfWorkInMemoryTests -{ - - public sealed class TestDbContext : DbContext - { - public TestDbContext(DbContextOptions options) : base(options) { } - public DbSet Hats => this.Set(); - } - - public sealed class Hat - { - public int Id { get; set; } - public string Colour { get; set; } = ""; - public Person? Owner { get; set; } - } - - public sealed class Person - { - public int Id { get; set; } - public string Name { get; set; } = ""; - } - - public sealed class MockEntityFrameworkUnitOfWork : EntityFrameworkUnitOfWork - { - public MockEntityFrameworkUnitOfWork(DbContext dbContext, IEntityFrameworkTransactionContext transactionContext) : base(dbContext, transactionContext) - { - } - } - - private TestDbContext context = null!; - private EntityFrameworkUnitOfWork uow = null!; - - [TestInitialize] - public void Setup() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) - .Options; - - this.context = new TestDbContext(options); - this.uow = new MockEntityFrameworkUnitOfWork(this.context, new EntityFrameworkTransactionContext()); - } - - [TestMethod] - public async Task CanAddAndRetrieveEntity() - { - var repo = this.uow.Repository(); - var entity = new Hat { Id = 1, Colour = "Red" }; - await this.uow.AddAsync(entity); - await this.uow.SaveChangesAsync(); - - var retrieved = await repo.Where(x => x.Id == 1).FirstOrDefaultAsync(); - Assert.IsNotNull(retrieved); - Assert.AreEqual("Red", retrieved.Colour); - } - - [TestMethod] - public async Task CanDeleteEntity() - { - var repo = this.uow.Repository(); - var entity = new Hat { Id = 3, Colour = "Yellow" }; - await this.uow.AddAsync(entity); - await this.uow.SaveChangesAsync(); - - this.uow.Remove(entity); - await this.uow.SaveChangesAsync(); - - var deleted = await repo.Where(x => x.Id == 3).FirstOrDefaultAsync(); - Assert.IsNull(deleted); - } - - [TestMethod] - public async Task Skip_WorksCorrectly() - { - var entities = new List - { - new() { Id = 1, Colour = "Red" }, - new() { Id = 2, Colour = "Blue" }, - new() { Id = 3, Colour = "Green" } - }; - - foreach (var e in entities) { - await this.uow.AddAsync(e); - } - - await this.uow.SaveChangesAsync(); - - var results = await this.uow.Repository() - .OrderBy(x => x.Id) - .Skip(1) - .ToListAsync(); - - Assert.HasCount(2, results); - Assert.AreEqual(2, results[0].Id); - Assert.AreEqual(3, results[1].Id); - } - - [TestMethod] - public async Task Take_WorksCorrectly() - { - var entities = new List - { - new() { Id = 1, Colour = "Red" }, - new() { Id = 2, Colour = "Blue" }, - new() { Id = 3, Colour = "Green" } - }; - - foreach (var e in entities) { - await this.uow.AddAsync(e); - } - - await this.uow.SaveChangesAsync(); - - var results = await this.uow.Repository() - .OrderBy(x => x.Id) - .Take(2) - .ToListAsync(); - - Assert.HasCount(2, results); - Assert.AreEqual(1, results[0].Id); - Assert.AreEqual(2, results[1].Id); - } - - [TestMethod] - public async Task OrderBy_WorksCorrectly() - { - var entities = new List - { - new() { Id = 1, Colour = "Red" }, - new() { Id = 2, Colour = "Blue" }, - new() { Id = 3, Colour = "Green" } - }; - - foreach (var e in entities) { - await this.uow.AddAsync(e); - } - - await this.uow.SaveChangesAsync(); - - var results = await this.uow.Repository() - .OrderBy(x => x.Colour) - .ToListAsync(); - - Assert.HasCount(3, results); - Assert.AreEqual("Blue", results[0].Colour); - Assert.AreEqual("Green", results[1].Colour); - Assert.AreEqual("Red", results[2].Colour); - } - - [TestMethod] - public async Task CanQueryWithWhere_Skip_Take_OrderBy_OrderByDescending() - { - var repo = this.uow.Repository(); - var entities = new List - { - new() { Id = 4, Colour = "Purple" }, - new() { Id = 5, Colour = "Orange" }, - new() { Id = 6, Colour = "Black" }, - new() { Id = 7, Colour = "White" }, - }; - - foreach (var e in entities) { - await this.uow.AddAsync(e); - } - - await this.uow.SaveChangesAsync(); - - var results = await repo.Where(e => e.Id > 4) - .OrderByDescending(e => e.Colour) - .Skip(1) - .Take(2) - .ToListAsync(); - - Assert.HasCount(2, results); - Assert.IsTrue(results.All(e => e.Id > 4)); - Assert.AreEqual("Orange", results[0].Colour); - Assert.AreEqual("Black", results[1].Colour); - } - - [TestMethod] - public void Repository_ReturnsSameInstanceForSameType() - { - var repo1 = this.uow.Repository(); - var repo2 = this.uow.Repository(); - - Assert.AreSame(repo1, repo2); - } - - [TestMethod] - public async Task SaveChangesAsync_UpdatesDatabase() - { - var repo = this.uow.Repository(); - var entity = new Hat { Id = 8, Colour = "Brown" }; - await this.uow.AddAsync(entity); - var result = await this.uow.SaveChangesAsync(); - - Assert.AreEqual(1, result); - var retrieved = await repo.Where(x => x.Id == 8).FirstOrDefaultAsync(); - Assert.IsNotNull(retrieved); - } - - [TestMethod] - public async Task Include_WorksCorrectly() - { - var repo = this.uow.Repository(); - - var hat = new Hat { - Id = 9, - Colour = "Magenta", - Owner = new Person { Id = 1, Name = "Alice" } - }; - await this.uow.AddAsync(hat); - await this.uow.SaveChangesAsync(); - - repo.Include(h => h.Owner); - var results = await repo.ToListAsync(); - - Assert.HasCount(1, results); - Assert.IsNotNull(results[0].Owner); - Assert.AreEqual("Alice", results[0].Owner!.Name); - } -} diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkTransactionContextTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkTransactionContextTests.cs deleted file mode 100644 index 12413be3..00000000 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkTransactionContextTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace Dfe.SignIn.Gateways.EntityFramework.UnitTests; - -[TestClass] -public sealed class EntityFrameworkTransactionContextTests -{ - [TestMethod] - public void InsideTransaction_ShouldBeFalse_Initially() - { - var ctx = new EntityFrameworkTransactionContext(); - - Assert.IsFalse(ctx.InsideTransaction); - } - - [TestMethod] - public void InsideTransaction_SetTrue_ShouldMakeInsideTransactionTrue() - { - var ctx = new EntityFrameworkTransactionContext { - InsideTransaction = true - }; - - Assert.IsTrue(ctx.InsideTransaction); - } - - [TestMethod] - public void InsideTransaction_SetTrueTwice_ShouldRemainTrue() - { - var ctx = new EntityFrameworkTransactionContext { - InsideTransaction = true - }; - - ctx.InsideTransaction = true; - - Assert.IsTrue(ctx.InsideTransaction); - } - - [TestMethod] - public void InsideTransaction_SetTrueTwiceAndFalseOnce_ShouldRemainTrue() - { - var ctx = new EntityFrameworkTransactionContext { - InsideTransaction = true - }; - ctx.InsideTransaction = true; - ctx.InsideTransaction = false; - - Assert.IsTrue(ctx.InsideTransaction); - } - - [TestMethod] - public void InsideTransaction_SetTrueThenFalse_ShouldBeFalse() - { - var ctx = new EntityFrameworkTransactionContext { - InsideTransaction = true - }; - ctx.InsideTransaction = false; - - Assert.IsFalse(ctx.InsideTransaction); - } - - [TestMethod] - public void InsideTransaction_SetFalseBelowZero_ShouldReturnFalse() - { - var ctx = new EntityFrameworkTransactionContext { - InsideTransaction = false - }; - - Assert.IsFalse(ctx.InsideTransaction); - } - - [TestMethod] - public void NestedIncrementAndDecrementSequence_ShouldBehaveCorrectly() - { - var ctx = new EntityFrameworkTransactionContext { - InsideTransaction = true - }; - ctx.InsideTransaction = true; - ctx.InsideTransaction = false; - ctx.InsideTransaction = false; - ctx.InsideTransaction = false; - - Assert.IsFalse(ctx.InsideTransaction); - } -} diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkUnitOfWorkTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkUnitOfWorkTests.cs deleted file mode 100644 index aef7b43e..00000000 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/EntityFrameworkUnitOfWorkTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Moq; - -namespace Dfe.SignIn.Gateways.EntityFramework.UnitTests; - -[TestClass] -public sealed class EntityFrameworkUnitOfWorkTests -{ - private Mock mockContext = null!; - private Mock mockDatabase = null!; - private EntityFrameworkUnitOfWork uow = null!; - - public sealed class TestEntity { public int Id { get; set; } } - - public sealed class MockEntityFrameworkUnitOfWork : EntityFrameworkUnitOfWork - { - public MockEntityFrameworkUnitOfWork(DbContext dbContext, IEntityFrameworkTransactionContext transactionContext) : base(dbContext, transactionContext) - { - } - } - - [TestInitialize] - public void Setup() - { - this.mockContext = new Mock(); - this.mockDatabase = new Mock(this.mockContext.Object); - this.mockContext.Setup(c => c.Database).Returns(this.mockDatabase.Object); - - this.uow = new MockEntityFrameworkUnitOfWork(this.mockContext.Object, new EntityFrameworkTransactionContext()); - } - - [TestMethod] - public void Repository_ReturnsSameInstanceForSameEntityType() - { - var repo1 = this.uow.Repository(); - var repo2 = this.uow.Repository(); - - Assert.AreSame(repo1, repo2); - } - - [TestMethod] - public async Task SaveChangesAsync_CallsDbContextSaveChangesAsync() - { - this.mockContext - .Setup(c => c.SaveChangesAsync(It.IsAny())) - .ReturnsAsync(1); - - var result = await this.uow.SaveChangesAsync(); - - this.mockContext.Verify(c => c.SaveChangesAsync(It.IsAny()), Times.Once); - Assert.AreEqual(1, result); - } - - [TestMethod] - public async Task BeginTransactionAsync_CallsBeginTransactionAsync() - { - this.mockDatabase.Setup(d => d.BeginTransactionAsync(It.IsAny())) - .ReturnsAsync(Mock.Of()); - - await this.uow.BeginTransactionAsync(); - - this.mockDatabase.Verify(d => d.BeginTransactionAsync(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task CommitTransactionAsync_CallsCommitAsync() - { - var mockTransaction = new Mock(); - this.mockDatabase.Setup(d => d.CurrentTransaction).Returns(mockTransaction.Object); - - await this.uow.CommitTransactionAsync(); - - mockTransaction.Verify(t => t.CommitAsync(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task RollbackTransactionAsync_CallsRollbackAsync() - { - var mockTransaction = new Mock(); - this.mockDatabase.Setup(d => d.CurrentTransaction).Returns(mockTransaction.Object); - - await this.uow.RollbackTransactionAsync(); - - mockTransaction.Verify(t => t.RollbackAsync(It.IsAny()), Times.Once); - } - - [TestMethod] - public void Dispose_CallsDbContextDispose() - { - this.uow.Dispose(); - this.mockContext.Verify(c => c.Dispose(), Times.Once); - } - - [TestMethod] - public async Task DisposeAsync_CallsDbContextDisposeAsync() - { - this.mockContext - .Setup(c => c.DisposeAsync()) - .Returns(ValueTask.CompletedTask) - .Verifiable(); - - await this.uow.DisposeAsync(); - - this.mockContext.Verify(c => c.DisposeAsync(), Times.Once); - } -} diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/ProtectTransactionInteractionDispatcherTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/ProtectTransactionInteractionDispatcherTests.cs deleted file mode 100644 index f9f52ffd..00000000 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/ProtectTransactionInteractionDispatcherTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Dfe.SignIn.Base.Framework; -using Moq; - -namespace Dfe.SignIn.Gateways.EntityFramework.UnitTests; - -[TestClass] -public sealed class ProtectTransactionInteractionDispatcherTests -{ - private Mock innerMock = null!; - - [TestInitialize] - public void Setup() - { - this.innerMock = new Mock(); - } - - [TestMethod] - public void DispatchAsync_Throws_WhenInsideTransaction() - { - var transactionContext = new EntityFrameworkTransactionContext { - InsideTransaction = true - }; - - var sut = new ProtectTransactionInteractionDispatcher( - this.innerMock.Object, - transactionContext - ); - - var ctx = new InteractionContext(new object()); - - Assert.ThrowsExactly(() => { - sut.DispatchAsync(ctx); - }); - - this.innerMock.Verify(x => x.DispatchAsync(It.IsAny>()), Times.Never); - } -} diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/UnitOfWorkDirectoriesTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/UnitOfWorkDirectoriesTests.cs deleted file mode 100644 index 0d75c12e..00000000 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/UnitOfWorkDirectoriesTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Dfe.SignIn.Gateways.EntityFramework.UnitTests; - -[TestClass] -public sealed class UnitOfWorkDirectoriesTests -{ - [TestMethod] - public void UnitOfWorkDirectories_CanBeConstructed() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("Dirs") - .Options; - - var dbContext = new DbDirectoriesContext(options); - - var uow = new UnitOfWorkDirectories(dbContext, new EntityFrameworkTransactionContext()); - - Assert.IsNotNull(uow); - Assert.IsInstanceOfType(uow, typeof(UnitOfWorkDirectories)); - Assert.IsInstanceOfType(uow, typeof(EntityFrameworkUnitOfWork)); - } -} diff --git a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/UnitOfWorkOrganisationsTests.cs b/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/UnitOfWorkOrganisationsTests.cs deleted file mode 100644 index b0dff344..00000000 --- a/tests/Dfe.SignIn.Gateways.EntityFramework.UnitTests/UnitOfWorkOrganisationsTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Dfe.SignIn.Gateways.EntityFramework.UnitTests; - -[TestClass] -public sealed class UnitOfWorkTestsOrganisations -{ - [TestMethod] - public void UnitOfWorkOrganisations_CanBeConstructed() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("Orgs") - .Options; - - var dbContext = new DbOrganisationsContext(options); - - var uow = new UnitOfWorkOrganisations(dbContext, new EntityFrameworkTransactionContext()); - - Assert.IsNotNull(uow); - Assert.IsInstanceOfType(uow, typeof(UnitOfWorkOrganisations)); - Assert.IsInstanceOfType(uow, typeof(EntityFrameworkUnitOfWork)); - } -} diff --git a/tests/Dfe.SignIn.PublicApi.UnitTests/Endpoints/Users/GetServiceUsers/GetServiceUsersTests.cs b/tests/Dfe.SignIn.PublicApi.UnitTests/Endpoints/Users/GetServiceUsers/GetServiceUsersTests.cs index 9ba0c4bc..588fa377 100644 --- a/tests/Dfe.SignIn.PublicApi.UnitTests/Endpoints/Users/GetServiceUsers/GetServiceUsersTests.cs +++ b/tests/Dfe.SignIn.PublicApi.UnitTests/Endpoints/Users/GetServiceUsers/GetServiceUsersTests.cs @@ -170,7 +170,7 @@ public async Task ReturnsNotFound_WhenApplicationIsNull() { var (autoMocker, clientSession, loggerFactory, httpContext) = CreateMocks(); - autoMocker.MockResponse(new GetApplicationByClientIdResponse { Application = null }); + autoMocker.MockResponse(new GetApplicationByClientIdResponse { Application = null! }); var result = await GetServiceUsersEndpoint.GetServiceUsers( clientSession,