docs: complete XML doc comments via fixdocs (2757 to 131 findings)
Add missing <returns>/<param>/<summary>/<typeparam> tags and clean up misused inheritdoc across 481 files so the documented API surface is complete. Documentation-only (zero code lines changed). The 131 remaining findings are inheritdoc-style warnings deliberately left to preserve hand-written implementation rationale (plan-decision notes, race-condition explanations).
This commit is contained in:
@@ -76,6 +76,7 @@ public sealed class AuditActorTests
|
||||
|
||||
private sealed class StubAccessor(string? value) : IAuditActorAccessor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string? CurrentActor { get; } = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ public sealed class HttpAuditActorAccessorTests
|
||||
|
||||
private sealed class HttpContextAccessorStub(HttpContext? context) : IHttpContextAccessor
|
||||
{
|
||||
/// <summary>Gets or sets the HTTP context for the stub.</summary>
|
||||
public HttpContext? HttpContext { get; set; } = context;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
private static CancellationToken Ct => TestContext.Current.CancellationToken;
|
||||
|
||||
/// <summary>Initializes the test host and server.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
var dbName = $"auth-int-tests-{Guid.NewGuid():N}";
|
||||
@@ -106,6 +107,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Disposes the test host and server.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _host.StopAsync(TestContext.Current.CancellationToken);
|
||||
@@ -122,6 +124,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
};
|
||||
|
||||
/// <summary>Tests that login with valid credentials returns 204 and sets cookie.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Login_with_valid_credentials_returns_204_and_sets_cookie()
|
||||
{
|
||||
@@ -134,6 +137,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Tests that login with invalid credentials returns 401.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Login_with_invalid_credentials_returns_401()
|
||||
{
|
||||
@@ -145,6 +149,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Tests that login when LDAP throws returns 503.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Login_when_ldap_throws_returns_503()
|
||||
{
|
||||
@@ -156,6 +161,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Tests that ping anonymous returns 401.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Ping_anonymous_returns_401()
|
||||
{
|
||||
@@ -166,6 +172,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Tests that ping after cookie login returns 200.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Ping_after_cookie_login_returns_200()
|
||||
{
|
||||
@@ -181,6 +188,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Tests that token after cookie login returns jwt.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Token_after_cookie_login_returns_jwt()
|
||||
{
|
||||
@@ -202,6 +210,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
|
||||
/// <summary>A system-wide DB row for a group the user holds grants an extra role on top of
|
||||
/// the appsettings baseline; the merged role surfaces in the issued JWT's Role claims.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Login_merges_db_role_grant_into_claims()
|
||||
{
|
||||
@@ -238,6 +247,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
/// still SUCCEEDS but the user is granted ZERO role claims. They are authenticated (can prove
|
||||
/// identity) yet authorized for nothing role-gated until the mapper recovers — the safe
|
||||
/// fail-closed behaviour, not a fail-open with a stale role set.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Login_when_role_mapper_throws_signs_in_with_no_role_claims()
|
||||
{
|
||||
@@ -307,6 +317,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
/// Also asserts that the old short-name literals "Username" and "DisplayName" are NOT emitted
|
||||
/// (the pre-Task-1.5 strings that would indicate the migration was incomplete).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Login_emits_canonical_ZbClaimTypes_on_cookie_principal()
|
||||
{
|
||||
@@ -367,6 +378,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
/// <see cref="JwtTokenService.RoleClaimType"/> docs for the rationale and the caveat that
|
||||
/// applies if a JwtBearer scheme is ever added).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Token_payload_uses_canonical_zb_claim_keys()
|
||||
{
|
||||
@@ -420,6 +432,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Tests that logout clears the cookie.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Logout_clears_the_cookie()
|
||||
{
|
||||
@@ -439,6 +452,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <summary>Anonymous browser GET of a protected route redirects to /login with a ReturnUrl.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Root_anonymous_browser_GET_redirects_to_login()
|
||||
{
|
||||
@@ -457,6 +471,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
/// via the <c>X-Requested-With</c> header — the ASP.NET cookie handler's IsAjaxRequest
|
||||
/// heuristic). The framework still writes a <c>Location</c> header alongside the 401;
|
||||
/// AJAX clients ignore it.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Root_anonymous_xhr_GET_returns_401()
|
||||
{
|
||||
@@ -479,11 +494,7 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
|
||||
private sealed class StubLdapAuthService : ILdapAuthService
|
||||
{
|
||||
/// <summary>Authenticates a user asynchronously using the stub service.</summary>
|
||||
/// <param name="username">The username to authenticate.</param>
|
||||
/// <param name="password">The password to verify.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The authentication result.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default)
|
||||
{
|
||||
if (username == "ldap-down")
|
||||
@@ -517,10 +528,12 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
/// </summary>
|
||||
private sealed class StubLdapGroupRoleMappingService : ILdapGroupRoleMappingService
|
||||
{
|
||||
/// <summary>Gets the seeded group-to-role mapping rows available for lookup.</summary>
|
||||
public List<LdapGroupRoleMapping> Rows { get; } = [];
|
||||
/// <summary>Gets or sets a value indicating whether the service should simulate a fault.</summary>
|
||||
public bool Throws { get; set; }
|
||||
|
||||
/// <summary>Returns seeded rows whose group matches one of <paramref name="ldapGroups"/>.</summary>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<LdapGroupRoleMapping>> GetByGroupsAsync(
|
||||
IEnumerable<string> ldapGroups, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -531,15 +544,15 @@ public sealed class AuthEndpointsIntegrationTests : IAsyncLifetime
|
||||
return Task.FromResult(matched);
|
||||
}
|
||||
|
||||
/// <summary>Not exercised by these tests.</summary>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<LdapGroupRoleMapping>> ListAllAsync(CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
/// <summary>Not exercised by these tests.</summary>
|
||||
/// <inheritdoc />
|
||||
public Task<LdapGroupRoleMapping> CreateAsync(LdapGroupRoleMapping row, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
/// <summary>Not exercised by these tests.</summary>
|
||||
/// <inheritdoc />
|
||||
public Task DeleteAsync(Guid id, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ public sealed class CanonicalAdminRolesTests
|
||||
{
|
||||
// --- (a) the mapper mints the CANONICAL role claim for each native group ----------------
|
||||
|
||||
/// <summary>Verifies that the mapper yields the canonical role claim for each native LDAP group.</summary>
|
||||
/// <param name="canonicalRole">The canonical role name to test.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Theory]
|
||||
[InlineData("Viewer")] // was ConfigViewer
|
||||
[InlineData("Designer")] // was ConfigEditor
|
||||
@@ -42,6 +45,10 @@ public sealed class CanonicalAdminRolesTests
|
||||
result.Roles.ShouldContain(canonicalRole);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a system-wide DB row role renders as the canonical role string.</summary>
|
||||
/// <param name="role">The admin role enum value to test.</param>
|
||||
/// <param name="expected">The expected canonical role string.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Theory]
|
||||
[InlineData(AdminRole.Viewer, "Viewer")]
|
||||
[InlineData(AdminRole.Designer, "Designer")]
|
||||
@@ -61,6 +68,8 @@ public sealed class CanonicalAdminRolesTests
|
||||
|
||||
// --- (b)/(c) the REAL registered authorization policies enforce on the canonical values ---
|
||||
|
||||
/// <summary>Verifies that the Deployments role check authorizes Designer and Administrator roles.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Deployments_role_check_authorizes_Designer_and_Administrator()
|
||||
{
|
||||
@@ -76,6 +85,8 @@ public sealed class CanonicalAdminRolesTests
|
||||
(await authz.AuthorizeAsync(UserInRole("Viewer"), policy)).Succeeded.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the FleetAdmin policy authorizes only the Administrator role.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task FleetAdmin_policy_authorizes_only_Administrator()
|
||||
{
|
||||
@@ -88,6 +99,8 @@ public sealed class CanonicalAdminRolesTests
|
||||
(await authz.AuthorizeAsync(UserInRole("Viewer"), "FleetAdmin")).Succeeded.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the DriverOperator policy authorizes Operator and Administrator roles.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DriverOperator_policy_authorizes_Operator_and_Administrator()
|
||||
{
|
||||
@@ -139,16 +152,31 @@ public sealed class CanonicalAdminRolesTests
|
||||
|
||||
private sealed class FakeMappingService(IReadOnlyList<LdapGroupRoleMapping> rows) : ILdapGroupRoleMappingService
|
||||
{
|
||||
/// <summary>Returns all seeded rows that belong to any of the specified LDAP groups.</summary>
|
||||
/// <param name="ldapGroups">The LDAP groups to look up.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that resolves to the matching role mappings.</returns>
|
||||
public Task<IReadOnlyList<LdapGroupRoleMapping>> GetByGroupsAsync(
|
||||
IEnumerable<string> ldapGroups, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(rows);
|
||||
|
||||
/// <summary>Returns all seeded role mapping rows.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that resolves to all role mappings.</returns>
|
||||
public Task<IReadOnlyList<LdapGroupRoleMapping>> ListAllAsync(CancellationToken cancellationToken)
|
||||
=> Task.FromResult(rows);
|
||||
|
||||
/// <summary>Not supported in this stub.</summary>
|
||||
/// <param name="row">The row to create.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Never returns; always throws.</returns>
|
||||
public Task<LdapGroupRoleMapping> CreateAsync(LdapGroupRoleMapping row, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary>Not supported in this stub.</summary>
|
||||
/// <param name="id">The identifier of the row to delete.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Never returns; always throws.</returns>
|
||||
public Task DeleteAsync(Guid id, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ public sealed class OtOpcUaGroupRoleMapperTests
|
||||
return new OtOpcUaGroupRoleMapper(options, new FakeMappingService(dbRows));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the mapper maps a configured group and drops unmapped groups.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Maps_config_group_and_drops_unmapped_group()
|
||||
{
|
||||
@@ -39,6 +41,8 @@ public sealed class OtOpcUaGroupRoleMapperTests
|
||||
result.Scope.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a system-wide DB row adds a role on top of the config baseline.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task System_wide_db_row_adds_role_on_top_of_config_baseline()
|
||||
{
|
||||
@@ -53,6 +57,8 @@ public sealed class OtOpcUaGroupRoleMapperTests
|
||||
result.Scope.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cluster-scoped DB row is ignored by the mapper.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Cluster_scoped_db_row_is_ignored()
|
||||
{
|
||||
@@ -72,6 +78,8 @@ public sealed class OtOpcUaGroupRoleMapperTests
|
||||
result.Roles.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the mapper output matches the expected RoleMapper.Map + Merge result for representative inputs.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Reproduces_RoleMapper_Map_plus_Merge_for_representative_inputs()
|
||||
{
|
||||
@@ -102,16 +110,31 @@ public sealed class OtOpcUaGroupRoleMapperTests
|
||||
/// <summary>In-memory stand-in for the EF-backed DB service; returns the configured rows verbatim.</summary>
|
||||
private sealed class FakeMappingService(IReadOnlyList<LdapGroupRoleMapping> rows) : ILdapGroupRoleMappingService
|
||||
{
|
||||
/// <summary>Returns all seeded rows that belong to any of the specified LDAP groups.</summary>
|
||||
/// <param name="ldapGroups">The LDAP groups to look up.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that resolves to the matching role mappings.</returns>
|
||||
public Task<IReadOnlyList<LdapGroupRoleMapping>> GetByGroupsAsync(
|
||||
IEnumerable<string> ldapGroups, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(rows);
|
||||
|
||||
/// <summary>Returns all seeded role mapping rows.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that resolves to all role mappings.</returns>
|
||||
public Task<IReadOnlyList<LdapGroupRoleMapping>> ListAllAsync(CancellationToken cancellationToken)
|
||||
=> Task.FromResult(rows);
|
||||
|
||||
/// <summary>Not supported in this stub.</summary>
|
||||
/// <param name="row">The row to create.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Never returns; always throws.</returns>
|
||||
public Task<LdapGroupRoleMapping> CreateAsync(LdapGroupRoleMapping row, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary>Not supported in this stub.</summary>
|
||||
/// <param name="id">The identifier of the row to delete.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Never returns; always throws.</returns>
|
||||
public Task DeleteAsync(Guid id, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
new(options, inner, NullLogger<OtOpcUaLdapAuthService>.Instance);
|
||||
|
||||
/// <summary>DevStubMode on → stub Administrator success WITHOUT hitting the library.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DevStubMode_grants_Administrator_without_calling_the_library()
|
||||
{
|
||||
@@ -38,6 +39,7 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
}
|
||||
|
||||
/// <summary>Enabled=false → denial, no library call (master switch wins over DevStubMode).</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Disabled_denies_without_calling_the_library_even_with_devstub()
|
||||
{
|
||||
@@ -53,6 +55,7 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
|
||||
/// <summary>Real path: a library success surfaces its Groups; Roles are left empty for the
|
||||
/// downstream mapper (the library returns groups, not roles).</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Real_path_success_surfaces_groups_and_leaves_roles_for_the_mapper()
|
||||
{
|
||||
@@ -73,6 +76,7 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
}
|
||||
|
||||
/// <summary>Real path: a library failure folds into a fail-closed error string.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Real_path_failure_folds_into_error()
|
||||
{
|
||||
@@ -90,6 +94,7 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
|
||||
/// <summary>Insecure transport without AllowInsecure fails closed at the auth boundary WITHOUT
|
||||
/// reaching the library — preserving the bespoke service's login-time guard after UseTls→Transport.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Insecure_transport_without_AllowInsecure_fails_closed_without_calling_library()
|
||||
{
|
||||
@@ -107,6 +112,9 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
}
|
||||
|
||||
/// <summary>Empty username/password are rejected up front without a library call.</summary>
|
||||
/// <param name="user">The username to test.</param>
|
||||
/// <param name="pw">The password to test.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Theory]
|
||||
[InlineData("", "pw")]
|
||||
[InlineData("user", "")]
|
||||
@@ -124,8 +132,14 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
/// <summary>Records whether the library service was invoked and returns a canned result.</summary>
|
||||
private sealed class RecordingLibService(LibLdapAuthResult result) : LibILdapAuthService
|
||||
{
|
||||
/// <summary>Gets a value indicating whether the library service was called.</summary>
|
||||
public bool Called { get; private set; }
|
||||
|
||||
/// <summary>Authenticates the user, records that the call was made, and returns the canned result.</summary>
|
||||
/// <param name="username">The username to authenticate.</param>
|
||||
/// <param name="password">The password to authenticate.</param>
|
||||
/// <param name="ct">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that resolves to the canned <see cref="LibLdapAuthResult"/>.</returns>
|
||||
public Task<LibLdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct)
|
||||
{
|
||||
Called = true;
|
||||
|
||||
@@ -62,6 +62,7 @@ public sealed class RoleMapperTests
|
||||
roles.ShouldBe(new[] { "Administrator" });
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Merge unions baseline roles with system-wide DB roles.</summary>
|
||||
[Fact]
|
||||
public void Merge_unions_baseline_and_systemwide_db_roles()
|
||||
{
|
||||
@@ -76,6 +77,7 @@ public sealed class RoleMapperTests
|
||||
result.ShouldNotContain("Designer"); // cluster-scoped row ignored (global-only)
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Merge with no DB rows returns the baseline roles unchanged.</summary>
|
||||
[Fact]
|
||||
public void Merge_with_no_db_rows_returns_baseline()
|
||||
=> RoleMapper.Merge(["Administrator"], []).ShouldBe(["Administrator"]);
|
||||
|
||||
Reference in New Issue
Block a user