Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -511,4 +511,76 @@ public async Task GetRogueCapabilitiesComplianceDetails_MatchesAggregateCounts()
Assert.Equal(aggregate.CompliantCount, details.CompliantCount);
Assert.Equal(aggregate.NonCompliantCount, details.NonCompliantCount);
}

[Fact]
public async Task GetRequirementsCompliance_ReturnsKnownRequirementsAndCounts()
{
var k8sCapId = CapabilityId.CreateFrom("k8s-cap");
var nonK8sCapId = CapabilityId.CreateFrom("non-k8s-cap");
var k8sCap = A.Capability.WithId(k8sCapId).WithJsonMetadata(AllTagsPresent).Build();
var nonK8sCap = A.Capability.WithId(nonK8sCapId).WithJsonMetadata(AllTagsPresent).Build();

var capabilityRepo = new Mock<ICapabilityRepository>();
capabilityRepo.Setup(r => r.GetAllActive()).ReturnsAsync(new[] { k8sCap, nonK8sCap });

var service = A
.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(k8sCapId))
.Build();

var result = await service.GetRequirementsCompliance();

Assert.Equal(5, result.Items.Count);

var tags = result.Items.Single(i => i.RequirementId == "tags");
Assert.Equal("Tags", tags.CategoryName);
Assert.Equal(2, tags.TotalCapabilities);

var externalSecrets = result.Items.Single(i => i.RequirementId == "external-secrets");
Assert.Equal("External Secrets", externalSecrets.CategoryName);
Assert.Equal(1, externalSecrets.TotalCapabilities);
}

[Fact]
public async Task GetRequirementComplianceDetails_Tags_ReturnsCapabilityLevelData()
{
var compliantCap = A
.Capability.WithId(CapabilityId.CreateFrom("compliant-cap"))
.WithJsonMetadata(AllTagsPresent)
.Build();
var nonCompliantCap = A
.Capability.WithId(CapabilityId.CreateFrom("non-compliant-cap"))
.WithJsonMetadata(EmptyMetadata)
.Build();

var capabilityRepo = new Mock<ICapabilityRepository>();
capabilityRepo.Setup(r => r.GetAllActive()).ReturnsAsync(new[] { compliantCap, nonCompliantCap });

var service = A.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object).Build();

var result = await service.GetRequirementComplianceDetails("tags");

Assert.Equal("tags", result.RequirementId);
Assert.Equal("Tags", result.CategoryName);
Assert.Equal(2, result.TotalCapabilities);
Assert.Equal(2, result.Capabilities.Count);
Assert.Contains(result.Capabilities, c => c.Status == ComplianceStatus.Compliant);
Assert.Contains(result.Capabilities, c => c.Status == ComplianceStatus.NonCompliant);
Assert.All(result.Capabilities, c => Assert.NotEmpty(c.Items));
}

[Fact]
public async Task GetRequirementComplianceDetails_UnknownRequirement_ThrowsKeyNotFoundException()
{
var capability = A.Capability.WithId(CapabilityId.CreateFrom("cap-1")).WithJsonMetadata(AllTagsPresent).Build();

var capabilityRepo = new Mock<ICapabilityRepository>();
capabilityRepo.Setup(r => r.GetAllActive()).ReturnsAsync(new[] { capability });

var service = A.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object).Build();

await Assert.ThrowsAsync<KeyNotFoundException>(
() => service.GetRequirementComplianceDetails("not-a-real-requirement")
);
}
}
117 changes: 117 additions & 0 deletions src/SelfService/Application/ComplianceApplicationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ public class ComplianceApplicationService : IComplianceApplicationService
"ECR pull policy",
};

private static readonly RequirementDefinition[] RequirementDefinitions =
{
new("tags", "Tags"),
new("external-secrets", "External Secrets"),
new("irsa-mutual-trust", "IRSA Mutual Trust"),
new("workload-liveness-and-readiness-probes", "Workload Liveness and Readiness Probes"),
new("ecr-pull-policy", "ECR pull policy"),
};

public ComplianceApplicationService(
ICapabilityRepository capabilityRepository,
IAwsAccountRepository awsAccountRepository,
Expand Down Expand Up @@ -105,6 +114,30 @@ public async Task<CostCentreComplianceDetailsResult> GetRogueCapabilitiesComplia
);
}

public async Task<RequirementsComplianceResult> GetRequirementsCompliance()
{
var allCapabilitiesDetails = await BuildComplianceDetailsForCapabilities("all", _ => true);

return new RequirementsComplianceResult
{
Items = RequirementDefinitions
.Select(definition => BuildRequirementSummary(definition, allCapabilitiesDetails.Capabilities))
.ToList(),
};
}

public async Task<RequirementComplianceDetailsResult> GetRequirementComplianceDetails(string requirementId)
{
var definition = FindRequirementDefinition(requirementId);
if (definition == null)
{
throw new KeyNotFoundException($"Unknown compliance requirement id '{requirementId}'.");
}

var allCapabilitiesDetails = await BuildComplianceDetailsForCapabilities("all", _ => true);
return BuildRequirementDetails(definition, allCapabilitiesDetails.Capabilities);
}

private async Task<CostCentreComplianceDetailsResult> BuildComplianceDetailsForCapabilities(
string costCentreLabel,
Func<Capability, bool> filter
Expand Down Expand Up @@ -198,6 +231,88 @@ private static CostCentreComplianceResult ToCostCentreComplianceResult(CostCentr
};
}

private static RequirementComplianceSummaryResult BuildRequirementSummary(
RequirementDefinition definition,
List<CostCentreCapabilityComplianceResult> capabilities
)
{
var categories = capabilities
.Select(capability =>
capability.Categories.FirstOrDefault(category => category.CategoryName == definition.CategoryName)
)
.Where(category => category != null)
.Select(category => category!)
.ToList();

var metadataSource = categories.FirstOrDefault();

return new RequirementComplianceSummaryResult
{
RequirementId = definition.RequirementId,
CategoryName = definition.CategoryName,
DisplayName = metadataSource?.DisplayName ?? definition.CategoryName,
Description = metadataSource?.Description,
HelpUrl = metadataSource?.HelpUrl,
TotalCapabilities = categories.Count,
CompliantCount = categories.Count(category => category.Status == ComplianceStatus.Compliant),
NonCompliantCount = categories.Count(category => category.Status == ComplianceStatus.NonCompliant),
UnknownCount = categories.Count(category => category.Status == ComplianceStatus.Unknown),
};
}

private static RequirementComplianceDetailsResult BuildRequirementDetails(
RequirementDefinition definition,
List<CostCentreCapabilityComplianceResult> capabilities
)
{
var capabilityCategories = capabilities
.Select(capability =>
{
var matchingCategory = capability.Categories.FirstOrDefault(category =>
category.CategoryName == definition.CategoryName
);
return new { capability, matchingCategory };
})
.Where(item => item.matchingCategory is not null)
.Select(item => new { item.capability, category = item.matchingCategory! })
.ToList();

var metadataSource = capabilityCategories.Select(item => item.category).FirstOrDefault();

return new RequirementComplianceDetailsResult
{
RequirementId = definition.RequirementId,
CategoryName = definition.CategoryName,
DisplayName = metadataSource?.DisplayName ?? definition.CategoryName,
Description = metadataSource?.Description,
HelpUrl = metadataSource?.HelpUrl,
TotalCapabilities = capabilityCategories.Count,
CompliantCount = capabilityCategories.Count(item => item.category.Status == ComplianceStatus.Compliant),
NonCompliantCount = capabilityCategories.Count(item =>
item.category.Status == ComplianceStatus.NonCompliant
),
UnknownCount = capabilityCategories.Count(item => item.category.Status == ComplianceStatus.Unknown),
Capabilities = capabilityCategories
.Select(item => new RequirementCapabilityComplianceResult
{
CapabilityId = item.capability.CapabilityId,
CapabilityName = item.capability.CapabilityName,
JsonMetadata = item.capability.JsonMetadata,
Status = item.category.Status,
Score = item.category.Score,
Items = item.category.Items,
})
.ToList(),
};
}

private static RequirementDefinition? FindRequirementDefinition(string requirementId)
{
return RequirementDefinitions.FirstOrDefault(definition =>
string.Equals(definition.RequirementId, requirementId, StringComparison.OrdinalIgnoreCase)
);
}

private static ComplianceCategoryResult CheckTagCompliance(string? jsonMetadata)
{
var evaluation = TagComplianceEvaluator.Evaluate(jsonMetadata);
Expand Down Expand Up @@ -477,4 +592,6 @@ private static ComplianceStatus DetermineOverallStatus(List<ComplianceCategoryRe
var jsonObject = JsonNode.Parse(jsonMetadata)?.AsObject();
return jsonObject?["dfds.cost.centre"]?.ToString();
}

private sealed record RequirementDefinition(string RequirementId, string CategoryName);
}
33 changes: 33 additions & 0 deletions src/SelfService/Application/ComplianceModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,36 @@ public class CostCentreCapabilityComplianceResult
public ComplianceStatus OverallStatus { get; set; }
public List<ComplianceCategoryResult> Categories { get; set; } = new();
}

public class RequirementsComplianceResult
{
public List<RequirementComplianceSummaryResult> Items { get; set; } = new();
}

public class RequirementComplianceSummaryResult
{
public string RequirementId { get; set; } = null!;
public string CategoryName { get; set; } = null!;
public string? DisplayName { get; set; }
public string? Description { get; set; }
public string? HelpUrl { get; set; }
public int TotalCapabilities { get; set; }
public int CompliantCount { get; set; }
public int NonCompliantCount { get; set; }
public int UnknownCount { get; set; }
}

public class RequirementComplianceDetailsResult : RequirementComplianceSummaryResult
{
public List<RequirementCapabilityComplianceResult> Capabilities { get; set; } = new();
}

public class RequirementCapabilityComplianceResult
{
public string CapabilityId { get; set; } = null!;
public string CapabilityName { get; set; } = null!;
public string? JsonMetadata { get; set; }
public ComplianceStatus Status { get; set; }
public double? Score { get; set; }
public List<ComplianceCategoryItem> Items { get; set; } = new();
}
2 changes: 2 additions & 0 deletions src/SelfService/Application/IComplianceApplicationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface IComplianceApplicationService
Task<CostCentreComplianceDetailsResult> GetCostCentreComplianceDetails(string costCentre);
Task<CostCentreComplianceResult> GetRogueCapabilitiesCompliance();
Task<CostCentreComplianceDetailsResult> GetRogueCapabilitiesComplianceDetails();
Task<RequirementsComplianceResult> GetRequirementsCompliance();
Task<RequirementComplianceDetailsResult> GetRequirementComplianceDetails(string requirementId);
}
Loading
Loading