Dead delegated-admin and repository members go: status transition, single-row reads, MCP-user flag

None of them had a production caller. DelegatedAdminService.GetAssignmentsForUpnAsync and GetAssigneesForTenantAsync, DelegatedAdminService.SetStatusAsync with IAdminRepository.SetDelegatedAdminStatusAsync (only a test called it; the operator revoke is RemoveAsync), and IAdminRepository.GetDelegatedAdminAsync and IsMcpUserAsync, which only had their own implementation.

Revoke_InvalidatesCache now pins the revoke the operator route runs (RemoveAsync); Disable_InvalidatesCache covers SetEnabledAsync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-10-19 17:32:55 +02:00
parent 012eb8c057
commit 2a74395874
4 changed files with 17 additions and 85 deletions
@@ -249,13 +249,27 @@ public class DelegatedAdminServiceTests
[Fact]
public async Task Revoke_InvalidatesCache()
{
// RemoveAsync is the revoke the operator route runs (DelegatedAdminManagementFunction.RevokeCoreAsync).
var (svc, repo) = Build();
Returns(repo, Row(TenantA));
repo.Setup(r => r.SetDelegatedAdminStatusAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(true);
repo.Setup(r => r.RemoveDelegatedAdminAsync(It.IsAny<string>(), It.IsAny<string>())).ReturnsAsync(true);
await svc.GetScopeAsync(Id(Upn));
await svc.SetStatusAsync(Upn, TenantA, Constants.DelegatedStatus.Revoked);
await svc.RemoveAsync(Upn, TenantA);
await svc.GetScopeAsync(Id(Upn));
repo.Verify(r => r.GetDelegatedTenantsAsync(It.IsAny<string>()), Times.Exactly(2));
}
[Fact]
public async Task Disable_InvalidatesCache()
{
var (svc, repo) = Build();
Returns(repo, Row(TenantA));
repo.Setup(r => r.SetDelegatedAdminEnabledAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>())).ReturnsAsync(true);
await svc.GetScopeAsync(Id(Upn));
await svc.SetEnabledAsync(Upn, TenantA, false);
await svc.GetScopeAsync(Id(Upn));
repo.Verify(r => r.GetDelegatedTenantsAsync(It.IsAny<string>()), Times.Exactly(2));
@@ -152,29 +152,6 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
// --- MCP Users ---
public async Task<bool> IsMcpUserAsync(string upn)
{
if (string.IsNullOrWhiteSpace(upn))
return false;
try
{
var normalizedUpn = upn.ToLowerInvariant();
var entity = await _mcpUsersTableClient.GetEntityAsync<McpUserEntity>(
"McpUsers", normalizedUpn);
return entity?.Value != null && entity.Value.IsEnabled;
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking MCP user status for {Upn}", upn);
return false;
}
}
public async Task<McpUserEntry?> GetMcpUserAsync(string upn)
{
if (string.IsNullOrWhiteSpace(upn))
@@ -338,23 +315,6 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
return entries;
}
public async Task<DelegatedAdminEntry?> GetDelegatedAdminAsync(string upn, string tenantId)
{
if (string.IsNullOrWhiteSpace(upn) || string.IsNullOrWhiteSpace(tenantId))
return null;
try
{
var result = await _delegatedAdminsTableClient.GetEntityAsync<DelegatedAdminEntity>(
upn.ToLowerInvariant(), tenantId.ToLowerInvariant());
return result.Value == null ? null : MapToDelegatedEntry(result.Value);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
return null;
}
}
public async Task<bool> UpsertDelegatedAdminAsync(
string upn, string tenantId, string role, string status, string source, string grantedBy)
{
@@ -380,27 +340,6 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
return true;
}
public async Task<bool> SetDelegatedAdminStatusAsync(string upn, string tenantId, string status)
{
upn = upn.ToLowerInvariant();
tenantId = tenantId.ToLowerInvariant();
try
{
var result = await _delegatedAdminsTableClient.GetEntityAsync<DelegatedAdminEntity>(upn, tenantId);
var entity = result.Value;
if (entity == null) return false;
entity.Status = status;
await _delegatedAdminsTableClient.UpdateEntityAsync(entity, ETag.All);
return true;
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
return false;
}
}
public async Task<bool> SetDelegatedAdminEnabledAsync(string upn, string tenantId, bool isEnabled)
{
upn = upn.ToLowerInvariant();
@@ -231,16 +231,6 @@ public class DelegatedAdminService
};
}
/// <summary>Transitions an assignment's status (e.g. PendingApproval → Active on approval, → Revoked).</summary>
public async Task<bool> SetStatusAsync(string upn, string tenantId, string status)
{
upn = upn.ToLowerInvariant();
tenantId = tenantId.ToLowerInvariant();
var ok = await _adminRepo.SetDelegatedAdminStatusAsync(upn, tenantId, status);
Invalidate(upn);
return ok;
}
public async Task<bool> SetEnabledAsync(string upn, string tenantId, bool isEnabled)
{
upn = upn.ToLowerInvariant();
@@ -263,14 +253,6 @@ public class DelegatedAdminService
public Task<List<DelegatedAdminEntry>> GetAllAsync()
=> _adminRepo.GetAllDelegatedAdminsAsync();
/// <summary>All assignment rows for a UPN (any status) — for the operator/admin management UI.</summary>
public Task<List<DelegatedAdminEntry>> GetAssignmentsForUpnAsync(string upn)
=> _adminRepo.GetDelegatedTenantsAsync(upn.ToLowerInvariant());
/// <summary>All assignment rows targeting a tenant (any status) — for the customer "who manages me?" UI.</summary>
public Task<List<DelegatedAdminEntry>> GetAssigneesForTenantAsync(string tenantId)
=> _adminRepo.GetDelegatedAssigneesAsync(tenantId.ToLowerInvariant());
// --- Tenant Groups (app-internal tenant bundles) ---
//
// ALL group mutations go through the service (never the repo directly) so the delegated-scope
@@ -23,7 +23,6 @@ namespace AutopilotMonitor.Shared.DataAccess
Task<bool> DisableGlobalAdminAsync(string upn);
// --- MCP Users ---
Task<bool> IsMcpUserAsync(string upn);
Task<McpUserEntry?> GetMcpUserAsync(string upn);
Task<List<McpUserEntry>> GetAllMcpUsersAsync();
Task<bool> AddMcpUserAsync(string upn, string addedBy);
@@ -41,10 +40,8 @@ namespace AutopilotMonitor.Shared.DataAccess
/// <summary>All assignment rows targeting one tenant (cross-partition RowKey scan — admin UI, not hot path).
/// Powers the customer's "who manages my tenant?" view including pending requests.</summary>
Task<List<DelegatedAdminEntry>> GetDelegatedAssigneesAsync(string tenantId);
Task<DelegatedAdminEntry?> GetDelegatedAdminAsync(string upn, string tenantId);
/// <summary>Creates or replaces an assignment row (upsert on PK=upn, RK=tenantId).</summary>
Task<bool> UpsertDelegatedAdminAsync(string upn, string tenantId, string role, string status, string source, string grantedBy);
Task<bool> SetDelegatedAdminStatusAsync(string upn, string tenantId, string status);
Task<bool> SetDelegatedAdminEnabledAsync(string upn, string tenantId, bool isEnabled);
Task<bool> RemoveDelegatedAdminAsync(string upn, string tenantId);