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