306 lines
11 KiB
C#
306 lines
11 KiB
C#
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);
|
|
}
|
|
}
|