diff --git a/KSeF.Client.Core/Models/RateLimits/EffectiveApiRateLimitsExtensions.cs b/KSeF.Client.Core/Models/RateLimits/EffectiveApiRateLimitsExtensions.cs
new file mode 100644
index 0000000..14d5400
--- /dev/null
+++ b/KSeF.Client.Core/Models/RateLimits/EffectiveApiRateLimitsExtensions.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+
+namespace KSeF.Client.Core.Models.RateLimits;
+
+///
+/// Klasa rozszerzająca funkcjonalność poprzez dodanie metod pomocniczych do pobierania limitów dla określonych grup
+///
+public static class EffectiveApiRateLimitsExtensions
+{
+ ///
+ /// Zwraca wartości limitów dla określonej grupy limitów żądań API KSeF.
+ ///
+ /// Obiekt zawierający limity żądań API KSeF.
+ /// Grupa limitów żądań API KSeF.
+ /// Obiekt zawierający wartości limitów dla określonej grupy.
+ /// Wyrzucany, gdy parametr jest null.
+ /// Wyrzucany, gdy parametr nie jest obsługiwany.
+ public static EffectiveApiRateLimitValues GetRateLimitValues(
+ this EffectiveApiRateLimits limits,
+ KsefClientRateLimitGroup group)
+ {
+ if (limits is null)
+ {
+ throw new ArgumentNullException(nameof(limits));
+ }
+
+ return group switch
+ {
+ KsefClientRateLimitGroup.OnlineSession => limits.OnlineSession,
+ KsefClientRateLimitGroup.BatchSession => limits.BatchSession,
+ KsefClientRateLimitGroup.InvoiceSend => limits.InvoiceSend,
+ KsefClientRateLimitGroup.InvoiceStatus => limits.InvoiceStatus,
+ KsefClientRateLimitGroup.SessionList => limits.SessionList,
+ KsefClientRateLimitGroup.SessionInvoiceList => limits.SessionInvoiceList,
+ KsefClientRateLimitGroup.SessionMisc => limits.SessionMisc,
+ KsefClientRateLimitGroup.InvoiceMetadata => limits.InvoiceMetadata,
+ KsefClientRateLimitGroup.InvoiceExport => limits.InvoiceExport,
+ KsefClientRateLimitGroup.InvoiceExportStatus => limits.InvoiceExportStatus,
+ KsefClientRateLimitGroup.InvoiceDownload => limits.InvoiceDownload,
+ KsefClientRateLimitGroup.Other => limits.Other,
+ KsefClientRateLimitGroup.CollectiveIdentifier => limits.CollectiveIdentifier,
+ _ => throw new ArgumentOutOfRangeException(nameof(group), group, null)
+ };
+ }
+
+ ///
+ /// Zwraca słownik zawierający wszystkie grupy limitów żądań API KSeF wraz z odpowiadającymi im wartościami limitów.
+ ///
+ /// Obiekt zawierający limity żądań API KSeF.
+ /// Słownik zawierający wszystkie grupy limitów i odpowiadające im wartości.
+ /// Wyrzucany, gdy parametr jest null.
+ public static IReadOnlyDictionary GetAllRateLimitValues(
+ this EffectiveApiRateLimits limits)
+ {
+ if (limits is null)
+ {
+ throw new ArgumentNullException(nameof(limits));
+ }
+
+ Dictionary result = new();
+
+ foreach (KsefClientRateLimitGroup group in Enum.GetValues(typeof(KsefClientRateLimitGroup)))
+ {
+ result[group] = limits.GetRateLimitValues(group);
+ }
+
+ return result;
+ }
+}
diff --git a/KSeF.Client.Core/Models/RateLimits/KsefClientRateLimitGroup.cs b/KSeF.Client.Core/Models/RateLimits/KsefClientRateLimitGroup.cs
new file mode 100644
index 0000000..e90c029
--- /dev/null
+++ b/KSeF.Client.Core/Models/RateLimits/KsefClientRateLimitGroup.cs
@@ -0,0 +1,72 @@
+namespace KSeF.Client.Core.Models.RateLimits;
+
+///
+/// Enum określający grupy limitów żądań API KSeF.
+///
+public enum KsefClientRateLimitGroup
+{
+ ///
+ /// Limity otwierania/zamykania sesji interaktywnych.
+ ///
+ OnlineSession = 0,
+
+ ///
+ /// Limity otwierania/zamykania sesji wsadowych.
+ ///
+ BatchSession = 1,
+
+ ///
+ /// Limity wysyłki faktur.
+ ///
+ InvoiceSend = 2,
+
+ ///
+ /// Limity pobierania statusu faktury z sesji.
+ ///
+ InvoiceStatus = 3,
+
+ ///
+ /// Limity pobierania listy sesji.
+ ///
+ SessionList = 4,
+
+ ///
+ /// Limity pobierania listy faktur w sesji.
+ ///
+ SessionInvoiceList = 5,
+
+ ///
+ /// Limity pozostałych operacji w ramach sesji.
+ ///
+ SessionMisc = 6,
+
+ ///
+ /// Limity pobierania metadanych faktur.
+ ///
+ InvoiceMetadata = 7,
+
+ ///
+ /// Limity eksportu paczki faktur.
+ ///
+ InvoiceExport = 8,
+
+ ///
+ /// Limity pobierania statusu eksportu paczki faktur.
+ ///
+ InvoiceExportStatus = 9,
+
+ ///
+ /// Limity pobierania faktur po numerze KSeF.
+ ///
+ InvoiceDownload = 10,
+
+ ///
+ /// Limity pozostałych operacji API.
+ ///
+ Other = 11,
+
+ ///
+ /// Limity generowania identyfikatorów zbiorczych.
+ ///
+ CollectiveIdentifier = 12
+}
diff --git a/KSeF.Client.Tests.Core/E2E/Limits/RateLimitsE2ETests.cs b/KSeF.Client.Tests.Core/E2E/Limits/RateLimitsE2ETests.cs
index 7525771..c1ef2df 100644
--- a/KSeF.Client.Tests.Core/E2E/Limits/RateLimitsE2ETests.cs
+++ b/KSeF.Client.Tests.Core/E2E/Limits/RateLimitsE2ETests.cs
@@ -661,21 +661,14 @@ private static int Adjust(int current, int delta, int min, int max)
private static bool AreRateLimitsEqual(EffectiveApiRateLimits expected, EffectiveApiRateLimits actual)
{
- return AreRateLimitValuesEqual(expected.OnlineSession, actual.OnlineSession)
- && AreRateLimitValuesEqual(expected.BatchSession, actual.BatchSession)
- && AreRateLimitValuesEqual(expected.InvoiceSend, actual.InvoiceSend)
- && AreRateLimitValuesEqual(expected.InvoiceStatus, actual.InvoiceStatus)
- && AreRateLimitValuesEqual(expected.SessionList, actual.SessionList)
- && AreRateLimitValuesEqual(expected.SessionInvoiceList, actual.SessionInvoiceList)
- && AreRateLimitValuesEqual(expected.SessionMisc, actual.SessionMisc)
- && AreRateLimitValuesEqual(expected.InvoiceMetadata, actual.InvoiceMetadata)
- && AreRateLimitValuesEqual(expected.InvoiceExport, actual.InvoiceExport)
- && AreRateLimitValuesEqual(expected.InvoiceExportStatus, actual.InvoiceExportStatus)
- && AreRateLimitValuesEqual(expected.InvoiceDownload, actual.InvoiceDownload)
- && AreRateLimitValuesEqual(expected.CollectiveIdentifier, actual.CollectiveIdentifier)
- && AreRateLimitValuesEqual(expected.Other, actual.Other);
- }
+ IReadOnlyDictionary expectedGroups = expected.GetAllRateLimitValues();
+ IReadOnlyDictionary actualGroups = actual.GetAllRateLimitValues();
+ return expectedGroups.Count == actualGroups.Count
+ && expectedGroups.All(pair =>
+ actualGroups.TryGetValue(pair.Key, out EffectiveApiRateLimitValues? actualValues)
+ && AreRateLimitValuesEqual(pair.Value, actualValues));
+ }
private static bool AreRateLimitValuesEqual(EffectiveApiRateLimitValues? expected, EffectiveApiRateLimitValues? actual)
{
if (expected is null || actual is null)
@@ -711,55 +704,17 @@ private static void AssertRateLimitsEqual(EffectiveApiRateLimits expected, Effec
Assert.NotNull(expected);
Assert.NotNull(actual);
- // OnlineSession
- Assert.Equal(expected.OnlineSession.PerSecond, actual.OnlineSession.PerSecond);
- Assert.Equal(expected.OnlineSession.PerMinute, actual.OnlineSession.PerMinute);
- Assert.Equal(expected.OnlineSession.PerHour, actual.OnlineSession.PerHour);
- // BatchSession
- Assert.Equal(expected.BatchSession.PerSecond, actual.BatchSession.PerSecond);
- Assert.Equal(expected.BatchSession.PerMinute, actual.BatchSession.PerMinute);
- Assert.Equal(expected.BatchSession.PerHour, actual.BatchSession.PerHour);
- // InvoiceSend
- Assert.Equal(expected.InvoiceSend.PerSecond, actual.InvoiceSend.PerSecond);
- Assert.Equal(expected.InvoiceSend.PerMinute, actual.InvoiceSend.PerMinute);
- Assert.Equal(expected.InvoiceSend.PerHour, actual.InvoiceSend.PerHour);
- // InvoiceStatus
- Assert.Equal(expected.InvoiceStatus.PerSecond, actual.InvoiceStatus.PerSecond);
- Assert.Equal(expected.InvoiceStatus.PerMinute, actual.InvoiceStatus.PerMinute);
- Assert.Equal(expected.InvoiceStatus.PerHour, actual.InvoiceStatus.PerHour);
- // SessionList
- Assert.Equal(expected.SessionList.PerSecond, actual.SessionList.PerSecond);
- Assert.Equal(expected.SessionList.PerMinute, actual.SessionList.PerMinute);
- Assert.Equal(expected.SessionList.PerHour, actual.SessionList.PerHour);
- // SessionInvoiceList
- Assert.Equal(expected.SessionInvoiceList.PerSecond, actual.SessionInvoiceList.PerSecond);
- Assert.Equal(expected.SessionInvoiceList.PerMinute, actual.SessionInvoiceList.PerMinute);
- Assert.Equal(expected.SessionInvoiceList.PerHour, actual.SessionInvoiceList.PerHour);
- // SessionMisc
- Assert.Equal(expected.SessionMisc.PerSecond, actual.SessionMisc.PerSecond);
- Assert.Equal(expected.SessionMisc.PerMinute, actual.SessionMisc.PerMinute);
- Assert.Equal(expected.SessionMisc.PerHour, actual.SessionMisc.PerHour);
- // InvoiceMetadata
- Assert.Equal(expected.InvoiceMetadata.PerSecond, actual.InvoiceMetadata.PerSecond);
- Assert.Equal(expected.InvoiceMetadata.PerMinute, actual.InvoiceMetadata.PerMinute);
- Assert.Equal(expected.InvoiceMetadata.PerHour, actual.InvoiceMetadata.PerHour);
- // InvoiceExport
- Assert.Equal(expected.InvoiceExport.PerSecond, actual.InvoiceExport.PerSecond);
- Assert.Equal(expected.InvoiceExport.PerMinute, actual.InvoiceExport.PerMinute);
- Assert.Equal(expected.InvoiceExport.PerHour, actual.InvoiceExport.PerHour);
- // InvoiceExportStatus
- AssertRateLimitValuesEqual(expected.InvoiceExportStatus, actual.InvoiceExportStatus);
- // InvoiceDownload
- Assert.Equal(expected.InvoiceDownload.PerSecond, actual.InvoiceDownload.PerSecond);
- Assert.Equal(expected.InvoiceDownload.PerMinute, actual.InvoiceDownload.PerMinute);
- Assert.Equal(expected.InvoiceDownload.PerHour, actual.InvoiceDownload.PerHour);
- // CollectiveIdentifier
- Assert.Equal(expected.CollectiveIdentifier.PerSecond, actual.CollectiveIdentifier.PerSecond);
- Assert.Equal(expected.CollectiveIdentifier.PerMinute, actual.CollectiveIdentifier.PerMinute);
- Assert.Equal(expected.CollectiveIdentifier.PerHour, actual.CollectiveIdentifier.PerHour);
- // Other
- Assert.Equal(expected.Other.PerSecond, actual.Other.PerSecond);
- Assert.Equal(expected.Other.PerMinute, actual.Other.PerMinute);
- Assert.Equal(expected.Other.PerHour, actual.Other.PerHour);
+ IReadOnlyDictionary expectedGroups = expected.GetAllRateLimitValues();
+ IReadOnlyDictionary actualGroups = actual.GetAllRateLimitValues();
+
+ Assert.Equal(expectedGroups.Count, actualGroups.Count);
+
+ foreach (KeyValuePair pair in expectedGroups)
+ {
+ Assert.True(
+ actualGroups.TryGetValue(pair.Key, out EffectiveApiRateLimitValues? actualValues),
+ $"Brak grupy limitów '{pair.Key}' w aktualnych wartościach.");
+ AssertRateLimitValuesEqual(pair.Value, actualValues);
+ }
}
}
diff --git a/KSeF.Client.Tests/Models/RateLimits/EffectiveApiRateLimitsExtensionsTests.cs b/KSeF.Client.Tests/Models/RateLimits/EffectiveApiRateLimitsExtensionsTests.cs
new file mode 100644
index 0000000..4bbd623
--- /dev/null
+++ b/KSeF.Client.Tests/Models/RateLimits/EffectiveApiRateLimitsExtensionsTests.cs
@@ -0,0 +1,160 @@
+using System.Reflection;
+
+using KSeF.Client.Core.Models.RateLimits;
+
+namespace KSeF.Client.Tests.Models.RateLimits;
+
+///
+/// Testy jednostkowe dla rozszerzeń klasy oraz kontraktu grup limitów.
+///
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2263", Justification = "Projekt wspiera net48, gdzie generyczne przeciążenie nie jest dostępne.")]
+public class EffectiveApiRateLimitsExtensionsTests
+{
+ ///
+ /// Sprawdza, czy zwraca każdą grupę limitów
+ /// oraz właściwy obiekt wartości. Lista grup jest pobierana z enuma, więc nowa poprawnie obsłużona
+ /// grupa jest automatycznie objęta tym testem.
+ ///
+ [Fact]
+ public void GetAll_ShouldContainEveryRateLimitGroup()
+ {
+ EffectiveApiRateLimits limits = CreateLimits();
+ IReadOnlyDictionary result = limits.GetAllRateLimitValues();
+ KsefClientRateLimitGroup[] expectedGroups = GetRateLimitGroups().ToArray();
+
+ Assert.Equal(expectedGroups.Length, result.Count);
+ Assert.Equal(expectedGroups.OrderBy(group => group), result.Keys.OrderBy(group => group));
+
+ foreach (KsefClientRateLimitGroup group in expectedGroups)
+ {
+ EffectiveApiRateLimitValues expected = GetPropertyValue(limits, group);
+ Assert.Same(expected, result[group]);
+ }
+ }
+
+ ///
+ /// Sprawdza, czy każda właściwość limitu ma odpowiadającą wartość w enumie grup.
+ ///
+ [Fact]
+ public void RateLimitProperties_ShouldHaveMatchingRateLimitGroups()
+ {
+ string[] propertyNames = GetRateLimitProperties()
+ .Select(property => property.Name)
+ .OrderBy(name => name)
+ .ToArray();
+
+ string[] groupNames = Enum.GetNames(typeof(KsefClientRateLimitGroup))
+ .OrderBy(name => name)
+ .ToArray();
+
+ Assert.Equal(propertyNames, groupNames);
+ }
+
+ ///
+ /// Sprawdza mapowanie każdej grupy do właściwej właściwości modelu i wszystkich jej wartości.
+ /// Dane testowe są generowane na podstawie właściwości modelu, dlatego nowa grupa nie wymaga
+ /// dopisywania osobnego przypadku testowego.
+ ///
+ [Fact]
+ public void Get_ShouldReturnValuesForEveryRateLimitGroup()
+ {
+ EffectiveApiRateLimits limits = CreateLimits();
+
+ foreach (KsefClientRateLimitGroup group in GetRateLimitGroups())
+ {
+ EffectiveApiRateLimitValues expected = GetPropertyValue(limits, group);
+ EffectiveApiRateLimitValues actual = limits.GetRateLimitValues(group);
+
+ Assert.Same(expected, actual);
+ Assert.Equal(expected.PerSecond, actual.PerSecond);
+ Assert.Equal(expected.PerMinute, actual.PerMinute);
+ Assert.Equal(expected.PerHour, actual.PerHour);
+ }
+ }
+
+ [Fact]
+ public void Get_ShouldThrowWhenLimitsAreNull()
+ {
+ Assert.Throws(() =>
+ EffectiveApiRateLimitsExtensions.GetRateLimitValues(null!, KsefClientRateLimitGroup.Other));
+ }
+
+ [Fact]
+ public void Get_ShouldThrowWhenGroupIsUnsupported()
+ {
+ Assert.Throws(() =>
+ CreateLimits().GetRateLimitValues((KsefClientRateLimitGroup)999));
+ }
+
+ [Fact]
+ public void GetAll_ShouldThrowWhenLimitsAreNull()
+ {
+ Assert.Throws(() => EffectiveApiRateLimitsExtensions.GetAllRateLimitValues(null!));
+ }
+
+ ///
+ /// Zwraca wszystkie grupy zdefiniowane w .
+ /// Użycie enuma jako źródła danych sprawia, że nowe grupy są automatycznie testowane.
+ ///
+ private static IEnumerable GetRateLimitGroups()
+ {
+ return Enum.GetValues(typeof(KsefClientRateLimitGroup))
+ .Cast();
+ }
+
+ ///
+ /// Zwraca właściwości modelu reprezentujące wartości limitów.
+ /// Test kontraktowy zakłada, że każda z nich ma właściwość o takiej samej nazwie w enumie grup.
+ ///
+ private static IEnumerable GetRateLimitProperties()
+ {
+ return typeof(EffectiveApiRateLimits)
+ .GetProperties()
+ .Where(property => property.PropertyType == typeof(EffectiveApiRateLimitValues));
+ }
+
+ ///
+ /// Tworzy model z unikalnymi wartościami dla każdej właściwości limitu.
+ /// Dzięki temu błędne mapowanie grup jest wykrywane bez ręcznego dopisywania danych testowych.
+ ///
+ private static EffectiveApiRateLimits CreateLimits()
+ {
+ EffectiveApiRateLimits limits = new();
+ int seed = 1;
+
+ foreach (PropertyInfo property in GetRateLimitProperties())
+ {
+ property.SetValue(limits, CreateValues(seed));
+ seed++;
+ }
+
+ return limits;
+ }
+
+ ///
+ /// Pobiera oczekiwaną właściwość modelu na podstawie nazwy grupy.
+ /// Brak właściwości oznacza niespójność kontraktu i powoduje jednoznaczne niepowodzenie testu.
+ ///
+ private static EffectiveApiRateLimitValues GetPropertyValue(
+ EffectiveApiRateLimits limits,
+ KsefClientRateLimitGroup group)
+ {
+ PropertyInfo property = typeof(EffectiveApiRateLimits).GetProperty(group.ToString())
+ ?? throw new InvalidOperationException($"Brak właściwości dla grupy limitów '{group}'.");
+
+ return Assert.IsType(property.GetValue(limits));
+ }
+
+ ///
+ /// Tworzy wartości limitu z różnymi wartościami dla sekundy, minuty i godziny.
+ ///
+ private static EffectiveApiRateLimitValues CreateValues(int seed)
+ {
+ return new EffectiveApiRateLimitValues
+ {
+ PerSecond = seed,
+ PerMinute = seed + 100,
+ PerHour = seed + 1000
+ };
+ }
+}