using EntKube.Clusters.Features.AdoptCluster; using EntKube.Clusters.Features.AdoptCluster.Components.Gitea; using EntKube.Clusters.Infrastructure.Cleura; using FluentAssertions; using k8s.Models; using Moq; namespace EntKube.Clusters.Tests.Features; public class GiteaComponentTests { [Fact] public void GiteaInstaller_HasCorrectComponentName() { // The installer's ComponentName must match the check's ComponentName // so the coordinator can pair them correctly. GiteaInstaller installer = new( new Microsoft.Extensions.Logging.Abstractions.NullLogger(), new CleuraS3Client(Mock.Of()), Mock.Of()); installer.ComponentName.Should().Be("gitea"); } [Fact] public void GiteaCheck_HasCorrectComponentName() { // The check's ComponentName identifies which component is being evaluated // during cluster adoption scans. GiteaCheck check = new(new Microsoft.Extensions.Logging.Abstractions.NullLogger()); check.ComponentName.Should().Be("gitea"); } [Fact] public void GiteaCheck_HasDisplayName() { // The display name is shown in the UI so users can identify the component. GiteaCheck check = new(new Microsoft.Extensions.Logging.Abstractions.NullLogger()); check.DisplayName.Should().NotBeNullOrWhiteSpace(); check.DisplayName.Should().Contain("Gitea"); } [Fact] public void GiteaSchema_IsRegistered() { // The configuration schema must be registered so the UI can render // typed configuration forms for the Gitea component. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.ComponentName.Should().Be("gitea"); schema.Parameters.Should().NotBeEmpty(); } [Fact] public void GiteaSchema_ContainsReplicasParameter() { // The replicas parameter controls how many Gitea pods are deployed. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "replicas"); } [Fact] public void GiteaSchema_ContainsActionsParameter() { // Gitea Actions (CI/CD) is a key feature that can be toggled. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "actionsEnabled"); } [Fact] public void GiteaSchema_ContainsStorageParameter() { // Repository persistence size is configurable for capacity planning. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "persistenceSize"); } [Fact] public void GiteaSchema_ContainsSshParameter() { // SSH access can be enabled/disabled depending on security requirements. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "sshEnabled"); } [Fact] public void GiteaInstaller_GetSchema_ReturnsGiteaSchema() { // The installer's default GetSchema() implementation should delegate // to ComponentSchemas and return the gitea schema. IComponentInstaller installer = new GiteaInstaller( new Microsoft.Extensions.Logging.Abstractions.NullLogger(), new CleuraS3Client(Mock.Of()), Mock.Of()); ConfigurationSchema schema = installer.GetSchema(); schema.ComponentName.Should().Be("gitea"); schema.Parameters.Should().NotBeEmpty(); } [Fact] public void BuildActRunnerStatefulSet_DefaultReplicas_ReturnsValidStatefulSet() { // Arrange & Act V1StatefulSet sts = GiteaInstaller.BuildActRunnerStatefulSet("gitea", 2, "10Gi"); // Assert sts.Metadata.Name.Should().Be("act-runner"); sts.Metadata.NamespaceProperty.Should().Be("gitea"); sts.Spec.Replicas.Should().Be(2); sts.Spec.ServiceName.Should().Be("act-runner"); } [Fact] public void BuildActRunnerStatefulSet_UsesCorrectImage() { // Arrange & Act V1StatefulSet sts = GiteaInstaller.BuildActRunnerStatefulSet("gitea", 1, "5Gi"); // Assert V1Container container = sts.Spec.Template.Spec.Containers[0]; container.Image.Should().Be("gitea/act_runner:latest-dind-rootless"); container.Name.Should().Be("runner"); } [Fact] public void BuildActRunnerStatefulSet_SetsEnvironmentVariables() { // Arrange & Act V1StatefulSet sts = GiteaInstaller.BuildActRunnerStatefulSet("gitea", 1, "5Gi"); // Assert — runner needs DOCKER_HOST and GITEA_INSTANCE_URL at minimum V1Container container = sts.Spec.Template.Spec.Containers[0]; container.Env.Should().Contain(e => e.Name == "DOCKER_HOST"); container.Env.Should().Contain(e => e.Name == "GITEA_INSTANCE_URL"); container.Env.Should().Contain(e => e.Name == "GITEA_RUNNER_REGISTRATION_TOKEN"); } [Fact] public void BuildActRunnerStatefulSet_ConfiguresPvcWithRequestedSize() { // Arrange & Act V1StatefulSet sts = GiteaInstaller.BuildActRunnerStatefulSet("gitea", 3, "20Gi"); // Assert V1PersistentVolumeClaim pvc = sts.Spec.VolumeClaimTemplates[0]; pvc.Metadata.Name.Should().Be("runner-data"); pvc.Spec.Resources.Requests["storage"].ToString().Should().Be("20Gi"); } [Fact] public void BuildActRunnerStatefulSet_WithHarborUrl_SetsRegistryMirrorEnv() { // Arrange & Act — provide a Harbor registry URL for the runners. V1StatefulSet sts = GiteaInstaller.BuildActRunnerStatefulSet("gitea", 1, "5Gi", "harbor.example.com"); // Assert — the Docker daemon should be configured to mirror through Harbor. V1Container container = sts.Spec.Template.Spec.Containers[0]; container.Env.Should().Contain(e => e.Name == "DOCKERD_ROOTLESS_FLAGS" && e.Value == "--registry-mirror=https://harbor.example.com"); } [Fact] public void BuildActRunnerStatefulSet_WithoutHarborUrl_NoMirrorEnv() { // Arrange & Act — no Harbor URL, runners pull images directly. V1StatefulSet sts = GiteaInstaller.BuildActRunnerStatefulSet("gitea", 1, "5Gi"); // Assert — no Docker daemon mirror flag should be present. V1Container container = sts.Spec.Template.Spec.Containers[0]; container.Env.Should().NotContain(e => e.Name == "DOCKERD_ROOTLESS_FLAGS"); } [Fact] public void GiteaSchema_ContainsCnpgClusterNameParameter() { // Gitea should reference the shared CNPG cluster for its database. // The cnpgClusterName parameter tells the installer which CNPG cluster // to create the gitea database on, instead of requiring manual dbHost. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "cnpgClusterName" && p.Type == ParameterType.PostgresCluster); } [Fact] public void GiteaSchema_ContainsCnpgNamespaceParameter() { // The namespace where the CNPG cluster lives — needed to resolve // the service endpoint ({clusterName}-rw.{namespace}.svc). ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "cnpgNamespace"); } [Fact] public void GiteaSchema_IngressProviderIsSelect() { // Ingress provider should be a dropdown selector, not a free-text // field, so users pick from the supported providers. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); ConfigurationParameter ingressProvider = schema.Parameters.First(p => p.Key == "ingressProvider"); ingressProvider.Type.Should().Be(ParameterType.Select); ingressProvider.AllowedValues.Should().Contain("traefik"); ingressProvider.AllowedValues.Should().Contain("istio"); ingressProvider.AllowedValues.Should().Contain("gatewayapi"); } [Fact] public void GiteaSchema_RedisClusterIsRedisClusterType() { // Redis should be a cluster selector dropdown, not a manual host // textbox. The UI populates it from provisioned Redis clusters. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "redisClusterName" && p.Type == ParameterType.RedisCluster); } [Fact] public void GiteaSchema_DatabaseFieldsDoNotIncludeManualHostUserPassword() { // With the CNPG cluster selector, manual dbHost/dbUser/dbPassword // fields are no longer needed — credentials are auto-generated // and stored in the secrets vault. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().NotContain(p => p.Key == "dbHost"); schema.Parameters.Should().NotContain(p => p.Key == "dbUser"); schema.Parameters.Should().NotContain(p => p.Key == "dbPassword"); } [Fact] public void GiteaSchema_ContainsOidcParameters() { // OIDC integration allows Gitea to authenticate users via an external // identity provider like Keycloak. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "oidcEnabled" && p.Type == ParameterType.Boolean); schema.Parameters.Should().Contain(p => p.Key == "oidcDiscoveryUrl"); schema.Parameters.Should().Contain(p => p.Key == "oidcClientId"); schema.Parameters.Should().Contain(p => p.Key == "oidcClientSecret"); } [Fact] public void GiteaSchema_OidcFieldsDependOnOidcEnabled() { // OIDC-specific fields should only be visible when oidcEnabled is true. // This keeps the UI clean for installations without OIDC. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); ConfigurationParameter discoveryUrl = schema.Parameters.First(p => p.Key == "oidcDiscoveryUrl"); discoveryUrl.DependsOnKey.Should().Be("oidcEnabled"); discoveryUrl.DependsOnValues.Should().Contain("true"); } [Fact] public void GiteaSchema_ObjectStorageCascading() { // Object storage fields should cascade based on the objectStorageType // selection — selecting "bucket" shows the bucket selector, "s3" shows // manual S3 fields, "azure" shows Azure fields, "none" hides everything. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); ConfigurationParameter storageType = schema.Parameters.First(p => p.Key == "objectStorageType"); storageType.Type.Should().Be(ParameterType.Select); storageType.AllowedValues.Should().Contain("none"); storageType.AllowedValues.Should().Contain("bucket"); storageType.AllowedValues.Should().Contain("s3"); storageType.AllowedValues.Should().Contain("azure"); ConfigurationParameter bucket = schema.Parameters.First(p => p.Key == "storageBucketId"); bucket.DependsOnKey.Should().Be("objectStorageType"); bucket.DependsOnValues.Should().Contain("bucket"); ConfigurationParameter s3Endpoint = schema.Parameters.First(p => p.Key == "s3Endpoint"); s3Endpoint.DependsOnKey.Should().Be("objectStorageType"); s3Endpoint.DependsOnValues.Should().Contain("s3"); ConfigurationParameter azureName = schema.Parameters.First(p => p.Key == "azureAccountName"); azureName.DependsOnKey.Should().Be("objectStorageType"); azureName.DependsOnValues.Should().Contain("azure"); } [Fact] public void GiteaSchema_ContainsVersionParameter() { // Users should be able to select which version of Gitea to install // or upgrade to. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "version"); } [Fact] public void GiteaSchema_ContainsProjectsParameter() { // Projects (kanban boards) should be configurable per installation. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); schema.Parameters.Should().Contain(p => p.Key == "projectsEnabled" && p.Type == ParameterType.Boolean); } [Fact] public void GiteaSchema_SshPortDependsOnSshEnabled() { // SSH port should only be visible when SSH is enabled. ConfigurationSchema schema = ComponentSchemas.GetSchema("gitea"); ConfigurationParameter sshPort = schema.Parameters.First(p => p.Key == "sshPort"); sshPort.DependsOnKey.Should().Be("sshEnabled"); sshPort.DependsOnValues.Should().Contain("true"); } }