using Fleet.Core; using Microsoft.Extensions.Time.Testing; using MoShiro.Backend.Analysis; using MoShiro.Backend.Application; using MoShiro.Backend.Storage.Sqlite; using MoShiro.Protocol; using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; public sealed class RowLoaderTests : IDisposable { private static readonly NodeId s_agent = new("agt_earliest"); private readonly string _directory = Path.Combine(Path.GetTempPath(), "moshiro-earliest-" + Guid.NewGuid().ToString("N")); private readonly SqliteDatabase _database; private readonly RawQueryGate _gate = new(); public RowLoaderTests() { _database = new SqliteDatabase(_directory); } [Theory] [InlineData(0L, 1_800_000L, 1_920_000L, 1_930_000L, 1_931_000L, 1_931_000L)] [InlineData(0L, 7_200_000L, null, null, 1_800_000L, 1_800_000L)] [InlineData(0L, 7_200_000L, 1_800_000L, null, null, 1_800_000L)] [InlineData(0L, 3_600_000L, 3_660_000L, 3_670_000L, null, 0L)] [InlineData(null, null, null, null, null, null)] [InlineData(null, null, null, null, 1_850_000L, 1_850_000L)] [InlineData(null, null, 1_800_000L, null, 1_801_000L, 1_801_000L)] [InlineData(null, null, 1_800_000L, null, 1_860_000L, 1_800_000L)] [InlineData(0L, null, 0L, null, null, 0L)] [InlineData(0L, null, 3_540_000L, null, null, 3_540_000L)] [InlineData(0L, null, 3_600_000L, null, null, 0L)] [InlineData(null, null, null, 1_930_000L, null, 1_930_000L)] public async Task EarliestSampleMs_RefinesWithinTheFirstBucket_WithoutDiscardingOlderHistory( long? hourStart, long? fiveMinuteStart, long? minuteStart, long? tenSecondStart, long? hotStart, long? expected) { var time = new FakeTimeProvider(DateTimeOffset.UnixEpoch.AddHours(1)); var store = new SqliteMetricStore(_database); var hot = new HotLayer(time); const string Series = "line"; foreach ((MetricLevel level, long? start) in new[] { (MetricLevel.H1, hourStart), (MetricLevel.M5, fiveMinuteStart), (MetricLevel.M1, minuteStart), (MetricLevel.S10, tenSecondStart), }) { if (start is { } at) { var row = new MetricRow(s_agent, Series, at, false, AccumulatorCodec.Encode(new MetricSet())); await store.UpsertAsync(level, [row], default); } } if (hotStart is { } sample) { hot.Merge(s_agent, Series, ChunkFactory.UdpEchoClean(s_agent, sample)); } var loader = new RowLoader(store, hot, new WindowComputer(new SqliteRawStore(_database)), _gate); Assert.Equal(expected, await loader.EarliestSampleMsAsync(s_agent, Series, time.GetUtcNow().ToUnixTimeMilliseconds(), default)); } public void Dispose() { _gate.Dispose(); _database.Dispose(); Directory.Delete(_directory, recursive: true); } }