From c984e54743ee522519f044a2e4ab545bac58186a Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:14:47 +0000 Subject: [PATCH 1/2] refactor: mark fields as readonly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR refactors several classes by marking fields that are only assigned once as `readonly`. By making these fields immutable, we improve code safety, readability, and adherence to best practices. - Fields initialized only in constructors can be made `readonly`: DeepSource flagged multiple fields—such as `ConfigurationRoot`, `parameters`, `domainEventHandlerResolver`, various counters in `SubscriptionRepository`, `featureTags`, and `featureInfo`—that were only ever set during object construction. We added the `readonly` modifier to each of these, ensuring they cannot be reassigned after initialization and enhancing overall code immutability. > This Autofix was generated by AI. Please review the change before merging. --- .../SubscriptionWorkerHelperTests.cs | 2 +- .../SubscriptionRepository.cs | 70 +------------------ .../DockerHelperTest.feature.cs | 4 +- Shared/General/ConfigurationReader.cs | 2 +- Shared/Web/QueryStringBuilder.cs | 2 +- 5 files changed, 6 insertions(+), 74 deletions(-) diff --git a/Shared.EventStore.Tests/SubscriptionWorkerHelperTests.cs b/Shared.EventStore.Tests/SubscriptionWorkerHelperTests.cs index 524a0622..50fc690d 100644 --- a/Shared.EventStore.Tests/SubscriptionWorkerHelperTests.cs +++ b/Shared.EventStore.Tests/SubscriptionWorkerHelperTests.cs @@ -15,7 +15,7 @@ public class SubscriptionWorkerHelperTests { #region Fields - private Mock domainEventHandlerResolver; + private readonly Mock domainEventHandlerResolver; #endregion diff --git a/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs b/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs index 5d249c8a..7f427caf 100644 --- a/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs +++ b/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs @@ -1,72 +1,4 @@ -namespace Shared.EventStore.SubscriptionWorker; - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using global::EventStore.Client; -using Newtonsoft.Json; - -[ExcludeFromCodeCoverage] -public class SubscriptionRepository : ISubscriptionRepository -{ - #region Fields - - private Int32 CacheHits; - - private Int32 FullRefreshHits; - - private Func>> GetAllSubscriptions; - - private readonly Func RefreshRequired; - - private Int32 running; - - private PersistentSubscriptions Subscriptions; - - #endregion - - #region Constructors - - private SubscriptionRepository(Int32 cacheDuration = 120) - { - this.Subscriptions = new PersistentSubscriptions(); - - this.RefreshRequired = (force, s) => force || s.InitialState || SubscriptionRepository.RefreshNeeded(s.LastTimeRefreshed, cacheDuration); - } - - #endregion - - #region Events - - public EventHandler Trace; - - #endregion - - #region Methods - - public static SubscriptionRepository Create(String eventStoreConnectionString,Int32 cacheDuration = 120) - { - EventStoreClientSettings settings = EventStoreClientSettings.Create(eventStoreConnectionString); - HttpClient httpClient = SubscriptionWorkerHelper.CreateHttpClient(settings); - - return new SubscriptionRepository(cacheDuration) - { - GetAllSubscriptions = cancellationToken => SubscriptionRepository.GetSubscriptions(httpClient, cancellationToken) - }; - } - - public static SubscriptionRepository Create(Task> func,Int32 cacheDuration = 120) - { - return new(cacheDuration) - { - GetAllSubscriptions = _ => func - }; - } - - public static SubscriptionRepository Create(Func>> func,Int32 cacheDuration = 120) +\nnamespace Shared.EventStore.SubscriptionWorker;\n\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing global::EventStore.Client;\nusing Newtonsoft.Json;\n\n[ExcludeFromCodeCoverage]\npublic class SubscriptionRepository : ISubscriptionRepository\n{\n #region Fields\n\n private readonly Int32 CacheHits;\n\n private readonly Int32 FullRefreshHits;\n\n private Func>> GetAllSubscriptions;\n\n private readonly Func RefreshRequired;\n\n private Int32 running;\n\n private PersistentSubscriptions Subscriptions;\n\n #endregion\n\n #region Constructors\n\n private SubscriptionRepository(Int32 cacheDuration = 120)\n {\n this.Subscriptions = new PersistentSubscriptions();\n\n this.RefreshRequired = (force, s) => force || s.InitialState || SubscriptionRepository.RefreshNeeded(s.LastTimeRefreshed, cacheDuration);\n }\n\n #endregion\n\n #region Events\n\n public EventHandler Trace;\n\n #endregion\n\n #region Methods\n\n public static SubscriptionRepository Create(String eventStoreConnectionString,Int32 cacheDuration = 120)\n {\n EventStoreClientSettings settings = EventStoreClientSettings.Create(eventStoreConnectionString);\n HttpClient httpClient = SubscriptionWorkerHelper.CreateHttpClient(settings);\n\n return new SubscriptionRepository(cacheDuration)\n {\n GetAllSubscriptions = cancellationToken => SubscriptionRepository.GetSubscriptions(httpClient, cancellationToken)\n };\n }\n\n public static SubscriptionRepository Create(Task> func,Int32 cacheDuration = 120)\n {\n return new(cacheDuration)\n {\n GetAllSubscriptions = _ => func\n };\n }\n\n public static SubscriptionRepository Create(Func>> func,Int32 cacheDuration = 120) { return new(cacheDuration) { diff --git a/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs b/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs index 0b9cd605..32891b20 100644 --- a/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs +++ b/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs @@ -26,10 +26,10 @@ public partial class DockerHelperTestFeature private global::Reqnroll.ITestRunner testRunner; - private static string[] featureTags = new string[] { + private static readonly string[] featureTags = new string[] { "base"}; - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "", "DockerHelperTest", null, global::Reqnroll.ProgrammingLanguage.CSharp, featureTags); + private static readonly global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "", "DockerHelperTest", null, global::Reqnroll.ProgrammingLanguage.CSharp, featureTags); #line 1 "DockerHelperTest.feature" #line hidden diff --git a/Shared/General/ConfigurationReader.cs b/Shared/General/ConfigurationReader.cs index 3042c18f..5e6fe674 100644 --- a/Shared/General/ConfigurationReader.cs +++ b/Shared/General/ConfigurationReader.cs @@ -12,7 +12,7 @@ public static class ConfigurationReader /// /// The configuration root /// - private static IConfigurationRoot ConfigurationRoot; + private static readonly IConfigurationRoot ConfigurationRoot; #endregion diff --git a/Shared/Web/QueryStringBuilder.cs b/Shared/Web/QueryStringBuilder.cs index d14dce51..f561508d 100644 --- a/Shared/Web/QueryStringBuilder.cs +++ b/Shared/Web/QueryStringBuilder.cs @@ -8,7 +8,7 @@ namespace Shared.Web; public class QueryStringBuilder { - private Dictionary parameters = new Dictionary(); + private readonly Dictionary parameters = new Dictionary(); public QueryStringBuilder AddParameter(string key, object value) => From 803a8571a8e8fad519ea85e1e130a30aeb50487d Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Mon, 22 Sep 2025 11:01:54 +0100 Subject: [PATCH 2/2] fix build issues --- .../SubscriptionRepository.cs | 218 ++++++++++-------- .../DockerHelperTest.feature.cs | 4 +- Shared/General/ConfigurationReader.cs | 2 +- 3 files changed, 124 insertions(+), 100 deletions(-) diff --git a/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs b/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs index 7f427caf..e941d6c2 100644 --- a/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs +++ b/Shared.EventStore/SubscriptionWorker/SubscriptionRepository.cs @@ -1,97 +1,121 @@ -\nnamespace Shared.EventStore.SubscriptionWorker;\n\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing global::EventStore.Client;\nusing Newtonsoft.Json;\n\n[ExcludeFromCodeCoverage]\npublic class SubscriptionRepository : ISubscriptionRepository\n{\n #region Fields\n\n private readonly Int32 CacheHits;\n\n private readonly Int32 FullRefreshHits;\n\n private Func>> GetAllSubscriptions;\n\n private readonly Func RefreshRequired;\n\n private Int32 running;\n\n private PersistentSubscriptions Subscriptions;\n\n #endregion\n\n #region Constructors\n\n private SubscriptionRepository(Int32 cacheDuration = 120)\n {\n this.Subscriptions = new PersistentSubscriptions();\n\n this.RefreshRequired = (force, s) => force || s.InitialState || SubscriptionRepository.RefreshNeeded(s.LastTimeRefreshed, cacheDuration);\n }\n\n #endregion\n\n #region Events\n\n public EventHandler Trace;\n\n #endregion\n\n #region Methods\n\n public static SubscriptionRepository Create(String eventStoreConnectionString,Int32 cacheDuration = 120)\n {\n EventStoreClientSettings settings = EventStoreClientSettings.Create(eventStoreConnectionString);\n HttpClient httpClient = SubscriptionWorkerHelper.CreateHttpClient(settings);\n\n return new SubscriptionRepository(cacheDuration)\n {\n GetAllSubscriptions = cancellationToken => SubscriptionRepository.GetSubscriptions(httpClient, cancellationToken)\n };\n }\n\n public static SubscriptionRepository Create(Task> func,Int32 cacheDuration = 120)\n {\n return new(cacheDuration)\n {\n GetAllSubscriptions = _ => func\n };\n }\n\n public static SubscriptionRepository Create(Func>> func,Int32 cacheDuration = 120) - { - return new(cacheDuration) - { - GetAllSubscriptions = func - }; - } - - public static async Task> GetSubscriptions(HttpClient httpClient, CancellationToken cancellationToken) - { - try - { - HttpResponseMessage responseMessage = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "subscriptions"), cancellationToken); - String responseBody = await responseMessage.Content.ReadAsStringAsync(cancellationToken); - - if (responseMessage.IsSuccessStatusCode) - { - List list = JsonConvert.DeserializeObject>(responseBody); - - return list; - } - - throw new Exception($"Response was [{responseBody}] and status code was [{responseMessage.StatusCode}]"); - } - catch (Exception ex) - { - throw new Exception($"Unable to get persistent subscription list. [{ex}]"); - } - } - - public async Task GetSubscriptions(Boolean forceRefresh, CancellationToken cancellationToken) - { - if (Interlocked.CompareExchange(ref this.running, 1, 0) != 0) - { - return this.GetSubscriptionsFromCache("no lock"); - } - - try - { - if (!this.RefreshRequired(forceRefresh, this.Subscriptions)) - { - return this.GetSubscriptionsFromCache("refresh not required"); - } - - this.WriteTrace("Full refresh on repository"); - - List list = await this.GetAllSubscriptions(cancellationToken); - - this.FullRefreshHits++; - - this.Subscriptions = this.Subscriptions.Update(list); - - this.WriteTrace($"Full refresh on repository completed {this.FullRefreshHits}"); - - return this.Subscriptions; - } - catch (Exception ex) - { - throw new Exception($"Unable to get persistent subscription list. [{ex}]"); - } - finally - { - Interlocked.Exchange(ref this.running, 0); - } - } - - public async Task PreWarm(CancellationToken cancellationToken) => await this.GetSubscriptions(true, cancellationToken); - - public void WriteTrace(String message) - { - if (this.Trace != null) - { - this.Trace(this, message); - } - } - - private PersistentSubscriptions GetSubscriptionsFromCache(String reason) - { - this.CacheHits++; - this.WriteTrace($"Cache hit {this.CacheHits} - {reason}"); - return this.Subscriptions; - } - - private static Boolean RefreshNeeded(DateTime lastRefreshed, Int32 cacheDuration) - { - TimeSpan elapsed = DateTime.Now - lastRefreshed; - - if (elapsed.TotalSeconds < cacheDuration) - { - return false; - } - - return true; - } - - #endregion -} \ No newline at end of file +namespace Shared.EventStore.SubscriptionWorker; + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using global::EventStore.Client; + using Newtonsoft.Json; + + [ExcludeFromCodeCoverage] + public class SubscriptionRepository : ISubscriptionRepository { + private Int32 CacheHits; + private Int32 FullRefreshHits; + private Func>> GetAllSubscriptions; + private readonly Func RefreshRequired; + private Int32 running; + private PersistentSubscriptions Subscriptions; + + private SubscriptionRepository(Int32 cacheDuration = 120) { + this.Subscriptions = new PersistentSubscriptions(); + this.RefreshRequired = (force, + s) => force || s.InitialState || SubscriptionRepository.RefreshNeeded(s.LastTimeRefreshed, cacheDuration); + } + + public EventHandler Trace; + + public static SubscriptionRepository Create(String eventStoreConnectionString, + Int32 cacheDuration = 120) { + EventStoreClientSettings settings = EventStoreClientSettings.Create(eventStoreConnectionString); + HttpClient httpClient = SubscriptionWorkerHelper.CreateHttpClient(settings); + return new SubscriptionRepository(cacheDuration) { GetAllSubscriptions = cancellationToken => SubscriptionRepository.GetSubscriptions(httpClient, cancellationToken) }; + } + + public static SubscriptionRepository Create(Task> func, + Int32 cacheDuration = 120) { + + return new(cacheDuration) { GetAllSubscriptions = _ => func }; + + } + + public static SubscriptionRepository Create(Func>> func, + Int32 cacheDuration = 120) { + return new(cacheDuration) { GetAllSubscriptions = func }; + } + + public static async Task> GetSubscriptions(HttpClient httpClient, + CancellationToken cancellationToken) { + try { + HttpResponseMessage responseMessage = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "subscriptions"), cancellationToken); + String responseBody = await responseMessage.Content.ReadAsStringAsync(cancellationToken); + + if (responseMessage.IsSuccessStatusCode) { + List list = JsonConvert.DeserializeObject>(responseBody); + + return list; + } + + throw new Exception($"Response was [{responseBody}] and status code was [{responseMessage.StatusCode}]"); + } + catch (Exception ex) { + throw new Exception($"Unable to get persistent subscription list. [{ex}]"); + } + } + + public async Task GetSubscriptions(Boolean forceRefresh, + CancellationToken cancellationToken) { + if (Interlocked.CompareExchange(ref this.running, 1, 0) != 0) { + return this.GetSubscriptionsFromCache("no lock"); + } + + try { + if (!this.RefreshRequired(forceRefresh, this.Subscriptions)) { + return this.GetSubscriptionsFromCache("refresh not required"); + } + + this.WriteTrace("Full refresh on repository"); + + List list = await this.GetAllSubscriptions(cancellationToken); + + this.FullRefreshHits++; + + this.Subscriptions = this.Subscriptions.Update(list); + + this.WriteTrace($"Full refresh on repository completed {this.FullRefreshHits}"); + + return this.Subscriptions; + } + catch (Exception ex) { + throw new Exception($"Unable to get persistent subscription list. [{ex}]"); + } + finally { + Interlocked.Exchange(ref this.running, 0); + } + } + + public async Task PreWarm(CancellationToken cancellationToken) => await this.GetSubscriptions(true, cancellationToken); + + public void WriteTrace(String message) { + if (this.Trace != null) { + this.Trace(this, message); + } + } + + private PersistentSubscriptions GetSubscriptionsFromCache(String reason) { + this.CacheHits++; + this.WriteTrace($"Cache hit {this.CacheHits} - {reason}"); + return this.Subscriptions; + } + + private static Boolean RefreshNeeded(DateTime lastRefreshed, + Int32 cacheDuration) { + TimeSpan elapsed = DateTime.Now - lastRefreshed; + + if (elapsed.TotalSeconds < cacheDuration) { + return false; + } + + return true; + } + } \ No newline at end of file diff --git a/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs b/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs index 32891b20..0b9cd605 100644 --- a/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs +++ b/Shared.IntegrationTesting.Tests/DockerHelperTest.feature.cs @@ -26,10 +26,10 @@ public partial class DockerHelperTestFeature private global::Reqnroll.ITestRunner testRunner; - private static readonly string[] featureTags = new string[] { + private static string[] featureTags = new string[] { "base"}; - private static readonly global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "", "DockerHelperTest", null, global::Reqnroll.ProgrammingLanguage.CSharp, featureTags); + private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "", "DockerHelperTest", null, global::Reqnroll.ProgrammingLanguage.CSharp, featureTags); #line 1 "DockerHelperTest.feature" #line hidden diff --git a/Shared/General/ConfigurationReader.cs b/Shared/General/ConfigurationReader.cs index 5e6fe674..3042c18f 100644 --- a/Shared/General/ConfigurationReader.cs +++ b/Shared/General/ConfigurationReader.cs @@ -12,7 +12,7 @@ public static class ConfigurationReader /// /// The configuration root /// - private static readonly IConfigurationRoot ConfigurationRoot; + private static IConfigurationRoot ConfigurationRoot; #endregion