mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-26 05:29:43 +00:00
eee65ba000
The three metrics MCP tools previously ignored the time-window: get_metrics never read the days query param (returned all-time totals), get_usage_metrics didn't expose days at all and the service was hardcoded to 90, get_platform_metrics had a fixed 500-most-recent-sessions cap. A "last 7 days" question therefore returned cross-tenant all-time aggregates (~1300) instead of the actual 7-day count (~500). End-to-end fix: - Plumbing days through MetricsSummaryFunction → IMetricsRepository → TableStorageService.GetMetricsSummaryAsync, with a server-side RowKey cutoff on SessionsIndex (reuses the inverted-tick D19 prefix pattern; extracted as ComputeCutoffRowKeyPrefix and the two pre-existing call sites consolidated). - UsageMetricsService and PlatformMetricsService now accept int days, with per-window in-memory caches and a WindowDays field echoed on the response. - MCP zod schemas accept any integer 1-365 (replacing the [7,30,90] whitelist), so callers can now ask for 5 or 12 days and get the right answer. - Function-layer defaults stay at 90 for /usage and /platform routes (no dashboard regression) and 30 for /summary (Agent API, MCP-only path). Tests: 22 new xUnit cases across MetricsCutoffRowKeyTests, UsageMetricsServiceWindowTests, PlatformMetricsServiceWindowTests covering boundary, clamping, and end-to-end window semantics. Backend 808/808 green, web vitest 208/208 green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
58 lines
2.4 KiB
C#
58 lines
2.4 KiB
C#
using AutopilotMonitor.Functions.Services;
|
|
|
|
namespace AutopilotMonitor.Functions.Tests;
|
|
|
|
/// <summary>
|
|
/// Verifies ComputeCutoffRowKeyPrefix produces a SessionsIndex RowKey upper bound that
|
|
/// lexicographically separates sessions newer than `now - days` (kept) from older ones (excluded).
|
|
/// </summary>
|
|
public class MetricsCutoffRowKeyTests
|
|
{
|
|
private static string IndexRowKey(DateTime startedAt, string sessionId)
|
|
=> $"{(DateTime.MaxValue.Ticks - startedAt.Ticks):D19}_{sessionId}";
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(5)]
|
|
[InlineData(7)]
|
|
[InlineData(12)]
|
|
[InlineData(30)]
|
|
[InlineData(90)]
|
|
[InlineData(365)]
|
|
public void Cutoff_keeps_sessions_inside_window_excludes_outside(int days)
|
|
{
|
|
var cutoffPrefix = TableStorageService.ComputeCutoffRowKeyPrefix(days);
|
|
|
|
// Inside the window: 1 hour newer than the cutoff.
|
|
var insideRowKey = IndexRowKey(DateTime.UtcNow.AddDays(-days).AddHours(1), Guid.NewGuid().ToString());
|
|
// Outside the window: 1 hour older than the cutoff.
|
|
var outsideRowKey = IndexRowKey(DateTime.UtcNow.AddDays(-days).AddHours(-1), Guid.NewGuid().ToString());
|
|
|
|
// RowKey lt cutoffPrefix → include
|
|
Assert.True(string.CompareOrdinal(insideRowKey, cutoffPrefix) < 0,
|
|
$"days={days}: session inside window should be < cutoff (inside={insideRowKey}, cutoff={cutoffPrefix})");
|
|
Assert.False(string.CompareOrdinal(outsideRowKey, cutoffPrefix) < 0,
|
|
$"days={days}: session outside window should NOT be < cutoff (outside={outsideRowKey}, cutoff={cutoffPrefix})");
|
|
}
|
|
|
|
[Fact]
|
|
public void Cutoff_prefix_is_19_digits_padded()
|
|
{
|
|
var prefix = TableStorageService.ComputeCutoffRowKeyPrefix(30);
|
|
Assert.Equal(19, prefix.Length);
|
|
Assert.True(prefix.All(char.IsDigit), $"Prefix must be all digits, got: {prefix}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Larger_window_produces_lexicographically_larger_prefix()
|
|
{
|
|
// Older cutoff → larger inverted ticks → larger prefix string.
|
|
var prefix7 = TableStorageService.ComputeCutoffRowKeyPrefix(7);
|
|
var prefix30 = TableStorageService.ComputeCutoffRowKeyPrefix(30);
|
|
var prefix90 = TableStorageService.ComputeCutoffRowKeyPrefix(90);
|
|
|
|
Assert.True(string.CompareOrdinal(prefix7, prefix30) < 0);
|
|
Assert.True(string.CompareOrdinal(prefix30, prefix90) < 0);
|
|
}
|
|
}
|