using FluentAssertions; using YamlDotNet.RepresentationModel; using Xunit; namespace BluejayInfraLint.Tests; [Trait("Category", "Unit")] public sealed class FcDesktopCapacityPolicyTests { private static readonly ManifestInventory Inventory = ManifestInventory.Load(); [Fact] public void FcDesktop_AppDirectoryMustExist() { Directory.Exists(Path.Combine(Inventory.BluejayRoot, "apps", "fc-desktop")) .Should() .BeTrue(); } [Fact] public void FcDesktop_MustHaveExactlyOneResourceQuota() { FcDesktopDocuments() .Where(document => document.Kind == "ResourceQuota") .Should() .ContainSingle(); } [Fact] public void FcDesktop_ResourceQuotaMustAdoptLiveSessionCapObject() { var quota = ResourceQuota(); quota.RelativePath.Should().Be("fc-desktop/resourcequota.yaml"); quota.Name.Should().Be("fc-desktop-session-cap"); quota.Namespace.Should().Be("fc-desktop"); } [Theory] [InlineData("count/pods", "15")] [InlineData("requests.cpu", "8")] [InlineData("requests.memory", "16Gi")] [InlineData("requests.storage", "500Gi")] [InlineData("persistentvolumeclaims", "30")] public void FcDesktop_ResourceQuotaMustDeclarePhaseOneHardLimits(string key, string value) { ResourceQuota().Scalar("spec", "hard", key).Should().Be(value); } [Fact] public void FcDesktop_ResourceQuotaMustCarryTraceableLabels() { ResourceQuotaLabels() .Should() .Contain(new Dictionary { ["app.kubernetes.io/name"] = "fc-desktop", ["app.kubernetes.io/part-of"] = "remotedesktop", ["app.kubernetes.io/component"] = "capacity-guard", ["app.kubernetes.io/managed-by"] = "argocd", ["flowercore.io/owner"] = "infra", }); } [Fact] public void FcDesktop_ResourceQuotaMustUseRequestsKeysForComputeCap() { var hardKeys = HardLimitKeys(ResourceQuota()); hardKeys.Should().Contain(new[] { "requests.cpu", "requests.memory" }); hardKeys.Should().NotContain(new[] { "cpu", "memory" }); } [Fact] public void FcDesktop_ResourceQuotaMustAvoidDestructiveArgoAnnotations() { var quota = ResourceQuota(); quota.Scalar("metadata", "annotations", "argocd.argoproj.io/hook").Should().BeNull(); quota.Scalar("metadata", "annotations", "argocd.argoproj.io/hook-delete-policy").Should().BeNull(); var syncOptions = quota.Scalar("metadata", "annotations", "argocd.argoproj.io/sync-options") ?? string.Empty; syncOptions.Should().NotContain("Force=true"); syncOptions.Should().NotContain("Replace=true"); } [Fact] public void FcDesktop_ResourceQuotaMustRecordPhaseAInfraOnlyScope() { ResourceQuota().Scalar("metadata", "annotations", "flowercore.io/phase") .Should() .Be("sprint-44-cx-9-phase-a"); } [Fact] public void FcDesktop_MustHaveExactlyOneLimitRange() { FcDesktopDocuments() .Where(document => document.Kind == "LimitRange") .Should() .ContainSingle(); } [Fact] public void FcDesktop_LimitRangeMustLiveBesideResourceQuota() { var limitRange = LimitRange(); limitRange.RelativePath.Should().Be("fc-desktop/limitrange.yaml"); limitRange.Name.Should().Be("fc-desktop-pod-defaults"); limitRange.Namespace.Should().Be("fc-desktop"); } [Fact] public void FcDesktop_LimitRangeMustHaveSingleContainerRule() { var limit = LimitRangeRule(); LimitRange().MappingSequence("spec", "limits").Should().ContainSingle(); ManifestNodeExtensions.Scalar(limit, "type").Should().Be("Container"); } [Theory] [InlineData("default", "cpu", "1.0")] [InlineData("default", "memory", "2Gi")] [InlineData("defaultRequest", "cpu", "500m")] [InlineData("defaultRequest", "memory", "1Gi")] [InlineData("max", "cpu", "2.0")] [InlineData("max", "memory", "4Gi")] [InlineData("min", "cpu", "100m")] [InlineData("min", "memory", "128Mi")] public void FcDesktop_LimitRangeMustDeclarePerPodShape(string section, string key, string value) { ManifestNodeExtensions.Scalar(LimitRangeRule(), section, key).Should().Be(value); } [Fact] public void FcDesktop_LimitRangeMustCarryTraceableLabels() { LimitRangeLabels() .Should() .Contain(new Dictionary { ["app.kubernetes.io/name"] = "fc-desktop", ["app.kubernetes.io/part-of"] = "remotedesktop", ["app.kubernetes.io/component"] = "capacity-guard", ["app.kubernetes.io/managed-by"] = "argocd", ["flowercore.io/owner"] = "infra", }); } [Fact] public void FcDesktop_LimitRangeMustAvoidDestructiveArgoAnnotations() { var limitRange = LimitRange(); limitRange.Scalar("metadata", "annotations", "argocd.argoproj.io/hook").Should().BeNull(); limitRange.Scalar("metadata", "annotations", "argocd.argoproj.io/hook-delete-policy").Should().BeNull(); var syncOptions = limitRange.Scalar("metadata", "annotations", "argocd.argoproj.io/sync-options") ?? string.Empty; syncOptions.Should().NotContain("Force=true"); syncOptions.Should().NotContain("Replace=true"); } [Fact] public void FcDesktop_LimitRangeMustRecordPhaseAInfraOnlyScope() { LimitRange().Scalar("metadata", "annotations", "flowercore.io/phase") .Should() .Be("sprint-44-cx-9-phase-a"); } [Fact] public void FcDesktop_BluejayInfraMustNotOwnDeploymentOrService() { FcDesktopDocuments() .Select(document => document.Kind) .Should() .NotContain(new[] { "Deployment", "Service" }); } [Fact] public void FcDesktop_BluejayInfraMustOnlyOwnInfraResourceKinds() { var allowedKinds = new HashSet(StringComparer.Ordinal) { "Certificate", "IngressRoute", "NetworkPolicy", "ResourceQuota", "LimitRange", }; FcDesktopDocuments() .Select(document => document.Kind) .Should() .OnlyContain(kind => allowedKinds.Contains(kind)); } [Fact] public void FcDesktop_NetworkPolicySetMustRemainPresent() { FcDesktopDocuments() .Where(document => document.Kind == "NetworkPolicy") .Select(document => document.Name) .Should() .BeEquivalentTo( "desktop-isolation", "fc-desktop-default-deny", "remotedesktop-web-isolation", "cm-acme-http-solver-allow"); } [Fact] public void FcDesktop_TlsIngressMustRemainOwnedByInfra() { FcDesktopDocuments() .Should() .Contain(document => document.Kind == "Certificate" && document.Name == "remotedesktop-web-tls") .And .Contain(document => document.Kind == "IngressRoute" && document.Name == "remotedesktop-web"); } private static IReadOnlyList FcDesktopDocuments() { return Inventory.Documents .Where(document => document.RelativePath.StartsWith("fc-desktop/", StringComparison.Ordinal)) .ToList(); } private static ManifestDocument ResourceQuota() { return FcDesktopDocuments() .Single(document => document.Kind == "ResourceQuota"); } private static ManifestDocument LimitRange() { return FcDesktopDocuments() .Single(document => document.Kind == "LimitRange"); } private static YamlMappingNode LimitRangeRule() { return LimitRange() .MappingSequence("spec", "limits") .Single(); } private static IReadOnlySet HardLimitKeys(ManifestDocument document) { var hard = ManifestNodeExtensions.Mapping(document.Root, "spec", "hard") ?? throw new InvalidOperationException($"{document.Descriptor} is missing spec.hard."); return hard.Children.Keys .OfType() .Select(key => key.Value) .Where(value => !string.IsNullOrWhiteSpace(value)) .Cast() .ToHashSet(StringComparer.Ordinal); } private static IReadOnlyDictionary ResourceQuotaLabels() { return Labels(ResourceQuota()); } private static IReadOnlyDictionary LimitRangeLabels() { return Labels(LimitRange()); } private static IReadOnlyDictionary Labels(ManifestDocument document) { var labels = ManifestNodeExtensions.Mapping(document.Root, "metadata", "labels") ?? throw new InvalidOperationException($"{document.Descriptor} is missing metadata.labels."); return labels.Children .Where(entry => entry.Key is YamlScalarNode && entry.Value is YamlScalarNode) .ToDictionary( entry => ((YamlScalarNode)entry.Key).Value ?? string.Empty, entry => ((YamlScalarNode)entry.Value).Value ?? string.Empty, StringComparer.Ordinal); } }