diff --git a/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt b/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt
index e4ea653..f8e31b4 100644
--- a/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt
+++ b/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt
@@ -27,6 +27,7 @@ namespace Particular.LicensingComponent.Report
public QueueThroughput() { }
public Particular.LicensingComponent.Report.DailyThroughput[] DailyThroughputFromAudit { get; init; }
public Particular.LicensingComponent.Report.DailyThroughput[] DailyThroughputFromBroker { get; init; }
+ public Particular.LicensingComponent.Report.DailyThroughput[] DailyThroughputFromEndpoint { get; init; }
public Particular.LicensingComponent.Report.DailyThroughput[] DailyThroughputFromMonitoring { get; init; }
public string[] EndpointIndicators { get; init; }
public string NameHash { get; set; }
diff --git a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs
index 2119178..f9e37c7 100644
--- a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs
+++ b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs
@@ -135,6 +135,29 @@ public void Should_be_able_to_read_a_V2_report()
Assert.That(Validate(validationResult));
}
+ [Test]
+ public void Should_be_able_to_read_a_V3_report()
+ {
+ //Arrange
+ using var stream = GetResourceStream("throughput-report-v3.0.json");
+
+ //Act
+ var report = ValidatingReportReader.ReadAndValidate(stream!, out var validationResult);
+ var data = report!.ReportData;
+
+ //Assert
+ // Want to be explicit with asserts to ensure that a 3.0 report can be read correctly
+ // An approval test would be too easy to just accept changes on
+ Assert.That(data.Queues, Has.Length.EqualTo(5));
+ Assert.That(data.Queues.All(q => !string.IsNullOrEmpty(q.QueueName)));
+ Assert.That(data.Queues.Any(q => q.QueueName == "EndpointWithSelfReportedUsage" && q.DailyThroughputFromEndpoint.Any(t => t.MessageCount == 65 && t.DateUTC.ToString("yyyy-MM-dd") == "2026-09-21")));
+ Assert.That(data.Queues.Any(q => q.QueueName == "EndpointWithJustBrokerData" && q.DailyThroughputFromEndpoint.Length == 0));
+
+ Assert.That(report.Signature, Is.EqualTo("wveYaUgrMafyParJVSL8HppdywT5yeon3ZTEWIaagNGhLYaWDkkX3c7v+nvlkGIAQhLidjptsODcoTEipd+E/rQM6PrRnxw45sJlIrakANu8dbfuEDcIRq7WvUBynfeN/qGYhEsJZaQI93R8Twn7kgfaXPR86jo1IeJOB7ITYbfoVPmevAqzK17yZzfkHxPGoRJL3lf824S4Ak5vpoyckrRXs2wV4XqfiifWzcjBniwoHrYw6bY5Be0hd/qGxXF+AGmo6wdAVj6WpaVZx3rkLmMyVEGZoTtLtL8aJX0sabx3olWU1EdT+ouc00R0latZFNrMf8ZHgiT63urhkedAsQ=="));
+
+ Assert.That(Validate(validationResult));
+ }
+
#if !DEBUG
[Ignore("This test is here to help with generating a signed report file from a file that only contains report data")]
#endif
diff --git a/src/Particular.LicensingComponent.Report.UnitTests/Particular.LicensingComponent.Report.UnitTests.csproj b/src/Particular.LicensingComponent.Report.UnitTests/Particular.LicensingComponent.Report.UnitTests.csproj
index 785f73e..170ae34 100644
--- a/src/Particular.LicensingComponent.Report.UnitTests/Particular.LicensingComponent.Report.UnitTests.csproj
+++ b/src/Particular.LicensingComponent.Report.UnitTests/Particular.LicensingComponent.Report.UnitTests.csproj
@@ -24,6 +24,8 @@
+
+
diff --git a/src/Particular.LicensingComponent.Report.UnitTests/throughput-report-v3.0.json b/src/Particular.LicensingComponent.Report.UnitTests/throughput-report-v3.0.json
new file mode 100644
index 0000000..c12584e
--- /dev/null
+++ b/src/Particular.LicensingComponent.Report.UnitTests/throughput-report-v3.0.json
@@ -0,0 +1,174 @@
+{
+ "ReportData": {
+ "CustomerName": "TestCustomer",
+ "MessageTransport": "AzureServiceBus",
+ "ReportMethod": "Broker",
+ "ToolType": "Platform Licensing Component",
+ "ToolVersion": "6.20",
+ "ScopeType": "testingScope",
+ "StartTime": "2026-09-20T00:00:00+00:00",
+ "EndTime": "2026-09-21T00:00:00+00:00",
+ "ReportDuration": "1.00:00:00",
+ "Queues": [
+ {
+ "QueueName": "REDACTED1",
+ "Throughput": 65,
+ "EndpointIndicators": [
+ "KnownEndpoint"
+ ],
+ "Scope": "",
+ "DailyThroughputFromBroker": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 50
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 55
+ }
+ ],
+ "DailyThroughputFromAudit": [],
+ "DailyThroughputFromMonitoring": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 60
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 65
+ }
+ ],
+ "DailyThroughputFromEndpoint": []
+ },
+ {
+ "QueueName": "EndpointWithSelfReportedUsage",
+ "Throughput": 65,
+ "EndpointIndicators": [
+ "KnownEndpoint"
+ ],
+ "Scope": "",
+ "DailyThroughputFromBroker": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 120
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 135
+ }
+ ],
+ "DailyThroughputFromAudit": [],
+ "DailyThroughputFromMonitoring": [],
+ "DailyThroughputFromEndpoint": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 60
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 65
+ }
+ ]
+ },
+ {
+ "QueueName": "EndpointWithAllSources",
+ "Throughput": 65,
+ "EndpointIndicators": [
+ "KnownEndpoint"
+ ],
+ "Scope": "",
+ "DailyThroughputFromBroker": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 120
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 135
+ }
+ ],
+ "DailyThroughputFromAudit": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 120
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 135
+ }
+ ],
+ "DailyThroughputFromMonitoring": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 95
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 105
+ }
+ ],
+ "DailyThroughputFromEndpoint": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 60
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 65
+ }
+ ]
+ },
+ {
+ "QueueName": "EndpointWithJustBrokerData",
+ "Throughput": 47,
+ "EndpointIndicators": [],
+ "UserIndicator": "PlannedToDecommission",
+ "Scope": "",
+ "DailyThroughputFromBroker": [
+ {
+ "DateUTC": "2026-09-20",
+ "MessageCount": 42
+ },
+ {
+ "DateUTC": "2026-09-21",
+ "MessageCount": 47
+ }
+ ],
+ "DailyThroughputFromAudit": [],
+ "DailyThroughputFromMonitoring": [],
+ "DailyThroughputFromEndpoint": []
+ },
+ {
+ "QueueName": "EndpointWithNoData",
+ "Throughput": 15,
+ "EndpointIndicators": [],
+ "UserIndicator": "NotNServiceBusEndpoint",
+ "Scope": "",
+ "DailyThroughputFromBroker": [],
+ "DailyThroughputFromAudit": [],
+ "DailyThroughputFromMonitoring": [],
+ "DailyThroughputFromEndpoint": []
+ }
+ ],
+ "TotalQueues": 5,
+ "IgnoredQueues": [],
+ "EnvironmentInformation": {
+ "AuditServicesData": {
+ "Versions": {
+ "4.3.6": 2
+ },
+ "Transports": {
+ "AzureServiceBus": 2
+ }
+ },
+ "EnvironmentData": {
+ "BrokerVersion": "1.2",
+ "ServiceControlVersion": "6.20.0",
+ "ServicePulseVersion": "2.8.0",
+ "AuditEnabled": "True",
+ "MonitoringEnabled": "True"
+ }
+ }
+ },
+ "Signature": "wveYaUgrMafyParJVSL8HppdywT5yeon3ZTEWIaagNGhLYaWDkkX3c7v+nvlkGIAQhLidjptsODcoTEipd+E/rQM6PrRnxw45sJlIrakANu8dbfuEDcIRq7WvUBynfeN/qGYhEsJZaQI93R8Twn7kgfaXPR86jo1IeJOB7ITYbfoVPmevAqzK17yZzfkHxPGoRJL3lf824S4Ak5vpoyckrRXs2wV4XqfiifWzcjBniwoHrYw6bY5Be0hd/qGxXF+AGmo6wdAVj6WpaVZx3rkLmMyVEGZoTtLtL8aJX0sabx3olWU1EdT+ouc00R0latZFNrMf8ZHgiT63urhkedAsQ=="
+}
\ No newline at end of file
diff --git a/src/Particular.LicensingComponent.Report/Report.cs b/src/Particular.LicensingComponent.Report/Report.cs
index c511174..f806bfd 100644
--- a/src/Particular.LicensingComponent.Report/Report.cs
+++ b/src/Particular.LicensingComponent.Report/Report.cs
@@ -165,6 +165,11 @@ public record QueueThroughput
/// Breakdown of daily throughput for each queue obtained from monitoring
///
public DailyThroughput[] DailyThroughputFromMonitoring { get; init; }
+
+ ///
+ /// Breakdown of daily throughput as recorded by the endpoint
+ ///
+ public DailyThroughput[] DailyThroughputFromEndpoint { get; init; }
}
///