using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging.Abstractions; using ScadaLink.SiteRuntime.Persistence; namespace ScadaLink.SiteRuntime.Tests; /// /// Negative tests verifying design constraints. /// public class NegativeTests { [Fact] public async Task Schema_NoAlarmStateTable() { // Per design decision: no alarm state table in site SQLite schema. // The site SQLite stores only deployed configs and static attribute overrides. var storage = new SiteStorageService( "Data Source=:memory:", NullLogger.Instance); await storage.InitializeAsync(); // Try querying a non-existent alarm_states table — should throw await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); // Re-initialize on this connection to get the schema await using var initCmd = connection.CreateCommand(); initCmd.CommandText = @" CREATE TABLE IF NOT EXISTS deployed_configurations ( instance_unique_name TEXT PRIMARY KEY, config_json TEXT NOT NULL, deployment_id TEXT NOT NULL, revision_hash TEXT NOT NULL, is_enabled INTEGER NOT NULL DEFAULT 1, deployed_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS static_attribute_overrides ( instance_unique_name TEXT NOT NULL, attribute_name TEXT NOT NULL, override_value TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (instance_unique_name, attribute_name) );"; await initCmd.ExecuteNonQueryAsync(); // Verify alarm_states does NOT exist await using var checkCmd = connection.CreateCommand(); checkCmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='alarm_states'"; var result = await checkCmd.ExecuteScalarAsync(); Assert.Null(result); } [Fact] public async Task Schema_NoLocalConfigAuthoring() { // Per design: sites cannot author/modify template configurations locally. // The SQLite schema has no template tables or editing tables. await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); await using var initCmd = connection.CreateCommand(); initCmd.CommandText = @" CREATE TABLE IF NOT EXISTS deployed_configurations ( instance_unique_name TEXT PRIMARY KEY, config_json TEXT NOT NULL, deployment_id TEXT NOT NULL, revision_hash TEXT NOT NULL, is_enabled INTEGER NOT NULL DEFAULT 1, deployed_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS static_attribute_overrides ( instance_unique_name TEXT NOT NULL, attribute_name TEXT NOT NULL, override_value TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (instance_unique_name, attribute_name) );"; await initCmd.ExecuteNonQueryAsync(); // Verify no template editing tables exist await using var checkCmd = connection.CreateCommand(); checkCmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type='table'"; var tableCount = (long)(await checkCmd.ExecuteScalarAsync())!; // Only 2 tables in this manually-created schema (tests the constraint that // no template editing tables exist in the manually-created subset) Assert.Equal(2, tableCount); } [Fact] public void SiteNode_DoesNotBindHttpPorts() { // Per design: site nodes use Host.CreateDefaultBuilder (not WebApplication.CreateBuilder). // This is verified structurally — the site path in Program.cs does not configure Kestrel. // This test documents the constraint; the actual verification is in the Program.cs code. // The SiteRuntime project does not reference ASP.NET Core packages var siteRuntimeAssembly = typeof(SiteRuntimeOptions).Assembly; var referencedAssemblies = siteRuntimeAssembly.GetReferencedAssemblies(); Assert.DoesNotContain(referencedAssemblies, a => a.Name != null && a.Name.Contains("AspNetCore")); } }