diff --git a/README.md b/README.md index 16cd6e76..dcfd39ca 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,9 @@ To run the tests, use Test Explorer in Visual Studio or run from the root folder dotnet test ``` +Please notice: to run the tests, you'll need a running Docker instance on your sistem. +Integration tests (identified by traits) on MS Sql, MySql, Postgres, use Docker to spin the integration database. + ## JS UI assets Tests are located inside src/Serilog.Ui.Web/assets/__tests__ diff --git a/Serilog.Ui.sln b/Serilog.Ui.sln index 62660dc1..c2de4980 100644 --- a/Serilog.Ui.sln +++ b/Serilog.Ui.sln @@ -29,15 +29,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.MongoDbProvider. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.Common.Tests", "tests\Serilog.Ui.Common.Tests\Serilog.Ui.Common.Tests.csproj", "{96689D04-8B2F-4C06-AE1D-3483B1508D59}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Ui.PostgreSqlProvider.Tests", "tests\Serilog.Ui.PostgreSqlProvider.Tests\Serilog.Ui.PostgreSqlProvider.Tests.csproj", "{D1688095-7E1F-42B2-BC3F-8CB29975CAD5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.PostgreSqlProvider.Tests", "tests\Serilog.Ui.PostgreSqlProvider.Tests\Serilog.Ui.PostgreSqlProvider.Tests.csproj", "{D1688095-7E1F-42B2-BC3F-8CB29975CAD5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.Web.Tests", "tests\Serilog.Ui.Web.Tests\Serilog.Ui.Web.Tests.csproj", "{9AD9BF6A-0CB0-467C-BB84-BE1C701C0113}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Ui.MySqlProvider.Tests", "tests\Serilog.Ui.MySqlProvider.Tests\Serilog.Ui.MySqlProvider.Tests.csproj", "{6986AD9C-3B9E-4DAF-A45F-643D964CEBD3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.MySqlProvider.Tests", "tests\Serilog.Ui.MySqlProvider.Tests\Serilog.Ui.MySqlProvider.Tests.csproj", "{6986AD9C-3B9E-4DAF-A45F-643D964CEBD3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Ui.MsSqlServerProvider.Tests", "tests\Serilog.Ui.MsSqlServerProvider.Tests\Serilog.Ui.MsSqlServerProvider.Tests.csproj", "{1F6675BC-32FC-47DE-96AB-422632AB2730}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.MsSqlServerProvider.Tests", "tests\Serilog.Ui.MsSqlServerProvider.Tests\Serilog.Ui.MsSqlServerProvider.Tests.csproj", "{1F6675BC-32FC-47DE-96AB-422632AB2730}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Ui.ElastichSearchProvider.Tests", "tests\Serilog.Ui.ElastichSearchProvider.Tests\Serilog.Ui.ElastichSearchProvider.Tests.csproj", "{1AB759E4-61CD-4195-9CA9-E70B63AF28B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.ElasticSearchProvider.Tests", "tests\Serilog.Ui.ElastichSearchProvider.Tests\Serilog.Ui.ElasticSearchProvider.Tests.csproj", "{1AB759E4-61CD-4195-9CA9-E70B63AF28B9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{83E91BE7-19B3-4AE0-992C-9DFF30FC409E}" ProjectSection(SolutionItems) = preProject diff --git a/src/Serilog.Ui.ElasticSearchProvider/Extensions/SerilogUiOptionBuilderExtensions.cs b/src/Serilog.Ui.ElasticSearchProvider/Extensions/SerilogUiOptionBuilderExtensions.cs index 7792aa57..466b955c 100644 --- a/src/Serilog.Ui.ElasticSearchProvider/Extensions/SerilogUiOptionBuilderExtensions.cs +++ b/src/Serilog.Ui.ElasticSearchProvider/Extensions/SerilogUiOptionBuilderExtensions.cs @@ -1,46 +1,46 @@ -using Elasticsearch.Net; -using Microsoft.Extensions.DependencyInjection; -using Nest; -using Serilog.Ui.Core; -using System; - -namespace Serilog.Ui.ElasticSearchProvider -{ - /// - /// ElasticSearch data provider specific extension methods for . - /// - public static class SerilogUiOptionBuilderExtensions - { - /// - /// Configures the SerilogUi to connect to a MongoDB database. - /// - /// The options builder. - /// The url of ElasticSearch server. - /// Name of the log index. - /// throw if endpoint is null - /// throw is indexName is null - public static void UseElasticSearchDb(this SerilogUiOptionsBuilder optionsBuilder, Uri endpoint, string indexName) - { - if (endpoint == null) - throw new ArgumentNullException(nameof(endpoint)); - - if (string.IsNullOrEmpty(indexName)) - throw new ArgumentNullException(nameof(indexName)); - - var options = new ElasticSearchDbOptions - { - IndexName = indexName - }; - - var builder = ((ISerilogUiOptionsBuilder)optionsBuilder); - - builder.Services.AddSingleton(options); - - var pool = new SingleNodeConnectionPool(endpoint); - var connectionSettings = new ConnectionSettings(pool, sourceSerializer: (builtin, values) => new VanillaSerializer()); - - builder.Services.AddSingleton(o => new ElasticClient(connectionSettings)); - builder.Services.AddScoped(); - } - } +using Elasticsearch.Net; +using Microsoft.Extensions.DependencyInjection; +using Nest; +using Serilog.Ui.Core; +using System; + +namespace Serilog.Ui.ElasticSearchProvider +{ + /// + /// ElasticSearch data provider specific extension methods for . + /// + public static class SerilogUiOptionBuilderExtensions + { + /// + /// Configures the SerilogUi to connect to a MongoDB database. + /// + /// The options builder. + /// The url of ElasticSearch server. + /// Name of the log index. + /// throw if endpoint is null + /// throw is indexName is null + public static void UseElasticSearchDb(this SerilogUiOptionsBuilder optionsBuilder, Uri endpoint, string indexName) + { + if (endpoint == null) + throw new ArgumentNullException(nameof(endpoint)); + + if (string.IsNullOrWhiteSpace(indexName)) + throw new ArgumentNullException(nameof(indexName)); + + var options = new ElasticSearchDbOptions + { + IndexName = indexName + }; + + var builder = ((ISerilogUiOptionsBuilder)optionsBuilder); + + builder.Services.AddSingleton(options); + + var pool = new SingleNodeConnectionPool(endpoint); + var connectionSettings = new ConnectionSettings(pool, sourceSerializer: (builtin, values) => new VanillaSerializer()); + + builder.Services.AddSingleton(o => new ElasticClient(connectionSettings)); + builder.Services.AddScoped(); + } + } } \ No newline at end of file diff --git a/src/Serilog.Ui.ElasticSearchProvider/Serilog.Ui.ElasticSearchProvider.csproj b/src/Serilog.Ui.ElasticSearchProvider/Serilog.Ui.ElasticSearchProvider.csproj index 88e3190f..c5b33c99 100644 --- a/src/Serilog.Ui.ElasticSearchProvider/Serilog.Ui.ElasticSearchProvider.csproj +++ b/src/Serilog.Ui.ElasticSearchProvider/Serilog.Ui.ElasticSearchProvider.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Serilog.Ui.MongoDbProvider/Extensions/SerilogUiOptionBuilderExtensions.cs b/src/Serilog.Ui.MongoDbProvider/Extensions/SerilogUiOptionBuilderExtensions.cs index 961e9f20..babb7a03 100644 --- a/src/Serilog.Ui.MongoDbProvider/Extensions/SerilogUiOptionBuilderExtensions.cs +++ b/src/Serilog.Ui.MongoDbProvider/Extensions/SerilogUiOptionBuilderExtensions.cs @@ -25,16 +25,20 @@ public static void UseMongoDb( string collectionName ) { - if (string.IsNullOrEmpty(connectionString)) + if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString)); - if (string.IsNullOrEmpty(collectionName)) + if (string.IsNullOrWhiteSpace(collectionName)) throw new ArgumentNullException(nameof(collectionName)); + var databaseName = MongoUrl.Create(connectionString).DatabaseName; + + if (string.IsNullOrWhiteSpace(databaseName)) throw new ArgumentException(nameof(MongoUrl.DatabaseName)); + var mongoProvider = new MongoDbOptions { ConnectionString = connectionString, - DatabaseName = MongoUrl.Create(connectionString).DatabaseName, + DatabaseName = databaseName, CollectionName = collectionName }; @@ -60,13 +64,13 @@ public static void UseMongoDb( string collectionName ) { - if (string.IsNullOrEmpty(connectionString)) + if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString)); - if (string.IsNullOrEmpty(databaseName)) + if (string.IsNullOrWhiteSpace(databaseName)) throw new ArgumentNullException(nameof(databaseName)); - if (string.IsNullOrEmpty(collectionName)) + if (string.IsNullOrWhiteSpace(collectionName)) throw new ArgumentNullException(nameof(collectionName)); var mongoProvider = new MongoDbOptions @@ -77,7 +81,7 @@ string collectionName }; ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(mongoProvider); - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(o => new MongoClient(connectionString)); + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.TryAddSingleton(o => new MongoClient(connectionString)); ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); } } diff --git a/src/Serilog.Ui.MongoDbProvider/MongoDbDataProvider.cs b/src/Serilog.Ui.MongoDbProvider/MongoDbDataProvider.cs index 3352a15c..48f8d15e 100644 --- a/src/Serilog.Ui.MongoDbProvider/MongoDbDataProvider.cs +++ b/src/Serilog.Ui.MongoDbProvider/MongoDbDataProvider.cs @@ -17,7 +17,6 @@ public MongoDbDataProvider(IMongoClient client, MongoDbOptions options) if (options is null) throw new ArgumentNullException(nameof(options)); _collection = client.GetDatabase(options.DatabaseName).GetCollection(options.CollectionName); - var s = _collection.CollectionNamespace; } public async Task<(IEnumerable, int)> FetchDataAsync( @@ -49,6 +48,12 @@ private async Task> GetLogsAsync( var builder = Builders.Filter.Empty; GenerateWhereClause(ref builder, level, searchCriteria, startDate, endDate); + if (!string.IsNullOrWhiteSpace(searchCriteria)) + { + await _collection.Indexes.CreateOneAsync( + new CreateIndexModel(Builders.IndexKeys.Text(p => p.RenderedMessage))); + } + var logs = await _collection .Find(builder) .Skip(count * page) @@ -89,7 +94,7 @@ private void GenerateWhereClause( if (!string.IsNullOrWhiteSpace(level)) builder &= Builders.Filter.Eq(entry => entry.Level, level); - if (!string.IsNullOrWhiteSpace(searchCriteria)) + if (!string.IsNullOrWhiteSpace(searchCriteria)) builder &= Builders.Filter.Text(searchCriteria); if (startDate != null) diff --git a/src/Serilog.Ui.MsSqlServerProvider/Extensions/SerilogUiOptionBuilderExtensions.cs b/src/Serilog.Ui.MsSqlServerProvider/Extensions/SerilogUiOptionBuilderExtensions.cs index 46dfae6e..b43caa30 100644 --- a/src/Serilog.Ui.MsSqlServerProvider/Extensions/SerilogUiOptionBuilderExtensions.cs +++ b/src/Serilog.Ui.MsSqlServerProvider/Extensions/SerilogUiOptionBuilderExtensions.cs @@ -1,47 +1,47 @@ -using Microsoft.Extensions.DependencyInjection; -using Serilog.Ui.Core; -using System; - -namespace Serilog.Ui.MsSqlServerProvider -{ - /// - /// SQL Server data provider specific extension methods for . - /// - public static class SerilogUiOptionBuilderExtensions - { - /// - /// Configures the SerilogUi to connect to a SQL Server database. - /// - /// The options builder. - /// The connection string. - /// Name of the table. - /// - /// Name of the table schema. default value is dbo - /// - /// throw if connectionString is null - /// throw is tableName is null - public static void UseSqlServer( - this SerilogUiOptionsBuilder optionsBuilder, - string connectionString, - string tableName, - string schemaName = "dbo" - ) - { - if (string.IsNullOrEmpty(connectionString)) - throw new ArgumentNullException(nameof(connectionString)); - - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); - - var relationProvider = new RelationalDbOptions - { - ConnectionString = connectionString, - TableName = tableName, - Schema = schemaName - }; - - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(relationProvider); - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); - } - } +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using System; + +namespace Serilog.Ui.MsSqlServerProvider +{ + /// + /// SQL Server data provider specific extension methods for . + /// + public static class SerilogUiOptionBuilderExtensions + { + /// + /// Configures the SerilogUi to connect to a SQL Server database. + /// + /// The options builder. + /// The connection string. + /// Name of the table. + /// + /// Name of the table schema. default value is dbo + /// + /// throw if connectionString is null + /// throw is tableName is null + public static void UseSqlServer( + this SerilogUiOptionsBuilder optionsBuilder, + string connectionString, + string tableName, + string schemaName = "dbo" + ) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentNullException(nameof(connectionString)); + + if (string.IsNullOrWhiteSpace(tableName)) + throw new ArgumentNullException(nameof(tableName)); + + var relationProvider = new RelationalDbOptions + { + ConnectionString = connectionString, + TableName = tableName, + Schema = !string.IsNullOrWhiteSpace(schemaName) ? schemaName : "dbo" + }; + + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(relationProvider); + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); + } + } } \ No newline at end of file diff --git a/src/Serilog.Ui.MsSqlServerProvider/Serilog.Ui.MsSqlServerProvider.csproj b/src/Serilog.Ui.MsSqlServerProvider/Serilog.Ui.MsSqlServerProvider.csproj index c849a967..0c8d2525 100644 --- a/src/Serilog.Ui.MsSqlServerProvider/Serilog.Ui.MsSqlServerProvider.csproj +++ b/src/Serilog.Ui.MsSqlServerProvider/Serilog.Ui.MsSqlServerProvider.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Serilog.Ui.MySqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs b/src/Serilog.Ui.MySqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs index 2ccb9521..76989e05 100644 --- a/src/Serilog.Ui.MySqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs +++ b/src/Serilog.Ui.MySqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs @@ -1,42 +1,42 @@ -using Microsoft.Extensions.DependencyInjection; -using Serilog.Ui.Core; -using System; - -namespace Serilog.Ui.MySqlProvider -{ - /// - /// MySQL data provider specific extension methods for . - /// - public static class SerilogUiOptionBuilderExtensions - { - /// - /// Configures the SerilogUi to connect to a MySQL database. - /// - /// The options builder. - /// The connection string. - /// Name of the table. - /// throw if connectionString is null - /// throw is tableName is null - public static void UseMySqlServer( - this SerilogUiOptionsBuilder optionsBuilder, - string connectionString, - string tableName - ) - { - if (string.IsNullOrEmpty(connectionString)) - throw new ArgumentNullException(nameof(connectionString)); - - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); - - var relationProvider = new RelationalDbOptions - { - ConnectionString = connectionString, - TableName = tableName - }; - - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(relationProvider); - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); - } - } +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using System; + +namespace Serilog.Ui.MySqlProvider +{ + /// + /// MySQL data provider specific extension methods for . + /// + public static class SerilogUiOptionBuilderExtensions + { + /// + /// Configures the SerilogUi to connect to a MySQL database. + /// + /// The options builder. + /// The connection string. + /// Name of the table. + /// throw if connectionString is null + /// throw is tableName is null + public static void UseMySqlServer( + this SerilogUiOptionsBuilder optionsBuilder, + string connectionString, + string tableName + ) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentNullException(nameof(connectionString)); + + if (string.IsNullOrWhiteSpace(tableName)) + throw new ArgumentNullException(nameof(tableName)); + + var relationProvider = new RelationalDbOptions + { + ConnectionString = connectionString, + TableName = tableName + }; + + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(relationProvider); + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); + } + } } \ No newline at end of file diff --git a/src/Serilog.Ui.PostgreSqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs b/src/Serilog.Ui.PostgreSqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs index e64a5b04..fc7a3912 100644 --- a/src/Serilog.Ui.PostgreSqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs +++ b/src/Serilog.Ui.PostgreSqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs @@ -1,47 +1,47 @@ -using Microsoft.Extensions.DependencyInjection; -using Serilog.Ui.Core; -using System; - -namespace Serilog.Ui.PostgreSqlProvider -{ - /// - /// PostgreSQL data provider specific extension methods for . - /// - public static class SerilogUiOptionBuilderExtensions - { - /// - /// Configures the SerilogUi to connect to a PostgreSQL database. - /// - /// The options builder. - /// The connection string. - /// Name of the table. - /// - /// Name of the table schema. default value is public - /// - /// throw if connectionString is null - /// throw is tableName is null - public static void UseNpgSql( - this SerilogUiOptionsBuilder optionsBuilder, - string connectionString, - string tableName, - string schemaName = "public" - ) - { - if (string.IsNullOrEmpty(connectionString)) - throw new ArgumentNullException(nameof(connectionString)); - - if (string.IsNullOrEmpty(tableName)) - throw new ArgumentNullException(nameof(tableName)); - - var relationProvider = new RelationalDbOptions - { - ConnectionString = connectionString, - TableName = tableName, - Schema = schemaName - }; - - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(relationProvider); - ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); - } - } +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using System; + +namespace Serilog.Ui.PostgreSqlProvider +{ + /// + /// PostgreSQL data provider specific extension methods for . + /// + public static class SerilogUiOptionBuilderExtensions + { + /// + /// Configures the SerilogUi to connect to a PostgreSQL database. + /// + /// The options builder. + /// The connection string. + /// Name of the table. + /// + /// Name of the table schema. default value is public + /// + /// throw if connectionString is null + /// throw is tableName is null + public static void UseNpgSql( + this SerilogUiOptionsBuilder optionsBuilder, + string connectionString, + string tableName, + string schemaName = "public" + ) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentNullException(nameof(connectionString)); + + if (string.IsNullOrWhiteSpace(tableName)) + throw new ArgumentNullException(nameof(tableName)); + + var relationProvider = new RelationalDbOptions + { + ConnectionString = connectionString, + TableName = tableName, + Schema = !string.IsNullOrWhiteSpace(schemaName) ? schemaName : "public" + }; + + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddSingleton(relationProvider); + ((ISerilogUiOptionsBuilder)optionsBuilder).Services.AddScoped(); + } + } } \ No newline at end of file diff --git a/src/Serilog.Ui.PostgreSqlProvider/LogLevelConverter.cs b/src/Serilog.Ui.PostgreSqlProvider/LogLevelConverter.cs index 291fcc8f..8341c0ac 100644 --- a/src/Serilog.Ui.PostgreSqlProvider/LogLevelConverter.cs +++ b/src/Serilog.Ui.PostgreSqlProvider/LogLevelConverter.cs @@ -1,53 +1,59 @@ -namespace Serilog.Ui.PostgreSqlProvider -{ - internal class LogLevelConverter - { - public static string GetLevelName(string value) - { - switch (value) - { - case "0": - return "Verbose"; - - case "1": - return "Debug"; - - case "2": - return "Information"; - - case "3": - return "Warning"; - - case "4": - return "Error"; - - default: - return ""; - } - } - - public static int GetLevelValue(string name) - { - switch (name) - { - case "Verbose": - return 0; - - case "Debug": - return 1; - - case "Information": - return 2; - - case "Warning": - return 3; - - case "Error": - return 4; - - default: - return 100; - } - } - } +namespace Serilog.Ui.PostgreSqlProvider +{ + internal class LogLevelConverter + { + public static string GetLevelName(string value) + { + switch (value) + { + case "0": + return "Verbose"; + + case "1": + return "Debug"; + + case "2": + return "Information"; + + case "3": + return "Warning"; + + case "4": + return "Error"; + + case "5": + return "Fatal"; + + default: + return ""; + } + } + + public static int GetLevelValue(string name) + { + switch (name) + { + case "Verbose": + return 0; + + case "Debug": + return 1; + + case "Information": + return 2; + + case "Warning": + return 3; + + case "Error": + return 4; + + case "Fatal": + return 5; + + default: + return 100; + } + } + } } \ No newline at end of file diff --git a/src/Serilog.Ui.PostgreSqlProvider/PostgreDataProvider.cs b/src/Serilog.Ui.PostgreSqlProvider/PostgreDataProvider.cs index 4abc00f1..65afdbf6 100644 --- a/src/Serilog.Ui.PostgreSqlProvider/PostgreDataProvider.cs +++ b/src/Serilog.Ui.PostgreSqlProvider/PostgreDataProvider.cs @@ -59,7 +59,9 @@ private async Task> GetLogsAsync(int page, { Offset = page * count, Count = count, - Level = LogLevelConverter.GetLevelValue(level), + // TODO: this level could be a text column, to be passed as parameter: + // https://github.com/b00ted/serilog-sinks-postgresql/blob/ce73c7423383d91ddc3823fe350c1c71fc23bab9/Serilog.Sinks.PostgreSQL/Sinks/PostgreSQL/ColumnWriters.cs#L97 + Level = LogLevelConverter.GetLevelValue(level), Search = searchCriteria != null ? "%" + searchCriteria + "%" : null, StartDate = startDate, EndDate = endDate diff --git a/src/Serilog.Ui.PostgreSqlProvider/Serilog.Ui.PostgreSqlProvider.csproj b/src/Serilog.Ui.PostgreSqlProvider/Serilog.Ui.PostgreSqlProvider.csproj index f0f0dbfc..364515aa 100644 --- a/src/Serilog.Ui.PostgreSqlProvider/Serilog.Ui.PostgreSqlProvider.csproj +++ b/src/Serilog.Ui.PostgreSqlProvider/Serilog.Ui.PostgreSqlProvider.csproj @@ -1,17 +1,18 @@  - - netstandard2.0 - latest - 2.1.0 - + + netstandard2.0 + latest + 2.1.0 + - - - - + + + + - - - + + + + \ No newline at end of file diff --git a/tests/Serilog.Ui.Common.Tests/DataSamples/ElasticSearchLogModelFaker.cs b/tests/Serilog.Ui.Common.Tests/DataSamples/ElasticSearchLogModelFaker.cs new file mode 100644 index 00000000..683cd3f5 --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/DataSamples/ElasticSearchLogModelFaker.cs @@ -0,0 +1,53 @@ +using Serilog.Ui.Core; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Serilog.Ui.Common.Tests.DataSamples +{ + public static class ElasticSearchLogModelFaker + { + public static LogModelPropsCollector LogsAsync(ILogger logger) + { + var logs = new List(); + logger.Information("90 Information"); + logs.Add(Spawn("Information", 90)); + Task.Delay(2000).Wait(); + logger.Information("91 Information"); + logs.Add(Spawn("Information", 91)); + Task.Delay(1000).Wait(); + logger.Information("92 Information"); + logs.Add(Spawn("Information", 92)); + Task.Delay(3000).Wait(); + + for (int i = 0; i < 15; i++) + { + logger.Warning($"Hello Warning {i}"); + logs.Add(Spawn("Warning", i)); + } + + logger.Information("Hello Information"); + logs.Add(Spawn("Information", 15)); + logger.Debug("Hello Debug"); + logs.Add(Spawn("Debug", 16)); + logger.Error("Hello Error"); + logs.Add(Spawn("Error", 17)); + logger.Fatal("Hello Fatal"); + logs.Add(Spawn("Information", 18)); + + return new LogModelPropsCollector(logs); + } + + private static LogModel Spawn(string level, int rowNum) + => new() + { + Exception = null, + Level = level, + Message = $"{rowNum} {level}", + Properties = PropertiesFaker.SerializedProperties, + PropertyType = "json", + RowNo = rowNum, + Timestamp = DateTime.UtcNow + }; + } +} diff --git a/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelFaker.cs b/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelFaker.cs index 973cc0ec..dd71a7d3 100644 --- a/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelFaker.cs +++ b/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelFaker.cs @@ -1,26 +1,32 @@ using Bogus; using Newtonsoft.Json; using Serilog.Ui.Core; +using System; using System.Collections.Generic; namespace Serilog.Ui.Common.Tests.DataSamples { public static class LogModelFaker { - private static readonly IEnumerable LogLevels = new List + internal static readonly IEnumerable LogLevels = new List { "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" }; - private static readonly Faker logsRules = new Faker() - .RuleFor(p => p.Level, f => f.PickRandom(LogLevels)) - .RuleFor(p => p.Properties, PropertiesFaker.SerializedProperties) - .RuleFor(p => p.Exception, (f) => JsonConvert.SerializeObject(f.System.Exception())) - .RuleFor(p => p.Message, f => f.System.Random.Words(5)) - .RuleFor(p => p.PropertyType, f => f.System.CommonFileType()) - .RuleFor(p => p.Timestamp, f => f.Date.Recent()) - .RuleFor(p => p.RowNo, f => f.Database.Random.Int()); + private static Faker LogsRules() + { + Bogus.DataSets.Date.SystemClock = () => DateTime.Parse("8/8/2019 2:00 PM"); + + return new Faker().UseSeed(1910) + .RuleFor(p => p.RowNo, f => f.Database.Random.Int()) + .RuleFor(p => p.Level, f => f.PickRandom(LogLevels)) + .RuleFor(p => p.Properties, PropertiesFaker.SerializedProperties) + .RuleFor(p => p.Exception, (f) => JsonConvert.SerializeObject(f.System.Exception())) + .RuleFor(p => p.Message, f => f.System.Random.Words(6)) + .RuleFor(p => p.PropertyType, f => f.System.CommonFileType()) + .RuleFor(p => p.Timestamp, f => new DateTime(f.Date.Recent().Ticks, DateTimeKind.Utc)); + } - public static IEnumerable Logs(int generationCount) => logsRules.Generate(generationCount); + public static IEnumerable Logs(int generationCount) => LogsRules().Generate(generationCount); public static IEnumerable Logs() => Logs(20); } } diff --git a/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelPropsCollector.cs b/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelPropsCollector.cs new file mode 100644 index 00000000..bdc5847f --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/DataSamples/LogModelPropsCollector.cs @@ -0,0 +1,44 @@ +using Serilog.Ui.Core; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Serilog.Ui.Common.Tests.DataSamples +{ + public class LogModelPropsCollector + { + public LogModelPropsCollector(IEnumerable models) + { + DataSet = models.ToList(); + Collect(models); + } + + public IReadOnlyCollection DataSet { get; } + public LogModel Example { get; private set; } + public Dictionary CountByLevel { get; private set; } + public IEnumerable TimesSamples { get; private set; } + public IEnumerable MessagePiecesSamples { get; private set; } + + private void Collect(IEnumerable models) + { + Example = models.First(); + + CountByLevel = LogModelFaker.LogLevels.ToDictionary(p => p, lvl => models.Count(m => m.Level == lvl)); + + if (models.Count() < 3) return; + + MessagePiecesSamples = new List + { + models.ElementAtOrDefault(0).Message, + models.ElementAtOrDefault(1).Message.Substring(1, models.ElementAtOrDefault(1).Message.Length / 2), + models.ElementAtOrDefault(2).Message.Substring(1, models.ElementAtOrDefault(2).Message.Length / 2), + }; + TimesSamples = new List + { + models.ElementAtOrDefault(0).Timestamp, + models.ElementAtOrDefault(1).Timestamp, + models.ElementAtOrDefault(2).Timestamp, + }.OrderBy(p => p.Ticks); + } + } +} diff --git a/tests/Serilog.Ui.Common.Tests/DataSamples/MongoDbLogModelFaker.cs b/tests/Serilog.Ui.Common.Tests/DataSamples/MongoDbLogModelFaker.cs index cffb6ad7..c0e96c3e 100644 --- a/tests/Serilog.Ui.Common.Tests/DataSamples/MongoDbLogModelFaker.cs +++ b/tests/Serilog.Ui.Common.Tests/DataSamples/MongoDbLogModelFaker.cs @@ -10,8 +10,11 @@ namespace Serilog.Ui.Common.Tests.DataSamples { public static class MongoDbLogModelFaker { - public static IEnumerable Logs(int generationCount) - => LogModelFaker.Logs(generationCount).Select(p => new MongoDbLogModel + public static (IEnumerable logs, LogModelPropsCollector collector) Logs(int generationCount) + { + var originalLogs = LogModelFaker.Logs(generationCount); + var modelCollector = new LogModelPropsCollector(originalLogs); + return (originalLogs.Select(p => new MongoDbLogModel { Id = p.RowNo, Level = p.Level, @@ -20,8 +23,9 @@ public static IEnumerable Logs(int generationCount) UtcTimeStamp = p.Timestamp.ToUniversalTime(), Properties = JsonConvert.DeserializeObject(p.Properties), Exception = JsonConvert.DeserializeObject(p.Exception).ToBsonDocument(), - }); + }), modelCollector); + } - public static IEnumerable Logs() => Logs(20); + public static (IEnumerable logs, LogModelPropsCollector collector) Logs() => Logs(20); } } diff --git a/tests/Serilog.Ui.Common.Tests/Serilog.Ui.Common.Tests.csproj b/tests/Serilog.Ui.Common.Tests/Serilog.Ui.Common.Tests.csproj index ac170702..640c53ff 100644 --- a/tests/Serilog.Ui.Common.Tests/Serilog.Ui.Common.Tests.csproj +++ b/tests/Serilog.Ui.Common.Tests/Serilog.Ui.Common.Tests.csproj @@ -1,18 +1,33 @@ - + - - netstandard2.0 - + + net6.0 + 10 + - - - - - - - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + diff --git a/tests/Serilog.Ui.Common.Tests/SqlUtil/Costants.cs b/tests/Serilog.Ui.Common.Tests/SqlUtil/Costants.cs new file mode 100644 index 00000000..68ac973d --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/SqlUtil/Costants.cs @@ -0,0 +1,74 @@ +using Serilog.Ui.Core; + +namespace Serilog.Ui.Common.Tests.SqlUtil +{ + public static class Costants + { + // https://github.com/serilog-mssql/serilog-sinks-mssqlserver#table-definition + public const string MsSqlCreateTable = "CREATE TABLE [Logs] (" + + "[Id] int IDENTITY(1,1) NOT NULL," + + "[Message] nvarchar(max) NULL," + + "[MessageTemplate] nvarchar(max) NULL," + + "[Level] nvarchar(128) NULL," + + "[TimeStamp] datetime NOT NULL," + + "[Exception] nvarchar(max) NULL," + + "[Properties] nvarchar(max) NULL," + + "CONSTRAINT[PK_Logs] PRIMARY KEY CLUSTERED([Id] ASC)" + + ");"; + + public const string MsSqlInsertFakeData = "INSERT [Logs]" + + "(Message, MessageTemplate, Level, TimeStamp, Exception, Properties)" + + $"values (" + + $"@{nameof(LogModel.Message)}," + + $"@{nameof(LogModel.Message)}," + + $"@{nameof(LogModel.Level)}," + + $"@{nameof(LogModel.Timestamp)}," + + $"@{nameof(LogModel.Exception)}," + + $"@{nameof(LogModel.Properties)}" + + $")"; + + // https://github.com/saleem-mirza/serilog-sinks-mysql/blob/dev/src/Serilog.Sinks.MySQL/Sinks/MySQL/MySqlSink.cs + public const string MySqlCreateTable = "CREATE TABLE IF NOT EXISTS Logs (" + + "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY," + + "Timestamp VARCHAR(100)," + + "LogLevel VARCHAR(15)," + + "Template TEXT," + + "Message TEXT," + + "Exception TEXT," + + "Properties TEXT," + + "_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP" + + ")"; + + public const string MySqlInsertFakeData = "INSERT INTO Logs" + + "(Timestamp, LogLevel, Template, Message, Exception, Properties)" + + "VALUES (" + + $"@{nameof(LogModel.Timestamp)}," + + $"@{nameof(LogModel.Level)}," + + $"@{nameof(LogModel.Message)}," + + $"@{nameof(LogModel.Message)}," + + $"@{nameof(LogModel.Exception)}," + + $"@{nameof(LogModel.Properties)}" + + ")"; + + // https://github.com/b00ted/serilog-sinks-postgresql/blob/master/Serilog.Sinks.PostgreSQL/Sinks/PostgreSQL/ColumnOptions.cs + public const string PostgresCreateTable = "CREATE TABLE IF NOT EXISTS public.logs (" + + "timestamp TimestampTz," + + "level INTEGER," + + "message_template TEXT," + + "message TEXT," + + "exception TEXT," + + "log_event TEXT" + + ")"; + + public const string PostgresInsertFakeData = "INSERT INTO public.logs" + + "(timestamp, level, message_template, message, exception, log_event)" + + "VALUES (" + + $"@{nameof(LogModel.Timestamp)}," + + $"@{nameof(LogModel.Level)}," + + $"@{nameof(LogModel.Message)}," + + $"@{nameof(LogModel.Message)}," + + $"@{nameof(LogModel.Exception)}," + + $"@{nameof(LogModel.Properties)}" + + ")"; + } +} diff --git a/tests/Serilog.Ui.Common.Tests/SqlUtil/DatabaseInstance.cs b/tests/Serilog.Ui.Common.Tests/SqlUtil/DatabaseInstance.cs new file mode 100644 index 00000000..57c5379d --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/SqlUtil/DatabaseInstance.cs @@ -0,0 +1,116 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using DotNet.Testcontainers.Containers; +using Microsoft.Data.SqlClient; +using MySql.Data.MySqlClient; +using Npgsql; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.Core; + +namespace Serilog.Ui.Common.Tests.SqlUtil +{ +#nullable enable + public abstract class DatabaseInstance : IIntegrationRunner + { + private bool disposedValue; + + protected virtual string Name { get; } = nameof(IContainer); + + /// + /// Gets or sets the Testcontainers container. + /// + protected virtual IContainer? Container { get; set; } + + /// + /// Gets or sets the IDataProvider. + /// + protected IDataProvider? Provider { get; set; } + + protected LogModelPropsCollector? Collector { get; set; } + + public IDataProvider GetDataProvider() => Guard.Against.Null(Provider)!; + + public LogModelPropsCollector GetPropsCollector() => Guard.Against.Null(Collector)!; + + public async Task InitializeAsync() + { + await Container!.StartAsync(); + await GetDbContextInstanceAsync(); + } + + /// + /// Register an operation to check for db readiness. + /// You can access the container connection string from this point onwards. + /// Runs before . + /// + /// + protected abstract Task CheckDbReadinessAsync(); + + /// + /// Register operations to setup the database in a functional status. + /// Runs after . + /// + /// + protected abstract Task InitializeAdditionalAsync(); + + private async Task GetDbContextInstanceAsync(CancellationToken token = default) + { + int retry = default; + + do + { + try + { + await CheckDbReadinessAsync(); + break; + } + catch (Exception ex) when (ex is SqlException || ex is MySqlException || ex is NpgsqlException) + { + retry += 1; + await Task.Delay(1000 * retry, token); + } + + } while (retry < 10); + + await InitializeAdditionalAsync(); + } + + /// + public async Task DisposeAsync() + { + if (Container != null) + { + await Container.DisposeAsync() + .ConfigureAwait(false); + } + } + + /// + /// Dispose any additional managed dependencies. + /// + /// If it's disposing. + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // additional dispose items + } + + disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationPaginationTests.cs b/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationPaginationTests.cs new file mode 100644 index 00000000..9354a93e --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationPaginationTests.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace Serilog.Ui.Common.Tests.TestSuites +{ + public interface IIntegrationPaginationTests + { + Task It_fetches_with_limit_and_skip(); + + Task It_fetches_with_limit(); + + Task It_fetches_with_skip(); + + Task It_throws_when_skip_is_zero(); + } +} diff --git a/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationRunner.cs b/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationRunner.cs new file mode 100644 index 00000000..ceac6982 --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationRunner.cs @@ -0,0 +1,13 @@ +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Core; +using System; +using Xunit; + +namespace Serilog.Ui.Common.Tests.TestSuites +{ + public interface IIntegrationRunner : IAsyncLifetime, IDisposable + { + IDataProvider GetDataProvider(); + LogModelPropsCollector GetPropsCollector(); + } +} diff --git a/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationSearchTests.cs b/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationSearchTests.cs new file mode 100644 index 00000000..144124dd --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/TestSuites/IIntegrationSearchTests.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +namespace Serilog.Ui.Common.Tests.TestSuites +{ + public interface IIntegrationSearchTests + { + Task It_finds_all_data_with_default_search(); + + Task It_finds_same_data_on_same_repeated_search(); + + Task It_finds_data_with_all_filters(); + + Task It_finds_only_data_emitted_after_date(); + + Task It_finds_only_data_emitted_before_date(); + + Task It_finds_only_data_emitted_in_dates_range(); + + Task It_finds_only_data_with_specific_level(); + + Task It_finds_only_data_with_specific_message_content(); + } +} diff --git a/tests/Serilog.Ui.Common.Tests/TestSuites/IUnitBaseTests.cs b/tests/Serilog.Ui.Common.Tests/TestSuites/IUnitBaseTests.cs new file mode 100644 index 00000000..6beb7b58 --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/TestSuites/IUnitBaseTests.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Serilog.Ui.Common.Tests.TestSuites +{ + public interface IUnitBaseTests + { + void It_throws_when_any_dependency_is_null(); + Task It_logs_and_throws_when_db_read_breaks_down(); + } +} diff --git a/tests/Serilog.Ui.Common.Tests/TestSuites/Impl/IntegrationPaginationTests.cs b/tests/Serilog.Ui.Common.Tests/TestSuites/Impl/IntegrationPaginationTests.cs new file mode 100644 index 00000000..8015cef0 --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/TestSuites/Impl/IntegrationPaginationTests.cs @@ -0,0 +1,55 @@ +using Ardalis.GuardClauses; +using FluentAssertions; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Core; +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Serilog.Ui.Common.Tests.TestSuites.Impl +{ + public abstract class IntegrationPaginationTests : IIntegrationPaginationTests + where DbRunner : class, IIntegrationRunner + { + protected readonly LogModelPropsCollector logCollector; + protected readonly IDataProvider provider; + + protected IntegrationPaginationTests(DbRunner instance) + { + logCollector = instance.GetPropsCollector(); + provider = Guard.Against.Null(instance.GetDataProvider()); + } + + [Fact] + public virtual async Task It_fetches_with_limit() + { + var (Logs, _) = await provider.FetchDataAsync(1, 5); + + Logs.Should().NotBeEmpty().And.HaveCount(5); + } + + [Fact] + public virtual async Task It_fetches_with_limit_and_skip() + { + var example = logCollector.Example; + var (Logs, _) = await provider.FetchDataAsync(2, 1, level: example.Level); + + Logs.Should().NotBeEmpty().And.HaveCount(1); + Logs.First().Level.Should().Be(example.Level); + Logs.First().Message.Should().NotBe(example.Message); + } + + [Fact] + public virtual async Task It_fetches_with_skip() + { + var example = logCollector.Example; + var (Logs, _) = await provider.FetchDataAsync(2, 1, level: example.Level); + + Logs.First().Level.Should().Be(example.Level); + Logs.First().Message.Should().NotBe(example.Message); + } + + public abstract Task It_throws_when_skip_is_zero(); + } +} diff --git a/tests/Serilog.Ui.Common.Tests/TestSuites/Impl/IntegrationSearchTests.cs b/tests/Serilog.Ui.Common.Tests/TestSuites/Impl/IntegrationSearchTests.cs new file mode 100644 index 00000000..77e39beb --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/TestSuites/Impl/IntegrationSearchTests.cs @@ -0,0 +1,152 @@ +using Ardalis.GuardClauses; +using FluentAssertions; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.Core; +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace MsSql.Tests.DataProvider +{ + public class IntegrationSearchTests : IIntegrationSearchTests + where DbRunner : class, IIntegrationRunner + { + protected readonly LogModelPropsCollector logCollector; + protected readonly IDataProvider provider; + + protected IntegrationSearchTests(DbRunner instance) + { + logCollector = instance.GetPropsCollector(); + provider = Guard.Against.Null(instance.GetDataProvider()); + } + + [Fact] + public virtual async Task It_finds_all_data_with_default_search() + { + var res = await provider.FetchDataAsync(1, 10); + + res.Item1.Should().HaveCount(10); + res.Item2.Should().Be(logCollector.DataSet.Count); + } + + [Fact] + public virtual Task It_finds_data_with_all_filters() + => It_finds_data_with_all_filters_by_utc(false, false); + + protected virtual async Task It_finds_data_with_all_filters_by_utc(bool checkWithUtc, bool excludeProps) + { + var (Logs, Count) = await provider.FetchDataAsync(1, + 10, + logCollector!.Example.Level, + logCollector!.Example.Message[3..], + logCollector!.Example.Timestamp.AddSeconds(-2), + logCollector!.Example.Timestamp.AddSeconds(2)); + + var log = Logs.First(); + log.Message.Should().Be(logCollector.Example.Message); + log.Level.Should().Be(logCollector.Example.Level); + if (!excludeProps) log.Properties.Should().Be(logCollector.Example.Properties); + ConvertToUtc(log.Timestamp, checkWithUtc).Should().BeCloseTo(logCollector.Example.Timestamp, TimeSpan.FromMinutes(5)); + Count.Should().BeCloseTo(1, 2); + } + + [Fact] + public virtual Task It_finds_only_data_emitted_after_date() + => It_finds_only_data_emitted_after_date_by_utc(false); + + protected async Task It_finds_only_data_emitted_after_date_by_utc(bool checkWithUtc) + { + var lastTimeStamp = logCollector!.TimesSamples + .ElementAt(logCollector.TimesSamples.Count() - 1).AddHours(-4); + var afterTimeStampCount = logCollector!.DataSet.Count(p => p.Timestamp > lastTimeStamp); + var (Logs, Count) = await provider.FetchDataAsync(1, 1000, startDate: lastTimeStamp); + + Logs.Should().NotBeEmpty(); + Logs.Should().HaveCount(afterTimeStampCount); + Count.Should().Be(afterTimeStampCount); + Logs.Should().OnlyContain(p => ConvertToUtc(p.Timestamp, checkWithUtc) > lastTimeStamp); + } + + [Fact] + public virtual Task It_finds_only_data_emitted_before_date() + => It_finds_only_data_emitted_before_date_by_utc(false); + + protected async Task It_finds_only_data_emitted_before_date_by_utc(bool checkWithUtc) + { + var firstTimeStamp = logCollector!.TimesSamples + .ElementAt(logCollector.TimesSamples.Count() - 1).AddSeconds(50); + var beforeTimeStampCount = logCollector!.DataSet.Count(p => p.Timestamp < firstTimeStamp); + var (Logs, Count) = await provider.FetchDataAsync(1, 1000, endDate: firstTimeStamp); + + Logs.Should().NotBeEmpty(); + Logs.Should().HaveCount(beforeTimeStampCount); + Count.Should().Be(beforeTimeStampCount); + Logs.Should().OnlyContain(p => ConvertToUtc(p.Timestamp, checkWithUtc) < firstTimeStamp); + } + + [Fact] + public virtual Task It_finds_only_data_emitted_in_dates_range() + => It_finds_only_data_emitted_in_dates_range_by_utc(false); + + protected async Task It_finds_only_data_emitted_in_dates_range_by_utc(bool checkWithUtc) + { + var firstTimeStamp = logCollector!.TimesSamples.First().AddSeconds(50); + var lastTimeStamp = logCollector.TimesSamples.Last().AddSeconds(50); + var inTimeStampCount = logCollector!.DataSet + .Count(p => p.Timestamp >= firstTimeStamp && p.Timestamp < lastTimeStamp); + var (Logs, Count) = await provider.FetchDataAsync(1, 1000, startDate: firstTimeStamp, endDate: lastTimeStamp); + + Logs.Should().NotBeEmpty(); + Logs.Should().HaveCount(inTimeStampCount); + Count.Should().Be(inTimeStampCount); + Logs.Should().OnlyContain(p => + ConvertToUtc(p.Timestamp, checkWithUtc) >= firstTimeStamp && + ConvertToUtc(p.Timestamp, checkWithUtc) < lastTimeStamp); + } + + [Fact] + public virtual async Task It_finds_only_data_with_specific_level() + { + var choosenLvl = logCollector!.CountByLevel.FirstOrDefault(p => p.Value > 0); + + var (Logs, Count) = await provider.FetchDataAsync(1, 10, level: choosenLvl.Key); + + Logs.Should().NotBeEmpty(); + Logs.Should().OnlyContain(p => p.Level == choosenLvl.Key); + Count.Should().Be(choosenLvl.Value); + } + + [Fact] + public virtual async Task It_finds_only_data_with_specific_message_content() + { + var msg = logCollector!.MessagePiecesSamples.FirstOrDefault(); + + var (Logs, Count) = await provider.FetchDataAsync(1, 10, searchCriteria: msg); + + Logs.Should().NotBeEmpty(); + Logs.Should().OnlyContain(p => + p.Message + .Split(" ", StringSplitOptions.None) + .Intersect(msg!.Split(" ", StringSplitOptions.None)).Any()); + Count.Should().BeLessThan(100).And.BeGreaterThanOrEqualTo(1); + } + + [Fact] + public virtual async Task It_finds_same_data_on_same_repeated_search() + { + var choosenLvl = logCollector!.CountByLevel.FirstOrDefault(p => p.Value > 0); + + var (Logs, Count) = await provider.FetchDataAsync(3, 3, level: choosenLvl.Key); + + var (Logs2nd, Count2nd) = await provider.FetchDataAsync(3, 3, level: choosenLvl.Key); + + Logs.Should().BeEquivalentTo(Logs2nd); + Count.Should().Be(Count2nd); + } + + private static DateTime ConvertToUtc(DateTime timestamp, bool checkWithUtc) + => checkWithUtc ? timestamp.ToUniversalTime() : timestamp; + } +} \ No newline at end of file diff --git a/tests/Serilog.Ui.Common.Tests/xunit.runner.json b/tests/Serilog.Ui.Common.Tests/xunit.runner.json new file mode 100644 index 00000000..13badeec --- /dev/null +++ b/tests/Serilog.Ui.Common.Tests/xunit.runner.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "diagnosticMessages": true, + "internalDiagnosticMessages": true, + "methodDisplayOptions": "replaceUnderscoreWithSpace,useOperatorMonikers", + "methodDisplay": "method" +} \ No newline at end of file diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderBaseTest.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderBaseTest.cs new file mode 100644 index 00000000..058928e9 --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderBaseTest.cs @@ -0,0 +1,46 @@ +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elasticsearch.Net; +using ElasticSearch.Tests.Util; +using FluentAssertions; +using Moq; +using Nest; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.ElasticSearchProvider; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace ElasticSearch.Tests.DataProvider +{ + [Trait("Unit-Base", "Elastic")] + public class DataProviderBaseTest : IUnitBaseTests, IClusterFixture + { + [U] + public void It_throws_when_any_dependency_is_null() + { + var suts = new List> + { + () => new ElasticSearchDbDataProvider(null, new()), + () => new ElasticSearchDbDataProvider(new ElasticClient(), null), + }; + + suts.ForEach(sut => sut.Should().ThrowExactly()); + } + + [U] + public Task It_logs_and_throws_when_db_read_breaks_down() + { + var mockClient = new Mock(); + mockClient + .Setup(mk => mk.SearchAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new ElasticsearchClientException("no connection to db")); + + var sut = new ElasticSearchDbDataProvider(mockClient.Object, new()); + var assert = () => sut.FetchDataAsync(1, 10); + + return assert.Should().ThrowExactlyAsync().WithMessage("no connection to db"); + } + } +} diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderPaginationTest.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderPaginationTest.cs new file mode 100644 index 00000000..753c0701 --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderPaginationTest.cs @@ -0,0 +1,31 @@ +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elasticsearch.Net; +using ElasticSearch.Tests.Util; +using FluentAssertions; +using Serilog.Ui.Common.Tests.TestSuites.Impl; +using System.Threading.Tasks; +using Xunit; + +namespace ElasticSearch.Tests.DataProvider +{ + [Trait("Integration-Pagination", "Elastic")] + public class DataProviderPaginationTest : IntegrationPaginationTests, + IClassFixture, + IClusterFixture + { + public DataProviderPaginationTest(ElasticTestProvider instance) : base(instance) { } + + public override Task It_fetches_with_limit() => base.It_fetches_with_limit(); + + public override Task It_fetches_with_limit_and_skip() => base.It_fetches_with_limit_and_skip(); + + public override Task It_fetches_with_skip() => base.It_fetches_with_skip(); + + [I] + public override Task It_throws_when_skip_is_zero() + { + var test = () => provider.FetchDataAsync(0, 1); + return test.Should().NotThrowAsync("because Elastic Client catches the error"); + } + } +} diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderSearchTest.cs.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderSearchTest.cs.cs new file mode 100644 index 00000000..ee2ae7ea --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/DataProvider/DataProviderSearchTest.cs.cs @@ -0,0 +1,57 @@ +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using ElasticSearch.Tests.Util; +using FluentAssertions; +using MsSql.Tests.DataProvider; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace ElasticSearch.Tests.DataProvider +{ + [Trait("Integration-Search", "Elastic")] + public class DataProviderSearchTest : IntegrationSearchTests, + IClassFixture, + IClusterFixture + { + public DataProviderSearchTest(ElasticTestProvider instance) : base(instance) { } + + public override Task It_finds_all_data_with_default_search() + => base.It_finds_all_data_with_default_search(); + + public override Task It_finds_data_with_all_filters() + => It_finds_data_with_all_filters_by_utc(true, true); + + public override Task It_finds_only_data_emitted_after_date() + => It_finds_only_data_emitted_after_date_by_utc(true); + + public override Task It_finds_only_data_emitted_before_date() + => It_finds_only_data_emitted_before_date_by_utc(true); + + [I] + public override async Task It_finds_only_data_emitted_in_dates_range() + { + var firstTimeStamp = logCollector!.TimesSamples.First().AddSeconds(-50); + var lastTimeStamp = logCollector.TimesSamples.Last(); + var inTimeStampCount = logCollector!.DataSet + .Count(p => p.Timestamp >= firstTimeStamp && p.Timestamp <= lastTimeStamp); + var (Logs, Count) = await provider.FetchDataAsync(1, 1000, startDate: firstTimeStamp, endDate: lastTimeStamp); + + Logs.Should().NotBeEmpty(); + Logs.Should().HaveCount(inTimeStampCount); + Count.Should().Be(inTimeStampCount); + Logs.Should().OnlyContain(p => + p.Timestamp.ToUniversalTime() >= firstTimeStamp && + p.Timestamp.ToUniversalTime() < lastTimeStamp); + } + //=> It_finds_only_data_emitted_in_dates_range_by_utc(true); + + public override Task It_finds_only_data_with_specific_level() + => base.It_finds_only_data_with_specific_level(); + + public override Task It_finds_only_data_with_specific_message_content() + => base.It_finds_only_data_with_specific_message_content(); + + public override Task It_finds_same_data_on_same_repeated_search() + => base.It_finds_same_data_on_same_repeated_search(); + } +} diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/ElasticSearchDbDataProviderTest.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/ElasticSearchDbDataProviderTest.cs deleted file mode 100644 index 213c072d..00000000 --- a/tests/Serilog.Ui.ElastichSearchProvider.Tests/ElasticSearchDbDataProviderTest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Xunit; - -namespace Serilog.Ui.ElastichSearchProvider.Tests -{ - public class ElasticSearchDbDataProviderTest - { - [Fact] - public void Test1() - { - - } - } -} \ No newline at end of file diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs new file mode 100644 index 00000000..48cb9b1f --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs @@ -0,0 +1,57 @@ +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using ElasticSearch.Tests.Util; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using Serilog.Ui.ElasticSearchProvider; +using Serilog.Ui.Web; +using System; +using System.Collections.Generic; +using Xunit; + +namespace ElasticSearch.Tests.Extensions +{ + [Trait("DI-DataProvider", "Elastic")] + public class SerilogUiOptionBuilderExtensionsTest : IClusterFixture + { + private readonly ServiceCollection serviceCollection; + + public SerilogUiOptionBuilderExtensionsTest() + { + serviceCollection = new ServiceCollection(); + } + + [U] + public void It_registers_provider_and_dependencies() + { + serviceCollection.AddSerilogUi((builder) => + { + builder.UseElasticSearchDb(new Uri("https://elastic.example.com"), "my-index"); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Should().NotBeNull().And.BeOfType(); + var options = services.GetRequiredService(); + options.Should().NotBeNull(); + options.IndexName.Should().Be("my-index"); + } + + [U] + public void It_throws_on_invalid_registration() + { + var uri = new Uri("https://elastic.example.com"); + var nullables = new List> + { + () => serviceCollection.AddSerilogUi((builder) => builder.UseElasticSearchDb(null, "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseElasticSearchDb(uri, null)), + () => serviceCollection.AddSerilogUi((builder) => builder.UseElasticSearchDb(uri, " ")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseElasticSearchDb(uri, "")), + }; + + foreach (var nullable in nullables) + { + nullable.Should().ThrowExactly(); + } + } + } +} diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Serilog.Ui.ElasticSearchProvider.Tests.csproj b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Serilog.Ui.ElasticSearchProvider.Tests.csproj new file mode 100644 index 00000000..709b74c1 --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Serilog.Ui.ElasticSearchProvider.Tests.csproj @@ -0,0 +1,52 @@ + + + + net6.0 + enable + false + ElasticSearch.Tests + ElasticSearch.Tests + + + + + %(Filename)%(Extension) + PreserveNewest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Serilog.Ui.ElastichSearchProvider.Tests.csproj b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Serilog.Ui.ElastichSearchProvider.Tests.csproj deleted file mode 100644 index 837555e8..00000000 --- a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Serilog.Ui.ElastichSearchProvider.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net6.0 - enable - - false - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/ElasticSearch7XTestBase.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/ElasticSearch7XTestBase.cs new file mode 100644 index 00000000..9abe471d --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/ElasticSearch7XTestBase.cs @@ -0,0 +1,56 @@ +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elastic.Elasticsearch.Xunit; +using Nest; +using Elasticsearch.Net; +using Elastic.Elasticsearch.Ephemeral; +using Xunit; +using ElasticSearch.Tests.Util; + +[assembly: TestFramework("Elastic.Elasticsearch.Xunit.Sdk.ElasticTestFramework", "Elastic.Elasticsearch.Xunit")] +[assembly: ElasticXunitConfiguration(typeof(SerilogSinkElasticsearchXunitRunOptions))] + +namespace ElasticSearch.Tests.Util +{ + // test base and configs from: + // https://github.com/serilog-contrib/serilog-sinks-elasticsearch/tree/dev/test/Serilog.Sinks.Elasticsearch.IntegrationTests/Bootstrap + public abstract class Elasticsearch7XTestBase : IClusterFixture + { + protected Elasticsearch7XTestBase(Elasticsearch7XCluster cl) => Cluster = cl; + + protected Elasticsearch7XCluster Cluster { get; } + + public IElasticClient Client => this.CreateClient(); + + protected virtual ConnectionSettings SetClusterSettings(ConnectionSettings s) => s; + + private IElasticClient CreateClient() => + Cluster.GetOrAddClient(c => + { + var clusterNodes = c.NodesUris(); + var settings = new ConnectionSettings(new StaticConnectionPool(clusterNodes)); + settings = SetClusterSettings(settings); + + var current = (IConnectionConfigurationValues)settings; + var notAlreadyAuthenticated = current.BasicAuthenticationCredentials == null && current.ClientCertificates == null; + + var config = Cluster.ClusterConfiguration; + if (config.EnableSecurity && notAlreadyAuthenticated) + settings = settings.BasicAuthentication(ClusterAuthentication.Admin.Username, ClusterAuthentication.Admin.Password); + + var client = new ElasticClient(settings); + return client; + }); + } + + public class SerilogSinkElasticsearchXunitRunOptions : ElasticXunitRunOptions + { + public SerilogSinkElasticsearchXunitRunOptions() + { + RunIntegrationTests = true; + RunUnitTests = true; + ClusterFilter = null; + TestFilter = null; + IntegrationTestsMayUseAlreadyRunningNode = false; + } + } +} diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/ElasticTestProvider.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/ElasticTestProvider.cs new file mode 100644 index 00000000..8888934f --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/ElasticTestProvider.cs @@ -0,0 +1,91 @@ +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Serilog; +using Serilog.Sinks.Elasticsearch; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.Core; +using Serilog.Ui.ElasticSearchProvider; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace ElasticSearch.Tests.Util +{ + public class ElasticTestProvider : Elasticsearch7XTestBase, IIntegrationRunner + { + private readonly ElasticSearchDbDataProvider provider; + private LogModelPropsCollector? logModelPropsCollector; + private bool disposedValue; + + public ElasticTestProvider(Elasticsearch7XCluster cl) : base(cl) + { + provider = new ElasticSearchDbDataProvider(Client, new ElasticSearchDbOptions + { + IndexName = $"{SetupSerilog.IndexPrefix}{DateTime.UtcNow:yyyy.MM.dd}" + }); + } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + public IDataProvider GetDataProvider() => provider; + + public LogModelPropsCollector GetPropsCollector() => logModelPropsCollector!; + + public Task InitializeAsync() + { + logModelPropsCollector = Cluster.Collector; + + return Client.Indices.RefreshAsync(SetupSerilog.IndexPrefix + "*"); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects) + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + public sealed class SetupSerilog + { + public const string IndexPrefix = "logs-7x-default-"; + public const string TemplateName = "serilog-logs-7x"; + private readonly LoggerConfiguration loggerConfig; + + public SetupSerilog() + { + loggerConfig = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Elasticsearch( + new ElasticsearchSinkOptions(new Uri($"http://localhost:9200")) + { + IndexFormat = IndexPrefix + "{0:yyyy.MM.dd}", + TemplateName = TemplateName, + AutoRegisterTemplate = true, + AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, + }); + } + + public LogModelPropsCollector InitializeLogs() + { + using var logger = loggerConfig.CreateLogger(); + return ElasticSearchLogModelFaker.LogsAsync(logger); + } + } +} \ No newline at end of file diff --git a/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/TestCluster.cs b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/TestCluster.cs new file mode 100644 index 00000000..84db577e --- /dev/null +++ b/tests/Serilog.Ui.ElastichSearchProvider.Tests/Util/TestCluster.cs @@ -0,0 +1,64 @@ +using Elastic.Elasticsearch.Ephemeral.Plugins; +using Elastic.Elasticsearch.Ephemeral; +using Elastic.Elasticsearch.Xunit; +using Elastic.Stack.ArtifactsApi.Products; +using Serilog.Ui.Common.Tests.DataSamples; + +namespace ElasticSearch.Tests.Util +{ + // test cluster configurations from: + // https://github.com/serilog-contrib/serilog-sinks-elasticsearch/blob/dev/test/Serilog.Sinks.Elasticsearch.IntegrationTests/Bootstrap/ClientTestClusterBase.cs + public class Elasticsearch7XCluster : TestCluster + { + public LogModelPropsCollector? Collector; + public Elasticsearch7XCluster() : base(CreateConfiguration()) + { + } + + private static ClientTestClusterConfiguration CreateConfiguration() + { + return new ClientTestClusterConfiguration("7.8.0") + { + MaxConcurrency = 1 + }; + } + + protected override void SeedCluster() { } + + protected override void OnAfterStarted() + { + var serilog = new SetupSerilog(); + Collector = serilog.InitializeLogs(); + } + } + + public abstract class TestCluster : XunitClusterBase + { + protected TestCluster(ClientTestClusterConfiguration configuration) : base(configuration) { } + } + + public class ClientTestClusterConfiguration : XunitClusterConfiguration + { + public ClientTestClusterConfiguration( + string elasticsearchVersion, + ClusterFeatures features = ClusterFeatures.None, + int numberOfNodes = 1, + params ElasticsearchPlugin[] plugins + ) + : base(elasticsearchVersion, features, new ElasticsearchPlugins(plugins), numberOfNodes) + { + HttpFiddlerAware = true; + CacheEsHomeInstallation = true; + + Add(AttributeKey("testingcluster"), "true"); + + Add($"script.max_compilations_per_minute", "10000", "<6.0.0-rc1"); + Add($"script.max_compilations_rate", "10000/1m", ">=6.0.0-rc1"); + + Add($"script.inline", "true", "<5.5.0"); + Add($"script.stored", "true", ">5.0.0-alpha1 <5.5.0"); + Add($"script.indexed", "true", "<5.0.0-alpha1"); + Add($"script.allowed_types", "inline,stored", ">=5.5.0"); + } + } +} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderBaseTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderBaseTest.cs new file mode 100644 index 00000000..93adad83 --- /dev/null +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderBaseTest.cs @@ -0,0 +1,45 @@ +using FluentAssertions; +using MongoDB.Driver; +using Moq; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.MongoDbProvider; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace MongoDb.Tests.DataProvider +{ + [Trait("Unit-Base", "MongoDb")] + public class DataProviderBaseTest : IUnitBaseTests + { + [Fact] + public void It_throws_when_any_dependency_is_null() + { + var suts = new List> + { + () => new MongoDbDataProvider(null, null), + () => new MongoDbDataProvider(new MongoClient(), null), + () => new MongoDbDataProvider(null, new MongoDbOptions()), + }; + + suts.ForEach(sut => sut.Should().ThrowExactly()); + } + + [Fact] + public Task It_logs_and_throws_when_db_read_breaks_down() + { + var mockClient = new Mock(); + var mockDb = new Mock(); + var mockColl = new Mock>(); + mockClient.Setup(p => p.GetDatabase(It.IsAny(), null)) + .Returns(mockDb.Object); + mockDb.Setup(p => p.GetCollection(It.IsAny(), null)).Returns(() => mockColl.Object); + + var sut = new MongoDbDataProvider(mockClient.Object, + new MongoDbOptions() { CollectionName = "coll", ConnectionString = "some", DatabaseName = "db" }); + var assert = () => sut.FetchDataAsync(1, 10); + return assert.Should().ThrowAsync(); + } + } +} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderPaginationTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderPaginationTest.cs new file mode 100644 index 00000000..58622f34 --- /dev/null +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderPaginationTest.cs @@ -0,0 +1,30 @@ +using FluentAssertions; +using MongoDb.Tests.Util; +using Serilog.Ui.Common.Tests.TestSuites.Impl; +using Serilog.Ui.MongoDbProvider; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace MongoDb.Tests.DataProvider +{ + [Collection(nameof(MongoDbDataProvider))] + [Trait("Integration-Pagination", "MongoDb")] + public class DataProviderPaginationTest : IntegrationPaginationTests + { + public DataProviderPaginationTest(BaseIntegrationTest instance) : base(instance) { } + + public override Task It_fetches_with_limit() => base.It_fetches_with_limit(); + + public override Task It_fetches_with_limit_and_skip() => base.It_fetches_with_limit_and_skip(); + + public override Task It_fetches_with_skip() => base.It_fetches_with_skip(); + + [Fact] + public override Task It_throws_when_skip_is_zero() + { + var test = () => provider.FetchDataAsync(0, 1); + return test.Should().ThrowAsync(); + } + } +} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderSearchTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderSearchTest.cs new file mode 100644 index 00000000..28eee1ca --- /dev/null +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/DataProviderSearchTest.cs @@ -0,0 +1,39 @@ +using MongoDb.Tests.Util; +using MsSql.Tests.DataProvider; +using Serilog.Ui.MongoDbProvider; +using System.Threading.Tasks; +using Xunit; + +namespace MongoDb.Tests.DataProvider +{ + [Collection(nameof(MongoDbDataProvider))] + [Trait("Integration-Search", "MongoDb")] + public class DataProviderSearchTest : IntegrationSearchTests + { + public DataProviderSearchTest(BaseIntegrationTest instance) : base(instance) { } + + public override Task It_finds_all_data_with_default_search() + => base.It_finds_all_data_with_default_search(); + + public override Task It_finds_data_with_all_filters() + => It_finds_data_with_all_filters_by_utc(true, false); + + public override Task It_finds_only_data_emitted_after_date() + => It_finds_only_data_emitted_after_date_by_utc(true); + + public override Task It_finds_only_data_emitted_before_date() + => It_finds_only_data_emitted_before_date_by_utc(true); + + public override Task It_finds_only_data_emitted_in_dates_range() + => It_finds_only_data_emitted_in_dates_range_by_utc(true); + + public override Task It_finds_only_data_with_specific_level() + => base.It_finds_only_data_with_specific_level(); + + public override Task It_finds_only_data_with_specific_message_content() + => base.It_finds_only_data_with_specific_message_content(); + + public override Task It_finds_same_data_on_same_repeated_search() + => base.It_finds_same_data_on_same_repeated_search(); + } +} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/MongoDbDataProviderTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/MongoDbDataProviderTest.cs deleted file mode 100644 index f81c8342..00000000 --- a/tests/Serilog.Ui.MongoDbProvider.Tests/DataProvider/MongoDbDataProviderTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FluentAssertions; -using Serilog.Ui.MongoDbProvider.Tests.Util; -using Serilog.Ui.MongoDbProvider.Tests.Util.Builders; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Xunit; - -namespace Serilog.Ui.MongoDbProvider.Tests.DataProvider -{ - public class MongoDbDataProviderTest : BaseIntegrationTest, IAsyncLifetime - { - public Task DisposeAsync() => Task.CompletedTask; - - public async Task InitializeAsync() - { - _builder = await MongoDbDataProviderBuilder.Build(false); - } - - [Fact] - public async Task It_finds_all_data_with_default_search() - { - var (Logs, Count) = await _builder._sut.FetchDataAsync(1, 10); - - Logs.Should().NotBeEmpty().And.HaveCount(10); - Count.Should().Be(20); - } - - [Fact] - public void It_throws_when_any_dependency_is_null() - { - var suts = new List> - { - () => new MongoDbDataProvider(null, null), - () => new MongoDbDataProvider(_builder._client, null), - () => new MongoDbDataProvider(null, _builder._options), - }; - - suts.ForEach(sut => sut.Should().ThrowExactly()); - } - } -} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs new file mode 100644 index 00000000..e516c015 --- /dev/null +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs @@ -0,0 +1,94 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using MongoDB.Driver; +using Serilog.Ui.Core; +using Serilog.Ui.MongoDbProvider; +using Serilog.Ui.Web; +using System; +using System.Collections.Generic; +using Xunit; + +namespace MongoDb.Tests.Extensions +{ + [Trait("DI-DataProvider", "MongoDb")] + public class SerilogUiOptionBuilderExtensionsTest + { + private readonly ServiceCollection serviceCollection; + + public SerilogUiOptionBuilderExtensionsTest() + { + serviceCollection = new ServiceCollection(); + } + + [Fact] + public void It_registers_provider_and_dependencies_with_connstring_and_collection() + { + serviceCollection.AddSerilogUi((builder) => + { + builder.UseMongoDb("mongodb://mongodb0.example.com:27017/my-db", "my-collection"); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Should().NotBeNull(); + services.GetRequiredService().Should().NotBeNull().And.BeOfType(); + services.GetRequiredService().Should().NotBeNull(); + } + + [Fact] + public void It_registers_provider_and_dependencies_with_connstring_collection_and_dbname() + { + serviceCollection.AddSerilogUi((builder) => + { + builder.UseMongoDb("mongodb://mongodb0.example.com:27017", "my-db", "my-collection"); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Should().NotBeNull(); + services.GetRequiredService().Should().NotBeNull().And.BeOfType(); + services.GetRequiredService().Should().NotBeNull(); + services.GetRequiredService().Settings.ApplicationName.Should().BeNullOrWhiteSpace(); + } + + [Fact] + public void It_registers_IMongoClient_only_when_not_registered() + { + serviceCollection.AddSingleton(p => + new MongoClient(new MongoClientSettings { ApplicationName = "my-app" })); + serviceCollection.AddSerilogUi((builder) => + { + builder.UseMongoDb("mongodb://mongodb0.example.com:27017", "my-db", "my-collection"); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Settings.ApplicationName.Should().Be("my-app"); + } + + [Fact] + public void It_throws_on_invalid_registration() + { + var nullables = new List> + { + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb(null, "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb(" ", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", null)), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", " ")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", "")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", "name", null)), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", "name", "")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", "name", " ")), + }; + + foreach (var nullable in nullables) + { + nullable.Should().ThrowExactly(); + } + + var act = () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("mongodb://mongodb0.example.com:27017", "name")); + act.Should().ThrowExactly(); + + var actConfig = () => serviceCollection.AddSerilogUi((builder) => builder.UseMongoDb("name", "name")); + actConfig.Should().ThrowExactly(); + } + } +} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Serilog.Ui.MongoDbProvider.Tests.csproj b/tests/Serilog.Ui.MongoDbProvider.Tests/Serilog.Ui.MongoDbProvider.Tests.csproj index 83f100e5..955b805d 100644 --- a/tests/Serilog.Ui.MongoDbProvider.Tests/Serilog.Ui.MongoDbProvider.Tests.csproj +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/Serilog.Ui.MongoDbProvider.Tests.csproj @@ -1,30 +1,47 @@ - + net6.0 enable - + MongoDb.Tests + MongoDb.Tests false - - + + %(Filename)%(Extension) + PreserveNewest + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + + + + + - diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseIntegrationTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseIntegrationTest.cs new file mode 100644 index 00000000..afa45475 --- /dev/null +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseIntegrationTest.cs @@ -0,0 +1,54 @@ +using Ardalis.GuardClauses; +using MongoDb.Tests.Util.Builders; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.Core; +using Serilog.Ui.MongoDbProvider; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace MongoDb.Tests.Util +{ + [CollectionDefinition(nameof(MongoDbDataProvider))] + public class MongoCollection : ICollectionFixture { } + + public class BaseIntegrationTest : IIntegrationRunner + { + private bool _disposedValue; + + internal BaseServiceBuilder? _builder; + + public Task DisposeAsync() => Task.CompletedTask; + + public virtual async Task InitializeAsync() + { + _builder = await MongoDbDataProviderBuilder.Build(false); + } + + public IDataProvider GetDataProvider() => Guard.Against.Null(_builder?._sut)!; + + public LogModelPropsCollector GetPropsCollector() => Guard.Against.Null(_builder?._collector)!; + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing && _builder != null) + { + _builder._runner.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + +} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseServiceBuilder.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseServiceBuilder.cs index 8b814716..128220b0 100644 --- a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseServiceBuilder.cs +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/BaseServiceBuilder.cs @@ -1,15 +1,20 @@ using Mongo2Go; using MongoDB.Driver; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Core; +using Serilog.Ui.MongoDbProvider; using System; -namespace Serilog.Ui.MongoDbProvider.Tests.Util +namespace MongoDb.Tests.Util { - public class BaseServiceBuilder : IDisposable + public class BaseServiceBuilder { internal MongoDbRunner _runner; internal MongoDbOptions _options; internal IMongoClient _client; internal IMongoDatabase _database; + internal IDataProvider? _sut; + internal LogModelPropsCollector? _collector; public BaseServiceBuilder(MongoDbOptions options) { @@ -17,29 +22,5 @@ public BaseServiceBuilder(MongoDbOptions options) (_runner, _client) = IntegrationDbGeneration.Generate(options); _database = _client.GetDatabase(options.DatabaseName); } - - private bool _disposedValue; - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - _runner.Dispose(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - _disposedValue = true; - } - } - - void IDisposable.Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/Builders/MongoDbDataProviderBuilder.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/Builders/MongoDbDataProviderBuilder.cs index c77f0785..b0c97d62 100644 --- a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/Builders/MongoDbDataProviderBuilder.cs +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/Builders/MongoDbDataProviderBuilder.cs @@ -1,14 +1,14 @@ using MongoDB.Driver; using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.MongoDbProvider; using System.Threading.Tasks; -namespace Serilog.Ui.MongoDbProvider.Tests.Util.Builders +namespace MongoDb.Tests.Util.Builders { public class MongoDbDataProviderBuilder : BaseServiceBuilder { private const string DefaultDbName = "IntegrationTests"; - internal MongoDbDataProvider _sut; internal IMongoCollection _mongoCollection; protected MongoDbDataProviderBuilder(MongoDbOptions options) : base(options) @@ -21,14 +21,15 @@ public static async Task Build(bool useLinq3) { var options = new MongoDbOptions() { CollectionName = "LogCollection", DatabaseName = DefaultDbName }; // , UseLinq3 = useLinq3 }; var builder = new MongoDbDataProviderBuilder(options); - await Seed(builder._mongoCollection); + builder._collector = await Seed(builder._mongoCollection); return builder; } - public static Task Seed(IMongoCollection collection) + public static async Task Seed(IMongoCollection collection) { - var array = MongoDbLogModelFaker.Logs(); - return collection.InsertManyAsync(array); + var (array, collector) = MongoDbLogModelFaker.Logs(100); + await collection.InsertManyAsync(array); + return collector; } } } diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/GenericBaseIntegrationTest.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/GenericBaseIntegrationTest.cs deleted file mode 100644 index 8ecee0d6..00000000 --- a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/GenericBaseIntegrationTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Serilog.Ui.MongoDbProvider.Tests.Util -{ - public class BaseIntegrationTest : IDisposable where T : BaseServiceBuilder - { -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - internal T _builder; -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - - public void Dispose() - { - if (_builder != null) - { - ((IDisposable)_builder).Dispose(); - } - - GC.SuppressFinalize(this); - } - } - -} diff --git a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/IntegrationDbGeneration.cs b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/IntegrationDbGeneration.cs index 1b3ea1c5..ccb413e2 100644 --- a/tests/Serilog.Ui.MongoDbProvider.Tests/Util/IntegrationDbGeneration.cs +++ b/tests/Serilog.Ui.MongoDbProvider.Tests/Util/IntegrationDbGeneration.cs @@ -1,14 +1,18 @@ using Mongo2Go; using MongoDB.Driver; +using Serilog.Ui.MongoDbProvider; +using System; -namespace Serilog.Ui.MongoDbProvider.Tests.Util +namespace MongoDb.Tests.Util { public static class IntegrationDbGeneration { public static (MongoDbRunner runner, IMongoClient client) Generate(MongoDbOptions options) { var runner = MongoDbRunner.Start(singleNodeReplSet: true, additionalMongodArguments: "--quiet"); - var client = new MongoClient(runner.ConnectionString); + var settings = MongoClientSettings.FromConnectionString(runner.ConnectionString); + settings.ServerSelectionTimeout = TimeSpan.FromSeconds(10); + var client = new MongoClient(settings); options.ConnectionString = runner.ConnectionString; return (runner, client); } diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderBaseTest.cs b/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderBaseTest.cs new file mode 100644 index 00000000..f0802c60 --- /dev/null +++ b/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderBaseTest.cs @@ -0,0 +1,34 @@ +using FluentAssertions; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.MsSqlServerProvider; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace MsSql.Tests.DataProvider +{ + [Trait("Unit-Base", "MsSql")] + public class DataProviderBaseTest : IUnitBaseTests + { + [Fact] + public void It_throws_when_any_dependency_is_null() + { + var suts = new List> + { + () => new SqlServerDataProvider(null), + }; + + suts.ForEach(sut => sut.Should().ThrowExactly()); + } + + [Fact] + public Task It_logs_and_throws_when_db_read_breaks_down() + { + var sut = new SqlServerDataProvider(new() { ConnectionString = "connString", Schema = "dbo", TableName = "logs" }); + + var assert = () => sut.FetchDataAsync(1, 10); + return assert.Should().ThrowExactlyAsync(); + } + } +} diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderPaginationTest.cs b/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderPaginationTest.cs new file mode 100644 index 00000000..111386b5 --- /dev/null +++ b/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderPaginationTest.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using Microsoft.Data.SqlClient; +using MsSql.Tests.Util; +using Serilog.Ui.Common.Tests.TestSuites.Impl; +using Serilog.Ui.MsSqlServerProvider; +using System.Threading.Tasks; +using Xunit; + +namespace MsSql.Tests.DataProvider +{ + [Collection(nameof(SqlServerDataProvider))] + [Trait("Integration-Pagination", "MsSql")] + public class DataProviderPaginationTest : IntegrationPaginationTests + { + public DataProviderPaginationTest(MsSqlServerTestProvider instance) : base(instance) + { + } + + public override Task It_fetches_with_limit() => base.It_fetches_with_limit(); + + public override Task It_fetches_with_limit_and_skip() => base.It_fetches_with_limit_and_skip(); + + public override Task It_fetches_with_skip() => base.It_fetches_with_skip(); + + [Fact] + public override Task It_throws_when_skip_is_zero() + { + var test = () => provider.FetchDataAsync(0, 1); + return test.Should().ThrowAsync(); + } + } +} diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderSearchTest.cs b/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderSearchTest.cs new file mode 100644 index 00000000..d637b55c --- /dev/null +++ b/tests/Serilog.Ui.MsSqlServerProvider.Tests/DataProvider/DataProviderSearchTest.cs @@ -0,0 +1,38 @@ +using MsSql.Tests.Util; +using Serilog.Ui.MsSqlServerProvider; +using System.Threading.Tasks; +using Xunit; + +namespace MsSql.Tests.DataProvider +{ + [Collection(nameof(SqlServerDataProvider))] + [Trait("Integration-Search", "MsSql")] + public class DataProviderSearchTest : IntegrationSearchTests + { + public DataProviderSearchTest(MsSqlServerTestProvider instance) : base(instance) { } + + public override Task It_finds_all_data_with_default_search() + => base.It_finds_all_data_with_default_search(); + + public override Task It_finds_data_with_all_filters() + => base.It_finds_data_with_all_filters(); + + public override Task It_finds_only_data_emitted_after_date() + => base.It_finds_only_data_emitted_after_date(); + + public override Task It_finds_only_data_emitted_before_date() + => base.It_finds_only_data_emitted_before_date(); + + public override Task It_finds_only_data_emitted_in_dates_range() + => base.It_finds_only_data_emitted_in_dates_range(); + + public override Task It_finds_only_data_with_specific_level() + => base.It_finds_only_data_with_specific_level(); + + public override Task It_finds_only_data_with_specific_message_content() + => base.It_finds_only_data_with_specific_message_content(); + + public override Task It_finds_same_data_on_same_repeated_search() + => base.It_finds_same_data_on_same_repeated_search(); + } +} \ No newline at end of file diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs b/tests/Serilog.Ui.MsSqlServerProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs new file mode 100644 index 00000000..4da3a64f --- /dev/null +++ b/tests/Serilog.Ui.MsSqlServerProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs @@ -0,0 +1,60 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using Serilog.Ui.MsSqlServerProvider; +using Serilog.Ui.Web; +using System; +using System.Collections.Generic; +using Xunit; + +namespace MsSql.Tests.Extensions +{ + [Trait("DI-DataProvider", "MsSql")] + public class SerilogUiOptionBuilderExtensionsTest + { + private readonly ServiceCollection serviceCollection; + + public SerilogUiOptionBuilderExtensionsTest() + { + serviceCollection = new ServiceCollection(); + } + + [Theory] + [InlineData(null, "dbo")] + [InlineData("schema", "schema")] + public void It_registers_provider_and_dependencies(string schemaName, string expected) + { + serviceCollection.AddSerilogUi((builder) => + { + builder.UseSqlServer("https://sqlserver.example.com", "my-table", schemaName); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Should().NotBeNull().And.BeOfType(); + var options = services.GetRequiredService(); + options.Should().NotBeNull(); + options.ConnectionString.Should().Be("https://sqlserver.example.com"); + options.TableName.Should().Be("my-table"); + options.Schema.Should().Be(expected); + } + + [Fact] + public void It_throws_on_invalid_registration() + { + var nullables = new List> + { + () => serviceCollection.AddSerilogUi((builder) => builder.UseSqlServer(null, "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseSqlServer(" ", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseSqlServer("", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseSqlServer("name", null)), + () => serviceCollection.AddSerilogUi((builder) => builder.UseSqlServer("name", " ")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseSqlServer("name", "")), + }; + + foreach (var nullable in nullables) + { + nullable.Should().ThrowExactly(); + } + } + } +} diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/Serilog.Ui.MsSqlServerProvider.Tests.csproj b/tests/Serilog.Ui.MsSqlServerProvider.Tests/Serilog.Ui.MsSqlServerProvider.Tests.csproj index d1607420..35559577 100644 --- a/tests/Serilog.Ui.MsSqlServerProvider.Tests/Serilog.Ui.MsSqlServerProvider.Tests.csproj +++ b/tests/Serilog.Ui.MsSqlServerProvider.Tests/Serilog.Ui.MsSqlServerProvider.Tests.csproj @@ -1,21 +1,38 @@ - + net6.0 enable - + MsSql.Tests + MsSql.Tests false - - - - + + %(Filename)%(Extension) + PreserveNewest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -23,7 +40,10 @@ + - + + + diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/SqlServerDataProviderTest.cs b/tests/Serilog.Ui.MsSqlServerProvider.Tests/SqlServerDataProviderTest.cs deleted file mode 100644 index 2b2f715c..00000000 --- a/tests/Serilog.Ui.MsSqlServerProvider.Tests/SqlServerDataProviderTest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Xunit; - -namespace Serilog.Ui.MsSqlServerProvider.Tests -{ - public class SqlServerDataProviderTest - { - [Fact] - public void Test1() - { - - } - } -} \ No newline at end of file diff --git a/tests/Serilog.Ui.MsSqlServerProvider.Tests/Util/MsSqlServerTestProvider.cs b/tests/Serilog.Ui.MsSqlServerProvider.Tests/Util/MsSqlServerTestProvider.cs new file mode 100644 index 00000000..ac8a70fd --- /dev/null +++ b/tests/Serilog.Ui.MsSqlServerProvider.Tests/Util/MsSqlServerTestProvider.cs @@ -0,0 +1,54 @@ +using Dapper; +using Testcontainers.MsSql; +using Microsoft.Data.SqlClient; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.SqlUtil; +using Serilog.Ui.Core; +using Serilog.Ui.MsSqlServerProvider; +using System.Threading.Tasks; +using Xunit; + +namespace MsSql.Tests.Util +{ + [CollectionDefinition(nameof(SqlServerDataProvider))] + public class SqlServerCollection : ICollectionFixture { } + + public sealed class MsSqlServerTestProvider : DatabaseInstance + { + public MsSqlServerTestProvider() + { + Container = new MsSqlBuilder().Build(); + } + + public RelationalDbOptions DbOptions { get; set; } = new() + { + TableName = "Logs", + Schema = "dbo" + }; + + protected override string Name => nameof(MsSqlContainer); + + protected override async Task CheckDbReadinessAsync() + { + DbOptions.ConnectionString = (Container as MsSqlContainer)?.GetConnectionString(); + + using var dataContext = new SqlConnection(DbOptions.ConnectionString); + + await dataContext.ExecuteAsync("SELECT DATABASEPROPERTYEX(N'master', 'Collation')"); + } + + protected override async Task InitializeAdditionalAsync() + { + var logs = LogModelFaker.Logs(100); + Collector = new LogModelPropsCollector(logs); + + using var dataContext = new SqlConnection(DbOptions.ConnectionString); + + await dataContext.ExecuteAsync(Costants.MsSqlCreateTable); + + await dataContext.ExecuteAsync(Costants.MsSqlInsertFakeData, logs); + + Provider = new SqlServerDataProvider(DbOptions); + } + } +} diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderBaseTest.cs b/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderBaseTest.cs new file mode 100644 index 00000000..9c438028 --- /dev/null +++ b/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderBaseTest.cs @@ -0,0 +1,35 @@ +using FluentAssertions; +using MySql.Data.MySqlClient; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.MySqlProvider; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace MySql.Tests.DataProvider +{ + [Trait("Unit-Base", "MySql")] + public class DataProviderBaseTest : IUnitBaseTests + { + [Fact] + public void It_throws_when_any_dependency_is_null() + { + var suts = new List> + { + () => new MySqlDataProvider(null), + }; + + suts.ForEach(sut => sut.Should().ThrowExactly()); + } + + [Fact] + public Task It_logs_and_throws_when_db_read_breaks_down() + { + var sut = new MySqlDataProvider(new() { ConnectionString = "connString", Schema = "dbo", TableName = "logs" }); + + var assert = () => sut.FetchDataAsync(1, 10); + return assert.Should().ThrowExactlyAsync(); + } + } +} diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderPaginationTest.cs b/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderPaginationTest.cs new file mode 100644 index 00000000..96ee2a8b --- /dev/null +++ b/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderPaginationTest.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using MySql.Data.MySqlClient; +using MySql.Tests.Util; +using Serilog.Ui.Common.Tests.TestSuites.Impl; +using Serilog.Ui.MySqlProvider; +using System.Threading.Tasks; +using Xunit; + +namespace MySql.Tests.DataProvider +{ + [Collection(nameof(MySqlDataProvider))] + [Trait("Integration-Pagination", "MySql")] + public class DataProviderPaginationTest : IntegrationPaginationTests + { + public DataProviderPaginationTest(MySqlTestProvider instance) : base(instance) + { + } + + public override Task It_fetches_with_limit() => base.It_fetches_with_limit(); + + public override Task It_fetches_with_limit_and_skip() => base.It_fetches_with_limit_and_skip(); + + public override Task It_fetches_with_skip() => base.It_fetches_with_skip(); + + [Fact] + public override Task It_throws_when_skip_is_zero() + { + var test = () => provider.FetchDataAsync(0, 1); + return test.Should().ThrowAsync(); + } + } +} diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderSearchTest.cs b/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderSearchTest.cs new file mode 100644 index 00000000..9e6f3b48 --- /dev/null +++ b/tests/Serilog.Ui.MySqlProvider.Tests/DataProvider/DataProviderSearchTest.cs @@ -0,0 +1,41 @@ +using MsSql.Tests.DataProvider; +using MySql.Tests.Util; +using Serilog.Ui.MySqlProvider; +using System.Threading.Tasks; +using Xunit; + +namespace MySql.Tests.DataProvider +{ + [Collection(nameof(MySqlDataProvider))] + [Trait("Integration-Search", "MySql")] + public class DataProviderSearchTest : IntegrationSearchTests + { + public DataProviderSearchTest(MySqlTestProvider instance) : base(instance) + { + } + + public override Task It_finds_all_data_with_default_search() + => base.It_finds_all_data_with_default_search(); + + public override Task It_finds_data_with_all_filters() + => base.It_finds_data_with_all_filters(); + + public override Task It_finds_only_data_emitted_after_date() + => base.It_finds_only_data_emitted_after_date(); + + public override Task It_finds_only_data_emitted_before_date() + => base.It_finds_only_data_emitted_before_date(); + + public override Task It_finds_only_data_emitted_in_dates_range() + => base.It_finds_only_data_emitted_in_dates_range(); + + public override Task It_finds_only_data_with_specific_level() + => base.It_finds_only_data_with_specific_level(); + + public override Task It_finds_only_data_with_specific_message_content() + => base.It_finds_only_data_with_specific_message_content(); + + public override Task It_finds_same_data_on_same_repeated_search() + => base.It_finds_same_data_on_same_repeated_search(); + } +} \ No newline at end of file diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs b/tests/Serilog.Ui.MySqlProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs new file mode 100644 index 00000000..78662117 --- /dev/null +++ b/tests/Serilog.Ui.MySqlProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs @@ -0,0 +1,57 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using Serilog.Ui.MySqlProvider; +using Serilog.Ui.Web; +using System; +using System.Collections.Generic; +using Xunit; + +namespace MySql.Tests.Extensions +{ + [Trait("DI-DataProvider", "MySql")] + public class SerilogUiOptionBuilderExtensionsTest + { + private readonly ServiceCollection serviceCollection; + + public SerilogUiOptionBuilderExtensionsTest() + { + serviceCollection = new ServiceCollection(); + } + + [Fact] + public void It_registers_provider_and_dependencies() + { + serviceCollection.AddSerilogUi((builder) => + { + builder.UseMySqlServer("https://mysqlserver.example.com", "my-table"); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Should().NotBeNull().And.BeOfType(); + var options = services.GetRequiredService(); + options.Should().NotBeNull(); + options.ConnectionString.Should().Be("https://mysqlserver.example.com"); + options.TableName.Should().Be("my-table"); + } + + [Fact] + public void It_throws_on_invalid_registration() + { + var nullables = new List> + { + () => serviceCollection.AddSerilogUi((builder) => builder.UseMySqlServer(null, "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMySqlServer(" ", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMySqlServer("", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMySqlServer("name", null)), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMySqlServer("name", " ")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseMySqlServer("name", "")), + }; + + foreach (var nullable in nullables) + { + nullable.Should().ThrowExactly(); + } + } + } +} diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/MySqlDataProviderTest.cs b/tests/Serilog.Ui.MySqlProvider.Tests/MySqlDataProviderTest.cs deleted file mode 100644 index 1ae9a1e1..00000000 --- a/tests/Serilog.Ui.MySqlProvider.Tests/MySqlDataProviderTest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Xunit; - -namespace Serilog.Ui.MySqlProvider.Tests -{ - public class MySqlDataProviderTest - { - [Fact] - public void Test1() - { - - } - } -} \ No newline at end of file diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/Serilog.Ui.MySqlProvider.Tests.csproj b/tests/Serilog.Ui.MySqlProvider.Tests/Serilog.Ui.MySqlProvider.Tests.csproj index f6026d1f..48637717 100644 --- a/tests/Serilog.Ui.MySqlProvider.Tests/Serilog.Ui.MySqlProvider.Tests.csproj +++ b/tests/Serilog.Ui.MySqlProvider.Tests/Serilog.Ui.MySqlProvider.Tests.csproj @@ -1,21 +1,38 @@ - + net6.0 enable - + MySql.Tests + MySql.Tests false - - - - + + %(Filename)%(Extension) + PreserveNewest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -23,7 +40,10 @@ + - + + + diff --git a/tests/Serilog.Ui.MySqlProvider.Tests/Util/MySqlTestProvider.cs b/tests/Serilog.Ui.MySqlProvider.Tests/Util/MySqlTestProvider.cs new file mode 100644 index 00000000..f7f2c67b --- /dev/null +++ b/tests/Serilog.Ui.MySqlProvider.Tests/Util/MySqlTestProvider.cs @@ -0,0 +1,57 @@ +using Ardalis.GuardClauses; +using Dapper; +using MySql.Data.MySqlClient; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.SqlUtil; +using Serilog.Ui.Core; +using Serilog.Ui.MySqlProvider; +using System.Threading.Tasks; +using Testcontainers.MySql; +using Xunit; + +namespace MySql.Tests.Util +{ + [CollectionDefinition(nameof(MySqlDataProvider))] + public class MySqlCollection : ICollectionFixture { } + + public sealed class MySqlTestProvider : DatabaseInstance + { + protected override string Name => nameof(MySqlContainer); + public MySqlTestProvider() : base() + { + Container = new MySqlBuilder().Build(); + } + + public RelationalDbOptions DbOptions { get; set; } = new() + { + TableName = "Logs", + Schema = "dbo" + }; + + protected override async Task CheckDbReadinessAsync() + { + Guard.Against.Null(Container); + + DbOptions.ConnectionString = (Container as MySqlContainer)?.GetConnectionString(); + + using var dataContext = new MySqlConnection(DbOptions.ConnectionString); + + await dataContext.ExecuteAsync("SELECT 1"); + } + + protected override async Task InitializeAdditionalAsync() + { + var logs = LogModelFaker.Logs(100); + Collector = new LogModelPropsCollector(logs); + + using var dataContext = new MySqlConnection(DbOptions.ConnectionString); + + await dataContext.ExecuteAsync(Costants.MySqlCreateTable); + + await dataContext.ExecuteAsync(Costants.MySqlInsertFakeData, logs); + + Provider = new MySqlDataProvider(DbOptions); + } + + } +} diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderBaseTest.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderBaseTest.cs new file mode 100644 index 00000000..90d52256 --- /dev/null +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderBaseTest.cs @@ -0,0 +1,34 @@ +using FluentAssertions; +using Serilog.Ui.Common.Tests.TestSuites; +using Serilog.Ui.PostgreSqlProvider; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Postgres.Tests.DataProvider +{ + [Trait("Unit-Base", "Postgres")] + public class DataProviderBaseTest : IUnitBaseTests + { + [Fact] + public void It_throws_when_any_dependency_is_null() + { + var suts = new List> + { + () => new PostgresDataProvider(null), + }; + + suts.ForEach(sut => sut.Should().ThrowExactly()); + } + + [Fact] + public Task It_logs_and_throws_when_db_read_breaks_down() + { + var sut = new PostgresDataProvider(new() { ConnectionString = "connString", Schema = "dbo", TableName = "logs" }); + + var assert = () => sut.FetchDataAsync(1, 10); + return assert.Should().ThrowExactlyAsync(); + } + } +} diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderPaginationTest.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderPaginationTest.cs new file mode 100644 index 00000000..d3b0d807 --- /dev/null +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderPaginationTest.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using Npgsql; +using Postgres.Tests.Util; +using Serilog.Ui.Common.Tests.TestSuites.Impl; +using Serilog.Ui.PostgreSqlProvider; +using System.Threading.Tasks; +using Xunit; + +namespace Postgres.Tests.DataProvider +{ + [Collection(nameof(PostgresDataProvider))] + [Trait("Integration-Pagination", "Postgres")] + public class DataProviderPaginationTest : IntegrationPaginationTests + { + public DataProviderPaginationTest(PostgresTestProvider instance) : base(instance) + { + } + + public override Task It_fetches_with_limit() => base.It_fetches_with_limit(); + + public override Task It_fetches_with_limit_and_skip() => base.It_fetches_with_limit_and_skip(); + + public override Task It_fetches_with_skip() => base.It_fetches_with_skip(); + + [Fact] + public override Task It_throws_when_skip_is_zero() + { + var test = () => provider.FetchDataAsync(0, 1); + return test.Should().ThrowAsync(); + } + } +} \ No newline at end of file diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderSearchTest.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderSearchTest.cs new file mode 100644 index 00000000..16ba7b53 --- /dev/null +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/DataProvider/DataProviderSearchTest.cs @@ -0,0 +1,41 @@ +using MsSql.Tests.DataProvider; +using Postgres.Tests.Util; +using Serilog.Ui.PostgreSqlProvider; +using System.Threading.Tasks; +using Xunit; + +namespace Postgres.Tests.DataProvider +{ + [Collection(nameof(PostgresDataProvider))] + [Trait("Integration-Search", "Postgres")] + public class DataProviderSearchTest : IntegrationSearchTests + { + public DataProviderSearchTest(PostgresTestProvider instance) : base(instance) + { + } + + public override Task It_finds_all_data_with_default_search() + => base.It_finds_all_data_with_default_search(); + + public override Task It_finds_data_with_all_filters() + => base.It_finds_data_with_all_filters(); + + public override Task It_finds_only_data_emitted_after_date() + => base.It_finds_only_data_emitted_after_date(); + + public override Task It_finds_only_data_emitted_before_date() + => base.It_finds_only_data_emitted_before_date(); + + public override Task It_finds_only_data_emitted_in_dates_range() + => base.It_finds_only_data_emitted_in_dates_range(); + + public override Task It_finds_only_data_with_specific_level() + => base.It_finds_only_data_with_specific_level(); + + public override Task It_finds_only_data_with_specific_message_content() + => base.It_finds_only_data_with_specific_message_content(); + + public override Task It_finds_same_data_on_same_repeated_search() + => base.It_finds_same_data_on_same_repeated_search(); + } +} \ No newline at end of file diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs new file mode 100644 index 00000000..2bd8bb4f --- /dev/null +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Extensions/SerilogUiOptionBuilderExtensionsTest.cs @@ -0,0 +1,60 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Serilog.Ui.Core; +using Serilog.Ui.PostgreSqlProvider; +using Serilog.Ui.Web; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Postgres.Tests.Extensions +{ + [Trait("DI-DataProvider", "Postgres")] + public class SerilogUiOptionBuilderExtensionsTest + { + private readonly ServiceCollection serviceCollection; + + public SerilogUiOptionBuilderExtensionsTest() + { + serviceCollection = new ServiceCollection(); + } + + [Theory] + [InlineData(null, "public")] + [InlineData("schema", "schema")] + public void It_registers_provider_and_dependencies(string schemaName, string expected) + { + serviceCollection.AddSerilogUi((builder) => + { + builder.UseNpgSql("https://npgsql.example.com", "my-table", schemaName); + }); + var services = serviceCollection.BuildServiceProvider(); + + services.GetRequiredService().Should().NotBeNull().And.BeOfType(); + var options = services.GetRequiredService(); + options.Should().NotBeNull(); + options.ConnectionString.Should().Be("https://npgsql.example.com"); + options.TableName.Should().Be("my-table"); + options.Schema.Should().Be(expected); + } + + [Fact] + public void It_throws_on_invalid_registration() + { + var nullables = new List> + { + () => serviceCollection.AddSerilogUi((builder) => builder.UseNpgSql(null, "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseNpgSql(" ", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseNpgSql("", "name")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseNpgSql("name", null)), + () => serviceCollection.AddSerilogUi((builder) => builder.UseNpgSql("name", " ")), + () => serviceCollection.AddSerilogUi((builder) => builder.UseNpgSql("name", "")), + }; + + foreach (var nullable in nullables) + { + nullable.Should().ThrowExactly(); + } + } + } +} diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/Model/LogLevelConverterTest.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Model/LogLevelConverterTest.cs new file mode 100644 index 00000000..24392a81 --- /dev/null +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Model/LogLevelConverterTest.cs @@ -0,0 +1,38 @@ +using FluentAssertions; +using Serilog.Ui.PostgreSqlProvider; +using Xunit; + +namespace Postgres.Tests.Model +{ + [Trait("Custom", "Postgres")] + public class LogLevelConverterTest + { + [Theory] + [InlineData("0", "Verbose")] + [InlineData("1", "Debug")] + [InlineData("2", "Information")] + [InlineData("3", "Warning")] + [InlineData("4", "Error")] + [InlineData("5", "Fatal")] + [InlineData("random", "")] + public void It_maps_the_correct_log_level_name(string input, string expected) + { + var act = LogLevelConverter.GetLevelName(input); + act.Should().Be(expected); + } + + [Theory] + [InlineData("Verbose", 0)] + [InlineData("Debug", 1)] + [InlineData("Information", 2)] + [InlineData("Warning", 3)] + [InlineData("Error", 4)] + [InlineData("Fatal", 5)] + [InlineData("random", 100)] + public void It_maps_the_correct_log_level_value(string input, int expected) + { + var act = LogLevelConverter.GetLevelValue(input); + act.Should().Be(expected); + } + } +} diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/PostgresDataProviderTest.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/PostgresDataProviderTest.cs deleted file mode 100644 index e5e03baf..00000000 --- a/tests/Serilog.Ui.PostgreSqlProvider.Tests/PostgresDataProviderTest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Xunit; - -namespace Serilog.Ui.PostgreSqlProvider.Tests -{ - public class PostgresDataProviderTest - { - [Fact] - public void Test1() - { - - } - } -} \ No newline at end of file diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/Serilog.Ui.PostgreSqlProvider.Tests.csproj b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Serilog.Ui.PostgreSqlProvider.Tests.csproj index 7bd8eb29..6e28cec6 100644 --- a/tests/Serilog.Ui.PostgreSqlProvider.Tests/Serilog.Ui.PostgreSqlProvider.Tests.csproj +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Serilog.Ui.PostgreSqlProvider.Tests.csproj @@ -1,29 +1,50 @@ - + net6.0 enable - + Postgres.Tests + Postgres.Tests false - - - - + + %(Filename)%(Extension) + PreserveNewest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + + + + + - diff --git a/tests/Serilog.Ui.PostgreSqlProvider.Tests/Util/PostgresTestProvider.cs b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Util/PostgresTestProvider.cs new file mode 100644 index 00000000..877e2f40 --- /dev/null +++ b/tests/Serilog.Ui.PostgreSqlProvider.Tests/Util/PostgresTestProvider.cs @@ -0,0 +1,70 @@ +using Ardalis.GuardClauses; +using Dapper; +//using +using Npgsql; +using Serilog.Ui.Common.Tests.DataSamples; +using Serilog.Ui.Common.Tests.SqlUtil; +using Serilog.Ui.Core; +using Serilog.Ui.PostgreSqlProvider; +using System.Linq; +using System.Threading.Tasks; +using Testcontainers.PostgreSql; +using Xunit; + +namespace Postgres.Tests.Util +{ + [CollectionDefinition(nameof(PostgresDataProvider))] + public class PostgresCollection : ICollectionFixture { } + public sealed class PostgresTestProvider : DatabaseInstance + { + protected override string Name => nameof(PostgreSqlContainer); + + public PostgresTestProvider() + { + Container = new PostgreSqlBuilder().Build(); + } + + public RelationalDbOptions DbOptions { get; set; } = new() + { + TableName = "logs", + Schema = "public" + }; + + protected override async Task CheckDbReadinessAsync() + { + DbOptions.ConnectionString = (Container as PostgreSqlContainer)?.GetConnectionString() ?? string.Empty; + + using var dataContext = new NpgsqlConnection(DbOptions.ConnectionString); + + await dataContext.ExecuteAsync("SELECT 1"); + } + + protected override async Task InitializeAdditionalAsync() + { + var logs = LogModelFaker.Logs(100); + + // manual conversion due to current implementation, based on a INT level column + var postgresTableLogs = logs.Select(p => new + { + p.RowNo, + Level = LogLevelConverter.GetLevelValue(p.Level), + p.Message, + p.Exception, + p.PropertyType, + p.Properties, + p.Timestamp, + }); + + Collector = new LogModelPropsCollector(logs); + + using var dataContext = new NpgsqlConnection(DbOptions.ConnectionString); + + await dataContext.ExecuteAsync(Costants.PostgresCreateTable); + + await dataContext.ExecuteAsync(Costants.PostgresInsertFakeData, postgresTableLogs); + + Provider = new PostgresDataProvider(DbOptions); + } + + } +} diff --git a/tests/Serilog.Ui.Web.Tests/Serilog.Ui.Web.Tests.csproj b/tests/Serilog.Ui.Web.Tests/Serilog.Ui.Web.Tests.csproj index a73ec95c..8b37a555 100644 --- a/tests/Serilog.Ui.Web.Tests/Serilog.Ui.Web.Tests.csproj +++ b/tests/Serilog.Ui.Web.Tests/Serilog.Ui.Web.Tests.csproj @@ -1,28 +1,42 @@ - + net6.0 enable - + Ui.Web.Tests + Ui.Web.Tests false - - - - + + %(Filename)%(Extension) + PreserveNewest + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + + - diff --git a/tests/Serilog.Ui.Web.Tests/SerilogUiMiddlewareTest.cs b/tests/Serilog.Ui.Web.Tests/SerilogUiMiddlewareTest.cs index 77bb7146..a08c0a4d 100644 --- a/tests/Serilog.Ui.Web.Tests/SerilogUiMiddlewareTest.cs +++ b/tests/Serilog.Ui.Web.Tests/SerilogUiMiddlewareTest.cs @@ -1,13 +1,14 @@ +using FluentAssertions; using Xunit; -namespace Serilog.Ui.Web.Tests +namespace Ui.Web.Tests { public class SerilogUiMiddlewareTest { - [Fact] - public void Test1() + [Fact(Skip = "Sample Test")] + public void To_be_implemented() { - + true.Should().BeTrue(); } } } \ No newline at end of file