Skip to content

Commit

Permalink
Add initial tests for Chats service and check how Testcontainers pack…
Browse files Browse the repository at this point in the history
…age works
  • Loading branch information
cvetomir-todorov committed May 28, 2024
1 parent c73012d commit 7652b09
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 0 deletions.
29 changes: 29 additions & 0 deletions source/CecoChat.Chats.Testing/CecoChat.Chats.Testing.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="6.0.2" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Testcontainers" Version="3.8.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CecoChat.Chats.Client\CecoChat.Chats.Client.csproj" />
<ProjectReference Include="..\CecoChat.Chats.Service\CecoChat.Chats.Service.csproj" />
</ItemGroup>

<ItemGroup>
<Content Include="..\certificates\services.pfx" Link="services.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

</Project>
181 changes: 181 additions & 0 deletions source/CecoChat.Chats.Testing/TestContainers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using Cassandra;
using Common;
using Common.Cassandra;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Networks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NUnit.Framework;

namespace CecoChat.Chats.Testing;

public class TestContainers
{
private INetwork _network;
private IContainer _cassandra;

[OneTimeSetUp]
public async Task Setup()
{
_network = new NetworkBuilder()
.WithName("cecochat")
.Build();

const int port = 9042;
const string localDc = "Europe";

_cassandra = new ContainerBuilder()
.WithImage("cassandra:4.1.3")
.WithName("cecochat-test-cassandra0")
.WithHostname("cassandra0")
.WithNetwork(_network)
.WithPortBinding(hostPort: port, containerPort: port)
.WithEnvironment(new Dictionary<string, string>
{
{ "CASSANDRA_SEEDS", "cassandra0" },
{ "CASSANDRA_CLUSTER_NAME", "cecochat" },
{ "CASSANDRA_DC", localDc },
{ "CASSANDRA_RACK", "Rack0" },
{ "CASSANDRA_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch" },
{ "CASSANDRA_NUM_TOKENS", "128" },
{ "HEAP_NEWSIZE", "128M" },
{ "MAX_HEAP_SIZE", "512M" }
})
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Starting listening for CQL clients on /0.0.0.0:9042"))
.WithWaitStrategy(Wait.ForUnixContainer().UntilCassandraQueryExecuted(port, localDc))
.WithLogger(new NUnitProgressLogger())
.Build();

await _cassandra.StartAsync();
}

[OneTimeTearDown]
public async Task TearDown()
{
await _cassandra.DisposeAsync();
await _network.DisposeAsync();
}

[Test]
public void Test1()
{
CassandraOptions options = new()
{
ContactPoints = [$"{_cassandra.Hostname}:9042"],
LocalDc = "Europe",
SocketConnectTimeout = TimeSpan.FromSeconds(5),
ExponentialReconnectPolicy = true,
ExponentialReconnectPolicyBaseDelay = TimeSpan.FromMilliseconds(500),
ExponentialReconnectPolicyMaxDelay = TimeSpan.FromSeconds(5)
};

using (CassandraDbContext db = new(new NullLogger<CassandraDbContext>(), new OptionsWrapper<CassandraOptions>(options)))
{
TestContext.Progress.WriteLine("Exists keyspace? {0}", db.ExistsKeyspace("unknown"));
}
}
}

public class NUnitProgressLogger : ILogger
{
#pragma warning disable IDE0060
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
#pragma warning restore IDE0060

#pragma warning disable IDE0060
public bool IsEnabled(LogLevel logLevel) => true;
#pragma warning restore IDE0060

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
string message = formatter.Invoke(state, exception);
string level;
switch (logLevel)
{
case LogLevel.Trace:
level = "VRB";
break;
case LogLevel.Debug:
level = "DBG";
break;
case LogLevel.Information:
level = "INF";
break;
case LogLevel.Warning:
level = "WRN";
break;
case LogLevel.Error:
level = "ERR";
break;
case LogLevel.Critical:
level = "CRI";
break;
default:
throw new EnumValueNotSupportedException(logLevel);
}
TestContext.Progress.WriteLine("[{0} Custom] {1}", level, message);
}
}

public static class WaitForContainerOsExtensions
{
public static IWaitForContainerOS UntilCassandraQueryExecuted(this IWaitForContainerOS waitFor, int port, string localDc)
{
return waitFor.AddCustomWaitStrategy(new CassandraQueryExecutedWaitStrategy(port, localDc));
}
}

public class CassandraQueryExecutedWaitStrategy : IWaitUntil
{
private readonly int _port;
private readonly string _localDc;
private bool _showedError;

public CassandraQueryExecutedWaitStrategy(int port, string localDc)
{
_port = port;
_localDc = localDc;
}

public Task<bool> UntilAsync(IContainer container)
{
CassandraOptions options = new()
{
ContactPoints = [$"{container.Hostname}:{_port}"],
LocalDc = _localDc,
SocketConnectTimeout = TimeSpan.FromSeconds(5),
ExponentialReconnectPolicy = true,
ExponentialReconnectPolicyBaseDelay = TimeSpan.FromMilliseconds(500),
ExponentialReconnectPolicyMaxDelay = TimeSpan.FromSeconds(5)
};

CassandraDbContext? db = null;

try
{
db = new(new NullLogger<CassandraDbContext>(), new OptionsWrapper<CassandraOptions>(options));
// we simply want to make sure that there is connection to the cassandra cluster
// we don't care if this keyspace actually exists
db.ExistsKeyspace("non-existing");

return Task.FromResult(true);
}
catch (NoHostAvailableException noHostAvailableException)
{
if (!_showedError)
{
container.Logger.LogError(noHostAvailableException, "No connection to Cassandra at {Host}:{Port} (showing this error only the first time)", container.Hostname, _port);
_showedError = true;
}

return Task.FromResult(false);
}
finally
{
db?.Dispose();
}
}
}
7 changes: 7 additions & 0 deletions source/CecoChat.sln
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CecoChat.Backplane", "CecoC
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CecoChat.IdGen.Testing", "CecoChat.IdGen.Testing\CecoChat.IdGen.Testing.csproj", "{83038940-C5C1-4723-AF41-04F2AE05C710}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CecoChat.Chats.Testing", "CecoChat.Chats.Testing\CecoChat.Chats.Testing.csproj", "{864EDCC9-1E20-404A-8F62-66F90E6320F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -247,6 +249,10 @@ Global
{83038940-C5C1-4723-AF41-04F2AE05C710}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83038940-C5C1-4723-AF41-04F2AE05C710}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83038940-C5C1-4723-AF41-04F2AE05C710}.Release|Any CPU.Build.0 = Release|Any CPU
{864EDCC9-1E20-404A-8F62-66F90E6320F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{864EDCC9-1E20-404A-8F62-66F90E6320F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{864EDCC9-1E20-404A-8F62-66F90E6320F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{864EDCC9-1E20-404A-8F62-66F90E6320F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -295,6 +301,7 @@ Global
{EFDD5358-77F7-4657-AD8F-905845EBDC4A} = {40DD0429-AC4C-4A22-B668-34DC395023D1}
{D04E96F5-3698-4AC3-BFDF-DFF28B6B3BBB} = {EFDD5358-77F7-4657-AD8F-905845EBDC4A}
{83038940-C5C1-4723-AF41-04F2AE05C710} = {DA6D302C-F6BB-4BC3-BDDD-AE316FB64537}
{864EDCC9-1E20-404A-8F62-66F90E6320F8} = {AD63D00D-89DD-4F1E-A311-DE9EAE1F701A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5EAA668-8419-44F5-8BE5-9175A9B20BF8}
Expand Down

0 comments on commit 7652b09

Please sign in to comment.