Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
cd0428c
test: replace generic Workflow coverage tests
TehWardy Aug 12, 2026
0e82f6a
test: cover Workflow exception policies explicitly
TehWardy Aug 12, 2026
b5ac07b
test: cover Workflow event exception policies
TehWardy Aug 12, 2026
340ece6
test: cover scheduled task exception policy
TehWardy Aug 12, 2026
756c864
test: cover workflow migration exception policies
TehWardy Aug 12, 2026
50638c9
test: cover workflow request orchestration
TehWardy Aug 12, 2026
38ca7fc
test: fully cover workflow script processing
TehWardy Aug 12, 2026
2f8f3c6
test: fully cover workflow communication processing
TehWardy Aug 12, 2026
6e51120
test: fully cover workflow result processing
TehWardy Aug 12, 2026
00a1044
test: raise flow instance processing above 95 percent
TehWardy Aug 12, 2026
ca3d740
test: fully cover workflow engine exposures
TehWardy Aug 12, 2026
e9e41aa
test: fully cover scheduled task foundation
TehWardy Aug 12, 2026
123ff94
test: fully cover scheduled task processing
TehWardy Aug 12, 2026
96b4c40
test: fully cover calendar foundation
TehWardy Aug 12, 2026
4bf254a
test: cover calendar event foundation service
TehWardy Aug 12, 2026
f802a21
test: cover calendar event processing service
TehWardy Aug 12, 2026
60bf7a5
test: cover calendar event publication service
TehWardy Aug 12, 2026
7e7cda7
test: cover calendar event publication processing
TehWardy Aug 12, 2026
fa0c63c
test: cover calendar event orchestration service
TehWardy Aug 12, 2026
18de175
test: cover workflow package exports
TehWardy Aug 12, 2026
3ed38e6
test: cover workflow package imports
TehWardy Aug 12, 2026
c1faaa5
test: cover workflow event processing service
TehWardy Aug 12, 2026
648bdeb
test: cover workflow instance processing behavior
TehWardy Aug 12, 2026
8e53965
test: cover flow definition processing service
TehWardy Aug 12, 2026
5920be1
test: cover flow definition aggregation service
TehWardy Aug 12, 2026
97a1289
test: cover scheduled task runner orchestration
TehWardy Aug 12, 2026
f477a6d
test: cover scheduled task orchestration service
TehWardy Aug 12, 2026
3414470
test: cover calendar processing service
TehWardy Aug 12, 2026
000e4fa
test: cover calendar orchestration service
TehWardy Aug 12, 2026
d768cb7
test: cover scheduled task event publication
TehWardy Aug 12, 2026
cf687dc
test: cover calendar event publication
TehWardy Aug 12, 2026
4d94b67
test: cover workflow event orchestration
TehWardy Aug 12, 2026
d847fea
test: cover flow instance data processing
TehWardy Aug 12, 2026
6f103f4
test(workflow): cover calendar exposure failures
TehWardy Aug 12, 2026
991cb7d
test(workflow): cover calendar event exposure failures
TehWardy Aug 12, 2026
6959662
test(workflow): cover workflow event exposure failures
TehWardy Aug 12, 2026
250b088
test(workflow): cover flow instance data exposure failures
TehWardy Aug 12, 2026
91a34ed
test(workflow): cover scheduled task exposure failures
TehWardy Aug 12, 2026
4df3515
test(workflow): cover flow definition exposure failures
TehWardy Aug 12, 2026
c6951f2
test(workflow): exceed 95 percent service coverage
TehWardy Aug 12, 2026
ad3680f
ci(workflow): enforce coverage above 95 percent
TehWardy Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ jobs:
"Services and exposures line coverage: $coveredLines/$($lines.Count) ($percentage)" |
Write-Output

if ($lineRate -le 0.90) {
throw "Services and exposures line coverage must be greater than 90%; actual: $percentage."
if ($lineRate -le 0.95) {
throw "Services and exposures line coverage must be greater than 95%; actual: $percentage."
}

- name: Upload test results
Expand Down Expand Up @@ -207,4 +207,4 @@ jobs:

- name: Deploy coverage report
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// ---------------------------------------------------------------
// Copyright (c) Paul.Ward@ccoder.co.uk
// ---------------------------------------------------------------

using cCoder.Workflow.Activities.Models;
using Moq;
using Xunit;

namespace cCoder.Workflow.Engine.Tests;

public sealed partial class FlowCommunicationProcessingServiceTests
{
[Fact]
public async Task ShouldConnectWorkflowRequestAsync()
{
// Given
WorkflowRequest request = CreateWorkflowRequest();

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.ConnectAsync(
url: $"{request.Api}Hubs/Workflow"))
.Returns(value: Task.CompletedTask);

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.SendAsync(
level: "info",
message: It.IsAny<string>(),
instanceId: request.InstanceId.ToString()))
.Returns(value: Task.CompletedTask);

var service = CreateService();

// When
await service.ConnectWorkflowRequestAsync(workflowRequest: request);

// Then
workflowHubConnectionBrokerMock.VerifyAll();
loggingBrokerMock.VerifyNoOtherCalls();
}

[Fact]
public async Task ShouldLogWarningWhenWorkflowConnectionFailsAsync()
{
// Given
WorkflowRequest request = CreateWorkflowRequest();

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.ConnectAsync(
url: $"{request.Api}Hubs/Workflow"))
.Throws(exception: new InvalidOperationException());

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.DisconnectAsync())
.Returns(value: ValueTask.CompletedTask);

var service = CreateService();

// When
await service.ConnectWorkflowRequestAsync(workflowRequest: request);

// Then
workflowHubConnectionBrokerMock.VerifyAll();
loggingBrokerMock.VerifyNoOtherCalls();
}

[Fact]
public async Task ShouldDisconnectAndLogWhenWorkflowSendFailsAsync()
{
// Given
WorkflowRequest request = CreateWorkflowRequest();

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.ConnectAsync(
url: $"{request.Api}Hubs/Workflow"))
.Returns(value: Task.CompletedTask);

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.SendAsync(
level: "info",
message: It.IsAny<string>(),
instanceId: request.InstanceId.ToString()))
.Throws(exception: new InvalidOperationException("send failed"));

workflowHubConnectionBrokerMock
.Setup(expression: broker => broker.DisconnectAsync())
.Returns(value: ValueTask.CompletedTask);

var service = CreateService();

// When
await service.ConnectWorkflowRequestAsync(workflowRequest: request);

// Then
workflowHubConnectionBrokerMock.VerifyAll();

loggingBrokerMock.Verify(
expression: broker => broker.LogError(
message: "{Message}",
args: It.Is<object[]>(match: arguments =>
arguments.Single() as string == "send failed")),
times: Times.Once());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// ---------------------------------------------------------------
// Copyright (c) Paul.Ward@ccoder.co.uk
// ---------------------------------------------------------------

using cCoder.Workflow.Activities.Models;
using FluentAssertions;
using Moq;
using Xunit;

namespace cCoder.Workflow.Engine.Tests;

public sealed partial class FlowCommunicationProcessingServiceTests
{
[Theory]
[MemberData(
nameof(WorkflowRequestOrchestrationServiceTests.ExceptionMappings),
MemberType = typeof(WorkflowRequestOrchestrationServiceTests))]
public async Task ShouldMapLogWorkflowRequestAsyncFailure(
Exception exception,
Type expectedType)
{
// Given
const string message = "message";
WorkflowRequest request = CreateWorkflowRequest();

loggingBrokerMock
.Setup(expression: broker => broker.LogError(
message: "{Message}",
args: It.IsAny<object[]>()))
.Throws(exception: exception);

var service = CreateService();

// When
Func<Task> action = async () => await service
.LogWorkflowRequestAsync(
workflowRequest: request,
level: WorkflowLogLevel.Error,
message: message);

// Then
Exception thrown = (await action
.Should()
.ThrowAsync<Exception>()).Which;

thrown
.Should()
.BeOfType(expectedType: expectedType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// ---------------------------------------------------------------
// Copyright (c) Paul.Ward@ccoder.co.uk
// ---------------------------------------------------------------

using cCoder.Workflow.Activities.Models;
using cCoder.Workflow.Engine.Models.Exceptions;
using FluentAssertions;
using Moq;
using Xunit;

namespace cCoder.Workflow.Engine.Tests;

public sealed partial class FlowCommunicationProcessingServiceTests
{
[Fact]
public async Task ShouldLogWorkflowRequestAsync()
{
// Given
const string message = "message";
WorkflowRequest request = CreateWorkflowRequest();
var service = CreateService();

// When
await service.LogWorkflowRequestAsync(
workflowRequest: request,
level: WorkflowLogLevel.Info,
message: message);

await service.LogWorkflowRequestAsync(
workflowRequest: request,
level: WorkflowLogLevel.Error,
message: message);

await service.LogWorkflowRequestAsync(
workflowRequest: request,
level: WorkflowLogLevel.Fatal,
message: message);

// Then
loggingBrokerMock.Verify(
expression: broker => broker.LogError(
message: "{Message}",
args: It.Is<object[]>(match: arguments =>
arguments.Single() as string == message)),
times: Times.Exactly(callCount: 2));
}

[Fact]
public async Task ShouldTruncateLongWorkflowRequestLogAsync()
{
// Given
string message = new(c: 'a', count: 4001);
string loggedMessage = null;
WorkflowRequest request = CreateWorkflowRequest();

loggingBrokerMock
.Setup(expression: broker => broker.LogError(
message: "{Message}",
args: It.IsAny<object[]>()))
.Callback<string, object[]>(action: (_, arguments) =>
loggedMessage = arguments.Single() as string);

var service = CreateService();

// When
await service.LogWorkflowRequestAsync(
workflowRequest: request,
level: WorkflowLogLevel.Error,
message: message);

// Then
loggedMessage.Length
.Should()
.BeLessThan(expected: message.Length);

loggedMessage
.Should()
.Contain(expected: "characters cut");
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public async Task ShouldRejectInvalidWorkflowRequestAsync(string api)
{
// Given
WorkflowRequest request = CreateWorkflowRequest();
request.Api = api;
var service = CreateService();

// When
Func<Task> action = async () => await service
.LogWorkflowRequestAsync(
workflowRequest: request,
level: WorkflowLogLevel.Info,
message: "message");

// Then
await action
.Should()
.ThrowAsync<WorkflowEngineValidationException>();

loggingBrokerMock.VerifyNoOtherCalls();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// ---------------------------------------------------------------
// Copyright (c) Paul.Ward@ccoder.co.uk
// ---------------------------------------------------------------

using cCoder.Workflow.Activities.Models;
using cCoder.Workflow.Engine.Brokers;
using cCoder.Workflow.Engine.Brokers.Loggings;
using cCoder.Workflow.Engine.Services.Processings;
using Moq;

namespace cCoder.Workflow.Engine.Tests;

public sealed partial class FlowCommunicationProcessingServiceTests
{
private readonly Mock<ILoggingBroker> loggingBrokerMock = new();

private readonly Mock<IWorkflowHubConnectionBroker>
workflowHubConnectionBrokerMock =
new(behavior: MockBehavior.Strict);

private FlowCommunicationProcessingService CreateService() =>
new(
logger: loggingBrokerMock.Object,
workflowHubConnectionBroker:
workflowHubConnectionBrokerMock.Object);

private static WorkflowRequest CreateWorkflowRequest() =>
new(
api: "https://localhost/",
token: "token",
flowId: Guid.NewGuid(),
instanceId: Guid.NewGuid());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// ---------------------------------------------------------------
// Copyright (c) Paul.Ward@ccoder.co.uk
// ---------------------------------------------------------------

using cCoder.Workflow.Activities.Models;
using cCoder.Workflow.Engine.Exposures;
using cCoder.Workflow.Engine.Services.Orchestrations;
using Moq;
using Xunit;

namespace cCoder.Workflow.Engine.Tests;

public sealed partial class FlowExecutionOrchestrationAdapterTests
{
[Fact]
public async Task ShouldExecuteAsync()
{
// Given
WorkflowRequest request = new(
api: "https://localhost/",
token: "token",
flowId: Guid.NewGuid(),
instanceId: Guid.NewGuid());

Mock<IWorkflowRequestOrchestrationService> serviceMock =
new(behavior: MockBehavior.Strict);

serviceMock
.Setup(expression: service => service
.ExecuteWorkflowRequestAsync(workflowRequest: request))
.Returns(value: ValueTask.CompletedTask);

FlowExecutionOrchestrationAdapter adapter =
new(workflowRequestOrchestrationService: serviceMock.Object);

// When
await adapter.ExecuteAsync(request: request);

// Then
serviceMock.VerifyAll();
}
}
Loading
Loading