using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace ScadaLink.SiteEventLogging.Tests; public class SiteEventLoggerTests : IDisposable { private readonly SiteEventLogger _logger; private readonly SqliteConnection _verifyConnection; private readonly string _dbPath; public SiteEventLoggerTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"test_events_{Guid.NewGuid()}.db"); var options = Options.Create(new SiteEventLogOptions { DatabasePath = _dbPath }); _logger = new SiteEventLogger(options, NullLogger.Instance); // Separate connection for verification queries _verifyConnection = new SqliteConnection($"Data Source={_dbPath}"); _verifyConnection.Open(); } public void Dispose() { _verifyConnection.Dispose(); _logger.Dispose(); if (File.Exists(_dbPath)) File.Delete(_dbPath); } [Fact] public async Task LogEventAsync_InsertsRecord() { await _logger.LogEventAsync("script", "Error", "inst-1", "ScriptActor:Monitor", "Script failed", "{\"stack\":\"...\"}"); using var cmd = _verifyConnection.CreateCommand(); cmd.CommandText = "SELECT COUNT(*) FROM site_events"; var count = (long)cmd.ExecuteScalar()!; Assert.Equal(1, count); } [Fact] public async Task LogEventAsync_StoresAllFields() { await _logger.LogEventAsync("alarm", "Warning", "inst-2", "AlarmActor:TempHigh", "Alarm triggered", "{\"value\":95}"); using var cmd = _verifyConnection.CreateCommand(); cmd.CommandText = "SELECT event_type, severity, instance_id, source, message, details FROM site_events LIMIT 1"; using var reader = cmd.ExecuteReader(); Assert.True(reader.Read()); Assert.Equal("alarm", reader.GetString(0)); Assert.Equal("Warning", reader.GetString(1)); Assert.Equal("inst-2", reader.GetString(2)); Assert.Equal("AlarmActor:TempHigh", reader.GetString(3)); Assert.Equal("Alarm triggered", reader.GetString(4)); Assert.Equal("{\"value\":95}", reader.GetString(5)); } [Fact] public async Task LogEventAsync_NullableFieldsAllowed() { await _logger.LogEventAsync("deployment", "Info", null, "DeploymentManager", "Deployed instance"); using var cmd = _verifyConnection.CreateCommand(); cmd.CommandText = "SELECT instance_id, details FROM site_events LIMIT 1"; using var reader = cmd.ExecuteReader(); Assert.True(reader.Read()); Assert.True(reader.IsDBNull(0)); Assert.True(reader.IsDBNull(1)); } [Fact] public async Task LogEventAsync_StoresIso8601UtcTimestamp() { await _logger.LogEventAsync("connection", "Info", null, "DCL", "Connected"); using var cmd = _verifyConnection.CreateCommand(); cmd.CommandText = "SELECT timestamp FROM site_events LIMIT 1"; var ts = (string)cmd.ExecuteScalar()!; var parsed = DateTimeOffset.Parse(ts); Assert.Equal(TimeSpan.Zero, parsed.Offset); } [Fact] public async Task LogEventAsync_ThrowsOnEmptyEventType() { await Assert.ThrowsAsync(() => _logger.LogEventAsync("", "Info", null, "Source", "Message")); } [Fact] public async Task LogEventAsync_ThrowsOnEmptySeverity() { await Assert.ThrowsAsync(() => _logger.LogEventAsync("script", "", null, "Source", "Message")); } [Fact] public async Task LogEventAsync_ThrowsOnEmptySource() { await Assert.ThrowsAsync(() => _logger.LogEventAsync("script", "Info", null, "", "Message")); } [Fact] public async Task LogEventAsync_ThrowsOnEmptyMessage() { await Assert.ThrowsAsync(() => _logger.LogEventAsync("script", "Info", null, "Source", "")); } [Fact] public async Task LogEventAsync_MultipleEvents_AutoIncrementIds() { await _logger.LogEventAsync("script", "Info", null, "S1", "First"); await _logger.LogEventAsync("script", "Info", null, "S2", "Second"); await _logger.LogEventAsync("script", "Info", null, "S3", "Third"); using var cmd = _verifyConnection.CreateCommand(); cmd.CommandText = "SELECT id FROM site_events ORDER BY id"; using var reader = cmd.ExecuteReader(); var ids = new List(); while (reader.Read()) ids.Add(reader.GetInt64(0)); Assert.Equal(3, ids.Count); Assert.True(ids[0] < ids[1] && ids[1] < ids[2]); } [Fact] public async Task AllEventTypes_Accepted() { var types = new[] { "script", "alarm", "deployment", "connection", "store_and_forward", "instance_lifecycle" }; foreach (var t in types) { await _logger.LogEventAsync(t, "Info", null, "Test", $"Event type: {t}"); } using var cmd = _verifyConnection.CreateCommand(); cmd.CommandText = "SELECT COUNT(DISTINCT event_type) FROM site_events"; var count = (long)cmd.ExecuteScalar()!; Assert.Equal(6, count); } }