Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions Lite.Tests/AlertHistorySourceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using DuckDB.NET.Data;
using PerformanceMonitorLite.Database;
using PerformanceMonitorLite.Services;
using PerformanceMonitorLite.Tests.Helpers;
using Xunit;

namespace PerformanceMonitorLite.Tests;

/// <summary>
/// Tests that the archive view source column correctly distinguishes live vs archived alerts,
/// and that dismiss operations only target live alerts.
/// </summary>
public class AlertHistorySourceTests : IDisposable
{
private readonly string _tempDir;
private readonly string _dbPath;
private readonly TestAlertDataHelper _helper;

public AlertHistorySourceTests()
{
_tempDir = Path.Combine(Path.GetTempPath(), "LiteTests_" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(_tempDir);
_dbPath = Path.Combine(_tempDir, "test.duckdb");
_helper = new TestAlertDataHelper(_dbPath);
}

public void Dispose()
{
try
{
if (Directory.Exists(_tempDir))
Directory.Delete(_tempDir, recursive: true);
}
catch
{
/* Best-effort cleanup */
}
}

private async Task<DuckDBConnection> InitializeDatabaseAsync()
{
var initializer = new DuckDbInitializer(_dbPath);
await initializer.InitializeAsync();

var connection = new DuckDBConnection($"Data Source={_dbPath}");
await connection.OpenAsync(TestContext.Current.CancellationToken);
return connection;
}

[Fact]
public async Task LiveAlerts_HaveSourceLive()
{
using var connection = await InitializeDatabaseAsync();

var recentTime = DateTime.UtcNow.AddHours(-1);
await _helper.InsertLiveAlertAsync(connection, recentTime, 1, "Server1", "High CPU");

await _helper.RefreshArchiveViewsAsync();

using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT source FROM v_config_alert_log WHERE metric_name = 'High CPU'";
var source = (string)(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken))!;

Assert.Equal("live", source);
}

[Fact]
public async Task ArchivedAlerts_HaveSourceArchive()
{
using var connection = await InitializeDatabaseAsync();

var oldAlerts = new List<TestAlertRecord>
{
TestAlertDataHelper.CreateAlert(
alertTime: DateTime.UtcNow.AddDays(-14),
metricName: "Blocking",
serverName: "Server1")
};

await _helper.CreateArchivedAlertsParquetAsync(connection, oldAlerts);
await _helper.RefreshArchiveViewsAsync();

using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT source FROM v_config_alert_log WHERE metric_name = 'Blocking'";
var source = (string)(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken))!;

Assert.Equal("archive", source);
}

[Fact]
public async Task MixedAlerts_CorrectSourcePerRow()
{
using var connection = await InitializeDatabaseAsync();

// Insert a live alert (recent)
await _helper.InsertLiveAlertAsync(
connection,
DateTime.UtcNow.AddHours(-2),
1, "Server1", "High CPU");

// Create an archived alert (old)
var archivedAlerts = new List<TestAlertRecord>
{
TestAlertDataHelper.CreateAlert(
alertTime: DateTime.UtcNow.AddDays(-14),
metricName: "Deadlock Detected",
serverName: "Server1")
};
await _helper.CreateArchivedAlertsParquetAsync(connection, archivedAlerts);
await _helper.RefreshArchiveViewsAsync();

// Query both and verify sources
using var cmd = connection.CreateCommand();
cmd.CommandText = @"
SELECT metric_name, source
FROM v_config_alert_log
ORDER BY alert_time DESC";

var results = new List<(string MetricName, string Source)>();
using var reader = await cmd.ExecuteReaderAsync(TestContext.Current.CancellationToken);
while (await reader.ReadAsync(TestContext.Current.CancellationToken))
{
results.Add((reader.GetString(0), reader.GetString(1)));
}

Assert.Equal(2, results.Count);
Assert.Contains(results, r => r.MetricName == "High CPU" && r.Source == "live");
Assert.Contains(results, r => r.MetricName == "Deadlock Detected" && r.Source == "archive");
}

[Fact]
public async Task DismissUpdate_OnlyAffectsLiveTable()
{
using var connection = await InitializeDatabaseAsync();

// Insert a live alert
var liveTime = DateTime.UtcNow.AddHours(-1);
await _helper.InsertLiveAlertAsync(
connection, liveTime, 1, "Server1", "High CPU");

// Create an archived alert
var archiveTime = DateTime.UtcNow.AddDays(-14);
var archivedAlerts = new List<TestAlertRecord>
{
TestAlertDataHelper.CreateAlert(
alertTime: archiveTime,
metricName: "Blocking",
serverName: "Server1")
};
await _helper.CreateArchivedAlertsParquetAsync(connection, archivedAlerts);
await _helper.RefreshArchiveViewsAsync();

// Dismiss the live alert
using var dismissCmd = connection.CreateCommand();
dismissCmd.CommandText = @"
UPDATE config_alert_log
SET dismissed = TRUE
WHERE metric_name = 'High CPU'
AND dismissed = FALSE";
var affected = await dismissCmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
Assert.Equal(1, affected);

// Attempt to dismiss the archived alert — should affect 0 rows
using var dismissArchiveCmd = connection.CreateCommand();
dismissArchiveCmd.CommandText = @"
UPDATE config_alert_log
SET dismissed = TRUE
WHERE metric_name = 'Blocking'
AND dismissed = FALSE";
var archivedAffected = await dismissArchiveCmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
Assert.Equal(0, archivedAffected);

// The archived alert should still be visible in the view (undismissed)
using var checkCmd = connection.CreateCommand();
checkCmd.CommandText = @"
SELECT COUNT(1)
FROM v_config_alert_log
WHERE metric_name = 'Blocking'
AND dismissed = FALSE";
var stillVisible = Convert.ToInt64(await checkCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
Assert.Equal(1, stillVisible);
}

[Fact]
public async Task ViewWithNoParquet_AllRowsAreLive()
{
using var connection = await InitializeDatabaseAsync();

// No parquet files — just live data
await _helper.InsertLiveAlertAsync(
connection, DateTime.UtcNow.AddHours(-1), 1, "Server1", "High CPU");
await _helper.InsertLiveAlertAsync(
connection, DateTime.UtcNow.AddHours(-2), 2, "Server2", "TempDB Space");

await _helper.RefreshArchiveViewsAsync();

using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE source = 'live'";
var liveCount = Convert.ToInt64(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
Assert.Equal(2, liveCount);

using var archiveCmd = connection.CreateCommand();
archiveCmd.CommandText = "SELECT COUNT(1) FROM v_config_alert_log WHERE source = 'archive'";
var archiveCount = Convert.ToInt64(await archiveCmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
Assert.Equal(0, archiveCount);
}

[Fact]
public async Task AlertHistoryRow_IsArchived_ReflectsSource()
{
var liveRow = new AlertHistoryRow { Source = "live" };
var archiveRow = new AlertHistoryRow { Source = "archive" };
var defaultRow = new AlertHistoryRow();

Assert.False(liveRow.IsArchived);
Assert.True(archiveRow.IsArchived);
Assert.False(defaultRow.IsArchived);

await Task.CompletedTask;
}

[Fact]
public async Task MultipleParquetFiles_AllMarkedAsArchive()
{
using var connection = await InitializeDatabaseAsync();

// Create two separate parquet files (simulating multiple archive cycles)
var batch1 = new List<TestAlertRecord>
{
TestAlertDataHelper.CreateAlert(
alertTime: DateTime.UtcNow.AddDays(-14),
metricName: "High CPU",
serverName: "Server1"),
TestAlertDataHelper.CreateAlert(
alertTime: DateTime.UtcNow.AddDays(-13),
metricName: "Blocking",
serverName: "Server1")
};

var batch2 = new List<TestAlertRecord>
{
TestAlertDataHelper.CreateAlert(
alertTime: DateTime.UtcNow.AddDays(-21),
metricName: "Deadlock Detected",
serverName: "Server2")
};

await _helper.CreateArchivedAlertsParquetAsync(
connection, batch1, "20260301_0000_config_alert_log.parquet");
await _helper.CreateArchivedAlertsParquetAsync(
connection, batch2, "20260215_0000_config_alert_log.parquet");

await _helper.RefreshArchiveViewsAsync();

using var cmd = connection.CreateCommand();
cmd.CommandText = @"
SELECT COUNT(1) FROM v_config_alert_log WHERE source = 'archive'";
var archiveCount = Convert.ToInt64(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken));
Assert.Equal(3, archiveCount);
}
}
Loading
Loading