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);
+ }
+ }
+}