too much for one commit
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
using EntKube.Provisioning.Domain;
|
||||
using EntKube.Provisioning.Features.RedisClusters;
|
||||
using EntKube.Provisioning.Features.RedisClusters.AdoptRedisCluster;
|
||||
using EntKube.Provisioning.Features.RedisClusters.CreateRedisCluster;
|
||||
using EntKube.Provisioning.Features.RedisClusters.UpgradeRedisCluster;
|
||||
using EntKube.Provisioning.Features.RedisClusters.ConfigureRedisCluster;
|
||||
using EntKube.Provisioning.Infrastructure;
|
||||
using EntKube.SharedKernel.Domain;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
||||
namespace EntKube.Provisioning.Tests.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for all Redis cluster management features. Each test verifies that
|
||||
/// the handler correctly orchestrates domain logic + K8s client interactions.
|
||||
/// </summary>
|
||||
public class RedisClusterFeatureTests
|
||||
{
|
||||
private readonly InMemoryRedisClusterRepository repository;
|
||||
private readonly Mock<IRedisClusterClient> redisClient;
|
||||
|
||||
public RedisClusterFeatureTests()
|
||||
{
|
||||
repository = new InMemoryRedisClusterRepository();
|
||||
redisClient = new Mock<IRedisClusterClient>();
|
||||
}
|
||||
|
||||
// ─── Create Cluster ───────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task CreateCluster_WithValidRequest_CreatesAndCallsK8sClient()
|
||||
{
|
||||
// Arrange — A platform admin wants a new 3-replica Redis instance
|
||||
// with sentinel enabled for HA.
|
||||
|
||||
CreateRedisClusterHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
CreateRedisClusterRequest request = new(
|
||||
EnvironmentId: Guid.NewGuid(),
|
||||
ClusterId: Guid.NewGuid(),
|
||||
Name: "platform-cache",
|
||||
Namespace: "redis",
|
||||
RedisVersion: "7.2.5",
|
||||
Replicas: 3,
|
||||
StorageSize: "10Gi",
|
||||
SentinelEnabled: true,
|
||||
KubeConfig: "apiVersion: v1\nkind: Config",
|
||||
ContextName: "prod-ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<Guid> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Cluster created in repository and K8s client called.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().NotBe(Guid.Empty);
|
||||
|
||||
IReadOnlyList<RedisCluster> clusters = await repository.GetAllAsync();
|
||||
clusters.Should().HaveCount(1);
|
||||
clusters[0].Name.Should().Be("platform-cache");
|
||||
clusters[0].Status.Should().Be(RedisClusterStatus.Provisioning);
|
||||
|
||||
redisClient.Verify(c => c.CreateClusterAsync(
|
||||
"apiVersion: v1\nkind: Config",
|
||||
"prod-ctx",
|
||||
It.Is<RedisClusterSpec>(s =>
|
||||
s.Name == "platform-cache" &&
|
||||
s.RedisVersion == "7.2.5" &&
|
||||
s.Replicas == 3 &&
|
||||
s.SentinelEnabled == true),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateCluster_WithMissingName_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
CreateRedisClusterHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
CreateRedisClusterRequest request = new(
|
||||
EnvironmentId: Guid.NewGuid(),
|
||||
ClusterId: Guid.NewGuid(),
|
||||
Name: "",
|
||||
Namespace: "redis",
|
||||
RedisVersion: "7.2.5",
|
||||
Replicas: 3,
|
||||
StorageSize: "10Gi",
|
||||
SentinelEnabled: true,
|
||||
KubeConfig: "config",
|
||||
ContextName: "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<Guid> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Domain validation rejects empty names.
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Contain("name");
|
||||
}
|
||||
|
||||
// ─── Adopt Clusters ───────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptClusters_DiscoversTwoInstances_CreatesBothInRunningState()
|
||||
{
|
||||
// Arrange — The K8s cluster has two existing Redis instances.
|
||||
|
||||
redisClient.Setup(c => c.DiscoverClustersAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<DiscoveredRedisCluster>
|
||||
{
|
||||
new("session-cache", "redis", "7.2.5", 3, "10Gi", true),
|
||||
new("rate-limiter", "redis", "7.0.15", 1, "5Gi", false)
|
||||
});
|
||||
|
||||
AdoptRedisClustersHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
AdoptRedisClustersRequest request = new(Guid.NewGuid(), Guid.NewGuid(), "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<List<Guid>> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Both clusters adopted.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().HaveCount(2);
|
||||
|
||||
IReadOnlyList<RedisCluster> clusters = await repository.GetAllAsync();
|
||||
clusters.Should().HaveCount(2);
|
||||
clusters.Should().OnlyContain(c => c.Status == RedisClusterStatus.Running);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptClusters_AlreadyAdopted_ReturnsExistingIdWithoutDuplicate()
|
||||
{
|
||||
// Arrange — One instance already adopted.
|
||||
|
||||
Guid clusterId = Guid.NewGuid();
|
||||
|
||||
RedisCluster existing = RedisCluster.Adopt(
|
||||
Guid.NewGuid(), clusterId, "session-cache", "redis", "7.2.5", 3, "10Gi", true);
|
||||
await repository.AddAsync(existing);
|
||||
|
||||
redisClient.Setup(c => c.DiscoverClustersAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<DiscoveredRedisCluster>
|
||||
{
|
||||
new("session-cache", "redis", "7.2.5", 3, "10Gi", true)
|
||||
});
|
||||
|
||||
AdoptRedisClustersHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
AdoptRedisClustersRequest request = new(Guid.NewGuid(), clusterId, "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<List<Guid>> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Returns existing ID without creating a duplicate.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().HaveCount(1);
|
||||
result.Value![0].Should().Be(existing.Id);
|
||||
|
||||
IReadOnlyList<RedisCluster> clusters = await repository.GetAllAsync();
|
||||
clusters.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
// ─── Upgrade ──────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task UpgradeCluster_WithValidVersion_TransitionsToUpgrading()
|
||||
{
|
||||
// Arrange — A running Redis instance at version 7.0.15.
|
||||
|
||||
RedisCluster redisCluster = RedisCluster.Create(
|
||||
Guid.NewGuid(), Guid.NewGuid(), "cache", "redis", "7.0.15", 3, "10Gi", true);
|
||||
redisCluster.MarkRunning();
|
||||
await repository.AddAsync(redisCluster);
|
||||
|
||||
redisClient.Setup(c => c.GetAvailableVersionsAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<string> { "7.0.15", "7.2.5" });
|
||||
|
||||
UpgradeRedisClusterHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
UpgradeRedisClusterRequest request = new(redisCluster.Id, "7.2.5", "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<string> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
redisCluster.Status.Should().Be(RedisClusterStatus.Upgrading);
|
||||
redisCluster.TargetVersion.Should().Be("7.2.5");
|
||||
|
||||
redisClient.Verify(c => c.UpgradeClusterAsync(
|
||||
"config", "ctx", "cache", "redis", "7.2.5",
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpgradeCluster_UnavailableVersion_ReturnsFailure()
|
||||
{
|
||||
// Arrange — Target version not in the available list.
|
||||
|
||||
RedisCluster redisCluster = RedisCluster.Create(
|
||||
Guid.NewGuid(), Guid.NewGuid(), "cache", "redis", "7.0.15", 3, "10Gi", true);
|
||||
redisCluster.MarkRunning();
|
||||
await repository.AddAsync(redisCluster);
|
||||
|
||||
redisClient.Setup(c => c.GetAvailableVersionsAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<string> { "7.0.15" });
|
||||
|
||||
UpgradeRedisClusterHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
UpgradeRedisClusterRequest request = new(redisCluster.Id, "7.2.5", "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<string> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Contain("not available");
|
||||
}
|
||||
|
||||
// ─── Configure ────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task ConfigureCluster_ScaleReplicas_UpdatesAndCallsK8s()
|
||||
{
|
||||
// Arrange — A running 3-replica cluster to be scaled to 5.
|
||||
|
||||
RedisCluster redisCluster = RedisCluster.Create(
|
||||
Guid.NewGuid(), Guid.NewGuid(), "cache", "redis", "7.2.5", 3, "10Gi", true);
|
||||
redisCluster.MarkRunning();
|
||||
await repository.AddAsync(redisCluster);
|
||||
|
||||
ConfigureRedisClusterHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
ConfigureRedisClusterRequest request = new(
|
||||
redisCluster.Id, Replicas: 5, StorageSize: null, SentinelEnabled: null,
|
||||
KubeConfig: "config", ContextName: "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<string> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
redisCluster.Replicas.Should().Be(5);
|
||||
|
||||
redisClient.Verify(c => c.UpdateClusterAsync(
|
||||
"config", "ctx",
|
||||
It.Is<RedisClusterSpec>(s => s.Replicas == 5),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConfigureCluster_NonExistent_ReturnsFailure()
|
||||
{
|
||||
// Arrange — No cluster with this ID.
|
||||
|
||||
ConfigureRedisClusterHandler handler = new(repository, redisClient.Object);
|
||||
|
||||
ConfigureRedisClusterRequest request = new(
|
||||
Guid.NewGuid(), Replicas: 5, StorageSize: null, SentinelEnabled: null,
|
||||
KubeConfig: "config", ContextName: "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<string> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Contain("not found");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user