Skip to content

Comments

Improve NTLM Collection Logging BED-7374#272

Open
lrfalslev wants to merge 8 commits intov4from
lfalslev/bed-7374
Open

Improve NTLM Collection Logging BED-7374#272
lrfalslev wants to merge 8 commits intov4from
lfalslev/bed-7374

Conversation

@lrfalslev
Copy link
Contributor

@lrfalslev lrfalslev commented Feb 20, 2026

Description

Add CompStatus Logging To Registry Processor
RunLog on Collection Failure
RegistryProcessor Unit Tests

Motivation and Context

BED-7374

How Has This Been Tested?

Unit Tests Pass

Validated in lab that:
Comp Status Logs on Success
image

RunLog and CompStatus Log on Failure
image
image

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Documentation updates are needed, and have been made accordingly.
  • I have added and/or updated tests to cover my changes.
  • All new and existing tests passed.
  • My changes include a database migration.

Summary by CodeRabbit

  • New Features

    • Registry operations now emit per-attempt status events to report progress and outcomes.
  • Bug Fixes

    • Added input validation to fail fast on invalid targets.
    • Improved port-scan early-exit handling with immediate success reporting when skipped.
  • Tests

    • Added comprehensive unit tests covering registry collection success, failure, and exception scenarios.
  • Chores

    • Removed unused imports and tidied namespace usage.

@lrfalslev lrfalslev self-assigned this Feb 20, 2026
@lrfalslev lrfalslev added the enhancement New feature or request label Feb 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

Walkthrough

Introduces an IStrategyExecutor interface and injects it into RegistryProcessor; RegistryProcessor now emits per-attempt CSVComputerStatus events. Adds input validation to DotNetWmiRegistryStrategy, an early-exit status report when port scanning is skipped, small import cleanups, and extensive unit tests for registry collection flows.

Changes

Cohort / File(s) Summary
Strategy Executor & Result
src/SharpHoundRPC/Registry/StrategyExecutor.cs, src/SharpHoundRPC/Registry/StrategyExecutorResult.cs
Added IStrategyExecutor interface; StrategyExecutor now implements it. StrategyExecutorResult<T> gains SuccessfulStrategy to report which strategy succeeded.
Registry Processor: injection & status events
src/CommonLib/Processors/RegistryProcessor.cs
RegistryProcessor constructor now accepts IStrategyExecutor; added ComputerStatusDelegate/ComputerStatusEvent and per-attempt CSVComputerStatus publishing with combined failure handling. Replaced local StrategyExecutor creation with injected executor.
Computer availability short-circuit
src/CommonLib/Processors/ComputerAvailability.cs
When port scan is skipped, method sends a CSVComputerStatus with success and immediately returns a connectable ComputerStatus (early-exit).
Registry strategies & small fixes
src/SharpHoundRPC/Registry/DotNetWmiRegistryStrategy.cs, src/SharpHoundRPC/Registry/RemoteRegistryStrategy.cs, src/SharpHoundRPC/Registry/NativeUtils.cs
Added null/empty target validation in DotNetWmiRegistryStrategy.CanExecute. Adjusted namespace import in RemoteRegistryStrategy. Removed unused usings in NativeUtils.
Tests & test helpers
test/unit/Facades/MockExtentions.cs, test/unit/RegistryProcessorTests.cs
Added VerifyNoLogs<T> mock extension. Added comprehensive RegistryProcessorTest suite covering per-attempt failures, multiple success scenarios, exception handling, and full-value reads; tests subscribe to ComputerStatusEvent to validate published statuses.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RegistryProcessor
    participant IStrategyExecutor
    participant ComputerStatusEvent

    Client->>RegistryProcessor: ReadRegistrySettings(targetMachine)
    RegistryProcessor->>IStrategyExecutor: CollectAsync(targetMachine, queries, strategies)
    
    loop per strategy attempt
        IStrategyExecutor->>IStrategyExecutor: Execute strategy attempt
        alt attempt fails
            IStrategyExecutor-->>RegistryProcessor: attempt failure details
            RegistryProcessor->>ComputerStatusEvent: Publish CSVComputerStatus (failure)
        else attempt succeeds
            IStrategyExecutor-->>RegistryProcessor: StrategyExecutorResult (WasSuccessful=true, SuccessfulStrategy)
        end
    end

    alt overall success
        RegistryProcessor->>ComputerStatusEvent: Publish CSVComputerStatus (success with strategy)
        RegistryProcessor->>Client: APIResult (Collected=true, data)
    else overall failure
        RegistryProcessor->>Client: APIResult (Collected=false, combined FailureReason)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mykeelium
  • definitelynotagoblin

Poem

🐰 I hopped through code with tiny paws,

Emitting statuses, obeying new laws.
Strategies tried, one hopped ahead,
Events were sent, no errors spread.
A carrot of tests — now rest, go to bed. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Improve NTLM Collection Logging BED-7374' clearly summarizes the main change: adding improved logging for NTLM collection with reference to the issue tracker.
Description check ✅ Passed The description covers all major template sections including motivation, testing approach with evidence, type of change selection, and relevant checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lfalslev/bed-7374

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/CommonLib/Processors/ComputerAvailability.cs (1)

98-110: LGTM – minor: missing _log.LogTrace for parity with the other success path.

Every other exit path in this method emits a trace log before or alongside SendComputerStatus. The normal (port-scan performed) success path at line 126 logs "{ComputerName} is available for enumeration" before its status send, but the new _skipPortScan success branch is silent.

🔍 Proposed trace log for consistency
         if (_skipPortScan) {
+            _log.LogTrace("{ComputerName} is available for enumeration (port scan skipped)", computerName);
             await SendComputerStatus(new CSVComputerStatus {
                 Status = CSVComputerStatus.StatusSuccess,
                 Task = "ComputerAvailability",
                 ComputerName = computerName,
                 ObjectId = objectId,
             });
         
             return new ComputerStatus {
                 Connectable = true,
                 Error = null
             };
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CommonLib/Processors/ComputerAvailability.cs` around lines 98 - 110, Add
a trace log to the _skipPortScan success branch to match the other success path:
call _log.LogTrace with a message like "{ComputerName} is available for
enumeration" (using the computerName variable) immediately before calling
SendComputerStatus in the block that constructs the CSVComputerStatus and
returns a ComputerStatus; this keeps logging parity with the other path that
logs before SendComputerStatus.
test/unit/Facades/MockExtentions.cs (1)

36-46: Inconsistent nullability annotation on the formatter parameter.

The existing VerifyLogContains and VerifyLog methods use Func<It.IsAnyType, Exception?, string> (nullable Exception?), but this new method uses Func<It.IsAnyType, Exception, string>. While nullable reference types are erased at runtime and this likely won't cause a Moq matching failure, it should be consistent with the other helpers for clarity.

Proposed fix
     public static void VerifyNoLogs<T>(this Mock<ILogger<T>> mockLogger, LogLevel logLevel)
     {
         mockLogger.Verify(
             x => x.Log(
                 logLevel,
                 It.IsAny<EventId>(),
                 It.IsAny<It.IsAnyType>(),
                 It.IsAny<Exception>(),
-                It.IsAny<Func<It.IsAnyType, Exception, string>>()),
+                It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
             Times.Never);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/Facades/MockExtentions.cs` around lines 36 - 46, The
VerifyNoLogs<T> helper uses a non-nullable formatter signature which is
inconsistent with VerifyLog and VerifyLogContains; update the formatter
parameter type in VerifyNoLogs<T> from Func<It.IsAnyType, Exception, string> to
Func<It.IsAnyType, Exception?, string> so the nullable Exception annotation
matches the other helper methods (locate the VerifyNoLogs<T> method and adjust
the formatter type accordingly).
test/unit/RegistryProcessorTests.cs (1)

152-170: Prefer ThrowsAsync over Throws for async method setup.

_mockStrategyExecutor.Setup(...).Throws(exception) throws synchronously before a Task is returned, which is atypical for async methods. Using .ThrowsAsync(exception) returns a faulted Task instead, more closely simulating real-world async failure behavior.

Proposed fix
             _mockStrategyExecutor.Setup(se => se.CollectAsync(
                     It.IsAny<string>(),
                     It.IsAny<IEnumerable<RegistryQuery>>(),
                     It.IsAny<IEnumerable<ICollectionStrategy<RegistryQueryResult, RegistryQuery>>>()))
-                .Throws(exception);
+                .ThrowsAsync(exception);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/RegistryProcessorTests.cs` around lines 152 - 170, The test
RegistryProcessor_ReadRegistrySettings_HandlesException is setting up the async
CollectAsync call with .Throws(exception) which triggers a synchronous throw;
update the mock setup for _mockStrategyExecutor.Setup(se =>
se.CollectAsync(...)) to use .ThrowsAsync(exception) so the setup returns a
faulted Task and better simulates async failure, keeping the rest of the
assertions (results and log verification) unchanged.
src/CommonLib/Processors/RegistryProcessor.cs (1)

146-148: Consider using the standard delegate-capture pattern for event invocation.

ComputerStatusEvent could theoretically become null between the null-check and Invoke if a handler is removed concurrently. The idiomatic C# pattern captures the delegate first:

Proposed fix
 private async Task SendComputerStatus(CSVComputerStatus status) {
-    if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
+    var handler = ComputerStatusEvent;
+    if (handler is not null) await handler.Invoke(status);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CommonLib/Processors/RegistryProcessor.cs` around lines 146 - 148, The
current SendComputerStatus method checks ComputerStatusEvent for null then
awaits ComputerStatusEvent.Invoke(status), which can race if handlers are
removed concurrently; fix it by capturing the event to a local variable (e.g.,
var handler = ComputerStatusEvent), check that local for null, and then await
handler.Invoke(status) so the captured delegate cannot become null between the
check and invocation; update the SendComputerStatus method to use this
delegate-capture pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/CommonLib/Processors/RegistryProcessor.cs`:
- Around line 92-97: The success message in RegistryProcessor (inside
ReadRegistrySettings) should not infer the successful strategy via
_strategies[collectedData.FailureAttempts?.Count() ?? 0]; add a
SuccessfulStrategyType property to StrategyExecutorResult<T>, set that property
to the strategy's GetType() on the success path inside
StrategyExecutor.CollectAsync when a strategy succeeds, and update
RegistryProcessor to use collectedData.SuccessfulStrategyType?.Name (or
fallback) for the Task string instead of indexing _strategies; this removes
fragile coupling to _strategies ordering and prevents IndexOutOfRangeException.

In `@test/unit/RegistryProcessorTests.cs`:
- Around line 164-165: The Assert.Equal call in RegistryProcessorTests.cs has
its arguments reversed: swap them so the expected value is first and the actual
is second; change the assertion to call Assert.Equal(exception.ToString(),
results.FailureReason) (leave Assert.False(results.Collected) as-is) so xUnit's
expected/actual diagnostics are correct.

---

Nitpick comments:
In `@src/CommonLib/Processors/ComputerAvailability.cs`:
- Around line 98-110: Add a trace log to the _skipPortScan success branch to
match the other success path: call _log.LogTrace with a message like
"{ComputerName} is available for enumeration" (using the computerName variable)
immediately before calling SendComputerStatus in the block that constructs the
CSVComputerStatus and returns a ComputerStatus; this keeps logging parity with
the other path that logs before SendComputerStatus.

In `@src/CommonLib/Processors/RegistryProcessor.cs`:
- Around line 146-148: The current SendComputerStatus method checks
ComputerStatusEvent for null then awaits ComputerStatusEvent.Invoke(status),
which can race if handlers are removed concurrently; fix it by capturing the
event to a local variable (e.g., var handler = ComputerStatusEvent), check that
local for null, and then await handler.Invoke(status) so the captured delegate
cannot become null between the check and invocation; update the
SendComputerStatus method to use this delegate-capture pattern.

In `@test/unit/Facades/MockExtentions.cs`:
- Around line 36-46: The VerifyNoLogs<T> helper uses a non-nullable formatter
signature which is inconsistent with VerifyLog and VerifyLogContains; update the
formatter parameter type in VerifyNoLogs<T> from Func<It.IsAnyType, Exception,
string> to Func<It.IsAnyType, Exception?, string> so the nullable Exception
annotation matches the other helper methods (locate the VerifyNoLogs<T> method
and adjust the formatter type accordingly).

In `@test/unit/RegistryProcessorTests.cs`:
- Around line 152-170: The test
RegistryProcessor_ReadRegistrySettings_HandlesException is setting up the async
CollectAsync call with .Throws(exception) which triggers a synchronous throw;
update the mock setup for _mockStrategyExecutor.Setup(se =>
se.CollectAsync(...)) to use .ThrowsAsync(exception) so the setup returns a
faulted Task and better simulates async failure, keeping the rest of the
assertions (results and log verification) unchanged.

Comment on lines 92 to 97
await SendComputerStatus(new CSVComputerStatus
{
Task = $"{nameof(ReadRegistrySettings)} - {_strategies[collectedData.FailureAttempts?.Count() ?? 0].GetType().Name}",
ComputerName = targetMachine,
Status = CSVComputerStatus.StatusSuccess
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine StrategyExecutorResult.cs
echo "=== Locating StrategyExecutorResult.cs ==="
fd "StrategyExecutorResult.cs" -type f

echo ""
echo "=== Locating StrategyExecutor.cs ==="
fd "StrategyExecutor.cs" -type f

echo ""
echo "=== Locating RegistryProcessor.cs ==="
fd "RegistryProcessor.cs" -type f

Repository: SpecterOps/SharpHoundCommon

Length of output: 720


🏁 Script executed:

#!/bin/bash
# Examine the StrategyExecutorResult.cs file
if [ -f "$(fd 'StrategyExecutorResult.cs' -type f | head -1)" ]; then
  cat "$(fd 'StrategyExecutorResult.cs' -type f | head -1)"
fi

Repository: SpecterOps/SharpHoundCommon

Length of output: 241


🏁 Script executed:

#!/bin/bash
# Examine StrategyExecutor.cs - look for success path and WasSuccessful assignments
echo "=== StrategyExecutor.cs - Full file (if manageable) ==="
fd "StrategyExecutor.cs" -type f -exec wc -l {} \;
fd "StrategyExecutor.cs" -type f -exec head -200 {} \;

echo ""
echo "=== Searching for WasSuccessful assignments ==="
rg -n "WasSuccessful\s*=" --type cs

Repository: SpecterOps/SharpHoundCommon

Length of output: 1154


🏁 Script executed:

#!/bin/bash
# Examine RegistryProcessor.cs around lines 92-97
echo "=== RegistryProcessor.cs lines 85-105 ==="
sed -n '85,105p' src/CommonLib/Processors/RegistryProcessor.cs

echo ""
echo "=== Search for _strategies field in RegistryProcessor ==="
rg -n "_strategies" src/CommonLib/Processors/RegistryProcessor.cs -A2 -B2

Repository: SpecterOps/SharpHoundCommon

Length of output: 2263


🏁 Script executed:

#!/bin/bash
# Examine StrategyExecutorResult.cs
echo "=== StrategyExecutorResult.cs ==="
cat src/SharpHoundRPC/Registry/StrategyExecutorResult.cs

Repository: SpecterOps/SharpHoundCommon

Length of output: 457


🏁 Script executed:

#!/bin/bash
# Examine StrategyExecutor.cs - look at the success path around line 38
echo "=== StrategyExecutor.cs context around WasSuccessful = true ==="
sed -n '30,50p' src/SharpHoundRPC/Registry/StrategyExecutor.cs

echo ""
echo "=== Full StrategyExecutor.cs (check file size) ==="
wc -l src/SharpHoundRPC/Registry/StrategyExecutor.cs

Repository: SpecterOps/SharpHoundCommon

Length of output: 982


🏁 Script executed:

#!/bin/bash
# Get more context from StrategyExecutor.cs - check the full file
cat src/SharpHoundRPC/Registry/StrategyExecutor.cs

Repository: SpecterOps/SharpHoundCommon

Length of output: 1991


🏁 Script executed:

#!/bin/bash
# Examine StrategyResult to see what info is available
echo "=== StrategyResult.cs or related ==="
fd "StrategyResult.cs" -type f

echo ""
echo "=== Search for StrategyResult definition and StrategyType ==="
rg -n "class StrategyResult|StrategyType" src/SharpHoundRPC/Registry/ -A3

Repository: SpecterOps/SharpHoundCommon

Length of output: 1705


🏁 Script executed:

#!/bin/bash
# Verify the _strategies array definition in RegistryProcessor to confirm ordering assumptions
echo "=== RegistryProcessor._strategies initialization ==="
sed -n '27,37p' src/CommonLib/Processors/RegistryProcessor.cs

Repository: SpecterOps/SharpHoundCommon

Length of output: 780


🏁 Script executed:

#!/bin/bash
# Check if there are any other usages of FailureAttempts?.Count() that might have similar issues
echo "=== Search for FailureAttempts usage ==="
rg "FailureAttempts" src/ -n -A2 -B2

Repository: SpecterOps/SharpHoundCommon

Length of output: 3475


Fragile strategy-name resolution via index arithmetic.

Line 94 infers the successful strategy by indexing _strategies[collectedData.FailureAttempts?.Count() ?? 0]. This silently couples RegistryProcessor to the internal iteration order of StrategyExecutor and the exact composition of _strategies. If StrategyExecutor ever skips a strategy without recording a failure attempt, reorders strategies, or if _strategies is modified independently, this will silently report the wrong strategy name—or throw IndexOutOfRangeException.

The successful strategy type is available in StrategyExecutor.CollectAsync at the point of success but is never captured in the result. Add a SuccessfulStrategyType property to StrategyExecutorResult<T> and set it on the success path, then use that instead of array indexing.

Proposed fix in StrategyExecutorResult

In StrategyExecutorResult.cs:

 public class StrategyExecutorResult<T> {
     public IEnumerable<T>? Results { get; set; } = null;
     public IEnumerable<StrategyResult<T>>? FailureAttempts { get; set; } = null;
     public bool WasSuccessful = false;
+    public Type? SuccessfulStrategyType { get; set; } = null;
 }

In StrategyExecutor.cs, set it on the success path:

 return new StrategyExecutorResult<T> {
     Results = results,
     FailureAttempts = attempts,
-    WasSuccessful = true
+    WasSuccessful = true,
+    SuccessfulStrategyType = strategy.GetType()
 };

Then in RegistryProcessor.cs:

 await SendComputerStatus(new CSVComputerStatus
 {
-    Task = $"{nameof(ReadRegistrySettings)} - {_strategies[collectedData.FailureAttempts?.Count() ?? 0].GetType().Name}",
+    Task = $"{nameof(ReadRegistrySettings)} - {collectedData.SuccessfulStrategyType?.Name ?? "Unknown"}",
     ComputerName = targetMachine,
     Status = CSVComputerStatus.StatusSuccess
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CommonLib/Processors/RegistryProcessor.cs` around lines 92 - 97, The
success message in RegistryProcessor (inside ReadRegistrySettings) should not
infer the successful strategy via
_strategies[collectedData.FailureAttempts?.Count() ?? 0]; add a
SuccessfulStrategyType property to StrategyExecutorResult<T>, set that property
to the strategy's GetType() on the success path inside
StrategyExecutor.CollectAsync when a strategy succeeds, and update
RegistryProcessor to use collectedData.SuccessfulStrategyType?.Name (or
fallback) for the Task string instead of indexing _strategies; this removes
fragile coupling to _strategies ordering and prevents IndexOutOfRangeException.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
test/unit/RegistryProcessorTests.cs (1)

152-156: Prefer .ThrowsAsync() over .Throws() for the async CollectAsync mock.

Moq documents ThrowsAsync as the correct counterpart to Throws for async methods — synchronous methods use .Throws(), async methods use .ThrowsAsync(). With .Throws(), the mock throws synchronously at the call site before a Task is ever returned. While this still gets caught by the try-catch in ReadRegistrySettings today, it doesn't accurately model the faulted-Task path that a real async implementation would produce.

♻️ Suggested fix
-                .Throws(exception);
+                .ThrowsAsync(exception);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/RegistryProcessorTests.cs` around lines 152 - 156, The test is
mocking an async method but uses Moq.Throws which throws synchronously; change
the mock setup on _mockStrategyExecutor for CollectAsync to use
.ThrowsAsync(exception) so the mock returns a faulted Task (rather than throwing
at call time) to more accurately simulate async failure; update the Setup call
that targets CollectAsync and replace .Throws(...) with .ThrowsAsync(...) while
keeping the same exception instance so ReadRegistrySettings exercises the
faulted-Task path.
src/SharpHoundRPC/Registry/StrategyExecutorResult.cs (1)

11-12: WasSuccessful is a public field while all other members are properties.

Since this class is being touched anyway to add SuccessfulStrategy, converting WasSuccessful to a property would make the class consistent.

♻️ Suggested refactor
-        public bool WasSuccessful = false;
+        public bool WasSuccessful { get; set; } = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SharpHoundRPC/Registry/StrategyExecutorResult.cs` around lines 11 - 12,
Convert the public field WasSuccessful in class StrategyExecutorResult to a
public auto-property (public bool WasSuccessful { get; set; } = false;) so it
matches the rest of the class style (like SuccessfulStrategy) and preserves the
default value; update any references that access the field if necessary to use
the property name (WasSuccessful) — leave SuccessfulStrategy as is.
src/SharpHoundRPC/Registry/StrategyExecutor.cs (1)

7-13: Consider moving IStrategyExecutor to its own file.

Conventionally, interfaces live in dedicated files (e.g., IStrategyExecutor.cs). Co-locating it with StrategyExecutor.cs makes it harder to discover and violates the single-responsibility principle for file organisation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SharpHoundRPC/Registry/StrategyExecutor.cs` around lines 7 - 13, Move the
IStrategyExecutor interface out of StrategyExecutor.cs into its own file named
IStrategyExecutor.cs: create a new file containing the IStrategyExecutor
definition (preserving the existing namespace and the generic CollectAsync<T,
TQuery> signature and return type Task<StrategyExecutorResult<T>>), remove the
interface from StrategyExecutor.cs so that only the StrategyExecutor class
remains there, and ensure any using directives and project file entries (if
needed) are updated so the compiler still resolves IStrategyExecutor correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/SharpHoundRPC/Registry/StrategyExecutor.cs`:
- Around line 7-13: Move the IStrategyExecutor interface out of
StrategyExecutor.cs into its own file named IStrategyExecutor.cs: create a new
file containing the IStrategyExecutor definition (preserving the existing
namespace and the generic CollectAsync<T, TQuery> signature and return type
Task<StrategyExecutorResult<T>>), remove the interface from StrategyExecutor.cs
so that only the StrategyExecutor class remains there, and ensure any using
directives and project file entries (if needed) are updated so the compiler
still resolves IStrategyExecutor correctly.

In `@src/SharpHoundRPC/Registry/StrategyExecutorResult.cs`:
- Around line 11-12: Convert the public field WasSuccessful in class
StrategyExecutorResult to a public auto-property (public bool WasSuccessful {
get; set; } = false;) so it matches the rest of the class style (like
SuccessfulStrategy) and preserves the default value; update any references that
access the field if necessary to use the property name (WasSuccessful) — leave
SuccessfulStrategy as is.

In `@test/unit/RegistryProcessorTests.cs`:
- Around line 152-156: The test is mocking an async method but uses Moq.Throws
which throws synchronously; change the mock setup on _mockStrategyExecutor for
CollectAsync to use .ThrowsAsync(exception) so the mock returns a faulted Task
(rather than throwing at call time) to more accurately simulate async failure;
update the Setup call that targets CollectAsync and replace .Throws(...) with
.ThrowsAsync(...) while keeping the same exception instance so
ReadRegistrySettings exercises the faulted-Task path.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant