From 760fbb27737d9e3f6f6ff5b6b7d4921196bbf3c2 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Mon, 6 Mar 2023 22:07:41 +0200 Subject: [PATCH 1/7] Added some basic types --- src/HttpClient.Cache/CacheItemPriority.cs | 9 +++++++++ src/HttpClient.Cache/Class1.cs | 5 ----- src/HttpClient.Cache/ICacheEntry.cs | 18 ++++++++++++++++++ src/HttpClient.Cache/IChangeToken.cs | 10 ++++++++++ .../PostEvictionCallbackRegistration.cs | 10 ++++++++++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/HttpClient.Cache/CacheItemPriority.cs delete mode 100644 src/HttpClient.Cache/Class1.cs create mode 100644 src/HttpClient.Cache/ICacheEntry.cs create mode 100644 src/HttpClient.Cache/IChangeToken.cs create mode 100644 src/HttpClient.Cache/PostEvictionCallbackRegistration.cs diff --git a/src/HttpClient.Cache/CacheItemPriority.cs b/src/HttpClient.Cache/CacheItemPriority.cs new file mode 100644 index 0000000..add89ff --- /dev/null +++ b/src/HttpClient.Cache/CacheItemPriority.cs @@ -0,0 +1,9 @@ +namespace HttpClient.Cache; + +public enum CacheItemPriority +{ + Low, + Normal, + High, + NeverRemove +} \ No newline at end of file diff --git a/src/HttpClient.Cache/Class1.cs b/src/HttpClient.Cache/Class1.cs deleted file mode 100644 index a603f63..0000000 --- a/src/HttpClient.Cache/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace HttpClient.Cache; - -public class Class1 -{ -} \ No newline at end of file diff --git a/src/HttpClient.Cache/ICacheEntry.cs b/src/HttpClient.Cache/ICacheEntry.cs new file mode 100644 index 0000000..454ff14 --- /dev/null +++ b/src/HttpClient.Cache/ICacheEntry.cs @@ -0,0 +1,18 @@ +namespace HttpClient.Cache; + +public interface ICacheEntry: IDisposable +{ + object Value { get; set; } + + DateTimeOffset? AbsoluteExpiration { get; set; } + + TimeSpan? AbsoluteExpirationRelativeToNow { get; set; } + + TimeSpan? SlidingExpiration { get; set; } + + IList ExpirationTokens { get; } + + IList PostEvictionCallbacks { get; } + + CacheItemPriority Priority { get; set; } +} \ No newline at end of file diff --git a/src/HttpClient.Cache/IChangeToken.cs b/src/HttpClient.Cache/IChangeToken.cs new file mode 100644 index 0000000..b9df011 --- /dev/null +++ b/src/HttpClient.Cache/IChangeToken.cs @@ -0,0 +1,10 @@ +namespace HttpClient.Cache; + +public interface IChangeToken +{ + bool HasChanged { get; } + + bool ActiveChangeCallbacks { get; } + + IDisposable RegisterChangeCallback(Action callback, object state); +} \ No newline at end of file diff --git a/src/HttpClient.Cache/PostEvictionCallbackRegistration.cs b/src/HttpClient.Cache/PostEvictionCallbackRegistration.cs new file mode 100644 index 0000000..d03f387 --- /dev/null +++ b/src/HttpClient.Cache/PostEvictionCallbackRegistration.cs @@ -0,0 +1,10 @@ +namespace HttpClient.Cache; + +public class PostEvictionCallbackRegistration +{ + public PostEvictionDelegate EvictionCallback { get; set; } + + public object State { get; set; } +} + +public delegate void PostEvictionDelegate(object key, object value, string reason, object state); \ No newline at end of file From 6aca16af80b467f97fc5b3ed4d41054e64064fa4 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Tue, 7 Mar 2023 22:31:23 +0200 Subject: [PATCH 2/7] Base cache data presentation --- src/HttpClient.Cache/CacheData.cs | 14 ++++++++++ src/HttpClient.Cache/CacheDataExtensions.cs | 31 +++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/HttpClient.Cache/CacheData.cs create mode 100644 src/HttpClient.Cache/CacheDataExtensions.cs diff --git a/src/HttpClient.Cache/CacheData.cs b/src/HttpClient.Cache/CacheData.cs new file mode 100644 index 0000000..137853c --- /dev/null +++ b/src/HttpClient.Cache/CacheData.cs @@ -0,0 +1,14 @@ +namespace HttpClient.Cache; + +public class CacheData +{ + public CacheData(byte[] data, HttpResponseMessage response) + { + Data = data; + Response = response; + } + + public byte[] Data { get; } + + public HttpResponseMessage Response { get; } +} \ No newline at end of file diff --git a/src/HttpClient.Cache/CacheDataExtensions.cs b/src/HttpClient.Cache/CacheDataExtensions.cs new file mode 100644 index 0000000..75b888d --- /dev/null +++ b/src/HttpClient.Cache/CacheDataExtensions.cs @@ -0,0 +1,31 @@ +using System.Text.Json; + +namespace HttpClient.Cache; + +public static class CacheDataExtensions +{ + public static byte[] Serialize(this CacheData cacheData) + { + string json = JsonSerializer.Serialize(cacheData); + byte[] bytes = new byte[json.Length * sizeof(char)]; + + Buffer.BlockCopy(json.ToCharArray(), 0, bytes, 0, bytes.Length); + return bytes; + } + + public static CacheData? Deserialize(this byte[] cacheData) + { + try + { + char[] chars = new char[cacheData.Length / sizeof(char)]; + Buffer.BlockCopy(cacheData, 0, chars, 0, cacheData.Length); + string json = new string(chars); + CacheData? data = JsonSerializer.Deserialize(json); + return data; + } + catch + { + return null; + } + } +} \ No newline at end of file From e2fb3ab2c1b20b8fd6f14bceff400ec299af1994 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Tue, 7 Mar 2023 23:11:59 +0200 Subject: [PATCH 3/7] Added base stats to check the cache rate --- .../Stats/CacheStatsProvider.cs | 42 +++++++++++++++++++ .../Stats/CacheStatsResult.cs | 12 ++++++ .../Stats/ICacheStatsProvider.cs | 12 ++++++ src/HttpClient.Cache/Stats/StatsReport.cs | 25 +++++++++++ .../HttpClient.Cache.Tests.csproj | 13 +++++- .../Stats/StatsProviderTests.cs | 26 ++++++++++++ tests/HttpClient.Cache.Tests/UnitTest1.cs | 10 ----- 7 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 src/HttpClient.Cache/Stats/CacheStatsProvider.cs create mode 100644 src/HttpClient.Cache/Stats/CacheStatsResult.cs create mode 100644 src/HttpClient.Cache/Stats/ICacheStatsProvider.cs create mode 100644 src/HttpClient.Cache/Stats/StatsReport.cs create mode 100644 tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs delete mode 100644 tests/HttpClient.Cache.Tests/UnitTest1.cs diff --git a/src/HttpClient.Cache/Stats/CacheStatsProvider.cs b/src/HttpClient.Cache/Stats/CacheStatsProvider.cs new file mode 100644 index 0000000..1b6bd73 --- /dev/null +++ b/src/HttpClient.Cache/Stats/CacheStatsProvider.cs @@ -0,0 +1,42 @@ +using System.Collections.Concurrent; +using System.Net; + +namespace HttpClient.Cache.Stats; + +public class CacheStatsProvider : ICacheStatsProvider +{ + private readonly string _cacheType; + private readonly ConcurrentDictionary _values; + + public CacheStatsProvider(string cacheType) + { + _cacheType = cacheType; + _values = new ConcurrentDictionary(); + } + + public void ReportHit(HttpStatusCode code) + { + _values.AddOrUpdate(code, _ => new CacheStatsResult { CacheHit = 1 }, (_, existing) => + { + existing.CacheHit++; + return existing; + }); + } + + public void ReportMiss(HttpStatusCode code) + { + _values.AddOrUpdate(code, _ => new CacheStatsResult { CacheMiss = 1 }, (_, existing) => + { + existing.CacheMiss++; + return existing; + }); + } + + public StatsReport GetReport() + { + return new StatsReport(_cacheType) + { + PerStatusCode = new Dictionary(_values) + }; + } +} \ No newline at end of file diff --git a/src/HttpClient.Cache/Stats/CacheStatsResult.cs b/src/HttpClient.Cache/Stats/CacheStatsResult.cs new file mode 100644 index 0000000..4cd917c --- /dev/null +++ b/src/HttpClient.Cache/Stats/CacheStatsResult.cs @@ -0,0 +1,12 @@ +namespace HttpClient.Cache.Stats; + +public class CacheStatsResult +{ + public long CacheHit { get; set; } + public long CacheMiss { get; set; } + public long TotalRequests => CacheHit + CacheMiss; + + public double TotalHitsPercent => CacheHit * 1.0 / TotalRequests; + + public double TotalMissPercent => CacheMiss * 1.0 / TotalRequests; +} \ No newline at end of file diff --git a/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs b/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs new file mode 100644 index 0000000..2d6d472 --- /dev/null +++ b/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs @@ -0,0 +1,12 @@ +using System.Net; + +namespace HttpClient.Cache.Stats; + +public interface ICacheStatsProvider +{ + void ReportHit(HttpStatusCode code); + + void ReportMiss(HttpStatusCode code); + + StatsReport GetReport(); +} \ No newline at end of file diff --git a/src/HttpClient.Cache/Stats/StatsReport.cs b/src/HttpClient.Cache/Stats/StatsReport.cs new file mode 100644 index 0000000..e4f4522 --- /dev/null +++ b/src/HttpClient.Cache/Stats/StatsReport.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace HttpClient.Cache.Stats; + +public class StatsReport +{ + public StatsReport(string cacheType) + { + CacheType = cacheType; + PerStatusCode = new Dictionary(); + CreatedAt = DateTimeOffset.Now; + } + + public DateTimeOffset CreatedAt { get; } + + public string CacheType { get; } + + public Dictionary PerStatusCode { get; init; } + + public CacheStatsResult Total => new() + { + CacheHit = PerStatusCode.Sum(status => status.Value.CacheHit), + CacheMiss = PerStatusCode.Sum(status => status.Value.CacheMiss) + }; +} \ No newline at end of file diff --git a/tests/HttpClient.Cache.Tests/HttpClient.Cache.Tests.csproj b/tests/HttpClient.Cache.Tests/HttpClient.Cache.Tests.csproj index 6f8dc75..2974db9 100644 --- a/tests/HttpClient.Cache.Tests/HttpClient.Cache.Tests.csproj +++ b/tests/HttpClient.Cache.Tests/HttpClient.Cache.Tests.csproj @@ -9,8 +9,13 @@ - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -21,4 +26,8 @@ + + + + diff --git a/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs b/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs new file mode 100644 index 0000000..fb3d741 --- /dev/null +++ b/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs @@ -0,0 +1,26 @@ +using System.Net; +using FluentAssertions; +using HttpClient.Cache.Stats; + +namespace HttpClient.Cache.Tests.Stats; + +public class StatsProviderTests +{ + [Fact] + public void ReportHit_ReportCacheHitWith201_ReportSuccessful() + { + CacheStatsProvider provider = new("test-cache"); + var expected = new StatsReport("test-cache") + { + PerStatusCode = new Dictionary + { + { HttpStatusCode.Created, new CacheStatsResult { CacheHit = 1L, CacheMiss = 0L } } + } + }; + + provider.ReportHit(HttpStatusCode.Created); + var stats = provider.GetReport(); + + stats.Should().BeEquivalentTo(expected, ignore => ignore.Excluding(x => x.CreatedAt)); + } +} \ No newline at end of file diff --git a/tests/HttpClient.Cache.Tests/UnitTest1.cs b/tests/HttpClient.Cache.Tests/UnitTest1.cs deleted file mode 100644 index 6661d86..0000000 --- a/tests/HttpClient.Cache.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace HttpClient.Cache.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - Assert.True(true); - } -} \ No newline at end of file From dff2a0b12a7e22e12a4b304e222f9f70e1d3e501 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Wed, 8 Mar 2023 14:01:03 +0200 Subject: [PATCH 4/7] Added code coverage step for build --- .../bug_report.md | 0 .../config.yml | 0 .../feature_request.md | 0 .github/workflows/dotnet-build.yml | 43 +++++++++++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) rename .github/{issue_template => ISSUE_TEMPLATE}/bug_report.md (100%) rename .github/{issue_template => ISSUE_TEMPLATE}/config.yml (100%) rename .github/{issue_template => ISSUE_TEMPLATE}/feature_request.md (100%) diff --git a/.github/issue_template/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 100% rename from .github/issue_template/bug_report.md rename to .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/issue_template/config.yml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/issue_template/config.yml rename to .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/issue_template/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md similarity index 100% rename from .github/issue_template/feature_request.md rename to .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index e433b3c..3932c16 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -9,12 +9,37 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - - name: Restore - run: dotnet restore - - name: Build - run: dotnet build --no-restore --configuration Release - - name: Test - run: dotnet test --no-build --configuration Release --filter "FullyQualifiedName!~AcceptanceTests" \ No newline at end of file + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release + + - name: Test + run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage + + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: coverage/**/coverage.cobertura.xml + badge: true + fail_below_min: true + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: '60 80' + + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + recreate: true + path: code-coverage-results.md \ No newline at end of file From 77382782b066b55b6f64203588272ca87be2ae51 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Wed, 8 Mar 2023 14:08:10 +0200 Subject: [PATCH 5/7] Remove fail gate if low coverage for now --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 3932c16..3ab5598 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -29,7 +29,7 @@ jobs: with: filename: coverage/**/coverage.cobertura.xml badge: true - fail_below_min: true + fail_below_min: false format: markdown hide_branch_rate: false hide_complexity: true From 288473e9a34c68f3041d745acba8f6e8b494d815 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Wed, 8 Mar 2023 16:14:26 +0200 Subject: [PATCH 6/7] Added more tests for StatsProvider --- .../Stats/StatsProviderTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs b/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs index fb3d741..1d46867 100644 --- a/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs +++ b/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs @@ -6,6 +6,17 @@ namespace HttpClient.Cache.Tests.Stats; public class StatsProviderTests { + [Fact] + public void GetReport_GetDefaultEmptyReportIfNoActions_ReportSuccessful() + { + CacheStatsProvider provider = new("test-cache"); + var expected = new StatsReport("test-cache"); + + var stats = provider.GetReport(); + + stats.Should().BeEquivalentTo(expected, ignore => ignore.Excluding(x => x.CreatedAt)); + } + [Fact] public void ReportHit_ReportCacheHitWith201_ReportSuccessful() { @@ -23,4 +34,22 @@ public void ReportHit_ReportCacheHitWith201_ReportSuccessful() stats.Should().BeEquivalentTo(expected, ignore => ignore.Excluding(x => x.CreatedAt)); } + + [Fact] + public void ReportHit_ReportCacheMissWith503_ReportSuccessful() + { + CacheStatsProvider provider = new("test-cache"); + var expected = new StatsReport("test-cache") + { + PerStatusCode = new Dictionary + { + { HttpStatusCode.ServiceUnavailable, new CacheStatsResult { CacheHit = 0L, CacheMiss = 1L } } + } + }; + + provider.ReportMiss(HttpStatusCode.ServiceUnavailable); + var stats = provider.GetReport(); + + stats.Should().BeEquivalentTo(expected, ignore => ignore.Excluding(x => x.CreatedAt)); + } } \ No newline at end of file From 201c24e47352320e4a817cd69aeb92e687ed2e60 Mon Sep 17 00:00:00 2001 From: Leefrost Date: Wed, 8 Mar 2023 20:06:47 +0200 Subject: [PATCH 7/7] Tweaks around cache stats --- .../{StatsReport.cs => CacheStatsReport.cs} | 4 +-- ...ovider.cs => DefaultCacheStatsProvider.cs} | 8 ++--- .../Stats/ICacheStatsProvider.cs | 2 +- ...rTests.cs => DefaultStatsProviderTests.cs} | 34 +++++++++++++++---- 4 files changed, 34 insertions(+), 14 deletions(-) rename src/HttpClient.Cache/Stats/{StatsReport.cs => CacheStatsReport.cs} (88%) rename src/HttpClient.Cache/Stats/{CacheStatsProvider.cs => DefaultCacheStatsProvider.cs} (82%) rename tests/HttpClient.Cache.Tests/Stats/{StatsProviderTests.cs => DefaultStatsProviderTests.cs} (56%) diff --git a/src/HttpClient.Cache/Stats/StatsReport.cs b/src/HttpClient.Cache/Stats/CacheStatsReport.cs similarity index 88% rename from src/HttpClient.Cache/Stats/StatsReport.cs rename to src/HttpClient.Cache/Stats/CacheStatsReport.cs index e4f4522..0a0133c 100644 --- a/src/HttpClient.Cache/Stats/StatsReport.cs +++ b/src/HttpClient.Cache/Stats/CacheStatsReport.cs @@ -2,9 +2,9 @@ namespace HttpClient.Cache.Stats; -public class StatsReport +public class CacheStatsReport { - public StatsReport(string cacheType) + public CacheStatsReport(string cacheType) { CacheType = cacheType; PerStatusCode = new Dictionary(); diff --git a/src/HttpClient.Cache/Stats/CacheStatsProvider.cs b/src/HttpClient.Cache/Stats/DefaultCacheStatsProvider.cs similarity index 82% rename from src/HttpClient.Cache/Stats/CacheStatsProvider.cs rename to src/HttpClient.Cache/Stats/DefaultCacheStatsProvider.cs index 1b6bd73..bd7d6dc 100644 --- a/src/HttpClient.Cache/Stats/CacheStatsProvider.cs +++ b/src/HttpClient.Cache/Stats/DefaultCacheStatsProvider.cs @@ -3,12 +3,12 @@ namespace HttpClient.Cache.Stats; -public class CacheStatsProvider : ICacheStatsProvider +public class DefaultCacheStatsProvider : ICacheStatsProvider { private readonly string _cacheType; private readonly ConcurrentDictionary _values; - public CacheStatsProvider(string cacheType) + public DefaultCacheStatsProvider(string cacheType) { _cacheType = cacheType; _values = new ConcurrentDictionary(); @@ -32,9 +32,9 @@ public void ReportMiss(HttpStatusCode code) }); } - public StatsReport GetReport() + public CacheStatsReport GetReport() { - return new StatsReport(_cacheType) + return new CacheStatsReport(_cacheType) { PerStatusCode = new Dictionary(_values) }; diff --git a/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs b/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs index 2d6d472..c6361f0 100644 --- a/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs +++ b/src/HttpClient.Cache/Stats/ICacheStatsProvider.cs @@ -8,5 +8,5 @@ public interface ICacheStatsProvider void ReportMiss(HttpStatusCode code); - StatsReport GetReport(); + CacheStatsReport GetReport(); } \ No newline at end of file diff --git a/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs b/tests/HttpClient.Cache.Tests/Stats/DefaultStatsProviderTests.cs similarity index 56% rename from tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs rename to tests/HttpClient.Cache.Tests/Stats/DefaultStatsProviderTests.cs index 1d46867..fbc53c7 100644 --- a/tests/HttpClient.Cache.Tests/Stats/StatsProviderTests.cs +++ b/tests/HttpClient.Cache.Tests/Stats/DefaultStatsProviderTests.cs @@ -1,16 +1,17 @@ using System.Net; using FluentAssertions; +using FluentAssertions.Execution; using HttpClient.Cache.Stats; namespace HttpClient.Cache.Tests.Stats; -public class StatsProviderTests +public class DefaultStatsProviderTests { [Fact] public void GetReport_GetDefaultEmptyReportIfNoActions_ReportSuccessful() { - CacheStatsProvider provider = new("test-cache"); - var expected = new StatsReport("test-cache"); + DefaultCacheStatsProvider provider = new("test-cache"); + var expected = new CacheStatsReport("test-cache"); var stats = provider.GetReport(); @@ -20,8 +21,8 @@ public void GetReport_GetDefaultEmptyReportIfNoActions_ReportSuccessful() [Fact] public void ReportHit_ReportCacheHitWith201_ReportSuccessful() { - CacheStatsProvider provider = new("test-cache"); - var expected = new StatsReport("test-cache") + DefaultCacheStatsProvider provider = new("test-cache"); + var expected = new CacheStatsReport("test-cache") { PerStatusCode = new Dictionary { @@ -38,8 +39,8 @@ public void ReportHit_ReportCacheHitWith201_ReportSuccessful() [Fact] public void ReportHit_ReportCacheMissWith503_ReportSuccessful() { - CacheStatsProvider provider = new("test-cache"); - var expected = new StatsReport("test-cache") + DefaultCacheStatsProvider provider = new("test-cache"); + var expected = new CacheStatsReport("test-cache") { PerStatusCode = new Dictionary { @@ -52,4 +53,23 @@ public void ReportHit_ReportCacheMissWith503_ReportSuccessful() stats.Should().BeEquivalentTo(expected, ignore => ignore.Excluding(x => x.CreatedAt)); } + + [Fact] + public void GetReport_ReportOneMissAndHit_ReportTotalIsSuccessful() + { + DefaultCacheStatsProvider provider = new("test-cache"); + + provider.ReportHit(HttpStatusCode.Created); + provider.ReportMiss(HttpStatusCode.Created); + var stats = provider.GetReport(); + + using (new AssertionScope()) + { + stats.Total.CacheMiss.Should().Be(1L); + stats.Total.CacheHit.Should().Be(1L); + stats.Total.TotalRequests.Should().Be(2L); + stats.Total.TotalHitsPercent.Should().Be(0.5); + stats.Total.TotalMissPercent.Should().Be(0.5); + } + } } \ No newline at end of file