Skip to content

Conversation

@brant-livefront
Copy link
Contributor

📔 Objective

In PR #6718 we added a queue name for the hosted service that reads the events. However, there were a couple of Claude comments (Findings 2, 3 and 4) that still would be good to address. This PR addresses all three:

  • Finding 2: The queue name was still hardcoded on the write side
    • AzureQueueEventWriteService now pulls this from global settings
  • Finding 3: The read service fails silently
    • I've added an Information level log for this case
  • Finding 4: The read service used IConfiguration instead of GlobalSettings
    • This was necessary to address Finding 2 and it is also more in line with our normal way of handling configuration

⚠️ Note: This involves refactoring the globalSettings.Events object to a custom one and away from ConnectionStringSettings. I've preserved the ConnectionString (even preserved conforming with IConnectionStringSettings) so that it would be backwards compatible. That is, globalSettings.Events.ConnectionString and any configs that specify it should function just the same. However this effects things outside of just this Queue service (for instance InstallationDeviceRepository), so it would be good to give it a very close look and test accordingly.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@claude
Copy link
Contributor

claude bot commented Dec 11, 2025

Claude finished @brant-livefront's task in 1m 36s —— View job


Code Review Complete ✓

I've reviewed the PR changes that address the findings from PR #6718. Overall, the implementation correctly addresses the three findings, with some suggestions for improvement.

Summary

✅ Successfully Addresses:

  • Finding 2: Queue name no longer hardcoded on write side - now pulls from globalSettings.Events.QueueName
  • Finding 3: Read service no longer fails silently - added information-level logging
  • Finding 4: Read service now uses GlobalSettings instead of IConfiguration

Key Changes:

  • Refactored globalSettings.Events from ConnectionStringSettings to AzureQueueEventSettings
  • Added QueueName property with proper null handling
  • Updated service registration to check both ConnectionString and QueueName
  • Improved logging in AzureQueueHostedService

Findings

❓ Missing Test Coverage (line 95 of EventIntegrationsServiceCollectionExtensions.cs)

The validation logic checks both ConnectionString AND QueueName, but there are no tests covering scenarios where only one is present. This could prevent regression if someone changes the && to || by mistake. Recommended: Add test cases for partial configuration scenarios.

⚠️ Defensive Programming Opportunity (line 11 of AzureQueueEventWriteService.cs)

While service registration validates configuration, the constructor could throw unclear exceptions if instantiated directly. Suggested: Add guard clauses with descriptive error messages for better debugging.

📝 Documentation Enhancements

  • AzureQueueHostedService.cs (line 65): Consider structured logging for better production queryability
  • secrets.json.example (line 38): Add comments explaining configuration behavior and when to leave values empty

👍 Well Done

  • Proper null safety using value?.Trim('"') pattern consistently
  • Intentional design allowing null QueueName to disable the service
  • Good logging improvement for troubleshooting
  • Maintains backward compatibility with IConnectionStringSettings

Recommendation

Approval with minor improvements recommended. The core implementation is solid and addresses all previous findings. The suggestions above are enhancements for robustness and maintainability but are not blocking issues.


@github-actions
Copy link
Contributor

github-actions bot commented Dec 11, 2025

Logo
Checkmarx One – Scan Summary & Details2babbc22-325a-4101-8999-0f0443438598

New Issues (2)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 1519
detailsMethod at line 1519 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
ID: dMGF5qNfAN72zlvQcA1MgbhHv%2Fc%3D
Attack Vector
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 1395
detailsMethod at line 1395 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
ID: iOCFr11iI9znjDnv46yLfiS4aDY%3D
Attack Vector
Fixed Issues (1)

Great job! The following issues were fixed in this Pull Request

Severity Issue Source File / Package
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 300

@codecov
Copy link

codecov bot commented Dec 11, 2025

Codecov Report

❌ Patch coverage is 38.46154% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.11%. Comparing base (196e555) to head (aaf8d48).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/EventsProcessor/AzureQueueHostedService.cs 0.00% 5 Missing ⚠️
src/Core/Settings/GlobalSettings.cs 60.00% 0 Missing and 2 partials ⚠️
...ces/Implementations/AzureQueueEventWriteService.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6724      +/-   ##
==========================================
+ Coverage   54.10%   54.11%   +0.01%     
==========================================
  Files        1922     1922              
  Lines       85153    85159       +6     
  Branches     7608     7610       +2     
==========================================
+ Hits        46073    46086      +13     
+ Misses      37325    37316       -9     
- Partials     1755     1757       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@withinfocus withinfocus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude's findings 1 and 6 are needed.

@brant-livefront brant-livefront force-pushed the brant/add-config-for-queue-write branch from 89c456f to 2fba65b Compare December 11, 2025 22:19
@brant-livefront brant-livefront changed the base branch from main to brant/refactor-event-integrations-service-collection-extensions December 11, 2025 22:19
@brant-livefront
Copy link
Contributor Author

I rebased this onto the event integration service collection refactor (instead of main) because I realized we really should add a test for the queue name before starting the azure queue event write service. Previously, the value was hardcoded so it wasn't part of our checks. Since these have been moved in that branch, I needed to rebase onto it to make changes in the final place.

Copy link
Contributor

@withinfocus withinfocus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but fix lint.

{
public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(globalSettings.Events.ConnectionString, "event"),
new QueueClient(globalSettings.Events.ConnectionString, globalSettings.Events.QueueName),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Null Reference Exception

If globalSettings.Events.QueueName is null, this will throw an ArgumentNullException when instantiating the QueueClient, even though the service collection registration checks for null values.

While the registration in AddEventWriteServices (line 94-95 of EventIntegrationsServiceCollectionExtensions.cs) now checks both ConnectionString and QueueName, if someone were to manually instantiate this service or if the registration check fails to prevent it, this would throw.

Recommendation: Add a guard clause or null check with a more descriptive error message:

public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
    new QueueClient(
        globalSettings.Events.ConnectionString ?? throw new ArgumentNullException(nameof(globalSettings.Events.ConnectionString)),
        globalSettings.Events.QueueName ?? throw new ArgumentNullException(nameof(globalSettings.Events.QueueName))
    ),
    JsonHelpers.IgnoreWritingNull)
{ }

This would make debugging easier by clearly identifying which parameter is null.

public string ConnectionString
{
get => _connectionString;
set => _connectionString = value?.Trim('"');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good Null Safety Implementation

The null-conditional operator (value?.Trim('"')) correctly handles null values, allowing the QueueName to be null (which is intentional per the author's note to disable the service in production).

This is consistent with the pattern used in other settings classes like AzureServiceBusSettings.ConnectionString (line 326).

if (string.IsNullOrWhiteSpace(storageConnectionString) ||
string.IsNullOrWhiteSpace(queueName))
{
_logger.LogInformation("Azure Queue Hosted Service is disabled. Missing connection string or queue name.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Excellent Error Handling

The information-level log message clearly indicates why the service is disabled. This is much better than the previous silent failure.

Minor suggestion: Consider using structured logging to make this more queryable:

_logger.LogInformation("Azure Queue Hosted Service is disabled. ConnectionStringPresent: {ConnectionStringPresent}, QueueNamePresent: {QueueNamePresent}", 
    !string.IsNullOrWhiteSpace(storageConnectionString), 
    !string.IsNullOrWhiteSpace(queueName));

This would help operators understand exactly which configuration is missing without needing to look at code.

}

if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Correctly Addresses Previous Review Finding #2

This now checks both ConnectionString and QueueName before registering the Azure Queue service, preventing the hardcoded queue name issue from the previous review.

The check aligns with the documentation in the XML comments (lines 69-70) and the read-side implementation in AzureQueueHostedService.cs (lines 60-63).

{
["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net"
["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net",
["GlobalSettings:Events:QueueName"] = "event"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Missing Test Coverage

The author mentioned adding "a test to prevent service start if QueueName is null/empty", but I cannot find such a test in this file.

There should be tests for these scenarios:

  1. When ConnectionString is present but QueueName is null/empty → Should register NoopEventWriteService
  2. When QueueName is present but ConnectionString is null/empty → Should register NoopEventWriteService

Recommendation: Add these test cases:

[Fact]
public void AddEventWriteServices_EventsConnectionStringPresentButQueueNameMissing_RegistersNoopService()
{
    var services = new ServiceCollection();
    var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
    {
        ["GlobalSettings:Events:ConnectionString"] = "DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;EndpointSuffix=core.windows.net",
        ["GlobalSettings:Events:QueueName"] = null
    });

    services.AddEventWriteServices(globalSettings);

    Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(NoopEventWriteService));
}

[Fact]
public void AddEventWriteServices_QueueNamePresentButConnectionStringMissing_RegistersNoopService()
{
    var services = new ServiceCollection();
    var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
    {
        ["GlobalSettings:Events:ConnectionString"] = null,
        ["GlobalSettings:Events:QueueName"] = "event"
    });

    services.AddEventWriteServices(globalSettings);

    Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) && s.ImplementationType == typeof(NoopEventWriteService));
}

}
}

public class AzureQueueEventSettings : IConnectionStringSettings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Note: Inconsistent Null Handling Pattern in Codebase

The new AzureQueueEventSettings class uses null-conditional operators (value?.Trim('"')) in both ConnectionString and QueueName setters (lines 406, 412), which is the safe approach.

However, most other settings classes in this file use value.Trim('"') without null checks (e.g., ConnectionStringSettings at line 423), which will throw NullReferenceException if value is null.

For this PR: The null-safe approach here is correct and aligns with the intentional design of allowing null QueueName to disable the service.

For future consideration: The codebase has an inconsistent pattern that could be standardized in a separate refactoring PR.

withinfocus
withinfocus previously approved these changes Dec 12, 2025
public string ConnectionString
{
get => _connectionString;
set => _connectionString = value?.Trim('"');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good null safety: The null-conditional operator value?.Trim('"') correctly handles null values. However, note that ConnectionStringSettings.ConnectionString (line 423, not changed in this PR) uses value.Trim('"') without the null-conditional operator, creating an inconsistency in the codebase.

While this isn't a blocker for this PR, consider filing a follow-up issue to address the null safety inconsistency in ConnectionStringSettings to maintain consistency across similar settings classes.


if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString) &&
CoreHelpers.SettingHasValue(globalSettings.Events.QueueName))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage: While the implementation correctly validates both ConnectionString and QueueName, there's no test case covering the scenario where ConnectionString is present but QueueName is missing (or vice versa).

Consider adding test cases like:

  • AddEventWriteServices_ConnectionStringPresentButQueueNameMissing_DoesNotRegisterAzureQueueService
  • AddEventWriteServices_QueueNamePresentButConnectionStringMissing_DoesNotRegisterAzureQueueService

This would ensure the validation logic works correctly and prevent regression if someone changes the && to || by mistake.

{
public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(globalSettings.Events.ConnectionString, "event"),
new QueueClient(globalSettings.Events.ConnectionString, globalSettings.Events.QueueName),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime exception if QueueName is null: While AddEventWriteServices now checks for null QueueName before registering this service, this constructor doesn't validate that globalSettings.Events.QueueName is non-null. If this service is ever instantiated directly (outside of DI), it could throw ArgumentNullException from the QueueClient constructor.

Consider adding a guard clause for defensive programming:

public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
    new QueueClient(
        globalSettings.Events.ConnectionString 
            ?? throw new ArgumentNullException(nameof(globalSettings), "Events.ConnectionString cannot be null"),
        globalSettings.Events.QueueName 
            ?? throw new ArgumentNullException(nameof(globalSettings), "Events.QueueName cannot be null")),
    JsonHelpers.IgnoreWritingNull)
{ }

This provides better error messages and fail-fast behavior if the service is misconfigured.

if (string.IsNullOrWhiteSpace(storageConnectionString) ||
string.IsNullOrWhiteSpace(queueName))
{
_logger.LogInformation("Azure Queue Hosted Service is disabled. Missing connection string or queue name.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good improvement: This log message is helpful for troubleshooting configuration issues. The service now gracefully handles missing configuration and logs appropriately.

Minor suggestion: Consider using structured logging with named parameters for better log aggregation and filtering:

_logger.LogInformation("Azure Queue Hosted Service is disabled. ConnectionString present: {HasConnectionString}, QueueName present: {HasQueueName}", 
    !string.IsNullOrWhiteSpace(storageConnectionString), 
    !string.IsNullOrWhiteSpace(queueName));

This would make it easier to diagnose which specific configuration value is missing in production environments.

"key": "<your Installation Key>"
},
"events": {
"connectionString": "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation needed: The example shows queueName: "event" as the default value, but it's not clear:

  1. Whether this queue name must match exactly in production environments
  2. What happens if this is left empty (service is disabled, which is intentional per PR author's comment)
  3. Any naming conventions or restrictions for queue names

Consider adding a comment above this section:

// Azure Queue Storage configuration for events
// Leave connectionString and queueName empty to disable the service in production
// Default queue name is "event" for local development
"events": {

This would help developers understand the configuration's purpose and behavior.

public virtual MailSettings Mail { get; set; } = new MailSettings();
public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings();
public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings();
public virtual AzureQueueEventSettings Events { get; set; } = new AzureQueueEventSettings();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backward compatibility concern: Changing from ConnectionStringSettings to AzureQueueEventSettings is a breaking change in the configuration schema. Existing appsettings.json files across the codebase only have:

"events": {
  "connectionString": "SECRET"
}

They don't have the new queueName field. While the code handles this gracefully (the service won't register if QueueName is null), this means:

  1. Existing deployments will break silently - The Azure Queue event write service won't start, and events won't be queued, but there will be no error - just a log message in AzureQueueHostedService.
  2. All appsettings files need updating - Every environment configuration file needs the queueName field added.

Recommendations:

  • Document this breaking change in the PR description
  • Provide migration instructions for updating all appsettings files
  • Consider whether a default value (e.g., "event") should be used when queueName is not specified for backward compatibility
  • Update all appsettings*.json files that currently have events.connectionString to also include queueName

@sonarqubecloud
Copy link

Base automatically changed from brant/refactor-event-integrations-service-collection-extensions to main December 12, 2025 21:17
@brant-livefront brant-livefront dismissed withinfocus’s stale review December 12, 2025 21:17

The base branch was changed.

@brant-livefront brant-livefront force-pushed the brant/add-config-for-queue-write branch from c85201b to aaf8d48 Compare December 12, 2025 21:25
@brant-livefront
Copy link
Contributor Author

Rebased and re-targeted to main now that the other PRs have all merged.

public string QueueName
{
get => _queueName;
set => _queueName = value?.Trim('"');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good null safety: The null-conditional operator value?.Trim('"') correctly allows null values, which is intentional for disabling the service in production (as noted by the PR author).

This is consistent with patterns used elsewhere in the codebase like AzureServiceBusSettings.ConnectionString at line 326.


if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString))
if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString) &&
CoreHelpers.SettingHasValue(globalSettings.Events.QueueName))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for partial configuration: The check validates both ConnectionString AND QueueName, but there are no tests covering scenarios where only one is present.

Recommended test cases:

[Fact]
public void AddEventWriteServices_ConnectionStringPresentButQueueNameMissing_RegistersNoopService()
{
    var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
    {
        ["GlobalSettings:Events:ConnectionString"] = "DefaultEndpoints...",
        ["GlobalSettings:Events:QueueName"] = null
    });
    
    services.AddEventWriteServices(globalSettings);
    
    Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) 
        && s.ImplementationType == typeof(NoopEventWriteService));
}

[Fact]
public void AddEventWriteServices_QueueNamePresentButConnectionStringMissing_RegistersNoopService()
{
    var globalSettings = CreateGlobalSettings(new Dictionary<string, string?>
    {
        ["GlobalSettings:Events:ConnectionString"] = null,
        ["GlobalSettings:Events:QueueName"] = "event"
    });
    
    services.AddEventWriteServices(globalSettings);
    
    Assert.Contains(services, s => s.ServiceType == typeof(IEventWriteService) 
        && s.ImplementationType == typeof(NoopEventWriteService));
}

This ensures the && logic works correctly and prevents regression.

{
public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(globalSettings.Events.ConnectionString, "event"),
new QueueClient(globalSettings.Events.ConnectionString, globalSettings.Events.QueueName),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Defensive programming suggestion: While the service collection registration validates both values, this constructor could still throw if instantiated directly (outside of DI).

Consider adding guard clauses for better error messages:

public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
    new QueueClient(
        globalSettings.Events.ConnectionString 
            ?? throw new ArgumentNullException(nameof(globalSettings), "Events.ConnectionString cannot be null"),
        globalSettings.Events.QueueName 
            ?? throw new ArgumentNullException(nameof(globalSettings), "Events.QueueName cannot be null")),
    JsonHelpers.IgnoreWritingNull)
{ }

This provides fail-fast behavior with clear error messages if misconfigured.

if (string.IsNullOrWhiteSpace(storageConnectionString) ||
string.IsNullOrWhiteSpace(queueName))
{
_logger.LogInformation("Azure Queue Hosted Service is disabled. Missing connection string or queue name.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Excellent error handling: The information-level log message properly addresses Finding #3 from the previous review. This makes troubleshooting much easier.

Minor enhancement suggestion (optional): Consider using structured logging to make this more queryable in production:

_logger.LogInformation(
    "Azure Queue Hosted Service is disabled. ConnectionString present: {HasConnectionString}, QueueName present: {HasQueueName}", 
    !string.IsNullOrWhiteSpace(storageConnectionString), 
    !string.IsNullOrWhiteSpace(queueName));

This would help operators quickly identify which specific configuration value is missing without inspecting code.

},
"events": {
"connectionString": "",
"queueName": "event"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Documentation suggestion: While the example shows "event" as the queue name, it's not clear to developers:

  • Whether this exact value is required in production
  • What happens if left empty (service is disabled)
  • Naming conventions or restrictions for queue names

Consider adding a comment:

// Azure Queue Storage configuration for events
// Leave connectionString and queueName empty to disable the service
// Default queue name "event" is used for local development
"events": {
  "connectionString": "",
  "queueName": "event"
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants