WIP
This commit is contained in:
parent
54b057886c
commit
7fc1ae0650
204 changed files with 4345 additions and 134 deletions
42
PlanTempus.Tests/CodeSnippets/TestPostgresLISTENNOTIFY.cs
Normal file
42
PlanTempus.Tests/CodeSnippets/TestPostgresLISTENNOTIFY.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using Npgsql;
|
||||
|
||||
namespace PlanTempus.X.TDD.CodeSnippets;
|
||||
|
||||
internal class TestPostgresLISTENNOTIFY
|
||||
{
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
var connectionString = "Host=192.168.1.63;Database=ptdb01;Username=postgres;Password=3911";
|
||||
|
||||
try
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(connectionString);
|
||||
await conn.OpenAsync();
|
||||
|
||||
Console.WriteLine("Forbundet til databasen. Lytter efter notifikationer...");
|
||||
|
||||
conn.Notification += (o, e) =>
|
||||
{
|
||||
Console.WriteLine("Notifikation modtaget:");
|
||||
Console.WriteLine($" PID: {e.PID}");
|
||||
Console.WriteLine($" Kanal: {e.Channel}");
|
||||
Console.WriteLine($" Payload: {e.Payload}");
|
||||
Console.WriteLine("------------------------");
|
||||
};
|
||||
|
||||
await using (var cmd = new NpgsqlCommand("LISTEN config_changes;", conn))
|
||||
{
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
Console.WriteLine("Tryk på en tast for at stoppe...");
|
||||
|
||||
while (!Console.KeyAvailable) await conn.WaitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Der opstod en fejl: {ex.Message}");
|
||||
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
PlanTempus.Tests/CodeSnippets/sandbox.sql
Normal file
11
PlanTempus.Tests/CodeSnippets/sandbox.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
INSERT INTO "system".app_configuration ("key",value,"label",content_type,valid_from,expires_at,created_at,modified_at,etag) VALUES
|
||||
('Email:Templates:Welcome','{"subject":"Velkommen til vores platform","template":"welcome-dk.html","sender":"velkommen@firma.dk"}','test','application/json','2024-01-01 01:00:00+01',NULL,'2025-02-03 16:46:36.665888+01','2025-02-03 16:47:30.528326+01','c48949c4-c02f-4c77-b81c-e281a810def1'::uuid),
|
||||
('Email:Templates:Password','{"subject":"Nulstil dit kodeord","template":"reset-password-dk.html","sender":"support@firma.dk"}','Email Templates','application/json','2024-01-01 01:00:00+01',NULL,'2025-02-03 16:47:56.537775+01','2025-02-03 16:47:56.537775+01','26500738-4f5b-4cc8-a0e4-2a6a5fd57675'::uuid),
|
||||
('Debug','true',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','f1348731-9396-4f1d-b40a-7fbd23a897d2'::uuid),
|
||||
('Database:ConnectionString','"Server=db.example.com;Port=5432"',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','2aa0bc3e-fa24-449a-8f25-a76d9b4d535e'::uuid),
|
||||
('Database:Timeout','30',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','d25ebb14-49f6-4e33-9ac7-a3253705d0fb'::uuid),
|
||||
('Database:UseSSL','true',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','f4d52ec4-b723-4561-9b18-0e7a68b89a17'::uuid),
|
||||
('Logging:FileOptions','{"Path": "/var/logs/app.log", "MaxSizeMB": 100, "RetentionDays": 7}',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','06c0891d-a860-4acc-917a-d0877f511c1b'::uuid),
|
||||
('Features:Experimental','{"Enabled": true, "RolloutPercentage": 25, "AllowedUserGroups": ["beta"]}',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','0136fdef-51d9-4909-82ef-f72053ce6d6d'::uuid),
|
||||
('API:Endpoints','"/api/users"',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','fe362b69-a486-48ad-9165-2e623e2e6f70'::uuid),
|
||||
('API:Endpoints','"/api/products"',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','c087e2d4-1f38-4814-b4dd-f30c463dc6d1'::uuid);
|
||||
37
PlanTempus.Tests/CommandQueryHandlerTests/HandlerTest.cs
Normal file
37
PlanTempus.Tests/CommandQueryHandlerTests/HandlerTest.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Autofac;
|
||||
using Insight.Database;
|
||||
using PlanTempus.Components;
|
||||
using PlanTempus.Components.Accounts.Create;
|
||||
using PlanTempus.Core.CommandQueries;
|
||||
using PlanTempus.Core.Database;
|
||||
using PlanTempus.Core.Database.ConnectionFactory;
|
||||
using Shouldly;
|
||||
namespace PlanTempus.X.TDD.CommandQueryHandlerTests;
|
||||
|
||||
[TestClass]
|
||||
public class HandlerTest : TestFixture
|
||||
{
|
||||
[TestInitialize]
|
||||
public void This()
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ShouldResolveCommandHandlerAndDispatchToGenericCommandHandler()
|
||||
{
|
||||
var commandHandler = Container.Resolve<ICommandHandler>();
|
||||
commandHandler.ShouldBeOfType<CommandHandler>();
|
||||
|
||||
var command = new CreateAccountCommand
|
||||
{
|
||||
Email = $"{GetRandomWord()}@dumbanddumber.com5", // Lloyd Christmas
|
||||
Password = "1234AceVentura#LOL", // Ace Ventura
|
||||
IsActive = true,
|
||||
CorrelationId = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var result = await commandHandler.Handle(command);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
53
PlanTempus.Tests/CommandQueryHandlerTests/ResponseTests.cs
Normal file
53
PlanTempus.Tests/CommandQueryHandlerTests/ResponseTests.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Newtonsoft.Json;
|
||||
using PlanTempus.Core.CommandQueries;
|
||||
using Shouldly;
|
||||
|
||||
namespace PlanTempus.X.TDD.CommandQueryHandlerTests;
|
||||
|
||||
[TestClass]
|
||||
public class ProblemDetailsTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestFormatOfProblemDetails()
|
||||
{
|
||||
// Arrange
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Type = "https://example.com/errors/invalid-input",
|
||||
Title = "Invalid Input",
|
||||
Status = 400,
|
||||
Detail = "The request body is invalid.",
|
||||
Instance = "/api/users"
|
||||
};
|
||||
|
||||
problemDetails.AddExtension("invalidFields", new[]
|
||||
{
|
||||
new { Field = "name", Message = "The 'name' field is required." },
|
||||
new { Field = "email", Message = "The 'email' field must be a valid email address." }
|
||||
});
|
||||
|
||||
var json = JsonConvert.SerializeObject(problemDetails, Formatting.Indented);
|
||||
|
||||
var expectedJson = """
|
||||
{
|
||||
"Type": "https://example.com/errors/invalid-input",
|
||||
"Title": "Invalid Input",
|
||||
"Status": 400,
|
||||
"Detail": "The request body is invalid.",
|
||||
"Instance": "/api/users",
|
||||
"invalidFields": [
|
||||
{
|
||||
"Field": "name",
|
||||
"Message": "The 'name' field is required."
|
||||
},
|
||||
{
|
||||
"Field": "email",
|
||||
"Message": "The 'email' field must be a valid email address."
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
JsonConvert.DeserializeObject(json).ShouldBeSameAs(JsonConvert.DeserializeObject(expectedJson));
|
||||
}
|
||||
}
|
||||
188
PlanTempus.Tests/ConfigurationSystem/SetupConfigurationTests.cs
Normal file
188
PlanTempus.Tests/ConfigurationSystem/SetupConfigurationTests.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
using System.Data;
|
||||
using Autofac;
|
||||
using Insight.Database;
|
||||
using Newtonsoft.Json;
|
||||
using PlanTempus.Core.Database.ConnectionFactory;
|
||||
using Shouldly;
|
||||
|
||||
namespace PlanTempus.X.TDD.ConfigurationSystem;
|
||||
|
||||
[TestClass]
|
||||
public class SetupConfigurationTests : TestFixture
|
||||
{
|
||||
private IDbConnection _connection;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
var connectionFactory = Container.Resolve<IDbConnectionFactory>();
|
||||
_connection = connectionFactory.Create();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_connection.ExecuteSql(@"
|
||||
TRUNCATE TABLE app_configuration_history;
|
||||
TRUNCATE TABLE app_configuration CASCADE;");
|
||||
|
||||
_connection.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InsertConfiguration_ShouldCreateHistoryRecord()
|
||||
{
|
||||
// Arrange
|
||||
var configData = new
|
||||
{
|
||||
key = "test.key",
|
||||
value = "test value",
|
||||
label = "Test Label"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _connection.QuerySql<dynamic>(@"
|
||||
INSERT INTO app_configuration (key, value, label)
|
||||
VALUES (@key, @value, @label)
|
||||
RETURNING *", configData).Single();
|
||||
|
||||
var history = _connection.QuerySql<dynamic>(@"
|
||||
SELECT key, value, label, action_type
|
||||
FROM app_configuration_history
|
||||
WHERE id = @id AND action_type = 'I'",
|
||||
new { id = (int)result.id })
|
||||
.Single();
|
||||
|
||||
// Assert
|
||||
var expected = JsonConvert.SerializeObject(new
|
||||
{
|
||||
configData.key,
|
||||
configData.value,
|
||||
configData.label,
|
||||
action_type = "I"
|
||||
});
|
||||
var actual = JsonConvert.SerializeObject(history) as string;
|
||||
actual.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateConfiguration_ShouldUpdateModifiedAt()
|
||||
{
|
||||
// Arrange
|
||||
var configData = new
|
||||
{
|
||||
key = "test.key",
|
||||
value = "original value"
|
||||
};
|
||||
|
||||
var original = _connection.QuerySql<dynamic>(@"
|
||||
INSERT INTO app_configuration (key, value)
|
||||
VALUES (@key, @value)
|
||||
RETURNING modified_at", configData)
|
||||
.Single();
|
||||
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Act
|
||||
var updated = _connection.QuerySql<dynamic>(@"
|
||||
UPDATE app_configuration
|
||||
SET value = @value
|
||||
WHERE key = @key
|
||||
RETURNING modified_at",
|
||||
new { configData.key, value = "updated value" })
|
||||
.Single();
|
||||
|
||||
// Assert
|
||||
((DateTime)updated.modified_at).ShouldBeGreaterThan((DateTime)original.modified_at);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteConfiguration_ShouldCreateHistoryRecord()
|
||||
{
|
||||
// Arrange
|
||||
var configData = new
|
||||
{
|
||||
key = "test.key",
|
||||
value = "test value"
|
||||
};
|
||||
|
||||
var original = _connection.QuerySql<dynamic>(@"
|
||||
INSERT INTO app_configuration (key, value)
|
||||
VALUES (@key, @value)
|
||||
RETURNING id", configData)
|
||||
.Single();
|
||||
|
||||
// Act
|
||||
_connection.ExecuteSql(
|
||||
"DELETE FROM app_configuration WHERE id = @id",
|
||||
new { id = (int)original.id });
|
||||
|
||||
// Assert
|
||||
var history = _connection.QuerySql<dynamic>(@"
|
||||
SELECT key, value, action_type
|
||||
FROM app_configuration_history
|
||||
WHERE id = @id AND action_type = 'D'",
|
||||
new { id = (int)original.id })
|
||||
.Single();
|
||||
|
||||
var expected = JsonConvert.SerializeObject(new
|
||||
{
|
||||
configData.key,
|
||||
configData.value,
|
||||
action_type = "D"
|
||||
});
|
||||
var actual = JsonConvert.SerializeObject(history) as string;
|
||||
actual.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InsertConfiguration_ShouldSetAllColumns()
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTime.UtcNow;
|
||||
now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, DateTimeKind.Utc);
|
||||
var configData = new
|
||||
{
|
||||
key = "test.columns",
|
||||
value = "test value",
|
||||
label = "Test Label",
|
||||
content_type = "application/json",
|
||||
valid_from = now,
|
||||
expires_at = now.AddDays(30)
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _connection.QuerySql<dynamic>(@"
|
||||
INSERT INTO app_configuration (
|
||||
key,
|
||||
value,
|
||||
label,
|
||||
content_type,
|
||||
valid_from,
|
||||
expires_at)
|
||||
VALUES (
|
||||
@key,
|
||||
@value,
|
||||
@label,
|
||||
@content_type,
|
||||
@valid_from,
|
||||
@expires_at)
|
||||
RETURNING key, value, label, content_type,
|
||||
CAST(EXTRACT(EPOCH FROM date_trunc('minute', valid_from)) AS INTEGER) as valid_from,
|
||||
CAST(EXTRACT(EPOCH FROM date_trunc('minute', expires_at)) AS INTEGER) as expires_at", configData)
|
||||
.Single();
|
||||
|
||||
// Assert
|
||||
var expected = JsonConvert.SerializeObject(new
|
||||
{
|
||||
configData.key,
|
||||
configData.value,
|
||||
configData.label,
|
||||
configData.content_type,
|
||||
valid_from = ((DateTimeOffset)configData.valid_from).ToUnixTimeSeconds(),
|
||||
expires_at = ((DateTimeOffset)configData.expires_at).ToUnixTimeSeconds()
|
||||
});
|
||||
|
||||
Assert.AreEqual(expected, JsonConvert.SerializeObject(result));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using PlanTempus.Core.Configurations;
|
||||
using PlanTempus.Core.Configurations.JsonConfigProvider;
|
||||
using PlanTempus.Core.Configurations.SmartConfigProvider;
|
||||
using Shouldly;
|
||||
|
||||
namespace PlanTempus.X.TDD.ConfigurationTests;
|
||||
|
||||
[TestClass]
|
||||
public class JsonConfigurationProviderTests : TestFixture
|
||||
{
|
||||
private const string _testFolder = "ConfigurationTests/";
|
||||
|
||||
public JsonConfigurationProviderTests() : base(_testFolder)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void GetSection_ShouldReturnCorrectFeatureSection()
|
||||
{
|
||||
// Arrange
|
||||
var expectedJObject = JObject.Parse(@"{
|
||||
'Enabled': true,
|
||||
'RolloutPercentage': 25,
|
||||
'AllowedUserGroups': ['beta']
|
||||
}") as JToken;
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var section = builder.GetSection("Feature");
|
||||
|
||||
// Assert
|
||||
section.ShouldNotBeNull();
|
||||
section.Value.ShouldBeEquivalentTo(expectedJObject);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_ShouldReturnCorrectFeatureObject()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFeature = new Feature
|
||||
{
|
||||
Enabled = true,
|
||||
RolloutPercentage = 25,
|
||||
AllowedUserGroups = new List<string> { "beta" }
|
||||
};
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actualFeature = builder.GetSection("Feature").ToObject<Feature>();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var actualFeatureObsoleted = builder.GetSection("Feature").Get<Feature>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
// Assert
|
||||
actualFeature.ShouldBeEquivalentTo(expectedFeature);
|
||||
actualFeatureObsoleted.ShouldBeEquivalentTo(expectedFeature);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_ShouldReturnCorrectValueAsString()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFeature = "123";
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actualFeature = builder.GetSection("AnotherSetting").Get<string>("Thresholds:High");
|
||||
|
||||
// Assert
|
||||
actualFeature.ShouldBeEquivalentTo(expectedFeature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Testing a stupid indexer for compability with Microsoft ConfigurationBuilder
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Indexer_ShouldReturnValueAsString()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "SHA256";
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actual = builder["Authentication"];
|
||||
|
||||
// Assert
|
||||
actual.ShouldBeEquivalentTo(expected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_ShouldReturnCorrectValueAsInt()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFeature = 22;
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actualFeature = builder.GetSection("AnotherSetting:Temperature").Get<int>("Indoor:Max:Limit");
|
||||
|
||||
// Assert
|
||||
actualFeature.ShouldBe(expectedFeature);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_ShouldReturnCorrectValueAsNullBool()
|
||||
{
|
||||
// Arrange
|
||||
bool? expectedFeature = null;
|
||||
|
||||
var configRoot = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actualFeature = configRoot.Get<bool?>("MissingKey:WhatThen");
|
||||
|
||||
// Assert
|
||||
actualFeature.ShouldBe(expectedFeature);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Feature
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public int RolloutPercentage { get; set; }
|
||||
public List<string> AllowedUserGroups { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using PlanTempus.Core.Configurations.Common;
|
||||
|
||||
namespace PlanTempus.X.TDD.ConfigurationTests;
|
||||
|
||||
[TestClass]
|
||||
public class ConfigurationTests : TestFixture
|
||||
{
|
||||
[TestInitialize]
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void ConfigurationSettingsTest()
|
||||
{
|
||||
var pairs = new List<KeyValuePair<string, JToken>>
|
||||
{
|
||||
new("Debug", true),
|
||||
// Database konfiguration
|
||||
new("Database:ConnectionString", "Server=db.example.com;Port=5432"),
|
||||
new("Database:Timeout", 30),
|
||||
new("Database:UseSSL", true),
|
||||
|
||||
// Logging konfiguration med JObject
|
||||
new("Logging:FileOptions", JObject.Parse(@"{
|
||||
'Path': '/var/logs/app.log',
|
||||
'MaxSizeMB': 100,
|
||||
'RetentionDays': 7
|
||||
}")),
|
||||
|
||||
// Feature flags med kompleks konfiguration
|
||||
new("Features:Experimental", JObject.Parse(@"{
|
||||
'Enabled': true,
|
||||
'RolloutPercentage': 25,
|
||||
'AllowedUserGroups': ['beta']
|
||||
}")),
|
||||
|
||||
// API endpoints med array
|
||||
new("API:Endpoints", "/api/users"),
|
||||
new("API:Endpoints", "/api/products")
|
||||
};
|
||||
|
||||
var result = KeyValueToJson.Convert(pairs);
|
||||
|
||||
var expected = JObject.Parse(@"{
|
||||
'Debug' : true,
|
||||
'Database': {
|
||||
'ConnectionString': 'Server=db.example.com;Port=5432',
|
||||
'Timeout': 30,
|
||||
'UseSSL': true
|
||||
},
|
||||
'Logging': {
|
||||
'FileOptions': {
|
||||
'Path': '/var/logs/app.log',
|
||||
'MaxSizeMB': 100,
|
||||
'RetentionDays': 7
|
||||
}
|
||||
},
|
||||
'Features': {
|
||||
'Experimental': {
|
||||
'Enabled': true,
|
||||
'RolloutPercentage': 25,
|
||||
'AllowedUserGroups': ['beta']
|
||||
}
|
||||
},
|
||||
'API': {
|
||||
'Endpoints': ['/api/users', '/api/products']
|
||||
}
|
||||
}");
|
||||
|
||||
Assert.IsTrue(JToken.DeepEquals(expected, result));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
using Autofac;
|
||||
using Insight.Database;
|
||||
using PlanTempus.Core.Configurations;
|
||||
using PlanTempus.Core.Configurations.JsonConfigProvider;
|
||||
using PlanTempus.Core.Configurations.SmartConfigProvider;
|
||||
using PlanTempus.Core.Database.ConnectionFactory;
|
||||
using Shouldly;
|
||||
|
||||
namespace PlanTempus.X.TDD.ConfigurationTests;
|
||||
|
||||
[TestClass]
|
||||
public class SmartConfigProviderTests : TestFixture
|
||||
{
|
||||
private const string _testFolder = "ConfigurationTests/";
|
||||
|
||||
[TestMethod]
|
||||
public void TrySmartConfigWithOptionsForPostgres()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.AddSmartConfig(options => options.UsePostgres("DefaultConnection"))
|
||||
.Build();
|
||||
|
||||
var actualFeature = config.Get<bool>("Database:UseSSL");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_ShouldReturnCorrectValueAsBool()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFeature = true;
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.AddSmartConfig(options => options.UsePostgres("DefaultConnection"))
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actualFeature = config.Get<bool>("Database:UseSSL");
|
||||
|
||||
// Assert
|
||||
actualFeature.ShouldBe(expectedFeature);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Get_ShouldReturnCorrectValueWhenSelectingIntoValueRowInConfigTable()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFeature = 100;
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_testFolder}appconfiguration.dev.json")
|
||||
.AddSmartConfig(options => options.UsePostgres("DefaultConnection"))
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var actualFeature = builder.GetSection("Logging:FileOptions").Get<int>("MaxSizeMB");
|
||||
var withoutSectionThisAlsoWorks = builder.Get<int>("Logging:FileOptions:MaxSizeMB");
|
||||
|
||||
// Assert
|
||||
actualFeature.ShouldBe(expectedFeature);
|
||||
actualFeature.ShouldBe(withoutSectionThisAlsoWorks);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryGetActiveConfigurations()
|
||||
{
|
||||
var connFactory = Container.Resolve<IDbConnectionFactory>();
|
||||
|
||||
const string sql = @"
|
||||
SELECT id, ""key"", value, label, content_type,
|
||||
valid_from, expires_at, created_at, modified_at, etag
|
||||
FROM app_configuration
|
||||
WHERE CURRENT_TIMESTAMP BETWEEN valid_from AND expires_at
|
||||
OR (valid_from IS NULL AND expires_at IS NULL)";
|
||||
|
||||
using (var conn = connFactory.Create())
|
||||
{
|
||||
var result = conn.QuerySql(sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=192.168.1.63;Port=5432;Database=sandbox;User Id=sathumper;Password=3911;"
|
||||
},
|
||||
"ApplicationInsights": {
|
||||
"ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548",
|
||||
"UseSeqLoggingTelemetryChannel": true
|
||||
},
|
||||
"SeqConfiguration": {
|
||||
"IngestionEndpoint": "http://localhost:5341",
|
||||
"ApiKey": null,
|
||||
"Environment": "MSTEST"
|
||||
},
|
||||
"Authentication": "SHA256",
|
||||
"Feature": {
|
||||
"Enabled": true,
|
||||
"RolloutPercentage": 25,
|
||||
"AllowedUserGroups": [
|
||||
"beta"
|
||||
]
|
||||
},
|
||||
"AnotherSetting": {
|
||||
"Thresholds": {
|
||||
"High": "123",
|
||||
"Low": "-1"
|
||||
},
|
||||
"Temperature": {
|
||||
"Indoor": {
|
||||
"Max": {
|
||||
"Limit": 22
|
||||
},
|
||||
"Min": {
|
||||
"Limit": 18
|
||||
}
|
||||
},
|
||||
"Outdoor": {
|
||||
"Max": {
|
||||
"Limit": 12
|
||||
},
|
||||
"Min": {
|
||||
"Limit": 9
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Seq",
|
||||
"Args": {
|
||||
"serverUrl": "http://localhost:5341",
|
||||
"apiKey": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": [
|
||||
"WithMachineName",
|
||||
"WithThreadId",
|
||||
"WithProcessId",
|
||||
"WithEnvironmentName"
|
||||
],
|
||||
"Properties": {
|
||||
"Application": "PlanTempus"
|
||||
}
|
||||
}
|
||||
}
|
||||
71
PlanTempus.Tests/Logging/SeqBackgroundServiceTest.cs
Normal file
71
PlanTempus.Tests/Logging/SeqBackgroundServiceTest.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using System.Net;
|
||||
using Autofac;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.Channel;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
using PlanTempus.Core.SeqLogging;
|
||||
using PlanTempus.Core.Telemetry;
|
||||
|
||||
namespace PlanTempus.X.TDD.Logging;
|
||||
|
||||
[TestClass]
|
||||
public class SeqBackgroundServiceTest : TestFixture
|
||||
{
|
||||
private CancellationTokenSource _cts;
|
||||
private IMessageChannel<ITelemetry> _messageChannel;
|
||||
private SeqBackgroundService _service;
|
||||
|
||||
[TestInitialize]
|
||||
public void SetupThis()
|
||||
{
|
||||
_messageChannel = new MessageChannel();
|
||||
var telemetryClient = Container.Resolve<TelemetryClient>();
|
||||
|
||||
var config = new SeqConfiguration("http://localhost:5341", null, "MSTEST");
|
||||
|
||||
var httpClient = new SeqHttpClient(config);
|
||||
var logger = new SeqLogger<SeqBackgroundService>(httpClient, config);
|
||||
|
||||
_service = new SeqBackgroundService(telemetryClient, _messageChannel, logger);
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Messages_ShouldBeProcessedFromQueue()
|
||||
{
|
||||
await _service.StartAsync(_cts.Token);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var eventTelemetry = new EventTelemetry
|
||||
{
|
||||
Name = "Test Event",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
eventTelemetry.Properties.Add("TestId", Guid.NewGuid().ToString());
|
||||
eventTelemetry.Metrics.Add("TestMetric", 42.0);
|
||||
|
||||
await _messageChannel.Writer.WriteAsync(eventTelemetry);
|
||||
}
|
||||
|
||||
// wait for processing
|
||||
await Task.Delay(5000);
|
||||
|
||||
_cts.Cancel(); //not sure about this, we need to analyse more before this is "the way"
|
||||
await _service.StopAsync(CancellationToken.None);
|
||||
|
||||
|
||||
var hasMoreMessages = await _messageChannel.Reader.WaitToReadAsync();
|
||||
Assert.IsFalse(hasMoreMessages, "Queue should be empty after 5 seconds");
|
||||
}
|
||||
|
||||
private class TestMessageHandler : HttpMessageHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
|
||||
}
|
||||
}
|
||||
}
|
||||
145
PlanTempus.Tests/Logging/SeqLoggerTests.cs
Normal file
145
PlanTempus.Tests/Logging/SeqLoggerTests.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
using Autofac;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
using PlanTempus.Core.SeqLogging;
|
||||
|
||||
namespace PlanTempus.X.TDD.Logging;
|
||||
|
||||
[TestClass]
|
||||
public class SeqLoggerTests : TestFixture
|
||||
{
|
||||
private readonly string _testId;
|
||||
private readonly SeqHttpClient _httpClient;
|
||||
private readonly SeqLogger<SeqLoggerTests> _logger;
|
||||
|
||||
public SeqLoggerTests()
|
||||
{
|
||||
_testId = Guid.NewGuid().ToString();
|
||||
var config = new SeqConfiguration("http://localhost:5341", null, "MSTEST");
|
||||
_httpClient = new SeqHttpClient(config);
|
||||
_logger = new SeqLogger<SeqLoggerTests>(_httpClient, config);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LogTraceTelemetry_SendsCorrectDataWithErrorLevel()
|
||||
{
|
||||
// Arrange
|
||||
var traceTelemetry = new TraceTelemetry
|
||||
{
|
||||
Message = "Test trace error message",
|
||||
SeverityLevel = SeverityLevel.Error,
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
traceTelemetry.Properties.Add("TestId", _testId);
|
||||
|
||||
// Act
|
||||
await _logger.LogAsync(traceTelemetry);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LogTraceTelemetry_SendsCorrectDataWithWarningLevel()
|
||||
{
|
||||
// Arrange
|
||||
var traceTelemetry = new TraceTelemetry
|
||||
{
|
||||
Message = "Test trace warning message",
|
||||
SeverityLevel = SeverityLevel.Warning,
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
traceTelemetry.Properties.Add("TestId", _testId);
|
||||
|
||||
// Act
|
||||
await _logger.LogAsync(traceTelemetry);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LogEventTelemetry_SendsCorrectData()
|
||||
{
|
||||
// Arrange
|
||||
var eventTelemetry = new EventTelemetry
|
||||
{
|
||||
Name = "Test Event",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
eventTelemetry.Properties.Add("TestId", _testId);
|
||||
eventTelemetry.Metrics.Add("TestMetric", 42.0);
|
||||
|
||||
// Act
|
||||
await _logger.LogAsync(eventTelemetry);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LogExceptionTelemetry_SendsCorrectData()
|
||||
{
|
||||
try
|
||||
{
|
||||
var t = 0;
|
||||
var result = 10 / t;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Arrange
|
||||
var exceptionTelemetry = new ExceptionTelemetry(e)
|
||||
{
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
exceptionTelemetry.Properties.Add("TestId", _testId);
|
||||
|
||||
// Act
|
||||
await _logger.LogAsync(exceptionTelemetry);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LogDependencyTelemetry_SendsCorrectData()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyTelemetry = new DependencyTelemetry
|
||||
{
|
||||
Name = "SQL Query",
|
||||
Type = "SQL",
|
||||
Target = "TestDB",
|
||||
Success = true,
|
||||
Duration = TimeSpan.FromMilliseconds(100),
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
dependencyTelemetry.Properties.Add("TestId", _testId);
|
||||
|
||||
// Act
|
||||
await _logger.LogAsync(dependencyTelemetry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is for scope test in SeqLogger. It is not testing anything related to the TelemetryChannel which logs to Seq.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[TestMethod]
|
||||
public async Task LogRequestTelemetryInOperationHolderWithParentChild_SendsCorrectData()
|
||||
{
|
||||
var telemetryClient = Container.Resolve<TelemetryClient>();
|
||||
|
||||
using (var parent = telemetryClient.StartOperation<RequestTelemetry>("Parent First"))
|
||||
{
|
||||
parent.Telemetry.Duration = TimeSpan.FromMilliseconds(250);
|
||||
parent.Telemetry.Url = new Uri("http://parent.test.com/api/test");
|
||||
|
||||
using (var child = telemetryClient.StartOperation<RequestTelemetry>("Child 1"))
|
||||
{
|
||||
child.Telemetry.Success = true;
|
||||
child.Telemetry.ResponseCode = "200";
|
||||
child.Telemetry.Duration = TimeSpan.FromMilliseconds(50);
|
||||
child.Telemetry.Url = new Uri("http://child.test.com/api/test");
|
||||
child.Telemetry.Timestamp = DateTimeOffset.UtcNow;
|
||||
|
||||
child.Telemetry.Properties.Add("httpMethod", HttpMethod.Get.ToString());
|
||||
child.Telemetry.Properties.Add("TestId", _testId);
|
||||
|
||||
await _logger.LogAsync(child);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
await _logger.LogAsync(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
PlanTempus.Tests/Logging/SeqTelemetryChannelTest.cs
Normal file
60
PlanTempus.Tests/Logging/SeqTelemetryChannelTest.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using Autofac;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.Channel;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
using PlanTempus.Core.SeqLogging;
|
||||
using PlanTempus.Core.Telemetry;
|
||||
|
||||
namespace PlanTempus.X.TDD.Logging;
|
||||
|
||||
[TestClass]
|
||||
public class SeqTelemetryChannelTest : TestFixture
|
||||
{
|
||||
private CancellationTokenSource _cts;
|
||||
private IMessageChannel<ITelemetry> _messageChannel;
|
||||
private SeqBackgroundService _service;
|
||||
private TelemetryClient _telemetryClient;
|
||||
|
||||
[TestInitialize]
|
||||
public void SetupThis()
|
||||
{
|
||||
//it is important to use the same MessageChannel as the BackgroundService uses
|
||||
//we know that IMessageChannel<ITelemetry> _messageChannel; is registered via Autofac and manually injected into SeqBackgroundService
|
||||
//so we can get it by calling the Autofac Container in this test.
|
||||
|
||||
_messageChannel = Container.Resolve<IMessageChannel<ITelemetry>>();
|
||||
_service = Container.Resolve<SeqBackgroundService>();
|
||||
_telemetryClient = Container.Resolve<TelemetryClient>();
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Messages_ShouldBeProcessedFromQueue()
|
||||
{
|
||||
await _service.StartAsync(_cts.Token);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var eventTelemetry = new EventTelemetry
|
||||
{
|
||||
Name = "Test Event 3",
|
||||
Timestamp = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
eventTelemetry.Properties.Add("TestId", Guid.NewGuid().ToString());
|
||||
eventTelemetry.Metrics.Add("TestMetric", 42.0);
|
||||
|
||||
//we don't write to the _messageChannel.Writer.WriteAsync(eventTelemetry);, but the TelemetryClient which is configured to use SeqTelemetryChannel
|
||||
_telemetryClient.TrackEvent(eventTelemetry);
|
||||
}
|
||||
|
||||
// wait for processing
|
||||
await Task.Delay(5000);
|
||||
|
||||
await _service.StopAsync(CancellationToken.None);
|
||||
|
||||
var hasMoreMessages = await _messageChannel.Reader.WaitToReadAsync();
|
||||
Assert.IsFalse(hasMoreMessages, "Queue should be empty after 5 seconds");
|
||||
}
|
||||
}
|
||||
87
PlanTempus.Tests/PasswordHasherTest.cs
Normal file
87
PlanTempus.Tests/PasswordHasherTest.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using PlanTempus.Core;
|
||||
using Sodium;
|
||||
|
||||
namespace PlanTempus.X.TDD;
|
||||
|
||||
[TestClass]
|
||||
public class PasswordHasherTests : TestFixture
|
||||
{
|
||||
[TestMethod]
|
||||
public void MyTestMethod()
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
var salt = PasswordHash.ScryptGenerateSalt();
|
||||
|
||||
// 2. Konverter password til byte[]
|
||||
var passwordBytes = Encoding.UTF8.GetBytes("password123");
|
||||
|
||||
// 3. Kald ScryptHashBinary korrekt
|
||||
var hash = PasswordHash.ScryptHashBinary(
|
||||
passwordBytes,
|
||||
salt
|
||||
);
|
||||
|
||||
stopwatch.Stop();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HashPassword_ShouldCreateValidHashFormat()
|
||||
{
|
||||
// Arrange
|
||||
var password = "TestPassword123";
|
||||
|
||||
// Act
|
||||
var hashedPassword = new SecureTokenizer().TokenizeText(password);
|
||||
var parts = hashedPassword.Split('.');
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(3, parts.Length);
|
||||
Assert.AreEqual("100000", parts[0]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VerifyPassword_WithCorrectPassword_ShouldReturnTrue()
|
||||
{
|
||||
// Arrange
|
||||
var password = "TestPassword123";
|
||||
var hashedPassword = new SecureTokenizer().TokenizeText(password);
|
||||
|
||||
// Act
|
||||
var result = new SecureTokenizer().VerifyToken(hashedPassword, password);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VerifyPassword_WithWrongPassword_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var correctPassword = "TestPassword123";
|
||||
var wrongPassword = "WrongPassword123";
|
||||
var hashedPassword = new SecureTokenizer().TokenizeText(correctPassword);
|
||||
|
||||
// Act
|
||||
var result = new SecureTokenizer().VerifyToken(hashedPassword, wrongPassword);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VerifyPassword_WithInvalidHashFormat_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var password = "TestPassword123";
|
||||
var invalidHash = "InvalidHash";
|
||||
|
||||
// Act
|
||||
var result = new SecureTokenizer().VerifyToken(invalidHash, password);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
}
|
||||
43
PlanTempus.Tests/PlanTempus.X.TDD.csproj
Normal file
43
PlanTempus.Tests/PlanTempus.X.TDD.csproj
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="moq" Version="4.20.72" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="CrypticWizard.RandomWordGenerator" Version="0.9.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PlanTempus.Application\PlanTempus.Application.csproj" />
|
||||
<ProjectReference Include="..\PlanTempus.Components\PlanTempus.Components.csproj" />
|
||||
<ProjectReference Include="..\PlanTempus.Database\PlanTempus.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appconfiguration.dev.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ConfigurationTests\appconfiguration.dev.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ConfigurationTests\appconfiguration.dev.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
130
PlanTempus.Tests/PostgresTests.cs
Normal file
130
PlanTempus.Tests/PostgresTests.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
using Autofac;
|
||||
using Insight.Database;
|
||||
using PlanTempus.Components.Accounts.Exceptions;
|
||||
using PlanTempus.Core.Database;
|
||||
using PlanTempus.Core.Database.ConnectionFactory;
|
||||
using Shouldly;
|
||||
|
||||
namespace PlanTempus.X.TDD;
|
||||
|
||||
[TestClass]
|
||||
public class PostgresTests : TestFixture
|
||||
{
|
||||
private IDbConnectionFactory _connFactory;
|
||||
private IDatabaseOperations _databaseOperations;
|
||||
|
||||
[TestInitialize]
|
||||
public void MyTestMethod()
|
||||
{
|
||||
_connFactory = Container.Resolve<IDbConnectionFactory>();
|
||||
_databaseOperations = Container.Resolve<IDatabaseOperations>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestDefaultConnection()
|
||||
{
|
||||
//https://stackoverflow.com/questions/69169247/how-to-create-idbconnection-factory-using-autofac-for-dapper
|
||||
|
||||
using (var conn = _connFactory.Create())
|
||||
{
|
||||
conn.ExecuteSql("SELECT 1 as p");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestScopeConnectionWithLogging()
|
||||
{
|
||||
using var db = _databaseOperations.CreateScope(nameof(TestScopeConnectionWithLogging));
|
||||
try
|
||||
{
|
||||
var user = await db.Connection.QuerySqlAsync<string>(
|
||||
"SELECT tablename FROM pg_tables limit 5");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.Error(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestScopeConnectionWithErrorLogging()
|
||||
{
|
||||
using var db = _databaseOperations.CreateScope(nameof(TestScopeConnectionWithLogging));
|
||||
try
|
||||
{
|
||||
var user = await db.Connection.QuerySqlAsync<string>(
|
||||
"SELECT tablename FROM pg_tables limit 5");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestForUniqueAccountEmail()
|
||||
{
|
||||
using var db = _databaseOperations.CreateScope(nameof(TestForUniqueAccountEmail));
|
||||
try
|
||||
{
|
||||
var sql = @"INSERT INTO system.accounts(email, password_hash, security_stamp, email_confirmed, access_failed_count, lockout_enabled, is_active)
|
||||
VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @AccessFailedCount, @LockoutEnabled, @IsActive)
|
||||
RETURNING id, created_at, email, is_active";
|
||||
|
||||
var parameters = new
|
||||
{
|
||||
Email = $"{GetRandomWord()}@mars.com",
|
||||
PasswordHash = "MartianRover2025",
|
||||
SecurityStamp = "MarsOrBust",
|
||||
EmailConfirmed = true,
|
||||
AccessFailedCount = 0,
|
||||
LockoutEnabled = false,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
var account = await db.Connection.QuerySqlAsync<dynamic>(sql, parameters);
|
||||
|
||||
//EmailAlreadyRegistreredException
|
||||
//try insert, to test exception
|
||||
var ex = await Should.ThrowAsync<Npgsql.PostgresException>(async () =>
|
||||
await db.Connection.QuerySqlAsync<dynamic>(sql, parameters));
|
||||
ex.ConstraintName.ShouldBe("accounts_email_key");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.Error(ex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestSimpleDatabaseOperation()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _databaseOperations.ExecuteAsync(async connection =>
|
||||
{
|
||||
return await connection.QuerySqlAsync<string>(
|
||||
"SELECT tablename FROM pg_tables limit 5");
|
||||
}, nameof(TestSimpleDatabaseOperation));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetupPostgresql_LISTEN()
|
||||
{
|
||||
//this is not in use a the moment... kind of premature test to get my mind in to gear
|
||||
//var builder = new ConfigurationBuilder()
|
||||
// .AddPostgresConfiguration(options =>
|
||||
// {
|
||||
// options.ConnectionString = "Host=192.168.1.57;Database=ptdb01;Username=postgres;Password=3911";
|
||||
// options.Channel = "config_changes";
|
||||
// options.ConfigurationQuery = @"select * from dev.app_configuration";
|
||||
// });
|
||||
}
|
||||
}
|
||||
1
PlanTempus.Tests/SecureConnectionStringTests.cs
Normal file
1
PlanTempus.Tests/SecureConnectionStringTests.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
namespace PlanTempus.X.TDD;
|
||||
94
PlanTempus.Tests/TestFixture.cs
Normal file
94
PlanTempus.Tests/TestFixture.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using System.Diagnostics;
|
||||
using Autofac;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PlanTempus.Components.ModuleRegistry;
|
||||
using PlanTempus.Core.Configurations;
|
||||
using PlanTempus.Core.Configurations.JsonConfigProvider;
|
||||
using PlanTempus.Core.ModuleRegistry;
|
||||
using PlanTempus.Core.SeqLogging;
|
||||
using PlanTempus.Database.ModuleRegistry;
|
||||
using CrypticWizard.RandomWordGenerator;
|
||||
|
||||
namespace PlanTempus.X.TDD;
|
||||
|
||||
/// <summary>
|
||||
/// Act as base class for tests. Avoids duplication of test setup code
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public abstract class TestFixture
|
||||
{
|
||||
private readonly string _configurationFilePath;
|
||||
|
||||
protected TestFixture() : this(null)
|
||||
{
|
||||
}
|
||||
public string GetRandomWord()
|
||||
{
|
||||
var myWordGenerator = new WordGenerator();
|
||||
return myWordGenerator.GetWord(WordGenerator.PartOfSpeech.verb);
|
||||
}
|
||||
public TestFixture(string configurationFilePath)
|
||||
{
|
||||
if (configurationFilePath is not null)
|
||||
_configurationFilePath = configurationFilePath?.TrimEnd('/') + "/";
|
||||
|
||||
CreateContainerBuilder();
|
||||
Container = ContainerBuilder.Build();
|
||||
}
|
||||
|
||||
protected IContainer Container { get; private set; }
|
||||
protected ContainerBuilder ContainerBuilder { get; private set; }
|
||||
|
||||
public virtual IConfigurationRoot Configuration()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile($"{_configurationFilePath}appconfiguration.dev.json")
|
||||
.Build();
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
protected virtual void CreateContainerBuilder()
|
||||
{
|
||||
var configuration = Configuration();
|
||||
var builder = new ContainerBuilder();
|
||||
|
||||
builder.RegisterGeneric(typeof(Logger<>))
|
||||
.As(typeof(ILogger<>))
|
||||
.SingleInstance();
|
||||
|
||||
|
||||
builder.RegisterModule(new DbPostgreSqlModule
|
||||
{
|
||||
ConnectionString = configuration.GetConnectionString("DefaultConnection")
|
||||
});
|
||||
|
||||
builder.RegisterModule(new TelemetryModule
|
||||
{
|
||||
TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject<TelemetryConfig>()
|
||||
});
|
||||
builder.RegisterModule(new SeqLoggingModule
|
||||
{
|
||||
SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject<SeqConfiguration>()
|
||||
});
|
||||
|
||||
builder.RegisterModule<CommandModule>();
|
||||
builder.RegisterModule<SecurityModule>();
|
||||
|
||||
ContainerBuilder = builder;
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanUp()
|
||||
{
|
||||
Trace.Flush();
|
||||
var telemetryClient = Container.Resolve<TelemetryClient>();
|
||||
telemetryClient.Flush();
|
||||
|
||||
if (Container is null) return;
|
||||
|
||||
Container.Dispose();
|
||||
Container = null;
|
||||
}
|
||||
}
|
||||
14
PlanTempus.Tests/appconfiguration.dev.json
Normal file
14
PlanTempus.Tests/appconfiguration.dev.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=192.168.1.63;Port=5432;Database=ptmain;User Id=sathumper;Password=3911;"
|
||||
},
|
||||
"ApplicationInsights": {
|
||||
"ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548",
|
||||
"UseSeqLoggingTelemetryChannel": true
|
||||
},
|
||||
"SeqConfiguration": {
|
||||
"IngestionEndpoint": "http://localhost:5341",
|
||||
"ApiKey": null,
|
||||
"Environment": "MSTEST"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue