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:
Joseph Doherty
2026-06-03 12:34:34 -04:00
parent c6d9b20d9f
commit bd6c0b4d3d
481 changed files with 2550 additions and 1668 deletions
@@ -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"]);