too much for one commit

This commit is contained in:
Nils Blomgren
2026-05-13 14:01:32 +02:00
parent a96dd33039
commit 328d494530
394 changed files with 98104 additions and 78 deletions

View File

@@ -0,0 +1,305 @@
using EntKube.SharedKernel.Domain;
using FluentAssertions;
namespace EntKube.Provisioning.Tests.Domain;
/// <summary>
/// Tests for the RedisCluster aggregate root. This rich domain model tracks
/// everything about a Redis instance managed by the OT-OPERATORS Redis Operator:
/// replica count, storage, sentinel mode, persistence, and version lifecycle.
///
/// The OT-OPERATORS Redis Operator manages Redis, RedisCluster, RedisReplication,
/// and RedisSentinel CRDs on Kubernetes. Each Redis instance can be standalone,
/// replicated, or run with Sentinel for HA.
/// </summary>
public class RedisClusterTests
{
// ─── Creation ──────────────────────────────────────────────────────────────
[Fact]
public void Create_WithValidInputs_ReturnsClusterInProvisioningState()
{
// Arrange & Act — A platform admin provisions a new Redis instance
// for shared caching on a specific Kubernetes cluster.
Guid environmentId = Guid.NewGuid();
Guid clusterId = Guid.NewGuid();
Provisioning.Domain.RedisCluster redisCluster = Provisioning.Domain.RedisCluster.Create(
environmentId: environmentId,
clusterId: clusterId,
name: "platform-redis",
ns: "redis",
redisVersion: "7.2.5",
replicas: 3,
storageSize: "10Gi",
sentinelEnabled: true);
// Assert — The cluster starts in Provisioning state, waiting for the
// reconciler to create the Redis CR.
redisCluster.Id.Should().NotBe(Guid.Empty);
redisCluster.EnvironmentId.Should().Be(environmentId);
redisCluster.ClusterId.Should().Be(clusterId);
redisCluster.Name.Should().Be("platform-redis");
redisCluster.Namespace.Should().Be("redis");
redisCluster.RedisVersion.Should().Be("7.2.5");
redisCluster.Replicas.Should().Be(3);
redisCluster.StorageSize.Should().Be("10Gi");
redisCluster.SentinelEnabled.Should().BeTrue();
redisCluster.Status.Should().Be(Provisioning.Domain.RedisClusterStatus.Provisioning);
}
[Fact]
public void Create_WithEmptyName_ThrowsArgumentException()
{
// Arrange & Act — The domain rejects invalid cluster names.
Action act = () => Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "", "ns", "7.2.5", 3, "10Gi", true);
// Assert
act.Should().Throw<ArgumentException>().WithParameterName("name");
}
[Fact]
public void Create_WithZeroReplicas_ThrowsArgumentException()
{
// Arrange & Act — Must have at least 1 replica.
Action act = () => Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "cache", "ns", "7.2.5", 0, "10Gi", true);
// Assert
act.Should().Throw<ArgumentException>().WithParameterName("replicas");
}
[Fact]
public void Create_WithEmptyNamespace_ThrowsArgumentException()
{
// Arrange & Act
Action act = () => Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "cache", "", "7.2.5", 3, "10Gi", true);
// Assert
act.Should().Throw<ArgumentException>().WithParameterName("ns");
}
// ─── Adoption ──────────────────────────────────────────────────────────────
[Fact]
public void Adopt_FromExistingInstance_CreatesRunningCluster()
{
// Arrange & Act — When we adopt an existing Redis instance that's
// already running on the K8s cluster, we create it in Running state.
Provisioning.Domain.RedisCluster redisCluster = Provisioning.Domain.RedisCluster.Adopt(
environmentId: Guid.NewGuid(),
clusterId: Guid.NewGuid(),
name: "existing-redis",
ns: "redis",
redisVersion: "7.0.15",
replicas: 3,
storageSize: "5Gi",
sentinelEnabled: true);
// Assert — Adopted clusters start in Running state.
redisCluster.Status.Should().Be(Provisioning.Domain.RedisClusterStatus.Running);
redisCluster.Name.Should().Be("existing-redis");
redisCluster.LastReconcileAt.Should().NotBeNull();
}
// ─── Status transitions ────────────────────────────────────────────────────
[Fact]
public void MarkRunning_TransitionsFromProvisioning()
{
// Arrange — A freshly created cluster in Provisioning state.
Provisioning.Domain.RedisCluster redisCluster = CreateTestCluster();
// Act — The reconciler confirmed the Redis instance is ready.
redisCluster.MarkRunning();
// Assert
redisCluster.Status.Should().Be(Provisioning.Domain.RedisClusterStatus.Running);
redisCluster.LastReconcileAt.Should().NotBeNull();
}
[Fact]
public void MarkDegraded_SetsStatusToDegraded()
{
// Arrange
Provisioning.Domain.RedisCluster redisCluster = CreateTestCluster();
redisCluster.MarkRunning();
// Act — Reconciler detected an issue.
redisCluster.MarkDegraded("Replica pod not ready");
// Assert
redisCluster.Status.Should().Be(Provisioning.Domain.RedisClusterStatus.Degraded);
redisCluster.StatusMessage.Should().Be("Replica pod not ready");
}
// ─── Version upgrades ──────────────────────────────────────────────────────
[Fact]
public void RequestUpgrade_WhenRunning_SetsUpgradingStatusAndTargetVersion()
{
// Arrange — A running cluster at version 7.0.15.
Provisioning.Domain.RedisCluster redisCluster = Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "cache", "ns", "7.0.15", 3, "10Gi", true);
redisCluster.MarkRunning();
// Act — Platform admin requests upgrade to 7.2.5.
Result<string> result = redisCluster.RequestUpgrade("7.2.5");
// Assert — The cluster transitions to Upgrading state.
result.IsSuccess.Should().BeTrue();
redisCluster.Status.Should().Be(Provisioning.Domain.RedisClusterStatus.Upgrading);
redisCluster.TargetVersion.Should().Be("7.2.5");
}
[Fact]
public void RequestUpgrade_WhenNotRunning_ReturnsFailure()
{
// Arrange — A cluster still provisioning can't be upgraded.
Provisioning.Domain.RedisCluster redisCluster = CreateTestCluster();
// Act
Result<string> result = redisCluster.RequestUpgrade("7.2.5");
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("running");
}
[Fact]
public void RequestUpgrade_ToSameVersion_ReturnsFailure()
{
// Arrange — A cluster already at version 7.2.5.
Provisioning.Domain.RedisCluster redisCluster = Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "cache", "ns", "7.2.5", 3, "10Gi", true);
redisCluster.MarkRunning();
// Act — Try to upgrade to the same version.
Result<string> result = redisCluster.RequestUpgrade("7.2.5");
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("same version");
}
[Fact]
public void CompleteUpgrade_TransitionsToRunningWithNewVersion()
{
// Arrange — A cluster mid-upgrade.
Provisioning.Domain.RedisCluster redisCluster = Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "cache", "ns", "7.0.15", 3, "10Gi", true);
redisCluster.MarkRunning();
redisCluster.RequestUpgrade("7.2.5");
// Act — Reconciler confirmed upgrade completed.
redisCluster.CompleteUpgrade();
// Assert — Version updated, status back to Running.
redisCluster.RedisVersion.Should().Be("7.2.5");
redisCluster.Status.Should().Be(Provisioning.Domain.RedisClusterStatus.Running);
redisCluster.TargetVersion.Should().BeNull();
}
// ─── Scaling ───────────────────────────────────────────────────────────────
[Fact]
public void ScaleReplicas_UpdatesReplicaCount()
{
// Arrange — A running 3-replica cluster.
Provisioning.Domain.RedisCluster redisCluster = CreateTestCluster();
redisCluster.MarkRunning();
// Act — Scale up to 5 replicas for more read capacity.
Result<string> result = redisCluster.ScaleReplicas(5);
// Assert
result.IsSuccess.Should().BeTrue();
redisCluster.Replicas.Should().Be(5);
}
[Fact]
public void ScaleReplicas_ToZero_ReturnsFailure()
{
// Arrange
Provisioning.Domain.RedisCluster redisCluster = CreateTestCluster();
redisCluster.MarkRunning();
// Act
Result<string> result = redisCluster.ScaleReplicas(0);
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("at least 1");
}
// ─── Configuration ─────────────────────────────────────────────────────────
[Fact]
public void ConfigureSentinel_EnablesOrDisablesSentinel()
{
// Arrange — A cluster without sentinel.
Provisioning.Domain.RedisCluster redisCluster = Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "cache", "ns", "7.2.5", 3, "10Gi", false);
redisCluster.MarkRunning();
// Act — Enable sentinel for HA.
redisCluster.ConfigureSentinel(true);
// Assert
redisCluster.SentinelEnabled.Should().BeTrue();
}
// ─── Helper ────────────────────────────────────────────────────────────────
private static Provisioning.Domain.RedisCluster CreateTestCluster()
{
return Provisioning.Domain.RedisCluster.Create(
Guid.NewGuid(),
Guid.NewGuid(),
"test-redis",
"redis",
"7.2.5",
3,
"10Gi",
true);
}
}