267 lines
9.9 KiB
C#
267 lines
9.9 KiB
C#
using EntKube.Clusters.Domain;
|
|
using EntKube.Clusters.Features.AdoptCluster;
|
|
using EntKube.Clusters.Features.AdoptCluster.UpgradeComponent;
|
|
using EntKube.Clusters.Infrastructure;
|
|
using EntKube.SharedKernel.Domain;
|
|
using FluentAssertions;
|
|
using Moq;
|
|
|
|
namespace EntKube.Clusters.Tests.Features;
|
|
|
|
/// <summary>
|
|
/// Tests the upgrade handler — the flow for upgrading an already-installed
|
|
/// component to a specific version. Unlike deploy, upgrade validates that
|
|
/// the component is installed and requires an explicit target version.
|
|
/// </summary>
|
|
public class UpgradeComponentHandlerTests
|
|
{
|
|
private readonly InMemoryClusterRepository repository;
|
|
private readonly Mock<IComponentInstaller> harborInstaller;
|
|
private readonly UpgradeComponentHandler handler;
|
|
|
|
public UpgradeComponentHandlerTests()
|
|
{
|
|
repository = new InMemoryClusterRepository();
|
|
harborInstaller = new Mock<IComponentInstaller>();
|
|
harborInstaller.Setup(i => i.ComponentName).Returns("harbor");
|
|
|
|
List<IComponentInstaller> installers = new() { harborInstaller.Object };
|
|
handler = new UpgradeComponentHandler(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger<UpgradeComponentHandler>.Instance);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ClusterNotFound_ReturnsFailure()
|
|
{
|
|
// Arrange — Upgrading a component on a cluster that doesn't exist.
|
|
|
|
UpgradeComponentRequest request = new("harbor", "1.17.0");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(Guid.NewGuid(), request);
|
|
|
|
// Assert
|
|
|
|
result.IsFailure.Should().BeTrue();
|
|
result.Error.Should().Contain("not found");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ClusterNotConnected_ReturnsFailure()
|
|
{
|
|
// Arrange — Can't upgrade components on a disconnected cluster.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"offline-cluster", "https://k8s.dev:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
await repository.AddAsync(cluster);
|
|
|
|
UpgradeComponentRequest request = new("harbor", "1.17.0");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert
|
|
|
|
result.IsFailure.Should().BeTrue();
|
|
result.Error.Should().Contain("connected");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnknownComponent_ReturnsFailure()
|
|
{
|
|
// Arrange — Trying to upgrade a component that has no installer.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"test-cluster", "https://k8s.test:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
cluster.MarkConnected();
|
|
await repository.AddAsync(cluster);
|
|
|
|
UpgradeComponentRequest request = new("nonexistent", "1.0.0");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert
|
|
|
|
result.IsFailure.Should().BeTrue();
|
|
result.Error.Should().Contain("No installer found");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ComponentNotInstalled_ReturnsFailure()
|
|
{
|
|
// Arrange — Harbor exists as a component but is NotInstalled. You
|
|
// can't upgrade something that isn't there — use Deploy first.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"test-cluster", "https://k8s.test:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
cluster.MarkConnected();
|
|
|
|
List<ComponentCheckResult> components = new()
|
|
{
|
|
new ComponentCheckResult("harbor", ComponentStatus.NotInstalled,
|
|
new List<string>(), new List<string> { "Not found" })
|
|
};
|
|
|
|
cluster.UpdateComponents(components);
|
|
await repository.AddAsync(cluster);
|
|
|
|
UpgradeComponentRequest request = new("harbor", "1.17.0");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert
|
|
|
|
result.IsFailure.Should().BeTrue();
|
|
result.Error.Should().Contain("not installed");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ComponentNotTracked_ReturnsFailure()
|
|
{
|
|
// Arrange — The cluster has no components tracked at all. The
|
|
// component hasn't been scanned, so we don't know its state.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"empty-cluster", "https://k8s.test:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
cluster.MarkConnected();
|
|
await repository.AddAsync(cluster);
|
|
|
|
UpgradeComponentRequest request = new("harbor", "1.17.0");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert
|
|
|
|
result.IsFailure.Should().BeTrue();
|
|
result.Error.Should().Contain("not installed");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_InstalledComponent_CallsInstallerAndUpdatesVersion()
|
|
{
|
|
// Arrange — Harbor is installed at 1.16.0. The operator wants to
|
|
// upgrade to 1.17.0. The handler should call the installer with
|
|
// the new version and update the tracked version on success.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"prod-cluster", "https://k8s.prod:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
cluster.MarkConnected();
|
|
|
|
List<ComponentCheckResult> components = new()
|
|
{
|
|
new ComponentCheckResult("harbor", ComponentStatus.Installed,
|
|
new List<string> { "Running" }, new List<string>(),
|
|
new DiscoveredConfiguration("1.16.0", "harbor", "harbor", new Dictionary<string, string>()))
|
|
};
|
|
|
|
cluster.UpdateComponents(components);
|
|
await repository.AddAsync(cluster);
|
|
|
|
harborInstaller
|
|
.Setup(i => i.InstallAsync(cluster, It.Is<ComponentInstallOptions>(o => o.Version == "1.17.0"), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new InstallResult(true, "harbor", "Harbor upgraded to 1.17.0", new List<string> { "Helm upgrade harbor v1.17.0" }));
|
|
|
|
UpgradeComponentRequest request = new("harbor", "1.17.0");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert — The handler should succeed and the tracked version
|
|
// should be updated to reflect the upgrade.
|
|
|
|
result.IsSuccess.Should().BeTrue();
|
|
result.Value!.Success.Should().BeTrue();
|
|
result.Value.Message.Should().Contain("1.17.0");
|
|
|
|
KubernetesCluster? updated = await repository.GetByIdAsync(cluster.Id);
|
|
ClusterComponent harbor = updated!.Components.First(c => c.ComponentName == "harbor");
|
|
harbor.Version.Should().Be("1.17.0");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_InstallerFails_ReturnsFailureWithoutUpdatingVersion()
|
|
{
|
|
// Arrange — The upgrade attempt fails (e.g., Helm reports an error).
|
|
// The tracked version should NOT be updated.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"prod-cluster", "https://k8s.prod:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
cluster.MarkConnected();
|
|
|
|
List<ComponentCheckResult> components = new()
|
|
{
|
|
new ComponentCheckResult("harbor", ComponentStatus.Installed,
|
|
new List<string> { "Running" }, new List<string>(),
|
|
new DiscoveredConfiguration("1.16.0", "harbor", "harbor", new Dictionary<string, string>()))
|
|
};
|
|
|
|
cluster.UpdateComponents(components);
|
|
await repository.AddAsync(cluster);
|
|
|
|
harborInstaller
|
|
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new InstallResult(false, "harbor", "Helm upgrade failed: chart version not found", new List<string>()));
|
|
|
|
UpgradeComponentRequest request = new("harbor", "99.99.99");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert — Failure reported, version unchanged.
|
|
|
|
result.IsFailure.Should().BeTrue();
|
|
result.Error.Should().Contain("failed");
|
|
|
|
KubernetesCluster? unchanged = await repository.GetByIdAsync(cluster.Id);
|
|
ClusterComponent harbor = unchanged!.Components.First(c => c.ComponentName == "harbor");
|
|
harbor.Version.Should().Be("1.16.0");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_PassesExistingNamespaceToInstaller()
|
|
{
|
|
// Arrange — When upgrading, the handler should pass the component's
|
|
// existing namespace so Helm upgrades in the right place.
|
|
|
|
KubernetesCluster cluster = KubernetesCluster.Register(
|
|
"test-cluster", "https://k8s.test:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
|
cluster.MarkConnected();
|
|
|
|
List<ComponentCheckResult> components = new()
|
|
{
|
|
new ComponentCheckResult("harbor", ComponentStatus.Installed,
|
|
new List<string>(), new List<string>(),
|
|
new DiscoveredConfiguration("1.16.0", "custom-harbor-ns", "harbor", new Dictionary<string, string>()))
|
|
};
|
|
|
|
cluster.UpdateComponents(components);
|
|
await repository.AddAsync(cluster);
|
|
|
|
harborInstaller
|
|
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new InstallResult(true, "harbor", "Upgraded", new List<string>()));
|
|
|
|
UpgradeComponentRequest request = new("harbor", "1.17.0");
|
|
|
|
// Act
|
|
|
|
await handler.HandleAsync(cluster.Id, request);
|
|
|
|
// Assert — The installer should receive the existing namespace.
|
|
|
|
harborInstaller.Verify(i => i.InstallAsync(
|
|
cluster,
|
|
It.Is<ComponentInstallOptions>(o => o.Namespace == "custom-harbor-ns" && o.Version == "1.17.0"),
|
|
It.IsAny<CancellationToken>()), Times.Once);
|
|
}
|
|
}
|