From 77bb57fce40d93abcbbf20a0dddbc908d9f80918 Mon Sep 17 00:00:00 2001 From: Paul Welter Date: Wed, 6 Sep 2023 22:37:22 -0500 Subject: [PATCH] fix data cache, cleanup tests --- .github/workflows/dotnet.yml | 8 + FluentCommand.sln | 7 - .../DistributedDataCache.cs | 84 +++++- .../IDistributedCacheSerializer.cs | 37 +++ .../MessagePackCacheSerializer.cs | 44 ++- .../Bulk/DataBulkCopy.cs | 13 + src/FluentCommand/DataCommand.cs | 32 ++- src/FluentCommand/IDataCache.cs | 19 +- .../BatchDatabaseCollection.cs | 13 - .../FluentCommand.Generators.Tests.csproj | 6 +- .../FluentCommand.PostgreSQL.Tests.csproj | 4 +- .../FluentCommand.SQLite.Tests.csproj | 4 +- .../DataBulkCopyTests.cs | 131 +++++++++ .../DataCacheTests.cs | 54 ++++ .../DataCommandProcedureTests.cs | 36 ++- .../DataCommandSqlAsyncTests.cs | 58 +--- .../DataCommandSqlTests.cs | 79 ++---- .../DataMergeGeneratorTests.cs | 263 ++++++++++++++++++ .../DataMergeTests.cs | 66 +++-- .../DataQueryTests.cs | 20 +- .../DataSessionTests.cs | 5 +- .../DatabaseFixture.cs | 137 +++------ .../DatabaseInitializer.cs | 65 +++++ .../DatabaseQueryLogger.cs | 22 ++ .../DatabaseTestBase.cs | 31 +-- .../FluentCommand.SqlServer.Tests.csproj | 12 +- .../GeneratorTests.cs | 10 +- .../JsonTests.cs | 8 +- .../appsettings.json | 3 +- .../FluentCommand.Tests.csproj | 6 +- 30 files changed, 914 insertions(+), 363 deletions(-) delete mode 100644 test/FluentCommand.Batch.Tests/BatchDatabaseCollection.cs create mode 100644 test/FluentCommand.SqlServer.Tests/DataBulkCopyTests.cs create mode 100644 test/FluentCommand.SqlServer.Tests/DataCacheTests.cs create mode 100644 test/FluentCommand.SqlServer.Tests/DataMergeGeneratorTests.cs create mode 100644 test/FluentCommand.SqlServer.Tests/DatabaseInitializer.cs create mode 100644 test/FluentCommand.SqlServer.Tests/DatabaseQueryLogger.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index d5b7a4a2..c9a9d137 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -3,6 +3,7 @@ name: Build env: DOTNET_NOLOGO: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_ENVIRONMENT: github ASPNETCORE_ENVIRONMENT: github BUILD_PATH: "${{github.workspace}}/artifacts" COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} @@ -43,6 +44,13 @@ jobs: volumes: - postgres_data:/var/lib/postgresql/data + redis: + image: redis + ports: + - 6379:6379 + volumes: + - redis_data:/data + steps: - name: Checkout uses: actions/checkout@v3 diff --git a/FluentCommand.sln b/FluentCommand.sln index fa60bd62..cd25f541 100644 --- a/FluentCommand.sln +++ b/FluentCommand.sln @@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.SQLite.Tests" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.SqlServer.Tests", "test\FluentCommand.SqlServer.Tests\FluentCommand.SqlServer.Tests.csproj", "{5DE2495F-EC9E-49C5-8211-5ACB44A780A8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Batch.Tests", "test\FluentCommand.Batch.Tests\FluentCommand.Batch.Tests.csproj", "{CCC34DD0-4706-44CB-AC03-A0E607F449A2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{38D98603-4878-4CFA-ACF5-5BEC1218B149}" ProjectSection(SolutionItems) = preProject coverlet.runsettings = coverlet.runsettings @@ -61,10 +59,6 @@ Global {5DE2495F-EC9E-49C5-8211-5ACB44A780A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DE2495F-EC9E-49C5-8211-5ACB44A780A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {5DE2495F-EC9E-49C5-8211-5ACB44A780A8}.Release|Any CPU.Build.0 = Release|Any CPU - {CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Release|Any CPU.Build.0 = Release|Any CPU {A003BB6E-286D-4F52-B980-992816892CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A003BB6E-286D-4F52-B980-992816892CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {A003BB6E-286D-4F52-B980-992816892CEE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -105,7 +99,6 @@ Global {A1DC06EF-CBFB-4EE7-883D-4BF0369F2654} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} {0F3235EA-8397-4D99-AB07-DF7987409DD4} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} {5DE2495F-EC9E-49C5-8211-5ACB44A780A8} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} - {CCC34DD0-4706-44CB-AC03-A0E607F449A2} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} {A003BB6E-286D-4F52-B980-992816892CEE} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} {CFCFEA34-ABFA-490E-85BF-A7DC12392245} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} {00F44ED1-561E-43DC-ABC1-A78411FFC6F3} = {228D7B65-A886-4D86-AED0-F3CB0C61819B} diff --git a/src/FluentCommand.Caching/DistributedDataCache.cs b/src/FluentCommand.Caching/DistributedDataCache.cs index 304c086d..3151f3a5 100644 --- a/src/FluentCommand.Caching/DistributedDataCache.cs +++ b/src/FluentCommand.Caching/DistributedDataCache.cs @@ -8,12 +8,22 @@ namespace FluentCommand.Caching; +/// +/// Distributed cache implemenation +/// +/// public partial class DistributedDataCache : IDataCache { private readonly ILogger _logger; private readonly IDistributedCache _distributedCache; private readonly IDistributedCacheSerializer _distributedCacheSerializer; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The distributed cache. + /// The distributed cache serializer. public DistributedDataCache(ILogger logger, IDistributedCache distributedCache, IDistributedCacheSerializer distributedCacheSerializer) { _logger = logger; @@ -21,7 +31,17 @@ public DistributedDataCache(ILogger logger, IDistributedCa _distributedCacheSerializer = distributedCacheSerializer; } - public T Get(string key) + /// + /// Gets the specified cache entry from the cache as an object. + /// + /// The type of item in cache + /// A unique identifier for the cache entry. + /// + /// Success is true if the key was found; otherwise false + /// Value is the cache entry that is identified by key + /// + /// '{nameof(key)}' cannot be null or empty. - key + public (bool Success, T Value) Get(string key) { if (string.IsNullOrEmpty(key)) throw new ArgumentException($"'{nameof(key)}' cannot be null or empty.", nameof(key)); @@ -31,17 +51,28 @@ public T Get(string key) if (cachedBuffer == null) { LogCacheMiss(_logger, key); - return default; + return (false, default); } var cachedItem = _distributedCacheSerializer.Deserialize(cachedBuffer); LogCacheHit(_logger, key); - return cachedItem; + return (true, cachedItem); } - public async Task GetAsync(string key, CancellationToken cancellationToken = default) + /// + /// Gets the specified cache entry from the cache as an object. + /// + /// The type of item in cache + /// A unique identifier for the cache entry. + /// The cancellation instruction. + /// + /// Success is true if the key was found; otherwise false + /// Value is the cache entry that is identified by key + /// + /// '{nameof(key)}' cannot be null or empty. - key + public async Task<(bool Success, T Value)> GetAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(key)) throw new ArgumentException($"'{nameof(key)}' cannot be null or empty.", nameof(key)); @@ -53,7 +84,7 @@ public async Task GetAsync(string key, CancellationToken cancellationToken if (cachedBuffer == null) { LogCacheMiss(_logger, key); - return default; + return (false, default); } var cachedItem = await _distributedCacheSerializer @@ -62,9 +93,19 @@ public async Task GetAsync(string key, CancellationToken cancellationToken LogCacheHit(_logger, key); - return cachedItem; + return (true, cachedItem); } + /// + /// Inserts a cache entry into the cache, specifying information about how the entry will be evicted. + /// + /// The type of item in cache + /// A unique identifier for the cache entry. + /// The object to insert into cache. + /// The fixed date and time at which the cache entry will expire. + /// A value that indicates whether a cache entry should be evicted if it has not been accessed in a given span of time. + /// '{nameof(key)}' cannot be null or empty. - key + /// value public void Set(string key, T value, DateTimeOffset? absoluteExpiration = null, TimeSpan? slidingExpiration = null) { if (string.IsNullOrEmpty(key)) @@ -86,6 +127,17 @@ public void Set(string key, T value, DateTimeOffset? absoluteExpiration = nul LogCacheInsert(_logger, key); } + /// + /// Inserts a cache entry into the cache, specifying information about how the entry will be evicted. + /// + /// The type of item in cache + /// A unique identifier for the cache entry. + /// The object to insert into cache. + /// The fixed date and time at which the cache entry will expire. + /// A value that indicates whether a cache entry should be evicted if it has not been accessed in a given span of time. + /// The cancellation instruction. + /// '{nameof(key)}' cannot be null or empty. - key + /// value public async Task SetAsync(string key, T value, DateTimeOffset? absoluteExpiration = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(key)) @@ -112,6 +164,11 @@ await _distributedCache LogCacheInsert(_logger, key); } + /// + /// Removes the cache entry from the cache + /// + /// A unique identifier for the cache entry. + /// '{nameof(key)}' cannot be null or empty. - key public void Remove(string key) { if (string.IsNullOrEmpty(key)) @@ -122,6 +179,12 @@ public void Remove(string key) _distributedCache.Remove(key); } + /// + /// Removes the cache entry from the cache + /// + /// A unique identifier for the cache entry. + /// The cancellation instruction. + /// '{nameof(key)}' cannot be null or empty. - key public async Task RemoveAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(key)) @@ -133,16 +196,17 @@ public async Task RemoveAsync(string key, CancellationToken cancellationToken = } - [LoggerMessage(0, LogLevel.Trace, "Cache Hit; Key: '{cacheKey}'")] + [LoggerMessage(0, LogLevel.Information, "Cache Hit; Key: '{cacheKey}'")] static partial void LogCacheHit(ILogger logger, string cacheKey); - [LoggerMessage(1, LogLevel.Trace, "Cache Miss; Key: '{cacheKey}'")] + [LoggerMessage(1, LogLevel.Information, "Cache Miss; Key: '{cacheKey}'")] static partial void LogCacheMiss(ILogger logger, string cacheKey); - [LoggerMessage(2, LogLevel.Trace, "Cache Insert; Key: '{cacheKey}'")] + [LoggerMessage(2, LogLevel.Information, "Cache Insert; Key: '{cacheKey}'")] static partial void LogCacheInsert(ILogger logger, string cacheKey); - [LoggerMessage(3, LogLevel.Trace, "Cache Remove; Key: '{cacheKey}'")] + [LoggerMessage(3, LogLevel.Information, "Cache Remove; Key: '{cacheKey}'")] static partial void LogCacheRemove(ILogger logger, string cacheKey); + } diff --git a/src/FluentCommand.Caching/IDistributedCacheSerializer.cs b/src/FluentCommand.Caching/IDistributedCacheSerializer.cs index b73fedfa..dcf3f23c 100644 --- a/src/FluentCommand.Caching/IDistributedCacheSerializer.cs +++ b/src/FluentCommand.Caching/IDistributedCacheSerializer.cs @@ -1,12 +1,49 @@ namespace FluentCommand.Caching; +/// +/// Interface defining a serializer of distrubted cache +/// public interface IDistributedCacheSerializer { + /// + /// Serializes the specified instance to a byte array for caching. + /// + /// The type to serialize + /// The instance to serialize. + /// + /// The byte array of the serialized instance + /// byte[] Serialize(T instance); + /// + /// Serializes the specified instance to a byte array for caching. + /// + /// The type to serialize + /// The instance to serialize. + /// The cancellation token. + /// + /// The byte array of the serialized instance + /// Task SerializeAsync(T instance, CancellationToken cancellationToken = default); + /// + /// Deserializes the specified byte array into an instance of . + /// + /// The type to deserialize + /// The byte array to deserialize. + /// + /// An instance of deserialized + /// T Deserialize(byte[] byteArray); + /// + /// Deserializes the specified byte array into an instance of . + /// + /// The type to deserialize + /// The byte array to deserialize. + /// The cancellation token. + /// + /// An instance of deserialized + /// Task DeserializeAsync(byte[] byteArray, CancellationToken cancellationToken = default); } diff --git a/src/FluentCommand.Caching/MessagePackCacheSerializer.cs b/src/FluentCommand.Caching/MessagePackCacheSerializer.cs index adedabd2..030c487a 100644 --- a/src/FluentCommand.Caching/MessagePackCacheSerializer.cs +++ b/src/FluentCommand.Caching/MessagePackCacheSerializer.cs @@ -1,38 +1,78 @@ -using System.Threading; - using MessagePack; using MessagePack.Resolvers; namespace FluentCommand.Caching; +/// +/// A MessagePack implementation of IDistributedCacheSerializer +/// +/// public class MessagePackCacheSerializer : IDistributedCacheSerializer { private readonly MessagePackSerializerOptions _messagePackSerializerOptions; + /// + /// Initializes a new instance of the class. + /// + /// The message pack serializer options. public MessagePackCacheSerializer(MessagePackSerializerOptions messagePackSerializerOptions = null) { _messagePackSerializerOptions = messagePackSerializerOptions ?? ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray); } + /// + /// Deserializes the specified byte array into an instance of . + /// + /// The type to deserialize + /// The byte array to deserialize. + /// + /// An instance of deserialized + /// public T Deserialize(byte[] byteArray) { var value = MessagePackSerializer.Deserialize(byteArray, _messagePackSerializerOptions); return value; } + /// + /// Deserializes the specified byte array into an instance of . + /// + /// The type to deserialize + /// The byte array to deserialize. + /// The cancellation token. + /// + /// An instance of deserialized + /// public Task DeserializeAsync(byte[] byteArray, CancellationToken cancellationToken = default) { var value = MessagePackSerializer.Deserialize(byteArray, _messagePackSerializerOptions, cancellationToken); return Task.FromResult(value); } + /// + /// Serializes the specified instance to a byte array for caching. + /// + /// The type to serialize + /// The instance to serialize. + /// + /// The byte array of the serialized instance + /// public byte[] Serialize(T instance) { var value = MessagePackSerializer.Serialize(instance, _messagePackSerializerOptions); return value; } + /// + /// Serializes the specified instance to a byte array for caching. + /// + /// The type to serialize + /// The instance to serialize. + /// The cancellation token. + /// + /// The byte array of the serialized instance + /// public Task SerializeAsync(T instance, CancellationToken cancellationToken = default) { var value = MessagePackSerializer.Serialize(instance, _messagePackSerializerOptions, cancellationToken); diff --git a/src/FluentCommand.SqlServer/Bulk/DataBulkCopy.cs b/src/FluentCommand.SqlServer/Bulk/DataBulkCopy.cs index 4934fb3a..bf52852c 100644 --- a/src/FluentCommand.SqlServer/Bulk/DataBulkCopy.cs +++ b/src/FluentCommand.SqlServer/Bulk/DataBulkCopy.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Data.Common; using FluentCommand.Extensions; @@ -367,8 +368,10 @@ public void WriteToServer(DataTable table, DataRowState rowState) { // resolve auto map if (_autoMap == true) + { foreach (DataColumn column in table.Columns) Mapping(column.ColumnName, column.ColumnName); + } _dataSession.EnsureConnection(); @@ -394,6 +397,16 @@ public void WriteToServer(IDataReader reader) try { + // resolve auto map + if (_autoMap == true) + { + for (int i = 0; i < reader.FieldCount; i++) + { + var name = reader.GetName(i); + Mapping(name, name); + } + } + _dataSession.EnsureConnection(); using var bulkCopy = Create(); diff --git a/src/FluentCommand/DataCommand.cs b/src/FluentCommand/DataCommand.cs index 714c08f4..86070ef8 100644 --- a/src/FluentCommand/DataCommand.cs +++ b/src/FluentCommand/DataCommand.cs @@ -560,12 +560,14 @@ private TResult QueryFactory(Func query, bool supportCache) try { var cacheKey = CacheKey(supportCache); - if (GetCache(cacheKey) is TResult results) - return results; + + (bool cacheSuccess, TResult cacheValue) = GetCache(cacheKey); + if (cacheSuccess) + return cacheValue; _dataSession.EnsureConnection(); - results = query(); + var results = query(); TriggerCallbacks(); @@ -609,13 +611,13 @@ private async Task QueryFactoryAsync( { var cacheKey = CacheKey(supportCache); - var cacheValue = await GetCacheAsync(cacheKey, cancellationToken).ConfigureAwait(false); - if (cacheValue is TResult results) - return results; + (bool cacheSuccess, TResult cacheValue) = await GetCacheAsync(cacheKey, cancellationToken).ConfigureAwait(false); + if (cacheSuccess) + return cacheValue; await _dataSession.EnsureConnectionAsync(cancellationToken).ConfigureAwait(false); - results = await query(cancellationToken).ConfigureAwait(false); + var results = await query(cancellationToken).ConfigureAwait(false); TriggerCallbacks(); @@ -683,32 +685,32 @@ private string CacheKey(bool supportCache) return $"global:data:{hashCode}"; } - private T GetCache(string key) + private (bool Success, T Value) GetCache(string key) { if (_slidingExpiration == null && _absoluteExpiration == null) - return default; + return (false, default); if (key == null) - return default; + return (false, default); var cache = _dataSession.Cache; if (cache == null) - return default; + return (false, default); return cache.Get(key); } - private async Task GetCacheAsync(string key, CancellationToken cancellationToken) + private async Task<(bool Success, T Value)> GetCacheAsync(string key, CancellationToken cancellationToken) { if (_slidingExpiration == null && _absoluteExpiration == null) - return default; + return (false, default); if (key == null) - return default; + return (false, default); var cache = _dataSession.Cache; if (cache == null) - return default; + return (false, default); return await cache .GetAsync(key, cancellationToken) diff --git a/src/FluentCommand/IDataCache.cs b/src/FluentCommand/IDataCache.cs index 031272d4..f264ebe3 100644 --- a/src/FluentCommand/IDataCache.cs +++ b/src/FluentCommand/IDataCache.cs @@ -8,21 +8,30 @@ public interface IDataCache /// /// Gets the specified cache entry from the cache as an object. /// + /// The type of item in cache /// A unique identifier for the cache entry. - /// The cache entry that is identified by key. - T Get(string key); + /// + /// Success is true if the key was found; otherwise false + /// Value is the cache entry that is identified by key + /// + (bool Success, T Value) Get(string key); /// /// Gets the specified cache entry from the cache as an object. /// + /// The type of item in cache /// A unique identifier for the cache entry. /// The cancellation instruction. - /// The cache entry that is identified by key. - Task GetAsync(string key, CancellationToken cancellationToken = default); + /// + /// Success is true if the key was found; otherwise false + /// Value is the cache entry that is identified by key + /// + Task<(bool Success, T Value)> GetAsync(string key, CancellationToken cancellationToken = default); /// /// Inserts a cache entry into the cache, specifying information about how the entry will be evicted. /// + /// The type of item in cache /// A unique identifier for the cache entry. /// The object to insert into cache. /// The fixed date and time at which the cache entry will expire. @@ -32,7 +41,7 @@ public interface IDataCache /// /// Inserts a cache entry into the cache, specifying information about how the entry will be evicted. /// - /// + /// The type of item in cache /// A unique identifier for the cache entry. /// The object to insert into cache. /// The fixed date and time at which the cache entry will expire. diff --git a/test/FluentCommand.Batch.Tests/BatchDatabaseCollection.cs b/test/FluentCommand.Batch.Tests/BatchDatabaseCollection.cs deleted file mode 100644 index 2c2dd36e..00000000 --- a/test/FluentCommand.Batch.Tests/BatchDatabaseCollection.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -using FluentCommand.SqlServer.Tests; - -using Xunit; - -namespace FluentCommand.Batch.Tests; - -[CollectionDefinition(BatchDatabaseCollection.CollectionName)] -public class BatchDatabaseCollection : ICollectionFixture -{ - public const string CollectionName = "BatchDatabaseCollection"; -} diff --git a/test/FluentCommand.Generators.Tests/FluentCommand.Generators.Tests.csproj b/test/FluentCommand.Generators.Tests/FluentCommand.Generators.Tests.csproj index 8ff38d65..dc3bdeb2 100644 --- a/test/FluentCommand.Generators.Tests/FluentCommand.Generators.Tests.csproj +++ b/test/FluentCommand.Generators.Tests/FluentCommand.Generators.Tests.csproj @@ -12,9 +12,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/FluentCommand.PostgreSQL.Tests/FluentCommand.PostgreSQL.Tests.csproj b/test/FluentCommand.PostgreSQL.Tests/FluentCommand.PostgreSQL.Tests.csproj index c121a11d..dba49769 100644 --- a/test/FluentCommand.PostgreSQL.Tests/FluentCommand.PostgreSQL.Tests.csproj +++ b/test/FluentCommand.PostgreSQL.Tests/FluentCommand.PostgreSQL.Tests.csproj @@ -24,13 +24,13 @@ - + all runtime; build; native; contentfiles; analyzers - + diff --git a/test/FluentCommand.SQLite.Tests/FluentCommand.SQLite.Tests.csproj b/test/FluentCommand.SQLite.Tests/FluentCommand.SQLite.Tests.csproj index 645b3061..764cbb60 100644 --- a/test/FluentCommand.SQLite.Tests/FluentCommand.SQLite.Tests.csproj +++ b/test/FluentCommand.SQLite.Tests/FluentCommand.SQLite.Tests.csproj @@ -24,13 +24,13 @@ - + all runtime; build; native; contentfiles; analyzers - + diff --git a/test/FluentCommand.SqlServer.Tests/DataBulkCopyTests.cs b/test/FluentCommand.SqlServer.Tests/DataBulkCopyTests.cs new file mode 100644 index 00000000..7aa00b36 --- /dev/null +++ b/test/FluentCommand.SqlServer.Tests/DataBulkCopyTests.cs @@ -0,0 +1,131 @@ +using System; + +using Bogus; + +using FluentAssertions; + +using FluentCommand.Bulk; +using FluentCommand.Entities; + +using Microsoft.Extensions.DependencyInjection; + +using Xunit; +using Xunit.Abstractions; + +namespace FluentCommand.SqlServer.Tests; + +public class DataBulkCopyTests : DatabaseTestBase +{ + public DataBulkCopyTests(ITestOutputHelper output, DatabaseFixture databaseFixture) : base(output, databaseFixture) + { + } + + [Fact] + public void WriteServerAutoMap() + { + var generator = CreateGenerator(); + var users = generator.Generate(100); + + using var session = Services.GetRequiredService(); + { + session.Should().NotBeNull(); + + session.BulkCopy("[User]") + .AutoMap() + .Ignore("RowVersion") + .Ignore("Audits") + .Ignore("AssignedTasks") + .Ignore("CreatedTasks") + .Ignore("Roles") + .WriteToServer(users); + } + } + + + [Fact] + public void WriteServerManualMap() + { + var generator = CreateGenerator(); + var users = generator.Generate(100); + + using var session = Services.GetRequiredService(); + session.Should().NotBeNull(); + + session.BulkCopy("[User]") + .Mapping("Id", "Id") + .Mapping("EmailAddress", "EmailAddress") + .Mapping("IsEmailAddressConfirmed", "IsEmailAddressConfirmed") + .Mapping("FirstName", "FirstName") + .Mapping("LastName", "LastName") + .Mapping("DisplayName", "DisplayName") + .Mapping("PasswordHash", "PasswordHash") + .Mapping("ResetHash", "ResetHash") + .Mapping("InviteHash", "InviteHash") + .Mapping("AccessFailedCount", "AccessFailedCount") + .Mapping("LockoutEnabled", "LockoutEnabled") + .Mapping("LockoutEnd", "LockoutEnd") + .Mapping("LastLogin", "LastLogin") + .Mapping("IsDeleted", "IsDeleted") + .Mapping("Created", "Created") + .Mapping("CreatedBy", "CreatedBy") + .Mapping("Updated", "Updated") + .Mapping("UpdatedBy", "UpdatedBy") + .WriteToServer(users); + } + + + [Fact] + public void WriteServerStrongMap() + { + var generator = CreateGenerator(); + var users = generator.Generate(100); + + using var session = Services.GetRequiredService(); + session.Should().NotBeNull(); + + session.BulkCopy("[User]") + .Mapping(map => + { + map.Mapping(u => u.Id, "Id"); + map.Mapping(u => u.EmailAddress, "EmailAddress"); + }) + .Mapping("IsEmailAddressConfirmed", "IsEmailAddressConfirmed") + .Mapping("FirstName", "FirstName") + .Mapping("LastName", "LastName") + .Mapping("DisplayName", "DisplayName") + .Mapping("PasswordHash", "PasswordHash") + .Mapping("ResetHash", "ResetHash") + .Mapping("InviteHash", "InviteHash") + .Mapping("AccessFailedCount", "AccessFailedCount") + .Mapping("LockoutEnabled", "LockoutEnabled") + .Mapping("LockoutEnd", "LockoutEnd") + .Mapping("LastLogin", "LastLogin") + .Mapping("IsDeleted", "IsDeleted") + .Mapping("Created", "Created") + .Mapping("CreatedBy", "CreatedBy") + .Mapping("Updated", "Updated") + .Mapping("UpdatedBy", "UpdatedBy") + .WriteToServer(users); + } + + + private static Faker CreateGenerator() + { + Faker fakerUser = new Faker() + .RuleFor(u => u.Id, f => Guid.NewGuid()) + .RuleFor(u => u.FirstName, (f, u) => f.Name.FirstName()) + .RuleFor(u => u.LastName, (f, u) => f.Name.LastName()) + .RuleFor(u => u.DisplayName, (f, u) => $"{u.FirstName} {u.LastName}") + .RuleFor(u => u.EmailAddress, (f, u) => f.Internet.Email(u.FirstName, u.LastName, uniqueSuffix: $"+{DateTime.Now.Ticks}")) + .RuleFor(u => u.IsEmailAddressConfirmed, f => true) + .RuleFor(u => u.PasswordHash, f => f.Random.AlphaNumeric(10)) + .RuleFor(u => u.ResetHash, f => f.Random.AlphaNumeric(10)) + .RuleFor(u => u.InviteHash, f => f.Random.AlphaNumeric(10)) + .RuleFor(u => u.Created, f => f.Date.Past()) + .RuleFor(u => u.CreatedBy, "UnitTest") + .RuleFor(u => u.Updated, DateTime.Now) + .RuleFor(u => u.UpdatedBy, "UnitTest"); + + return fakerUser; + } +} diff --git a/test/FluentCommand.SqlServer.Tests/DataCacheTests.cs b/test/FluentCommand.SqlServer.Tests/DataCacheTests.cs new file mode 100644 index 00000000..a78b26ba --- /dev/null +++ b/test/FluentCommand.SqlServer.Tests/DataCacheTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; + +using FluentAssertions; + +using FluentCommand.Entities; +using FluentCommand.Query; + +using Microsoft.Extensions.DependencyInjection; + +using Xunit; +using Xunit.Abstractions; + +namespace FluentCommand.SqlServer.Tests; + +public class DataCacheTests : DatabaseTestBase +{ + public DataCacheTests(ITestOutputHelper output, DatabaseFixture databaseFixture) : base(output, databaseFixture) + { + } + + [Fact] + public async System.Threading.Tasks.Task SqlQueryInEntityAsync() + { + await using var session = Services.GetRequiredService(); + session.Should().NotBeNull(); + + var values = new[] { 1, 2, 3 }; + + var results = await session + .Sql(builder => builder + .Select() + .WhereIn(p => p.Id, values) + .Tag() + ) + .UseCache(TimeSpan.FromSeconds(5)) + .QueryAsync(); + + results.Should().NotBeNull(); + + var list = results.ToList(); + list.Count.Should().Be(3); + + var cacheResults = await session + .Sql(builder => builder + .Select() + .WhereIn(p => p.Id, values) + .Tag() + ) + .UseCache(TimeSpan.FromSeconds(5)) + .QueryAsync(); + } + +} diff --git a/test/FluentCommand.SqlServer.Tests/DataCommandProcedureTests.cs b/test/FluentCommand.SqlServer.Tests/DataCommandProcedureTests.cs index a686ce96..ede5c882 100644 --- a/test/FluentCommand.SqlServer.Tests/DataCommandProcedureTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataCommandProcedureTests.cs @@ -1,15 +1,15 @@ using System; -using System.Collections.Generic; using System.Data; using System.Linq; -using DataGenerator; -using DataGenerator.Sources; +using Bogus; using FluentAssertions; using FluentCommand.Entities; +using Microsoft.Extensions.DependencyInjection; + using Xunit; using Xunit.Abstractions; @@ -28,7 +28,7 @@ public void ProcedureQueryParameterOut() var email = "%@battlestar.com"; - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var users = session @@ -59,7 +59,7 @@ public void ProcedureExecuteUpsert() var username = "test." + DateTime.Now.Ticks; var email = username + "@email.com"; - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var user = session @@ -93,7 +93,7 @@ public void ProcedureExecuteReturn() var email = "william.adama@battlestar.com"; - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); session.Should().NotBeNull(); result = session @@ -109,7 +109,7 @@ public void ProcedureExecuteReturn() [Fact] public void ProcedureExecuteTransaction() { - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); session.Should().NotBeNull(); using var transaction = session.BeginTransaction(IsolationLevel.Unspecified); @@ -148,19 +148,15 @@ public void ProcedureExecuteTransaction() [Fact] public void ProcedureExecuteMerge() { - var generator = Generator.Create(c => c - .ExcludeName("xunit") - .Entity(e => - { - e.AutoMap(); - - e.Property(p => p.DisplayName).DataSource(); - e.Property(p => p.EmailAddress).Value(u => $"{u.FirstName}.{u.LastName}.{Guid.NewGuid()}@mailinator.com"); - }) - ); - var users = generator.List(100); - - using var session = GetConfiguration().CreateSession(); + var fakerUser = new Faker() + .RuleFor(u => u.FirstName, (f, u) => f.Name.FirstName()) + .RuleFor(u => u.LastName, (f, u) => f.Name.LastName()) + .RuleFor(u => u.DisplayName, (f, u) => $"{u.FirstName} {u.LastName}") + .RuleFor(u => u.EmailAddress, (f, u) => f.Internet.Email(u.FirstName, u.LastName, uniqueSuffix: $"+{DateTime.Now.Ticks}")); + + var users = fakerUser.Generate(100); + + using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var result = session diff --git a/test/FluentCommand.SqlServer.Tests/DataCommandSqlAsyncTests.cs b/test/FluentCommand.SqlServer.Tests/DataCommandSqlAsyncTests.cs index ec1b7f32..112c0849 100644 --- a/test/FluentCommand.SqlServer.Tests/DataCommandSqlAsyncTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataCommandSqlAsyncTests.cs @@ -8,6 +8,7 @@ using FluentCommand.Extensions; using Microsoft.Data.SqlClient; +using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; @@ -23,7 +24,7 @@ public DataCommandSqlAsyncTests(ITestOutputHelper output, DatabaseFixture databa [Fact] public async System.Threading.Tasks.Task SqlQuerySingleEntityAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -60,7 +61,7 @@ public async System.Threading.Tasks.Task SqlQuerySingleEntityAsync() [Fact] public async System.Threading.Tasks.Task SqlQuerySingleEntityFactoryAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -78,7 +79,7 @@ public async System.Threading.Tasks.Task SqlQuerySingleEntityFactoryAsync() [Fact] public async System.Threading.Tasks.Task SqlQuerySingleEntityFactoryCacheAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -107,7 +108,7 @@ public async System.Threading.Tasks.Task SqlQuerySingleEntityFactoryCacheAsync() [Fact] public async System.Threading.Tasks.Task SqlQuerySingleEntityDynamicAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -124,7 +125,7 @@ public async System.Threading.Tasks.Task SqlQuerySingleEntityDynamicAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryEntityAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -160,7 +161,7 @@ public async System.Threading.Tasks.Task SqlQueryEntityAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryEntityErrorAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -199,7 +200,7 @@ public async System.Threading.Tasks.Task SqlQueryEntityErrorAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryEntityDynamicAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -213,43 +214,12 @@ public async System.Threading.Tasks.Task SqlQueryEntityDynamicAsync() users.Should().NotBeNull(); users.Should().NotBeEmpty(); } - - [Fact] - public async System.Threading.Tasks.Task SqlQueryEntityDynamicCacheAsync() - { - await using var session = GetConfiguration().CreateSession(); - session.Should().NotBeNull(); - - string email = "%@battlestar.com"; - string sql = "select * from [User] where EmailAddress like @EmailAddress"; - - var users = await session - .Sql(sql) - .Parameter("@EmailAddress", email) - .UseCache(TimeSpan.FromMinutes(5)) - .QueryAsync(); - - var userList = users.ToList(); - - userList.Should().NotBeNull(); - userList.Should().NotBeEmpty(); - - var cachedUsers = await session - .Sql(sql) - .Parameter("@EmailAddress", email) - .UseCache(TimeSpan.FromMinutes(5)) - .QueryAsync(); - - var cachedList = cachedUsers.ToList(); - - cachedList.Should().NotBeNull(); - cachedList.Should().NotBeEmpty(); - } + [Fact] public async System.Threading.Tasks.Task SqlQueryEntityFactoryAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -266,7 +236,7 @@ public async System.Threading.Tasks.Task SqlQueryEntityFactoryAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryTableAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -282,7 +252,7 @@ public async System.Threading.Tasks.Task SqlQueryTableAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryValueAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -298,7 +268,7 @@ public async System.Threading.Tasks.Task SqlQueryValueAsync() [Fact] public async System.Threading.Tasks.Task SqlReaderAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -335,7 +305,7 @@ public async System.Threading.Tasks.Task SqlQueryMultipleAsync() List roles = null; List priorities = null; - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); await session.Sql(sql) diff --git a/test/FluentCommand.SqlServer.Tests/DataCommandSqlTests.cs b/test/FluentCommand.SqlServer.Tests/DataCommandSqlTests.cs index 2e96d7d4..7d68f1b0 100644 --- a/test/FluentCommand.SqlServer.Tests/DataCommandSqlTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataCommandSqlTests.cs @@ -8,6 +8,7 @@ using FluentCommand.Extensions; using Microsoft.Data.SqlClient; +using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; @@ -23,7 +24,7 @@ public DataCommandSqlTests(ITestOutputHelper output, DatabaseFixture databaseFix [Fact] public void SqlQuerySingleEntity() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -59,7 +60,7 @@ public void SqlQuerySingleEntity() [Fact] public void SqlQuerySingleEntityFactory() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -76,7 +77,7 @@ public void SqlQuerySingleEntityFactory() [Fact] public void SqlQuerySingleEntityFactoryCache() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -103,7 +104,7 @@ public void SqlQuerySingleEntityFactoryCache() [Fact] public void SqlQuerySingleEntityDynamic() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -120,7 +121,7 @@ public void SqlQuerySingleEntityDynamic() [Fact] public void SqlQueryEntity() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -156,7 +157,7 @@ public void SqlQueryEntity() [Fact] public void SqlQueryEntityError() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -194,7 +195,7 @@ public void SqlQueryEntityError() [Fact] public void SqlQueryEntityDynamic() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -208,40 +209,10 @@ public void SqlQueryEntityDynamic() users.Should().NotBeEmpty(); } - [Fact] - public void SqlQueryEntityDynamicCache() - { - var session = GetConfiguration().CreateSession(); - session.Should().NotBeNull(); - - string email = "%@battlestar.com"; - string sql = "select * from [User] where EmailAddress like @EmailAddress"; - - var users = session - .Sql(sql) - .Parameter("@EmailAddress", email) - .UseCache(TimeSpan.FromMinutes(5)) - .Query() - .ToList(); - - users.Should().NotBeNull(); - users.Should().NotBeEmpty(); - - var cachedUsers = session - .Sql(sql) - .Parameter("@EmailAddress", email) - .UseCache(TimeSpan.FromMinutes(5)) - .Query() - .ToList(); - - cachedUsers.Should().NotBeNull(); - cachedUsers.Should().NotBeEmpty(); - } - [Fact] public void SqlQueryEntityFactory() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -258,7 +229,7 @@ public void SqlQueryEntityFactory() [Fact] public void SqlQueryTable() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -274,7 +245,7 @@ public void SqlQueryTable() [Fact] public void SqlQueryValue() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -290,7 +261,7 @@ public void SqlQueryValue() [Fact] public void SqlReader() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; @@ -326,18 +297,18 @@ public void SqlQueryMultiple() List roles = null; List priorities = null; - using (var session = GetConfiguration().CreateSession()) - { - session.Should().NotBeNull(); - session.Sql(sql) - .Parameter("@EmailAddress", email) - .QueryMultiple(q => - { - user = q.QuerySingle(); - roles = q.Query().ToList(); - priorities = q.Query().ToList(); - }); - } + using var session = Services.GetRequiredService(); + session.Should().NotBeNull(); + + session.Sql(sql) + .Parameter("@EmailAddress", email) + .QueryMultiple(q => + { + user = q.QuerySingle(); + roles = q.Query().ToList(); + priorities = q.Query().ToList(); + }); + user.Should().NotBeNull(); user.EmailAddress.Should().NotBeEmpty(); @@ -353,7 +324,7 @@ public void SqlQueryMultiple() [Fact] public void SqlQueryEntityTimeOut() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "%@battlestar.com"; diff --git a/test/FluentCommand.SqlServer.Tests/DataMergeGeneratorTests.cs b/test/FluentCommand.SqlServer.Tests/DataMergeGeneratorTests.cs new file mode 100644 index 00000000..106741d1 --- /dev/null +++ b/test/FluentCommand.SqlServer.Tests/DataMergeGeneratorTests.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; + +using FluentAssertions; + +using FluentCommand.Entities; +using FluentCommand.Merge; + +using Xunit; +using Xunit.Abstractions; + +namespace FluentCommand.SqlServer.Tests; + +public class DataMergeGeneratorTests +{ + public DataMergeGeneratorTests(ITestOutputHelper output) + { + Output = output; + } + + public ITestOutputHelper Output { get; } + + [Fact] + public void ParseIdentifier() + { + string result = DataMergeGenerator.ParseIdentifier("[Name]"); + result.Should().Be("Name"); + + result = DataMergeGenerator.ParseIdentifier("[Name"); + result.Should().Be("[Name"); + + result = DataMergeGenerator.ParseIdentifier("Name]"); + result.Should().Be("Name]"); + + result = DataMergeGenerator.ParseIdentifier("[Nam]e]"); + result.Should().Be("Nam]e"); + + result = DataMergeGenerator.ParseIdentifier("[]"); + result.Should().Be(""); + + result = DataMergeGenerator.ParseIdentifier("B"); + result.Should().Be("B"); + } + + [Fact] + public void QuoteIdentifier() + { + string result = DataMergeGenerator.QuoteIdentifier("[Name]"); + result.Should().Be("[Name]"); + + result = DataMergeGenerator.QuoteIdentifier("Name"); + result.Should().Be("[Name]"); + + result = DataMergeGenerator.QuoteIdentifier("[Name"); + result.Should().Be("[[Name]"); + + result = DataMergeGenerator.QuoteIdentifier("Name]"); + result.Should().Be("[Name]]]"); + + result = DataMergeGenerator.QuoteIdentifier("Nam]e"); + result.Should().Be("[Nam]]e]"); + + result = DataMergeGenerator.QuoteIdentifier(""); + result.Should().Be("[]"); + + result = DataMergeGenerator.QuoteIdentifier("B"); + result.Should().Be("[B]"); + + } + + [Fact] + public void BuildMergeTests() + { + var definition = new DataMergeDefinition(); + + DataMergeDefinition.AutoMap(definition); + definition.Columns.Should().NotBeNullOrEmpty(); + + definition.TargetTable = "dbo.User"; + + var column = definition.Columns.Find(c => c.SourceColumn == "EmailAddress"); + column.Should().NotBeNull(); + + column.IsKey = true; + column.CanUpdate = false; + + var sql = DataMergeGenerator.BuildMerge(definition); + sql.Should().NotBeNullOrEmpty(); + + Output.WriteLine("MergeStatement:"); + Output.WriteLine(sql); + } + + + [Fact] + public void BuildMergeDataTests() + { + var definition = new DataMergeDefinition(); + + DataMergeDefinition.AutoMap(definition); + definition.Columns.Should().NotBeNullOrEmpty(); + + definition.TargetTable = "dbo.User"; + + var column = definition.Columns.Find(c => c.SourceColumn == "EmailAddress"); + column.Should().NotBeNull(); + + column.IsKey = true; + column.CanUpdate = false; + + var users = new List + { + new UserImport + { + EmailAddress = "test@email.com", + DisplayName = "Test User", + FirstName = "Test", + LastName = "User" + }, + new UserImport + { + EmailAddress = "blah@email.com", + DisplayName = "Blah User", + FirstName = "Blah", + LastName = "User" + } + }; + + var listDataReader = new ListDataReader(users); + + var sql = DataMergeGenerator.BuildMerge(definition, listDataReader); + sql.Should().NotBeNullOrEmpty(); + + Output.WriteLine("MergeStatement:"); + Output.WriteLine(sql); + } + [Fact] + public void BuildMergeDataTypeTests() + { + var definition = new DataMergeDefinition(); + + DataMergeDefinition.AutoMap(definition); + definition.Columns.Should().NotBeNullOrEmpty(); + + definition.TargetTable = "dbo.DataType"; + + var column = definition.Columns.Find(c => c.SourceColumn == "Id"); + column.Should().NotBeNull(); + + column.IsKey = true; + column.CanUpdate = false; + + var users = new List + { + new DataType + { + Id = 1, + Name = "Test1", + Boolean = false, + Short = 2, + Long = 200, + Float = 200.20F, + Double = 300.35, + Decimal = 456.12M, + DateTime = DateTime.Now, + DateTimeOffset = DateTimeOffset.Now, + Guid = Guid.Empty, + TimeSpan = TimeSpan.FromHours(1), + DateOnly = new DateOnly(2022, 12, 1), + TimeOnly = new TimeOnly(1, 30, 0), + BooleanNull = false, + ShortNull = 2, + LongNull = 200, + FloatNull = 200.20F, + DoubleNull = 300.35, + DecimalNull = 456.12M, + DateTimeNull = DateTime.Now, + DateTimeOffsetNull = DateTimeOffset.Now, + GuidNull = Guid.Empty, + TimeSpanNull = TimeSpan.FromHours(1), + DateOnlyNull = new DateOnly(2022, 12, 1), + TimeOnlyNull = new TimeOnly(1, 30, 0), + }, + new DataType + { + Id = 2, + Name = "Test2", + Boolean = true, + Short = 3, + Long = 400, + Float = 600.20F, + Double = 700.35, + Decimal = 856.12M, + DateTime = DateTime.Now, + DateTimeOffset = DateTimeOffset.Now, + Guid = Guid.Empty, + TimeSpan = TimeSpan.FromHours(2), + DateOnly = new DateOnly(2022, 12, 12), + TimeOnly = new TimeOnly(6, 30, 0), + } + }; + + var listDataReader = new ListDataReader(users); + + var sql = DataMergeGenerator.BuildMerge(definition, listDataReader); + sql.Should().NotBeNullOrEmpty(); + + Output.WriteLine("MergeStatement:"); + Output.WriteLine(sql); + } + + [Fact] + public void BuildMergeDataOutputTests() + { + var definition = new DataMergeDefinition(); + + DataMergeDefinition.AutoMap(definition); + definition.Columns.Should().NotBeNullOrEmpty(); + + definition.IncludeOutput = true; + definition.TargetTable = "dbo.User"; + + var column = definition.Columns.Find(c => c.SourceColumn == "EmailAddress"); + column.Should().NotBeNull(); + + column.IsKey = true; + column.CanUpdate = false; + + var users = new List + { + new UserImport + { + EmailAddress = "test@email.com", + DisplayName = "Test User", + FirstName = "Test", + LastName = "User" + }, + new UserImport + { + EmailAddress = "blah@email.com", + DisplayName = "Blah User", + FirstName = "Blah", + LastName = "User" + }, + new UserImport + { + EmailAddress = $"random.{DateTime.Now.Ticks}@email.com", + DisplayName = "Random User", + FirstName = "Random", + LastName = "User" + } + }; + + var dataTable = new ListDataReader(users); + + var sql = DataMergeGenerator.BuildMerge(definition, dataTable); + sql.Should().NotBeNullOrEmpty(); + + Output.WriteLine("MergeStatement:"); + Output.WriteLine(sql); + } + +} diff --git a/test/FluentCommand.SqlServer.Tests/DataMergeTests.cs b/test/FluentCommand.SqlServer.Tests/DataMergeTests.cs index 98ddfb3e..e1917932 100644 --- a/test/FluentCommand.SqlServer.Tests/DataMergeTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataMergeTests.cs @@ -1,14 +1,15 @@ using System; using System.Linq; -using DataGenerator; -using DataGenerator.Sources; +using Bogus; using FluentAssertions; using FluentCommand.Entities; using FluentCommand.Merge; +using Microsoft.Extensions.DependencyInjection; + using Xunit; using Xunit.Abstractions; @@ -24,10 +25,10 @@ public DataMergeTests(ITestOutputHelper output, DatabaseFixture databaseFixture) [Fact] public void ExecuteTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); var result = session .MergeData("dbo.User") .Map(m => m @@ -42,10 +43,10 @@ public void ExecuteTest() [Fact] public void ExecuteOutputTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); var result = session .MergeData("dbo.User") .Map(m => m @@ -61,10 +62,10 @@ public void ExecuteOutputTest() [Fact] public void ExecuteBulkCopyTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - using var session = GetConfiguration().CreateSession(); + using var session = Services.GetRequiredService(); var result = session .MergeData("dbo.User") .Mode(DataMergeMode.BulkCopy) @@ -91,10 +92,10 @@ public void ExecuteBulkCopyTest() [Fact] public async System.Threading.Tasks.Task ExecuteAsyncTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); var result = await session .MergeData("dbo.User") .Map(m => m @@ -109,10 +110,10 @@ public async System.Threading.Tasks.Task ExecuteAsyncTest() [Fact] public async System.Threading.Tasks.Task ExecuteOutputAsyncTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); var changes = await session .MergeData("dbo.User") @@ -130,10 +131,10 @@ public async System.Threading.Tasks.Task ExecuteOutputAsyncTest() [Fact] public async System.Threading.Tasks.Task ExecuteAsyncBulkCopyTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); var result = await session .MergeData("dbo.User") .Mode(DataMergeMode.BulkCopy) @@ -161,10 +162,10 @@ public async System.Threading.Tasks.Task ExecuteAsyncBulkCopyTest() [Fact] public async System.Threading.Tasks.Task ExecuteAsyncBulkCopyAutoTest() { - var generator = UserImportGenerator(); - var users = generator.List(100); + var generator = CreateGenerator(); + var users = generator.Generate(100); - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); var result = await session .MergeData("dbo.User") .Mode(DataMergeMode.BulkCopy) @@ -178,19 +179,14 @@ public async System.Threading.Tasks.Task ExecuteAsyncBulkCopyAutoTest() result.Should().Be(100); } - private static Generator UserImportGenerator() + private static Faker CreateGenerator() { - var generator = Generator.Create(c => c - .ExcludeName("xunit") - .Entity(e => - { - e.AutoMap(); - - e.Property(p => p.DisplayName).DataSource(); - e.Property(p => p.EmailAddress).Value(u => $"test+{DateTime.Now.Ticks}@mailinator.com"); - }) - ); + var fakerUser = new Faker() + .RuleFor(u => u.FirstName, (f, u) => f.Name.FirstName()) + .RuleFor(u => u.LastName, (f, u) => f.Name.LastName()) + .RuleFor(u => u.DisplayName, (f, u) => $"{u.FirstName} {u.LastName}") + .RuleFor(u => u.EmailAddress, (f, u) => f.Internet.Email(u.FirstName, u.LastName, uniqueSuffix: $"+{DateTime.Now.Ticks}")); - return generator; + return fakerUser; } } diff --git a/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs b/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs index afd9e5d7..7331cf2e 100644 --- a/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataQueryTests.cs @@ -7,6 +7,8 @@ using FluentCommand.Extensions; using FluentCommand.Query; +using Microsoft.Extensions.DependencyInjection; + using Xunit; using Xunit.Abstractions; @@ -21,7 +23,7 @@ public DataQueryTests(ITestOutputHelper output, DatabaseFixture databaseFixture) [Fact] public async System.Threading.Tasks.Task SqlQuerySingleEntityAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -59,7 +61,7 @@ public async System.Threading.Tasks.Task SqlQuerySingleEntityAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryCountAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); string email = "kara.thrace@battlestar.com"; @@ -78,7 +80,7 @@ public async System.Threading.Tasks.Task SqlQueryCountAsync() [Fact] public async System.Threading.Tasks.Task SqlQuerySumAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var count = await session @@ -94,7 +96,7 @@ public async System.Threading.Tasks.Task SqlQuerySumAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryValuesAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var ids = await session @@ -110,7 +112,7 @@ public async System.Threading.Tasks.Task SqlQueryValuesAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryInEntityAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var values = new[] { 1, 2, 3 }; @@ -132,7 +134,7 @@ public async System.Threading.Tasks.Task SqlQueryInEntityAsync() [Fact] public async System.Threading.Tasks.Task SqlQueryInComplexEntityAsync() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var values = new[] { 1, 2, 3 }.ToDelimitedString(); @@ -171,7 +173,7 @@ public async System.Threading.Tasks.Task SqlQueryInComplexEntityAsync() [Fact] public async System.Threading.Tasks.Task SqlInsertValueQuery() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var id = Guid.NewGuid(); @@ -195,7 +197,7 @@ public async System.Threading.Tasks.Task SqlInsertValueQuery() [Fact] public async System.Threading.Tasks.Task SqlInsertEntityQuery() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var id = Guid.NewGuid(); @@ -226,7 +228,7 @@ public async System.Threading.Tasks.Task SqlInsertEntityQuery() [Fact] public async System.Threading.Tasks.Task SqlInsertUpdateDeleteEntityQuery() { - await using var session = GetConfiguration().CreateSession(); + await using var session = Services.GetRequiredService(); session.Should().NotBeNull(); var id = Guid.NewGuid(); diff --git a/test/FluentCommand.SqlServer.Tests/DataSessionTests.cs b/test/FluentCommand.SqlServer.Tests/DataSessionTests.cs index ab6d2e43..da797d09 100644 --- a/test/FluentCommand.SqlServer.Tests/DataSessionTests.cs +++ b/test/FluentCommand.SqlServer.Tests/DataSessionTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using Microsoft.Data.SqlClient; +using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; @@ -18,7 +19,7 @@ public DataSessionTests(ITestOutputHelper output, DatabaseFixture databaseFixtur [Fact] public void CreateConnectionName() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); session.Connection.Should().NotBeNull(); session.Connection.State.Should().Be(ConnectionState.Closed); @@ -37,7 +38,7 @@ public void CreateConnection() [Fact] public void EnsureConnectionByName() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); session.Connection.Should().NotBeNull(); session.Connection.State.Should().Be(ConnectionState.Closed); diff --git a/test/FluentCommand.SqlServer.Tests/DatabaseFixture.cs b/test/FluentCommand.SqlServer.Tests/DatabaseFixture.cs index 6b74bf32..5e3e3666 100644 --- a/test/FluentCommand.SqlServer.Tests/DatabaseFixture.cs +++ b/test/FluentCommand.SqlServer.Tests/DatabaseFixture.cs @@ -1,107 +1,50 @@ -using System; -using System.IO; -using System.Reflection; -using System.Text; - -using DbUp; -using DbUp.Engine.Output; +using FluentCommand.Caching; +using FluentCommand.Query.Generators; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; -using Xunit.Abstractions; +using XUnit.Hosting; namespace FluentCommand.SqlServer.Tests; -public class DatabaseFixture : IUpgradeLog, IDisposable +public class DatabaseFixture : TestHostFixture { - private readonly StringBuilder _buffer; - private readonly StringWriter _logger; - - public DatabaseFixture() - { - _buffer = new StringBuilder(); - _logger = new StringWriter(_buffer); - - ResolveConnectionString(); - - CreateDatabase(); - } - - - public string ConnectionString { get; set; } - - public string ConnectionName { get; set; } = "Tracker"; - - - private void CreateDatabase() - { - EnsureDatabase.For - .SqlDatabase(ConnectionString, this); - - var upgradeEngine = DeployChanges.To - .SqlDatabase(ConnectionString) - .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()) - .LogTo(this) - .Build(); - - var result = upgradeEngine.PerformUpgrade(); - - if (result.Successful) - return; - - _logger.WriteLine($"Exception: '{result.Error}'"); - - throw result.Error; - } - - private void ResolveConnectionString() - { - var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Test"; - var builder = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{environmentName}.json", true) - .AddEnvironmentVariables(); - - var configuration = builder.Build(); - - ConnectionString = configuration.GetConnectionString(ConnectionName); - } - - - public void Report(ITestOutputHelper output) - { - if (_buffer.Length == 0) - return; - - _logger.Flush(); - output.WriteLine(_logger.ToString()); - - // reset logger - _buffer.Clear(); - } - - public void Dispose() - { - - } - - - public void WriteInformation(string format, params object[] args) - { - _logger.Write("INFO : "); - _logger.WriteLine(format, args); - } - - public void WriteError(string format, params object[] args) - { - _logger.Write("ERROR: "); - _logger.WriteLine(format, args); - } - - public void WriteWarning(string format, params object[] args) - { - _logger.Write("WARN : "); - _logger.WriteLine(format, args); + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + var trackerConnnection = context.Configuration.GetConnectionString("Tracker"); + var cacheConnection = context.Configuration.GetConnectionString("DistributedCache"); + + services.AddHostedService(); + + services.AddStackExchangeRedisCache(options => + { + options.Configuration = cacheConnection; + options.InstanceName = "FluentCommand"; + }); + + services.TryAddSingleton(sp => new MessagePackCacheSerializer()); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddSingleton(sp => + new DataConfiguration( + SqlClientFactory.Instance, + trackerConnnection, + sp.GetService(), + sp.GetService(), + sp.GetService() + ) + ); + + services.TryAddTransient(sp => + new DataSession(sp.GetRequiredService()) + ); } } diff --git a/test/FluentCommand.SqlServer.Tests/DatabaseInitializer.cs b/test/FluentCommand.SqlServer.Tests/DatabaseInitializer.cs new file mode 100644 index 00000000..2dee5875 --- /dev/null +++ b/test/FluentCommand.SqlServer.Tests/DatabaseInitializer.cs @@ -0,0 +1,65 @@ +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using DbUp; +using DbUp.Engine.Output; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace FluentCommand.SqlServer.Tests; + +public class DatabaseInitializer : IHostedService, IUpgradeLog +{ + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public DatabaseInitializer(ILogger logger, IConfiguration configuration) + { + _logger = logger; + _configuration = configuration; + } + + + public Task StartAsync(CancellationToken cancellationToken) + { + var connectionString = _configuration.GetConnectionString("Tracker"); + + EnsureDatabase.For.SqlDatabase(connectionString, this); + + var upgradeEngine = DeployChanges.To + .SqlDatabase(connectionString) + .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()) + .LogTo(this) + .Build(); + + var result = upgradeEngine.PerformUpgrade(); + + return result.Successful + ? Task.CompletedTask + : Task.FromException(result.Error); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + + public void WriteError(string format, params object[] args) + { + _logger.LogError(format, args); + } + + public void WriteInformation(string format, params object[] args) + { + _logger.LogInformation(format, args); + } + + public void WriteWarning(string format, params object[] args) + { + _logger.LogWarning(format, args); + } +} diff --git a/test/FluentCommand.SqlServer.Tests/DatabaseQueryLogger.cs b/test/FluentCommand.SqlServer.Tests/DatabaseQueryLogger.cs new file mode 100644 index 00000000..6002dd94 --- /dev/null +++ b/test/FluentCommand.SqlServer.Tests/DatabaseQueryLogger.cs @@ -0,0 +1,22 @@ +using System; +using System.Data; + +using Microsoft.Extensions.Logging; + +namespace FluentCommand.SqlServer.Tests; + +public class DatabaseQueryLogger : DataQueryLogger +{ + private readonly ILogger _logger; + + public DatabaseQueryLogger(ILogger logger) + { + _logger = logger; + } + + public override void LogCommand(IDbCommand command, TimeSpan duration, Exception exception = null, object state = null) + { + var message = FormatCommand(command, duration, exception); + _logger.LogInformation(exception, message); + } +} diff --git a/test/FluentCommand.SqlServer.Tests/DatabaseTestBase.cs b/test/FluentCommand.SqlServer.Tests/DatabaseTestBase.cs index c52e5568..eeb2afe7 100644 --- a/test/FluentCommand.SqlServer.Tests/DatabaseTestBase.cs +++ b/test/FluentCommand.SqlServer.Tests/DatabaseTestBase.cs @@ -1,38 +1,15 @@ -using System; - -using Microsoft.Data.SqlClient; - using Xunit; using Xunit.Abstractions; +using XUnit.Hosting; + namespace FluentCommand.SqlServer.Tests; [Collection(DatabaseCollection.CollectionName)] -public abstract class DatabaseTestBase : IDisposable +public abstract class DatabaseTestBase : TestHostBase { protected DatabaseTestBase(ITestOutputHelper output, DatabaseFixture databaseFixture) + : base(output, databaseFixture) { - Output = output; - Fixture = databaseFixture; - } - - - public ITestOutputHelper Output { get; } - - public DatabaseFixture Fixture { get; } - - - protected IDataConfiguration GetConfiguration() - { - var dataLogger = new DataQueryLogger(Output.WriteLine); - return new DataConfiguration( - SqlClientFactory.Instance, - Fixture.ConnectionString, - queryLogger: dataLogger); - } - - public void Dispose() - { - Fixture?.Report(Output); } } diff --git a/test/FluentCommand.SqlServer.Tests/FluentCommand.SqlServer.Tests.csproj b/test/FluentCommand.SqlServer.Tests/FluentCommand.SqlServer.Tests.csproj index 4753b9c2..4e0dc89c 100644 --- a/test/FluentCommand.SqlServer.Tests/FluentCommand.SqlServer.Tests.csproj +++ b/test/FluentCommand.SqlServer.Tests/FluentCommand.SqlServer.Tests.csproj @@ -18,25 +18,27 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + + + + all runtime; build; native; contentfiles; analyzers - + + diff --git a/test/FluentCommand.SqlServer.Tests/GeneratorTests.cs b/test/FluentCommand.SqlServer.Tests/GeneratorTests.cs index e24f3b5d..64aad80a 100644 --- a/test/FluentCommand.SqlServer.Tests/GeneratorTests.cs +++ b/test/FluentCommand.SqlServer.Tests/GeneratorTests.cs @@ -3,6 +3,8 @@ using FluentCommand.Entities; using FluentCommand.Query; +using Microsoft.Extensions.DependencyInjection; + using Xunit; using Xunit.Abstractions; @@ -20,7 +22,7 @@ public GeneratorTests(ITestOutputHelper output, DatabaseFixture databaseFixture) [Fact] public async Task QuerySelectStatusAsync() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); var results = await session @@ -37,7 +39,7 @@ public async Task QuerySelectStatusAsync() [Fact] public async Task QuerySelectStatusRecordAsync() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); var results = await session @@ -54,7 +56,7 @@ public async Task QuerySelectStatusRecordAsync() [Fact] public async Task QuerySelectStatusReadOnlyAsync() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); var results = await session @@ -71,7 +73,7 @@ public async Task QuerySelectStatusReadOnlyAsync() [Fact] public async Task QuerySelectStatusConstructorAsync() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); var results = await session diff --git a/test/FluentCommand.SqlServer.Tests/JsonTests.cs b/test/FluentCommand.SqlServer.Tests/JsonTests.cs index b75d173f..c250ec94 100644 --- a/test/FluentCommand.SqlServer.Tests/JsonTests.cs +++ b/test/FluentCommand.SqlServer.Tests/JsonTests.cs @@ -3,6 +3,8 @@ using FluentCommand.Entities; using FluentCommand.Query; +using Microsoft.Extensions.DependencyInjection; + using Xunit; using Xunit.Abstractions; @@ -21,7 +23,7 @@ public JsonTests(ITestOutputHelper output, DatabaseFixture databaseFixture) [Fact] public void QueryJson() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string sql = "select TOP 1000 * from [User]"; @@ -35,7 +37,7 @@ public void QueryJson() [Fact] public async Task QueryJsonAsync() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); string sql = "select TOP 1000 * from [User]"; @@ -50,7 +52,7 @@ public async Task QueryJsonAsync() [Fact] public async Task QuerySelectAsync() { - var session = GetConfiguration().CreateSession(); + var session = Services.GetRequiredService(); session.Should().NotBeNull(); var json = await session diff --git a/test/FluentCommand.SqlServer.Tests/appsettings.json b/test/FluentCommand.SqlServer.Tests/appsettings.json index 258688f2..d6831a14 100644 --- a/test/FluentCommand.SqlServer.Tests/appsettings.json +++ b/test/FluentCommand.SqlServer.Tests/appsettings.json @@ -1,5 +1,6 @@ { "ConnectionStrings": { - "Tracker": "Data Source=(local);Initial Catalog=TrackerTest;Integrated Security=True;TrustServerCertificate=True" + "Tracker": "Data Source=(local);Initial Catalog=TrackerTest;Integrated Security=True;TrustServerCertificate=True", + "DistributedCache": "localhost:6379" } } diff --git a/test/FluentCommand.Tests/FluentCommand.Tests.csproj b/test/FluentCommand.Tests/FluentCommand.Tests.csproj index 4f0c3ecc..2f168598 100644 --- a/test/FluentCommand.Tests/FluentCommand.Tests.csproj +++ b/test/FluentCommand.Tests/FluentCommand.Tests.csproj @@ -11,14 +11,14 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + all runtime; build; native; contentfiles; analyzers - +