Skip to content

Commit

Permalink
feat(testing): add possibility to mock and test finalizers.
Browse files Browse the repository at this point in the history
This relates to #8.
  • Loading branch information
buehler committed Jun 25, 2020
1 parent b5e1a8e commit 5d59448
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 25 deletions.
10 changes: 6 additions & 4 deletions src/KubeOps/Operator/KubernetesOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class KubernetesOperator
.CreateDefaultBuilder()
.UseConsoleLifetime();

protected IHost? OperatorHost { get; set; }

public KubernetesOperator()
: this((Assembly.GetEntryAssembly()?.GetName().Name ?? DefaultOperatorName).ToLowerInvariant())
{
Expand Down Expand Up @@ -82,15 +84,15 @@ public virtual Task<int> Run(string[] args)

ConfigureOperatorLogging(args);

var host = Builder.Build();
OperatorHost = Builder.Build();

app
.Conventions
.UseDefaultConventions()
.UseConstructorInjection(host.Services);
.UseConstructorInjection(OperatorHost.Services);

DependencyInjector.Services = host.Services;
JsonConvert.DefaultSettings = () => host.Services.GetRequiredService<JsonSerializerSettings>();
DependencyInjector.Services = OperatorHost.Services;
JsonConvert.DefaultSettings = () => OperatorHost.Services.GetRequiredService<JsonSerializerSettings>();

return app.ExecuteAsync(args);
}
Expand Down
19 changes: 11 additions & 8 deletions src/KubeOps/Testing/KubernetesTestOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using k8s;
using k8s.Models;
using KubeOps.Operator;
using KubeOps.Operator.DependencyInjection;
using KubeOps.Operator.Client;
using KubeOps.Operator.Queue;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -13,22 +13,22 @@

namespace KubeOps.Testing
{
public class KubernetesTestOperator : KubernetesOperator, IDisposable
public class KubernetesTestOperator : KubernetesOperator
{
public IServiceProvider Services => DependencyInjector.Services;
public IServiceProvider Services { get; private set; } = new ServiceCollection().BuildServiceProvider();

public MockKubernetesClient MockedClient =>
Services.GetRequiredService<IKubernetesClient>() as MockKubernetesClient ??
throw new ArgumentException("Wrong kubernetes client registered.");

public MockResourceEventQueue<TEntity> GetMockedEventQueue<TEntity>()
where TEntity : IKubernetesObject<V1ObjectMeta>
=> Services.GetRequiredService<MockResourceQueueCollection>().Get<TEntity>();

public void Dispose()
{
Services.GetService<IHost>()?.StopAsync();
}

public override Task<int> Run(string[] args)
{
base.Run(args).ConfigureAwait(false);
Services = OperatorHost?.Services ?? throw new ArgumentException("Host not built.");
return Task.FromResult(0);
}

Expand All @@ -40,6 +40,9 @@ protected override void ConfigureOperatorServices()
services.RemoveAll(typeof(IResourceEventQueue<>));
services.AddTransient(typeof(IResourceEventQueue<>), typeof(MockResourceEventQueue<>));
services.AddSingleton<MockResourceQueueCollection>();
services.RemoveAll(typeof(IKubernetesClient));
services.AddSingleton<IKubernetesClient, MockKubernetesClient>();
});
base.ConfigureOperatorServices();
}
Expand Down
84 changes: 84 additions & 0 deletions src/KubeOps/Testing/MockKubernetesClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using k8s;
using k8s.Models;
using KubeOps.Operator.Client;
using KubeOps.Operator.Client.LabelSelectors;

namespace KubeOps.Testing
{
public class MockKubernetesClient : IKubernetesClient
{
public IKubernetes ApiClient { get; } = new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());

public object? GetResult { get; set; }

public IList<object>? ListResult { get; set; }

public object? SaveResult { get; set; }

public object? CreateResult { get; set; }

public object? UpdateResult { get; set; }

public Task<TResource?> Get<TResource>(string name, string? @namespace = null)
where TResource : class, IKubernetesObject<V1ObjectMeta>
=> Task.FromResult(GetResult as TResource);

public Task<IList<TResource>> List<TResource>(string? @namespace = null, string? labelSelector = null)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.FromResult(ListResult as IList<TResource> ?? new List<TResource>());

public Task<IList<TResource>> List<TResource>(string? @namespace = null, params ILabelSelector[] labelSelectors)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.FromResult(ListResult as IList<TResource> ?? new List<TResource>());

public Task<TResource> Save<TResource>(TResource resource)
where TResource : class, IKubernetesObject<V1ObjectMeta>
=> Task.FromResult(SaveResult as TResource)!;

public Task<TResource> Create<TResource>(TResource resource)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.FromResult((TResource) CreateResult!)!;

public Task<TResource> Update<TResource>(TResource resource)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.FromResult((TResource) UpdateResult!)!;

public Task UpdateStatus<TStatus>(IStatus<TStatus> resource)
=> Task.CompletedTask;

public Task Delete<TResource>(TResource resource)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.CompletedTask;

public Task Delete<TResource>(IEnumerable<TResource> resources)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.CompletedTask;

public Task Delete<TResource>(params TResource[] resources)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.CompletedTask;

public Task Delete<TResource>(string name, string? @namespace = null)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.CompletedTask;

public Task<Watcher<TResource>> Watch<TResource>(
TimeSpan timeout,
Action<WatchEventType, TResource> onEvent,
Action<Exception>? onError = null,
Action? onClose = null,
string? @namespace = null,
CancellationToken cancellationToken = default)
where TResource : IKubernetesObject<V1ObjectMeta>
=> Task.FromResult(
new Watcher<TResource>(
() => Task.FromResult(new StreamReader(new MemoryStream())),
(_, __) => { },
_ => { }));
}
}
6 changes: 6 additions & 0 deletions tests/KubeOps.TestOperator.Test/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Xunit;

[assembly: CollectionBehavior(
CollectionBehavior.CollectionPerAssembly,
MaxParallelThreads = 1,
DisableTestParallelization = true)]
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks;
using KubeOps.Testing;
using KubeOps.TestOperator.Entities;
using KubeOps.TestOperator.TestManager;
Expand All @@ -9,9 +7,9 @@
using Moq;
using Xunit;

namespace KubeOps.TestOperator.Test.Controller
namespace KubeOps.TestOperator.Test
{
public class TestControllerTest : IDisposable
public class TestControllerTest
{
private readonly Mock<IManager> _mock = new Mock<IManager>();

Expand All @@ -35,6 +33,7 @@ public async Task Test_If_Manager_Created_Is_Called()
await _operator.Run();
_mock.Setup(o => o.Created(It.IsAny<TestEntity>()));
_mock.Verify(o => o.Created(It.IsAny<TestEntity>()), Times.Never);
_operator.MockedClient.UpdateResult = new TestEntity();
var queue = _operator.GetMockedEventQueue<TestEntity>();
queue.Created(new TestEntity());
_mock.Verify(o => o.Created(It.IsAny<TestEntity>()), Times.Once);
Expand Down Expand Up @@ -83,10 +82,5 @@ public async Task Test_If_Manager_StatusModified_Is_Called()
queue.StatusUpdated(new TestEntity());
_mock.Verify(o => o.StatusModified(It.IsAny<TestEntity>()), Times.Once);
}

public void Dispose()
{
_operator.Dispose();
}
}
}
51 changes: 51 additions & 0 deletions tests/KubeOps.TestOperator.Test/TestFinalizer.Test.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Threading.Tasks;
using k8s.Models;
using KubeOps.Testing;
using KubeOps.TestOperator.Entities;
using KubeOps.TestOperator.Finalizer;
using KubeOps.TestOperator.TestManager;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq;
using Xunit;

namespace KubeOps.TestOperator.Test
{
public class TestFinalizerTest
{
private readonly Mock<IManager> _mock = new Mock<IManager>();

private readonly KubernetesTestOperator _operator;

public TestFinalizerTest()
{
_operator = new Operator()
.ConfigureServices(
services =>
{
services.RemoveAll(typeof(IManager));
services.AddSingleton(typeof(IManager), _mock.Object);
})
.ToKubernetesTestOperator();
}

[Fact]
public async Task Test_If_Manager_Finalized_Is_Called()
{
await _operator.Run();
_mock.Setup(o => o.Finalized(It.IsAny<TestEntity>()));
_mock.Verify(o => o.Finalized(It.IsAny<TestEntity>()), Times.Never);
_operator.MockedClient.UpdateResult = new TestEntity();
var queue = _operator.GetMockedEventQueue<TestEntity>();
queue.Finalizing(
new TestEntity
{
Metadata = new V1ObjectMeta
{
Finalizers = new[] { new TestEntityFinalizer(_mock.Object).Identifier },
}
});
_mock.Verify(o => o.Finalized(It.IsAny<TestEntity>()), Times.Once);
}
}
}
3 changes: 3 additions & 0 deletions tests/KubeOps.TestOperator/Controller/TestController.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Threading.Tasks;
using KubeOps.Operator.Controller;
using KubeOps.Operator.Entities.Extensions;
using KubeOps.Operator.Rbac;
using KubeOps.TestOperator.Entities;
using KubeOps.TestOperator.Finalizer;
using KubeOps.TestOperator.TestManager;

namespace KubeOps.TestOperator.Controller
Expand All @@ -20,6 +22,7 @@ public TestController(IManager manager)
protected override async Task<TimeSpan?> Created(TestEntity resource)
{
_manager.Created(resource);
await resource.RegisterFinalizer<TestEntityFinalizer, TestEntity>();
return await base.Created(resource);
}

Expand Down
23 changes: 23 additions & 0 deletions tests/KubeOps.TestOperator/Finalizer/TestEntityFinalizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Threading.Tasks;
using KubeOps.Operator.Finalizer;
using KubeOps.TestOperator.Entities;
using KubeOps.TestOperator.TestManager;

namespace KubeOps.TestOperator.Finalizer
{
public class TestEntityFinalizer : ResourceFinalizerBase<TestEntity>
{
private readonly IManager _manager;

public TestEntityFinalizer(IManager manager)
{
_manager = manager;
}

public override Task Finalize(TestEntity resource)
{
_manager.Finalized(resource);
return Task.CompletedTask;
}
}
}
2 changes: 2 additions & 0 deletions tests/KubeOps.TestOperator/Operator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using KubeOps.Operator;
using KubeOps.TestOperator.Controller;
using KubeOps.TestOperator.Entities;
using KubeOps.TestOperator.Finalizer;
using KubeOps.TestOperator.TestManager;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -15,6 +16,7 @@ public Operator()
{
services.AddTransient<IManager, TestManager.TestManager>();
services.AddResourceController<TestController, TestEntity>();
services.AddResourceFinalizer<TestEntityFinalizer, TestEntity>();
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/KubeOps.TestOperator/TestManager/IManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ public interface IManager
void NotModified(TestEntity entity);

void Deleted(TestEntity entity);

void Finalized(TestEntity entity);
}
}
9 changes: 6 additions & 3 deletions tests/KubeOps.TestOperator/TestManager/TestManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Diagnostics;
using KubeOps.TestOperator.Entities;
using KubeOps.TestOperator.Entities;
using Microsoft.Extensions.Logging;

namespace KubeOps.TestOperator.TestManager
Expand Down Expand Up @@ -38,5 +36,10 @@ public void Deleted(TestEntity entity)
{
_logger.LogDebug(nameof(Deleted));
}

public void Finalized(TestEntity entity)
{
_logger.LogDebug(nameof(Finalized));
}
}
}

0 comments on commit 5d59448

Please sign in to comment.