Skip to content

Commit f6eb486

Browse files
committed
feat(tests): add integration tests for reflector functionality
- special thanks to @egbakou for the base for the tests
1 parent ba0e984 commit f6eb486

File tree

12 files changed

+475
-2
lines changed

12 files changed

+475
-2
lines changed

.github/workflows/pipeline.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ jobs:
108108
- name: evaluate - build_push
109109
id: evaluate_build_push
110110
run: |
111-
if [ "${{ steps.evaluate_build.outputs.result }}" = "true" ]; then
111+
if [ "${{ steps.pathsFilter.outputs.src }}" = "true" ]; then
112112
result=true
113113
else
114114
result=false
@@ -228,6 +228,8 @@ jobs:
228228
with:
229229
context: ${{ env.container_image_build_context }}
230230
file: ${{ env.container_image_build_dockerfile }}
231+
build-args: |
232+
BUILD_CONFIGURATION=${{ env.build_configuration }}
231233
push: ${{ env.build_push == 'true' }}
232234
provenance: false
233235
platforms: ${{ env.container_image_build_platforms }}

Directory.Packages.props

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
55
</PropertyGroup>
66
<ItemGroup>
7+
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
78
<PackageVersion Include="ES.FX.Ignite" Version="9.1.3" />
89
<PackageVersion Include="ES.FX.Ignite.KubernetesClient" Version="9.1.3" />
910
<PackageVersion Include="ES.FX.Ignite.OpenTelemetry.Exporter.Seq" Version="9.1.3" />
@@ -12,6 +13,13 @@
1213
<PackageVersion Include="ES.FX.Additions.KubernetesClient" Version="9.1.3" />
1314
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
1415
<PackageVersion Include="Microsoft.AspNetCore.JsonPatch" Version="9.0.4" />
16+
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.4" />
17+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
1518
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
19+
<PackageVersion Include="Moq" Version="4.20.72" />
20+
<PackageVersion Include="Polly.Core" Version="8.5.2" />
21+
<PackageVersion Include="Testcontainers.K3s" Version="4.4.0" />
22+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
23+
<PackageVersion Include="xunit.v3" Version="2.0.1" />
1624
</ItemGroup>
1725
</Project>

ES.Kubernetes.Reflector.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ES.Kubernetes.Reflector", "
1414
EndProject
1515
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".src", ".src", "{868DDC6B-AB77-4639-A95B-E182CC65AF60}"
1616
EndProject
17+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{755CA48A-4728-4A3F-8EAB-03E99B2F6087}"
18+
EndProject
19+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ES.Kubernetes.Reflector.Tests", "tests\ES.Kubernetes.Reflector.Tests\ES.Kubernetes.Reflector.Tests.csproj", "{4C67A712-2206-40F3-A08F-C2F9EF22B424}"
20+
EndProject
1721
Global
1822
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1923
Debug|Any CPU = Debug|Any CPU
@@ -24,12 +28,17 @@ Global
2428
{D9295E0C-D3E8-1BE3-F512-1E2579A6882C}.Debug|Any CPU.Build.0 = Debug|Any CPU
2529
{D9295E0C-D3E8-1BE3-F512-1E2579A6882C}.Release|Any CPU.ActiveCfg = Release|Any CPU
2630
{D9295E0C-D3E8-1BE3-F512-1E2579A6882C}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{4C67A712-2206-40F3-A08F-C2F9EF22B424}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32+
{4C67A712-2206-40F3-A08F-C2F9EF22B424}.Debug|Any CPU.Build.0 = Debug|Any CPU
33+
{4C67A712-2206-40F3-A08F-C2F9EF22B424}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{4C67A712-2206-40F3-A08F-C2F9EF22B424}.Release|Any CPU.Build.0 = Release|Any CPU
2735
EndGlobalSection
2836
GlobalSection(SolutionProperties) = preSolution
2937
HideSolutionNode = FALSE
3038
EndGlobalSection
3139
GlobalSection(NestedProjects) = preSolution
3240
{D9295E0C-D3E8-1BE3-F512-1E2579A6882C} = {868DDC6B-AB77-4639-A95B-E182CC65AF60}
41+
{4C67A712-2206-40F3-A08F-C2F9EF22B424} = {755CA48A-4728-4A3F-8EAB-03E99B2F6087}
3342
EndGlobalSection
3443
GlobalSection(ExtensibilityGlobals) = postSolution
3544
SolutionGuid = {3D60B4E7-B886-4E0A-BD09-2AF8EC1BA851}

src/ES.Kubernetes.Reflector/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@
4343
app.Ignite();
4444
await app.RunAsync();
4545
return 0;
46-
});
46+
});
47+
48+
public partial class Program;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using ES.Kubernetes.Reflector.Mirroring.Core;
2+
3+
namespace ES.Kubernetes.Reflector.Tests.Additions;
4+
5+
public sealed class ReflectorAnnotationsBuilder
6+
{
7+
private readonly Dictionary<string, string> _annotations = new();
8+
9+
public ReflectorAnnotationsBuilder WithReflectionAllowed(bool allowed)
10+
{
11+
_annotations[Annotations.Reflection.Allowed] = allowed.ToString().ToLower();
12+
return this;
13+
}
14+
15+
public ReflectorAnnotationsBuilder WithAllowedNamespaces(params string[] namespaces)
16+
{
17+
_annotations[Annotations.Reflection.AllowedNamespaces] = string.Join(",", namespaces);
18+
return this;
19+
}
20+
21+
public ReflectorAnnotationsBuilder WithAutoEnabled(bool enabled)
22+
{
23+
_annotations[Annotations.Reflection.AutoEnabled] = enabled.ToString().ToLower();
24+
return this;
25+
}
26+
27+
public ReflectorAnnotationsBuilder WithAutoNamespaces(bool enabled, params string[] namespaces)
28+
{
29+
_annotations[Annotations.Reflection.AutoNamespaces] = enabled ? string.Join(",", namespaces) : string.Empty;
30+
return this;
31+
}
32+
33+
public Dictionary<string, string> Build()
34+
{
35+
if (_annotations.Count != 0) return _annotations;
36+
37+
_annotations[Annotations.Reflection.Allowed] = "true";
38+
_annotations[Annotations.Reflection.AllowedNamespaces] = string.Empty;
39+
_annotations[Annotations.Reflection.AutoEnabled] = "true";
40+
_annotations[Annotations.Reflection.AutoNamespaces] = string.Empty;
41+
return _annotations;
42+
}
43+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector">
12+
<PrivateAssets>all</PrivateAssets>
13+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
14+
</PackageReference>
15+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
16+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
17+
<PackageReference Include="Moq" />
18+
<PackageReference Include="Polly.Core" />
19+
<PackageReference Include="Testcontainers.K3s" />
20+
<PackageReference Include="xunit.runner.visualstudio">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
24+
<PackageReference Include="xunit.v3" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\..\src\ES.Kubernetes.Reflector\ES.Kubernetes.Reflector.csproj" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<Using Include="Xunit" />
33+
</ItemGroup>
34+
35+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Text;
2+
using k8s;
3+
using Testcontainers.K3s;
4+
5+
namespace ES.Kubernetes.Reflector.Tests.Fixtures;
6+
7+
public sealed class KubernetesFixture : IAsyncLifetime
8+
{
9+
public K3sContainer Container { get; } = new K3sBuilder()
10+
.WithName($"{nameof(KubernetesFixture)}-{Guid.CreateVersion7()}")
11+
.Build();
12+
13+
public async ValueTask DisposeAsync() => await Container.DisposeAsync();
14+
15+
public async ValueTask InitializeAsync() => await Container.StartAsync();
16+
17+
public async Task<KubernetesClientConfiguration> GetKubernetesClientConfiguration() =>
18+
await KubernetesClientConfiguration
19+
.BuildConfigFromConfigFileAsync(
20+
new MemoryStream(Encoding.UTF8.GetBytes(
21+
await Container.GetKubeconfigAsync())));
22+
23+
public async Task<IKubernetes> GetKubernetesClient() =>
24+
new k8s.Kubernetes(await GetKubernetesClientConfiguration());
25+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using k8s;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Mvc.Testing;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Hosting;
6+
7+
namespace ES.Kubernetes.Reflector.Tests.Fixtures;
8+
9+
public sealed class ReflectorFixture : WebApplicationFactory<Program>, IAsyncLifetime
10+
{
11+
private static readonly Lock Lock = new();
12+
13+
public KubernetesClientConfiguration? KubernetesClientConfiguration { get; set; }
14+
15+
16+
public async ValueTask InitializeAsync() => await Task.CompletedTask;
17+
18+
// https://github.com/serilog/serilog-aspnetcore/issues/289
19+
// https://github.com/dotnet/AspNetCore.Docs/issues/26609
20+
protected override IHost CreateHost(IHostBuilder builder)
21+
{
22+
lock (Lock)
23+
{
24+
return base.CreateHost(builder);
25+
}
26+
}
27+
28+
protected override void ConfigureWebHost(IWebHostBuilder builder)
29+
{
30+
builder.UseEnvironment("tests");
31+
builder.ConfigureServices(services =>
32+
{
33+
var kubernetesClientConfiguration = services.SingleOrDefault(
34+
d => d.ServiceType == typeof(KubernetesClientConfiguration));
35+
if (kubernetesClientConfiguration is not null) services.Remove(kubernetesClientConfiguration);
36+
37+
services.AddSingleton(s =>
38+
{
39+
var config = KubernetesClientConfiguration ??
40+
KubernetesClientConfiguration.BuildDefaultConfig();
41+
config.HttpClientTimeout = TimeSpan.FromMinutes(30);
42+
43+
return config;
44+
});
45+
});
46+
}
47+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Net;
2+
using ES.Kubernetes.Reflector.Tests.Integration.Fixtures;
3+
using k8s;
4+
using k8s.Autorest;
5+
using k8s.Models;
6+
using Polly;
7+
using Polly.Retry;
8+
9+
namespace ES.Kubernetes.Reflector.Tests.Integration.Base;
10+
11+
public class BaseIntegrationTest(ReflectorIntegrationFixture integrationFixture)
12+
{
13+
protected static readonly ResiliencePipeline<bool> ResourceExistsResiliencePipeline =
14+
new ResiliencePipelineBuilder<bool>()
15+
.AddRetry(new RetryStrategyOptions<bool>
16+
{
17+
ShouldHandle = new PredicateBuilder<bool>()
18+
.Handle<HttpOperationException>(ex =>
19+
ex.Response.StatusCode == HttpStatusCode.NotFound)
20+
.HandleResult(false),
21+
MaxRetryAttempts = 10,
22+
Delay = TimeSpan.FromSeconds(1)
23+
})
24+
.AddTimeout(TimeSpan.FromSeconds(30))
25+
.Build();
26+
27+
protected async Task<IKubernetes> GetKubernetesClient() =>
28+
await integrationFixture.Kubernetes.GetKubernetesClient();
29+
30+
protected async Task<V1Namespace?> CreateNamespaceAsync(string name)
31+
{
32+
var client = await GetKubernetesClient();
33+
var ns = new V1Namespace
34+
{
35+
ApiVersion = V1Namespace.KubeApiVersion,
36+
Kind = V1Namespace.KubeKind,
37+
Metadata = new V1ObjectMeta
38+
{
39+
Name = name
40+
}
41+
};
42+
43+
return await client.CoreV1.CreateNamespaceAsync(ns);
44+
}
45+
46+
47+
protected async Task DelayForReflection() =>
48+
await Task.Delay(TimeSpan.FromSeconds(1));
49+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using ES.Kubernetes.Reflector.Tests.Fixtures;
2+
3+
namespace ES.Kubernetes.Reflector.Tests.Integration.Fixtures;
4+
5+
public class ReflectorIntegrationFixture : IAsyncLifetime
6+
{
7+
public KubernetesFixture Kubernetes { get; init; } = new();
8+
public ReflectorFixture Reflector { get; init; } = new();
9+
10+
public async ValueTask InitializeAsync()
11+
{
12+
await Kubernetes.InitializeAsync();
13+
Reflector.KubernetesClientConfiguration =
14+
await Kubernetes.GetKubernetesClientConfiguration();
15+
await Reflector.InitializeAsync();
16+
Reflector.CreateClient();
17+
}
18+
19+
public async ValueTask DisposeAsync() => await Task.CompletedTask;
20+
}

0 commit comments

Comments
 (0)