Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing + ClickHouseMigratorTests #12

Merged
merged 5 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ClickHouse.Facades.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickHouse.Facades.Example"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickHouse.Facades.Tests", "src\ClickHouse.Facades.Tests\ClickHouse.Facades.Tests.csproj", "{EE903EEE-DD9E-4402-B758-0E2B13363138}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickHouse.Facades.Testing", "src\ClickHouse.Facades.Testing\ClickHouse.Facades.Testing.csproj", "{677931CB-3677-44A0-A358-0C32ADAC479A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -24,5 +26,9 @@ Global
{EE903EEE-DD9E-4402-B758-0E2B13363138}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE903EEE-DD9E-4402-B758-0E2B13363138}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE903EEE-DD9E-4402-B758-0E2B13363138}.Release|Any CPU.Build.0 = Release|Any CPU
{677931CB-3677-44A0-A358-0C32ADAC479A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{677931CB-3677-44A0-A358-0C32ADAC479A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{677931CB-3677-44A0-A358-0C32ADAC479A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{677931CB-3677-44A0-A358-0C32ADAC479A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
18 changes: 18 additions & 0 deletions src/ClickHouse.Facades.Testing/ClickHouse.Facades.Testing.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ClickHouse.Facades\ClickHouse.Facades.csproj" />
</ItemGroup>

</Project>
90 changes: 90 additions & 0 deletions src/ClickHouse.Facades.Testing/ClickHouseConnectionBrokerStub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Data;
using System.Data.Common;
using ClickHouse.Client.ADO;
using ClickHouse.Client.Copy;
using Microsoft.Extensions.DependencyInjection;

namespace ClickHouse.Facades.Testing;

internal class ClickHouseConnectionBrokerStub<TContext> : ClickHouseConnectionBroker
where TContext : ClickHouseContext<TContext>
{
private readonly ClickHouseConnectionTracker<TContext> _tracker;
private readonly ClickHouseConnectionResponseProducer<TContext> _responseProducer;

public ClickHouseConnectionBrokerStub(
IServiceProvider serviceProvider,
ClickHouseConnection connection) : base(connection)
{
_tracker = serviceProvider.GetRequiredService<ClickHouseConnectionTracker<TContext>>();
_responseProducer = serviceProvider.GetRequiredService<ClickHouseConnectionResponseProducer<TContext>>();
}

internal override string? ServerVersion => _responseProducer.ServerVersion;

internal override string? ServerTimezone => _responseProducer.ServerTimezone;

internal override ClickHouseCommand CreateCommand()
{
throw new NotImplementedException();
}

internal override Task<int> ExecuteNonQueryAsync(string statement, CancellationToken cancellationToken)
{
var result = _responseProducer.TryGetResponse(TestQueryType.ExecuteNonQuery, statement, out var response)
? (int) response!
: 0;

_tracker.Add(new ClickHouseTestResponse(TestQueryType.ExecuteNonQuery, statement, result));

return Task.FromResult(result);
}

internal override Task<object> ExecuteScalarAsync(string query, CancellationToken cancellationToken)
{
var result = _responseProducer.TryGetResponse(TestQueryType.ExecuteScalar, query, out var response)
? response!
: 0;

_tracker.Add(new ClickHouseTestResponse(TestQueryType.ExecuteScalar, query, result));

return Task.FromResult(result);
}

internal override Task<DbDataReader> ExecuteReaderAsync(string query, CancellationToken cancellationToken)
{
var result = _responseProducer.TryGetResponse(TestQueryType.ExecuteReader, query, out var response)
? (DbDataReader) response!
: new DataTableReader(new DataTable());

_tracker.Add(new ClickHouseTestResponse(TestQueryType.ExecuteReader, query, result.HasRows));

return Task.FromResult(result);
}

internal override DataTable ExecuteDataTable(string query, CancellationToken cancellationToken)
{
var responseMocked = _responseProducer
.TryGetResponse(TestQueryType.ExecuteReader, query, out var response);

var result = new DataTable();

if (responseMocked)
{
result.Load((DbDataReader) response!);
}

_tracker.Add(new ClickHouseTestResponse(TestQueryType.ExecuteReader, query, result));

return result;
}

internal override Task<long> BulkInsertAsync(
string destinationTable,
Func<ClickHouseBulkCopy, Task> saveAction,
int batchSize,
int maxDegreeOfParallelism)
{
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace ClickHouse.Facades.Testing;

internal class ClickHouseConnectionResponseProducer<TContext>
where TContext : ClickHouseContext<TContext>
{
private readonly Dictionary<TestQueryType, List<(Predicate<string>, Func<object?>)>> _responseDictionary = new();

private Func<string?>? _serverVersionProvider;
private Func<string?>? _serverTimezoneProvider;

internal void SetServerVersionProvider(Func<string?> serverVersionProvider)
{
_serverVersionProvider = serverVersionProvider;
}

internal void SetServerTimezoneProvider(Func<string?> serverTimezoneProvider)
{
_serverTimezoneProvider = serverTimezoneProvider;
}

internal string? ServerVersion => _serverVersionProvider?.Invoke();
internal string? ServerTimezone => _serverTimezoneProvider?.Invoke();

internal void Add(TestQueryType queryType, Predicate<string> sqlPredicate, Func<object?> result)
{
if (!_responseDictionary.ContainsKey(queryType))
{
_responseDictionary.Add(queryType, new List<(Predicate<string>, Func<object?>)>());
}

_responseDictionary[queryType].Add((sqlPredicate, result));
}

internal bool TryGetResponse(TestQueryType queryType, string sql, out object? response)
{
if (!_responseDictionary.ContainsKey(queryType))
{
response = null;
return false;
}

foreach (var match in _responseDictionary[queryType])
{
if (match.Item1(sql))
{
response = match.Item2();

return true;
}
}

response = null;

return false;
}
}
25 changes: 25 additions & 0 deletions src/ClickHouse.Facades.Testing/ClickHouseConnectionTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace ClickHouse.Facades.Testing;

internal class ClickHouseConnectionTracker<TContext>
where TContext : ClickHouseContext<TContext>
{
private readonly Dictionary<int, ClickHouseTestResponse> _records = new();
private int _recordsCount = 0;

internal void Add(ClickHouseTestResponse record)
{
_records.Add(++_recordsCount, record);
}

public IReadOnlyCollection<ClickHouseTestResponse> GetAllRecords()
{
return _records.Select(r => r.Value).ToList();
}

public ClickHouseTestResponse GetRecord(int index)
{
return _records[index];
}

public int RecordsCount => _recordsCount;
}
126 changes: 126 additions & 0 deletions src/ClickHouse.Facades.Testing/ClickHouseFacadesTestsCore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.Data;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace ClickHouse.Facades.Testing;

public class ClickHouseFacadesTestsCore
{
private readonly IServiceCollection _serviceCollection;
private IServiceProvider _serviceProvider;


[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
protected ClickHouseFacadesTestsCore()
{
_serviceCollection = new ServiceCollection();
SetupServiceCollection(_serviceCollection);
_serviceProvider = _serviceCollection.BuildServiceProvider();
}

protected T GetService<T>() where T : notnull
{
return _serviceProvider.GetRequiredService<T>();
}

/// <summary>
/// Is called in constructor. Should never access class members.
/// </summary>
protected virtual void SetupServiceCollection(IServiceCollection services)
{

}

protected void UpdateServiceCollection(Action<IServiceCollection> action)
{
action(_serviceCollection);

_serviceProvider = _serviceCollection.BuildServiceProvider();
}

protected void MockExecuteNonQuery<TContext>(Predicate<string> sqlPredicate, Func<int> resultProvider)
where TContext : ClickHouseContext<TContext>
{
GetService<ClickHouseConnectionResponseProducer<TContext>>()
.Add(TestQueryType.ExecuteNonQuery, sqlPredicate, () => resultProvider());
}

protected void MockExecuteScalar<TContext>(Predicate<string> sqlPredicate, Func<object> resultProvider)
where TContext : ClickHouseContext<TContext>
{
GetService<ClickHouseConnectionResponseProducer<TContext>>()
.Add(TestQueryType.ExecuteScalar, sqlPredicate, resultProvider);
}

protected void MockExecuteReader<TContext, TResult>(
Predicate<string> sqlPredicate,
IEnumerable<TResult> rows,
params (string ColumnName, Type DataType, Func<TResult, object> PropertySelector)[] columns)
where TContext : ClickHouseContext<TContext>
{
var dataTable = new DataTable();

foreach (var column in columns)
{
dataTable.Columns.Add(column.ColumnName, column.DataType);
}

foreach (var row in rows)
{
var dataRow = dataTable.NewRow();

foreach (var column in columns)
{
dataRow[column.ColumnName] = column.PropertySelector(row) ?? DBNull.Value;
}

dataTable.Rows.Add(dataRow);
}

var dataReader = dataTable.CreateDataReader();

GetService<ClickHouseConnectionResponseProducer<TContext>>()
.Add(TestQueryType.ExecuteReader, sqlPredicate, () => dataReader);
}

protected IReadOnlyCollection<ClickHouseTestResponse> GetClickHouseResponses<TContext>()
where TContext : ClickHouseContext<TContext>
{
return GetService<ClickHouseConnectionTracker<TContext>>().GetAllRecords();
}

protected ClickHouseTestResponse GetClickHouseResponse<TContext>(int index)
where TContext : ClickHouseContext<TContext>
{
return GetService<ClickHouseConnectionTracker<TContext>>().GetRecord(index);
}

protected int GetClickHouseResponsesCount<TContext>()
where TContext : ClickHouseContext<TContext>
{
return GetService<ClickHouseConnectionTracker<TContext>>().RecordsCount;
}

protected void MockServerVersion<TContext>(Func<string?> valueProvider)
where TContext : ClickHouseContext<TContext>
{
GetService<ClickHouseConnectionResponseProducer<TContext>>().SetServerVersionProvider(valueProvider);
}

protected void MockServerTimezone<TContext>(Func<string?> valueProvider)
where TContext : ClickHouseContext<TContext>
{
GetService<ClickHouseConnectionResponseProducer<TContext>>().SetServerTimezoneProvider(valueProvider);
}

protected void MockFacadeAbstraction<TAbstraction>(TAbstraction mock)
where TAbstraction : class
{
UpdateServiceCollection(services =>
{
services.RemoveAll<TAbstraction>();
services.AddTransient(_ => mock);
});
}
}
22 changes: 22 additions & 0 deletions src/ClickHouse.Facades.Testing/ClickHouseTestResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace ClickHouse.Facades.Testing;

public enum TestQueryType
{
ExecuteNonQuery = 1,
ExecuteScalar,
ExecuteReader,
}

public class ClickHouseTestResponse
{
public TestQueryType QueryType { get; }
public string Sql { get; }
public object? Result { get; }

internal ClickHouseTestResponse(TestQueryType queryType, string sql, object? result)
{
QueryType = queryType;
Sql = sql;
Result = result;
}
}
37 changes: 37 additions & 0 deletions src/ClickHouse.Facades.Testing/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using ClickHouse.Facades.Utility;
using Microsoft.Extensions.DependencyInjection;

namespace ClickHouse.Facades.Testing;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddClickHouseTestContext<TContext, TContextFactory>(
this IServiceCollection services,
Action<ClickHouseContextServiceBuilder<TContext>> builderAction)
where TContext : ClickHouseContext<TContext>, new()
where TContextFactory : ClickHouseContextFactory<TContext>
{
ExceptionHelpers.ThrowIfNull(builderAction);

var descriptor = new ServiceDescriptor(
typeof(IClickHouseContextFactory<TContext>),
serviceProvider => ActivatorUtilities
.CreateInstance<TContextFactory>(serviceProvider)
.Setup(
serviceProvider.GetRequiredService<ClickHouseFacadeFactory<TContext>>(),
connection => new ClickHouseConnectionBrokerStub<TContext>(serviceProvider, connection)),
ServiceLifetime.Singleton);

services.Add(descriptor);

var builder = ClickHouseContextServiceBuilder<TContext>.Create;
builderAction(builder);
builder.Build(services);

services.AddSingleton<ClickHouseFacadeFactory<TContext>>();
services.AddSingleton<ClickHouseConnectionTracker<TContext>>();
services.AddSingleton<ClickHouseConnectionResponseProducer<TContext>>();

return services;
}
}
Loading