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));
}
}