diff --git a/.github/workflows/build-test-asyncprocessing.yml b/.github/workflows/build-test-asyncprocessing.yml index 808b41a..ab3f941 100644 --- a/.github/workflows/build-test-asyncprocessing.yml +++ b/.github/workflows/build-test-asyncprocessing.yml @@ -18,6 +18,5 @@ jobs: with: project_name: DfE.CoreLibs.AsyncProcessing project_path: src/DfE.CoreLibs.AsyncProcessing - run_tests: false secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/build-test-template.yml b/.github/workflows/build-test-template.yml index 9b5adfd..cd9cf30 100644 --- a/.github/workflows/build-test-template.yml +++ b/.github/workflows/build-test-template.yml @@ -23,7 +23,6 @@ env: DOTNET_VERSION: '8.0.x' EF_VERSION: '6.0.5' JAVA_VERSION: '17' - CONNECTION_STRING: 'Server=localhost,1433;Database=sip;TrustServerCertificate=True;User Id=sa;Password=StrongPassword905' jobs: build-and-test: diff --git a/DfE.CoreLibs.sln b/DfE.CoreLibs.sln index b3402ea..0fe505d 100644 --- a/DfE.CoreLibs.sln +++ b/DfE.CoreLibs.sln @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DfE.CoreLibs.Http.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DfE.CoreLibs.Caching.Tests", "src\Tests\DfE.CoreLibs.Caching.Tests\DfE.CoreLibs.Caching.Tests.csproj", "{807147EB-9B76-42F6-B249-A0F0CF3C3462}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DfE.CoreLibs.AsyncProcessing.Tests", "src\Tests\DfE.CoreLibs.AsyncProcessing.Tests\DfE.CoreLibs.AsyncProcessing.Tests.csproj", "{5ABF8802-0C35-42D3-B2BB-83BD7159124F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +67,10 @@ Global {807147EB-9B76-42F6-B249-A0F0CF3C3462}.Debug|Any CPU.Build.0 = Debug|Any CPU {807147EB-9B76-42F6-B249-A0F0CF3C3462}.Release|Any CPU.ActiveCfg = Release|Any CPU {807147EB-9B76-42F6-B249-A0F0CF3C3462}.Release|Any CPU.Build.0 = Release|Any CPU + {5ABF8802-0C35-42D3-B2BB-83BD7159124F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ABF8802-0C35-42D3-B2BB-83BD7159124F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ABF8802-0C35-42D3-B2BB-83BD7159124F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ABF8802-0C35-42D3-B2BB-83BD7159124F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -73,6 +79,7 @@ Global {07AE8F19-9566-4F0C-92E6-0A2BF122DCC9} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4} {69529D73-DD34-43A2-9D06-F3783F68F05C} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4} {807147EB-9B76-42F6-B249-A0F0CF3C3462} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4} + {5ABF8802-0C35-42D3-B2BB-83BD7159124F} = {3F89DCAD-8EC7-41ED-A08F-A9EFAE263EB4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D11FBC-6C66-43E4-8F1F-46B105EDD95C} diff --git a/src/DfE.CoreLibs.AsyncProcessing/DfE.CoreLibs.AsyncProcessing.csproj b/src/DfE.CoreLibs.AsyncProcessing/DfE.CoreLibs.AsyncProcessing.csproj index aaca9d6..7d101d8 100644 --- a/src/DfE.CoreLibs.AsyncProcessing/DfE.CoreLibs.AsyncProcessing.csproj +++ b/src/DfE.CoreLibs.AsyncProcessing/DfE.CoreLibs.AsyncProcessing.csproj @@ -17,9 +17,9 @@ - - - + + + diff --git a/src/DfE.CoreLibs.Contracts/Academies/V1/EducationalPerformance/SchoolAbsenceDataDto.cs b/src/DfE.CoreLibs.Contracts/Academies/V1/EducationalPerformance/SchoolAbsenceDataDto.cs index 354709f..92cbba3 100644 --- a/src/DfE.CoreLibs.Contracts/Academies/V1/EducationalPerformance/SchoolAbsenceDataDto.cs +++ b/src/DfE.CoreLibs.Contracts/Academies/V1/EducationalPerformance/SchoolAbsenceDataDto.cs @@ -1,8 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + namespace DfE.CoreLibs.Contracts.Academies.V1.EducationalPerformance { /// /// Absence Data Response /// + [ExcludeFromCodeCoverage] public class SchoolAbsenceDataDto { /// diff --git a/src/DfE.CoreLibs.Contracts/Academies/V4/AddressDto.cs b/src/DfE.CoreLibs.Contracts/Academies/V4/AddressDto.cs index d458871..4f282d9 100644 --- a/src/DfE.CoreLibs.Contracts/Academies/V4/AddressDto.cs +++ b/src/DfE.CoreLibs.Contracts/Academies/V4/AddressDto.cs @@ -1,6 +1,9 @@ -namespace DfE.CoreLibs.Contracts.Academies.V4; +using System.Diagnostics.CodeAnalysis; + +namespace DfE.CoreLibs.Contracts.Academies.V4; [Serializable] +[ExcludeFromCodeCoverage] public class AddressDto { public string Street { get; set; } diff --git a/src/DfE.CoreLibs.Contracts/Academies/V4/Establishments/EstablishmentDto.cs b/src/DfE.CoreLibs.Contracts/Academies/V4/Establishments/EstablishmentDto.cs index b1b2a5f..9325112 100644 --- a/src/DfE.CoreLibs.Contracts/Academies/V4/Establishments/EstablishmentDto.cs +++ b/src/DfE.CoreLibs.Contracts/Academies/V4/Establishments/EstablishmentDto.cs @@ -1,6 +1,9 @@ -namespace DfE.CoreLibs.Contracts.Academies.V4.Establishments; +using System.Diagnostics.CodeAnalysis; + +namespace DfE.CoreLibs.Contracts.Academies.V4.Establishments; [Serializable] +[ExcludeFromCodeCoverage] public class EstablishmentDto { diff --git a/src/DfE.CoreLibs.Contracts/Academies/V4/PagedDataResponse.cs b/src/DfE.CoreLibs.Contracts/Academies/V4/PagedDataResponse.cs index b92ce63..a1ac376 100644 --- a/src/DfE.CoreLibs.Contracts/Academies/V4/PagedDataResponse.cs +++ b/src/DfE.CoreLibs.Contracts/Academies/V4/PagedDataResponse.cs @@ -1,6 +1,9 @@ -namespace DfE.CoreLibs.Contracts.Academies.V4; +using System.Diagnostics.CodeAnalysis; + +namespace DfE.CoreLibs.Contracts.Academies.V4; [Serializable] +[ExcludeFromCodeCoverage] public class PagedDataResponse where TResponse : class { @@ -20,6 +23,7 @@ public PagedDataResponse(IEnumerable data, PagingResponse pagingRespo } [Serializable] +[ExcludeFromCodeCoverage] public class PagingResponse { public int Page { get; set; } diff --git a/src/DfE.CoreLibs.Contracts/Academies/V4/Trusts/TrustDto.cs b/src/DfE.CoreLibs.Contracts/Academies/V4/Trusts/TrustDto.cs index d5e34f3..22db583 100644 --- a/src/DfE.CoreLibs.Contracts/Academies/V4/Trusts/TrustDto.cs +++ b/src/DfE.CoreLibs.Contracts/Academies/V4/Trusts/TrustDto.cs @@ -1,8 +1,10 @@ using DfE.CoreLibs.Contracts.Academies.V4.Establishments; +using System.Diagnostics.CodeAnalysis; namespace DfE.CoreLibs.Contracts.Academies.V4.Trusts; [Serializable] +[ExcludeFromCodeCoverage] public class TrustDto { public string Name { get; set; } diff --git a/src/Tests/DfE.CoreLibs.AsyncProcessing.Tests/DfE.CoreLibs.AsyncProcessing.Tests.csproj b/src/Tests/DfE.CoreLibs.AsyncProcessing.Tests/DfE.CoreLibs.AsyncProcessing.Tests.csproj new file mode 100644 index 0000000..1e1cfcd --- /dev/null +++ b/src/Tests/DfE.CoreLibs.AsyncProcessing.Tests/DfE.CoreLibs.AsyncProcessing.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tests/DfE.CoreLibs.AsyncProcessing.Tests/Services/BackgroundServiceFactoryTests.cs b/src/Tests/DfE.CoreLibs.AsyncProcessing.Tests/Services/BackgroundServiceFactoryTests.cs new file mode 100644 index 0000000..5d86e91 --- /dev/null +++ b/src/Tests/DfE.CoreLibs.AsyncProcessing.Tests/Services/BackgroundServiceFactoryTests.cs @@ -0,0 +1,109 @@ +using DfE.CoreLibs.AsyncProcessing.Interfaces; +using DfE.CoreLibs.AsyncProcessing.Services; +using DfE.CoreLibs.Testing.AutoFixture.Attributes; +using MediatR; +using NSubstitute; + +namespace DfE.CoreLibs.AsyncProcessing.Tests.Services +{ + public class BackgroundServiceFactoryTests + { + private readonly IMediator _mediator; + private readonly BackgroundServiceFactory _backgroundServiceFactory; + + public BackgroundServiceFactoryTests() + { + _mediator = Substitute.For(); + _backgroundServiceFactory = new BackgroundServiceFactory(_mediator); + } + + [Theory] + [CustomAutoData] + public async Task EnqueueTask_ShouldProcessTaskInQueue( + Func> taskFunc, + IBackgroundServiceEvent eventMock) + { + // Arrange + Func eventFactory = _ => eventMock; + + var semaphore = new SemaphoreSlim(0, 1); + bool taskExecuted = false; + + Func> wrappedTaskFunc = async () => + { + taskExecuted = true; + semaphore.Release(); + return await taskFunc(); + }; + + // Act + _backgroundServiceFactory.EnqueueTask(wrappedTaskFunc, eventFactory); + + // Ensure the task gets processed + await semaphore.WaitAsync(1000); + + // Assert + Assert.True(taskExecuted, "Task in the queue should have been processed."); + } + + [Theory] + [CustomAutoData] + public async Task EnqueueTask_ShouldPublishEvent_WhenEventFactoryIsProvided( + int taskResult, + IBackgroundServiceEvent eventMock) + { + // Arrange + Func eventFactory = _ => eventMock; + + // Act + _backgroundServiceFactory.EnqueueTask(() => Task.FromResult(taskResult), eventFactory); + + // Trigger processing + await Task.Delay(100); + + // Assert + await _mediator.Received(1).Publish(eventMock, Arg.Any()); + } + + [Theory] + [CustomAutoData] + public async Task StartProcessingQueue_ShouldProcessTasksSequentially( + Func> taskFunc, + IBackgroundServiceEvent eventMock) + { + // Arrange + Func eventFactory = _ => eventMock; + + int taskCount = 0; + Func> wrappedTaskFunc = async () => + { + Interlocked.Increment(ref taskCount); + return await taskFunc(); + }; + + _backgroundServiceFactory.EnqueueTask(wrappedTaskFunc, eventFactory); + _backgroundServiceFactory.EnqueueTask(wrappedTaskFunc, eventFactory); + + // Act + await Task.Delay(100); // Allow time for processing + + // Assert + Assert.Equal(2, taskCount); + } + + [Fact] + public async Task ExecuteAsync_ShouldStopProcessing_WhenCancellationRequested() + { + // Arrange + using var cts = new CancellationTokenSource(); + var task = _backgroundServiceFactory.StartAsync(cts.Token); + + // Act + await cts.CancelAsync(); + await task; + + // Assert + Assert.True(task.IsCompletedSuccessfully); + } + } +}