test(core): add metrics history store and log parsing tests

This commit is contained in:
dualfroz committed 2026-08-28 10:02:18 +02:00
1 parent 680a7198bf
commit 1ed14c0a19
2 files changed
+186

No files matched your search

@@ -0,0 +1,99 @@
using Vantage.Core.History;
using Vantage.Core.Models;
using Xunit;
namespace Vantage.Core.Tests.History;
public class SqliteMetricsStoreTests
{
private static readonly DateTimeOffset Origin = new(2026, 8, 26, 12, 0, 0, TimeSpan.Zero);
[Fact]
public async Task RecordAndQuery_ReturnsPointsInTimeOrder()
{
await using var store = SqliteMetricsStore.InMemory();
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin, 10));
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin.AddSeconds(30), 30));
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin.AddSeconds(15), 20));
var points = await store.QueryAsync("h1", MetricNames.Cpu, Origin, Origin.AddMinutes(1));
Assert.Equal(new[] { 10.0, 20.0, 30.0 }, points.Select(p => p.Value));
Assert.True(points[0].Timestamp < points[1].Timestamp);
Assert.True(points[1].Timestamp < points[2].Timestamp);
}
[Fact]
public async Task Query_IsScopedToHostAndMetricAndWindow()
{
await using var store = SqliteMetricsStore.InMemory();
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin, 55));
await store.RecordAsync(new MetricSample("h2", MetricNames.Cpu, Origin, 99));
await store.RecordAsync(new MetricSample("h1", MetricNames.Memory, Origin, 42));
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin.AddHours(2), 77));
var points = await store.QueryAsync("h1", MetricNames.Cpu, Origin.AddMinutes(-1), Origin.AddMinutes(1));
Assert.Single(points);
Assert.Equal(55, points[0].Value);
}
[Fact]
public async Task RecordHostMetrics_ExpandsStandardSeries()
{
await using var store = SqliteMetricsStore.InMemory();
var metrics = new HostMetrics
{
CpuUsagePercent = 40,
MemoryUsedBytes = 3_000_000_000,
MemoryTotalBytes = 4_000_000_000,
DiskUsedBytes = 50,
DiskTotalBytes = 100,
Load1 = 1.2,
Load5 = 1.1,
Load15 = 0.9,
CollectedAt = Origin
};
await store.RecordHostMetricsAsync("h1", metrics);
var cpu = await store.QueryAsync("h1", MetricNames.Cpu, Origin.AddMinutes(-1), Origin.AddMinutes(1));
var disk = await store.QueryAsync("h1", MetricNames.Disk, Origin.AddMinutes(-1), Origin.AddMinutes(1));
Assert.Equal(40, cpu.Single().Value);
Assert.Equal(50, disk.Single().Value);
Assert.Equal(6, await store.CountAsync());
}
[Fact]
public async Task PruneOlderThan_DropsExpiredRowsOnly()
{
await using var store = SqliteMetricsStore.InMemory();
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin.AddHours(-10), 1));
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin.AddHours(-1), 2));
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin, 3));
var removed = await store.PruneOlderThanAsync(Origin.AddHours(-5));
Assert.Equal(1, removed);
Assert.Equal(2, await store.CountAsync());
var survivors = await store.QueryAsync("h1", MetricNames.Cpu, Origin.AddDays(-1), Origin.AddDays(1));
Assert.Equal(new[] { 2.0, 3.0 }, survivors.Select(p => p.Value));
}
[Fact]
public async Task PruneOlderThan_WithCutoffBeforeAllData_RemovesNothing()
{
await using var store = SqliteMetricsStore.InMemory();
await store.RecordAsync(new MetricSample("h1", MetricNames.Cpu, Origin, 3));
var removed = await store.PruneOlderThanAsync(Origin.AddDays(-1));
Assert.Equal(0, removed);
Assert.Equal(1, await store.CountAsync());
}
}
+87
View File
@@ -0,0 +1,87 @@
using Vantage.Core.Logs;
using Vantage.Core.Models;
using Vantage.Core.Tests.Fakes;
using Vantage.Core.Tests.Fixtures;
using Xunit;
namespace Vantage.Core.Tests.Logs;
public class LogTests
{
[Fact]
public void Command_ForJournalUnit_UsesJournalctl()
{
var command = LogCommand.Build(LogSource.Journal("nginx"), new LogQuery { Lines = 50 });
Assert.Contains("journalctl -u 'nginx' -n 50", command);
Assert.Contains("-o short-iso", command);
}
[Fact]
public void Command_ForFile_UsesTail()
{
var command = LogCommand.Build(LogSource.File("/var/log/syslog"), new LogQuery { Lines = 120 });
Assert.Contains("tail -n 120 -- '/var/log/syslog'", command);
}
[Fact]
public void Command_WithFilter_AppendsGrep()
{
var command = LogCommand.Build(LogSource.Journal("sshd"), new LogQuery { Lines = 10, Filter = "Accepted" });
Assert.Contains("| grep -F -- 'Accepted'", command);
}
[Fact]
public void Command_QuotesFilterToPreventInjection()
{
var command = LogCommand.Build(LogSource.Journal("sshd"), new LogQuery { Filter = "a'; rm -rf /" });
Assert.Contains("grep -F -- 'a'\\''; rm -rf /'", command);
}
[Fact]
public void Parser_ExtractsTimestampUnitAndMessage()
{
var lines = LogLineParser.Parse(CollectorFixtures.JournalOutput);
Assert.Equal(3, lines.Count);
Assert.Equal("nginx", lines[0].Unit);
Assert.Equal("GET /api/status 200 12ms", lines[0].Message);
Assert.NotNull(lines[0].Timestamp);
Assert.Equal(2026, lines[0].Timestamp!.Value.Year);
}
[Fact]
public void Parser_PlainLineWithoutTimestamp_KeepsRawAsMessage()
{
var lines = LogLineParser.Parse("just a plain log line without iso stamp\n");
var line = Assert.Single(lines);
Assert.Null(line.Timestamp);
Assert.Equal("just a plain log line without iso stamp", line.Message);
}
[Fact]
public async Task SshLogProvider_RunsBuiltCommand()
{
var runner = new FakeSshCommandRunner(CollectorFixtures.JournalOutput);
var provider = new SshLogProvider(runner);
var lines = await provider.ReadAsync(
new HostConfig(), LogSource.Journal("nginx"), new LogQuery { Lines = 3 }, CancellationToken.None);
Assert.Contains("journalctl", runner.Commands.Single());
Assert.Equal(3, lines.Count);
}
[Fact]
public async Task MockLogProvider_HonorsFilter()
{
var provider = new MockLogProvider(() => new DateTimeOffset(2026, 8, 26, 12, 0, 0, TimeSpan.Zero));
var host = new HostConfig { Name = "vps01" };
var filtered = await provider.ReadAsync(
host, LogSource.Journal("sshd"), new LogQuery { Lines = 40, Filter = "sshd" }, CancellationToken.None);
Assert.NotEmpty(filtered);
Assert.All(filtered, line => Assert.Contains("sshd", line.Raw));
}
}