* test(diagnostics): add InMemory/Sqlite StructuredLog store conformance Add a shared IStructuredLogStore matrix that locks Take clamp, SourceId tie-break order, ListSources registry/heartbeat, DroppedEvents vs storage diagnostics, and portable exact-case filters after #8147/#8148. Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com> * test(diagnostics): lock Sequence and Id query tie-breaks Cover the later IStructuredLogStore order stages independently so a SourceId-only assertion cannot hide Sequence or Id regressions. Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com> * test(diagnostics): isolate Sequence tie-break from Id order Use IDs that sort opposite Sequence so a skip-Sequence, order-by-Id implementation cannot pass the shared matrix. Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|---|---|---|
| .. | ||
| Elsa.Diagnostics.StructuredLogs.Persistence.ConformanceTests.csproj | ||
| README.md | ||
| StructuredLogStoreConformanceTests.cs | ||
| StructuredLogStoreScenario.cs | ||
StructuredLogs persistence conformance suite
Shared InMemory / Sqlite assertions for IStructuredLogStore. The matrix locks
the contracts already aligned on main after #8147 (Take clamp) and #8148
(SourceId tie-break + registry-backed ListSources).
Shared contracts
| Surface | Locked behavior |
|---|---|
Null Take / max clamp |
StructuredLogsOptions.MaxRecentLogQuerySize; negative ceiling or Take yields empty |
| Timestamp / ReceivedAt ties | SourceId, then Sequence, then Id (each stage asserted with preceding fields tied; ascending after Relational DESC+Reverse) |
ListSources |
In-process IStructuredLogSourceRegistry metadata and SourceHeartbeatTimeout → Stale |
QueryAsync.DroppedEvents when writes fit |
0 |
| Portable filters | Exact-case equality, category prefix, text substring, level, and timestamp range applied before Take |
Store-specific contracts (intentionally not unified)
QueryAsync.DroppedEvents after ring overflow. InMemory reports
RingBuffer.DroppedCount. Relational QueryAsync stays 0; durable write-queue
drops are IStructuredLogStorageDiagnostics.DroppedWriteCount on /storage.
The matrix asserts both sides of that split so neither path can silently adopt
the other.
Filter equality case folding. InMemory uses OrdinalIgnoreCase. Relational
SQL uses = (collation-dependent; Sqlite default is BINARY). Category LIKE
and text LIKE are ASCII case-insensitive on Sqlite, which happens to match
InMemory for those predicates only. Tests seed exact-case filter values.
Case-differing SourceId / TenantId / id equality is not portable across
providers — recreate filters after a store change rather than relying on
case-insensitive =. Same spirit as User Tasks title-cursor collation notes.
SourceId sort case folding. InMemory ThenBy uses OrdinalIgnoreCase.
Relational ORDER BY SourceId follows the column collation. Tie-break tests
use lowercase source IDs (pod-a, pod-b) so both providers agree.