using Microsoft.Data.Sqlite; namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging.Tests; /// /// Pins the two SQLite provider capabilities the storage cap's size model depends on /// (register row 81, docs/plans/2026-08-17-site-event-log-cap-semantics-decision.md). /// /// /// The intended fix for row 81 was to measure site_events' own pages exactly through the /// dbstat virtual table. These tests are the evidence that this is not possible with the /// native SQLite this solution ships, and the tripwire that will tell us if that ever changes — /// if dbstat appears, the modelled estimate in /// should be replaced by a real /// measurement. /// public class SqliteCapabilityTests { private static SqliteConnection OpenMemory() { var connection = new SqliteConnection("Data Source=:memory:"); connection.Open(); return connection; } [Fact] public void DbStat_IsNotAvailable_SoThePerTablePageCountMustBeModelled() { // If this test starts FAILING, that is good news, not a regression: the bundled // e_sqlite3 has gained SQLITE_ENABLE_DBSTAT_VTAB and the cap can measure // `SELECT sum(pgsize) FROM dbstat WHERE name='site_events'` exactly instead of // modelling it. Delete the model, not this expectation, if that day comes. using var connection = OpenMemory(); using var compileOptions = connection.CreateCommand(); compileOptions.CommandText = "PRAGMA compile_options"; var options = new List(); using (var reader = compileOptions.ExecuteReader()) { while (reader.Read()) options.Add(reader.GetString(0)); } Assert.DoesNotContain("ENABLE_DBSTAT_VTAB", options); using var modules = connection.CreateCommand(); modules.CommandText = "SELECT name FROM pragma_module_list"; var names = new List(); using (var reader = modules.ExecuteReader()) { while (reader.Read()) names.Add(reader.GetString(0)); } // The list is non-empty (fts3/4/5, rtree, geopoly …) so an empty result is not being // mistaken for "no dbstat". Assert.NotEmpty(names); Assert.DoesNotContain("dbstat", names); Assert.DoesNotContain("sqlite_dbpage", names); using var query = connection.CreateCommand(); query.CommandText = "SELECT sum(pgsize) FROM dbstat"; var ex = Assert.Throws(() => query.ExecuteScalar()); Assert.Contains("dbstat", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void OctetLength_IsAvailable_SoRowWidthIsSampledInBytesNotCharacters() { // The size model samples row width with octet_length() (SQLite 3.43+). length() counts // CHARACTERS, so a multi-byte UTF-8 message would be under-sized and the cap enforced // late. A provider downgrade that drops octet_length must fail here rather than // silently shrinking every estimate at runtime. using var connection = OpenMemory(); using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT octet_length('skål'), length('skål')"; using var reader = cmd.ExecuteReader(); Assert.True(reader.Read()); Assert.Equal(5, reader.GetInt32(0)); // 'å' is two bytes in UTF-8 Assert.Equal(4, reader.GetInt32(1)); } }