Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add code coverage (🏗️🚧👷‍♂️) #386

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions .github/workflows/code-coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: "Code Coverage"

on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read

strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Coverlet coverage test
run: dotnet test -c Debug -p:coverletOutput=coverage.xml -p:CollectCoverage=true -p:CoverletOutputFormat=opencover -p:Threshold=80 -p:ThresholdStat=Total -p:ExcludeByAttribute=\"Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute\" -p:Exclude=\"[Arcus.Messaging.Tests.*]*\" src/Arcus.Messaging.Tests.Unit/Arcus.Messaging.Tests.Unit.csproj

- name: Codecov
uses: codecov/[email protected]
if: always()
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Arcus - Messaging
[![Build Status](https://dev.azure.com/codit/Arcus/_apis/build/status/Commit%20builds/CI%20-%20Arcus.Messaging?branchName=main)](https://dev.azure.com/codit/Arcus/_build/latest?definitionId=785&branchName=main)
[![NuGet Badge](https://buildstats.info/nuget/Arcus.Messaging.Health?includePreReleases=true)](https://www.nuget.org/packages/Arcus.Messaging.Health/)
[![codecov](https://codecov.io/gh/arcus-azure/arcus.messaging/branch/main/graph/badge.svg?token=7SNFJTC3LT)](https://codecov.io/gh/arcus-azure/arcus.messaging)

Messaging development in a breeze.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ public async Task<MessageResult> TryCustomDeserializeMessageAsync(string message
/// <exception cref="ArgumentNullException">
/// Thrown when the <paramref name="message"/>, <paramref name="messageContext"/>, or <paramref name="correlationInfo"/> is <c>null</c>.
/// </exception>
/// <exception cref="TypeNotFoundException">Thrown when no processing method was found on the message handler.</exception>
/// <exception cref="InvalidOperationException">Thrown when the message handler cannot process the message correctly.</exception>
/// <exception cref="AmbiguousMatchException">Thrown when more than a single processing method was found on the message handler.</exception>
public async Task<bool> ProcessMessageAsync<TMessageContext>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Microsoft.Azure.ServiceBus
/// <summary>
/// Extensions on the <see cref="ServiceBusReceivedMessage"/> to more easily retrieve user and system-related information in a more consumer-friendly manner.
/// </summary>
[Obsolete("Use new Azure SDK's " + nameof(ServiceBusReceivedMessage) + " extensions in the 'Azure.Messaging.ServiceBus' namespace instead")]
public static class MessageExtensions
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

<ItemGroup>
<PackageReference Include="Arcus.Testing.Security.Providers.InMemory" Version="0.5.0" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="Serilog" Version="2.10.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Linq;
using System;
using System.Linq;
using Arcus.Messaging.Abstractions;
using Arcus.Messaging.Abstractions.EventHubs;
using Arcus.Observability.Telemetry.Core;
using Azure.Identity;
using Azure.Messaging.EventHubs;
using Azure.Storage.Blobs;
Expand All @@ -13,6 +16,123 @@ public class EventDataExtensionsTests
{
private static readonly Faker BogusGenerator = new Faker();

[Fact]
public void GetCorrelationInfo_WithoutProperties_Succeeds()
{
// Arrange
var data = new EventData();

// Act
MessageCorrelationInfo correlationInfo = data.GetCorrelationInfo();

// Assert
Assert.NotNull(correlationInfo);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.OperationId));
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.TransactionId));
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.CycleId));
Assert.Null(correlationInfo.OperationParentId);
}

[Fact]
public void GetCorrelationInfo_WithDefaultTransactionIdProperty_Succeeds()
{
// Arrange
var data = new EventData();
var transactionId = Guid.NewGuid().ToString();
data.Properties[PropertyNames.TransactionId] = transactionId;

// Act
MessageCorrelationInfo correlationInfo = data.GetCorrelationInfo();

// Assert
Assert.NotNull(correlationInfo);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.OperationId));
Assert.Equal(transactionId, correlationInfo.TransactionId);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.CycleId));
Assert.Null(correlationInfo.OperationParentId);
}

[Fact]
public void GetCorrelationInfo_WithDefaultOperationParentIdProperty_Succeeds()
{
// Arrange
var data = new EventData();
var operationParentId = Guid.NewGuid().ToString();
data.Properties[PropertyNames.OperationParentId] = operationParentId;

// Act
MessageCorrelationInfo correlationInfo = data.GetCorrelationInfo();

// Assert
Assert.NotNull(correlationInfo);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.OperationId));
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.TransactionId));
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.CycleId));
Assert.Equal(operationParentId, correlationInfo.OperationParentId);
}

[Fact]
public void GetCorrelationInfo_WithCorrelationIdProperty_Succeeds()
{
// Arrange
var data = new EventData();
var operationId = Guid.NewGuid().ToString();
data.CorrelationId = operationId;

// Act
MessageCorrelationInfo correlationInfo = data.GetCorrelationInfo();

// Assert
Assert.NotNull(correlationInfo);
Assert.Equal(operationId, correlationInfo.OperationId);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.TransactionId));
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.CycleId));
Assert.Null(correlationInfo.OperationParentId);
}

[Fact]
public void GetCorrelationInfo_WithCustomTransactionIdProperty_Succeeds()
{
// Arrange
var data = new EventData();
var propertyName = $"MyTransactionId-{Guid.NewGuid()}";
var transactionId = Guid.NewGuid().ToString();
data.Properties[propertyName] = transactionId;

// Act
MessageCorrelationInfo correlationInfo = data.GetCorrelationInfo(propertyName);

// Assert
Assert.NotNull(correlationInfo);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.OperationId));
Assert.Equal(transactionId, correlationInfo.TransactionId);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.CycleId));
Assert.Null(correlationInfo.OperationParentId);
}

[Fact]
public void GetCorrelationInfo_WithCustomOperationParentIdProperty_Succeeds()
{
// Arrange
var data = new EventData();
var transactionIdPropertyName = $"MyTransactionId-{Guid.NewGuid()}";
var transactionId = Guid.NewGuid().ToString();
data.Properties[transactionIdPropertyName] = transactionId;
var operationParentIdPropertyName = $"MyOperationParentId-{Guid.NewGuid()}";
var operationParentId = Guid.NewGuid().ToString();
data.Properties[operationParentIdPropertyName] = operationParentId;

// Act
MessageCorrelationInfo correlationInfo = data.GetCorrelationInfo(transactionIdPropertyName, operationParentIdPropertyName);

// Assert
Assert.NotNull(correlationInfo);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.OperationId));
Assert.Equal(transactionId, correlationInfo.TransactionId);
Assert.False(string.IsNullOrWhiteSpace(correlationInfo.CycleId));
Assert.Equal(operationParentId, correlationInfo.OperationParentId);
}

[Fact]
public void GetMessageContextManually_WithValidArguments_Succeeds()
{
Expand Down
35 changes: 4 additions & 31 deletions src/Arcus.Messaging.Tests.Unit/Extensions/ObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,11 @@ public static ServiceBusReceivedMessage AsServiceBusReceivedMessage(
IDictionary<string, object> applicationProperties = null)
{
Guard.NotNull(messageBody, nameof(messageBody), "Requires a message body to wrap in an received Azure Service Bus message");

string serializedMessageBody = JsonConvert.SerializeObject(messageBody);
byte[] rawMessage = Encoding.UTF8.GetBytes(serializedMessageBody);
var amqp = new AmqpAnnotatedMessage(new AmqpMessageBody(new[] {new ReadOnlyMemory<byte>(rawMessage)}));
amqp.Header.DeliveryCount = BogusGenerator.Random.UInt();

if (operationId is null)
{
amqp.Properties.CorrelationId = new AmqpMessageId();
}
else
{
amqp.Properties.CorrelationId = new AmqpMessageId(operationId);
}

if (applicationProperties != null)
{
foreach (KeyValuePair<string, object> applicationProperty in applicationProperties)
{
amqp.ApplicationProperties[applicationProperty.Key] = applicationProperty.Value;
}
}

var serviceBusMessage = (ServiceBusReceivedMessage) Activator.CreateInstance(
type: typeof(ServiceBusReceivedMessage),
bindingAttr: BindingFlags.NonPublic | BindingFlags.Instance,
binder: null,
args: new object[] {amqp},
culture: null,
activationAttributes: null);

return serviceBusMessage;
return ServiceBusModelFactory.ServiceBusReceivedMessage(
body: BinaryData.FromObjectAsJson(messageBody),
correlationId: operationId,
properties: applicationProperties);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Arcus.Messaging.Abstractions;
using Arcus.Messaging.Abstractions.ServiceBus;
using Arcus.Messaging.Abstractions.ServiceBus.MessageHandling;
using Microsoft.Extensions.Logging;

namespace Arcus.Messaging.Tests.Unit.Fixture
{
public class OrderV1AzureServiceBusMessageHandlerFromTemplate : AzureServiceBusMessageHandler<Core.Messages.v1.Order>
{
public OrderV1AzureServiceBusMessageHandlerFromTemplate(
ILogger<OrderV1AzureServiceBusMessageHandlerFromTemplate> logger)
: base(logger)
{
}

public bool IsConfigured { get; private set; }


public override async Task ProcessMessageAsync(
Core.Messages.v1.Order message,
AzureServiceBusMessageContext messageContext,
MessageCorrelationInfo correlationInfo,
CancellationToken cancellationToken)
{
await CompleteMessageAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Threading;
using System.Threading.Tasks;
using Arcus.Messaging.Abstractions;
using Arcus.Messaging.Abstractions.ServiceBus;
using Arcus.Messaging.Abstractions.ServiceBus.MessageHandling;
using Azure.Messaging.ServiceBus;
using Microsoft.Extensions.Logging;

namespace Arcus.Messaging.Tests.Unit.Fixture
{
public class TestAzureServiceBusFallbackMessageHandlerFromTemplate : AzureServiceBusFallbackMessageHandler
{
public TestAzureServiceBusFallbackMessageHandlerFromTemplate(
ILogger<TestAzureServiceBusFallbackMessageHandlerFromTemplate> logger)
: base(logger)
{
}

public override async Task ProcessMessageAsync(
ServiceBusReceivedMessage message,
AzureServiceBusMessageContext messageContext,
MessageCorrelationInfo correlationInfo,
CancellationToken cancellationToken)
{
await CompleteMessageAsync(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serilog.Core;
using Serilog.Events;

namespace Arcus.Messaging.Tests.Unit.Fixture
{
public class TestLogEventPropertyFactory : ILogEventPropertyFactory
{
/// <summary>
/// Construct a <see cref="T:Serilog.Events.LogEventProperty" /> with the specified name and value.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="value">The value of the property.</param>
/// <param name="destructureObjects">If true, and the value is a non-primitive, non-array type,
/// then the value will be converted to a structure; otherwise, unknown types will
/// be converted to scalars, which are generally stored as strings.</param>
/// <returns></returns>
public LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false)
{
return new LogEventProperty(name, new ScalarValue(value));
}
}
}
56 changes: 56 additions & 0 deletions src/Arcus.Messaging.Tests.Unit/IDictionaryExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Arcus.Messaging.Tests.Core.Correlation;
using Bogus;
using Xunit;

namespace Arcus.Messaging.Tests.Unit
{
// ReSharper disable once InconsistentNaming
public class IDictionaryExtensionsTests
{
private static readonly Faker BogusGenerator = new Faker();

[Fact]
public void GetTraceParent_WithBlankDiagnosticId_GeneratesNewCorrelationIds()
{
// Arrange
IReadOnlyDictionary<string, object> properties =
new ReadOnlyDictionary<string, object>(new Dictionary<string, object>
{
["Diagnostic-Id"] = ""
});

// Act
(string transactionId, string operationParentId) = properties.GetTraceParent();

// Assert
Assert.Equal(32, transactionId.Length);
Assert.Equal(16, operationParentId.Length);
}

[Fact]
public void GetTraceParent_WithExtraPadding_GetsTrimmed()
{
// Arrange
var traceParent = TraceParent.Generate();
var noise = BogusGenerator.Random.String();
IReadOnlyDictionary<string, object> properties =
new ReadOnlyDictionary<string, object>(new Dictionary<string, object>
{
["Diagnostic-Id"] = $"{traceParent.DiagnosticId}{noise}"
});

// Act
(string transactionId, string operationParentId) = properties.GetTraceParent();

// Assert
Assert.Equal(32, transactionId.Length);
Assert.Equal(16, operationParentId.Length);
}
}
}
Loading