Skip to content

Fix/20260608 exceptions messages - #27

Merged
bitai-cs merged 2 commits into
mainfrom
fix/20260608-exceptions-messages
Jun 10, 2026
Merged

Fix/20260608 exceptions messages#27
bitai-cs merged 2 commits into
mainfrom
fix/20260608-exceptions-messages

Conversation

@bitai-cs

@bitai-cs bitai-cs commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Fix library messages.

Summary by Sourcery

Improve LDAP helper error handling and messaging for search and account management operations, while introducing clearer validation and duplicate checks.

Bug Fixes:

  • Return consistent, user-friendly messages when LDAP searches find no entries, including parent-entry searches and group membership evaluation.
  • Ensure account operations correctly surface 'not found' and validation errors instead of generic or misleading failures.

Enhancements:

  • Refine Searcher to catch Novell LdapException directly and use clearer, prioritized error messages for search failures.
  • Add a reusable protected helper in Searcher for mapping LDAP attribute sets into DTO entries.
  • Introduce a DataValidationException type and use it across account management flows for invalid input data.
  • Update account management APIs to accept flexible identifier attributes (DN or sAMAccountName) for password set, disable, and remove operations, and to verify uniqueness before user creation.
  • Improve wrapping and propagation of LDAP exceptions during account verification for more accurate diagnostics.
  • Adjust demo and tests to align with the new APIs and updated operation messages.

bitai-cs added 2 commits June 8, 2026 22:31
Renamed AccountManager MSAD methods for clarity and flexibility, now accepting explicit identifier attributes. Enhanced LDAP exception handling and made DataValidationException public. Refactored Searcher for extensibility and updated tests. Bumped version to 10.1.2.
@sourcery-ai

sourcery-ai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR refines LDAP helper exception messages and error handling, introduces a DataValidationException type, refactors some APIs to be identifier-based (DN or sAMAccountName), and updates tests and demos to match the new behavior and messages.

Sequence diagram for SetMsADUserAccountPassword with refined error handling

sequenceDiagram
    actor Client
    participant AccountManager
    participant Searcher
    participant LDAPServer
    participant Authenticator

    Client->>AccountManager: SetMsADUserAccountPassword(identifierAttribute, identifierValue, password, requestLabel, postUpdateTestAuthentication)
    AccountManager->>AccountManager: verifyMsADEntryAccountAuthenticity(identifierAttribute, identifierValue, true, requestLabel)
    AccountManager->>Searcher: SearchEntriesAsync(searchFilterCombiner, RequiredEntryAttributes.Few, requestLabel)
    Searcher->>LDAPServer: SearchAsync(SearchLimits, searchFilter, attributesToLoad, false)
    LDAPServer-->>Searcher: searchQueue
    loop each LDAP message
        Searcher->>Searcher: GetEntryFromAttributeSet(attributeSet, requiredEntryAttributes, requestLabel)
    end
    Searcher-->>AccountManager: LDAPSearchResult

    alt [searchResult unsuccessful]
        Searcher-->>AccountManager: throw LdapException or Exception
    else [no entries]
        Searcher-->>AccountManager: throw EntryNotFoundException
    else [entry not user]
        AccountManager-->>AccountManager: throw DataValidationException
    end

    alt [entry resolved]
        AccountManager->>LDAPServer: ModifyEntryAsync(entry.distinguishedName, modifications)
        alt [postUpdateTestAuthentication]
            AccountManager->>Authenticator: AuthenticateAsync(LDAPDistinguishedNameCredential, requestLabel)
            Authenticator-->>AccountManager: LDAPDistinguishedNameAuthenticationResult
        end
        AccountManager-->>Client: LDAPPasswordUpdateResult (success)
    else [EntryNotFoundException]
        AccountManager-->>Client: LDAPPasswordUpdateResult("User account not found.")
    else [DataValidationException]
        AccountManager-->>Client: LDAPPasswordUpdateResult("Invalid data found.")
    else [other Exception]
        AccountManager-->>Client: LDAPPasswordUpdateResult("Unexpected error while attempting to replace password.")
    end
Loading

File-Level Changes

Change Details Files
Refactor Searcher search pipeline to catch Novell LdapException directly, standardize messages, and expose entry-building as a protected method.
  • Replaced the private getSearchResultAsync helper with inlined logic inside SearchEntriesAsync, making the method async and operating directly on the ICombinableFilter searchFilterObject.
  • Changed exception handling in SearchEntriesAsync and SearchParentEntriesAsync to catch Novell.Directory.Ldap.LdapException explicitly and build combined messages using Message and LdapErrorMessage, plus a generic "Unexpected error encountered while performing search." message for unknown exceptions.
  • Introduced a protected async GetEntryFromAttributeSet method (moved from private getEntryFromAttributeSet) to construct LDAPEntry instances and recursively resolve memberOf group entries, enabling reuse and potential overrides.
  • Adjusted SearchParentEntriesAsync to throw EntryNotFoundException when no entries are found instead of returning an unsuccessful result, and mapped that to a user-friendly "Nonexistent entry." message in the outer catch block.
src/Bitai.LDAPHelper/Searcher.cs
Harden AccountManager operations with stronger validation, duplicate checks, identifier-based APIs, and clearer, user-focused messages.
  • In CreateUserAccountForMsAD, replaced generic InvalidOperationException validations with DataValidationException, added pre-create existence checks by DN and sAMAccountName using verifyMsADEntryAccountAuthenticity, and throw DuplicateNameException with clear messages when duplicates are found.
  • Renamed and redesigned SetUserAccountPasswordForMsAD to SetMsADUserAccountPassword, changing the signature to take an EntryAttribute identifierAttribute and identifierValue plus the new password, using verifyMsADEntryAccountAuthenticity for lookup, and returning clearer messages for EntryNotFoundException ("User account not found."), DataValidationException ("Invalid data found."), and generic failures ("Unexpected error while attempting to replace password.").
  • Renamed DisableUserAccountForMsAD to DisableMsADUserAccount and RemoveUserAccountForMsAD to RemoveMsADUserAccount, updating them to accept identifierAttribute and identifierValue instead of raw distinguishedName, enforcing that only sAMAccountName or distinguishedName are valid identifiers, and improving error messages (including templated identifier info in failure messages).
  • Replaced verifyUserAccountAuthenticity with a more generic verifyMsADEntryAccountAuthenticity that accepts an identifierAttribute and value, enforces supported identifier types, propagates search errors by rethrowing LdapException with enhanced messages when present, throws EntryNotFoundException when no entry is found, and uses DataValidationException instead of InvalidOperationException when the entry is not of objectClass "user".
src/Bitai.LDAPHelper/AccountManager.cs
Align Authenticator result types and construction with non-namespaced DTO aliases and slightly clearer result initialization.
  • Updated AuthenticateAsync overloads to use LDAPDomainAccountAuthenticationResult and LDAPDistinguishedNameAuthenticationResult without the DTO. prefix, matching the rest of the codebase.
  • Adjusted error/result construction to use object initializers where appropriate (e.g., setting OperationMessage on LDAPDomainAccountAuthenticationResult) while preserving existing behavior and messages.
  • Kept overall authentication logic intact but ensured exceptions result in failure messages like "Failed to authenticate ..." with cloned credentials.
src/Bitai.LDAPHelper/Authenticator.cs
Update demos and tests to use the new AccountManager API and to assert on the new standardized messages.
  • Adjusted demo methods to call SetMsADUserAccountPassword, DisableMsADUserAccount, and RemoveMsADUserAccount using EntryAttribute.distinguishedName instead of the previous DN-only methods and LDAPDistinguishedNameCredential.
  • Updated AccountManagerAdapterTests to use the new method names and signatures and to check for refined messages, e.g., expecting OperationMessage to start with "Unable to create" for validation failures and "User account not found" for missing accounts.
  • Modified SearcherAdapterTests and GroupMembershipValidatorTests to reflect new behaviors, such as SearchParentEntriesAsync returning a non-successful result with a "Nonexistent entry." message and GetAllGroupMembershipsAsync throwing EntryNotFoundException with message starting "Unable to evaluate without an entry".
  • Ensured message comparisons use StartsWith/Contains with StringComparison.OrdinalIgnoreCase where appropriate to avoid brittle casing dependencies.
demo/Bitai.LDAPHelper.Demo/Program.DemoMethods.cs
tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs
tests/Bitai.LDAPHelper.Tests/SearcherAdapterTests.cs
tests/Bitai.LDAPHelper.Tests/GroupMembershipValidatorTests.cs
Introduce DataValidationException for clearer distinction of validation errors from other failures.
  • Added a new serializable DataValidationException class deriving from System.Exception with standard constructors (parameterless, message, message+innerException).
  • Replaced various InvalidOperationException/ArgumentNullException usages in AccountManager where the error is logically a data validation issue (e.g., missing required user attributes, invalid identifierAttribute, non-user object class) with DataValidationException or with more precise ArgumentException where appropriate.
  • Updated error handling in operations to catch DataValidationException separately and return operation results with clear messages such as "Unable to create user account." and "Invalid data found." while preserving the original exception as the error object.
src/Bitai.LDAPHelper/DataValidationException.cs
src/Bitai.LDAPHelper/AccountManager.cs
Minor cleanup and namespace/using adjustments.
  • Added Novell.Directory.Ldap using directives where LdapException is now referenced directly (Searcher, AccountManager).
  • Reordered using directives and closed regions/blocks in BaseHelper.cs to keep formatting consistent and include required namespaces like Bitai.LDAPHelper.Extensions.
  • Made small comment and formatting tweaks (e.g., clarifying why generic Exception-based LdapException handling is commented out) without altering behavior.
src/Bitai.LDAPHelper/Searcher.cs
src/Bitai.LDAPHelper/AccountManager.cs
src/Bitai.LDAPHelper/BaseHelper.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 5 issues, and left some high level feedback:

  • In CreateUserAccountForMsAD, the pre‑existence checks for DN and sAMAccountName use broad catch (Exception) blocks that silently swallow all failures from verifyMsADEntryAccountAuthenticity; consider distinguishing the "not found" case from other errors so unexpected LDAP or connectivity issues don’t get ignored and misreported as duplicates.
  • The new pre‑create duplicate checks for DN/sAMAccountName introduce a race window between the check and AddEntryAsync; you may want to rely primarily on the directory’s own uniqueness constraint (e.g., by handling the LDAP duplicate error explicitly) or document/adjust the logic so it doesn’t assume the pre‑check guarantees uniqueness.
  • The new identifier‑based methods and validation (SetMsADUserAccountPassword, DisableMsADUserAccount, RemoveMsADUserAccount, verifyMsADEntryAccountAuthenticity) mix ArgumentException, ArgumentNullException and DataValidationException with slightly different message styles; aligning on a single exception type and consistent messages for invalid caller input vs domain validation would make error handling more predictable for consumers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CreateUserAccountForMsAD`, the pre‑existence checks for DN and sAMAccountName use broad `catch (Exception)` blocks that silently swallow all failures from `verifyMsADEntryAccountAuthenticity`; consider distinguishing the "not found" case from other errors so unexpected LDAP or connectivity issues don’t get ignored and misreported as duplicates.
- The new pre‑create duplicate checks for DN/sAMAccountName introduce a race window between the check and `AddEntryAsync`; you may want to rely primarily on the directory’s own uniqueness constraint (e.g., by handling the LDAP duplicate error explicitly) or document/adjust the logic so it doesn’t assume the pre‑check guarantees uniqueness.
- The new identifier‑based methods and validation (`SetMsADUserAccountPassword`, `DisableMsADUserAccount`, `RemoveMsADUserAccount`, `verifyMsADEntryAccountAuthenticity`) mix `ArgumentException`, `ArgumentNullException` and `DataValidationException` with slightly different message styles; aligning on a single exception type and consistent messages for invalid caller input vs domain validation would make error handling more predictable for consumers.

## Individual Comments

### Comment 1
<location path="src/Bitai.LDAPHelper/AccountManager.cs" line_range="66" />
<code_context>
+                //Generate username DistinguishedName LDAP attribute
+                InitializeMissingMsADUserAccountDN(newUserAccount);
+
+                LDAPEntry checkUserAccount = null;
+                // Check whether an entry with the same distinguished name already exists in the directory before creating a new user. Since the distinguished name must be unique, a duplicate entry cannot be created. If one already exists, return an error or ask for a different name.
+                try
</code_context>
<issue_to_address>
**issue (bug_risk):** The duplicate-entry pre-checks swallow all exceptions from the verification method, potentially masking real directory errors.

Both duplicate checks wrap `verifyMsADEntryAccountAuthenticity` in a broad `try/catch (Exception)` and then only use `checkUserAccount != null` in `finally`. Any error (e.g. connectivity, LDAP, permission) is treated as “entry not found”, allowing the create to proceed and hiding real directory failures. Catch only the expected `EntryNotFoundException` (and optionally `DataValidationException` if invalid entries should be treated as missing) and let other exceptions propagate or be handled explicitly so genuine errors aren’t suppressed.
</issue_to_address>

### Comment 2
<location path="tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs" line_range="70" />
<code_context>

             Assert.False(result.IsSuccessfulOperation);
-            Assert.Contains("error creating user", result.OperationMessage.ToLower());
+            Assert.StartsWith("unable to create", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
         }

</code_context>
<issue_to_address>
**suggestion (testing):** Also assert that the error is surfaced as a DataValidationException when required attributes are missing.

Since this path now uses a custom `DataValidationException` for missing required fields, please extend the test to assert that `result.ErrorObject` is a `DataValidationException` and, optionally, that its message mentions the missing property (e.g., `DistinguishedNameOfContainer is required`). This will help prevent regressions and keep validation failures distinguishable from other error types.

Suggested implementation:

```csharp
            var result = await accountManager.CreateUserAccountForMsAD(newUser, "TestCreate");

            Assert.False(result.IsSuccessfulOperation);

            var validationException = Assert.IsType<DataValidationException>(result.ErrorObject);
            Assert.Contains("DistinguishedNameOfContainer", validationException.Message);

            Assert.StartsWith("unable to create", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
        }

```

1. Ensure the correct namespace for `DataValidationException` is imported at the top of `AccountManagerAdapterTests.cs`, for example:
   `using Bitai.LDAPHelper.Exceptions;` (or the actual namespace where `DataValidationException` is defined).
2. If a different property name is required in the validation message (instead of `DistinguishedNameOfContainer`), update the `Assert.Contains` string accordingly to match the real exception message.
</issue_to_address>

### Comment 3
<location path="tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs" line_range="99" />
<code_context>
-            var userCredential = new LDAPDistinguishedNameCredential(mockUserEntry.DistinguishedName, "NewP@ssw0rd");
-
-            var result = await accountManager.SetUserAccountPasswordForMsAD(userCredential, "TestPassword", postUpdateTestAuthentication: true);
+            var result = await accountManager.SetMsADUserAccountPassword(EntryAttribute.distinguishedName, mockUserEntry.DistinguishedName, "TestPassword", postUpdateTestAuthentication: true);

             // Assert
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests covering the sAMAccountName-based path of SetMsADUserAccountPassword/DisableMsADUserAccount/RemoveMsADUserAccount.

The updated APIs support both `EntryAttribute.distinguishedName` and `EntryAttribute.sAMAccountName`, but the tests only cover the DN path. Please add tests that:
- Call `SetMsADUserAccountPassword(EntryAttribute.sAMAccountName, ...)` and verify password update (and successful authentication when `postUpdateTestAuthentication` is true).
- Call `DisableMsADUserAccount(EntryAttribute.sAMAccountName, ...)` and verify the account is disabled.
- Call `RemoveMsADUserAccount(EntryAttribute.sAMAccountName, ...)` and verify the account is removed.
This will exercise the sAMAccountName path and confirm both identifier types behave consistently.

Suggested implementation:

```csharp
            Assert.False(result.IsSuccessfulOperation);
            Assert.StartsWith("user account not found", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
        }

        [Fact]
        public async Task SetMsADUserAccountPassword_UsingSamAccountName_UpdatesPassword_AndAuthenticates()
        {
            // Arrange
            var mockConnection = new MockLdapConnectionAdapter();
            var accountManager = CreateAccountManagerAdapter(mockConnection);

            var mockUserEntry = CreateMockUserEntry();
            var userSearchFilter = BuildUserSearchFilter(mockUserEntry);

            mockConnection.AddSearchResult(
                userSearchFilter.ToString(),
                new List<MockLdapEntryAdapter> { mockUserEntry });

            // Act
            var result = await accountManager.SetMsADUserAccountPassword(
                EntryAttribute.sAMAccountName,
                mockUserEntry.SamAccountName,
                "NewP@ssw0rd",
                postUpdateTestAuthentication: true);

            // Assert
            Assert.True(result.IsSuccessfulOperation);
        }

        [Fact]
        public async Task DisableMsADUserAccount_UsingSamAccountName_DisablesAccount()
        {
            // Arrange
            var mockConnection = new MockLdapConnectionAdapter();
            var accountManager = CreateAccountManagerAdapter(mockConnection);

            var mockUserEntry = CreateMockUserEntry();
            var userSearchFilter = BuildUserSearchFilter(mockUserEntry);

            mockConnection.AddSearchResult(
                userSearchFilter.ToString(),
                new List<MockLdapEntryAdapter> { mockUserEntry });

            // Act
            var result = await accountManager.DisableMsADUserAccount(
                EntryAttribute.sAMAccountName,
                mockUserEntry.SamAccountName);

            // Assert
            Assert.True(result.IsSuccessfulOperation);
        }

        [Fact]
        public async Task RemoveMsADUserAccount_UsingSamAccountName_RemovesAccount()
        {
            // Arrange
            var mockConnection = new MockLdapConnectionAdapter();
            var accountManager = CreateAccountManagerAdapter(mockConnection);

            var mockUserEntry = CreateMockUserEntry();
            var userSearchFilter = BuildUserSearchFilter(mockUserEntry);

            mockConnection.AddSearchResult(
                userSearchFilter.ToString(),
                new List<MockLdapEntryAdapter> { mockUserEntry });

            // Act
            var result = await accountManager.RemoveMsADUserAccount(
                EntryAttribute.sAMAccountName,
                mockUserEntry.SamAccountName);

            // Assert
            Assert.True(result.IsSuccessfulOperation);
        }

```

Because only a small part of the file is visible, you will likely need to:

1. **Align helper usage**  
   - If `CreateAccountManagerAdapter`, `CreateMockUserEntry`, or `BuildUserSearchFilter` do not exist, replace those calls in the new tests with the same explicit arrange code used in the existing DN-based tests (e.g., direct construction of `MockLdapConnectionAdapter`, `AccountManagerAdapter`, user entries, and filters).
   - If you already have common factory/helper methods for the DN tests, reuse those instead of creating duplicate logic.

2. **Confirm property names**  
   - Ensure the user object exposes the correct sAMAccountName property. If your mock entry uses a different name (e.g., `SAMAccountName`, `sAMAccountName`, or another property), update `mockUserEntry.SamAccountName` in all three tests accordingly.

3. **Ensure `EntryAttribute.sAMAccountName` enum value exists**  
   - If the enum member is named slightly differently (e.g., `EntryAttribute.sAMAccountName` vs `EntryAttribute.SamAccountName`), adjust the new tests to use the exact member name.

4. **Optional stronger assertions**  
   - If your existing DN-based tests assert more than just `IsSuccessfulOperation` (for example, verifying disable flags or delete/search absence), mirror those assertions in the new sAMAccountName tests so behavior is validated consistently across identifier types.
</issue_to_address>

### Comment 4
<location path="tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs" line_range="137" />
<code_context>

             Assert.False(result.IsSuccessfulOperation);
-            Assert.Contains("does not exist", result.OperationMessage.ToLower());
+            Assert.StartsWith("user account not found", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
         }

</code_context>
<issue_to_address>
**suggestion (testing):** Consider asserting the specific ErrorObject type (EntryNotFoundException) in not-found scenarios.

In these not-found tests, you now validate the updated operation message. To better lock in the behavior, also assert that `result.ErrorObject` is an `EntryNotFoundException`, so callers can reliably distinguish missing accounts from other failures and ensure the exception-to-result mapping remains correct.

Suggested implementation:

```csharp
            Assert.False(result.IsSuccessfulOperation);
            Assert.StartsWith("user account not found", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
            Assert.IsType<EntryNotFoundException>(result.ErrorObject);
        }

```

If `EntryNotFoundException` is not already in scope in this test file, add the appropriate `using` directive at the top of `AccountManagerAdapterTests.cs`, for example:
- `using Bitai.LDAPHelper.Exceptions;`
or the correct namespace where `EntryNotFoundException` is defined.
</issue_to_address>

### Comment 5
<location path="tests/Bitai.LDAPHelper.Tests/SearcherAdapterTests.cs" line_range="114-116" />
<code_context>
             var result = await accountManager.CreateUserAccountForMsAD(newUser, "TestCreate");

             Assert.False(result.IsSuccessfulOperation);
-            Assert.Contains("error creating user", result.OperationMessage.ToLower());
+            Assert.StartsWith("unable to create", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
</code_context>
<issue_to_address>
**suggestion (testing):** Also assert that SearchParentEntriesAsync surfaces an EntryNotFoundException in ErrorObject for the empty case.

Since `SearchParentEntriesAsync` wraps `EntryNotFoundException` into `LDAPSearchResult`, please also assert that `result.HasErrorObject` is `true` and that `result.ErrorObject` is an `EntryNotFoundException`. This will exercise the error mapping and protect callers that rely on `ErrorObject` to detect this condition.

Suggested implementation:

```csharp
            var result = await searcher.SearchParentEntriesAsync(expectedDummiestUserSearchFilter, RequiredEntryAttributes.Minimun, "TestRequest");

            Assert.False(result.IsSuccessfulOperation);
            Assert.Null(result.Entries);
            Assert.StartsWith("nonexistent entry", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
            Assert.True(result.HasErrorObject);
            Assert.IsType<EntryNotFoundException>(result.ErrorObject);

```

If `EntryNotFoundException` is not in the current namespace, add the appropriate `using` (for example, `using Bitai.LDAPHelper;`) or fully qualify the type (for example, `Assert.IsType<Bitai.LDAPHelper.EntryNotFoundException>(result.ErrorObject);`) to match where `EntryNotFoundException` is actually defined in your codebase.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/Bitai.LDAPHelper/AccountManager.cs

Assert.False(result.IsSuccessfulOperation);
Assert.Contains("error creating user", result.OperationMessage.ToLower());
Assert.StartsWith("unable to create", result.OperationMessage, StringComparison.OrdinalIgnoreCase);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Also assert that the error is surfaced as a DataValidationException when required attributes are missing.

Since this path now uses a custom DataValidationException for missing required fields, please extend the test to assert that result.ErrorObject is a DataValidationException and, optionally, that its message mentions the missing property (e.g., DistinguishedNameOfContainer is required). This will help prevent regressions and keep validation failures distinguishable from other error types.

Suggested implementation:

            var result = await accountManager.CreateUserAccountForMsAD(newUser, "TestCreate");

            Assert.False(result.IsSuccessfulOperation);

            var validationException = Assert.IsType<DataValidationException>(result.ErrorObject);
            Assert.Contains("DistinguishedNameOfContainer", validationException.Message);

            Assert.StartsWith("unable to create", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
        }
  1. Ensure the correct namespace for DataValidationException is imported at the top of AccountManagerAdapterTests.cs, for example:
    using Bitai.LDAPHelper.Exceptions; (or the actual namespace where DataValidationException is defined).
  2. If a different property name is required in the validation message (instead of DistinguishedNameOfContainer), update the Assert.Contains string accordingly to match the real exception message.

Comment thread tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs

Assert.False(result.IsSuccessfulOperation);
Assert.Contains("does not exist", result.OperationMessage.ToLower());
Assert.StartsWith("user account not found", result.OperationMessage, StringComparison.OrdinalIgnoreCase);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Consider asserting the specific ErrorObject type (EntryNotFoundException) in not-found scenarios.

In these not-found tests, you now validate the updated operation message. To better lock in the behavior, also assert that result.ErrorObject is an EntryNotFoundException, so callers can reliably distinguish missing accounts from other failures and ensure the exception-to-result mapping remains correct.

Suggested implementation:

            Assert.False(result.IsSuccessfulOperation);
            Assert.StartsWith("user account not found", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
            Assert.IsType<EntryNotFoundException>(result.ErrorObject);
        }

If EntryNotFoundException is not already in scope in this test file, add the appropriate using directive at the top of AccountManagerAdapterTests.cs, for example:

  • using Bitai.LDAPHelper.Exceptions;
    or the correct namespace where EntryNotFoundException is defined.

Comment on lines 114 to +116
Assert.False(result.IsSuccessfulOperation);
Assert.Empty(result.Entries);
Assert.Contains("no one entry was found", result.OperationMessage.ToLower());
Assert.Null(result.Entries);
Assert.StartsWith("nonexistent entry", result.OperationMessage, StringComparison.OrdinalIgnoreCase);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Also assert that SearchParentEntriesAsync surfaces an EntryNotFoundException in ErrorObject for the empty case.

Since SearchParentEntriesAsync wraps EntryNotFoundException into LDAPSearchResult, please also assert that result.HasErrorObject is true and that result.ErrorObject is an EntryNotFoundException. This will exercise the error mapping and protect callers that rely on ErrorObject to detect this condition.

Suggested implementation:

            var result = await searcher.SearchParentEntriesAsync(expectedDummiestUserSearchFilter, RequiredEntryAttributes.Minimun, "TestRequest");

            Assert.False(result.IsSuccessfulOperation);
            Assert.Null(result.Entries);
            Assert.StartsWith("nonexistent entry", result.OperationMessage, StringComparison.OrdinalIgnoreCase);
            Assert.True(result.HasErrorObject);
            Assert.IsType<EntryNotFoundException>(result.ErrorObject);

If EntryNotFoundException is not in the current namespace, add the appropriate using (for example, using Bitai.LDAPHelper;) or fully qualify the type (for example, Assert.IsType<Bitai.LDAPHelper.EntryNotFoundException>(result.ErrorObject);) to match where EntryNotFoundException is actually defined in your codebase.

@bitai-cs
bitai-cs merged commit 21c4d65 into main Jun 10, 2026
3 checks passed
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.

1 participant