fix(site-event-log): queries on own read connection off the actor thread, bounded keyword window; cap trim counts/vacuums rarely (review 2026-10-21 02/P1, 02/P4)
This commit is contained in:
@@ -124,6 +124,61 @@ public class EventLogQueryServiceTests : IDisposable
|
||||
Assert.All(response.Entries, e => Assert.Equal("inst-1", e.InstanceId));
|
||||
}
|
||||
|
||||
private void InsertRawEvent(DateTimeOffset ts, string message) =>
|
||||
_eventLogger.WithConnection(c =>
|
||||
{
|
||||
using var cmd = c.CreateCommand();
|
||||
cmd.CommandText = @"INSERT INTO site_events (id, timestamp, event_type, severity, instance_id, source, message, details)
|
||||
VALUES ($id, $ts, 'script', 'Info', NULL, 'Src', $msg, NULL)";
|
||||
cmd.Parameters.AddWithValue("$id", Guid.NewGuid().ToString("N"));
|
||||
cmd.Parameters.AddWithValue("$ts", ts.ToUniversalTime().ToString("o"));
|
||||
cmd.Parameters.AddWithValue("$msg", message);
|
||||
cmd.ExecuteNonQuery();
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Review 2026-10-21 02/P1: a keyword search with no window is clamped to
|
||||
/// <c>KeywordSearchMaxWindow</c> (default 7 days) instead of full-scanning the log.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Query_KeywordSearch_WithoutWindow_IsClampedToMaxWindow()
|
||||
{
|
||||
InsertRawEvent(DateTimeOffset.UtcNow.AddDays(-30), "ancient needle");
|
||||
InsertRawEvent(DateTimeOffset.UtcNow.AddDays(-1), "recent needle");
|
||||
|
||||
var keyword = _queryService.ExecuteQuery(MakeRequest(keyword: "needle"));
|
||||
Assert.True(keyword.Success);
|
||||
Assert.Single(keyword.Entries);
|
||||
Assert.Equal("recent needle", keyword.Entries[0].Message);
|
||||
|
||||
// Non-keyword queries are unaffected.
|
||||
var all = _queryService.ExecuteQuery(MakeRequest());
|
||||
Assert.Equal(2, all.Entries.Count);
|
||||
}
|
||||
|
||||
/// <summary>Review 2026-10-21 02/P1: a query must complete while another thread holds the writer lock.</summary>
|
||||
[Fact]
|
||||
public async Task Query_DoesNotBlockOnTheWriteLock()
|
||||
{
|
||||
await SeedEvents();
|
||||
var held = new ManualResetEventSlim();
|
||||
var release = new ManualResetEventSlim();
|
||||
var holder = Task.Run(() => _eventLogger.WithConnection(_ =>
|
||||
{
|
||||
held.Set();
|
||||
release.Wait(TimeSpan.FromSeconds(30)); // blocks the holder thread only
|
||||
}));
|
||||
try
|
||||
{
|
||||
Assert.True(await Task.Run(() => held.Wait(TimeSpan.FromSeconds(5))));
|
||||
var q = Task.Run(() => _queryService.ExecuteQuery(MakeRequest()));
|
||||
var done = await Task.WhenAny(q, Task.Delay(TimeSpan.FromSeconds(10)));
|
||||
Assert.Same(q, done);
|
||||
Assert.Equal(5, (await q).Entries.Count);
|
||||
}
|
||||
finally { release.Set(); await holder; }
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a keyword search matches the event message.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user