using Microsoft.Data.Sqlite; namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging.Tests; /// /// Pins the site_events schema to the shape the storage-cap size model was calibrated /// against (register row 93, docs/plans/2026-08-17-site-event-log-cap-semantics-decision.md §4). /// /// /// hardcodes this schema twice over: /// the sampling SQL sums octet_length over the exact column list, and /// IndexedColumnCount = 4 plus the fill-factor/overhead constants are empirical for /// exactly these four secondary indexes. A schema migration that adds a column or an index /// invalidates both SILENTLY — the new column's bytes are never sampled, a fifth index is /// never charged — so this test turns that drift into a loud failure. The fix on failure is /// NEVER to just update the assertions: re-run the memo §4 calibration, update the /// constants and sampling SQL together, and only then /// re-pin the schema here. /// public class SiteEventsSchemaCalibrationTests { private const string RecalibrationNotice = "site_events schema drifted from the size-model calibration. Re-run the calibration in " + "docs/plans/2026-08-17-site-event-log-cap-semantics-decision.md §4 and update " + "EventLogPurgeService's constants + sampling SQL together with this pin (register row 93)."; private static SqliteConnection OpenWithSchema() { var connection = new SqliteConnection("Data Source=:memory:"); connection.Open(); SiteEventLogSchema.Apply(connection); return connection; } private static List ReadStrings(SqliteConnection connection, string sql) { using var cmd = connection.CreateCommand(); cmd.CommandText = sql; var values = new List(); using var reader = cmd.ExecuteReader(); while (reader.Read()) values.Add(reader.GetString(0)); return values; } /// Verifies that the modelled columns exactly match the sampled octet-length sum. [Fact] public void Columns_MatchTheSampledOctetLengthSum_Exactly() { // The sampler's payload_bytes expression sums octet_length over exactly these eight // columns, in this order. A ninth column would contribute real disk bytes that the // model never counts, under-sizing every estimate. using var connection = OpenWithSchema(); var columns = ReadStrings(connection, "SELECT name FROM pragma_table_info('site_events')"); Assert.True( columns.SequenceEqual( ["id", "timestamp", "event_type", "severity", "instance_id", "source", "message", "details"]), $"{RecalibrationNotice} Actual columns: {string.Join(", ", columns)}"); } /// Verifies that the table has exactly the four indexes the model charges for, plus the primary-key autoindex. [Fact] public void Indexes_AreExactlyTheFourTheModelCharges_PlusThePkAutoindex() { // IndexedColumnCount = 4 charges four secondary index entries per row; the TEXT // primary key's autoindex is absorbed into the empirically-measured fill factors. // A fifth secondary index would be silently under-counted. using var connection = OpenWithSchema(); var indexes = ReadStrings( connection, "SELECT name FROM pragma_index_list('site_events') ORDER BY name"); Assert.True( indexes.SequenceEqual( [ "idx_events_instance", "idx_events_severity", "idx_events_timestamp", "idx_events_type", "sqlite_autoindex_site_events_1", ]), $"{RecalibrationNotice} Actual indexes: {string.Join(", ", indexes)}"); } /// Verifies that each index covers exactly the column the sampler sums. /// The index name. /// The column the index is expected to cover. [Theory] [InlineData("idx_events_timestamp", "timestamp")] [InlineData("idx_events_type", "event_type")] [InlineData("idx_events_instance", "instance_id")] [InlineData("idx_events_severity", "severity")] public void EachIndex_CoversExactlyTheColumnTheSamplerSums(string indexName, string expectedColumn) { // The sampler's indexed_bytes expression sums octet_length(timestamp) + // octet_length(event_type) + octet_length(severity) + octet_length(instance_id) — // one column per index. Widening an index to a composite (or re-pointing it) would // change the real entry width without changing what is sampled. using var connection = OpenWithSchema(); var columns = ReadStrings( connection, $"SELECT name FROM pragma_index_info('{indexName}')"); Assert.True( columns.SequenceEqual([expectedColumn]), $"{RecalibrationNotice} Index {indexName} covers: {string.Join(", ", columns)}"); } }