From 7bcb7b0e665ec22c1ffebb9961ac72e81f497fcc Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Thu, 13 Feb 2025 00:40:42 +0100 Subject: [PATCH] Working on BackgroundService for Seq Logging --- Core/Configurations/IAppConfiguration.cs | 9 +++ .../SeqBackgroundServiceModule.cs | 20 +++++ Core/Telemetry/IMessageChannel.cs | 9 +++ Core/Telemetry/MessageChannel.cs | 22 ++++++ Core/Telemetry/SeqBackgroundService.cs | 60 ++++++++------- Core/Telemetry/{Class1.cs => SeqLogger.cs} | 0 .../SmartConfigProviderTests.cs | 27 ------- Tests/MessageChannelIntegrationTests.cs | 74 +++++++++++++++++++ Tests/PasswordHasherTest.cs | 72 ++++++++++++++++++ 9 files changed, 240 insertions(+), 53 deletions(-) create mode 100644 Core/Configurations/IAppConfiguration.cs create mode 100644 Core/ModuleRegistry/SeqBackgroundServiceModule.cs create mode 100644 Core/Telemetry/IMessageChannel.cs create mode 100644 Core/Telemetry/MessageChannel.cs rename Core/Telemetry/{Class1.cs => SeqLogger.cs} (100%) create mode 100644 Tests/MessageChannelIntegrationTests.cs create mode 100644 Tests/PasswordHasherTest.cs diff --git a/Core/Configurations/IAppConfiguration.cs b/Core/Configurations/IAppConfiguration.cs new file mode 100644 index 0000000..ab5826b --- /dev/null +++ b/Core/Configurations/IAppConfiguration.cs @@ -0,0 +1,9 @@ +namespace Core.Configurations +{ + /// + /// Marker interface for application configurations that should be automatically registered in the DI container. + /// Classes implementing this interface will be loaded from configuration and registered as singletons. + /// + public interface IAppConfiguration { } + +} diff --git a/Core/ModuleRegistry/SeqBackgroundServiceModule.cs b/Core/ModuleRegistry/SeqBackgroundServiceModule.cs new file mode 100644 index 0000000..fba38a4 --- /dev/null +++ b/Core/ModuleRegistry/SeqBackgroundServiceModule.cs @@ -0,0 +1,20 @@ +using Autofac; +using Core.Telemetry; + +namespace Core.ModuleRegistry +{ + public class SeqBackgroundServiceModule : Module + { + protected override void Load(ContainerBuilder builder) + { + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + } + } +} diff --git a/Core/Telemetry/IMessageChannel.cs b/Core/Telemetry/IMessageChannel.cs new file mode 100644 index 0000000..e048fbc --- /dev/null +++ b/Core/Telemetry/IMessageChannel.cs @@ -0,0 +1,9 @@ +using System.Threading.Channels; +namespace Core.Telemetry +{ + public interface IMessageChannel : IDisposable + { + ChannelWriter Writer { get; } + ChannelReader Reader { get; } + } +} diff --git a/Core/Telemetry/MessageChannel.cs b/Core/Telemetry/MessageChannel.cs new file mode 100644 index 0000000..c325d63 --- /dev/null +++ b/Core/Telemetry/MessageChannel.cs @@ -0,0 +1,22 @@ +using System.Threading.Channels; + +namespace Core.Telemetry +{ + public class MessageChannel : IMessageChannel + { + private readonly Channel _channel; + + public MessageChannel() + { + _channel = Channel.CreateUnbounded(); + } + + public ChannelWriter Writer => _channel.Writer; + public ChannelReader Reader => _channel.Reader; + + public void Dispose() + { + _channel.Writer.Complete(); + } + } +} diff --git a/Core/Telemetry/SeqBackgroundService.cs b/Core/Telemetry/SeqBackgroundService.cs index 91ddcc5..312a789 100644 --- a/Core/Telemetry/SeqBackgroundService.cs +++ b/Core/Telemetry/SeqBackgroundService.cs @@ -1,55 +1,63 @@ using Microsoft.ApplicationInsights; using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Channels; -using System.Threading.Tasks; namespace Core.Telemetry { public class SeqBackgroundService : BackgroundService { - private readonly Channel _channel; - private readonly HttpClient _client; - private readonly string _url; + private readonly IMessageChannel _messageChannel; private readonly TelemetryClient _telemetryClient; + private readonly HttpClient _httpClient; - public SeqBackgroundService(TelemetryClient telemetryClient, Configurations.IConfiguration configuration) + public SeqBackgroundService( + TelemetryClient telemetryClient, + IMessageChannel messageChannel, + HttpClient httpClient) { _telemetryClient = telemetryClient; - _url = configuration["Seq:Url"]; // eller hvor din Seq URL kommer fra - _client = new HttpClient(); - _channel = Channel.CreateUnbounded(); - } - - public void EnqueueMessage(string message) - { - _channel.Writer.TryWrite(message); + _messageChannel = messageChannel; + _httpClient = httpClient; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) + try { - try - { - await foreach (var message in _channel.Reader.ReadAllAsync(stoppingToken)) + while (!stoppingToken.IsCancellationRequested) + await foreach (var message in _messageChannel.Reader.ReadAllAsync(stoppingToken)) { - await _client.PostAsync(_url, new StringContent(message), stoppingToken); + + try + { + //using var response = await _httpClient.SendAsync(message, stoppingToken); + //if (!response.IsSuccessStatusCode) + //{ + // _telemetryClient.TrackTrace($"HTTP kald fejlede med status {response.StatusCode}", Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Warning); + // continue; + //} + } + catch (Exception ex) + { + _telemetryClient.TrackException(ex); + } } - } - catch (Exception ex) + } + catch (Exception ex) + { + if (ex is not OperationCanceledException) { _telemetryClient.TrackException(ex); + throw; } + + _telemetryClient.TrackTrace("Service shutdown pÃ¥begyndt"); } } public override async Task StopAsync(CancellationToken cancellationToken) { - _channel.Writer.Complete(); + + _messageChannel.Dispose(); await base.StopAsync(cancellationToken); } } diff --git a/Core/Telemetry/Class1.cs b/Core/Telemetry/SeqLogger.cs similarity index 100% rename from Core/Telemetry/Class1.cs rename to Core/Telemetry/SeqLogger.cs diff --git a/Tests/ConfigurationTests/SmartConfigProviderTests.cs b/Tests/ConfigurationTests/SmartConfigProviderTests.cs index 3bc95e8..bc24771 100644 --- a/Tests/ConfigurationTests/SmartConfigProviderTests.cs +++ b/Tests/ConfigurationTests/SmartConfigProviderTests.cs @@ -63,33 +63,6 @@ namespace Tests.ConfigurationTests } - [TestMethod] - public void GetPostgresSearchPath() - { - //ALTER USER sathumper SET search_path TO ptmain, public; - - var conn = Container.Resolve(); - - var result = conn.QuerySql("SHOW search_path;"); - using (var connw = new NpgsqlConnection(conn.ConnectionString + ";Password=3911")) - { - connw.Open(); - using (var cmd = new NpgsqlCommand("SHOW search_path; SELECT current_user;", connw)) - { - using (var reader = cmd.ExecuteReader()) - { - reader.Read(); - var r1 = $"Search path: {reader.GetString(0)}"; - reader.NextResult(); - reader.Read(); - var r2 = $"Current schema: {reader.GetString(0)}"; - } - } - - } - - - } [TestMethod] public void TryGetActiveConfigurations() { diff --git a/Tests/MessageChannelIntegrationTests.cs b/Tests/MessageChannelIntegrationTests.cs new file mode 100644 index 0000000..8a43d43 --- /dev/null +++ b/Tests/MessageChannelIntegrationTests.cs @@ -0,0 +1,74 @@ +using Autofac; +using System.Data; +using Insight.Database; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Logging; +using Core.Telemetry; +using Microsoft.ApplicationInsights; + +namespace Tests +{ + [TestClass] + public class MessageChannelIntegrationTests : TestFixture + { + private IMessageChannel _messageChannel; + private SeqBackgroundService _service; + private CancellationTokenSource _cts; + + [TestInitialize] + public void SetupThis() + { + _messageChannel = new MessageChannel(); + var telemetryClient = Container.Resolve(); + var httpClient = new HttpClient(new TestMessageHandler()); + _service = new SeqBackgroundService(telemetryClient, _messageChannel, httpClient); + _cts = new CancellationTokenSource(); + } + + [TestMethod] + public async Task Messages_ShouldBeProcessedFromQueue() + { + // Arrange + var processedMessages = new List(); + + // Start service + var serviceTask = _service.StartAsync(_cts.Token); + + // Act + // Send nogle beskeder til køen + for (int i = 0; i < 5; i++) + { + var message = new HttpRequestMessage(HttpMethod.Post, $"http://test.com/{i}"); + await _messageChannel.Writer.WriteAsync(message); + } + + // Vent lidt for at sikre processing + await Task.Delay(5000); + + // Stop servicen + _cts.Cancel(); + await _service.StopAsync(CancellationToken.None); + + // Assert + // Check at køen er tom + bool hasMoreMessages = await _messageChannel.Reader.WaitToReadAsync(); + Assert.IsFalse(hasMoreMessages, "Køen burde være tom"); + } + + private class TestMessageHandler : HttpMessageHandler + { + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)); + } + } + + [TestCleanup] + public void Cleanup() + { + _cts?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Tests/PasswordHasherTest.cs b/Tests/PasswordHasherTest.cs new file mode 100644 index 0000000..a635ac2 --- /dev/null +++ b/Tests/PasswordHasherTest.cs @@ -0,0 +1,72 @@ +using Autofac; +using System.Data; +using Insight.Database; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Logging; +using Core.Telemetry; +using Core.Entities.Users; + +namespace Tests +{ + [TestClass] + public class PasswordHasherTests : TestFixture + { + [TestMethod] + public void HashPassword_ShouldCreateValidHashFormat() + { + // Arrange + string password = "TestPassword123"; + + // Act + string hashedPassword = PasswordHasher.HashPassword(password); + string[] parts = hashedPassword.Split('.'); + + // Assert + Assert.AreEqual(3, parts.Length); + Assert.AreEqual("100000", parts[0]); + } + + [TestMethod] + public void VerifyPassword_WithCorrectPassword_ShouldReturnTrue() + { + // Arrange + string password = "TestPassword123"; + string hashedPassword = PasswordHasher.HashPassword(password); + + // Act + bool result = PasswordHasher.VerifyPassword(hashedPassword, password); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void VerifyPassword_WithWrongPassword_ShouldReturnFalse() + { + // Arrange + string correctPassword = "TestPassword123"; + string wrongPassword = "WrongPassword123"; + string hashedPassword = PasswordHasher.HashPassword(correctPassword); + + // Act + bool result = PasswordHasher.VerifyPassword(hashedPassword, wrongPassword); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void VerifyPassword_WithInvalidHashFormat_ShouldReturnFalse() + { + // Arrange + string password = "TestPassword123"; + string invalidHash = "InvalidHash"; + + // Act + bool result = PasswordHasher.VerifyPassword(invalidHash, password); + + // Assert + Assert.IsFalse(result); + } + } +} \ No newline at end of file